Skip to content

Conversation

@SeniorZhai
Copy link
Member

No description provided.

@SeniorZhai SeniorZhai requested a review from Copilot January 19, 2026 06:46
@SeniorZhai SeniorZhai added the bug Something isn't working label Jan 19, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds a mobile verification reminder feature and gates the wallet buy flow to ensure users have verified their mobile number. The feature prompts users to verify their mobile number if it hasn't been verified in the last 60 days or if there's no verification record.

Changes:

  • Added mobile verification reminder dialog with localized strings and background drawable
  • Gated wallet buy flow in both Privacy and Classic wallet fragments to check mobile verification status
  • Added verification flow integration through LandingActivity with new FROM_VERIFY_MOBILE_REMINDER constant

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
app/src/main/res/values/strings.xml Added English strings for verify mobile reminder dialog
app/src/main/res/values-zh-rCN/strings.xml Added Chinese translations for verify mobile reminder dialog
app/src/main/res/drawable/bg_reminder_verify_mobile.xml Added vector drawable background for verify mobile reminder
app/src/main/java/one/mixin/android/ui/wallet/PrivacyWalletFragment.kt Added mobile verification check before wallet buy flow
app/src/main/java/one/mixin/android/ui/wallet/ClassicWalletFragment.kt Added mobile verification check before wallet buy flow
app/src/main/java/one/mixin/android/ui/landing/MobileFragment.kt Added FROM_VERIFY_MOBILE_REMINDER constant and verification purpose handling
app/src/main/java/one/mixin/android/ui/landing/LandingActivity.kt Added showVerifyMobile and showChangePhone methods for navigation
app/src/main/java/one/mixin/android/ui/home/reminder/verify_mobile_reminder_bottom_sheet_dialog_fragment.kt New dialog fragment for showing mobile verification reminder
app/src/main/java/one/mixin/android/ui/home/ConversationListFragment.kt Added logic to show verify mobile reminder with priority over other reminders
app/src/main/java/one/mixin/android/ui/common/PinCodeFragment.kt Added logic to return to MainActivity after mobile verification
app/src/main/java/one/mixin/android/api/request/VerificationRequest.kt Added NONE verification purpose enum value

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 80 to 83
import one.mixin.android.ui.search.SearchMessageFragment
import one.mixin.android.ui.search.SearchSingleFragment
import one.mixin.android.ui.wallet.AllTransactionsFragment
import one.mixin.android.ui.wallet.WalletActivity
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The imports for SearchMessageFragment, SearchSingleFragment, AllTransactionsFragment, and WalletActivity are added but not used anywhere in this file. These should be removed to keep the imports clean.

Suggested change
import one.mixin.android.ui.search.SearchMessageFragment
import one.mixin.android.ui.search.SearchSingleFragment
import one.mixin.android.ui.wallet.AllTransactionsFragment
import one.mixin.android.ui.wallet.WalletActivity

Copilot uses AI. Check for mistakes.
import one.mixin.android.extension.tickVibrate
import one.mixin.android.session.Session
import one.mixin.android.session.decryptPinToken
import one.mixin.android.ui.home.MainActivity
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import for MainActivity is added but not used in this file. The class is only referenced in a variable name. This import should be removed.

Suggested change
import one.mixin.android.ui.home.MainActivity

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +27
package one.mixin.android.ui.home.reminder

import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import androidx.compose.runtime.Composable
import dagger.hilt.android.AndroidEntryPoint
import one.mixin.android.Constants
import one.mixin.android.R
import one.mixin.android.compose.theme.MixinAppTheme
import one.mixin.android.extension.booleanFromAttribute
import one.mixin.android.extension.defaultSharedPreferences
import one.mixin.android.extension.getSafeAreaInsetsTop
import one.mixin.android.extension.isNightMode
import one.mixin.android.extension.putLong
import one.mixin.android.extension.screenHeight
import one.mixin.android.session.Session
import one.mixin.android.ui.common.MixinComposeBottomSheetDialogFragment
import one.mixin.android.ui.landing.LandingActivity
import one.mixin.android.util.SystemUIManager
import java.time.Instant

