diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index cc4284cc7a..e0669f7e68 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -52,6 +52,7 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent +import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.features.send.api.navigation.SendRouter @@ -59,6 +60,7 @@ import com.tangem.features.staking.api.navigation.StakingRouter import com.tangem.features.tokendetails.navigation.TokenDetailsRouter import com.tangem.features.wallet.navigation.WalletRouter import com.tangem.operations.backup.BackupService +import com.tangem.sdk.api.BackupServiceHolder import com.tangem.sdk.api.TangemSdkManager import com.tangem.sdk.extensions.init import com.tangem.tap.common.ActivityResultCallbackHolder @@ -185,6 +187,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var shouldInitiallyAskPermissionUseCase: ShouldInitiallyAskPermissionUseCase + @Inject + lateinit var backupServiceHolder: BackupServiceHolder + + @Inject + lateinit var onboardingV2FeatureToggles: OnboardingV2FeatureToggles + internal val viewModel: MainViewModel by viewModels() private lateinit var appThemeModeFlow: SharedFlow @@ -294,7 +302,14 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac cardSdkOwner.register(activity = this) tangemSdkManager = injectedTangemSdkManager appStateHolder.tangemSdkManager = tangemSdkManager - backupService = BackupService.init(cardSdkConfigRepository.sdk, this) + + if (onboardingV2FeatureToggles.isOnboardingV2Enabled) { + backupServiceHolder.createAndSetService(cardSdkConfigRepository.sdk, this) + backupService = backupServiceHolder.backupService.get()!! // will be deleted eventually + } else { + backupService = BackupService.init(cardSdkConfigRepository.sdk, this) + } + lockUserWalletsTimer = LockUserWalletsTimer( owner = this, settingsRepository = settingsRepository, @@ -550,6 +565,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } } + // TODO add onboarding v2 handling store.dispatch(BackupAction.CheckForUnfinishedBackup) } diff --git a/app/src/main/java/com/tangem/tap/di/TangemSdkModule.kt b/app/src/main/java/com/tangem/tap/di/TangemSdkModule.kt new file mode 100644 index 0000000000..b2f2f6d5c5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/TangemSdkModule.kt @@ -0,0 +1,17 @@ +package com.tangem.tap.di + +import com.tangem.sdk.api.BackupServiceHolder +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object TangemSdkModule { + + @Provides + @Singleton + fun provideBackupServiceHolder(): BackupServiceHolder = BackupServiceHolder() +} \ No newline at end of file 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 2f18b059ca..7ee5761836 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 @@ -67,7 +67,10 @@ object OnboardingHelper { fun whereToNavigate(scanResponse: ScanResponse): AppRoute { if (store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled) { - return AppRoute.Onboarding(scanResponse) + return AppRoute.Onboarding( + scanResponse = scanResponse, + startFromBackup = false, + ) } return when (val type = scanResponse.productType) { 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 b031d03e46..fe71e3ac0d 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 @@ -218,7 +218,10 @@ internal class ChildFactory @Inject constructor( is AppRoute.Onboarding -> { route.asComponentChild( contextProvider = contextProvider(route, contextFactory), - params = OnboardingEntryComponent.Params(route.scanResponse), + params = OnboardingEntryComponent.Params( + scanResponse = route.scanResponse, + startBackupFlow = route.startFromBackup, + ), 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 6744b6c0be..f5982495e5 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 @@ -300,5 +300,8 @@ sealed class AppRoute(val path: String) : Route { ) : AppRoute(path = "/sell_crypto/${userWalletId.stringValue}") // Onboarding V2 - data class Onboarding(val scanResponse: ScanResponse) : AppRoute(path = "/onboarding_v2") + data class Onboarding( + val scanResponse: ScanResponse, + val startFromBackup: Boolean, + ) : AppRoute(path = "/onboarding_v2${if (startFromBackup) "/backup" else ""}") } \ 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 847e64541e..3dafb568ad 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 @@ -8,6 +8,7 @@ interface OnboardingEntryComponent : ComposableContentComponent { data class Params( val scanResponse: ScanResponse, + val startBackupFlow: Boolean, ) interface Factory : ComponentFactory diff --git a/features/onboarding-v2/impl/build.gradle.kts b/features/onboarding-v2/impl/build.gradle.kts index 25f02a083b..ba4fb5017f 100644 --- a/features/onboarding-v2/impl/build.gradle.kts +++ b/features/onboarding-v2/impl/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { implementation(projects.domain.models) implementation(projects.domain.feedback) implementation(projects.domain.core) + implementation(projects.domain.card) /** Tangem libraries */ implementation(projects.libs.tangemSdkApi) @@ -54,6 +55,7 @@ dependencies { implementation(deps.kotlin.immutable.collections) implementation(deps.kotlin.serialization) implementation(deps.timber) + implementation(deps.firebase.crashlytics) /** DI */ implementation(deps.hilt.android) 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 9a040ff17c..07053a7b9e 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 @@ -40,12 +40,12 @@ internal class OnboardingEntryModel @Inject constructor( return when (scanResponse.productType) { ProductType.Note -> TODO() ProductType.Twins -> TODO() - ProductType.Wallet -> OnboardingRoute.Wallet12( + ProductType.Wallet -> OnboardingRoute.MultiWallet( scanResponse = scanResponse, withSeedPhraseFlow = false, titleProvider = titleProvider, ) - ProductType.Wallet2 -> OnboardingRoute.Wallet12( + ProductType.Wallet2 -> OnboardingRoute.MultiWallet( scanResponse = scanResponse, withSeedPhraseFlow = true, titleProvider = titleProvider, 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 3cd1b811a8..e25190780d 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 @@ -5,12 +5,12 @@ import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletCo import javax.inject.Inject internal class OnboardingChildFactory @Inject constructor( - private val wallet12ComponentFactory: OnboardingMultiWalletComponent.Factory, + private val multiWalletComponentFactory: OnboardingMultiWalletComponent.Factory, ) { fun createChild(route: OnboardingRoute, childContext: AppComponentContext): Any { return when (route) { - is OnboardingRoute.Wallet12 -> wallet12ComponentFactory.create( + is OnboardingRoute.MultiWallet -> multiWalletComponentFactory.create( context = childContext, params = OnboardingMultiWalletComponent.Params( scanResponse = route.scanResponse, 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 4dbd6593f0..b315398e7c 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 @@ -8,7 +8,7 @@ sealed class OnboardingRoute : Route { data object None : OnboardingRoute() - data class Wallet12( + data class MultiWallet( val titleProvider: TitleProvider, val scanResponse: ScanResponse, val withSeedPhraseFlow: Boolean, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt index 16400dd642..37d5e6acd7 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/entry/impl/ui/OnboardingEntry.kt @@ -31,7 +31,7 @@ internal inline fun OnboardingEntry( animation = stackAnimation(slide()), ) { when (it.configuration) { - is OnboardingRoute.Wallet12 -> { + is OnboardingRoute.MultiWallet -> { (it.instance as OnboardingMultiWalletComponent).Content( modifier = modifier, ) 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 8fbb204794..9b8676533f 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 @@ -32,12 +32,11 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor ) : OnboardingMultiWalletComponent, AppComponentContext by context { private val model: OnboardingMultiWalletModel = getOrCreateModel(params) - private val stackNavigation = StackNavigation() override val innerNavigation: InnerNavigation = object : InnerNavigation { override val state = MutableStateFlow( - Wallet12InnerNavigationState(1, 5), + MultiWalletInnerNavigationState(1, 5), ) override fun pop(onComplete: (Boolean) -> Unit) { @@ -83,8 +82,10 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor private fun onStepDone() { when (model.state.value.currentStep) { - OnboardingMultiWalletState.Step.CreateWallet -> + OnboardingMultiWalletState.Step.CreateWallet -> { stackNavigation.push(OnboardingMultiWalletState.Step.AddBackupDevice) + // innerNavigation.state.value TODO + } OnboardingMultiWalletState.Step.AddBackupDevice -> TODO() OnboardingMultiWalletState.Step.FinishBackup -> TODO() OnboardingMultiWalletState.Step.Done -> TODO() @@ -112,7 +113,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor } } -data class Wallet12InnerNavigationState( +data class MultiWalletInnerNavigationState( override val stackSize: Int, override val stackMaxSize: Int?, ) : InnerNavigationState \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt index ff0422eeab..f68f4741b3 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/MultiWalletBackupComponent.kt @@ -12,6 +12,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChild import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildComponent import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model.MultiWalletBackupModel import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.MultiWalletBackup +import kotlinx.coroutines.launch @Suppress("UnusedPrivateMember") class MultiWalletBackupComponent( @@ -26,6 +27,10 @@ class MultiWalletBackupComponent( params.parentParams.titleProvider.changeTitle( text = resourceReference(R.string.onboarding_navbar_title_creating_backup), ) + + componentScope.launch { + model.onDoneFlow.collect { onDone() } + } } @Composable diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/BackupState.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/BackupState.kt new file mode 100644 index 0000000000..b1fd1a646c --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/model/BackupState.kt @@ -0,0 +1,48 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model + +import android.graphics.Bitmap +import com.tangem.common.card.Card + +// TODO maybe delete some fields in the future +data class BackupState( + val primaryCardId: String? = null, + val primaryCardBatchId: String? = null, + val backupCardsNumber: Int = 0, + val backupCards: List = emptyList(), + val backupCardIds: List = emptyList(), + val backupCardBatchIds: List = emptyList(), + @Transient + val backupCardsArtworks: Map = emptyMap(), + val accessCode: String = "", + val accessCodeError: AccessCodeError? = null, + val backupStep: BackupStep? = null, + val maxBackupCards: Int = 2, + val canSkipBackup: Boolean = true, + val isInterruptedBackup: Boolean = false, + val showBtnLoading: Boolean = false, + val hasBackupError: Boolean = false, + val startedSource: BackupStartedSource = BackupStartedSource.Onboarding, + val hasRing: Boolean = false, +) + +enum class BackupStartedSource { + Onboarding, CreateBackup +} + +enum class AccessCodeError { + CodeTooShort, CodesDoNotMatch +} + +typealias CardId = String + +sealed class BackupStep { + data object InitBackup : BackupStep() + data object ScanOriginCard : BackupStep() + data object AddBackupCards : BackupStep() + data object SetAccessCode : BackupStep() + data object EnterAccessCode : BackupStep() + data object ReenterAccessCode : BackupStep() + data object WritePrimaryCard : BackupStep() + data class WriteBackupCard(val cardNumber: Int) : BackupStep() + data object Finished : BackupStep() +} \ No newline at end of file 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 1b6ddac97b..66ffa8ff7f 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 @@ -1,31 +1,201 @@ 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.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +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.child.MultiWalletChildParams +import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.backupCardAttestationFailedDialog +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.operations.backup.BackupService +import com.tangem.sdk.api.BackupServiceHolder +import com.tangem.sdk.api.TangemSdkManager import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import javax.inject.Inject @ComponentScoped class MultiWalletBackupModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, + private val backupServiceHolder: BackupServiceHolder, + private val cardSdkConfigRepository: CardSdkConfigRepository, + private val tangemSdkManager: TangemSdkManager, ) : Model() { @Suppress("UnusedPrivateMember") private val params = paramsContainer.require() + private val scanResponse + get() = params.multiWalletState.value.currentScanResponse + private val backupService + get() = backupServiceHolder.backupService.get()!! + + private val state = MutableStateFlow(BackupState()) private val _uiState = MutableStateFlow( - MultiWalletBackupUM( - finalizeButtonEnabled = false, - onAddBackupClick = {}, - onFinalizeButtonClick = {}, - ), + when (scanResponse.productType) { + ProductType.Wallet -> getWallet1State() + ProductType.Wallet2, + ProductType.Ring, + -> MultiWalletBackupUM.Wallet2( + isRing = scanResponse.productType == ProductType.Ring, + backupAdded = false, // backupService.hasIncompletedBackup, + onAddBackupClick = {}, + onFinalizeButtonClick = {}, + ) + else -> error("Type: ${scanResponse.productType.name} is not supported!") + }, ) val uiState: StateFlow = _uiState + val onDoneFlow = MutableSharedFlow() + + private fun getWallet1State(): MultiWalletBackupUM.Wallet1 { + return when (backupService.currentState) { + BackupService.State.Preparing -> { + MultiWalletBackupUM.Wallet1( + title = resourceReference(R.string.onboarding_title_no_backup_cards), + bodyText = resourceReference(R.string.onboarding_subtitle_no_backup_cards), + showFinalizeButton = false, + addBackupButtonEnabled = true, + addBackupButtonLoading = false, + artworkNumberOfBackupCards = 0, + onAddBackupClick = ::startBackupWallet1, + onFinalizeButtonClick = {}, + onSkipButtonClick = { onDoneFlow.tryEmit(Unit) }, + ) + } + is BackupService.State.FinalizingBackupCard -> TODO() + BackupService.State.FinalizingPrimaryCard -> TODO() + BackupService.State.Finished -> TODO() + } + } + + private fun startBackupWallet1() { + backupService.discardSavedBackup() + val primaryCard = scanResponse.primaryCard + + if (primaryCard != null) { + backupService.setPrimaryCard(primaryCard) + addBackupCardWithService() + } else { + // TODO wallet 2 ??? or from main? + // BackupAction.StartAddingPrimaryCard + } + } + + private fun setNumberOfBackupCardsWallet1(number: Int) { + // set state for Wallet1 for adding backup cards and disable button if there is more than 2 backup cards + _uiState.update { st -> + if (st !is MultiWalletBackupUM.Wallet1) return@update st + + when (number) { + 0 -> { + st.copy( + title = resourceReference(R.string.onboarding_title_no_backup_cards), + bodyText = resourceReference(R.string.onboarding_subtitle_no_backup_cards), + artworkNumberOfBackupCards = 0, + addBackupButtonEnabled = true, + ) + } + 1 -> { + st.copy( + title = resourceReference(R.string.onboarding_title_one_backup_card), + bodyText = resourceReference(R.string.onboarding_subtitle_one_backup_card), + artworkNumberOfBackupCards = 1, + addBackupButtonEnabled = true, + showFinalizeButton = true, + ) + } + 2 -> { + st.copy( + title = resourceReference(R.string.onboarding_title_two_backup_cards), + bodyText = resourceReference(R.string.onboarding_subtitle_two_backup_cards), + artworkNumberOfBackupCards = 2, + addBackupButtonEnabled = false, + ) + } + else -> st.copy(addBackupButtonEnabled = false) + } + } + } + + private fun addBackupCardWithService() { + updateWallet1State { st -> + st.copy(addBackupButtonLoading = true) + } + + backupService.addBackupCard { result -> + backupService.skipCompatibilityChecks = false + cardSdkConfigRepository.sdk.config.filter.cardIdFilter = null + + updateWallet1State { st -> + st.copy(addBackupButtonLoading = false) + } + + when (result) { + is CompletionResult.Success -> { + // TODO change artwork + state.update { + it.copy( + backupCards = it.backupCards + result.data, + backupCardsNumber = it.backupCardsNumber + 1, + ) + } + setNumberOfBackupCardsWallet1(state.value.backupCardsNumber) + } + is CompletionResult.Failure -> { + when (val error = result.error) { + is TangemSdkError.BackupFailedNotEmptyWallets -> { + updateWallet1State { st -> + st.copy( + dialog = resetBackupCardDialog( + onReset = { resetBackupCard(cardId = error.cardId) }, + onDismiss = { updateWallet1State { it.copy(dialog = null) } }, + ), + ) + } + } + is TangemSdkError.IssuerSignatureLoadingFailed -> { + updateWallet1State { st -> + st.copy( + dialog = backupCardAttestationFailedDialog( + onDismiss = { updateWallet1State { it.copy(dialog = null) } }, + ), + ) + } + } + else -> FirebaseCrashlytics.getInstance().recordException(result.error) + } + } + } + } + } + + private fun resetBackupCard(cardId: String) { + modelScope.launch { + tangemSdkManager.resetToFactorySettings( + cardId = cardId, + allowsRequestAccessCodeFromRepository = false, + ) + } + } + + private fun updateWallet1State(block: (MultiWalletBackupUM.Wallet1) -> MultiWalletBackupUM.Wallet1) { + _uiState.update { state -> + if (state !is MultiWalletBackupUM.Wallet1) return@update state + block(state) + } + } } \ No newline at end of file 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 new file mode 100644 index 0000000000..5b9acbb42e --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Dialogs.kt @@ -0,0 +1,27 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui + +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 backupCardAttestationFailedDialog(onDismiss: () -> Unit) = MultiWalletBackupUMDialog( + title = resourceReference(R.string.common_error), + bodyText = resourceReference(R.string.issuer_signature_loading_failed), + confirmText = resourceReference(R.string.common_ok), + cancelText = null, + warningCancelColor = true, + onDismiss = onDismiss, + onConfirm = onDismiss, + onCancel = null, +) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt index d050a2128f..5c397a1811 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/MultiWalletBackup.kt @@ -4,15 +4,19 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.PrimaryButtonIconEnd -import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.components.* +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.onboarding.v2.impl.R import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.state.MultiWalletBackupUM @@ -38,11 +42,90 @@ fun MultiWalletBackup(state: MultiWalletBackupUM, modifier: Modifier = Modifier) ) } + when (state) { + is MultiWalletBackupUM.Wallet1 -> Wallet1Content(state) + is MultiWalletBackupUM.Wallet2 -> TODO() + } + } + + val dialog = state.dialog + if (dialog != null) { + BasicDialog( + title = dialog.title.resolveReference(), + message = dialog.bodyText.resolveReference(), + confirmButton = DialogButtonUM( + title = dialog.confirmText.resolveReference(), + onClick = dialog.onConfirm, + ), + dismissButton = if (dialog.cancelText != null && dialog.onCancel != null) { + DialogButtonUM( + title = dialog.cancelText.resolveReference(), + warning = dialog.warningCancelColor, + onClick = dialog.onCancel, + ) + } else { + null + }, + onDismissDialog = dialog.onDismiss, + ) + } +} + +@Composable +private fun ColumnScope.Wallet1Content(state: MultiWalletBackupUM.Wallet1) { + Column( + Modifier + .fillMaxWidth() + .padding(start = 32.dp, end = 32.dp, bottom = 74.dp) + .weight(1f), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + // artwork + + Text( + text = state.title.resolveReference(), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 16.dp), + ) + + Text( + text = state.bodyText.resolveReference(), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 12.dp), + ) + } + + if (state.showFinalizeButton) { + SecondaryButtonIconEnd( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 12.dp) + .fillMaxWidth(), + iconResId = R.drawable.ic_tangem_24, + enabled = state.addBackupButtonEnabled, + showProgress = state.addBackupButtonLoading, + text = stringResource(R.string.onboarding_button_add_backup_card), + onClick = state.onAddBackupClick, + ) + + PrimaryButton( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth(), + text = stringResource(R.string.onboarding_button_finalize_backup), + onClick = state.onFinalizeButtonClick, + ) + } else { PrimaryButtonIconEnd( modifier = Modifier .padding(start = 16.dp, end = 16.dp, bottom = 12.dp) .fillMaxWidth(), iconResId = R.drawable.ic_tangem_24, + enabled = state.addBackupButtonEnabled, + showProgress = state.addBackupButtonLoading, text = stringResource(R.string.onboarding_button_add_backup_card), onClick = state.onAddBackupClick, ) @@ -51,20 +134,33 @@ fun MultiWalletBackup(state: MultiWalletBackupUM, modifier: Modifier = Modifier) modifier = Modifier .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) .fillMaxWidth(), - enabled = state.finalizeButtonEnabled, - text = stringResource(R.string.onboarding_button_finalize_backup), - onClick = state.onFinalizeButtonClick, + text = stringResource(R.string.onboarding_button_skip_backup), + onClick = state.onSkipButtonClick, ) } } @Preview @Composable -private fun Preview() { +private fun PreviewWallet1() { TangemThemePreview { MultiWalletBackup( - state = MultiWalletBackupUM( - finalizeButtonEnabled = true, + state = MultiWalletBackupUM.Wallet1( + title = stringReference("Title"), + bodyText = stringReference("Body body body"), + ), + ) + } +} + +@Preview +@Composable +private fun PreviewWallet2() { + TangemThemePreview { + MultiWalletBackup( + state = MultiWalletBackupUM.Wallet2( + isRing = false, + backupAdded = true, onAddBackupClick = {}, onFinalizeButtonClick = {}, ), diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Wallet1Artwork.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Wallet1Artwork.kt new file mode 100644 index 0000000000..6051542ddf --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/Wallet1Artwork.kt @@ -0,0 +1,21 @@ +package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.res.TangemThemePreview + +@Composable +fun Wallet1Artwork(modifier: Modifier = Modifier) { + Box(modifier) { + } +} + +@Preview +@Composable +private fun Preview() { + TangemThemePreview { + Wallet1Artwork() + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/state/MultiWalletBackupUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/state/MultiWalletBackupUM.kt index 013235cb7b..cb3351c3db 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/state/MultiWalletBackupUM.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/backup/ui/state/MultiWalletBackupUM.kt @@ -1,7 +1,45 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.state -data class MultiWalletBackupUM( - val finalizeButtonEnabled: Boolean, - val onAddBackupClick: () -> Unit, - val onFinalizeButtonClick: () -> Unit, +import androidx.annotation.IntRange +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference + +@Immutable +sealed class MultiWalletBackupUM { + + abstract val dialog: MultiWalletBackupUMDialog? + + data class Wallet1( + val title: TextReference = stringReference(""), + val bodyText: TextReference = stringReference(""), + val addBackupButtonEnabled: Boolean = true, + val addBackupButtonLoading: Boolean = false, + val showFinalizeButton: Boolean = false, + val oneBackupAdded: Boolean = false, + @IntRange(0, 2) val artworkNumberOfBackupCards: Int = 0, // highlights cards on 0, 1, 2 + val onAddBackupClick: () -> Unit = {}, + val onFinalizeButtonClick: () -> Unit = {}, + val onSkipButtonClick: () -> Unit = {}, + override val dialog: MultiWalletBackupUMDialog? = null, + ) : MultiWalletBackupUM() + + data class Wallet2( + val isRing: Boolean, + val backupAdded: Boolean, + val onAddBackupClick: () -> Unit, + val onFinalizeButtonClick: () -> Unit, + override val dialog: MultiWalletBackupUMDialog? = null, + ) : MultiWalletBackupUM() +} + +data class MultiWalletBackupUMDialog( + val title: TextReference, + val bodyText: TextReference, + val confirmText: TextReference, + val cancelText: TextReference?, + val warningCancelColor: Boolean = false, + val onConfirm: () -> Unit, + val onCancel: (() -> Unit)?, + val onDismiss: () -> Unit, ) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/Dialogs.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/Dialogs.kt index 4dcd0f1696..d3b790586d 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/Dialogs.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/Dialogs.kt @@ -4,13 +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.createwallet.ui.state.MultiWalletCreateWalletUM -internal fun resetCardDialog(onConfirm: () -> Unit, onDismiss: () -> Unit, onDismissButtonClick: () -> Unit) = +internal fun resetCardDialog(onConfirm: () -> Unit, dismiss: () -> Unit, onDismissButtonClick: () -> Unit) = MultiWalletCreateWalletUM.Dialog( title = resourceReference(R.string.onboarding_activation_error_title), description = resourceReference(R.string.onboarding_activation_error_message), - confirmButtonText = resourceReference(R.string.common_ok), - dismissButtonText = resourceReference(R.string.common_support), - onDismiss = onDismiss, - onConfirm = onConfirm, - onDismissButtonClick = onDismissButtonClick, + confirmButtonText = resourceReference(R.string.common_support), + dismissButtonText = resourceReference(R.string.common_ok), + dismissWarningColor = true, + onDismiss = dismiss, + onConfirm = { + onConfirm() + dismiss() + }, + onDismissButtonClick = { + 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/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 0a1992053f..e32cbde7d2 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 @@ -5,6 +5,7 @@ import com.tangem.common.core.TangemSdkError import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +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 @@ -26,6 +27,7 @@ class MultiWalletCreateWalletModel @Inject constructor( private val tangemSdkManager: TangemSdkManager, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val getCardInfoUseCase: GetCardInfoUseCase, + private val cardRepository: CardRepository, ) : Model() { private val params = paramsContainer.require() @@ -63,6 +65,7 @@ class MultiWalletCreateWalletModel @Inject constructor( ) } + cardRepository.startCardActivation(cardId = result.data.card.cardId) onDone.emit(Unit) // TODO // Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully()) @@ -82,14 +85,9 @@ class MultiWalletCreateWalletModel @Inject constructor( _uiState.update { state -> state.copy( dialog = resetCardDialog( - onConfirm = { - _uiState.update { it.copy(dialog = null) } - resetCard() - }, - onDismiss = { - _uiState.update { it.copy(dialog = null) } - }, - onDismissButtonClick = ::navigateToSupportScreen, + onConfirm = ::navigateToSupportScreen, + dismiss = { _uiState.update { it.copy(dialog = null) } }, + onDismissButtonClick = ::resetCard, ), ) } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt index 6b683f4ce6..8fb08b976c 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt @@ -32,6 +32,7 @@ fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier: Modifier ), dismissButton = DialogButtonUM( title = state.dialog.dismissButtonText.resolveReference(), + warning = state.dialog.dismissWarningColor, onClick = state.dialog.onDismissButtonClick, ), onDismissDialog = state.dialog.onDismiss, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt index 9c48ec07a2..c761e1f481 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt @@ -13,6 +13,7 @@ data class MultiWalletCreateWalletUM( val description: TextReference, val dismissButtonText: TextReference, val confirmButtonText: TextReference, + val dismissWarningColor: Boolean = false, val onConfirm: () -> Unit, val onDismissButtonClick: () -> Unit, val onDismiss: () -> Unit, 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 2510f246f6..1497b73c2d 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 @@ -28,6 +28,10 @@ internal class OnboardingMultiWalletModel @Inject constructor( ) init { + // TODO add analytics + // if (!manager.isActivationStarted(notNullCard.cardId)) { + // Analytics.send(Onboarding.Started()) + // } initScreenTitleSub() } @@ -49,11 +53,10 @@ internal class OnboardingMultiWalletModel @Inject constructor( params.titleProvider.changeTitle(title) modelScope.launch { - state - .map { it.currentStep } + state.map { it.currentStep } .collectLatest { step -> - val title = screenTitleByStep(step) - params.titleProvider.changeTitle(title) + val stepTitle = screenTitleByStep(step) + params.titleProvider.changeTitle(stepTitle) } } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt index b3c6b709c2..0c3ecc83ea 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/model/OnboardingMultiWalletState.kt @@ -6,7 +6,6 @@ data class OnboardingMultiWalletState( val currentStep: Step, val currentScanResponse: ScanResponse, ) { - enum class Step { CreateWallet, AddBackupDevice, FinishBackup, Done } diff --git a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/BackupServiceHolder.kt b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/BackupServiceHolder.kt new file mode 100644 index 0000000000..85795081fc --- /dev/null +++ b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/BackupServiceHolder.kt @@ -0,0 +1,17 @@ +package com.tangem.sdk.api + +import androidx.activity.ComponentActivity +import com.tangem.TangemSdk +import com.tangem.operations.backup.BackupService +import com.tangem.sdk.extensions.init +import java.lang.ref.WeakReference + +class BackupServiceHolder { + + lateinit var backupService: WeakReference + private set + + fun createAndSetService(tangemSdk: TangemSdk, activity: ComponentActivity) { + backupService = WeakReference(BackupService.init(tangemSdk, activity)) + } +} \ No newline at end of file