Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-23 16:08:03 +04:00
parent e43f438c1a
commit 35f4f97408
9 changed files with 73 additions and 192 deletions

View file

@ -7,12 +7,8 @@ import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.ui.ScanFailsDialog
import com.tangem.tap.common.ui.SimpleAlertDialog
import com.tangem.tap.common.ui.SimpleCancelableAlertDialog
import com.tangem.tap.common.ui.SimpleOkDialog
import com.tangem.tap.features.onboarding.OnboardingDialog
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedBackupFoundDialog
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.WalletActivationErrorDialog
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.WalletAlreadyWasUsedDialog
import com.tangem.tap.store
@ -60,14 +56,6 @@ class DialogManager : StoreSubscriber<GlobalState> {
context = context,
)
is OnboardingDialog.WalletActivationError -> WalletActivationErrorDialog.create(context, state.dialog)
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(
context = context,
scanResponse = state.dialog.scanResponse,
)
is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(
context = context,
unfinishedBackupScanResponse = state.dialog.scanResponse,
)
is AppDialog.TokensAreLinkedDialog -> SimpleAlertDialog.create(
title = context.getString(state.dialog.titleRes, state.dialog.currencySymbol),
message = context.getString(
@ -84,13 +72,6 @@ class DialogManager : StoreSubscriber<GlobalState> {
onSupport = state.dialog.onSupportClick,
onCancel = state.dialog.onCancel,
)
is AppDialog.RemoveWalletDialog -> SimpleCancelableAlertDialog.create(
title = context.getString(state.dialog.titleRes, state.dialog.currencyTitle),
messageRes = state.dialog.messageRes,
context = context,
primaryButtonRes = state.dialog.primaryButtonRes,
primaryButtonAction = state.dialog.onOk,
)
else -> null
}
dialog?.show()

View file

@ -15,15 +15,6 @@ sealed class AppDialog : StateDialog {
val onOk: VoidCallback? = null,
) : AppDialog()
data class RemoveWalletDialog(
val currencyTitle: String,
val onOk: () -> Unit,
) : AppDialog() {
val messageRes: Int = R.string.token_details_hide_alert_message
val titleRes: Int = R.string.token_details_hide_alert_title
val primaryButtonRes: Int = R.string.token_details_hide_alert_hide
}
data class TokensAreLinkedDialog(
val currencyTitle: String,
val currencySymbol: String,

View file

@ -5,7 +5,6 @@ import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.redux.legacy.LegacyMiddleware
import com.tangem.tap.features.details.redux.DetailsMiddleware
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware
import com.tangem.tap.proxy.redux.DaggerGraphMiddleware
import com.tangem.tap.proxy.redux.DaggerGraphState
import org.rekotlin.Middleware
@ -23,7 +22,6 @@ data class AppState(
logMiddleware,
GlobalMiddleware.handler,
DetailsMiddleware().detailsMiddleware,
BackupMiddleware().backupMiddleware,
LockUserWalletsTimerMiddleware().middleware,
AccessCodeRequestPolicyMiddleware().middleware,
DaggerGraphMiddleware.daggerGraphMiddleware,

View file

@ -1,14 +0,0 @@
package com.tangem.tap.features.onboarding.products.wallet.redux
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog
sealed class BackupDialog : StateDialog {
data class UnfinishedBackupFound(
val scanResponse: ScanResponse? = null,
) : BackupDialog()
data class ConfirmDiscardingBackup(
val scanResponse: ScanResponse? = null,
) : BackupDialog()
}

View file

@ -1,63 +0,0 @@
package com.tangem.tap.features.onboarding.products.wallet.redux
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics
import com.tangem.tap.backupService
import com.tangem.tap.common.analytics.events.Onboarding.Finished
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.mainScope
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
@Suppress("MemberNameEqualsClassName")
class BackupMiddleware {
val backupMiddleware: Middleware<AppState> = { dispatch, state ->
{ next ->
{ action ->
if (action is BackupAction) handleBackupAction(state, action)
next(action)
}
}
}
}
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) {
if (DemoHelper.tryHandle(appState)) return
when (action) {
is BackupAction.DiscardBackup -> {
backupService.discardSavedBackup()
}
is BackupAction.DiscardSavedBackup -> {
mainScope.launch {
backupService.discardSavedBackup()
val onboardingRepository = store.inject(DaggerGraphState::onboardingRepository)
val cardRepository = store.inject(DaggerGraphState::cardRepository)
val unfinishedBackup = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch
cardRepository.finishCardActivation(unfinishedBackup.card.cardId)
onboardingRepository.clearUnfinishedFinalizeOnboarding()
Analytics.send(Finished())
}
}
is BackupAction.ResumeFoundUnfinishedBackup -> {
if (action.unfinishedBackupScanResponse != null) {
store.dispatchNavigationAction {
replaceAll(
AppRoute.Onboarding(
scanResponse = action.unfinishedBackupScanResponse,
mode = AppRoute.Onboarding.Mode.ContinueFinalize,
),
)
}
}
}
}
}

View file

@ -1,19 +0,0 @@
package com.tangem.tap.features.onboarding.products.wallet.redux
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWalletId
import org.rekotlin.Action
sealed class OnboardingWalletAction : Action {
data class WalletSaved(val userWalletId: UserWalletId) : OnboardingWalletAction()
}
sealed class BackupAction : Action {
data object DiscardBackup : BackupAction()
data object DiscardSavedBackup : BackupAction()
data class ResumeFoundUnfinishedBackup(
val unfinishedBackupScanResponse: ScanResponse?,
) : BackupAction()
}

View file

@ -1,29 +0,0 @@
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.store
import com.tangem.wallet.R
object ConfirmDiscardingBackupDialog {
fun create(context: Context, unfinishedBackupScanResponse: ScanResponse? = null): AlertDialog {
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
setTitle(R.string.welcome_interrupted_backup_discard_title)
setMessage(R.string.welcome_interrupted_backup_discard_message)
setPositiveButton(R.string.welcome_interrupted_backup_discard_resume) { _, _ ->
store.dispatch(BackupAction.ResumeFoundUnfinishedBackup(unfinishedBackupScanResponse))
}
setNegativeButton(R.string.welcome_interrupted_backup_discard_discard) { _, _ ->
store.dispatch(BackupAction.DiscardSavedBackup)
}
setOnDismissListener {
store.dispatch(GlobalAction.HideDialog)
}
setCancelable(false)
}.create()
}
}

View file

@ -1,33 +0,0 @@
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tangem.core.analytics.Analytics
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.features.onboarding.v2.common.analytics.OnboardingEvent
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
import com.tangem.tap.store
import com.tangem.wallet.R
object UnfinishedBackupFoundDialog {
fun create(context: Context, scanResponse: ScanResponse? = null): AlertDialog {
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
setTitle(R.string.common_warning)
setMessage(R.string.welcome_interrupted_backup_alert_message)
setPositiveButton(R.string.welcome_interrupted_backup_alert_resume) { _, _ ->
Analytics.send(OnboardingEvent.Backup.ResumeInterruptedBackup())
store.dispatch(GlobalAction.HideDialog)
store.dispatch(BackupAction.ResumeFoundUnfinishedBackup(scanResponse))
}
setNegativeButton(R.string.welcome_interrupted_backup_alert_discard) { _, _ ->
Analytics.send(OnboardingEvent.Backup.CancelInterruptedBackup())
store.dispatch(GlobalAction.HideDialog)
store.dispatch(GlobalAction.ShowDialog(BackupDialog.ConfirmDiscardingBackup(scanResponse)))
}
setCancelable(false)
}.create()
}
}

