Update location permission UI.
This commit is contained in:
parent
ffc1463cda
commit
18e6c57e75
6 changed files with 263 additions and 12 deletions
|
@ -16,6 +16,7 @@ import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContract
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
import androidx.core.content.IntentCompat
|
import androidx.core.content.IntentCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.location.SignalPlace
|
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)) {
|
if (Permissions.hasAny(fragment.requireContext(), Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)) {
|
||||||
selectLocationLauncher.launch(chatColors)
|
selectLocationLauncher.launch(chatColors)
|
||||||
} else {
|
} else {
|
||||||
Permissions.with(fragment)
|
val dialog = MaterialAlertDialogBuilder(fragment.requireContext())
|
||||||
.request(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)
|
.setView(R.layout.permission_allow_location_dialog)
|
||||||
.ifNecessary()
|
.setPositiveButton(R.string.Permissions_continue) { _, _ ->
|
||||||
.withPermanentDenialDialog(fragment.getString(R.string.AttachmentManager_signal_requires_location_information_in_order_to_attach_a_location))
|
Permissions.with(fragment)
|
||||||
.onSomeGranted { selectLocationLauncher.launch(chatColors) }
|
.request(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||||
.execute()
|
.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.app.ActivityCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
import com.annimon.stream.function.Consumer;
|
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.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.util.BottomSheetUtil;
|
||||||
import org.thoughtcrime.securesms.util.LRUCache;
|
import org.thoughtcrime.securesms.util.LRUCache;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
|
|
||||||
|
@ -113,7 +115,11 @@ public class Permissions {
|
||||||
}
|
}
|
||||||
|
|
||||||
public PermissionsBuilder withPermanentDenialDialog(@NonNull String message, @Nullable Runnable onDialogDismissed) {
|
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) {
|
public PermissionsBuilder onAllGranted(Runnable allGrantedListener) {
|
||||||
|
@ -368,22 +374,34 @@ public class Permissions {
|
||||||
|
|
||||||
private static class SettingsDialogListener implements Runnable {
|
private static class SettingsDialogListener implements Runnable {
|
||||||
|
|
||||||
private final WeakReference<Context> context;
|
private final WeakReference<Context> context;
|
||||||
private final Runnable onDialogDismissed;
|
private final WeakReference<FragmentManager> fragmentManager;
|
||||||
private final String message;
|
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.message = message;
|
||||||
this.context = new WeakReference<>(context);
|
this.context = new WeakReference<>(context);
|
||||||
this.onDialogDismissed = onDialogDismissed;
|
this.onDialogDismissed = onDialogDismissed;
|
||||||
|
this.fragmentManager = new WeakReference<>(fragmentManager);
|
||||||
|
this.titleRes = titleRes;
|
||||||
|
this.detailsRes = detailsRes;
|
||||||
|
this.useBottomSheet = fragmentManager != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Context context = this.context.get();
|
Context context = this.context.get();
|
||||||
|
FragmentManager fragmentManager = this.fragmentManager.get();
|
||||||
|
|
||||||
if (context != null) {
|
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)
|
.setTitle(R.string.Permissions_permission_required)
|
||||||
.setMessage(message)
|
.setMessage(message)
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
|
@ -395,6 +413,7 @@ public class Permissions {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.show();
|
.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
7
app/src/main/res/drawable/ic_radio_button_checked.xml
Normal file
7
app/src/main/res/drawable/ic_radio_button_checked.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="25" android:viewportWidth="24" android:width="23.04dp">
|
||||||
|
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M12,12.801m-9,0a9,9 0,1 1,18 0a9,9 0,1 1,-18 0" android:strokeColor="#2C58C3" android:strokeWidth="2"/>
|
||||||
|
|
||||||
|
<path android:fillColor="#2C58C3" android:pathData="M12,12.801m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0" android:strokeColor="#2C58C3" android:strokeWidth="2"/>
|
||||||
|
|
||||||
|
</vector>
|
35
app/src/main/res/layout/permission_allow_location_dialog.xml
Normal file
35
app/src/main/res/layout/permission_allow_location_dialog.xml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="20dp"
|
||||||
|
tools:viewBindingIgnore="true">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
app:tint="@color/signal_colorOnPrimaryContainer"
|
||||||
|
app:srcCompat="@drawable/symbol_location_white_24" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/allow_location_title"
|
||||||
|
style="@style/Signal.Text.Title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/AttachmentManager_signal_allow_access_location" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/allow_location_body"
|
||||||
|
style="@style/Signal.Text.BodySmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:text="@string/AttachmentManager_signal_allow_signal_access_location" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -89,6 +89,16 @@
|
||||||
<string name="AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio">Signal requires the Storage permission in order to attach photos, videos, or audio, but it has been permanently denied. Please continue to the app settings menu, select \"Permissions\", and enable \"Storage\".</string>
|
<string name="AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio">Signal requires the Storage permission in order to attach photos, videos, or audio, but it has been permanently denied. Please continue to the app settings menu, select \"Permissions\", and enable \"Storage\".</string>
|
||||||
<string name="AttachmentManager_signal_requires_contacts_permission_in_order_to_attach_contact_information">Signal requires Contacts permission in order to attach contact information, but it has been permanently denied. Please continue to the app settings menu, select \"Permissions\", and enable \"Contacts\".</string>
|
<string name="AttachmentManager_signal_requires_contacts_permission_in_order_to_attach_contact_information">Signal requires Contacts permission in order to attach contact information, but it has been permanently denied. Please continue to the app settings menu, select \"Permissions\", and enable \"Contacts\".</string>
|
||||||
<string name="AttachmentManager_signal_requires_location_information_in_order_to_attach_a_location">Signal requires Location permission in order to attach a location, but it has been permanently denied. Please continue to the app settings menu, select \"Permissions\", and enable \"Location\".</string>
|
<string name="AttachmentManager_signal_requires_location_information_in_order_to_attach_a_location">Signal requires Location permission in order to attach a location, but it has been permanently denied. Please continue to the app settings menu, select \"Permissions\", and enable \"Location\".</string>
|
||||||
|
|
||||||
|
<!-- Dialog title asking users for location permission -->
|
||||||
|
<string name="AttachmentManager_signal_allow_access_location">Allow access to your location</string>
|
||||||
|
<!-- Dialog description that will explain the steps needed to give location permission -->
|
||||||
|
<string name="AttachmentManager_signal_to_send_location">To send your location:</string>
|
||||||
|
<!-- Alert dialog description asking for location permission -->
|
||||||
|
<string name="AttachmentManager_signal_allow_signal_access_location">Allow Signal access to send your location.</string>
|
||||||
|
<!-- Toast text explaining Signal's need for location access -->
|
||||||
|
<string name="AttachmentManager_signal_needs_location_access">Signal needs location access to send your location.</string>
|
||||||
|
|
||||||
<!-- Alert dialog title to show the recipient has not activated payments -->
|
<!-- Alert dialog title to show the recipient has not activated payments -->
|
||||||
<string name="AttachmentManager__not_activated_payments">%1$s hasn\'t activated Payments </string>
|
<string name="AttachmentManager__not_activated_payments">%1$s hasn\'t activated Payments </string>
|
||||||
<!-- Alert dialog description to send the recipient a request to activate payments -->
|
<!-- Alert dialog description to send the recipient a request to activate payments -->
|
||||||
|
@ -3437,6 +3447,14 @@
|
||||||
<!-- Storage permission row description -->
|
<!-- Storage permission row description -->
|
||||||
<string name="GrantPermissionsFragment__send_photos_videos_and_files">Send photos, videos and files from your device.</string>
|
<string name="GrantPermissionsFragment__send_photos_videos_and_files">Send photos, videos and files from your device.</string>
|
||||||
|
|
||||||
|
<!-- PermissionDeniedBottomSheet -->
|
||||||
|
<!-- Sheet describing step 1 on how to give permissions by opening settings -->
|
||||||
|
<string name="PermissionDeniedBottomSheet__1_tap_settings_below">1. Tap “Settings” below</string>
|
||||||
|
<!-- Sheet describing step 2 on how to give permissions by checking the permissions button in settings where %s will be replaced with an image of a checked button -->
|
||||||
|
<string name="PermissionDeniedBottomSheet__2_allow_permission">2. %s Allow the permission</string>
|
||||||
|
<!-- Label for button at the bottom of the sheet which opens the system permission settings -->
|
||||||
|
<string name="PermissionDeniedBottomSheet__settings">Settings</string>
|
||||||
|
|
||||||
<!-- PaymentsSecuritySetupFragment -->
|
<!-- PaymentsSecuritySetupFragment -->
|
||||||
<!-- Toolbar title -->
|
<!-- Toolbar title -->
|
||||||
<string name="PaymentsSecuritySetupFragment__security_setup">Security setup</string>
|
<string name="PaymentsSecuritySetupFragment__security_setup">Security setup</string>
|
||||||
|
|
Loading…
Add table
Reference in a new issue