Updated on 2026-08-14

This commit is contained in:
Tangem 2024-12-05 10:35:27 +03:00
parent 022c4bcfcd
commit d71d29b72b
30 changed files with 295 additions and 104 deletions

View file

@ -24,6 +24,7 @@ import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.repository.OnboardingRepository
import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
@ -119,6 +120,8 @@ interface ApplicationEntryPoint {
fun getOnboardingV2FeatureToggles(): OnboardingV2FeatureToggles fun getOnboardingV2FeatureToggles(): OnboardingV2FeatureToggles
fun getOnboardingRepository(): OnboardingRepository
fun getCoroutineDispatcherProvider(): CoroutineDispatcherProvider fun getCoroutineDispatcherProvider(): CoroutineDispatcherProvider
fun getExcludedBlockchains(): ExcludedBlockchains fun getExcludedBlockchains(): ExcludedBlockchains

View file

@ -586,7 +586,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
} }
} }
// TODO add onboarding v2 handling
store.dispatch(BackupAction.CheckForUnfinishedBackup) store.dispatch(BackupAction.CheckForUnfinishedBackup)
} }

View file

@ -38,6 +38,7 @@ import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.repository.OnboardingRepository
import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
@ -188,6 +189,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles
get() = entryPoint.getOnboardingV2FeatureToggles() get() = entryPoint.getOnboardingV2FeatureToggles()
private val onboardingRepository: OnboardingRepository
get() = entryPoint.getOnboardingRepository()
private val dispatchers: CoroutineDispatcherProvider private val dispatchers: CoroutineDispatcherProvider
get() = entryPoint.getCoroutineDispatcherProvider() get() = entryPoint.getCoroutineDispatcherProvider()
@ -273,6 +277,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
onrampFeatureToggles = onrampFeatureToggles, onrampFeatureToggles = onrampFeatureToggles,
environmentConfigStorage = environmentConfigStorage, environmentConfigStorage = environmentConfigStorage,
onboardingV2FeatureToggles = onboardingV2FeatureToggles, onboardingV2FeatureToggles = onboardingV2FeatureToggles,
onboardingRepository = onboardingRepository,
excludedBlockchains = excludedBlockchains, excludedBlockchains = excludedBlockchains,
), ),
), ),

View file

