Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-28 13:52:54 +03:00
parent f4198d9a68
commit 78770cae44
22 changed files with 177 additions and 63 deletions

View file

@ -26,6 +26,7 @@ import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
import com.tangem.tap.features.saveWallet.redux.SaveWalletAction
import com.tangem.tap.mainScope
import com.tangem.tap.proxy.redux.DaggerGraphState
@ -67,10 +68,21 @@ object OnboardingHelper {
}
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
if (store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled) {
val newOnboardingSupportTypes = scanResponse.productType == ProductType.Wallet2 ||
scanResponse.productType == ProductType.Ring ||
scanResponse.productType == ProductType.Wallet
if (store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled &&
newOnboardingSupportTypes
) {
val backupState = store.state.onboardingWalletState.backupState
return AppRoute.Onboarding(
scanResponse = scanResponse,
startFromBackup = false,
mode = when (backupState.startedSource) {
BackupStartedSource.Onboarding -> AppRoute.Onboarding.Mode.Onboarding
BackupStartedSource.CreateBackup -> AppRoute.Onboarding.Mode.AddBackup
},
)
}

View file

@ -235,6 +235,10 @@ internal class ChildFactory @Inject constructor(
params = OnboardingEntryComponent.Params(
scanResponse = route.scanResponse,
startBackupFlow = route.startFromBackup,
multiWalletMode = when (route.mode) {
AppRoute.Onboarding.Mode.Onboarding -> OnboardingEntryComponent.MultiWalletMode.Onboarding
AppRoute.Onboarding.Mode.AddBackup -> OnboardingEntryComponent.MultiWalletMode.AddBackup
},
),
componentFactory = onboardingEntryComponentFactory,
)

View file

@ -329,5 +329,12 @@ sealed class AppRoute(val path: String) : Route {
data class Onboarding(
val scanResponse: ScanResponse,
val startFromBackup: Boolean,
) : AppRoute(path = "/onboarding_v2${if (startFromBackup) "/backup" else ""}")
val mode: Mode = Mode.Onboarding,
) : AppRoute(path = "/onboarding_v2${if (startFromBackup) "/backup" else ""}") {
enum class Mode {
Onboarding, // general Mode
AddBackup, // continue backup process for existing wallet 1
}
}
}

View file

@ -9,7 +9,13 @@ interface OnboardingEntryComponent : ComposableContentComponent {
data class Params(
val scanResponse: ScanResponse,
val startBackupFlow: Boolean,
val multiWalletMode: MultiWalletMode,
)
enum class MultiWalletMode {
Onboarding,
AddBackup,
}
interface Factory : ComponentFactory<Params, OnboardingEntryComponent>
}

View file

