Update location permission UI.

This commit is contained in:
mtang-signal
2024-04-26 09:52:25 -04:00
committed by Greyson Parrelli
parent ffc1463cda
commit 18e6c57e75
6 changed files with 263 additions and 12 deletions

View File

@@ -16,6 +16,7 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContract
import androidx.core.content.IntentCompat
import androidx.fragment.app.Fragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.location.SignalPlace
@@ -106,12 +107,23 @@ class ConversationActivityResultContracts(private val fragment: Fragment, privat
if (Permissions.hasAny(fragment.requireContext(), Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)) {
selectLocationLauncher.launch(chatColors)
} else {
Permissions.with(fragment)
.request(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)
.ifNecessary()
.withPermanentDenialDialog(fragment.getString(R.string.AttachmentManager_signal_requires_location_information_in_order_to_attach_a_location))
.onSomeGranted { selectLocationLauncher.launch(chatColors) }
.execute()
val dialog = MaterialAlertDialogBuilder(fragment.requireContext())
.setView(R.layout.permission_allow_location_dialog)
.setPositiveButton(R.string.Permissions_continue) { _, _ ->
Permissions.with(fragment)
.request(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)
.ifNecessary()
.withPermanentDenialDialog(fragment.getString(R.string.AttachmentManager_signal_requires_location_information_in_order_to_attach_a_location), null, R.string.AttachmentManager_signal_allow_access_location, R.string.AttachmentManager_signal_to_send_location, fragment.parentFragmentManager)
.onAnyDenied { Toast.makeText(fragment.requireContext(), R.string.AttachmentManager_signal_needs_location_access, Toast.LENGTH_LONG).show() }
.onSomeGranted { selectLocationLauncher.launch(chatColors) }
.execute()
}
.setNegativeButton(R.string.Permissions_not_now) { d, _ ->
Toast.makeText(fragment.requireContext(), R.string.AttachmentManager_signal_needs_location_access, Toast.LENGTH_LONG).show()
d.dismiss()
}
.create()
dialog.show()
}
}

View File

@@ -0,0 +1,160 @@
package org.thoughtcrime.securesms.permissions
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.os.bundleOf
import org.signal.core.ui.BottomSheets
import org.signal.core.ui.Buttons
import org.signal.core.ui.Previews
import org.signal.core.ui.SignalPreview
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
private const val PLACEHOLDER = "__RADIO_BUTTON_PLACEHOLDER__"
/**
* Bottom sheet shown when a permission has been previously denied
*
* Displays rationale for the need of a permission and how to grant it
*/
class PermissionDeniedBottomSheet private constructor() : ComposeBottomSheetDialogFragment() {
companion object {
private const val ARG_TITLE = "argument.title_res"
private const val ARG_SUBTITLE = "argument.subtitle_res"
@JvmStatic
fun showPermissionFragment(titleRes: Int, subtitleRes: Int): ComposeBottomSheetDialogFragment {
return PermissionDeniedBottomSheet().apply {
arguments = bundleOf(
ARG_TITLE to titleRes,
ARG_SUBTITLE to subtitleRes
)
}
}
}
@Composable
override fun SheetContent() {
PermissionDeniedSheetContent(
titleRes = remember { requireArguments().getInt(ARG_TITLE) },
subtitleRes = remember { requireArguments().getInt(ARG_SUBTITLE) },
onSettingsClicked = this::goToSettings
)
}
private fun goToSettings() {
requireContext().startActivity(Permissions.getApplicationSettingsIntent(requireContext()))
dismissAllowingStateLoss()
}
}
@SignalPreview
@Composable
private fun PermissionDeniedSheetContentPreview() {
Previews.BottomSheetPreview {
PermissionDeniedSheetContent(
titleRes = R.string.AttachmentManager_signal_allow_access_location,
subtitleRes = R.string.AttachmentManager_signal_to_send_location,
onSettingsClicked = {}
)
}
}
@Composable
private fun PermissionDeniedSheetContent(
titleRes: Int,
subtitleRes: Int,
onSettingsClicked: () -> Unit
) {
Column(
modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 12.dp, bottom = 32.dp)
) {
BottomSheets.Handle(
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Text(
text = stringResource(titleRes),
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Center,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(bottom = 12.dp, top = 20.dp)
)
Text(
text = stringResource(subtitleRes),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(bottom = 32.dp)
)
Text(
text = stringResource(R.string.PermissionDeniedBottomSheet__1_tap_settings_below),
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(bottom = 24.dp)
)
val step2String = stringResource(id = R.string.PermissionDeniedBottomSheet__2_allow_permission, PLACEHOLDER)
val (step2Text, step2InlineContent) = remember(step2String) {
val parts = step2String.split(PLACEHOLDER)
val annotatedString = buildAnnotatedString {
append(parts[0])
appendInlineContent("radio")
append(parts[1])
}
val inlineContentMap = mapOf(
"radio" to InlineTextContent(Placeholder(22.sp, 22.sp, PlaceholderVerticalAlign.Center)) {
Image(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_radio_button_checked),
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
}
)
annotatedString to inlineContentMap
}
Text(
text = step2Text,
inlineContent = step2InlineContent,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(bottom = 32.dp)
)
Buttons.LargeTonal(
onClick = onSettingsClicked,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.fillMaxWidth(1f)
) {
Text(text = stringResource(id = R.string.PermissionDeniedBottomSheet__settings))
}
}
}

