mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-22 18:55:12 +00:00
Update location permission UI.
This commit is contained in:
committed by
Greyson Parrelli
parent
ffc1463cda
commit
18e6c57e75
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user