kopia lustrzana https://github.com/ryukoposting/Signal-Android
Add new handling to encourage the user to save their wallet recovery phrase.
This only effects those who have opted in to payments and have a non-zero balance.fork-5.53.8
rodzic
c6bfdeb4b0
commit
e676f324f1
|
@ -38,6 +38,7 @@ internal class PaymentsValues internal constructor(store: KeyValueStore) : Signa
|
|||
private const val PAYMENTS_CURRENT_CURRENCY = "payments_current_currency"
|
||||
private const val DEFAULT_CURRENCY_CODE = "GBP"
|
||||
private const val USER_CONFIRMED_MNEMONIC = "mob_payments_user_confirmed_mnemonic"
|
||||
private const val USER_CONFIRMED_MNEMONIC_LARGE_BALANCE = "mob_payments_user_confirmed_mnemonic_large_balance"
|
||||
private const val SHOW_ABOUT_MOBILE_COIN_INFO_CARD = "mob_payments_show_about_mobile_coin_info_card"
|
||||
private const val SHOW_ADDING_TO_YOUR_WALLET_INFO_CARD = "mob_payments_show_adding_to_your_wallet_info_card"
|
||||
private const val SHOW_CASHING_OUT_INFO_CARD = "mob_payments_show_cashing_out_info_card"
|
||||
|
@ -46,6 +47,7 @@ internal class PaymentsValues internal constructor(store: KeyValueStore) : Signa
|
|||
private const val PAYMENT_LOCK_ENABLED = "mob_payments_payment_lock_enabled"
|
||||
private const val PAYMENT_LOCK_TIMESTAMP = "mob_payments_payment_lock_timestamp"
|
||||
private const val PAYMENT_LOCK_SKIP_COUNT = "mob_payments_payment_lock_skip_count"
|
||||
private const val SHOW_SAVE_RECOVERY_PHRASE = "mob_show_save_recovery_phrase"
|
||||
|
||||
private val LARGE_BALANCE_THRESHOLD = Money.mobileCoin(BigDecimal.valueOf(500))
|
||||
|
||||
|
@ -53,18 +55,17 @@ internal class PaymentsValues internal constructor(store: KeyValueStore) : Signa
|
|||
const val MOB_PAYMENTS_ENABLED = "mob_payments_enabled"
|
||||
}
|
||||
|
||||
var paymentLock
|
||||
get() = getBoolean(PAYMENT_LOCK_ENABLED, false)
|
||||
set(enabled) = putBoolean(PAYMENT_LOCK_ENABLED, enabled)
|
||||
|
||||
var paymentLockTimestamp
|
||||
get() = getLong(PAYMENT_LOCK_TIMESTAMP, 0)
|
||||
set(timestamp) = putLong(PAYMENT_LOCK_TIMESTAMP, timestamp)
|
||||
|
||||
var paymentLockSkipCount
|
||||
get() = getInteger(PAYMENT_LOCK_SKIP_COUNT, 0)
|
||||
set(count) = putInteger(PAYMENT_LOCK_SKIP_COUNT, count)
|
||||
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)
|
||||
var showSaveRecoveryPhrase: Boolean by booleanValue(SHOW_SAVE_RECOVERY_PHRASE, true)
|
||||
var userConfirmedMnemonic
|
||||
get() = getBoolean(USER_CONFIRMED_MNEMONIC, false)
|
||||
private set(value) = putBoolean(USER_CONFIRMED_MNEMONIC, value)
|
||||
|
||||
private var userConfirmedMnemonicLargeBalance
|
||||
get() = getBoolean(USER_CONFIRMED_MNEMONIC_LARGE_BALANCE, false)
|
||||
set(value) = putBoolean(USER_CONFIRMED_MNEMONIC_LARGE_BALANCE, value)
|
||||
private val liveCurrentCurrency: MutableLiveData<Currency> by lazy { MutableLiveData(currentCurrency()) }
|
||||
private val liveMobileCoinLedger: MutableLiveData<MobileCoinLedgerWrapper> by lazy { MutableLiveData(mobileCoinLatestFullLedger()) }
|
||||
private val liveMobileCoinBalance: LiveData<Balance> by lazy { Transformations.map(liveMobileCoinLedger) { obj: MobileCoinLedgerWrapper -> obj.balance } }
|
||||
|
@ -79,20 +80,25 @@ internal class PaymentsValues internal constructor(store: KeyValueStore) : Signa
|
|||
PAYMENTS_CURRENT_CURRENCY,
|
||||
DEFAULT_CURRENCY_CODE,
|
||||
USER_CONFIRMED_MNEMONIC,
|
||||
USER_CONFIRMED_MNEMONIC_LARGE_BALANCE,
|
||||
SHOW_ABOUT_MOBILE_COIN_INFO_CARD,
|
||||
SHOW_ADDING_TO_YOUR_WALLET_INFO_CARD,
|
||||
SHOW_CASHING_OUT_INFO_CARD,
|
||||
SHOW_RECOVERY_PHRASE_INFO_CARD,
|
||||
SHOW_UPDATE_PIN_INFO_CARD
|
||||
SHOW_UPDATE_PIN_INFO_CARD,
|
||||
PAYMENT_LOCK_ENABLED,
|
||||
PAYMENT_LOCK_TIMESTAMP,
|
||||
PAYMENT_LOCK_SKIP_COUNT,
|
||||
SHOW_SAVE_RECOVERY_PHRASE
|
||||
)
|
||||
}
|
||||
|
||||
fun userConfirmedMnemonic(): Boolean {
|
||||
return store.getBoolean(USER_CONFIRMED_MNEMONIC, false)
|
||||
}
|
||||
|
||||
fun setUserConfirmedMnemonic(userConfirmedMnemonic: Boolean) {
|
||||
store.beginWrite().putBoolean(USER_CONFIRMED_MNEMONIC, userConfirmedMnemonic).commit()
|
||||
fun confirmMnemonic(confirmed: Boolean) {
|
||||
if (userHasLargeBalance()) {
|
||||
userConfirmedMnemonicLargeBalance = confirmed
|
||||
} else {
|
||||
userConfirmedMnemonic = confirmed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -219,11 +225,11 @@ internal class PaymentsValues internal constructor(store: KeyValueStore) : Signa
|
|||
return store.getBoolean(SHOW_CASHING_OUT_INFO_CARD, true)
|
||||
}
|
||||
|
||||
fun showRecoveryPhraseInfoCard(): Boolean {
|
||||
fun isMnemonicConfirmed(): Boolean {
|
||||
return if (userHasLargeBalance()) {
|
||||
store.getBoolean(SHOW_CASHING_OUT_INFO_CARD, true)
|
||||
userConfirmedMnemonicLargeBalance
|
||||
} else {
|
||||
false
|
||||
userConfirmedMnemonic
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,7 +329,7 @@ internal class PaymentsValues internal constructor(store: KeyValueStore) : Signa
|
|||
val existingEntropy = paymentsEntropy.bytes
|
||||
if (Arrays.equals(existingEntropy, entropyFromMnemonic)) {
|
||||
setMobileCoinPaymentsEnabled(true)
|
||||
setUserConfirmedMnemonic(true)
|
||||
confirmMnemonic(true)
|
||||
return WalletRestoreResult.ENTROPY_UNCHANGED
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,20 +5,35 @@ import android.view.View;
|
|||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.biometric.BiometricManager;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.BiometricDeviceAuthentication;
|
||||
import org.thoughtcrime.securesms.BiometricDeviceLockContract;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.navigation.SafeNavigation;
|
||||
import org.thoughtcrime.securesms.util.views.LearnMoreTextView;
|
||||
import org.whispersystems.signalservice.api.payments.PaymentsConstants;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
public class PaymentsRecoveryStartFragment extends Fragment {
|
||||
|
||||
private final OnBackPressed onBackPressed = new OnBackPressed();
|
||||
private static final String TAG = Log.tag(PaymentsRecoveryStartFragment.class);
|
||||
|
||||
private ActivityResultLauncher<String> activityResultLauncher;
|
||||
private boolean finishOnConfirm;
|
||||
|
||||
public PaymentsRecoveryStartFragment() {
|
||||
super(R.layout.payments_recovery_start_fragment);
|
||||
|
@ -26,13 +41,16 @@ public class PaymentsRecoveryStartFragment extends Fragment {
|
|||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
Toolbar toolbar = view.findViewById(R.id.payments_recovery_start_fragment_toolbar);
|
||||
TextView title = view.findViewById(R.id.payments_recovery_start_fragment_title);
|
||||
LearnMoreTextView message = view.findViewById(R.id.payments_recovery_start_fragment_message);
|
||||
TextView startButton = view.findViewById(R.id.payments_recovery_start_fragment_start);
|
||||
TextView pasteButton = view.findViewById(R.id.payments_recovery_start_fragment_paste);
|
||||
Toolbar toolbar = view.findViewById(R.id.payments_recovery_start_fragment_toolbar);
|
||||
TextView title = view.findViewById(R.id.payments_recovery_start_fragment_title);
|
||||
LearnMoreTextView message = view.findViewById(R.id.payments_recovery_start_fragment_message);
|
||||
TextView startButton = view.findViewById(R.id.payments_recovery_start_fragment_start);
|
||||
TextView pasteButton = view.findViewById(R.id.payments_recovery_start_fragment_paste);
|
||||
PaymentsRecoveryStartFragmentArgs args = PaymentsRecoveryStartFragmentArgs.fromBundle(requireArguments());
|
||||
RecoveryPhraseStates state = args.getRecoveryPhraseState();
|
||||
OnBackPressed onBackPressed = new OnBackPressed(state);
|
||||
|
||||
PaymentsRecoveryStartFragmentArgs args = PaymentsRecoveryStartFragmentArgs.fromBundle(requireArguments());
|
||||
finishOnConfirm = args.getFinishOnConfirm();
|
||||
|
||||
if (args.getIsRestore()) {
|
||||
title.setText(R.string.PaymentsRecoveryStartFragment__enter_recovery_phrase);
|
||||
|
@ -43,38 +61,138 @@ public class PaymentsRecoveryStartFragment extends Fragment {
|
|||
pasteButton.setVisibility(View.VISIBLE);
|
||||
pasteButton.setOnClickListener(v -> SafeNavigation.safeNavigate(Navigation.findNavController(v), PaymentsRecoveryStartFragmentDirections.actionPaymentsRecoveryStartToPaymentsRecoveryPaste()));
|
||||
} else {
|
||||
title.setText(R.string.PaymentsRecoveryStartFragment__view_recovery_phrase);
|
||||
message.setText(getString(R.string.PaymentsRecoveryStartFragment__your_balance_will_automatically_restore, PaymentsConstants.MNEMONIC_LENGTH));
|
||||
title.setText(getTitle(state));
|
||||
message.setText(getDescription(state));
|
||||
message.setLink(getString(R.string.PaymentsRecoveryStartFragment__learn_more__view));
|
||||
startButton.setOnClickListener(v -> SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), PaymentsRecoveryStartFragmentDirections.actionPaymentsRecoveryStartToPaymentsRecoveryPhrase(args.getFinishOnConfirm())));
|
||||
startButton.setOnClickListener(v -> {
|
||||
if (state == RecoveryPhraseStates.FROM_PAYMENTS_MENU_WITH_MNEMONIC_CONFIRMED && ServiceUtil.getKeyguardManager(requireContext()).isKeyguardSecure() && SignalStore.paymentsValues().getPaymentLock()) {
|
||||
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo
|
||||
.Builder()
|
||||
.setAllowedAuthenticators(BiometricDeviceAuthentication.ALLOWED_AUTHENTICATORS)
|
||||
.setTitle(requireContext().getString(R.string.BiometricDeviceAuthentication__signal))
|
||||
.setConfirmationRequired(false)
|
||||
.build();
|
||||
BiometricDeviceAuthentication biometricAuth = new BiometricDeviceAuthentication(BiometricManager.from(requireActivity()),
|
||||
new BiometricPrompt(requireActivity(), new BiometricAuthenticationListener()),
|
||||
promptInfo);
|
||||
biometricAuth.authenticate(requireContext(), true, this::showConfirmDeviceCredentialIntent);
|
||||
} else {
|
||||
goToRecoveryPhrase();
|
||||
}
|
||||
});
|
||||
startButton.setText(R.string.PaymentsRecoveryStartFragment__start);
|
||||
pasteButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
toolbar.setNavigationOnClickListener(v -> {
|
||||
if (args.getFinishOnConfirm()) {
|
||||
requireActivity().finish();
|
||||
} else {
|
||||
Navigation.findNavController(requireView()).popBackStack();
|
||||
message.setLearnMoreVisible(true);
|
||||
toolbar.setNavigationOnClickListener(v -> onBackPressed(state));
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), onBackPressed);
|
||||
activityResultLauncher = registerForActivityResult(new BiometricDeviceLockContract(), result -> {
|
||||
if (result == BiometricDeviceAuthentication.AUTHENTICATED) {
|
||||
goToRecoveryPhrase();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (args.getFinishOnConfirm()) {
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), onBackPressed);
|
||||
private Unit showConfirmDeviceCredentialIntent() {
|
||||
activityResultLauncher.launch(getString(R.string.BiometricDeviceAuthentication__signal));
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
private String getTitle(RecoveryPhraseStates state) {
|
||||
String title;
|
||||
|
||||
switch (state) {
|
||||
case FROM_PAYMENTS_MENU_WITH_MNEMONIC_NOT_CONFIRMED:
|
||||
case FROM_INFO_CARD_WITH_MNEMONIC_NOT_CONFIRMED:
|
||||
case FIRST_TIME_NON_ZERO_BALANCE_WITH_MNEMONIC_NOT_CONFIRMED:
|
||||
title = getString(R.string.PaymentsRecoveryStartFragment__save_recovery_phrase);
|
||||
break;
|
||||
default:
|
||||
title = getString(R.string.PaymentsRecoveryStartFragment__view_recovery_phrase);
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
message.setLearnMoreVisible(true);
|
||||
private String getDescription(RecoveryPhraseStates state) {
|
||||
String description;
|
||||
|
||||
switch (state) {
|
||||
case FROM_INFO_CARD_WITH_MNEMONIC_NOT_CONFIRMED:
|
||||
description = getString(R.string.PaymentsRecoveryStartFragment__time_to_save);
|
||||
break;
|
||||
case FIRST_TIME_NON_ZERO_BALANCE_WITH_MNEMONIC_NOT_CONFIRMED:
|
||||
description = getString(R.string.PaymentsRecoveryStartFragment__got_balance);
|
||||
break;
|
||||
default:
|
||||
description = getString(R.string.PaymentsRecoveryStartFragment__your_balance_will_automatically_restore, PaymentsConstants.MNEMONIC_LENGTH);
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
private void goToRecoveryPhrase() {
|
||||
PaymentsRecoveryStartFragmentArgs args = PaymentsRecoveryStartFragmentArgs.fromBundle(requireArguments());
|
||||
SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), PaymentsRecoveryStartFragmentDirections.actionPaymentsRecoveryStartToPaymentsRecoveryPhrase(args.getFinishOnConfirm()));
|
||||
}
|
||||
|
||||
private void onBackPressed(RecoveryPhraseStates state) {
|
||||
if (state == RecoveryPhraseStates.FIRST_TIME_NON_ZERO_BALANCE_WITH_MNEMONIC_NOT_CONFIRMED ||
|
||||
state == RecoveryPhraseStates.FROM_INFO_CARD_WITH_MNEMONIC_NOT_CONFIRMED)
|
||||
{
|
||||
showSkipRecoveryDialog();
|
||||
} else {
|
||||
goBack();
|
||||
}
|
||||
}
|
||||
|
||||
private void goBack() {
|
||||
if (finishOnConfirm) {
|
||||
requireActivity().finish();
|
||||
} else {
|
||||
Navigation.findNavController(requireView()).popBackStack();
|
||||
}
|
||||
}
|
||||
|
||||
private void showSkipRecoveryDialog() {
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.PaymentsRecoveryStartFragment__continue_without_saving)
|
||||
.setMessage(R.string.PaymentsRecoveryStartFragment__your_recovery_phrase)
|
||||
.setPositiveButton(R.string.PaymentsRecoveryStartFragment__skip_recovery_phrase, (d, w) -> goBack())
|
||||
.setNegativeButton(R.string.PaymentsRecoveryStartFragment__cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private class OnBackPressed extends OnBackPressedCallback {
|
||||
RecoveryPhraseStates state;
|
||||
|
||||
public OnBackPressed() {
|
||||
public OnBackPressed(RecoveryPhraseStates state) {
|
||||
super(true);
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
requireActivity().finish();
|
||||
onBackPressed(state);
|
||||
}
|
||||
}
|
||||
|
||||
private class BiometricAuthenticationListener extends BiometricPrompt.AuthenticationCallback {
|
||||
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, @NonNull CharSequence errorString) {
|
||||
Log.w(TAG, "Authentication error: " + errorCode);
|
||||
onAuthenticationFailed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
Log.i(TAG, "onAuthenticationSucceeded");
|
||||
goToRecoveryPhrase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
Log.w(TAG, "Unable to authenticate payment lock");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package org.thoughtcrime.securesms.payments.backup;
|
||||
|
||||
public enum RecoveryPhraseStates {
|
||||
FROM_PAYMENTS_MENU_WITH_MNEMONIC_CONFIRMED,
|
||||
FROM_PAYMENTS_MENU_WITH_MNEMONIC_NOT_CONFIRMED,
|
||||
FROM_INFO_CARD_WITH_MNEMONIC_NOT_CONFIRMED,
|
||||
FIRST_TIME_NON_ZERO_BALANCE_WITH_MNEMONIC_NOT_CONFIRMED,
|
||||
NONE
|
||||
}
|
|
@ -30,10 +30,12 @@ public class PaymentsRecoveryPhraseConfirmFragment extends Fragment {
|
|||
/**
|
||||
* The minimum number of characters required to show an error mark.
|
||||
*/
|
||||
private static final int ERROR_THRESHOLD = 1;
|
||||
private static final int ERROR_THRESHOLD = 1;
|
||||
public static final String RECOVERY_PHRASE_CONFIRMED = "recovery_phrase_confirmed";
|
||||
public static final String REQUEST_KEY_RECOVERY_PHRASE = "org.thoughtcrime.securesms.payments.backup.confirm.RECOVERY_PHRASE";
|
||||
|
||||
private Drawable validWordCheckMark;
|
||||
private Drawable invalidWordX;
|
||||
private Drawable validWordCheckMark;
|
||||
private Drawable invalidWordX;
|
||||
|
||||
public PaymentsRecoveryPhraseConfirmFragment() {
|
||||
super(R.layout.payments_recovery_phrase_confirm_fragment);
|
||||
|
@ -41,13 +43,13 @@ public class PaymentsRecoveryPhraseConfirmFragment extends Fragment {
|
|||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
Toolbar toolbar = view.findViewById(R.id.payments_recovery_phrase_confirm_fragment_toolbar);
|
||||
EditText word1 = view.findViewById(R.id.payments_recovery_phrase_confirm_fragment_word_1);
|
||||
EditText word2 = view.findViewById(R.id.payments_recovery_phrase_confirm_fragment_word_2);
|
||||
View seePhraseAgain = view.findViewById(R.id.payments_recovery_phrase_confirm_fragment_see_again);
|
||||
View done = view.findViewById(R.id.payments_recovery_phrase_confirm_fragment_done);
|
||||
TextInputLayout wordWrapper1 = view.findViewById(R.id.payments_recovery_phrase_confirm_fragment_word1_wrapper);
|
||||
TextInputLayout wordWrapper2 = view.findViewById(R.id.payments_recovery_phrase_confirm_fragment_word2_wrapper);
|
||||
Toolbar toolbar = view.findViewById(R.id.payments_recovery_phrase_confirm_fragment_toolbar);
|
||||
EditText word1 = view.findViewById(R.id.payments_recovery_phrase_confirm_fragment_word_1);
|
||||
EditText word2 = view.findViewById(R.id.payments_recovery_phrase_confirm_fragment_word_2);
|
||||
View seePhraseAgain = view.findViewById(R.id.payments_recovery_phrase_confirm_fragment_see_again);
|
||||
View done = view.findViewById(R.id.payments_recovery_phrase_confirm_fragment_done);
|
||||
TextInputLayout wordWrapper1 = view.findViewById(R.id.payments_recovery_phrase_confirm_fragment_word1_wrapper);
|
||||
TextInputLayout wordWrapper2 = view.findViewById(R.id.payments_recovery_phrase_confirm_fragment_word2_wrapper);
|
||||
|
||||
PaymentsRecoveryPhraseConfirmFragmentArgs args = PaymentsRecoveryPhraseConfirmFragmentArgs.fromBundle(requireArguments());
|
||||
|
||||
|
@ -65,7 +67,7 @@ public class PaymentsRecoveryPhraseConfirmFragment extends Fragment {
|
|||
word2.addTextChangedListener(new AfterTextChanged(e -> viewModel.validateWord2(e.toString())));
|
||||
seePhraseAgain.setOnClickListener(v -> Navigation.findNavController(requireView()).popBackStack());
|
||||
done.setOnClickListener(v -> {
|
||||
SignalStore.paymentsValues().setUserConfirmedMnemonic(true);
|
||||
SignalStore.paymentsValues().confirmMnemonic(true);
|
||||
ViewUtil.hideKeyboard(requireContext(), view);
|
||||
Toast.makeText(requireContext(), R.string.PaymentRecoveryPhraseConfirmFragment__recovery_phrase_confirmed, Toast.LENGTH_SHORT).show();
|
||||
|
||||
|
@ -73,6 +75,9 @@ public class PaymentsRecoveryPhraseConfirmFragment extends Fragment {
|
|||
requireActivity().setResult(Activity.RESULT_OK);
|
||||
requireActivity().finish();
|
||||
} else {
|
||||
Bundle result = new Bundle();
|
||||
result.putBoolean(RECOVERY_PHRASE_CONFIRMED, true);
|
||||
getParentFragmentManager().setFragmentResult(REQUEST_KEY_RECOVERY_PHRASE, result);
|
||||
Navigation.findNavController(view).popBackStack(R.id.paymentsHome, false);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -83,7 +83,7 @@ public class PaymentsRecoveryPhraseFragment extends Fragment {
|
|||
if (args.getFinishOnConfirm()) {
|
||||
requireActivity().finish();
|
||||
} else {
|
||||
toolbar.setNavigationOnClickListener(t -> Navigation.findNavController(view).popBackStack(R.id.paymentsHome, false));
|
||||
Navigation.findNavController(view).popBackStack(R.id.paymentsHome, false);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ public class ConfirmPaymentFragment extends BottomSheetDialogFragment {
|
|||
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo
|
||||
.Builder()
|
||||
.setAllowedAuthenticators(BiometricDeviceAuthentication.ALLOWED_AUTHENTICATORS)
|
||||
.setTitle(requireContext().getString(R.string.ConfirmPaymentFragment__unlock_to_send_payment))
|
||||
.setTitle(requireContext().getString(R.string.BiometricDeviceAuthentication__signal))
|
||||
.setConfirmationRequired(false)
|
||||
.build();
|
||||
biometricAuth = new BiometricDeviceAuthentication(BiometricManager.from(requireActivity()),
|
||||
|
@ -239,7 +239,7 @@ public class ConfirmPaymentFragment extends BottomSheetDialogFragment {
|
|||
}
|
||||
|
||||
public Unit showConfirmDeviceCredentialIntent() {
|
||||
activityResultLauncher.launch(getString(R.string.ConfirmPaymentFragment__unlock_to_send_payment));
|
||||
activityResultLauncher.launch(getString(R.string.BiometricDeviceAuthentication__signal));
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ public class PaymentsHomeAdapter extends MappingAdapter {
|
|||
default void onRestorePaymentsAccount() {}
|
||||
default void onSeeAll(@NonNull PaymentType paymentType) {}
|
||||
default void onPaymentItem(@NonNull PaymentItem model) {}
|
||||
default void onInfoCardDismissed() {}
|
||||
default void onInfoCardDismissed(InfoCard.Type type) {}
|
||||
default void onViewRecoveryPhrase() {}
|
||||
default void onUpdatePin() {}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package org.thoughtcrime.securesms.payments.preferences;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
@ -33,6 +32,9 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
|||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.payments.FiatMoneyUtil;
|
||||
import org.thoughtcrime.securesms.payments.MoneyView;
|
||||
import org.thoughtcrime.securesms.payments.backup.RecoveryPhraseStates;
|
||||
import org.thoughtcrime.securesms.payments.backup.confirm.PaymentsRecoveryPhraseConfirmFragment;
|
||||
import org.thoughtcrime.securesms.payments.preferences.model.InfoCard;
|
||||
import org.thoughtcrime.securesms.payments.preferences.model.PaymentItem;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.SpanUtil;
|
||||
|
@ -121,6 +123,12 @@ public class PaymentsHomeFragment extends LoggingFragment {
|
|||
|
||||
viewModel = new ViewModelProvider(this, new PaymentsHomeViewModel.Factory()).get(PaymentsHomeViewModel.class);
|
||||
|
||||
getParentFragmentManager().setFragmentResultListener(PaymentsRecoveryPhraseConfirmFragment.REQUEST_KEY_RECOVERY_PHRASE, this, (requestKey, result) -> {
|
||||
if (result.getBoolean(PaymentsRecoveryPhraseConfirmFragment.RECOVERY_PHRASE_CONFIRMED)) {
|
||||
viewModel.updateStore();
|
||||
}
|
||||
});
|
||||
|
||||
viewModel.getList().observe(getViewLifecycleOwner(), list -> {
|
||||
boolean hadPaymentItems = Stream.of(adapter.getCurrentList()).anyMatch(model -> model instanceof PaymentItem);
|
||||
|
||||
|
@ -139,7 +147,17 @@ public class PaymentsHomeFragment extends LoggingFragment {
|
|||
}
|
||||
header.setVisibility(enabled ? View.VISIBLE : View.GONE);
|
||||
});
|
||||
viewModel.getBalance().observe(getViewLifecycleOwner(), balance::setMoney);
|
||||
|
||||
viewModel.getBalance().observe(getViewLifecycleOwner(), balanceAmount -> {
|
||||
balance.setMoney(balanceAmount);
|
||||
if (SignalStore.paymentsValues().getShowSaveRecoveryPhrase() &&
|
||||
!SignalStore.paymentsValues().getUserConfirmedMnemonic() &&
|
||||
!balanceAmount.isEqualOrLessThanZero()) {
|
||||
SafeNavigation.safeNavigate(NavHostFragment.findNavController(this), PaymentsHomeFragmentDirections.actionPaymentsHomeToPaymentsBackup().setRecoveryPhraseState(RecoveryPhraseStates.FIRST_TIME_NON_ZERO_BALANCE_WITH_MNEMONIC_NOT_CONFIRMED));
|
||||
SignalStore.paymentsValues().setShowSaveRecoveryPhrase(false);
|
||||
}
|
||||
});
|
||||
|
||||
viewModel.getExchange().observe(getViewLifecycleOwner(), amount -> {
|
||||
if (amount != null) {
|
||||
exchange.setText(FiatMoneyUtil.format(getResources(), amount));
|
||||
|
@ -251,7 +269,10 @@ public class PaymentsHomeFragment extends LoggingFragment {
|
|||
viewModel.deactivatePayments();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.payments_home_fragment_menu_view_recovery_phrase) {
|
||||
SafeNavigation.safeNavigate(NavHostFragment.findNavController(this), R.id.action_paymentsHome_to_paymentsBackup);
|
||||
SafeNavigation.safeNavigate(NavHostFragment.findNavController(this),
|
||||
PaymentsHomeFragmentDirections.actionPaymentsHomeToPaymentsBackup().setRecoveryPhraseState(SignalStore.paymentsValues().isMnemonicConfirmed() ?
|
||||
RecoveryPhraseStates.FROM_PAYMENTS_MENU_WITH_MNEMONIC_CONFIRMED :
|
||||
RecoveryPhraseStates.FROM_PAYMENTS_MENU_WITH_MNEMONIC_NOT_CONFIRMED));
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.payments_home_fragment_menu_help) {
|
||||
startActivity(AppSettingsActivity.help(requireContext(), HelpFragment.PAYMENT_INDEX));
|
||||
|
@ -303,8 +324,11 @@ public class PaymentsHomeFragment extends LoggingFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onInfoCardDismissed() {
|
||||
viewModel.onInfoCardDismissed();
|
||||
public void onInfoCardDismissed(InfoCard.Type type) {
|
||||
viewModel.updateStore();
|
||||
if (type == InfoCard.Type.RECORD_RECOVERY_PHASE) {
|
||||
showSaveRecoveryPhrase();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -314,7 +338,12 @@ public class PaymentsHomeFragment extends LoggingFragment {
|
|||
|
||||
@Override
|
||||
public void onViewRecoveryPhrase() {
|
||||
SafeNavigation.safeNavigate(NavHostFragment.findNavController(PaymentsHomeFragment.this), R.id.action_paymentsHome_to_paymentsBackup);
|
||||
showSaveRecoveryPhrase();
|
||||
}
|
||||
|
||||
private void showSaveRecoveryPhrase() {
|
||||
SafeNavigation.safeNavigate(NavHostFragment.findNavController(PaymentsHomeFragment.this),
|
||||
PaymentsHomeFragmentDirections.actionPaymentsHomeToPaymentsBackup().setRecoveryPhraseState(RecoveryPhraseStates.FROM_INFO_CARD_WITH_MNEMONIC_NOT_CONFIRMED));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -188,7 +188,7 @@ public class PaymentsHomeViewModel extends ViewModel {
|
|||
return state.updatePayments(paymentItems, payments.size());
|
||||
}
|
||||
|
||||
public void onInfoCardDismissed() {
|
||||
public void updateStore() {
|
||||
store.update(s -> s);
|
||||
}
|
||||
|
||||
|
|
|
@ -78,10 +78,10 @@ public class InfoCard implements MappingModel<InfoCard> {
|
|||
List<InfoCard> infoCards = new ArrayList<>(Type.values().length);
|
||||
PaymentsValues paymentsValues = SignalStore.paymentsValues();
|
||||
|
||||
if (paymentsValues.showRecoveryPhraseInfoCard()) {
|
||||
infoCards.add(new InfoCard(R.string.payment_info_card_record_recovery_phrase,
|
||||
if (!paymentsValues.isMnemonicConfirmed()) {
|
||||
infoCards.add(new InfoCard(R.string.payment_info_card_save_recovery_phrase,
|
||||
R.string.payment_info_card_your_recovery_phrase_gives_you,
|
||||
R.string.payment_info_card_record_your_phrase,
|
||||
R.string.payment_info_card_save_your_phrase,
|
||||
R.drawable.ic_payments_info_card_restore_80,
|
||||
Type.RECORD_RECOVERY_PHASE,
|
||||
paymentsValues::dismissRecoveryPhraseInfoCard));
|
||||
|
|
|
@ -55,7 +55,7 @@ public class InfoCardViewHolder extends MappingViewHolder<InfoCard> {
|
|||
.setPositiveButton(R.string.payment_info_card_hide, (dialog, which) -> {
|
||||
model.dismiss();
|
||||
dialog.dismiss();
|
||||
callbacks.onInfoCardDismissed();
|
||||
callbacks.onInfoCardDismissed(model.getType());
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())
|
||||
.show();
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
@ -79,7 +78,7 @@ public class AdvancedPinPreferenceFragment extends ListSummaryPreferenceFragment
|
|||
.setCancelable(true)
|
||||
.setPositiveButton(android.R.string.ok, (d, which) -> d.dismiss())
|
||||
.show();
|
||||
} else if (!enabled && SignalStore.paymentsValues().mobileCoinPaymentsEnabled() && !SignalStore.paymentsValues().userConfirmedMnemonic()) {
|
||||
} else if (!enabled && SignalStore.paymentsValues().mobileCoinPaymentsEnabled() && !SignalStore.paymentsValues().getUserConfirmedMnemonic()) {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.ApplicationPreferencesActivity_record_payments_recovery_phrase)
|
||||
.setMessage(R.string.ApplicationPreferencesActivity_before_you_can_disable_your_pin)
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
app:enterAnim="@anim/fragment_open_enter"
|
||||
app:exitAnim="@anim/fragment_close_exit"
|
||||
app:popEnterAnim="@anim/fragment_close_enter"
|
||||
app:popExitAnim="@anim/fragment_close_exit"
|
||||
app:popUpTo="@id/paymentsHome" />
|
||||
app:popExitAnim="@anim/fragment_close_exit" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_paymentsRecoveryStart_to_paymentsRecoveryEntry"
|
||||
|
@ -46,6 +45,27 @@
|
|||
android:defaultValue="false"
|
||||
app:argType="boolean"
|
||||
app:nullable="false" />
|
||||
|
||||
<argument
|
||||
android:name="recovery_phrase_state"
|
||||
android:defaultValue="NONE"
|
||||
app:argType="org.thoughtcrime.securesms.payments.backup.RecoveryPhraseStates" />
|
||||
|
||||
<argument
|
||||
android:name="first_time_non_zero_balance"
|
||||
android:defaultValue="false"
|
||||
app:argType="boolean" />
|
||||
|
||||
<argument
|
||||
android:name="info_card"
|
||||
android:defaultValue="false"
|
||||
app:argType="boolean" />
|
||||
|
||||
<argument
|
||||
android:name="payment_menu"
|
||||
android:defaultValue="false"
|
||||
app:argType="boolean" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
|
|
@ -76,6 +76,26 @@
|
|||
android:defaultValue="false"
|
||||
app:argType="boolean" />
|
||||
|
||||
<argument
|
||||
android:name="recovery_phrase_state"
|
||||
android:defaultValue="NONE"
|
||||
app:argType="org.thoughtcrime.securesms.payments.backup.RecoveryPhraseStates" />
|
||||
|
||||
<argument
|
||||
android:name="first_time_non_zero_balance"
|
||||
android:defaultValue="false"
|
||||
app:argType="boolean" />
|
||||
|
||||
<argument
|
||||
android:name="info_card"
|
||||
android:defaultValue="false"
|
||||
app:argType="boolean" />
|
||||
|
||||
<argument
|
||||
android:name="payment_menu"
|
||||
android:defaultValue="false"
|
||||
app:argType="boolean" />
|
||||
|
||||
</action>
|
||||
|
||||
<action
|
||||
|
|
|
@ -2999,8 +2999,6 @@
|
|||
<string name="ConfirmPayment__payment_failed">Payment failed</string>
|
||||
<string name="ConfirmPayment__payment_will_continue_processing">Payment will continue processing</string>
|
||||
<string name="ConfirmPaymentFragment__invalid_recipient">Invalid recipient</string>
|
||||
<!-- Biometric/Device authentication prompt title which comes up before sending a payment -->
|
||||
<string name="ConfirmPaymentFragment__unlock_to_send_payment">Unlock to Send Payment</string>
|
||||
<!-- Title of a dialog show when we were unable to present the user\'s screenlock before sending a payment -->
|
||||
<string name="ConfirmPaymentFragment__failed_to_show_payment_lock">Failed to show payment lock</string>
|
||||
<!-- Body of a dialog show when we were unable to present the user\'s screenlock before sending a payment -->
|
||||
|
@ -3010,6 +3008,11 @@
|
|||
<string name="ConfirmPaymentFragment__this_person_has_not_activated_payments">This person has not activated payments</string>
|
||||
<string name="ConfirmPaymentFragment__unable_to_request_a_network_fee">Unable to request a network fee. To continue this payment tap okay to try again.</string>
|
||||
|
||||
<!-- BiometricDeviceAuthentication -->
|
||||
<!-- Biometric/Device authentication prompt title -->
|
||||
<string name="BiometricDeviceAuthentication__signal">Signal</string>
|
||||
|
||||
|
||||
<!-- CurrencyAmountFormatter_s_at_s -->
|
||||
<string name="CurrencyAmountFormatter_s_at_s">%1$s at %2$s</string>
|
||||
|
||||
|
@ -3744,9 +3747,11 @@
|
|||
<string name="payment_info_card_you_can_cash_out_mobilecoin">You can cash out MobileCoin anytime on an exchange that supports MobileCoin. Just make a transfer to your account at that exchange.</string>
|
||||
<string name="payment_info_card_hide_this_card">Hide this card?</string>
|
||||
<string name="payment_info_card_hide">Hide</string>
|
||||
<string name="payment_info_card_record_recovery_phrase">Record recovery phrase</string>
|
||||
<!-- Title of save recovery phrase card -->
|
||||
<string name="payment_info_card_save_recovery_phrase">Save recovery phrase</string>
|
||||
<string name="payment_info_card_your_recovery_phrase_gives_you">Your recovery phrase gives you another way to restore your payments account.</string>
|
||||
<string name="payment_info_card_record_your_phrase">Record your phrase</string>
|
||||
<!-- Button in save recovery phrase card -->
|
||||
<string name="payment_info_card_save_your_phrase">Save your phrase</string>
|
||||
<string name="payment_info_card_update_your_pin">Update your PIN</string>
|
||||
<string name="payment_info_card_with_a_high_balance">With a high balance, you may want to update to an alphanumeric PIN to add more protection to your account.</string>
|
||||
<string name="payment_info_card_update_pin">Update PIN</string>
|
||||
|
@ -3770,12 +3775,26 @@
|
|||
<!-- PaymentsRecoveryStartFragment -->
|
||||
<string name="PaymentsRecoveryStartFragment__recovery_phrase">Recovery phrase</string>
|
||||
<string name="PaymentsRecoveryStartFragment__view_recovery_phrase">View recovery phrase</string>
|
||||
<!-- Title in save recovery phrase screen -->
|
||||
<string name="PaymentsRecoveryStartFragment__save_recovery_phrase">Save recovery phrase</string>
|
||||
<string name="PaymentsRecoveryStartFragment__enter_recovery_phrase">Enter recovery phrase</string>
|
||||
<string name="PaymentsRecoveryStartFragment__your_balance_will_automatically_restore">Your balance will automatically restore when you reinstall Signal if you confirm your Signal PIN. You can also restore your balance using a recovery phrase, which is a %1$d-word phrase unique to you. Write it down and store it in a safe place.</string>
|
||||
<!-- Description in save recovery phrase screen which shows up when user has non zero balance -->
|
||||
<string name="PaymentsRecoveryStartFragment__got_balance">You’ve got a balance! Time to save your recovery phrase—a 24-word key you can use to restore your balance.</string>
|
||||
<!-- Description in save recovery phrase screen which shows up when user navigates from info card -->
|
||||
<string name="PaymentsRecoveryStartFragment__time_to_save">Time to save your recovery phrase—a 24-word key you can use to restore your balance. Learn more</string>
|
||||
<string name="PaymentsRecoveryStartFragment__your_recovery_phrase_is_a">Your recovery phrase is a %1$d-word phrase unique to you. Use this phrase to restore your balance.</string>
|
||||
<string name="PaymentsRecoveryStartFragment__start">Start</string>
|
||||
<string name="PaymentsRecoveryStartFragment__enter_manually">Enter manually</string>
|
||||
<string name="PaymentsRecoveryStartFragment__paste_from_clipboard">Paste from clipboard</string>
|
||||
<!-- Alert dialog title which asks before going back if user wants to save recovery phrase -->
|
||||
<string name="PaymentsRecoveryStartFragment__continue_without_saving">Continue Without Saving?</string>
|
||||
<!-- Alert dialog description to let user know why recovery phrase needs to be saved -->
|
||||
<string name="PaymentsRecoveryStartFragment__your_recovery_phrase">Your recovery phrase lets you restore your balance in a worst-case scenario. We strongly recommend you save it.</string>
|
||||
<!-- Alert dialog option to skip recovery phrase -->
|
||||
<string name="PaymentsRecoveryStartFragment__skip_recovery_phrase">Skip Recovery Phrase</string>
|
||||
<!-- Alert dialog option to cancel dialog-->
|
||||
<string name="PaymentsRecoveryStartFragment__cancel">Cancel</string>
|
||||
|
||||
<!-- PaymentsRecoveryPasteFragment -->
|
||||
<string name="PaymentsRecoveryPasteFragment__paste_recovery_phrase">Paste recovery phrase</string>
|
||||
|
|
|
@ -68,6 +68,8 @@ public abstract class Money {
|
|||
|
||||
public abstract boolean isNegative();
|
||||
|
||||
public abstract boolean isEqualOrLessThanZero();
|
||||
|
||||
public abstract Money negate();
|
||||
|
||||
public abstract Money abs();
|
||||
|
@ -147,6 +149,11 @@ public abstract class Money {
|
|||
return amount.signum() == -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqualOrLessThanZero() {
|
||||
return amount != null && amount.compareTo(BigInteger.ZERO) <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MobileCoin negate() {
|
||||
return new MobileCoin(amount.negate());
|
||||
|
|
Ładowanie…
Reference in New Issue