kopia lustrzana https://github.com/ryukoposting/Signal-Android
Prompt to setup payment bioauth, require to disable payment lock.
rodzic
f63ce79f16
commit
afe36b982f
|
@ -5,6 +5,7 @@ import android.content.Context
|
|||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
|
@ -13,7 +14,11 @@ import android.view.View
|
|||
import android.view.WindowManager
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.biometric.BiometricPrompt.PromptInfo
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.Navigation
|
||||
|
@ -25,6 +30,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||
import mobi.upod.timedurationpicker.TimeDurationPicker
|
||||
import mobi.upod.timedurationpicker.TimeDurationPickerDialog
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.BiometricDeviceAuthentication
|
||||
import org.thoughtcrime.securesms.BiometricDeviceLockContract
|
||||
import org.thoughtcrime.securesms.PassphraseChangeActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.ClickPreference
|
||||
|
@ -59,6 +66,8 @@ private val TAG = Log.tag(PrivacySettingsFragment::class.java)
|
|||
class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privacy) {
|
||||
|
||||
private lateinit var viewModel: PrivacySettingsViewModel
|
||||
private lateinit var biometricAuth: BiometricDeviceAuthentication
|
||||
private lateinit var biometricDeviceLockLauncher: ActivityResultLauncher<String>
|
||||
|
||||
private val incognitoSummary: CharSequence by lazy {
|
||||
SpannableStringBuilder(getString(R.string.preferences__this_setting_is_not_a_guarantee))
|
||||
|
@ -70,11 +79,35 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
|
|||
)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
biometricDeviceLockLauncher = registerForActivityResult(BiometricDeviceLockContract()) { result: Int ->
|
||||
if (result == BiometricDeviceAuthentication.AUTHENTICATED) {
|
||||
viewModel.togglePaymentLock(false)
|
||||
}
|
||||
}
|
||||
val promptInfo = PromptInfo.Builder()
|
||||
.setAllowedAuthenticators(BiometricDeviceAuthentication.ALLOWED_AUTHENTICATORS)
|
||||
.setTitle(requireContext().getString(R.string.BiometricDeviceAuthentication__signal))
|
||||
.setConfirmationRequired(false)
|
||||
.build()
|
||||
biometricAuth = BiometricDeviceAuthentication(
|
||||
BiometricManager.from(requireActivity()),
|
||||
BiometricPrompt(requireActivity(), BiometricAuthenticationListener()),
|
||||
promptInfo
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.refreshBlockedCount()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
biometricAuth.cancelAuthentication()
|
||||
}
|
||||
|
||||
override fun bindAdapter(adapter: MappingAdapter) {
|
||||
adapter.registerFactory(ValueClickPreference::class.java, LayoutFactory(::ValueClickPreferenceViewHolder, R.layout.value_click_preference_item))
|
||||
|
||||
|
@ -323,8 +356,10 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
|
|||
onClick = {
|
||||
if (!ServiceUtil.getKeyguardManager(requireContext()).isKeyguardSecure) {
|
||||
showGoToPhoneSettings()
|
||||
} else if (state.paymentLock) {
|
||||
biometricAuth.authenticate(requireContext(), true) { biometricDeviceLockLauncher?.launch(getString(R.string.BiometricDeviceAuthentication__signal)) }
|
||||
} else {
|
||||
viewModel.togglePaymentLock()
|
||||
viewModel.togglePaymentLock(true)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -512,4 +547,20 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
|
|||
valueText.text = model.value.resolve(context)
|
||||
}
|
||||
}
|
||||
|
||||
inner class BiometricAuthenticationListener : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationError(errorCode: Int, errorString: CharSequence) {
|
||||
Log.w(TAG, "Authentication error: $errorCode")
|
||||
onAuthenticationFailed()
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
Log.i(TAG, "onAuthenticationSucceeded")
|
||||
viewModel.togglePaymentLock(false)
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
Log.w(TAG, "Unable to authenticate payment lock")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,8 +74,8 @@ class PrivacySettingsViewModel(
|
|||
refresh()
|
||||
}
|
||||
|
||||
fun togglePaymentLock() {
|
||||
SignalStore.paymentsValues().paymentLock = state.value?.let { !it.paymentLock } ?: false
|
||||
fun togglePaymentLock(enable: Boolean) {
|
||||
SignalStore.paymentsValues().paymentLock = enable
|
||||
refresh()
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ internal class PaymentsValues internal constructor(store: KeyValueStore) : Signa
|
|||
const val MOB_PAYMENTS_ENABLED = "mob_payments_enabled"
|
||||
}
|
||||
|
||||
@get:JvmName("isPaymentLockEnabled")
|
||||
var paymentLock: Boolean by booleanValue(PAYMENT_LOCK_ENABLED, false)
|
||||
var paymentLockTimestamp: Long by longValue(PAYMENT_LOCK_TIMESTAMP, 0)
|
||||
var paymentLockSkipCount: Int by integerValue(PAYMENT_LOCK_SKIP_COUNT, 0)
|
||||
|
|
|
@ -34,7 +34,7 @@ object MediaIntentFactory {
|
|||
val initialCaption: String? = null,
|
||||
val leftIsRecent: Boolean = false,
|
||||
val hideAllMedia: Boolean = false,
|
||||
val showThread: Boolean= false,
|
||||
val showThread: Boolean = false,
|
||||
val sorting: Int,
|
||||
val isVideoGif: Boolean
|
||||
) : Parcelable
|
||||
|
|
|
@ -65,7 +65,7 @@ public class PaymentsRecoveryStartFragment extends Fragment {
|
|||
message.setText(getDescription(state));
|
||||
message.setLink(getString(R.string.PaymentsRecoveryStartFragment__learn_more__view));
|
||||
startButton.setOnClickListener(v -> {
|
||||
if (state == RecoveryPhraseStates.FROM_PAYMENTS_MENU_WITH_MNEMONIC_CONFIRMED && ServiceUtil.getKeyguardManager(requireContext()).isKeyguardSecure() && SignalStore.paymentsValues().getPaymentLock()) {
|
||||
if (state == RecoveryPhraseStates.FROM_PAYMENTS_MENU_WITH_MNEMONIC_CONFIRMED && ServiceUtil.getKeyguardManager(requireContext()).isKeyguardSecure() && SignalStore.paymentsValues().isPaymentLockEnabled()) {
|
||||
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo
|
||||
.Builder()
|
||||
.setAllowedAuthenticators(BiometricDeviceAuthentication.ALLOWED_AUTHENTICATORS)
|
||||
|
|
|
@ -211,7 +211,7 @@ public class ConfirmPaymentFragment extends BottomSheetDialogFragment {
|
|||
|
||||
|
||||
private boolean isPaymentLockEnabled(Context context) {
|
||||
return SignalStore.paymentsValues().getPaymentLock() && ServiceUtil.getKeyguardManager(context).isKeyguardSecure();
|
||||
return SignalStore.paymentsValues().isPaymentLockEnabled() && ServiceUtil.getKeyguardManager(context).isKeyguardSecure();
|
||||
}
|
||||
|
||||
private class Callbacks implements ConfirmPaymentAdapter.Callbacks {
|
||||
|
|
|
@ -222,6 +222,9 @@ public class PaymentsHomeFragment extends LoggingFragment {
|
|||
});
|
||||
break;
|
||||
case ACTIVATED:
|
||||
if (!SignalStore.paymentsValues().isPaymentLockEnabled()) {
|
||||
SafeNavigation.safeNavigate(NavHostFragment.findNavController(this), R.id.action_paymentsHome_to_securitySetup);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported event type: " + paymentStateEvent.name());
|
||||
|
|
|
@ -203,6 +203,7 @@ public class PaymentsHomeViewModel extends ViewModel {
|
|||
@Override
|
||||
public void onComplete(@Nullable Void result) {
|
||||
store.update(state -> state.updatePaymentsEnabled(PaymentsHomeState.PaymentsState.ACTIVATED));
|
||||
paymentStateEvents.postValue(PaymentStateEvent.ACTIVATED);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package org.thoughtcrime.securesms.payments.securitysetup
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.databinding.PaymentsSecuritySetupFragmentBinding
|
||||
import org.thoughtcrime.securesms.payments.preferences.PaymentsHomeFragmentDirections
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
/**
|
||||
* Fragment to let user know to enable payment lock to protect their funds
|
||||
*/
|
||||
class PaymentsSecuritySetupFragment : Fragment(R.layout.payments_security_setup_fragment) {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val binding = PaymentsSecuritySetupFragmentBinding.bind(view)
|
||||
|
||||
requireActivity().onBackPressedDispatcher.addCallback(
|
||||
viewLifecycleOwner,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
showSkipDialog()
|
||||
}
|
||||
}
|
||||
)
|
||||
binding.paymentsSecuritySetupEnableLock.setOnClickListener {
|
||||
findNavController().safeNavigate(PaymentsHomeFragmentDirections.actionPaymentsHomeToPrivacySettings(true))
|
||||
}
|
||||
binding.paymentsSecuritySetupFragmentNotNow.setOnClickListener { showSkipDialog() }
|
||||
binding.toolbar.setNavigationOnClickListener { showSkipDialog() }
|
||||
}
|
||||
|
||||
private fun showSkipDialog() {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(getString(R.string.PaymentsSecuritySetupFragment__skip_this_step))
|
||||
.setMessage(getString(R.string.PaymentsSecuritySetupFragment__skipping_this_step))
|
||||
.setPositiveButton(R.string.PaymentsSecuritySetupFragment__skip) { _, _ -> findNavController().popBackStack() }
|
||||
.setNegativeButton(R.string.PaymentsSecuritySetupFragment__cancel) { _, _ -> }
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="88dp"
|
||||
android:height="88dp"
|
||||
android:viewportWidth="88"
|
||||
android:viewportHeight="88">
|
||||
<path
|
||||
android:pathData="M44,44m-44,0a44,44 0,1 1,88 0a44,44 0,1 1,-88 0"
|
||||
android:fillColor="#FBFCFE"/>
|
||||
<path
|
||||
android:pathData="M44,44m-44,0a44,44 0,1 1,88 0a44,44 0,1 1,-88 0"
|
||||
android:fillColor="#50679F"
|
||||
android:fillAlpha="0.05"/>
|
||||
<path
|
||||
android:pathData="M55.73,39.699V33.125C55.73,30.014 54.495,27.03 52.295,24.83C50.095,22.63 47.111,21.395 44,21.395C40.889,21.395 37.905,22.63 35.705,24.83C33.505,27.03 32.27,30.014 32.27,33.125V39.699C31.154,39.855 30.132,40.408 29.391,41.256C28.65,42.104 28.239,43.191 28.234,44.318V61.921C28.238,63.162 28.733,64.351 29.611,65.229C30.488,66.107 31.678,66.602 32.919,66.605H55.081C56.322,66.602 57.512,66.107 58.389,65.229C59.267,64.351 59.762,63.162 59.766,61.921V44.318C59.761,43.191 59.35,42.104 58.609,41.256C57.868,40.408 56.846,39.855 55.73,39.699V39.699ZM44,23.932C46.437,23.935 48.774,24.904 50.498,26.628C52.221,28.351 53.19,30.688 53.193,33.125V39.633H34.807V33.125C34.81,30.688 35.779,28.351 37.502,26.628C39.226,24.904 41.563,23.935 44,23.932V23.932ZM57.228,61.922C57.228,62.492 57.002,63.037 56.599,63.44C56.196,63.843 55.65,64.069 55.081,64.07H32.919C32.35,64.069 31.804,63.843 31.401,63.44C30.998,63.037 30.772,62.492 30.772,61.922V44.318C30.772,43.748 30.999,43.202 31.402,42.8C31.804,42.397 32.35,42.171 32.919,42.17H55.081C55.65,42.171 56.196,42.397 56.598,42.8C57.001,43.202 57.228,43.748 57.228,44.318V61.922Z"
|
||||
android:fillColor="#2C58C3"/>
|
||||
</vector>
|
|
@ -0,0 +1,104 @@
|
|||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/signal_m3_toolbar_height"
|
||||
android:theme="?actionBarStyle"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navigationIcon="@drawable/ic_arrow_left_24"
|
||||
app:title="@string/PaymentsSecuritySetupFragment__security_setup"
|
||||
app:titleTextAppearance="@style/TextAppearance.Signal.Body1.Bold" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/payments_security_setup_fragment_image"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="72dp"
|
||||
android:layout_marginTop="162dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:importantForAccessibility="no"
|
||||
app:layout_constraintBottom_toTopOf="@id/payments_security_setup_fragment_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:srcCompat="@drawable/ic_payment_security_setup_lock" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/payments_security_setup_fragment_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="40dp"
|
||||
android:layout_marginEnd="40dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/PaymentsSecuritySetupFragment__protect_your_funds"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Title2"
|
||||
app:layout_constraintBottom_toTopOf="@id/payments_security_setup_fragment_message"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/payments_security_setup_fragment_image" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/payments_security_setup_fragment_message"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="40dp"
|
||||
android:layout_marginEnd="40dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="6sp"
|
||||
android:text="@string/PaymentsSecuritySetupFragment__help_prevent"
|
||||
android:textAppearance="@style/Signal.Text.BodyMedium"
|
||||
android:textColor="@color/signal_text_secondary"
|
||||
app:layout_constraintBottom_toTopOf="@id/payments_security_setup_enable_lock"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/payments_security_setup_fragment_title" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/payments_security_setup_enable_lock"
|
||||
style="@style/Signal.Widget.Button.Large.Primary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="48dp"
|
||||
android:layout_marginEnd="48dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:text="@string/PaymentsSecuritySetupFragment__enable_payment_lock"
|
||||
app:layout_constraintBottom_toTopOf="@id/payments_security_setup_fragment_not_now"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/payments_security_setup_fragment_not_now"
|
||||
style="@style/Signal.Widget.Button.Medium.Secondary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="48dp"
|
||||
android:layout_marginEnd="48dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/PaymentsSecuritySetupFragment__not_now"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
|
@ -112,6 +112,14 @@
|
|||
|
||||
</action>
|
||||
|
||||
<action
|
||||
android:id="@+id/action_paymentsHome_to_securitySetup"
|
||||
app:destination="@id/paymentsSecuritySetup"
|
||||
app:enterAnim="@anim/fragment_open_enter"
|
||||
app:exitAnim="@anim/fragment_open_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
@ -192,6 +200,28 @@
|
|||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/paymentsSecuritySetup"
|
||||
android:name="org.thoughtcrime.securesms.payments.securitysetup.PaymentsSecuritySetupFragment"
|
||||
tools:layout="@layout/payments_security_setup_fragment">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_paymentsHome_to_privacySettings"
|
||||
app:destination="@id/privacy_settings"
|
||||
app:enterAnim="@anim/fragment_open_enter"
|
||||
app:exitAnim="@anim/fragment_open_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit"
|
||||
app:popUpTo="@id/paymentsHome">
|
||||
|
||||
<argument
|
||||
android:name="show_payment_lock"
|
||||
app:argType="boolean" />
|
||||
|
||||
</action>
|
||||
|
||||
</fragment>
|
||||
|
||||
<action
|
||||
android:id="@+id/action_directly_to_createPayment"
|
||||
app:destination="@id/payments_create"
|
||||
|
|
|
@ -2909,6 +2909,26 @@
|
|||
<!-- Alert dialog button to not enable payment lock for now -->
|
||||
<string name="PaymentsHomeFragment__not_now">Not Now</string>
|
||||
|
||||
<!-- PaymentsSecuritySetupFragment -->
|
||||
<!-- Toolbar title -->
|
||||
<string name="PaymentsSecuritySetupFragment__security_setup">Security setup</string>
|
||||
<!-- Title to enable payment lock -->
|
||||
<string name="PaymentsSecuritySetupFragment__protect_your_funds">Protect your funds</string>
|
||||
<!-- Description as to why payment lock is required -->
|
||||
<string name="PaymentsSecuritySetupFragment__help_prevent">Help prevent a person with your phone from accessing your funds by adding another layer of security. You can disable this option in Settings.</string>
|
||||
<!-- Option to enable payment lock -->
|
||||
<string name="PaymentsSecuritySetupFragment__enable_payment_lock">Enable payment lock</string>
|
||||
<!-- Option to cancel -->
|
||||
<string name="PaymentsSecuritySetupFragment__not_now">Not now</string>
|
||||
<!-- Dialog title to confirm skipping the step -->
|
||||
<string name="PaymentsSecuritySetupFragment__skip_this_step">Skip this step?</string>
|
||||
<!-- Dialog description to let users know why payment lock is required -->
|
||||
<string name="PaymentsSecuritySetupFragment__skipping_this_step">Skipping this step could allow anyone who has physical access to your phone to transfer funds or view your recovery phrase.</string>
|
||||
<!-- Dialog option to cancel -->
|
||||
<string name="PaymentsSecuritySetupFragment__cancel">Cancel</string>
|
||||
<!-- Dialog option to skip -->
|
||||
<string name="PaymentsSecuritySetupFragment__skip">Skip</string>
|
||||
|
||||
<!-- PaymentsAddMoneyFragment -->
|
||||
<string name="PaymentsAddMoneyFragment__add_funds">Add funds</string>
|
||||
<string name="PaymentsAddMoneyFragment__your_wallet_address">Your Wallet Address</string>
|
||||
|
|
Ładowanie…
Reference in New Issue