From 539c1a268976453e03c1b5a3b115003c3baa34d7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 5 Aug 2025 21:44:05 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tangem/tap/routing/utils/ChildFactory.kt | 12 ++ .../com/tangem/common/routing/AppRoute.kt | 5 + .../hotwallet/WalletActivationComponent.kt | 14 ++ .../entry/AddExistingWalletModel.kt | 49 ++++-- .../DefaultAddExistingWalletComponent.kt | 52 +++---- .../routing/AddExistingWalletChildFactory.kt | 3 +- .../CreateMobileWalletModel.kt | 2 +- .../check/ManualBackupCheckComponent.kt | 4 +- .../check/di/ManualBackupCheckModule.kt | 20 +++ .../check/entity/ManualBackupCheckUM.kt | 2 + .../check/model/ManualBackupCheckModel.kt | 73 +++++++-- .../phrase/ManualBackupPhraseComponent.kt | 2 + .../phrase/di/ManualBackupPhraseModule.kt | 20 +++ .../phrase/model/ManualBackupPhraseModel.kt | 42 ++++- .../start/di/ManualBackupStartModule.kt | 20 +++ .../stepper/api/HotWalletStepperComponent.kt | 13 +- .../entry/DefaultWalletActivationComponent.kt | 92 +++++++++++ .../entry/WalletActivationModel.kt | 147 ++++++++++++++++++ .../WalletActivationStepperStateManager.kt | 91 +++++++++++ .../entry/di/WalletActivationModule.kt | 42 +++++ .../routing/WalletActivationChildFactory.kt | 85 ++++++++++ .../entry/routing/WalletActivationRoute.kt | 31 ++++ .../entry/ui/WalletActivationContent.kt | 42 +++++ .../api/PushNotificationsParams.kt | 3 + .../impl/model/PushNotificationsModel.kt | 7 +- .../intents/WalletWarningsClickIntents.kt | 3 +- .../domain/GetMultiWalletWarningsFactory.kt | 3 +- 27 files changed, 808 insertions(+), 71 deletions(-) create mode 100644 features/hot-wallet/api/src/main/kotlin/com/tangem/features/hotwallet/WalletActivationComponent.kt create mode 100644 features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/di/ManualBackupCheckModule.kt create mode 100644 features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/di/ManualBackupPhraseModule.kt create mode 100644 features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/start/di/ManualBackupStartModule.kt create mode 100644 features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/DefaultWalletActivationComponent.kt create mode 100644 features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/WalletActivationModel.kt create mode 100644 features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/WalletActivationStepperStateManager.kt create mode 100644 features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/di/WalletActivationModule.kt create mode 100644 features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/routing/WalletActivationChildFactory.kt create mode 100644 features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/routing/WalletActivationRoute.kt create mode 100644 features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/ui/WalletActivationContent.kt diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 436c6fcc3a..0f2952af12 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -14,6 +14,7 @@ import com.tangem.features.details.component.DetailsComponent import com.tangem.features.disclaimer.api.components.DisclaimerComponent import com.tangem.features.hotwallet.AddExistingWalletComponent import com.tangem.features.hotwallet.CreateMobileWalletComponent +import com.tangem.features.hotwallet.WalletActivationComponent import com.tangem.features.managetokens.component.ChooseManagedTokensComponent import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.component.ManageTokensSource @@ -91,6 +92,7 @@ internal class ChildFactory @Inject constructor( private val createWalletSelectionComponentFactory: CreateWalletSelectionComponent.Factory, private val createMobileWalletComponentFactory: CreateMobileWalletComponent.Factory, private val addExistingWalletComponentFactory: AddExistingWalletComponent.Factory, + private val walletActivationComponentFactory: WalletActivationComponent.Factory, private val sendWithSwapComponentFactory: SendWithSwapComponent.Factory, private val sendEntryPointComponentFactory: SendEntryPointComponent.Factory, private val walletConnectFeatureToggles: WalletConnectFeatureToggles, @@ -385,6 +387,7 @@ internal class ChildFactory @Inject constructor( context = context, params = PushNotificationsParams( modelCallbacks = PushNotificationsModelCallbacksStub(), + nextRoute = AppRoute.Home(), ), componentFactory = pushNotificationsComponentFactory, ) @@ -464,6 +467,15 @@ internal class ChildFactory @Inject constructor( componentFactory = addExistingWalletComponentFactory, ) } + is AppRoute.WalletActivation -> { + createComponentChild( + context = context, + params = WalletActivationComponent.Params( + userWalletId = route.userWalletId, + ), + componentFactory = walletActivationComponentFactory, + ) + } is AppRoute.SendEntryPoint -> { createComponentChild( context = context, diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index def139603e..f36983b866 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -301,6 +301,11 @@ sealed class AppRoute(val path: String) : Route { @Serializable object AddExistingWallet : AppRoute(path = "/add_existing_wallet") + @Serializable + data class WalletActivation( + val userWalletId: UserWalletId, + ) : AppRoute(path = "/wallet_activation/${userWalletId.stringValue}") + @Serializable data class SendEntryPoint( val userWalletId: UserWalletId, diff --git a/features/hot-wallet/api/src/main/kotlin/com/tangem/features/hotwallet/WalletActivationComponent.kt b/features/hot-wallet/api/src/main/kotlin/com/tangem/features/hotwallet/WalletActivationComponent.kt new file mode 100644 index 0000000000..8b99c19f36 --- /dev/null +++ b/features/hot-wallet/api/src/main/kotlin/com/tangem/features/hotwallet/WalletActivationComponent.kt @@ -0,0 +1,14 @@ +package com.tangem.features.hotwallet + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.models.wallet.UserWalletId + +interface WalletActivationComponent : ComposableContentComponent { + + data class Params( + val userWalletId: UserWalletId, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/AddExistingWalletModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/AddExistingWalletModel.kt index b404d14746..ca924d1eb8 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/AddExistingWalletModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/AddExistingWalletModel.kt @@ -13,8 +13,11 @@ import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletSt import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent import com.tangem.features.hotwallet.setaccesscode.AccessCodeComponent import com.tangem.features.hotwallet.setupfinished.MobileWalletSetupFinishedComponent +import com.tangem.features.hotwallet.stepper.api.HotWalletStepperComponent +import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import javax.inject.Inject @@ -25,30 +28,30 @@ internal class AddExistingWalletModel @Inject constructor( private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase, ) : Model() { + val hotWalletStepperComponentModelCallback = HotWalletStepperComponentModelCallback() val addExistingWalletStartModelCallbacks = AddExistingWalletStartModelCallbacks() val addExistingWalletImportModelCallbacks = AddExistingWalletImportModelCallbacks() val manualBackupCompletedComponentModelCallbacks = ManualBackupCompletedComponentModelCallbacks() val accessCodeModelCallbacks = AccessCodeModelCallbacks() + val pushNotificationsCallbacks = PushNotificationsCallbacks() val mobileWalletSetupFinishedComponentModelCallbacks = MobileWalletSetupFinishedComponentModelCallbacks() val stackNavigation = StackNavigation() + val startRoute = AddExistingWalletRoute.Start + val currentRoute: MutableStateFlow = MutableStateFlow(startRoute) - fun onChildBack(currentRoute: AddExistingWalletRoute) { - when (currentRoute) { + fun onChildBack() { + when (currentRoute.value) { + is AddExistingWalletRoute.Start -> router.pop() is AddExistingWalletRoute.Import -> stackNavigation.pop() is AddExistingWalletRoute.BackupCompleted -> Unit is AddExistingWalletRoute.SetAccessCode -> Unit is AddExistingWalletRoute.ConfirmAccessCode -> stackNavigation.pop() is AddExistingWalletRoute.PushNotifications -> Unit is AddExistingWalletRoute.SetupFinished -> Unit - is AddExistingWalletRoute.Start -> Unit } } - fun onSkipAccessCode() { - navigateToPushNotificationsOrNext() - } - private fun navigateToPushNotificationsOrNext() { modelScope.launch { val shouldRequestPush = shouldAskPermissionUseCase(PUSH_PERMISSION) @@ -62,6 +65,20 @@ internal class AddExistingWalletModel @Inject constructor( } } + private fun navigateToSetupFinished() { + stackNavigation.replaceAll(AddExistingWalletRoute.SetupFinished) + } + + inner class HotWalletStepperComponentModelCallback : HotWalletStepperComponent.ModelCallback { + override fun onBackClick() { + onChildBack() + } + + override fun onSkipClick() { + navigateToPushNotificationsOrNext() + } + } + inner class AddExistingWalletStartModelCallbacks : AddExistingWalletStartComponent.ModelCallbacks { override fun onBackClick() { router.pop() @@ -74,13 +91,13 @@ internal class AddExistingWalletModel @Inject constructor( inner class AddExistingWalletImportModelCallbacks : AddExistingWalletImportComponent.ModelCallbacks { override fun onWalletImported(userWalletId: UserWalletId) { - stackNavigation.replaceCurrent(AddExistingWalletRoute.BackupCompleted(userWalletId)) + stackNavigation.replaceAll(AddExistingWalletRoute.BackupCompleted(userWalletId)) } } inner class ManualBackupCompletedComponentModelCallbacks : ManualBackupCompletedComponent.ModelCallbacks { override fun onContinueClick(userWalletId: UserWalletId) { - stackNavigation.replaceCurrent(AddExistingWalletRoute.SetAccessCode(userWalletId)) + stackNavigation.replaceAll(AddExistingWalletRoute.SetAccessCode(userWalletId)) } } @@ -94,6 +111,20 @@ internal class AddExistingWalletModel @Inject constructor( } } + inner class PushNotificationsCallbacks : PushNotificationsModelCallbacks { + override fun onAllowSystemPermission() { + navigateToSetupFinished() + } + + override fun onDenySystemPermission() { + navigateToSetupFinished() + } + + override fun onDismiss() { + navigateToSetupFinished() + } + } + inner class MobileWalletSetupFinishedComponentModelCallbacks : MobileWalletSetupFinishedComponent.ModelCallbacks { override fun onContinueClick() { diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/DefaultAddExistingWalletComponent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/DefaultAddExistingWalletComponent.kt index 53e611282b..e1e673e034 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/DefaultAddExistingWalletComponent.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/DefaultAddExistingWalletComponent.kt @@ -6,19 +6,20 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.stack.childStack +import com.arkivanov.decompose.value.ObserveLifecycleMode +import com.arkivanov.decompose.value.subscribe import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.core.ui.extensions.TextReference import com.tangem.features.hotwallet.AddExistingWalletComponent import com.tangem.features.hotwallet.addexistingwallet.entry.routing.AddExistingWalletChildFactory -import com.tangem.features.hotwallet.addexistingwallet.entry.routing.AddExistingWalletRoute import com.tangem.features.hotwallet.addexistingwallet.entry.ui.AddExistingWalletContent import com.tangem.features.hotwallet.stepper.api.HotWalletStepperComponent import com.tangem.features.hotwallet.stepper.impl.DefaultHotWalletStepperComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlinx.coroutines.launch internal class DefaultAddExistingWalletComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, @@ -30,13 +31,11 @@ internal class DefaultAddExistingWalletComponent @AssistedInject constructor( private val model: AddExistingWalletModel = getOrCreateModel(params) - private val startRoute = AddExistingWalletRoute.Start - private val innerStack = childStack( key = "addExistingWalletInnerStack", source = model.stackNavigation, serializer = null, - initialConfiguration = startRoute, + initialConfiguration = model.startRoute, handleBackButton = true, childFactory = { configuration, factoryContext -> addExistingWalletChildFactory.createChild( @@ -50,32 +49,28 @@ internal class DefaultAddExistingWalletComponent @AssistedInject constructor( private val stepperComponent = stepperComponentFactory.create( context = this, params = HotWalletStepperComponent.Params( - initState = HotWalletStepperComponent.StepperUM( - currentStep = 0, - steps = 0, - title = TextReference.EMPTY, - showBackButton = false, - showSkipButton = false, - showFeedbackButton = false, - ), - callback = object : HotWalletStepperComponent.ModelCallback { - override fun onBackClick() { - onChildBack() - } - - override fun onSkipClick() { - model.onSkipAccessCode() - } - }, + initState = HotWalletStepperComponent.StepperUM.initialState(), + callback = model.hotWalletStepperComponentModelCallback, ), ) + init { + innerStack.subscribe( + lifecycle = lifecycle, + mode = ObserveLifecycleMode.CREATE_DESTROY, + ) { stack -> + componentScope.launch { + model.currentRoute.emit(stack.active.configuration) + } + } + } + @Composable override fun Content(modifier: Modifier) { val stackState by innerStack.subscribeAsState() val currentRoute = stackState.active.configuration - BackHandler(onBack = ::onChildBack) + BackHandler(onBack = model::onChildBack) val stepperState = stepperStateManager.getStepperState(currentRoute) stepperState?.let { stepperComponent.updateState(it) } @@ -86,17 +81,6 @@ internal class DefaultAddExistingWalletComponent @AssistedInject constructor( ) } - private fun onChildBack() { - val isEmptyStack = innerStack.value.backStack.isEmpty() - - if (isEmptyStack) { - router.pop() - } else { - val currentRoute = innerStack.value.active.configuration - model.onChildBack(currentRoute) - } - } - @AssistedFactory interface Factory : AddExistingWalletComponent.Factory { override fun create(context: AppComponentContext, params: Unit): DefaultAddExistingWalletComponent diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/routing/AddExistingWalletChildFactory.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/routing/AddExistingWalletChildFactory.kt index 710b2da90f..8a25cc1176 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/routing/AddExistingWalletChildFactory.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/routing/AddExistingWalletChildFactory.kt @@ -9,7 +9,6 @@ import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupComplete import com.tangem.features.hotwallet.setaccesscode.AccessCodeComponent import com.tangem.features.hotwallet.setupfinished.MobileWalletSetupFinishedComponent import com.tangem.features.pushnotifications.api.PushNotificationsComponent -import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacksStub import com.tangem.features.pushnotifications.api.PushNotificationsParams import javax.inject.Inject @@ -63,7 +62,7 @@ internal class AddExistingWalletChildFactory @Inject constructor( is AddExistingWalletRoute.PushNotifications -> pushNotificationsComponent.create( context = childContext, params = PushNotificationsParams( - modelCallbacks = PushNotificationsModelCallbacksStub(), + modelCallbacks = model.pushNotificationsCallbacks, ), ) is AddExistingWalletRoute.SetupFinished -> MobileWalletSetupFinishedComponent( diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt index a4a7a4f243..d59cf2e39f 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt @@ -48,7 +48,7 @@ internal class CreateMobileWalletModel @Inject constructor( saveUserWalletUseCase( hotUserWalletBuilder.build(), ) - router.push(AppRoute.Wallet) + router.replaceAll(AppRoute.Wallet) }.onFailure { Timber.e(it) diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/ManualBackupCheckComponent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/ManualBackupCheckComponent.kt index dfaf6fe787..7f25916f2d 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/ManualBackupCheckComponent.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/ManualBackupCheckComponent.kt @@ -7,7 +7,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.crypto.bip39.Mnemonic +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.features.hotwallet.manualbackup.check.model.ManualBackupCheckModel import com.tangem.features.hotwallet.manualbackup.check.ui.ManualBackupCheckContent import dagger.assisted.Assisted @@ -33,7 +33,7 @@ internal class ManualBackupCheckComponent @AssistedInject constructor( } data class Params( - val generatedWords: Mnemonic, + val userWalletId: UserWalletId, val callbacks: ModelCallbacks, ) } \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/di/ManualBackupCheckModule.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/di/ManualBackupCheckModule.kt new file mode 100644 index 0000000000..34c8d3bb8c --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/di/ManualBackupCheckModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.hotwallet.manualbackup.check.di + +import com.tangem.core.decompose.model.Model +import com.tangem.features.hotwallet.manualbackup.check.model.ManualBackupCheckModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface ManualBackupCheckModule { + + @Binds + @IntoMap + @ClassKey(ManualBackupCheckModel::class) + fun bindManualBackupCheckModel(model: ManualBackupCheckModel): Model +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/entity/ManualBackupCheckUM.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/entity/ManualBackupCheckUM.kt index 19eac1ac68..5d2bc48d20 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/entity/ManualBackupCheckUM.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/entity/ManualBackupCheckUM.kt @@ -2,10 +2,12 @@ package com.tangem.features.hotwallet.manualbackup.check.entity import androidx.compose.ui.text.input.TextFieldValue import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf internal data class ManualBackupCheckUM( val onCompleteButtonClick: () -> Unit, val wordFields: ImmutableList, + val words: ImmutableList = persistentListOf(), val completeButtonEnabled: Boolean, val completeButtonProgress: Boolean, ) { diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/model/ManualBackupCheckModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/model/ManualBackupCheckModel.kt index ffe2a0ad15..6d62bc73c9 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/model/ManualBackupCheckModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/check/model/ManualBackupCheckModel.kt @@ -2,16 +2,25 @@ package com.tangem.features.hotwallet.manualbackup.check.model import androidx.compose.runtime.Stable import androidx.compose.ui.text.input.TextFieldValue +import arrow.core.getOrElse import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.domain.wallets.usecase.UpdateWalletUseCase import com.tangem.features.hotwallet.manualbackup.check.ManualBackupCheckComponent import com.tangem.features.hotwallet.manualbackup.check.entity.ManualBackupCheckUM +import com.tangem.hot.sdk.TangemHotSdk +import com.tangem.hot.sdk.model.HotAuth +import com.tangem.hot.sdk.model.UnlockHotWallet import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject import kotlin.Boolean import kotlin.Int @@ -20,13 +29,15 @@ import kotlin.Suppress import kotlin.collections.List import kotlin.collections.all import kotlin.collections.map -import kotlin.error @Stable @ModelScoped internal class ManualBackupCheckModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, + private val getUserWalletUseCase: GetUserWalletUseCase, + private val updateWalletUseCase: UpdateWalletUseCase, + private val tangemHotSdk: TangemHotSdk, ) : Model() { private val params = paramsContainer.require() @@ -35,6 +46,28 @@ internal class ManualBackupCheckModel @Inject constructor( internal val uiState: StateFlow field = MutableStateFlow(getInitialUIState()) + init { + modelScope.launch { + runCatching { + val userWallet = getUserWalletUseCase(params.userWalletId) + .getOrElse { error("User wallet with id ${params.userWalletId} not found") } + if (userWallet is UserWallet.Hot) { + val unlockHotWallet = UnlockHotWallet(userWallet.hotWalletId, HotAuth.NoAuth) + val seedPhrasePrivateInfo = tangemHotSdk.exportMnemonic(unlockHotWallet) + uiState.update { + it.copy( + words = seedPhrasePrivateInfo.mnemonic.mnemonicComponents.filterIndexed { index, _ -> + WORD_FIELD_INDICES.contains(index + 1) + }.toImmutableList(), + ) + } + } + }.onFailure { + Timber.e(it) + } + } + } + @Suppress("MagicNumber") private fun getInitialUIState(): ManualBackupCheckUM { val wordFields = List(WORD_FIELD_INDICES.size) { index -> @@ -57,12 +90,37 @@ internal class ManualBackupCheckModel @Inject constructor( onCompleteButtonClick = { val currentUIState = uiState.value if (currentUIState.completeButtonEnabled) { - callbacks.onCompleteClick() + backupWallet() } }, ) } + private fun backupWallet() { + modelScope.launch { + uiState.update { + it.copy(completeButtonProgress = true) + } + + runCatching { + val userWallet = getUserWalletUseCase(params.userWalletId) + .getOrElse { error("User wallet with id ${params.userWalletId} not found") } + if (userWallet is UserWallet.Hot) { + updateWalletUseCase(userWallet.walletId) { + userWallet.copy(backedUp = true) + } + callbacks.onCompleteClick() + } + }.onFailure { + Timber.e(it) + + uiState.update { + it.copy(completeButtonProgress = false) + } + } + } + } + private fun updateWordField(shownIndex: Int, newText: TextFieldValue) { uiState.update { currentState -> val updatedFields = currentState.wordFields.map { wordField -> @@ -89,14 +147,9 @@ internal class ManualBackupCheckModel @Inject constructor( } private fun checkWordField(word: String, shownIndex: Int): Boolean { - val generatedWords = params.generatedWords - val wordList = generatedWords.mnemonicComponents - - return if (shownIndex <= wordList.size) { - wordList[shownIndex - 1] == word - } else { - false - } + val words = uiState.value.words + val listIndex = WORD_FIELD_INDICES.indexOf(shownIndex) + return words.getOrNull(listIndex)?.let { word == it } == true } companion object { diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/ManualBackupPhraseComponent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/ManualBackupPhraseComponent.kt index 1dd8b360fc..62d7b79ed4 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/ManualBackupPhraseComponent.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/ManualBackupPhraseComponent.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.features.hotwallet.manualbackup.phrase.model.ManualBackupPhraseModel import com.tangem.features.hotwallet.manualbackup.phrase.ui.ManualBackupPhraseContent import dagger.assisted.Assisted @@ -32,6 +33,7 @@ internal class ManualBackupPhraseComponent @AssistedInject constructor( } data class Params( + val userWalletId: UserWalletId, val callbacks: ModelCallbacks, ) } \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/di/ManualBackupPhraseModule.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/di/ManualBackupPhraseModule.kt new file mode 100644 index 0000000000..3b5c8c8018 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/di/ManualBackupPhraseModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.hotwallet.manualbackup.phrase.di + +import com.tangem.core.decompose.model.Model +import com.tangem.features.hotwallet.manualbackup.phrase.model.ManualBackupPhraseModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface ManualBackupPhraseModule { + + @Binds + @IntoMap + @ClassKey(ManualBackupPhraseModel::class) + fun bindManualBackupPhraseModel(model: ManualBackupPhraseModel): Model +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/model/ManualBackupPhraseModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/model/ManualBackupPhraseModel.kt index 4320a86026..e9625260ad 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/model/ManualBackupPhraseModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/model/ManualBackupPhraseModel.kt @@ -1,14 +1,24 @@ package com.tangem.features.hotwallet.manualbackup.phrase.model import androidx.compose.runtime.Stable +import arrow.core.getOrElse import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.hotwallet.manualbackup.phrase.ManualBackupPhraseComponent import com.tangem.features.hotwallet.manualbackup.phrase.entity.ManualBackupPhraseUM +import com.tangem.hot.sdk.TangemHotSdk +import com.tangem.hot.sdk.model.HotAuth +import com.tangem.hot.sdk.model.UnlockHotWallet import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @Stable @@ -16,17 +26,39 @@ import javax.inject.Inject internal class ManualBackupPhraseModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, + private val getUserWalletUseCase: GetUserWalletUseCase, + private val tangemHotSdk: TangemHotSdk, ) : Model() { private val params = paramsContainer.require() private val callbacks = params.callbacks internal val uiState: StateFlow - field = MutableStateFlow(getInitialUIState()) - - private fun getInitialUIState(): ManualBackupPhraseUM { - return ManualBackupPhraseUM( + field = MutableStateFlow( + ManualBackupPhraseUM( onContinueClick = callbacks::onContinueClick, - ) + ), + ) + + init { + modelScope.launch { + runCatching { + val userWallet = getUserWalletUseCase(params.userWalletId) + .getOrElse { error("User wallet with id ${params.userWalletId} not found") } + if (userWallet is UserWallet.Hot) { + val unlockHotWallet = UnlockHotWallet(userWallet.hotWalletId, HotAuth.NoAuth) + val seedPhrasePrivateInfo = tangemHotSdk.exportMnemonic(unlockHotWallet) + uiState.update { + it.copy( + words = seedPhrasePrivateInfo.mnemonic.mnemonicComponents.mapIndexed { index, s -> + ManualBackupPhraseUM.MnemonicGridItem(index + 1, s) + }.toImmutableList(), + ) + } + } + }.onFailure { + Timber.e(it) + } + } } } \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/start/di/ManualBackupStartModule.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/start/di/ManualBackupStartModule.kt new file mode 100644 index 0000000000..2f506e7b30 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/start/di/ManualBackupStartModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.hotwallet.manualbackup.start.di + +import com.tangem.core.decompose.model.Model +import com.tangem.features.hotwallet.manualbackup.start.ManualBackupStartModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface ManualBackupStartModule { + + @Binds + @IntoMap + @ClassKey(ManualBackupStartModel::class) + fun bindManualBackupStartModel(model: ManualBackupStartModel): Model +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/stepper/api/HotWalletStepperComponent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/stepper/api/HotWalletStepperComponent.kt index c14de91c72..169d037699 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/stepper/api/HotWalletStepperComponent.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/stepper/api/HotWalletStepperComponent.kt @@ -15,7 +15,18 @@ interface HotWalletStepperComponent : ComposableContentComponent { val showBackButton: Boolean, val showSkipButton: Boolean, val showFeedbackButton: Boolean, - ) + ) { + companion object { + fun initialState() = StepperUM( + currentStep = 0, + steps = 0, + title = TextReference.EMPTY, + showBackButton = false, + showSkipButton = false, + showFeedbackButton = false, + ) + } + } interface ModelCallback { fun onBackClick() diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/DefaultWalletActivationComponent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/DefaultWalletActivationComponent.kt new file mode 100644 index 0000000000..f24a1ea128 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/DefaultWalletActivationComponent.kt @@ -0,0 +1,92 @@ +package com.tangem.features.hotwallet.walletactivation.entry + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.arkivanov.decompose.router.stack.childStack +import com.arkivanov.decompose.value.ObserveLifecycleMode +import com.arkivanov.decompose.value.subscribe +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.childByContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.features.hotwallet.WalletActivationComponent +import com.tangem.features.hotwallet.stepper.api.HotWalletStepperComponent +import com.tangem.features.hotwallet.stepper.impl.DefaultHotWalletStepperComponent +import com.tangem.features.hotwallet.walletactivation.entry.routing.WalletActivationChildFactory +import com.tangem.features.hotwallet.walletactivation.entry.ui.WalletActivationContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.launch + +internal class DefaultWalletActivationComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted private val params: WalletActivationComponent.Params, + private val stepperStateManager: WalletActivationStepperStateManager, + walletActivationChildFactory: WalletActivationChildFactory, + stepperComponentFactory: DefaultHotWalletStepperComponent.Factory, +) : WalletActivationComponent, AppComponentContext by appComponentContext { + + private val model: WalletActivationModel = getOrCreateModel(params) + + private val innerStack = childStack( + key = "walletActivationInnerStack", + source = model.stackNavigation, + serializer = null, + initialConfiguration = model.startRoute, + handleBackButton = true, + childFactory = { configuration, factoryContext -> + walletActivationChildFactory.createChild( + route = configuration, + childContext = childByContext(factoryContext), + model = model, + ) + }, + ) + + private val stepperComponent = stepperComponentFactory.create( + context = this, + params = HotWalletStepperComponent.Params( + initState = HotWalletStepperComponent.StepperUM.initialState(), + callback = model.hotWalletStepperComponentModelCallback, + ), + ) + + init { + innerStack.subscribe( + lifecycle = lifecycle, + mode = ObserveLifecycleMode.CREATE_DESTROY, + ) { stack -> + componentScope.launch { + model.currentRoute.emit(stack.active.configuration) + } + } + } + + @Composable + override fun Content(modifier: Modifier) { + val stackState by innerStack.subscribeAsState() + val currentRoute = stackState.active.configuration + + BackHandler(onBack = model::onChildBack) + + val stepperState = stepperStateManager.getStepperState(currentRoute) + stepperState?.let { stepperComponent.updateState(it) } + + WalletActivationContent( + stackState = stackState, + stepperComponent = stepperComponent.takeIf { stepperState != null }, + modifier = modifier, + ) + } + + @AssistedFactory + interface Factory : WalletActivationComponent.Factory { + override fun create( + context: AppComponentContext, + params: WalletActivationComponent.Params, + ): DefaultWalletActivationComponent + } +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/WalletActivationModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/WalletActivationModel.kt new file mode 100644 index 0000000000..3d03a96a04 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/WalletActivationModel.kt @@ -0,0 +1,147 @@ +package com.tangem.features.hotwallet.walletactivation.entry + +import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.decompose.router.stack.pop +import com.arkivanov.decompose.router.stack.push +import com.arkivanov.decompose.router.stack.replaceAll +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.navigation.Router +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.settings.ShouldAskPermissionUseCase +import com.tangem.features.hotwallet.manualbackup.check.ManualBackupCheckComponent +import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent +import com.tangem.features.hotwallet.manualbackup.phrase.ManualBackupPhraseComponent +import com.tangem.features.hotwallet.manualbackup.start.ManualBackupStartComponent +import com.tangem.features.hotwallet.setaccesscode.AccessCodeComponent +import com.tangem.features.hotwallet.setupfinished.MobileWalletSetupFinishedComponent +import com.tangem.features.hotwallet.walletactivation.entry.routing.WalletActivationRoute +import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION +import com.tangem.features.hotwallet.WalletActivationComponent +import com.tangem.features.hotwallet.stepper.api.HotWalletStepperComponent +import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@ModelScoped +internal class WalletActivationModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase, +) : Model() { + + val params = paramsContainer.require() + + val hotWalletStepperComponentModelCallback = HotWalletStepperComponentModelCallback() + val manualBackupStartModelCallbacks = ManualBackupStartModelCallbacks() + val manualBackupPhraseModelCallbacks = ManualBackupPhraseModelCallbacks() + val manualBackupCheckModelCallbacks = ManualBackupCheckModelCallbacks() + val manualBackupCompletedModelCallbacks = ManualBackupCompletedModelCallbacks() + val accessCodeModelCallbacks = AccessCodeModelCallbacks() + val pushNotificationsCallbacks = PushNotificationsCallbacks() + val mobileWalletSetupFinishedModelCallbacks = MobileWalletSetupFinishedModelCallbacks() + + val stackNavigation = StackNavigation() + val startRoute = WalletActivationRoute.ManualBackupStart + val currentRoute: MutableStateFlow = MutableStateFlow(startRoute) + + fun onChildBack() { + when (currentRoute.value) { + is WalletActivationRoute.ManualBackupStart -> router.pop() + is WalletActivationRoute.ManualBackupPhrase -> stackNavigation.pop() + is WalletActivationRoute.ManualBackupCheck -> stackNavigation.pop() + is WalletActivationRoute.ManualBackupCompleted -> Unit + is WalletActivationRoute.SetAccessCode -> Unit + is WalletActivationRoute.ConfirmAccessCode -> Unit + is WalletActivationRoute.PushNotifications -> Unit + is WalletActivationRoute.SetupFinished -> Unit + } + } + + private fun navigateToPushNotificationsOrNext() { + modelScope.launch { + val shouldRequestPush = shouldAskPermissionUseCase(PUSH_PERMISSION) + if (shouldRequestPush) { + // is yet blocked by [REDACTED_TASK_KEY] + // stackNavigation.replaceAll(AddExistingWalletRoute.PushNotifications) + stackNavigation.replaceAll(WalletActivationRoute.SetupFinished) + } else { + stackNavigation.replaceAll(WalletActivationRoute.SetupFinished) + } + } + } + + private fun navigateToSetupFinished() { + stackNavigation.replaceAll(WalletActivationRoute.SetupFinished) + } + + inner class HotWalletStepperComponentModelCallback : HotWalletStepperComponent.ModelCallback { + override fun onBackClick() { + onChildBack() + } + + override fun onSkipClick() { + navigateToPushNotificationsOrNext() + } + } + + inner class ManualBackupStartModelCallbacks : ManualBackupStartComponent.ModelCallbacks { + override fun onContinueClick() { + stackNavigation.push(WalletActivationRoute.ManualBackupPhrase) + } + } + + inner class ManualBackupPhraseModelCallbacks : ManualBackupPhraseComponent.ModelCallbacks { + override fun onContinueClick() { + stackNavigation.push( + WalletActivationRoute.ManualBackupCheck, + ) + } + } + + inner class ManualBackupCheckModelCallbacks : ManualBackupCheckComponent.ModelCallbacks { + override fun onCompleteClick() { + stackNavigation.push(WalletActivationRoute.ManualBackupCompleted) + } + } + + inner class ManualBackupCompletedModelCallbacks : ManualBackupCompletedComponent.ModelCallbacks { + override fun onContinueClick(userWalletId: UserWalletId) { + stackNavigation.push(WalletActivationRoute.SetAccessCode) + } + } + + inner class AccessCodeModelCallbacks : AccessCodeComponent.ModelCallbacks { + override fun onAccessCodeSet(userWalletId: UserWalletId, accessCode: String) { + stackNavigation.push(WalletActivationRoute.ConfirmAccessCode(accessCode)) + } + + override fun onAccessCodeConfirmed(userWalletId: UserWalletId) { + navigateToPushNotificationsOrNext() + } + } + + inner class PushNotificationsCallbacks : PushNotificationsModelCallbacks { + override fun onAllowSystemPermission() { + navigateToSetupFinished() + } + + override fun onDenySystemPermission() { + navigateToSetupFinished() + } + + override fun onDismiss() { + navigateToSetupFinished() + } + } + + inner class MobileWalletSetupFinishedModelCallbacks : MobileWalletSetupFinishedComponent.ModelCallbacks { + override fun onContinueClick() { + router.pop() + } + } +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/WalletActivationStepperStateManager.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/WalletActivationStepperStateManager.kt new file mode 100644 index 0000000000..5a33b36b87 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/WalletActivationStepperStateManager.kt @@ -0,0 +1,91 @@ +package com.tangem.features.hotwallet.walletactivation.entry + +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.hotwallet.impl.R +import com.tangem.features.hotwallet.stepper.api.HotWalletStepperComponent +import com.tangem.features.hotwallet.walletactivation.entry.routing.WalletActivationRoute +import javax.inject.Inject + +internal class WalletActivationStepperStateManager @Inject constructor() { + + fun getStepperState(route: WalletActivationRoute): HotWalletStepperComponent.StepperUM? { + return when (route) { + is WalletActivationRoute.ManualBackupStart -> HotWalletStepperComponent.StepperUM( + currentStep = STEP_BACKUP, + steps = STEPS_COUNT, + title = resourceReference(R.string.common_backup), + showBackButton = true, + showSkipButton = false, + showFeedbackButton = true, + ) + is WalletActivationRoute.ManualBackupPhrase -> HotWalletStepperComponent.StepperUM( + currentStep = STEP_BACKUP_PHRASE, + steps = STEPS_COUNT, + title = resourceReference(R.string.common_backup), + showBackButton = true, + showSkipButton = false, + showFeedbackButton = true, + ) + is WalletActivationRoute.ManualBackupCheck -> HotWalletStepperComponent.StepperUM( + currentStep = STEP_BACKUP_CHECK, + steps = STEPS_COUNT, + title = resourceReference(R.string.common_backup), + showBackButton = true, + showSkipButton = false, + showFeedbackButton = true, + ) + is WalletActivationRoute.ManualBackupCompleted -> HotWalletStepperComponent.StepperUM( + currentStep = STEP_BACKUP_COMPLETED, + steps = STEPS_COUNT, + title = resourceReference(R.string.common_backup), + showBackButton = false, + showSkipButton = false, + showFeedbackButton = false, + ) + is WalletActivationRoute.SetAccessCode -> HotWalletStepperComponent.StepperUM( + currentStep = STEP_ACCESS_CODE, + steps = STEPS_COUNT, + title = resourceReference(R.string.access_code_navtitle), + showBackButton = false, + showSkipButton = true, + showFeedbackButton = false, + ) + is WalletActivationRoute.ConfirmAccessCode -> HotWalletStepperComponent.StepperUM( + currentStep = STEP_ACCESS_CODE, + steps = STEPS_COUNT, + title = resourceReference(R.string.access_code_navtitle), + showBackButton = true, + showSkipButton = false, + showFeedbackButton = false, + ) + is WalletActivationRoute.PushNotifications -> HotWalletStepperComponent.StepperUM( + currentStep = STEP_NOTIFICATIONS, + steps = STEPS_COUNT, + title = resourceReference(R.string.onboarding_title_notifications), + showBackButton = false, + showSkipButton = false, + showFeedbackButton = false, + ) + is WalletActivationRoute.SetupFinished -> HotWalletStepperComponent.StepperUM( + currentStep = STEP_DONE, + steps = STEPS_COUNT, + title = resourceReference(R.string.common_done), + showBackButton = false, + showSkipButton = false, + showFeedbackButton = false, + ) + } + } + + companion object { + private const val STEPS_COUNT = 7 + + private const val STEP_BACKUP = 1 + private const val STEP_BACKUP_PHRASE = 2 + private const val STEP_BACKUP_CHECK = 3 + private const val STEP_BACKUP_COMPLETED = 4 + private const val STEP_ACCESS_CODE = 5 + private const val STEP_NOTIFICATIONS = 6 + private const val STEP_DONE = 7 + } +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/di/WalletActivationModule.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/di/WalletActivationModule.kt new file mode 100644 index 0000000000..51362cff09 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/di/WalletActivationModule.kt @@ -0,0 +1,42 @@ +package com.tangem.features.hotwallet.walletactivation.entry.di + +import com.tangem.core.decompose.model.Model +import com.tangem.features.hotwallet.WalletActivationComponent +import com.tangem.features.hotwallet.walletactivation.entry.DefaultWalletActivationComponent +import com.tangem.features.hotwallet.walletactivation.entry.WalletActivationModel +import com.tangem.features.hotwallet.walletactivation.entry.WalletActivationStepperStateManager +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface WalletActivationModuleBinds { + + @Binds + @Singleton + fun bindWalletActivationComponentFactory( + impl: DefaultWalletActivationComponent.Factory, + ): WalletActivationComponent.Factory + + @Binds + @IntoMap + @ClassKey(WalletActivationModel::class) + fun bindWalletActivationModel(model: WalletActivationModel): Model +} + +@Module +@InstallIn(SingletonComponent::class) +internal object WalletActivationModule { + + @Provides + @Singleton + fun provideWalletActivationStepperStateManager(): WalletActivationStepperStateManager { + return WalletActivationStepperStateManager() + } +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/routing/WalletActivationChildFactory.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/routing/WalletActivationChildFactory.kt new file mode 100644 index 0000000000..2b93835929 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/routing/WalletActivationChildFactory.kt @@ -0,0 +1,85 @@ +package com.tangem.features.hotwallet.walletactivation.entry.routing + +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.features.hotwallet.manualbackup.check.ManualBackupCheckComponent +import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent +import com.tangem.features.hotwallet.manualbackup.phrase.ManualBackupPhraseComponent +import com.tangem.features.hotwallet.manualbackup.start.ManualBackupStartComponent +import com.tangem.features.hotwallet.setaccesscode.AccessCodeComponent +import com.tangem.features.hotwallet.setupfinished.MobileWalletSetupFinishedComponent +import com.tangem.features.hotwallet.walletactivation.entry.WalletActivationModel +import com.tangem.features.pushnotifications.api.PushNotificationsComponent +import com.tangem.features.pushnotifications.api.PushNotificationsParams +import javax.inject.Inject + +internal class WalletActivationChildFactory @Inject constructor( + private val pushNotificationsComponent: PushNotificationsComponent.Factory, + private val accessCodeComponentFactory: AccessCodeComponent.Factory, +) { + + fun createChild( + route: WalletActivationRoute, + childContext: AppComponentContext, + model: WalletActivationModel, + ): ComposableContentComponent { + return when (route) { + is WalletActivationRoute.ManualBackupStart -> ManualBackupStartComponent( + context = childContext, + params = ManualBackupStartComponent.Params( + callbacks = model.manualBackupStartModelCallbacks, + ), + ) + is WalletActivationRoute.ManualBackupPhrase -> ManualBackupPhraseComponent( + context = childContext, + params = ManualBackupPhraseComponent.Params( + userWalletId = model.params.userWalletId, + callbacks = model.manualBackupPhraseModelCallbacks, + ), + ) + is WalletActivationRoute.ManualBackupCheck -> ManualBackupCheckComponent( + context = childContext, + params = ManualBackupCheckComponent.Params( + userWalletId = model.params.userWalletId, + callbacks = model.manualBackupCheckModelCallbacks, + ), + ) + is WalletActivationRoute.ManualBackupCompleted -> ManualBackupCompletedComponent( + context = childContext, + params = ManualBackupCompletedComponent.Params( + userWalletId = model.params.userWalletId, + callbacks = model.manualBackupCompletedModelCallbacks, + ), + ) + is WalletActivationRoute.SetAccessCode -> accessCodeComponentFactory.create( + context = childContext, + params = AccessCodeComponent.Params( + isConfirmMode = false, + userWalletId = model.params.userWalletId, + callbacks = model.accessCodeModelCallbacks, + ), + ) + is WalletActivationRoute.ConfirmAccessCode -> accessCodeComponentFactory.create( + context = childContext, + params = AccessCodeComponent.Params( + isConfirmMode = true, + accessCodeToConfirm = route.accessCode, + userWalletId = model.params.userWalletId, + callbacks = model.accessCodeModelCallbacks, + ), + ) + is WalletActivationRoute.PushNotifications -> pushNotificationsComponent.create( + context = childContext, + params = PushNotificationsParams( + modelCallbacks = model.pushNotificationsCallbacks, + ), + ) + is WalletActivationRoute.SetupFinished -> MobileWalletSetupFinishedComponent( + context = childContext, + params = MobileWalletSetupFinishedComponent.Params( + callbacks = model.mobileWalletSetupFinishedModelCallbacks, + ), + ) + } + } +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/routing/WalletActivationRoute.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/routing/WalletActivationRoute.kt new file mode 100644 index 0000000000..26b6998a2e --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/routing/WalletActivationRoute.kt @@ -0,0 +1,31 @@ +package com.tangem.features.hotwallet.walletactivation.entry.routing + +import com.tangem.core.decompose.navigation.Route +import kotlinx.serialization.Serializable + +internal sealed class WalletActivationRoute : Route { + + @Serializable + object ManualBackupStart : WalletActivationRoute() + + @Serializable + object ManualBackupPhrase : WalletActivationRoute() + + @Serializable + data object ManualBackupCheck : WalletActivationRoute() + + @Serializable + object ManualBackupCompleted : WalletActivationRoute() + + @Serializable + data object SetAccessCode : WalletActivationRoute() + + @Serializable + data class ConfirmAccessCode(val accessCode: String) : WalletActivationRoute() + + @Serializable + object PushNotifications : WalletActivationRoute() + + @Serializable + object SetupFinished : WalletActivationRoute() +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/ui/WalletActivationContent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/ui/WalletActivationContent.kt new file mode 100644 index 0000000000..0c2e2719e9 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/ui/WalletActivationContent.kt @@ -0,0 +1,42 @@ +package com.tangem.features.hotwallet.walletactivation.entry.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.arkivanov.decompose.router.stack.ChildStack +import com.arkivanov.decompose.extensions.compose.stack.Children +import com.arkivanov.decompose.extensions.compose.stack.animation.slide +import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.hotwallet.stepper.api.HotWalletStepperComponent +import com.tangem.features.hotwallet.walletactivation.entry.routing.WalletActivationRoute + +@Composable +internal fun WalletActivationContent( + stackState: ChildStack, + stepperComponent: HotWalletStepperComponent?, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(color = TangemTheme.colors.background.primary) + .fillMaxSize() + .imePadding() + .systemBarsPadding(), + ) { + stepperComponent?.Content(Modifier) + + Children( + stack = stackState, + animation = stackAnimation(slide()), + modifier = Modifier.fillMaxSize(), + ) { + it.instance.Content(Modifier.fillMaxSize()) + } + } +} \ No newline at end of file diff --git a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt index 27bc4d7131..4e152b9c09 100644 --- a/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt +++ b/features/push-notifications/api/src/main/java/com/tangem/features/pushnotifications/api/PushNotificationsParams.kt @@ -1,6 +1,9 @@ package com.tangem.features.pushnotifications.api +import com.tangem.common.routing.AppRoute + data class PushNotificationsParams( val isBottomSheet: Boolean = false, + val nextRoute: AppRoute? = null, val modelCallbacks: PushNotificationsModelCallbacks, ) \ No newline at end of file diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt index 735932ce2c..9a6115aa48 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt @@ -1,7 +1,6 @@ package com.tangem.features.pushnotifications.impl.model import androidx.compose.runtime.Stable -import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam @@ -71,7 +70,7 @@ internal class PushNotificationsModel @Inject constructor( neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) params.modelCallbacks.onDenySystemPermission() if (!params.isBottomSheet) { - appRouter.push(AppRoute.Home()) + params.nextRoute?.let { appRouter.push(it) } } } } @@ -85,7 +84,7 @@ internal class PushNotificationsModel @Inject constructor( neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) params.modelCallbacks.onAllowSystemPermission() if (!params.isBottomSheet) { - appRouter.push(AppRoute.Home()) + params.nextRoute?.let { appRouter.push(it) } } } } @@ -99,7 +98,7 @@ internal class PushNotificationsModel @Inject constructor( neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) params.modelCallbacks.onDenySystemPermission() if (!params.isBottomSheet) { - appRouter.push(AppRoute.Home()) + params.nextRoute?.let { appRouter.push(it) } } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt index fa98ada4f2..5e3757b744 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt @@ -387,7 +387,8 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( } override fun onFinishWalletActivationClick() { - // TODO implement wallet activation process + val userWallet = getSelectedUserWallet() ?: return + appRouter.push(AppRoute.WalletActivation(userWallet.walletId)) } private suspend fun fetchCryptoCurrencies(userWalletId: UserWalletId, currencies: List) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index 4427d9de7e..248f54a3e6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -266,8 +266,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( ) { if (userWallet !is UserWallet.Hot) return - // TODO [REDACTED_TASK_KEY] set an actual value - val shouldShowFinishActivation = false + val shouldShowFinishActivation = !userWallet.backedUp addIf( element = WalletNotification.FinishWalletActivation(