@ -148,8 +148,14 @@ class DialogManager : StoreSubscriber<GlobalState> {
is BackupDialog.AttestationFailed -> AttestationFailedDialog.create(context) is BackupDialog.AttestationFailed -> AttestationFailedDialog.create(context)
is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context) is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context)
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context) is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context) is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(
is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(context) context = context,
scanResponse = state.dialog.scanResponse,
)
is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(
context = context,
unfinishedBackupScanResponse = state.dialog.scanResponse,
)
is BackupDialog.ResetBackupCard -> ResetBackupCardDialog.create( is BackupDialog.ResetBackupCard -> ResetBackupCardDialog.create(
context = context, context = context,
cardId = state.dialog.cardId, cardId = state.dialog.cardId,

View file

@ -3,6 +3,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
import android.net.Uri import android.net.Uri
import com.tangem.common.CompletionResult import com.tangem.common.CompletionResult
import com.tangem.common.card.Card import com.tangem.common.card.Card
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.onboarding.data.model.CreateWalletResponse import com.tangem.feature.onboarding.data.model.CreateWalletResponse
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
@ -98,7 +99,10 @@ sealed class BackupAction : Action {
data object DiscardBackup : BackupAction() data object DiscardBackup : BackupAction()
data object DiscardSavedBackup : BackupAction() data object DiscardSavedBackup : BackupAction()
data object ResumeFoundUnfinishedBackup : BackupAction()
data class ResumeFoundUnfinishedBackup(
val unfinishedBackupScanResponse: ScanResponse?,
) : BackupAction()
data class ResetBackupCard(val cardId: String) : BackupAction() data class ResetBackupCard(val cardId: String) : BackupAction()
} }

View file

@ -581,21 +581,50 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
store.state.globalState.onboardingState.onboardingManager?.finishActivation(it) store.state.globalState.onboardingState.onboardingManager?.finishActivation(it)
} }
backupService.discardSavedBackup() backupService.discardSavedBackup()
val isOnboardingV2Enabled = store.inject(DaggerGraphState::onboardingV2FeatureToggles)
.isOnboardingV2Enabled
if (isOnboardingV2Enabled) {
val onboardingRepository = store.inject(DaggerGraphState::onboardingRepository)
onboardingRepository.clearUnfinishedFinalizeOnboarding()
}
} }
} }
is BackupAction.CheckForUnfinishedBackup -> { is BackupAction.CheckForUnfinishedBackup -> {
if (backupService.hasIncompletedBackup) { val isOnboardingV2Enabled = store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound))
if (isOnboardingV2Enabled) {
val onboardingRepository = store.inject(DaggerGraphState::onboardingRepository)
mainScope.launch {
val onboardingScanResponse = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound(onboardingScanResponse)))
}
} else if (backupService.hasIncompletedBackup) {
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound()))
} }
} }
is BackupAction.ResumeFoundUnfinishedBackup -> { is BackupAction.ResumeFoundUnfinishedBackup -> {
store.dispatch( if (action.unfinishedBackupScanResponse != null) {
GlobalAction.Onboarding.StartForUnfinishedBackup( // onboarding V2
addedBackupCardsCount = backupService.addedBackupCardsCount, store.dispatchNavigationAction {
), push(
) AppRoute.Onboarding(
scanResponse = action.unfinishedBackupScanResponse,
startFromBackup = false,
),
)
}
} else {
store.dispatch(
GlobalAction.Onboarding.StartForUnfinishedBackup(
addedBackupCardsCount = backupService.addedBackupCardsCount,
),
)
store.dispatchNavigationAction { push(AppRoute.OnboardingWallet()) } store.dispatchNavigationAction { push(AppRoute.OnboardingWallet()) }
}
} }
is BackupAction.SkipBackup -> { is BackupAction.SkipBackup -> {
Analytics.send(Onboarding.Backup.Skipped()) Analytics.send(Onboarding.Backup.Skipped())

View file

@ -3,6 +3,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import com.tangem.common.card.Card import com.tangem.common.card.Card
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog import com.tangem.domain.redux.StateDialog
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import org.rekotlin.StateType import org.rekotlin.StateType
@ -99,22 +100,29 @@ enum class AccessCodeError {
typealias CardId = String typealias CardId = String
sealed class BackupStep { sealed class BackupStep {
object InitBackup : BackupStep() data object InitBackup : BackupStep()
object ScanOriginCard : BackupStep() data object ScanOriginCard : BackupStep()
object AddBackupCards : BackupStep() data object AddBackupCards : BackupStep()
object SetAccessCode : BackupStep() data object SetAccessCode : BackupStep()
object EnterAccessCode : BackupStep() data object EnterAccessCode : BackupStep()
object ReenterAccessCode : BackupStep() data object ReenterAccessCode : BackupStep()
object WritePrimaryCard : BackupStep() data object WritePrimaryCard : BackupStep()
data class WriteBackupCard(val cardNumber: Int) : BackupStep() data class WriteBackupCard(val cardNumber: Int) : BackupStep()
object Finished : BackupStep() data object Finished : BackupStep()
} }
sealed class BackupDialog : StateDialog { sealed class BackupDialog : StateDialog {
object AttestationFailed : BackupDialog() data object AttestationFailed : BackupDialog()
object AddMoreBackupCards : BackupDialog() data object AddMoreBackupCards : BackupDialog()
object BackupInProgress : BackupDialog() data object BackupInProgress : BackupDialog()
object UnfinishedBackupFound : BackupDialog()
object ConfirmDiscardingBackup : BackupDialog() data class UnfinishedBackupFound(
val scanResponse: ScanResponse? = null,
) : BackupDialog()
data class ConfirmDiscardingBackup(
val scanResponse: ScanResponse? = null,
) : BackupDialog()
data class ResetBackupCard(val cardId: String) : BackupDialog() data class ResetBackupCard(val cardId: String) : BackupDialog()
} }

View file

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

View file

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

View file

@ -20,6 +20,7 @@ import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.repository.OnboardingRepository
import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
@ -72,5 +73,6 @@ data class DaggerGraphState(
val onrampFeatureToggles: OnrampFeatureToggles? = null, val onrampFeatureToggles: OnrampFeatureToggles? = null,
val environmentConfigStorage: EnvironmentConfigStorage? = null, val environmentConfigStorage: EnvironmentConfigStorage? = null,
val onboardingV2FeatureToggles: OnboardingV2FeatureToggles? = null, val onboardingV2FeatureToggles: OnboardingV2FeatureToggles? = null,
val onboardingRepository: OnboardingRepository? = null,
val excludedBlockchains: ExcludedBlockchains? = null, val excludedBlockchains: ExcludedBlockchains? = null,
) : StateType ) : StateType

View file

@ -28,6 +28,7 @@ dependencies {
implementation(projects.domain.txhistory.models) implementation(projects.domain.txhistory.models)
implementation(projects.domain.staking.models) implementation(projects.domain.staking.models)
implementation(projects.domain.onramp.models) implementation(projects.domain.onramp.models)
implementation(projects.domain.models)
/** Tangem libraries */ /** Tangem libraries */
implementation(deps.tangem.blockchain) implementation(deps.tangem.blockchain)

View file

@ -9,6 +9,7 @@ import com.tangem.datasource.api.common.adapter.DateTimeAdapter
import com.tangem.datasource.api.common.adapter.LocalDateAdapter import com.tangem.datasource.api.common.adapter.LocalDateAdapter
import com.tangem.datasource.api.common.adapter.addStakeKitEnumFallbackAdapters import com.tangem.datasource.api.common.adapter.addStakeKitEnumFallbackAdapters
import com.tangem.datasource.local.config.providers.models.ProviderModel import com.tangem.datasource.local.config.providers.models.ProviderModel
import com.tangem.domain.models.scan.serialization.*
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -49,7 +50,16 @@ class MoshiModule {
@Singleton @Singleton
fun provideSdkMoshiConverter(): MoshiJsonConverter { fun provideSdkMoshiConverter(): MoshiJsonConverter {
return MoshiJsonConverter( return MoshiJsonConverter(
adapters = listOf(BigDecimalAdapter()) + MoshiJsonConverter.getTangemSdkAdapters(), adapters = MoshiJsonConverter.getTangemSdkAdapters() +
listOf(
BigDecimalAdapter(),
WalletDerivedKeysMapAdapter(),
ScanResponseDerivedKeysMapAdapter(),
ByteArrayKeyAdapter(),
ExtendedPublicKeysMapAdapter(),
CardBackupStatusAdapter(),
DerivationPathAdapterWithMigration(),
),
typedAdapters = MoshiJsonConverter.getTangemSdkTypedAdapters(), typedAdapters = MoshiJsonConverter.getTangemSdkTypedAdapters(),
) )
} }

View file

@ -107,6 +107,8 @@ object PreferencesKeys {
val ONRAMP_TRANSACTIONS_STATUSES_KEY by lazy { stringPreferencesKey(name = "onrampTransactionsStatuses") } val ONRAMP_TRANSACTIONS_STATUSES_KEY by lazy { stringPreferencesKey(name = "onrampTransactionsStatuses") }
val ONBOARDING_FINALIZE_SCAN_RESPONSE_KEY by lazy { stringPreferencesKey(name = "onboardingFinalizeScanResponse") }
val IS_GOOGLE_SERVICES_AVAILABLE_KEY by lazy { booleanPreferencesKey(name = "isGoogleServicesAvailable") } val IS_GOOGLE_SERVICES_AVAILABLE_KEY by lazy { booleanPreferencesKey(name = "isGoogleServicesAvailable") }
val IS_GOOGLE_PAY_AVAILABLE_KEY by lazy { booleanPreferencesKey(name = "isGooglePayAvailable") } val IS_GOOGLE_PAY_AVAILABLE_KEY by lazy { booleanPreferencesKey(name = "isGooglePayAvailable") }

View file

@ -26,5 +26,6 @@ dependencies {
// region Domain modules // region Domain modules
implementation(projects.domain.onboarding) implementation(projects.domain.onboarding)
implementation(projects.domain.models)
// endregion // endregion
} }

View file

@ -2,9 +2,8 @@ package com.tangem.data.onboarding
import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.get import com.tangem.datasource.local.preferences.utils.*
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault import com.tangem.domain.models.scan.ScanResponse
import com.tangem.datasource.local.preferences.utils.store
import com.tangem.domain.onboarding.repository.OnboardingRepository import com.tangem.domain.onboarding.repository.OnboardingRepository
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -30,4 +29,23 @@ internal class DefaultOnboardingRepository(
override suspend fun saveTwinsOnboardingShown() { override suspend fun saveTwinsOnboardingShown() {
appPreferencesStore.store(key = PreferencesKeys.WAS_TWINS_ONBOARDING_SHOWN, value = true) appPreferencesStore.store(key = PreferencesKeys.WAS_TWINS_ONBOARDING_SHOWN, value = true)
} }
override suspend fun getUnfinishedFinalizeOnboarding(): ScanResponse? {
return appPreferencesStore
.getObjectSyncOrNull<ScanResponse>(PreferencesKeys.ONBOARDING_FINALIZE_SCAN_RESPONSE_KEY)
}
override suspend fun saveUnfinishedFinalizeOnboarding(scanResponse: ScanResponse) {
appPreferencesStore
.storeObject<ScanResponse>(
PreferencesKeys.ONBOARDING_FINALIZE_SCAN_RESPONSE_KEY,
scanResponse,
)
}
override suspend fun clearUnfinishedFinalizeOnboarding() {
appPreferencesStore.editData { mutablePreferences ->
mutablePreferences.remove(PreferencesKeys.ONBOARDING_FINALIZE_SCAN_RESPONSE_KEY)
}
}
} }

View file

@ -6,4 +6,6 @@ plugins {
dependencies { dependencies {
implementation(deps.arrow.core) implementation(deps.arrow.core)
implementation(deps.kotlin.coroutines) implementation(deps.kotlin.coroutines)
implementation(projects.domain.models)
} }

View file

@ -1,5 +1,6 @@
package com.tangem.domain.onboarding.repository package com.tangem.domain.onboarding.repository
import com.tangem.domain.models.scan.ScanResponse
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
/** /**
@ -15,4 +16,10 @@ interface OnboardingRepository {
suspend fun wasTwinsOnboardingShownSync(): Boolean suspend fun wasTwinsOnboardingShownSync(): Boolean
suspend fun saveTwinsOnboardingShown() suspend fun saveTwinsOnboardingShown()
suspend fun getUnfinishedFinalizeOnboarding(): ScanResponse?
suspend fun saveUnfinishedFinalizeOnboarding(scanResponse: ScanResponse)
suspend fun clearUnfinishedFinalizeOnboarding()
} }

View file

@ -25,6 +25,7 @@ dependencies {
implementation(projects.core.res) implementation(projects.core.res)
implementation(projects.core.decompose) implementation(projects.core.decompose)
implementation(projects.core.navigation) implementation(projects.core.navigation)
implementation(projects.core.datasource)
/** Common */ /** Common */
implementation(projects.common.ui) implementation(projects.common.ui)
@ -39,6 +40,7 @@ dependencies {
implementation(projects.domain.wallets.models) implementation(projects.domain.wallets.models)
implementation(projects.domain.legacy) implementation(projects.domain.legacy)
implementation(projects.domain.settings) implementation(projects.domain.settings)
implementation(projects.domain.onboarding)
/** Tangem libraries */ /** Tangem libraries */
implementation(projects.libs.tangemSdkApi) implementation(projects.libs.tangemSdkApi)
@ -62,6 +64,7 @@ dependencies {
implementation(deps.lottie.compose) implementation(deps.lottie.compose)
implementation(deps.decompose.ext.compose) implementation(deps.decompose.ext.compose)
implementation(deps.androidx.activity.compose) implementation(deps.androidx.activity.compose)
implementation(deps.androidx.datastore)
/** Other libraries */ /** Other libraries */
implementation(deps.kotlin.immutable.collections) implementation(deps.kotlin.immutable.collections)

View file

@ -53,8 +53,6 @@ internal class OnboardingEntryModel @Inject constructor(
} }
return when (scanResponse.productType) { return when (scanResponse.productType) {
ProductType.Note -> TODO()
ProductType.Twins -> TODO()
ProductType.Wallet -> OnboardingRoute.MultiWallet( ProductType.Wallet -> OnboardingRoute.MultiWallet(
scanResponse = scanResponse, scanResponse = scanResponse,
withSeedPhraseFlow = false, withSeedPhraseFlow = false,
@ -71,8 +69,7 @@ internal class OnboardingEntryModel @Inject constructor(
onDone = ::onMultiWalletOnboardingDone, onDone = ::onMultiWalletOnboardingDone,
mode = multiWalletNavigationMode, mode = multiWalletNavigationMode,
) )
ProductType.Start2Coin -> TODO() else -> error("Unsupported")
ProductType.Visa -> TODO()
} }
} }

View file

@ -123,7 +123,6 @@ internal class MultiWalletAccessCodeModel @Inject constructor(
if (checkAccessCode()) { if (checkAccessCode()) {
backupServiceHolder.backupService.get()?.setAccessCode(_uiState.value.accessCodeFirst.text) backupServiceHolder.backupService.get()?.setAccessCode(_uiState.value.accessCodeFirst.text)
// TODO delete access code from state
// make on done // make on done
params.multiWalletState.update { params.multiWalletState.update {
it.copy(accessCode = OnboardingMultiWalletState.AccessCode(uiState.value.accessCodeFirst.text)) it.copy(accessCode = OnboardingMultiWalletState.AccessCode(uiState.value.accessCodeFirst.text))

View file

@ -1,6 +1,5 @@
package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model
// TODO maybe delete some fields in the future
data class BackupState( data class BackupState(
val backupCardsNumber: Int = 0, val numberOfBackupCards: Int = 0,
) )

View file

@ -15,9 +15,9 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEv
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams 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.MultiWalletBackupComponent
import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.backupCardAttestationFailedDialog import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.backupCardAttestationFailedDialog
import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.onlyOneBackupDeviceDialog
import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.resetBackupCardDialog import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.resetBackupCardDialog
import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.state.MultiWalletBackupUM import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.state.MultiWalletBackupUM
import com.tangem.operations.backup.BackupService
import com.tangem.sdk.api.BackupServiceHolder import com.tangem.sdk.api.BackupServiceHolder
import com.tangem.sdk.api.TangemSdkManager import com.tangem.sdk.api.TangemSdkManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -70,30 +70,19 @@ class MultiWalletBackupModel @Inject constructor(
} }
private fun getInitState(): MultiWalletBackupUM { private fun getInitState(): MultiWalletBackupUM {
return when (backupService.currentState) { return MultiWalletBackupUM(
BackupService.State.Preparing -> { title = resourceReference(R.string.onboarding_title_no_backup_cards),
MultiWalletBackupUM( bodyText = resourceReference(R.string.onboarding_subtitle_no_backup_cards),
title = resourceReference(R.string.onboarding_title_no_backup_cards), finalizeButtonEnabled = false,
bodyText = resourceReference(R.string.onboarding_subtitle_no_backup_cards), addBackupButtonEnabled = true,
finalizeButtonEnabled = false, addBackupButtonLoading = false,
addBackupButtonEnabled = true, onAddBackupClick = ::startBackupWallet,
addBackupButtonLoading = false, onFinalizeButtonClick = ::onFinalizeClick,
onAddBackupClick = ::startBackupWallet, )
onFinalizeButtonClick = ::onFinalizeClick,
onSkipButtonClick = {
// eventFlow.tryEmit(Unit)
},
)
}
// TODO
is BackupService.State.FinalizingBackupCard -> TODO()
BackupService.State.FinalizingPrimaryCard -> TODO()
BackupService.State.Finished -> TODO()
}
} }
private fun startBackupWallet() { private fun startBackupWallet() {
if (state.value.backupCardsNumber == 0) { if (state.value.numberOfBackupCards == 0) {
backupService.discardSavedBackup() backupService.discardSavedBackup()
} }
val primaryCard = scanResponse.primaryCard val primaryCard = scanResponse.primaryCard
@ -144,19 +133,35 @@ class MultiWalletBackupModel @Inject constructor(
} }
} }
private fun onFinalizeClick() { private fun onFinalizeClick(fromDialog: Boolean = false) {
when (state.value.backupCardsNumber) { when (state.value.numberOfBackupCards) {
1 -> { 1 -> {
// TODO show dialog
params.multiWalletState.update { it.copy(isThreeCards = false) } params.multiWalletState.update { it.copy(isThreeCards = false) }
if (fromDialog.not()) {
showOnlyOneBackupWarningDialog()
return
}
} }
2 -> { 2 -> {
params.multiWalletState.update { it.copy(isThreeCards = true) } params.multiWalletState.update { it.copy(isThreeCards = true) }
} }
} }
modelScope.launch { eventFlow.emit(MultiWalletBackupComponent.Event.Done) } modelScope.launch { eventFlow.emit(MultiWalletBackupComponent.Event.Done) }
analyticsHandler.send(OnboardingEvent.Backup.Finished(cardsCount = state.value.backupCardsNumber + 1)) analyticsHandler.send(OnboardingEvent.Backup.Finished(cardsCount = state.value.numberOfBackupCards + 1))
}
private fun showOnlyOneBackupWarningDialog() {
_uiState.update {
it.copy(
dialog = onlyOneBackupDeviceDialog(
onDismiss = { _uiState.update { it.copy(dialog = null) } },
onConfirm = { onFinalizeClick(fromDialog = true) },
),
)
}
} }
private fun addBackupCardWithService() { private fun addBackupCardWithService() {
@ -176,7 +181,7 @@ class MultiWalletBackupModel @Inject constructor(
is CompletionResult.Success -> { is CompletionResult.Success -> {
state.update { state.update {
it.copy( it.copy(
backupCardsNumber = it.backupCardsNumber + 1, numberOfBackupCards = it.numberOfBackupCards + 1,
) )
} }
@ -186,12 +191,12 @@ class MultiWalletBackupModel @Inject constructor(
) )
params.backups.update { params.backups.update {
it.copy( it.copy(
card2 = if (state.value.backupCardsNumber == 1) backupCardInfo else it.card2, card2 = if (state.value.numberOfBackupCards == 1) backupCardInfo else it.card2,
card3 = if (state.value.backupCardsNumber == 2) backupCardInfo else it.card3, card3 = if (state.value.numberOfBackupCards == 2) backupCardInfo else it.card3,
) )
} }
setNumberOfBackupCards(state.value.backupCardsNumber) setNumberOfBackupCards(state.value.numberOfBackupCards)
} }
is CompletionResult.Failure -> { is CompletionResult.Failure -> {
when (val error = result.error) { when (val error = result.error) {

View file

@ -28,4 +28,18 @@ internal fun backupCardAttestationFailedDialog(onDismiss: () -> Unit) = MultiWal
onDismiss = onDismiss, onDismiss = onDismiss,
onConfirm = onDismiss, onConfirm = onDismiss,
onCancel = null, onCancel = null,
)
internal fun onlyOneBackupDeviceDialog(onDismiss: () -> Unit, onConfirm: () -> Unit) = MultiWalletBackupUMDialog(
title = resourceReference(R.string.common_warning),
bodyText = resourceReference(R.string.onboarding_alert_message_not_max_backup_cards_added),
confirmText = resourceReference(R.string.common_continue),
cancelText = resourceReference(R.string.common_cancel),
warningCancelColor = false,
onDismiss = onDismiss,
onConfirm = {
onDismiss()
onConfirm()
},
onCancel = onDismiss,
) )

View file

@ -13,7 +13,6 @@ data class MultiWalletBackupUM(
val finalizeButtonEnabled: Boolean = false, val finalizeButtonEnabled: Boolean = false,
val onAddBackupClick: () -> Unit = {}, val onAddBackupClick: () -> Unit = {},
val onFinalizeButtonClick: () -> Unit = {}, val onFinalizeButtonClick: () -> Unit = {},
val onSkipButtonClick: () -> Unit = {},
val dialog: MultiWalletBackupUMDialog? = null, val dialog: MultiWalletBackupUMDialog? = null,
) )

View file

@ -37,8 +37,6 @@ internal class Wallet1ChooseOptionModel @Inject constructor(
} }
fun onSkipClick() { fun onSkipClick() {
// TODO show confirmation dialog
if (skipClicked) return if (skipClicked) return
skipClicked = true skipClicked = true

View file

@ -12,6 +12,7 @@ import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.scan.isRing import com.tangem.domain.models.scan.isRing
import com.tangem.domain.onboarding.repository.OnboardingRepository
import com.tangem.domain.wallets.builder.UserWalletBuilder import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWallet
@ -22,6 +23,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChild
import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.MultiWalletFinalizeComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.MultiWalletFinalizeComponent
import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.ui.state.MultiWalletFinalizeUM import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.ui.state.MultiWalletFinalizeUM
import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.resetCardDialog import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.resetCardDialog
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState.FinalizeStage.*
import com.tangem.operations.backup.BackupService import com.tangem.operations.backup.BackupService
import com.tangem.sdk.api.BackupServiceHolder import com.tangem.sdk.api.BackupServiceHolder
import com.tangem.sdk.api.TangemSdkManager import com.tangem.sdk.api.TangemSdkManager
@ -46,23 +48,66 @@ internal class MultiWalletFinalizeModel @Inject constructor(
private val generateWalletNameUseCase: GenerateWalletNameUseCase, private val generateWalletNameUseCase: GenerateWalletNameUseCase,
private val userWalletsListManager: UserWalletsListManager, private val userWalletsListManager: UserWalletsListManager,
private val cardRepository: CardRepository, private val cardRepository: CardRepository,
private val onboardingRepository: OnboardingRepository,
) : Model() { ) : Model() {
private val params = paramsContainer.require<MultiWalletChildParams>() private val params = paramsContainer.require<MultiWalletChildParams>()
private val multiWalletState get() = params.multiWalletState private val multiWalletState get() = params.multiWalletState
private val _uiState = MutableStateFlow( private val _uiState = MutableStateFlow(getInitialState())
MultiWalletFinalizeUM(
isRing = backupServiceHolder.backupService.get()?.primaryCardBatchId?.let { isRing(it) } ?: false,
onScanClick = ::onLinkClick,
cardNumber = backupServiceHolder.backupService.get()?.primaryCardId?.lastMasked().orEmpty(),
),
)
private val backupCardIds = backupServiceHolder.backupService.get()?.backupCardIds.orEmpty()
private var walletHasBackupError = false
private val backupCardIds = backupServiceHolder.backupService.get()?.backupCardIds.orEmpty()
private var walletHasBackupError = false
val uiState = _uiState.asStateFlow() val uiState = _uiState.asStateFlow()
val onEvent = MutableSharedFlow<MultiWalletFinalizeComponent.Event>() val onEvent = MutableSharedFlow<MultiWalletFinalizeComponent.Event>()
init {
// save scan response to preferences to be able
// to continue finalize process after app restart
modelScope.launch {
onboardingRepository.saveUnfinishedFinalizeOnboarding(
scanResponse = multiWalletState.value.currentScanResponse,
)
}
}
private fun getInitialState(): MultiWalletFinalizeUM {
val backupService = backupServiceHolder.backupService.get() ?: return MultiWalletFinalizeUM()
val initialStep = getInitialStep()
val batchId = when (initialStep) {
MultiWalletFinalizeUM.Step.Primary -> backupService.primaryCardBatchId
MultiWalletFinalizeUM.Step.BackupDevice1 -> backupService.backupCardsBatchIds.getOrNull(0)
MultiWalletFinalizeUM.Step.BackupDevice2 -> backupService.backupCardsBatchIds.getOrNull(1)
}
val cardId = when (initialStep) {
MultiWalletFinalizeUM.Step.Primary -> backupService.primaryCardId
MultiWalletFinalizeUM.Step.BackupDevice1 -> backupService.backupCardIds.getOrNull(0)
MultiWalletFinalizeUM.Step.BackupDevice2 -> backupService.backupCardIds.getOrNull(1)
}
return MultiWalletFinalizeUM(
isRing = batchId?.let { isRing(it) } ?: false,
onScanClick = ::onLinkClick,
scanPrimary = initialStep == MultiWalletFinalizeUM.Step.Primary,
cardNumber = cardId?.lastMasked().orEmpty(),
step = getInitialStep(),
)
}
private fun getInitialStep(): MultiWalletFinalizeUM.Step {
val startFromFinalize =
params.multiWalletState.value.startFromFinalize ?: return MultiWalletFinalizeUM.Step.Primary
return when (startFromFinalize) {
ScanPrimaryCard -> MultiWalletFinalizeUM.Step.Primary
ScanBackupFirstCard -> MultiWalletFinalizeUM.Step.BackupDevice1
ScanBackupSecondCard -> MultiWalletFinalizeUM.Step.BackupDevice2
}
}
private fun onLinkClick() { private fun onLinkClick() {
when (_uiState.value.step) { when (_uiState.value.step) {
MultiWalletFinalizeUM.Step.Primary -> { MultiWalletFinalizeUM.Step.Primary -> {
@ -79,8 +124,6 @@ internal class MultiWalletFinalizeModel @Inject constructor(
private fun writePrimaryCard() { private fun writePrimaryCard() {
val backupService = backupServiceHolder.backupService.get() ?: return val backupService = backupServiceHolder.backupService.get() ?: return
// TODO store.dispatchOnMain(BackupAction.SetHasRing(hasRing = isRing))
val primaryCardBatchId = backupService.primaryCardBatchId ?: return val primaryCardBatchId = backupService.primaryCardBatchId ?: return
val isRing = isRing(primaryCardBatchId) val isRing = isRing(primaryCardBatchId)
val iconScanRes = if (isRing) R.drawable.img_hand_scan_ring else null val iconScanRes = if (isRing) R.drawable.img_hand_scan_ring else null
@ -107,9 +150,6 @@ internal class MultiWalletFinalizeModel @Inject constructor(
private fun writeBackupCard(cardIndex: Int) { private fun writeBackupCard(cardIndex: Int) {
val backupService = backupServiceHolder.backupService.get() ?: return val backupService = backupServiceHolder.backupService.get() ?: return
// TODO store.dispatchOnMain(BackupAction.SetHasRing(hasRing = isRing))
val backupCardBatchId = backupService.backupCardsBatchIds.getOrNull(cardIndex) ?: return val backupCardBatchId = backupService.backupCardsBatchIds.getOrNull(cardIndex) ?: return
val isRing = isRing(backupCardBatchId) val isRing = isRing(backupCardBatchId)
val iconScanRes = if (isRing) R.drawable.img_hand_scan_ring else null val iconScanRes = if (isRing) R.drawable.img_hand_scan_ring else null
@ -151,8 +191,6 @@ internal class MultiWalletFinalizeModel @Inject constructor(
} }
private fun finishBackup() { private fun finishBackup() {
// TODO Analytics.send(Onboarding.Backup.Finished(backupState.backupCardsNumber))
modelScope.launch { modelScope.launch {
val scanResponse = params.multiWalletState.value.currentScanResponse val scanResponse = params.multiWalletState.value.currentScanResponse
val userWallet = createUserWallet(scanResponse) val userWallet = createUserWallet(scanResponse)
@ -183,8 +221,12 @@ internal class MultiWalletFinalizeModel @Inject constructor(
it.copy(resultUserWallet = userWallet) it.copy(resultUserWallet = userWallet)
} }
cardRepository.finishCardActivation(scanResponse.card.cardId) // user wallet is fully created and saved, remove scan response from preferences
// to prevent showing finalize screen dialog on next app start
onboardingRepository.clearUnfinishedFinalizeOnboarding()
cardRepository.finishCardActivation(scanResponse.card.cardId)
backupServiceHolder.backupService.get()?.discardSavedBackup()
onEvent.emit(MultiWalletFinalizeComponent.Event.ThreeBackupCardsAdded) onEvent.emit(MultiWalletFinalizeComponent.Event.ThreeBackupCardsAdded)
} }
} }
@ -201,7 +243,7 @@ internal class MultiWalletFinalizeModel @Inject constructor(
@Suppress("MagicNumber") @Suppress("MagicNumber")
private fun ScanResponse.updateScanResponseAfterBackup(): ScanResponse { private fun ScanResponse.updateScanResponseAfterBackup(): ScanResponse {
val cardsCount = if (params.multiWalletState.value.isThreeCards) 3 else 2 val cardsCount = if (params.multiWalletState.value.isThreeCards) 2 else 1
val card = card.copy( val card = card.copy(
backupStatus = CardDTO.BackupStatus.Active(cardCount = cardsCount), backupStatus = CardDTO.BackupStatus.Active(cardCount = cardsCount),

View file

@ -1,7 +1,7 @@
package com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.ui package com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.ui
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier

View file

@ -135,8 +135,6 @@ internal class MultiWalletSeedPhraseModel @Inject constructor(
} }
private fun openGeneratedSeedPhrase() { private fun openGeneratedSeedPhrase() {
// TODO analytics SeedPhraseEvents.ButtonGenerateSeedPhrase
val words12 = state.value.generatedWords12 ?: return val words12 = state.value.generatedWords12 ?: return
val words24 = state.value.generatedWords24 ?: return val words24 = state.value.generatedWords24 ?: return

View file

@ -5,13 +5,18 @@ import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.navigation.Router
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.wallets.usecase.GetCardImageUseCase import com.tangem.domain.wallets.usecase.GetCardImageUseCase
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent 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.analytics.OnboardingEvent
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams 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.common.ui.interruptBackupDialog
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState.FinalizeStage
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM
import com.tangem.operations.backup.BackupService
import com.tangem.sdk.api.BackupServiceHolder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -23,6 +28,8 @@ internal class OnboardingMultiWalletModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
private val router: Router, private val router: Router,
private val analyticsHandler: AnalyticsEventHandler, private val analyticsHandler: AnalyticsEventHandler,
private val backupServiceHolder: BackupServiceHolder,
private val appPreferencesStore: AppPreferencesStore,
) : Model() { ) : Model() {
private val params = paramsContainer.require<OnboardingMultiWalletComponent.Params>() private val params = paramsContainer.require<OnboardingMultiWalletComponent.Params>()
private val getCardImageUseCase = GetCardImageUseCase() private val getCardImageUseCase = GetCardImageUseCase()
@ -35,6 +42,7 @@ internal class OnboardingMultiWalletModel @Inject constructor(
accessCode = null, accessCode = null,
isThreeCards = true, isThreeCards = true,
resultUserWallet = null, resultUserWallet = null,
startFromFinalize = getInitialStartFromFinalize(),
), ),
) )
val backups = MutableStateFlow(MultiWalletChildParams.Backup()) val backups = MutableStateFlow(MultiWalletChildParams.Backup())
@ -48,10 +56,11 @@ internal class OnboardingMultiWalletModel @Inject constructor(
} }
fun onBack() { fun onBack() {
_uiState.update { _uiState.update { st ->
it.copy( st.copy(
dialog = interruptBackupDialog( dialog = interruptBackupDialog(
onConfirm = { onConfirm = {
removeFinalizeScanResponseState()
router.pop() router.pop()
}, },
dismiss = { _uiState.update { it.copy(dialog = null) } }, dismiss = { _uiState.update { it.copy(dialog = null) } },
@ -60,20 +69,30 @@ internal class OnboardingMultiWalletModel @Inject constructor(
} }
} }
private fun removeFinalizeScanResponseState() {
// discarding onboarding means we have to remove scan response from preferences
// to prevent showing finalize screen dialog on next app start
modelScope.launch {
appPreferencesStore.editData { mutablePreferences ->
mutablePreferences.remove(PreferencesKeys.ONBOARDING_FINALIZE_SCAN_RESPONSE_KEY)
}
}
}
private fun subscribeToBackups() { private fun subscribeToBackups() {
modelScope.launch { modelScope.launch {
backups.collectLatest { bst -> backups.collectLatest { backup ->
when { when {
_uiState.value.artwork2Url == null && bst.card2 != null -> { _uiState.value.artwork2Url == null && backup.card2 != null -> {
val artwork = val artwork =
getCardImageUseCase.invoke(bst.card2.cardId, bst.card2.cardPublicKey) getCardImageUseCase.invoke(backup.card2.cardId, backup.card2.cardPublicKey)
_uiState.update { _uiState.update {
it.copy(artwork2Url = artwork) it.copy(artwork2Url = artwork)
} }
} }
_uiState.value.artwork3Url == null && bst.card3 != null -> { _uiState.value.artwork3Url == null && backup.card3 != null -> {
val artwork = val artwork =
getCardImageUseCase.invoke(bst.card3.cardId, bst.card3.cardPublicKey) getCardImageUseCase.invoke(backup.card3.cardId, backup.card3.cardPublicKey)
_uiState.update { _uiState.update {
it.copy(artwork3Url = artwork) it.copy(artwork3Url = artwork)
} }
@ -85,9 +104,10 @@ internal class OnboardingMultiWalletModel @Inject constructor(
private fun getInitialStep(): OnboardingMultiWalletState.Step { private fun getInitialStep(): OnboardingMultiWalletState.Step {
val card = params.scanResponse.card val card = params.scanResponse.card
val backupService = backupServiceHolder.backupService.get()!!
// todo check local storage for scan response
return when { return when {
backupService.hasIncompletedBackup -> OnboardingMultiWalletState.Step.Finalize
card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup -> card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup ->
OnboardingMultiWalletState.Step.AddBackupDevice OnboardingMultiWalletState.Step.AddBackupDevice
card.wallets.isNotEmpty() && card.backupStatus?.isActive == true -> card.wallets.isNotEmpty() && card.backupStatus?.isActive == true ->
@ -97,6 +117,19 @@ internal class OnboardingMultiWalletModel @Inject constructor(
} }
} }
private fun getInitialStartFromFinalize(): FinalizeStage? {
val backupService = backupServiceHolder.backupService.get() ?: return null
return when (val state = backupService.currentState) {
BackupService.State.FinalizingPrimaryCard -> FinalizeStage.ScanPrimaryCard
is BackupService.State.FinalizingBackupCard -> if (state.index == 1) {
FinalizeStage.ScanBackupFirstCard
} else {
FinalizeStage.ScanBackupSecondCard
}
else -> null
}
}
private fun initScreenTitle() { private fun initScreenTitle() {
val title = screenTitleByStep(getInitialStep()) val title = screenTitleByStep(getInitialStep())
params.titleProvider.changeTitle(title) params.titleProvider.changeTitle(title)

View file

@ -8,8 +8,13 @@ data class OnboardingMultiWalletState(
val accessCode: AccessCode?, val accessCode: AccessCode?,
val isThreeCards: Boolean, val isThreeCards: Boolean,
val currentScanResponse: ScanResponse, val currentScanResponse: ScanResponse,
val resultUserWallet: UserWallet?, // TODO make callback instead val startFromFinalize: FinalizeStage?,
val resultUserWallet: UserWallet?,
) { ) {
enum class FinalizeStage {
ScanPrimaryCard, ScanBackupFirstCard, ScanBackupSecondCard
}
/** /**
* Wallet1 * Wallet1
* CreateWallet -> ChooseBackupOption -> AddBackupDevice -> Finalize -> [Done] * CreateWallet -> ChooseBackupOption -> AddBackupDevice -> Finalize -> [Done]