View File

@@ -19,6 +19,7 @@ import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.annimon.stream.Stream;
import com.annimon.stream.function.Consumer;
@@ -26,6 +27,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.BottomSheetUtil;
import org.thoughtcrime.securesms.util.LRUCache;
import org.thoughtcrime.securesms.util.ServiceUtil;
@@ -113,7 +115,11 @@ public class Permissions {
}
public PermissionsBuilder withPermanentDenialDialog(@NonNull String message, @Nullable Runnable onDialogDismissed) {
return onAnyPermanentlyDenied(new SettingsDialogListener(permissionObject.getContext(), message, onDialogDismissed));
return withPermanentDenialDialog(message, onDialogDismissed, 0, 0, null);
}
public PermissionsBuilder withPermanentDenialDialog(@NonNull String message, @Nullable Runnable onDialogDismissed, int titleRes, int detailsRes, @Nullable FragmentManager fragmentManager) {
return onAnyPermanentlyDenied(new SettingsDialogListener(permissionObject.getContext(), message, onDialogDismissed, titleRes, detailsRes, fragmentManager));
}
public PermissionsBuilder onAllGranted(Runnable allGrantedListener) {
@@ -368,22 +374,34 @@ public class Permissions {
private static class SettingsDialogListener implements Runnable {
private final WeakReference<Context> context;
private final Runnable onDialogDismissed;
private final String message;
private final WeakReference<Context> context;
private final WeakReference<FragmentManager> fragmentManager;
private final Runnable onDialogDismissed;
private final String message;
private final int titleRes;
private final int detailsRes;
private final boolean useBottomSheet;
SettingsDialogListener(Context context, String message, @Nullable Runnable onDialogDismissed) {
SettingsDialogListener(Context context, String message, @Nullable Runnable onDialogDismissed, int titleRes, int detailsRes, @Nullable FragmentManager fragmentManager) {
this.message = message;
this.context = new WeakReference<>(context);
this.onDialogDismissed = onDialogDismissed;
this.fragmentManager = new WeakReference<>(fragmentManager);
this.titleRes = titleRes;
this.detailsRes = detailsRes;
this.useBottomSheet = fragmentManager != null;
}
@Override
public void run() {
Context context = this.context.get();
FragmentManager fragmentManager = this.fragmentManager.get();
if (context != null) {
new MaterialAlertDialogBuilder(context)
if (useBottomSheet && fragmentManager != null) {
PermissionDeniedBottomSheet.showPermissionFragment(titleRes, detailsRes).show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
} else if (!useBottomSheet){
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.Permissions_permission_required)
.setMessage(message)
.setCancelable(false)
@@ -395,6 +413,7 @@ public class Permissions {
}
})
.show();
}
}
}
}