View file

@ -23,20 +23,25 @@ import com.tangem.core.decompose.navigation.getOrCreateTyped
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.onboarding.repository.OnboardingRepository
import com.tangem.features.hotwallet.HotAccessCodeRequestComponent
import com.tangem.features.hotwallet.accesscoderequest.proxy.HotWalletPasswordRequesterProxy
import com.tangem.features.onboarding.v2.common.analytics.OnboardingEvent
import com.tangem.features.walletconnect.components.WcRoutingComponent
import com.tangem.hot.sdk.TangemHotSdk
import com.tangem.hot.sdk.android.create
import com.tangem.sdk.api.BackupServiceHolder
import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.hot.TangemHotSDKProxy
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
import com.tangem.tap.features.root.RootDetectedWarningComponent
import com.tangem.tap.routing.RootContent
import com.tangem.tap.routing.component.RoutingComponent
@ -46,6 +51,7 @@ import com.tangem.tap.routing.utils.ChildFactory
import com.tangem.tap.routing.utils.DeepLinkFactory
import com.tangem.tap.store
import com.tangem.utils.logging.TangemLogger
import com.tangem.wallet.R
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -71,6 +77,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
private val trackingContextProxy: TrackingContextProxy,
private val analyticsEventHandler: AnalyticsEventHandler,
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
private val backupServiceHolder: BackupServiceHolder,
) : RoutingComponent,
AppComponentContext by context,
SnackbarHandler {
@ -251,9 +258,71 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
}
private fun checkForUnfinishedBackup() {
if (DemoHelper.tryHandle { store.state }) return
componentScope.launch(dispatchers.main) {
val onboardingScanResponse = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound(onboardingScanResponse)))
val scanResponse = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch
messageSender.send(unfinishedBackupFoundDialog(scanResponse))
}
}
private fun unfinishedBackupFoundDialog(scanResponse: ScanResponse): DialogMessage = DialogMessage(
title = resourceReference(R.string.common_warning),
message = resourceReference(R.string.welcome_interrupted_backup_alert_message),
isDismissable = false,
firstActionBuilder = {
EventMessageAction(
title = resourceReference(R.string.welcome_interrupted_backup_alert_resume),
onClick = {
analyticsEventHandler.send(OnboardingEvent.Backup.ResumeInterruptedBackup())
resumeUnfinishedBackup(scanResponse)
},
)
},
secondActionBuilder = {
EventMessageAction(
title = resourceReference(R.string.welcome_interrupted_backup_alert_discard),
onClick = {
analyticsEventHandler.send(OnboardingEvent.Backup.CancelInterruptedBackup())
messageSender.send(confirmDiscardingBackupDialog(scanResponse))
},
)
},
)
private fun confirmDiscardingBackupDialog(scanResponse: ScanResponse): DialogMessage = DialogMessage(
title = resourceReference(R.string.welcome_interrupted_backup_discard_title),
message = resourceReference(R.string.welcome_interrupted_backup_discard_message),
isDismissable = false,
firstActionBuilder = {
EventMessageAction(
title = resourceReference(R.string.welcome_interrupted_backup_discard_resume),
onClick = { resumeUnfinishedBackup(scanResponse) },
)
},
secondActionBuilder = {
EventMessageAction(
title = resourceReference(R.string.welcome_interrupted_backup_discard_discard),
onClick = { discardSavedBackup() },
)
},
)
private fun resumeUnfinishedBackup(scanResponse: ScanResponse) {
router.replaceAll(
AppRoute.Onboarding(
scanResponse = scanResponse,
mode = AppRoute.Onboarding.Mode.ContinueFinalize,
),
)
}
private fun discardSavedBackup() {
componentScope.launch(dispatchers.main) {
backupServiceHolder.backupService.get()?.discardSavedBackup()
val unfinishedBackup = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch
cardRepository.finishCardActivation(unfinishedBackup.card.cardId)
onboardingRepository.clearUnfinishedFinalizeOnboarding()
analyticsEventHandler.send(Onboarding.Finished())
}
}