@AndroidEntryPoint
class VerifyMobileReminderBottomSheetDialogFragment : MixinComposeBottomSheetDialogFragment() {
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The filename uses snake_case (verify_mobile_reminder_bottom_sheet_dialog_fragment.kt) while the class name uses PascalCase (VerifyMobileReminderBottomSheetDialogFragment). Kotlin convention is to use PascalCase for filenames that match the class name. The file should be renamed to VerifyMobileReminderBottomSheetDialogFragment.kt to follow standard Kotlin naming conventions.

Copilot uses AI. Check for mistakes.
Comment on lines 135 to 154
lifecycleScope.launch {
if (Session.isAnonymous() && !Session.hasPhone()) {
navTo(AddPhoneBeforeFragment.newInstance(), AddPhoneBeforeFragment.TAG)
return@launch
}
val phoneVerifiedAt: String? = Session.getAccount()?.phoneVerifiedAt
val shouldVerifyMobile: Boolean = phoneVerifiedAt.isNullOrBlank() || runCatching {
val verifiedAtMillis: Long = Instant.parse(phoneVerifiedAt).toEpochMilli()
val sixtyDaysMillis: Long = 60L * 24L * 60L * 60L * 1000L
System.currentTimeMillis() - verifiedAtMillis > sixtyDaysMillis
}.getOrDefault(true)
if (shouldVerifyMobile) {
LandingActivity.showVerifyMobile(requireContext())
return@launch
}
WalletActivity.showBuy(requireActivity(), false, null, null)
defaultSharedPreferences.putBoolean(PREF_HAS_USED_BUY, false)
RxBus.publish(BadgeEvent(PREF_HAS_USED_BUY))
sendReceiveView.buyBadge.isVisible = false
}
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mobile verification check logic is duplicated in ClassicWalletFragment. Consider extracting this logic into a shared utility function or extension method to improve maintainability and reduce code duplication. This would make it easier to update the verification logic in one place if requirements change.

Copilot uses AI. Check for mistakes.
companion object {
const val TAG: String = "VerifyMobileReminderBottomSheetDialogFragment"
private const val PREF_VERIFY_MOBILE_REMINDER_SNOOZE: String = "pref_verify_mobile_reminder_snooze"
private const val VERIFY_MOBILE_INTERVAL_MILLIS: Long = 60L * 24L * 60L * 60L * 1000L
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constant VERIFY_MOBILE_INTERVAL_MILLIS is defined as 60 days but the calculation is incorrect. The formula used is 60L * 24L * 60L * 60L * 1000L which equals 60 days * 24 hours * 60 minutes * 60 seconds * 1000 milliseconds = 60 days. However, this should be 60 days * 24 hours * 3600 seconds * 1000 milliseconds OR 60L * 24L * 60L * 60L * 1000L. The current calculation appears correct but the naming suggests it should be clearer. The same calculation is duplicated in PrivacyWalletFragment (line 143) and ClassicWalletFragment (line 179). This magic number should be extracted to a shared constant.

Copilot uses AI. Check for mistakes.
val phoneVerifiedAt: String? = Session.getAccount()?.phoneVerifiedAt
val shouldVerifyMobile: Boolean = phoneVerifiedAt.isNullOrBlank() || runCatching {
val verifiedAtMillis: Long = Instant.parse(phoneVerifiedAt).toEpochMilli()
val sixtyDaysMillis: Long = 60L * 24L * 60L * 60L * 1000L
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number for the 60-day interval (60L * 24L * 60L * 60L * 1000L) is duplicated here and in verify_mobile_reminder_bottom_sheet_dialog_fragment.kt. This should be extracted to a shared constant to ensure consistency and make maintenance easier.

Copilot uses AI. Check for mistakes.
@SeniorZhai SeniorZhai added testing Now testing, but you can review and removed bug Something isn't working labels Jan 20, 2026
@SeniorZhai SeniorZhai requested a review from Copilot January 20, 2026 03:59
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +176 to +180
val phoneVerifiedAt: String? = Session.getAccount()?.phoneVerifiedAt
val shouldVerifyMobile: Boolean = phoneVerifiedAt.isNullOrBlank() || runCatching {
val verifiedAtMillis: Long = Instant.parse(phoneVerifiedAt).toEpochMilli()
System.currentTimeMillis() - verifiedAtMillis > Constants.INTERVAL_60_DAYS
}.getOrDefault(true)
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phone verification logic is duplicated here, identical to the logic in PrivacyWalletFragment. Consider extracting this verification check into a shared utility function or extension function on Session to improve maintainability and reduce code duplication. The logic checks if the phone is verified and if it was verified more than 60 days ago.

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +44
fun shouldShow(context: Context): Boolean {
if (!Session.hasPhone()) return false
val account = Session.getAccount() ?: return false
val lastSnoozeTimeMillis: Long = context.defaultSharedPreferences.getLong(PREF_VERIFY_MOBILE_REMINDER_SNOOZE, 0)
if (System.currentTimeMillis() - lastSnoozeTimeMillis < Constants.INTERVAL_7_DAYS) return false
val phoneVerifiedAt: String? = account.phoneVerifiedAt
if (phoneVerifiedAt.isNullOrBlank()) return true
val verifiedAtMillis: Long = runCatching {
Instant.parse(phoneVerifiedAt).toEpochMilli()
}.getOrNull() ?: return true
return System.currentTimeMillis() - verifiedAtMillis > Constants.INTERVAL_60_DAYS
}
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phone verification date check logic is duplicated in this file (lines 40-43) and in PrivacyWalletFragment (lines 141-144) and ClassicWalletFragment (lines 177-180). This same logic for checking if the phone was verified more than 60 days ago appears in multiple places. Consider extracting this into a shared utility function or extension function on Session to avoid code duplication and improve maintainability.

Suggested change
fun shouldShow(context: Context): Boolean {
if (!Session.hasPhone()) return false
val account = Session.getAccount() ?: return false
val lastSnoozeTimeMillis: Long = context.defaultSharedPreferences.getLong(PREF_VERIFY_MOBILE_REMINDER_SNOOZE, 0)
if (System.currentTimeMillis() - lastSnoozeTimeMillis < Constants.INTERVAL_7_DAYS) return false
val phoneVerifiedAt: String? = account.phoneVerifiedAt
if (phoneVerifiedAt.isNullOrBlank()) return true
val verifiedAtMillis: Long = runCatching {
Instant.parse(phoneVerifiedAt).toEpochMilli()
}.getOrNull() ?: return true
return System.currentTimeMillis() - verifiedAtMillis > Constants.INTERVAL_60_DAYS
}
private fun isPhoneVerifiedMoreThanSixtyDaysAgo(phoneVerifiedAt: String?): Boolean {
if (phoneVerifiedAt.isNullOrBlank()) return true
val verifiedAtMillis: Long = runCatching {
Instant.parse(phoneVerifiedAt).toEpochMilli()
}.getOrNull() ?: return true
return System.currentTimeMillis() - verifiedAtMillis > Constants.INTERVAL_60_DAYS
}
fun shouldShow(context: Context): Boolean {
if (!Session.hasPhone()) return false
val account = Session.getAccount() ?: return false
val lastSnoozeTimeMillis: Long = context.defaultSharedPreferences.getLong(PREF_VERIFY_MOBILE_REMINDER_SNOOZE, 0)
if (System.currentTimeMillis() - lastSnoozeTimeMillis < Constants.INTERVAL_7_DAYS) return false
return isPhoneVerifiedMoreThanSixtyDaysAgo(account.phoneVerifiedAt)
}

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +60
fun showChangePhone(context: Context) {
val intent =
Intent(context, LandingActivity::class.java).apply {
putExtra(ARGS_FROM, FROM_CHANGE_PHONE_ACCOUNT)
}
context.startActivity(intent)
}

Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The showChangePhone function is added but does not appear to be used anywhere in the codebase based on the changes in this PR. If this function is intended for future use, consider adding a comment explaining its purpose. If it's not needed, it should be removed to avoid maintaining unused code.

Suggested change
fun showChangePhone(context: Context) {
val intent =
Intent(context, LandingActivity::class.java).apply {
putExtra(ARGS_FROM, FROM_CHANGE_PHONE_ACCOUNT)
}
context.startActivity(intent)
}

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,104 @@
package one.mixin.android.ui.home.reminder
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file name uses snake_case (verify_mobile_reminder_bottom_sheet_dialog_fragment.kt) instead of PascalCase which is inconsistent with Kotlin naming conventions. The file should be named VerifyMobileReminderBottomSheetDialogFragment.kt to match the class name inside and follow standard Kotlin file naming conventions.

Copilot uses AI. Check for mistakes.
Comment on lines +140 to +144
val phoneVerifiedAt: String? = Session.getAccount()?.phoneVerifiedAt
val shouldVerifyMobile: Boolean = phoneVerifiedAt.isNullOrBlank() || runCatching {
val verifiedAtMillis: Long = Instant.parse(phoneVerifiedAt).toEpochMilli()
System.currentTimeMillis() - verifiedAtMillis > Constants.INTERVAL_60_DAYS
}.getOrDefault(true)
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phone verification logic is duplicated between PrivacyWalletFragment and ClassicWalletFragment. Consider extracting this verification check into a shared utility function or extension function on Session to improve maintainability and reduce code duplication. The logic checks if the phone is verified and if it was verified more than 60 days ago.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

testing Now testing, but you can review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants