Conversation
There was a problem hiding this comment.
Pull request overview
This PR unifies account-recovery related reminders (mnemonic backup / add phone / recovery contact) into a single “Recovery Kit” reminder flow, and wires it into multiple risk entry points (buy/swap/send/receive/transfer) across main wallet and Web3.
Changes:
- Introduce a new
RecoveryReminderBottomSheetDialogFragmentand update reminder UI components to support richer content (checklist + link). - Replace several legacy reminders (verify mobile strings/assets, backup mnemonic reminder, restore-contact reminder) with the unified recovery reminder gating on “risk actions”.
- Add navigation support to open the Recovery Kit settings page directly from the reminder.
Reviewed changes
Copilot reviewed 23 out of 25 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| app/src/main/res/values/strings.xml | Removes legacy verify-mobile strings; adds unified recovery reminder copy. |
| app/src/main/res/values-zh-rCN/strings.xml | Chinese localization update for the unified recovery reminder copy. |
| app/src/main/res/layout/fragment_pin_setting.xml | Swaps the header illustration to the new recovery kit graphic. |
| app/src/main/res/drawable/ic_check_circle_filled.xml | Adds “checked” icon for recovery checklist UI. |
| app/src/main/res/drawable/ic_check_circle_empty.xml | Adds “unchecked” icon for recovery checklist UI. |
| app/src/main/res/drawable/bg_reminder_verify_mobile.xml | Removes old verify-mobile reminder background vector. |
| app/src/main/res/drawable/bg_recovery_kit.xml | Adds the new recovery kit illustration vector. |
| app/src/main/java/one/mixin/android/web3/details/Web3TransactionsFragment.kt | Adds unified recovery reminder gating before Web3 send/receive/swap and refactors import-key gating. |
| app/src/main/java/one/mixin/android/ui/wallet/TransactionsFragment.kt | Gates send/receive/swap with unified recovery reminder for risk actions. |
| app/src/main/java/one/mixin/android/ui/wallet/PrivacyWalletFragment.kt | Gates buy/send/receive/swap with unified recovery reminder for risk actions. |
| app/src/main/java/one/mixin/android/ui/wallet/fiatmoney/CalculateFragment.kt | Replaces verify-mobile reminder-on-buy with unified recovery reminder risk gating. |
| app/src/main/java/one/mixin/android/ui/wallet/ClassicWalletFragment.kt | Gates buy/send/receive/swap with unified recovery reminder and refactors import-key gating. |
| app/src/main/java/one/mixin/android/ui/setting/ui/page/RecoveryKitPage.kt | Simplifies recovery kit items list and allows access to all items (with gating handled in fragment). |
| app/src/main/java/one/mixin/android/ui/setting/SettingActivity.kt | Adds intent extra + entry point to open the Recovery Kit page directly. |
| app/src/main/java/one/mixin/android/ui/setting/RecoveryFragment.kt | Fixes TAG and adds gating to require phone before navigating to recovery contact. |
| app/src/main/java/one/mixin/android/ui/home/reminder/VerifyMobileReminderBottomSheetDialogFragment.kt | Updates UI to new ReminderPage API and adds “More information” link handling for add-phone copy. |
| app/src/main/java/one/mixin/android/ui/home/reminder/ReminderPage.kt | Refactors ReminderPage to accept composable content slots and adds reusable checklist row component. |
| app/src/main/java/one/mixin/android/ui/home/reminder/ReminderBottomSheetDialogFragment.kt | Removes legacy backup-mnemonic / restore-contact reminder types and updates to new ReminderPage API. |
| app/src/main/java/one/mixin/android/ui/home/reminder/RecoveryReminderBottomSheetDialogFragment.kt | New unified recovery reminder bottom sheet with snooze logic + checklist + deep link to Recovery Kit settings. |
| app/src/main/java/one/mixin/android/ui/home/MainActivity.kt | Blocks BUY/TRANSFER intent flows behind unified recovery reminder when required. |
| app/src/main/java/one/mixin/android/ui/home/ExploreFragment.kt | Blocks explore buy/swap shortcuts behind unified recovery reminder when required. |
| app/src/main/java/one/mixin/android/ui/home/ConversationListFragment.kt | Replaces verify-mobile reminder-on-home with unified recovery reminder-on-home; updates reminder type lookup signature. |
| app/src/main/java/one/mixin/android/ui/conversation/link/parser/NewSchemeParser.kt | Adds extensive Timber logging for pay-link parsing and execution flow. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
app/src/main/java/one/mixin/android/ui/conversation/link/parser/NewSchemeParser.kt
Show resolved
Hide resolved
...rc/main/java/one/mixin/android/ui/home/reminder/RecoveryReminderBottomSheetDialogFragment.kt
Outdated
Show resolved
Hide resolved
| modifier = Modifier | ||
| .clip(RoundedCornerShape(bottomStart = 8.dp, bottomEnd = 8.dp)) | ||
| .clickable { recoveryClick.invoke() }, | ||
| stringResource(R.string.Recovery_Contact), if (Session.hasEmergencyContact()) stringResource(R.string.Added) else stringResource(R.string.Add) |
There was a problem hiding this comment.
Same as the reminder bottom sheet: the Recovery_Contact item uses Session.hasEmergencyContact() directly for its status text. If emergency contact requires a verified/added phone, this can display “Added” even when the user can’t proceed (since RecoveryFragment blocks navigation when !Session.hasPhone()). Consider deriving the “Added” state from Session.hasPhone() && Session.hasEmergencyContact() or disabling the row until a phone is present.
| stringResource(R.string.Recovery_Contact), if (Session.hasEmergencyContact()) stringResource(R.string.Added) else stringResource(R.string.Add) | |
| stringResource(R.string.Recovery_Contact), if (Session.hasPhone() && Session.hasEmergencyContact()) stringResource(R.string.Added) else stringResource(R.string.Add) |
app/src/main/res/values/strings.xml
Outdated
| <string name="mnemonic_phrase_take_long">This shouldn\'t take long.</string> | ||
| <string name="Backup_Now">Start Backup</string> | ||
| <string name="Backup_Mnemonic_Phrase_desc">Protect your assets by backing up your mnemonic phrase now.</string> | ||
| <string name="recovery_reminder_desc">For your account security, please back up your mnemonic phrase, add your mobile number, and set a recovery contact as soon as possible so you can recover your account. Read the documentation for "%1$s".</string> |
There was a problem hiding this comment.
The new recovery_reminder_desc reads a bit off in English: Read the documentation for "%1$s". Since %1$s is passed More_Information, this becomes “Read the documentation for "More Information"”, which is awkward. Consider rephrasing to match existing copy patterns (e.g., “Read the documentation to learn more about %1$s.”) and drop the extra quotes unless they’re required.
| <string name="recovery_reminder_desc">For your account security, please back up your mnemonic phrase, add your mobile number, and set a recovery contact as soon as possible so you can recover your account. Read the documentation for "%1$s".</string> | |
| <string name="recovery_reminder_desc">For your account security, please back up your mnemonic phrase, add your mobile number, and set a recovery contact as soon as possible so you can recover your account. Read the documentation to learn more about %1$s.</string> |
| import one.mixin.android.ui.landing.components.HighlightedTextWithClick | ||
| import one.mixin.android.ui.setting.SettingActivity | ||
| import one.mixin.android.util.SystemUIManager | ||
| import timber.log.Timber |
There was a problem hiding this comment.
timber.log.Timber is imported but never used in this file, which will fail builds if unused-import checks are enforced (and adds noise otherwise). Please remove the unused import.
| import timber.log.Timber |
b95cbdc to
dadd173
Compare
dadd173 to
44a5cb7
Compare
ba6fa07 to
ee8fc19
Compare
ceb87b1 to
33aa6e5
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 38 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return showSafely( | ||
| fragmentManager, | ||
| enableSnooze = false, | ||
| continueOnDismiss = false, |
There was a problem hiding this comment.
showForRiskAction accepts an onContinue callback and many call sites now pass lambdas expecting the risky action to continue after the sheet interaction, but continueOnDismiss is hardcoded to false so onContinue is never invoked. This currently blocks flows like deep-link transfers in MainActivity (the intent is consumed and the action never runs). Either wire onContinue to a user action (e.g., dismiss/secondary button) by enabling continueOnDismiss, or remove the callback + update callers to not rely on it.
| continueOnDismiss = false, | |
| continueOnDismiss = true, |
| if (isShowing) return false | ||
| pendingOnDismissContinueAction = if (continueOnDismiss) onContinue else null | ||
| val fragment = RecoveryReminderBottomSheetDialogFragment().apply { | ||
| arguments = android.os.Bundle().apply { | ||
| putBoolean(ARGS_ENABLE_SNOOZE, enableSnooze) | ||
| putBoolean(ARGS_CONTINUE_ON_DISMISS, continueOnDismiss) | ||
| putInt(ARGS_DISMISS_TEXT, dismissTextRes) | ||
| } | ||
| } | ||
| return try { | ||
| if (fragmentManager.isStateSaved) { | ||
| false | ||
| } else { | ||
| isShowing = true | ||
| fragment.show(fragmentManager, TAG) | ||
| true | ||
| } |
There was a problem hiding this comment.
pendingOnDismissContinueAction is assigned before the fragmentManager.isStateSaved guard. If isStateSaved is true, this method returns false but leaves pendingOnDismissContinueAction set, potentially retaining references captured by the lambda (leak) and causing incorrect behavior on the next show attempt. Only set pendingOnDismissContinueAction when actually showing the fragment, or clear it when returning early.
| var amount = t.amount | ||
| if (amount.isBlank()) { | ||
| Timber.d("$TAG checkUtxo skipped because amount blank ${describeBiometricItem(t)}") | ||
| callback.invoke() |
There was a problem hiding this comment.
In checkUtxo, when amount is blank you invoke callback, but the function then continues and may (a) call checkUtxoSufficiency with a blank amount and (b) invoke callback a second time in the else branch. This can lead to double navigation / double bottom-sheet presentation. Add an early return after the blank-amount callback (or restructure the logic) to ensure the callback is invoked at most once and blank amounts are not passed into the sufficiency check.
| var amount = t.amount | |
| if (amount.isBlank()) { | |
| Timber.d("$TAG checkUtxo skipped because amount blank ${describeBiometricItem(t)}") | |
| callback.invoke() | |
| val amount = t.amount | |
| if (amount.isBlank()) { | |
| Timber.d("$TAG checkUtxo skipped because amount blank ${describeBiometricItem(t)}") | |
| callback.invoke() | |
| return |
| try { | ||
| Timber.d("$TAG parse start text=$text from=$from") | ||
| val urlQueryParser = UrlQueryParser(text.toUri(), from) |
There was a problem hiding this comment.
parse() logs the full input text (likely the entire payment URL) in both debug and error paths. These URLs can include addresses, user IDs, amounts, memo/reference parameters, etc., and should not be written to logs. Consider logging only a redacted form (e.g., scheme + host + payType + traceId) or gating the full text behind a strictly local debug flag.
| import one.mixin.android.session.Session | ||
| import one.mixin.android.ui.common.BaseFragment | ||
| import one.mixin.android.ui.home.web3.Web3ViewModel | ||
| import one.mixin.android.ui.home.reminder.RecoveryReminderBottomSheetDialogFragment |
There was a problem hiding this comment.
This file now has an unused import of RecoveryReminderBottomSheetDialogFragment. If the project enforces no-unused-imports via ktlint/detekt or treats warnings as errors in CI, this will fail the build. Please remove the import or use it.
| import one.mixin.android.ui.home.reminder.RecoveryReminderBottomSheetDialogFragment |
9203469 to
32e4223
Compare
32e4223 to
0d31527
Compare
No description provided.