diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 5ff29dc0d4..7ab791d0b4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -26,6 +26,7 @@ import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction +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 + }, ) } 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 59331b1445..356dbcbe9e 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 @@ -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, ) 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 fc52efb20d..94e2a9f2ae 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 @@ -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 + } + } } \ No newline at end of file diff --git a/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt b/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt index 3dafb568ad..1e1bc4d93b 100644 --- a/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt +++ b/features/onboarding-v2/api/src/main/kotlin/com/tangem/features/onboarding/v2/entry/OnboardingEntryComponent.kt @@ -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 } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/done/impl/ui/OnboardingDone.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/done/impl/ui/OnboardingDone.kt index 6d68d82135..d357369c9f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/done/impl/ui/OnboardingDone.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/done/impl/ui/OnboardingDone.kt @@ -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 } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt index 14cc6bf1b0..6134fd218f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/DefaultOnboardingEntryComponent.kt @@ -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 -> diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt index 75c7d7837d..54dd730ffd 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/model/OnboardingEntryModel.kt @@ -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() diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt index b4fc46358c..3721bc7927 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingChildFactory.kt @@ -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( diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt index c549a59034..01618cbde2 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/routing/OnboardingRoute.kt @@ -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() diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/api/OnboardingMultiWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/api/OnboardingMultiWalletComponent.kt index 32ce1ba533..44fdf461ed 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/api/OnboardingMultiWalletComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/api/OnboardingMultiWalletComponent.kt @@ -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 } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt index 0cc5d5aa77..750a119975 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/DefaultOnboardingMultiWalletComponent.kt @@ -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 @@ -17,6 +18,7 @@ import com.arkivanov.decompose.router.slot.navigate import com.arkivanov.decompose.router.stack.* import com.arkivanov.decompose.value.Value import com.arkivanov.essenty.instancekeeper.getOrCreateSimple +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.model.getOrCreateModel @@ -24,6 +26,7 @@ import com.tangem.core.decompose.navigation.inner.InnerNavigation import com.tangem.core.decompose.navigation.inner.InnerNavigationState import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent +import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEvent import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams import com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.MultiWalletAccessCodeComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.MultiWalletBackupComponent @@ -45,6 +48,7 @@ import kotlinx.coroutines.launch internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor( @Assisted private val context: AppComponentContext, @Assisted private val params: OnboardingMultiWalletComponent.Params, + private val analyticsHandler: AnalyticsEventHandler, ) : OnboardingMultiWalletComponent, AppComponentContext by context { private val model: OnboardingMultiWalletModel = getOrCreateModel(params) @@ -70,6 +74,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor multiWalletState = model.state, parentParams = params, innerNavigation = innerNavigationStateFlow, + backups = model.backups, ) private val childStack: Value> = @@ -92,13 +97,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() } } } @@ -183,6 +185,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor } Done -> { // final step - navigate to parent + analyticsHandler.send(OnboardingEvent.Finished) val userWallet = childParams.multiWalletState.value.resultUserWallet ?: return params.onDone(userWallet) } @@ -237,6 +240,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, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/analytics/OnboardingEvent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/analytics/OnboardingEvent.kt new file mode 100644 index 0000000000..2945b7ce27 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/analytics/OnboardingEvent.kt @@ -0,0 +1,68 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent + +sealed class OnboardingEvent( + category: String, + event: String, + params: Map = mapOf(), +) : AnalyticsEvent(category, event, params) { + + data object Started : OnboardingEvent("Onboarding", "Onboarding Started") + data object Finished : OnboardingEvent("Onboarding", "Onboarding Finished") + + sealed class CreateWallet( + event: String, + params: Map = mapOf(), + ) : OnboardingEvent("Onboarding / Create Wallet", event, params) { + + data object ScreenOpened : CreateWallet("Create Wallet Screen Opened") + data object ButtonCreateWallet : CreateWallet("Button - Create Wallet") + class WalletCreatedSuccessfully( + creationType: WalletCreationType = WalletCreationType.PrivateKey, + seedPhraseLength: Int? = null, + ) : CreateWallet( + event = "Wallet Created Successfully", + params = buildMap { + put("Creation Type", creationType.value) + if (seedPhraseLength != null) { + put("Seed Phrase Length", seedPhraseLength.toString()) + } + }, + ) + + sealed class WalletCreationType(val value: String) { + data object PrivateKey : WalletCreationType(value = "Private Key") + data object NewSeed : WalletCreationType(value = "New Seed") + data object SeedImport : WalletCreationType(value = "Seed Import") + } + } + + sealed class Backup( + event: String, + params: Map = mapOf(), + ) : OnboardingEvent("Onboarding / Backup", event, params) { + + data object ScreenOpened : Backup("Backup Screen Opened") + data object Started : Backup("Backup Started") + data object Skipped : Backup("Backup Skipped") + data object SettingAccessCodeStarted : Backup("Setting Access Code Started") + data object AccessCodeEntered : Backup("Access Code Entered") + data object AccessCodeReEntered : Backup("Access Code Re-entered") + + class Finished(cardsCount: Int) : Backup( + event = "Backup Finished", + params = mapOf("Cards count" to "$cardsCount"), + ) + + data object ResetCancelEvent : Backup( + event = "Reset Card Notification", + params = mapOf("Option" to "Cancel"), + ) + + data object ResetPerformEvent : Backup( + event = "Reset Card Notification", + params = mapOf("Option" to "Reset"), + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/MultiWalletChildParams.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/MultiWalletChildParams.kt index f912a9b20c..8fee788c8b 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/MultiWalletChildParams.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/MultiWalletChildParams.kt @@ -8,5 +8,34 @@ import kotlinx.coroutines.flow.MutableStateFlow class MultiWalletChildParams( val multiWalletState: MutableStateFlow, val innerNavigation: MutableStateFlow, + val backups: MutableStateFlow, val parentParams: OnboardingMultiWalletComponent.Params, -) \ No newline at end of file +) { + data class Backup( + val card2: BackupCardInfo? = null, + val card3: BackupCardInfo? = null, + ) { + data class BackupCardInfo( + val cardId: String, + val cardPublicKey: ByteArray, + ) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as BackupCardInfo + + if (cardId != other.cardId) return false + if (!cardPublicKey.contentEquals(other.cardPublicKey)) return false + + return true + } + + override fun hashCode(): Int { + var result = cardId.hashCode() + result = 31 * result + cardPublicKey.contentHashCode() + return result + } + } + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/model/MultiWalletAccessCodeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/model/MultiWalletAccessCodeModel.kt index 5d301c7325..755d6c3b3a 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/model/MultiWalletAccessCodeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/model/MultiWalletAccessCodeModel.kt @@ -1,9 +1,11 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.model import androidx.compose.ui.text.input.TextFieldValue +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEvent import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams import com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.ui.state.MultiWalletAccessCodeUM import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState @@ -23,6 +25,7 @@ internal class MultiWalletAccessCodeModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, private val backupServiceHolder: BackupServiceHolder, + private val analyticsHandler: AnalyticsEventHandler, ) : Model() { private val _uiState = MutableStateFlow(getInitialState()) @@ -32,6 +35,8 @@ internal class MultiWalletAccessCodeModel @Inject constructor( val onDismiss = MutableSharedFlow() init { + analyticsHandler.send(OnboardingEvent.Backup.SettingAccessCodeStarted) + params.multiWalletState.update { it.copy(accessCode = null) } @@ -111,6 +116,8 @@ internal class MultiWalletAccessCodeModel @Inject constructor( it.copy(atLeast4CharError = true) } } + + analyticsHandler.send(OnboardingEvent.Backup.AccessCodeEntered) } MultiWalletAccessCodeUM.Step.ConfirmAccessCode -> { if (checkAccessCode()) { @@ -123,6 +130,8 @@ internal class MultiWalletAccessCodeModel @Inject constructor( } modelScope.launch { onDismiss.emit(Unit) } } + + analyticsHandler.send(OnboardingEvent.Backup.AccessCodeReEntered) } } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt index 1528ccd94d..89a5685f63 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/MultiWalletBackupModel.kt @@ -3,6 +3,7 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -10,6 +11,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.models.scan.ProductType import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEvent import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.MultiWalletBackupComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.backupCardAttestationFailedDialog @@ -33,6 +35,7 @@ class MultiWalletBackupModel @Inject constructor( private val backupServiceHolder: BackupServiceHolder, private val cardSdkConfigRepository: CardSdkConfigRepository, private val tangemSdkManager: TangemSdkManager, + private val analyticsHandler: AnalyticsEventHandler, ) : Model() { @Suppress("UnusedPrivateMember") @@ -57,6 +60,15 @@ class MultiWalletBackupModel @Inject constructor( val uiState: StateFlow = _uiState val eventFlow = MutableSharedFlow() + init { + // for wallet 1 this event is sent in Wallet1ChooseOptionModel + if (scanResponse.productType == ProductType.Wallet2 || scanResponse.productType == ProductType.Ring) { + analyticsHandler.send(OnboardingEvent.Backup.ScreenOpened) + } + + analyticsHandler.send(OnboardingEvent.Backup.Started) + } + private fun getInitState(): MultiWalletBackupUM { return when (backupService.currentState) { BackupService.State.Preparing -> { @@ -73,6 +85,7 @@ class MultiWalletBackupModel @Inject constructor( }, ) } + // TODO is BackupService.State.FinalizingBackupCard -> TODO() BackupService.State.FinalizingPrimaryCard -> TODO() BackupService.State.Finished -> TODO() @@ -88,9 +101,6 @@ class MultiWalletBackupModel @Inject constructor( if (primaryCard != null) { backupService.setPrimaryCard(primaryCard) addBackupCardWithService() - } else { - // TODO wallet 2 ??? or from main? - // BackupAction.StartAddingPrimaryCard } } @@ -145,6 +155,8 @@ class MultiWalletBackupModel @Inject constructor( } } modelScope.launch { eventFlow.emit(MultiWalletBackupComponent.Event.Done) } + + analyticsHandler.send(OnboardingEvent.Backup.Finished(cardsCount = state.value.backupCardsNumber + 1)) } private fun addBackupCardWithService() { @@ -167,6 +179,18 @@ class MultiWalletBackupModel @Inject constructor( backupCardsNumber = it.backupCardsNumber + 1, ) } + + val backupCardInfo = MultiWalletChildParams.Backup.BackupCardInfo( + cardId = result.data.cardId, + cardPublicKey = result.data.cardPublicKey, + ) + params.backups.update { + it.copy( + card2 = if (state.value.backupCardsNumber == 1) backupCardInfo else it.card2, + card3 = if (state.value.backupCardsNumber == 2) backupCardInfo else it.card3, + ) + } + setNumberOfBackupCards(state.value.backupCardsNumber) } is CompletionResult.Failure -> { @@ -176,7 +200,12 @@ class MultiWalletBackupModel @Inject constructor( st.copy( dialog = resetBackupCardDialog( onReset = { resetBackupCard(cardId = error.cardId) }, - onDismiss = { _uiState.update { it.copy(dialog = null) } }, + onDismiss = { + _uiState.update { it.copy(dialog = null) } + }, + onDismissClick = { + analyticsHandler.send(OnboardingEvent.Backup.ResetCancelEvent) + }, ), ) } @@ -198,6 +227,8 @@ class MultiWalletBackupModel @Inject constructor( } private fun resetBackupCard(cardId: String) { + analyticsHandler.send(OnboardingEvent.Backup.ResetPerformEvent) + modelScope.launch { tangemSdkManager.resetToFactorySettings( cardId = cardId, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Dialogs.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Dialogs.kt index 5b9acbb42e..15d4a0b457 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Dialogs.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Dialogs.kt @@ -4,16 +4,20 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.features.onboarding.v2.impl.R import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.state.MultiWalletBackupUMDialog -internal fun resetBackupCardDialog(onReset: () -> Unit, onDismiss: () -> Unit) = MultiWalletBackupUMDialog( - title = resourceReference(R.string.common_attention), - bodyText = resourceReference(R.string.onboarding_linking_error_card_with_wallets), - confirmText = resourceReference(R.string.common_cancel), - cancelText = resourceReference(R.string.card_settings_action_sheet_reset), - warningCancelColor = true, - onDismiss = onDismiss, - onConfirm = onDismiss, - onCancel = { onReset(); onDismiss() }, -) +internal fun resetBackupCardDialog(onReset: () -> Unit, onDismiss: () -> Unit, onDismissClick: () -> Unit) = + MultiWalletBackupUMDialog( + title = resourceReference(R.string.common_attention), + bodyText = resourceReference(R.string.onboarding_linking_error_card_with_wallets), + confirmText = resourceReference(R.string.common_cancel), + cancelText = resourceReference(R.string.card_settings_action_sheet_reset), + warningCancelColor = true, + onDismiss = { + onDismiss() + onDismissClick() + }, + onConfirm = onDismiss, + onCancel = { onReset(); onDismiss() }, + ) internal fun backupCardAttestationFailedDialog(onDismiss: () -> Unit) = MultiWalletBackupUMDialog( title = resourceReference(R.string.common_error), diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt index d0c206de50..4fe5566d5e 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt @@ -1,5 +1,6 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.model +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -9,6 +10,7 @@ import com.tangem.domain.wallets.builder.UserWalletBuilder import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.domain.wallets.usecase.SaveWalletUseCase +import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEvent import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableSharedFlow @@ -23,18 +25,25 @@ internal class Wallet1ChooseOptionModel @Inject constructor( private val generateWalletNameUseCase: GenerateWalletNameUseCase, private val cardRepository: CardRepository, private val saveWalletUseCase: SaveWalletUseCase, + private val analyticsHandler: AnalyticsEventHandler, ) : Model() { private val params = paramsContainer.require() private var skipClicked = false val returnToParentFlow = MutableSharedFlow() + init { + analyticsHandler.send(OnboardingEvent.Backup.ScreenOpened) + } + fun onSkipClick() { // TODO show confirmation dialog if (skipClicked) return skipClicked = true + analyticsHandler.send(OnboardingEvent.Backup.Skipped) + modelScope.launch { val scanResponse = params.multiWalletState.value.currentScanResponse diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt index c934b818b3..b7b153a6f7 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt @@ -2,6 +2,7 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.mo import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -11,6 +12,7 @@ import com.tangem.domain.feedback.GetCardInfoUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.features.onboarding.v2.impl.R +import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEvent import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state.MultiWalletCreateWalletUM import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.resetCardDialog @@ -24,6 +26,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject +@Suppress("LongParameterList") @ComponentScoped internal class MultiWalletCreateWalletModel @Inject constructor( paramsContainer: ParamsContainer, @@ -32,6 +35,7 @@ internal class MultiWalletCreateWalletModel @Inject constructor( private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val getCardInfoUseCase: GetCardInfoUseCase, private val cardRepository: CardRepository, + private val analyticsHandler: AnalyticsEventHandler, ) : Model() { private val params = paramsContainer.require() @@ -50,7 +54,10 @@ internal class MultiWalletCreateWalletModel @Inject constructor( } else { resourceReference(R.string.onboarding_create_wallet_body) }, - onCreateWalletClick = { createWallet(false) }, + onCreateWalletClick = { + analyticsHandler.send(OnboardingEvent.CreateWallet.ButtonCreateWallet) + createWallet(false) + }, showOtherOptionsButton = params.parentParams.withSeedPhraseFlow, onOtherOptionsClick = { modelScope.launch { @@ -64,6 +71,10 @@ internal class MultiWalletCreateWalletModel @Inject constructor( val uiState: StateFlow = _uiState val onDone = MutableSharedFlow() + init { + analyticsHandler.send(OnboardingEvent.CreateWallet.ScreenOpened) + } + private fun createWallet(shouldReset: Boolean) { modelScope.launch { val result = tangemSdkManager.createProductWallet( @@ -91,8 +102,7 @@ internal class MultiWalletCreateWalletModel @Inject constructor( onDone.emit(OnboardingMultiWalletState.Step.ChooseBackupOption) } - // TODO - // Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully()) + analyticsHandler.send(OnboardingEvent.CreateWallet.WalletCreatedSuccessfully()) } is CompletionResult.Failure -> { diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt index 757a4c8753..1214bf588d 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt @@ -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 @@ -78,7 +79,7 @@ internal class MultiWalletFinalizeModel @Inject constructor( private fun writePrimaryCard() { val backupService = backupServiceHolder.backupService.get() ?: return - // store.dispatchOnMain(BackupAction.SetHasRing(hasRing = isRing)) + // TODO store.dispatchOnMain(BackupAction.SetHasRing(hasRing = isRing)) val primaryCardBatchId = backupService.primaryCardBatchId ?: return val isRing = isRing(primaryCardBatchId) @@ -107,7 +108,7 @@ internal class MultiWalletFinalizeModel @Inject constructor( private fun writeBackupCard(cardIndex: Int) { val backupService = backupServiceHolder.backupService.get() ?: return - // store.dispatchOnMain(BackupAction.SetHasRing(hasRing = isRing)) + // TODO store.dispatchOnMain(BackupAction.SetHasRing(hasRing = isRing)) val backupCardBatchId = backupService.backupCardsBatchIds.getOrNull(cardIndex) ?: return val isRing = isRing(backupCardBatchId) @@ -119,7 +120,6 @@ internal class MultiWalletFinalizeModel @Inject constructor( is CompletionResult.Success -> { val backupValidator = BackupValidator() if (backupValidator.isValidBackupStatus(CardDTO(result.data)).not()) { - // TODO store.dispatchOnMain(BackupAction.ErrorInBackupCard) walletHasBackupError = true } @@ -155,43 +155,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 +211,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) } }, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt index a1925f6a35..d636e7e2ca 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt @@ -2,6 +2,7 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.mode import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -11,6 +12,7 @@ import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.feedback.GetCardInfoUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType +import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEvent import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.builder.GenerateSeedPhraseUiStateBuilder import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.builder.ImportSeedPhraseUiStateBuilder @@ -41,6 +43,7 @@ internal class MultiWalletSeedPhraseModel @Inject constructor( private val cardRepository: CardRepository, private val getCardInfoUseCase: GetCardInfoUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, + private val analyticsHandler: AnalyticsEventHandler, ) : Model() { private val params = paramsContainer.require() @@ -76,6 +79,7 @@ internal class MultiWalletSeedPhraseModel @Inject constructor( state.value.generatedWords12 ?: return@importWallet }, passphrase = null, + generatedSeedPhrase = true, ) }, // ============================================= @@ -85,7 +89,13 @@ internal class MultiWalletSeedPhraseModel @Inject constructor( modelScope = modelScope, mnemonicRepository = mnemonicRepository, updateUiState = { block -> updateUiStateSpecific(block) }, - importWallet = ::importWallet, + importWallet = { mnemonic, passphrase -> + importWallet( + mnemonic = mnemonic, + passphrase = passphrase, + generatedSeedPhrase = false, + ) + }, ) val uiState = _uiState.asStateFlow() @@ -159,7 +169,7 @@ internal class MultiWalletSeedPhraseModel @Inject constructor( // ============================================= // | DON'T FIXME !!! MODIFY WITH CAUTION !!! | // ============================================= - private fun importWallet(mnemonic: Mnemonic, passphrase: String?) { + private fun importWallet(mnemonic: Mnemonic, passphrase: String?, generatedSeedPhrase: Boolean) { val currentState = state.value if (currentState.readyToImport.not()) return @@ -175,7 +185,17 @@ internal class MultiWalletSeedPhraseModel @Inject constructor( when (result) { is CompletionResult.Success -> { - // TODO on success send analytics event + analyticsHandler.send( + OnboardingEvent.CreateWallet.WalletCreatedSuccessfully( + creationType = if (generatedSeedPhrase) { + OnboardingEvent.CreateWallet.WalletCreationType.NewSeed + } else { + OnboardingEvent.CreateWallet.WalletCreationType.SeedImport + }, + seedPhraseLength = mnemonic.mnemonicComponents.size, + ), + ) + multiWalletState.update { it.copy( currentScanResponse = it.currentScanResponse.copy( diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/common/ui/ResetCardDialog.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/common/ui/Dialogs.kt similarity index 60% rename from features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/common/ui/ResetCardDialog.kt rename to features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/common/ui/Dialogs.kt index 7ef775ba52..ecf06866f2 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/common/ui/ResetCardDialog.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/common/ui/Dialogs.kt @@ -19,4 +19,20 @@ internal fun resetCardDialog(onConfirm: () -> Unit, dismiss: () -> Unit, onDismi onDismissButtonClick() dismiss() }, - ) \ No newline at end of file + ) + +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() + }, +) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt index cfd4d2429a..2ebc2f3867 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletModel.kt @@ -1,11 +1,16 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.model +import com.tangem.core.analytics.api.AnalyticsEventHandler 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.analytics.OnboardingEvent +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.* @@ -16,6 +21,8 @@ import javax.inject.Inject internal class OnboardingMultiWalletModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + private val analyticsHandler: AnalyticsEventHandler, ) : Model() { private val params = paramsContainer.require() private val getCardImageUseCase = GetCardImageUseCase() @@ -30,16 +37,50 @@ internal class OnboardingMultiWalletModel @Inject constructor( resultUserWallet = null, ), ) - + val backups = MutableStateFlow(MultiWalletChildParams.Backup()) val uiState = _uiState.asStateFlow() init { - // TODO add analytics - // if (!manager.isActivationStarted(notNullCard.cardId)) { - // Analytics.send(Onboarding.Started()) - // } - initScreenTitleSub() + analyticsHandler.send(OnboardingEvent.Started) + 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 -> + when { + _uiState.value.artwork2Url == null && bst.card2 != null -> { + val artwork = + getCardImageUseCase.invoke(bst.card2.cardId, bst.card2.cardPublicKey) + _uiState.update { + it.copy(artwork2Url = artwork) + } + } + _uiState.value.artwork3Url == null && bst.card3 != null -> { + val artwork = + getCardImageUseCase.invoke(bst.card3.cardId, bst.card3.cardPublicKey) + _uiState.update { + it.copy(artwork3Url = artwork) + } + } + } + } + } } private fun getInitialStep(): OnboardingMultiWalletState.Step { @@ -56,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() { @@ -76,7 +109,7 @@ internal class OnboardingMultiWalletModel @Inject constructor( if (artwork != getCardImageUseCase.getDefaultFallbackUrl()) { _uiState.update { - it.copy(artworkUrl = artwork) + it.copy(artwork1Url = artwork) } } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt index c698e7e721..2c5b1af15f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/OnboardingMultiWallet.kt @@ -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,12 +23,31 @@ 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(), ) { if (isSeedPhraseState.not()) { WalletArtworks( - url = state.artworkUrl, + url1 = state.artwork1Url, + url2 = state.artwork2Url, + url3 = state.artwork3Url, modifier = Modifier .weight(.56f) .padding(horizontal = 34.dp) @@ -51,7 +73,7 @@ internal fun OnboardingMultiWallet( private fun Preview() { TangemThemePreview { OnboardingMultiWallet( - state = OnboardingMultiWalletUM(artworkUrl = null), + state = OnboardingMultiWalletUM(artwork1Url = null), artworksState = WalletArtworksState.Folded, isSeedPhraseState = false, childContent = { md -> @@ -71,7 +93,7 @@ private fun Preview() { private fun Preview2() { TangemThemePreview { OnboardingMultiWallet( - state = OnboardingMultiWalletUM(artworkUrl = null), + state = OnboardingMultiWalletUM(artwork1Url = null), artworksState = WalletArtworksState.Folded, isSeedPhraseState = true, childContent = { md -> diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt index 2c1b7f784c..78d304117a 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/WalletArtwork.kt @@ -58,7 +58,13 @@ sealed class WalletArtworksState { } @Composable -fun WalletArtworks(url: String?, state: WalletArtworksState, modifier: Modifier = Modifier) { +fun WalletArtworks( + url1: String?, + url2: String?, + url3: String?, + state: WalletArtworksState, + modifier: Modifier = Modifier, +) { if (state is WalletArtworksState.Hidden) return BoxWithConstraints( @@ -116,7 +122,9 @@ fun WalletArtworks(url: String?, state: WalletArtworksState, modifier: Modifier } AnimatedWalletCards( - url = url, + url1 = url1, + url2 = url2, + url3 = url3, modifier = Modifier .widthIn(max = 450.dp) .matchParentSize(), @@ -143,9 +151,12 @@ private data class WalletCardTransitionState( val zIndex: Float = 0f, ) +@Suppress("LongParameterList") @Composable private fun AnimatedWalletCards( - url: String?, + url1: String?, + url2: String?, + url3: String?, transition1: Transition, transition2: Transition, transition3: Transition, @@ -190,7 +201,7 @@ private fun AnimatedWalletCards( scaleY = scaleY3 rotationZ = rotation3 }, - url = url, + url = url3, ) WalletCard( @@ -208,7 +219,7 @@ private fun AnimatedWalletCards( scaleX = scaleX2 scaleY = scaleY2 }, - url = url, + url = url2, ) WalletCard( @@ -225,7 +236,7 @@ private fun AnimatedWalletCards( translationY = translationY1 rotationZ = rotation1 }, - url = url, + url = url1, ) } } @@ -450,7 +461,9 @@ private fun Preview() { var state: WalletArtworksState by remember { mutableStateOf(WalletArtworksState.Folded) } WalletArtworks( - url = null, + url1 = null, + url2 = null, + url3 = null, state = state, modifier = Modifier.fillMaxWidth(), ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/state/OnboardingMultiWalletUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/state/OnboardingMultiWalletUM.kt index 1e4884ea7a..5aa205dc81 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/state/OnboardingMultiWalletUM.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/ui/state/OnboardingMultiWalletUM.kt @@ -1,5 +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 artworkUrl: String? = null, + val artwork1Url: String? = null, + val artwork2Url: String? = null, + val artwork3Url: String? = null, + val dialog: OnboardingDialogUM? = null, ) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt index 543671a35c..640ac88a7f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/stepper/impl/DefaultOnboardingStepperComponent.kt @@ -6,6 +6,8 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.essenty.instancekeeper.getOrCreateSimple +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.decompose.context.AppComponentContext import com.tangem.domain.feedback.GetCardInfoUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase @@ -23,11 +25,19 @@ internal class DefaultOnboardingStepperComponent @AssistedInject constructor( @Assisted val params: OnboardingStepperComponent.Params, private val getCardInfoUseCase: GetCardInfoUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, + private val analyticsHandler: AnalyticsEventHandler, ) : OnboardingStepperComponent, AppComponentContext by context { override val state = instanceKeeper.getOrCreateSimple { MutableStateFlow(params.initState) } private fun openSupport() { + analyticsHandler.send( + AnalyticsEvent( + category = "Onboarding", + event = "Button - Chat", + ), + ) + componentScope.launch { val cardInfo = getCardInfoUseCase(params.scanResponse).getOrNull() ?: return@launch sendFeedbackEmailUseCase(FeedbackEmailType.DirectUserRequest(cardInfo)) diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index be6834bd6b..bbddfd2a4d 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -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) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt index 4e031b0729..5124c8df29 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/WalletRouterModule.kt @@ -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) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 7c1a971dd1..7788000953 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -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) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt index 4532ced599..8cb1601460 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt @@ -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) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt index 16ef0fd1a6..8fbfd08c16 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt @@ -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() } }