@ -20,7 +20,6 @@ import com.tangem.core.ui.components.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.onboarding.v2.impl.R
import kotlinx.coroutines.delay
@Composable
internal fun OnboardingDone(onContinueClick: () -> Unit, modifier: Modifier = Modifier) {
@ -85,7 +84,6 @@ internal fun OnboardingDone(onContinueClick: () -> Unit, modifier: Modifier = Mo
}
LaunchedEffect(Unit) {
delay(timeMillis = 500)
showConfetti = true
}

View file

@ -1,5 +1,6 @@
package com.tangem.features.onboarding.v2.entry.impl
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
@ -48,7 +49,7 @@ internal class DefaultOnboardingEntryComponent @AssistedInject constructor(
popBack = {
popInternal { success ->
if (success.not()) {
model.stackNavigation.pop() // TODO
model.stackNavigation.pop()
}
}
},
@ -126,6 +127,9 @@ internal class DefaultOnboardingEntryComponent @AssistedInject constructor(
override fun Content(modifier: Modifier) {
val innerStackState by innerStack.subscribeAsState()
// prevent back navigation
BackHandler { }
OnboardingEntry(
modifier = modifier,
stepperContent = { modifierParam ->

View file

@ -16,6 +16,7 @@ import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.onboarding.v2.TitleProvider
import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent
import com.tangem.features.onboarding.v2.entry.impl.routing.OnboardingRoute
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.NonCancellable
@ -46,6 +47,11 @@ internal class OnboardingEntryModel @Inject constructor(
val startRoute = routeByProductType(params.scanResponse)
private fun routeByProductType(scanResponse: ScanResponse): OnboardingRoute {
val multiWalletNavigationMode = when (params.multiWalletMode) {
OnboardingEntryComponent.MultiWalletMode.Onboarding -> OnboardingMultiWalletComponent.Mode.Onboarding
OnboardingEntryComponent.MultiWalletMode.AddBackup -> OnboardingMultiWalletComponent.Mode.AddBackup
}
return when (scanResponse.productType) {
ProductType.Note -> TODO()
ProductType.Twins -> TODO()
@ -54,6 +60,7 @@ internal class OnboardingEntryModel @Inject constructor(
withSeedPhraseFlow = false,
titleProvider = titleProvider,
onDone = ::onMultiWalletOnboardingDone,
mode = multiWalletNavigationMode,
)
ProductType.Ring,
ProductType.Wallet2,
@ -62,6 +69,7 @@ internal class OnboardingEntryModel @Inject constructor(
withSeedPhraseFlow = true,
titleProvider = titleProvider,
onDone = ::onMultiWalletOnboardingDone,
mode = multiWalletNavigationMode,
)
ProductType.Start2Coin -> TODO()
ProductType.Visa -> TODO()

View file

@ -21,6 +21,7 @@ internal class OnboardingChildFactory @Inject constructor(
withSeedPhraseFlow = route.withSeedPhraseFlow,
titleProvider = route.titleProvider,
onDone = route.onDone,
mode = route.mode,
),
)
is OnboardingRoute.ManageTokens -> onboardingManageTokensComponentFactory.create(

View file

@ -4,6 +4,7 @@ import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.onboarding.v2.TitleProvider
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
sealed class OnboardingRoute : Route {
@ -13,6 +14,7 @@ sealed class OnboardingRoute : Route {
val titleProvider: TitleProvider,
val scanResponse: ScanResponse,
val withSeedPhraseFlow: Boolean,
val mode: OnboardingMultiWalletComponent.Mode,
val onDone: (UserWallet) -> Unit,
) : OnboardingRoute()

View file

@ -13,8 +13,14 @@ interface OnboardingMultiWalletComponent : ComposableContentComponent, InnerNavi
val titleProvider: TitleProvider,
val scanResponse: ScanResponse,
val withSeedPhraseFlow: Boolean,
val mode: Mode,
val onDone: (UserWallet) -> Unit,
)
enum class Mode {
Onboarding,
AddBackup,
}
interface Factory : ComponentFactory<Params, OnboardingMultiWalletComponent>
}

View file

@ -1,5 +1,6 @@
package com.tangem.features.onboarding.v2.multiwallet.impl
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
@ -93,13 +94,10 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
override val state = innerNavigationStateFlow
override fun pop(onComplete: (Boolean) -> Unit) {
when (childStack.active.configuration) {
CreateWallet -> { } // TODO show dialog
ChooseBackupOption -> { } // TODO show dialog
SeedPhrase -> { componentScope.launch { backButtonClickFlow.emit(Unit) } }
AddBackupDevice -> {}
Finalize -> {}
Done -> {}
if (childStack.active.configuration == SeedPhrase) {
componentScope.launch { backButtonClickFlow.emit(Unit) }
} else {
model.onBack()
}
}
}
@ -238,6 +236,8 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
val state by model.uiState.collectAsStateWithLifecycle()
val artworksState by artworksState.collectAsStateWithLifecycle()
BackHandler { model.onBack() }
OnboardingMultiWallet(
state = state,
modifier = modifier,

View file

@ -73,6 +73,7 @@ class MultiWalletBackupModel @Inject constructor(
},
)
}
// TODO
is BackupService.State.FinalizingBackupCard -> TODO()
BackupService.State.FinalizingPrimaryCard -> TODO()
BackupService.State.Finished -> TODO()

View file

@ -17,6 +17,7 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.MultiWalletFinalizeComponent
import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.ui.state.MultiWalletFinalizeUM
@ -155,43 +156,36 @@ internal class MultiWalletFinalizeModel @Inject constructor(
modelScope.launch {
val scanResponse = params.multiWalletState.value.currentScanResponse
val userWallet = createUserWallet(scanResponse)
userWalletsListManager.save(
userWallet = userWallet.copy(
scanResponse = scanResponse.updateScanResponseAfterBackup(),
),
canOverride = true,
)
// TODO
// BackupStartedSource.CreateBackup -> updateWallet(
// userWalletsListManager = userWalletsListManager,
// userWallet = userWallet,
// backupState = backupState,
// )
// TODO maybe here we should check for backup cards activation status
// if (notActivatedCardIds.isEmpty()) {
// delay(1000)
// store.dispatchWithMain(BackupAction.BackupFinished(userWallet?.walletId))
// return@launch
// }
//
// Analytics.send(Onboarding.Finished())
//
// store.state.globalState.onboardingState.onboardingManager?.finishActivation(notActivatedCardIds)
// handleFinishBackup(requireNotNull(scanResponse), userWallet)
// delay(1000)
// store.dispatchWithMain(BackupAction.BackupFinished(userWalletId = userWallet?.walletId))
cardRepository.finishCardActivation(scanResponse.card.cardId)
when (params.parentParams.mode) {
OnboardingMultiWalletComponent.Mode.Onboarding -> {
userWalletsListManager.save(
userWallet = userWallet.copy(
scanResponse = scanResponse.updateScanResponseAfterBackup(),
),
canOverride = true,
)
}
OnboardingMultiWalletComponent.Mode.AddBackup -> {
userWalletsListManager.update(
userWalletId = userWallet.walletId,
update = { wallet ->
wallet.copy(
scanResponse = scanResponse.updateScanResponseAfterBackup(),
)
},
)
}
}
// save user wallet for manage tokens screen
params.multiWalletState.update {
it.copy(resultUserWallet = userWallet)
}
cardRepository.finishCardActivation(scanResponse.card.cardId)
onEvent.emit(MultiWalletFinalizeComponent.Event.ThreeBackupCardsAdded)
}
}
@ -218,8 +212,8 @@ internal class MultiWalletFinalizeModel @Inject constructor(
}
private fun handleActivationError() {
_uiState.update {
it.copy(
_uiState.update { st ->
st.copy(
dialog = resetCardDialog(
onConfirm = ::navigateToSupportScreen,
dismiss = { _uiState.update { it.copy(dialog = null) } },

View file

@ -19,4 +19,20 @@ internal fun resetCardDialog(onConfirm: () -> Unit, dismiss: () -> Unit, onDismi
onDismissButtonClick()
dismiss()
},
)
)
internal fun interruptBackupDialog(onConfirm: () -> Unit, dismiss: () -> Unit) = OnboardingDialogUM(
title = resourceReference(R.string.onboarding_exit_alert_title),
message = resourceReference(R.string.onboarding_exit_alert_message),
confirmButtonText = resourceReference(R.string.common_ok),
dismissButtonText = resourceReference(R.string.common_cancel),
dismissWarningColor = true,
onDismiss = dismiss,
onConfirmClick = {
onConfirm()
dismiss()
},
onDismissButtonClick = {
dismiss()
},
)

View file

@ -3,10 +3,12 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.model
import com.tangem.core.decompose.di.ComponentScoped
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.scan.CardDTO
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.interruptBackupDialog
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
@ -17,6 +19,7 @@ import javax.inject.Inject
internal class OnboardingMultiWalletModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
) : Model() {
private val params = paramsContainer.require<OnboardingMultiWalletComponent.Params>()
private val getCardImageUseCase = GetCardImageUseCase()
@ -32,7 +35,6 @@ internal class OnboardingMultiWalletModel @Inject constructor(
),
)
val backups = MutableStateFlow(MultiWalletChildParams.Backup())
val uiState = _uiState.asStateFlow()
init {
@ -40,11 +42,24 @@ internal class OnboardingMultiWalletModel @Inject constructor(
// if (!manager.isActivationStarted(notNullCard.cardId)) {
// Analytics.send(Onboarding.Started())
// }
initScreenTitleSub()
initScreenTitle()
loadCardArtwork()
subscribeToBackups()
}
fun onBack() {
_uiState.update {
it.copy(
dialog = interruptBackupDialog(
onConfirm = {
router.pop()
},
dismiss = { _uiState.update { it.copy(dialog = null) } },
),
)
}
}
private fun subscribeToBackups() {
modelScope.launch {
backups.collectLatest { bst ->
@ -82,17 +97,9 @@ internal class OnboardingMultiWalletModel @Inject constructor(
}
}
private fun initScreenTitleSub() {
private fun initScreenTitle() {
val title = screenTitleByStep(getInitialStep())
params.titleProvider.changeTitle(title)
modelScope.launch {
state.map { it.currentStep }
.collectLatest { step ->
val stepTitle = screenTitleByStep(step)
params.titleProvider.changeTitle(stepTitle)
}
}
}
private fun loadCardArtwork() {

View file

@ -8,6 +8,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.BasicDialog
import com.tangem.core.ui.components.DialogButtonUM
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM
@ -20,6 +23,23 @@ internal fun OnboardingMultiWallet(
modifier: Modifier = Modifier,
childContent: @Composable (Modifier) -> Unit = {},
) {
if (state.dialog != null) {
BasicDialog(
title = state.dialog.title.resolveReference(),
message = state.dialog.message.resolveReference(),
confirmButton = DialogButtonUM(
title = state.dialog.confirmButtonText.resolveReference(),
onClick = state.dialog.onConfirmClick,
),
dismissButton = DialogButtonUM(
title = state.dialog.dismissButtonText.resolveReference(),
warning = state.dialog.dismissWarningColor,
onClick = state.dialog.onDismissButtonClick,
),
onDismissDialog = state.dialog.onDismiss,
)
}
Column(
modifier = modifier.fillMaxSize(),
) {

View file

@ -1,7 +1,10 @@
package com.tangem.features.onboarding.v2.multiwallet.impl.ui.state
import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.OnboardingDialogUM
internal data class OnboardingMultiWalletUM(
val artwork1Url: String? = null,
val artwork2Url: String? = null,
val artwork3Url: String? = null,
val dialog: OnboardingDialogUM? = null,
)

View file

@ -95,6 +95,7 @@ dependencies {
implementation(projects.features.pushNotifications.api)
implementation(projects.features.markets.api)
implementation(projects.features.onramp.api)
implementation(projects.features.onboardingV2.api)
/** Common modules */
implementation(projects.common.ui)

View file

@ -4,6 +4,7 @@ import com.tangem.common.routing.AppRouter
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.feature.wallet.presentation.router.DefaultWalletRouter
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.features.wallet.navigation.WalletRouter
import dagger.Module
import dagger.Provides
@ -21,7 +22,8 @@ internal object WalletRouterModule {
appRouter: AppRouter,
urlOpener: UrlOpener,
reduxStateHolder: ReduxStateHolder,
onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
): WalletRouter {
return DefaultWalletRouter(appRouter, urlOpener, reduxStateHolder)
return DefaultWalletRouter(appRouter, urlOpener, reduxStateHolder, onboardingV2FeatureToggles)
}
}

View file

@ -17,6 +17,7 @@ import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRoute.ManageTokens.Source
import com.tangem.common.routing.AppRouter
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.redux.StateDialog
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
@ -27,6 +28,7 @@ import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewM
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel
import com.tangem.features.markets.entry.MarketsEntryComponent
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import kotlin.properties.Delegates
/** Default implementation of wallet feature router */
@ -34,6 +36,7 @@ internal class DefaultWalletRouter(
private val router: AppRouter,
private val urlOpener: UrlOpener,
private val reduxStateHolder: ReduxStateHolder,
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
) : InnerWalletRouter {
private var navController: NavHostController by Delegates.notNull()
@ -106,10 +109,24 @@ internal class DefaultWalletRouter(
)
}
override fun openOnboardingScreen() {
router.push(
AppRoute.OnboardingWallet(canSkipBackup = false),
)
override fun openOnboardingScreen(scanResponse: ScanResponse, continueBackup: Boolean) {
if (onboardingV2FeatureToggles.isOnboardingV2Enabled) {
router.push(
AppRoute.Onboarding(
scanResponse = scanResponse,
startFromBackup = true,
mode = if (continueBackup) {
AppRoute.Onboarding.Mode.AddBackup
} else {
AppRoute.Onboarding.Mode.Onboarding
},
),
)
} else {
router.push(
AppRoute.OnboardingWallet(canSkipBackup = false),
)
}
}
override fun openOnrampSuccessScreen(txId: String) {

View file

@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.router
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.markets.entry.MarketsEntryComponent
@ -36,7 +37,7 @@ internal interface InnerWalletRouter : WalletRouter {
fun openDetailsScreen(selectedWalletId: UserWalletId)
/** Open onboarding screen */
fun openOnboardingScreen()
fun openOnboardingScreen(scanResponse: ScanResponse, continueBackup: Boolean = false)
/** Open onramp success screen for [txId] */
fun openOnrampSuccessScreen(txId: String)

View file

@ -104,9 +104,13 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
canSkipBackup = false,
),
)
// navigation action shouldn't be out of coroutine to avoid race
router.openOnboardingScreen(
scanResponse = it.scanResponse,
continueBackup = true,
)
}
// navigation action shouldn't be out of coroutine to avoid race
router.openOnboardingScreen()
}
}