From 9812f9f2536c26559aa2177eec78112016266d7c Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 13 Feb 2025 19:01:17 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tasks/visa/VisaCardActivationTask.kt | 9 +- .../visa/VisaCustomerWalletApproveTask.kt | 3 +- .../tap/domain/visa/VisaCardScanHandler.kt | 7 +- .../tangem/data/visa/DefaultVisaRepository.kt | 3 +- .../domain/common/visa/VisaUtilities.kt | 3 - .../common/visa/VisaWalletPublicKeyUtility.kt | 13 +-- .../impl/DefaultOnboardingVisaComponent.kt | 32 +++---- .../OnboardingVisaInProgressComponent.kt | 13 ++- .../model/OnboardingVisaInProgressModel.kt | 87 +++++++++++++++++-- .../OnboardingVisaOtherWalletComponent.kt | 5 +- .../model/OnboardingVisaOtherWalletModel.kt | 5 +- .../v2/visa/impl/model/OnboardingVisaModel.kt | 52 ++++++++--- .../v2/visa/impl/route/OnboardingVisaRoute.kt | 8 +- .../onboarding/v2/visa/impl/route/Step.kt | 9 +- .../onboarding/v2/visa/impl/route/Titles.kt | 2 +- 15 files changed, 184 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt index 379634ea9c..78ea74f626 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt @@ -5,6 +5,7 @@ import arrow.core.getOrElse import arrow.core.raise.catch import arrow.core.raise.either import com.tangem.common.CompletionResult +import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.CompletionCallback @@ -173,10 +174,10 @@ class VisaCardActivationTask @AssistedInject constructor( val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) - return if (card.wallets.any { it.curve == VisaUtilities.mandatoryCurve }) { + return if (card.wallets.any { it.curve == EllipticCurve.Secp256k1 }) { createOTP() } else { - val createWalletTask = CreateWalletTask(VisaUtilities.mandatoryCurve) + val createWalletTask = CreateWalletTask(EllipticCurve.Secp256k1) val timedResult = RealtimeMonotonicTimeSource.measureTimedValue { suspendCancellableCoroutine { continuation -> @@ -241,13 +242,13 @@ class VisaCardActivationTask @AssistedInject constructor( val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) val wallet = - card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } + card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) val derivedPublicKey = wallet.derivedKeys[VisaUtilities.visaDefaultDerivationPath] ?: return CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.MissingWallet.message)) - val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnVisaCurve(derivedPublicKey.publicKey) + val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(derivedPublicKey.publicKey) .getOrElse { return CompletionResult.Failure(TangemSdkError.Underlying(it.message)) } .value diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt index 89fb5b4965..778c08b933 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt @@ -4,6 +4,7 @@ import arrow.core.getOrElse import com.tangem.common.CompletionResult import com.tangem.common.card.Card import com.tangem.common.card.CardWallet +import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.CompletionCallback @@ -81,7 +82,7 @@ class VisaCustomerWalletApproveTask( return } - val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: run { + val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run { callback(CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.MissingWallet.message))) return } diff --git a/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt b/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt index ad7336846e..4b6dd922b9 100644 --- a/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt +++ b/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt @@ -3,6 +3,7 @@ package com.tangem.tap.domain.visa import arrow.core.getOrElse import com.tangem.common.CompletionResult import com.tangem.common.card.CardWallet +import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes @@ -56,7 +57,7 @@ internal class VisaCardScanHandler @Inject constructor( session = session, ) - val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: run { + val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run { val activationInput = VisaActivationInput(card.cardId, card.cardPublicKey.toHexString(), card.isAccessCodeSet) val activationStatus = VisaCardActivationStatus.NotStartedActivation(activationInput) @@ -110,7 +111,7 @@ internal class VisaCardScanHandler @Inject constructor( val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) - val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: run { + val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run { Timber.e("Failed to find extended public key while handling wallet authorization") return CompletionResult.Failure( TangemSdkError.Underlying(VisaCardScanHandlerError.FailedToFindDerivedWalletKey.errorDescription), @@ -124,7 +125,7 @@ internal class VisaCardScanHandler @Inject constructor( ) } - val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnVisaCurve(extendedPublicKey.publicKey) + val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(extendedPublicKey.publicKey) .getOrElse { return CompletionResult.Failure( TangemSdkError.Underlying("Cannot generate address on Visa curve"), diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt b/data/visa/src/debug/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt index f3211c1994..b7ce28f265 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt +++ b/data/visa/src/debug/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt @@ -6,6 +6,7 @@ import androidx.paging.PagingData import arrow.fx.coroutines.parZip import com.tangem.blockchain.common.address.Address import com.tangem.blockchain.common.address.AddressType +import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString import com.tangem.data.common.cache.CacheRegistry @@ -195,7 +196,7 @@ internal class DefaultVisaRepository @Inject constructor( if (VisaConstants.IS_DEMO_MODE_ENABLED) return getDemoPublicKey() val cardWallet = userWallet.scanResponse.card.wallets.firstOrNull { - it.curve == VisaUtilities.mandatoryCurve + it.curve == EllipticCurve.Secp256k1 } requireNotNull(cardWallet) { "Visa card wallet not found" } diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaUtilities.kt b/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaUtilities.kt index d62fffa929..de113d6a18 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaUtilities.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaUtilities.kt @@ -2,7 +2,6 @@ package com.tangem.domain.common.visa import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.derivation.DerivationStyle -import com.tangem.common.card.EllipticCurve import com.tangem.common.card.FirmwareVersion import com.tangem.domain.models.scan.CardDTO @@ -10,8 +9,6 @@ private const val VISA_BATCH_START = "AE" object VisaUtilities { - val mandatoryCurve = EllipticCurve.Secp256k1 - const val tokenId = "tether" val visaBlockchain = Blockchain.Polygon diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaWalletPublicKeyUtility.kt b/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaWalletPublicKeyUtility.kt index 70af83d8d9..e7500baf4f 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaWalletPublicKeyUtility.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaWalletPublicKeyUtility.kt @@ -5,6 +5,7 @@ import arrow.core.raise.catch import arrow.core.raise.either import com.tangem.blockchain.common.address.Address import com.tangem.blockchain.common.address.AddressType +import com.tangem.common.card.EllipticCurve import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.domain.models.scan.CardDTO @@ -19,7 +20,7 @@ object VisaWalletPublicKeyUtility { } fun findKeyWithoutDerivation(targetAddress: String, card: CardDTO): Either = either { - val wallet = findWalletOnVisaCurve(card).bind() + val wallet = findWalletOnSecp256k1(card).bind() validatePublicKey( targetAddress = targetAddress, @@ -29,13 +30,13 @@ object VisaWalletPublicKeyUtility { wallet.publicKey } - fun generateAddressOnVisaCurve(walletPublicKey: ByteArray): Either = either { + fun generateAddressOnSecp256k1(walletPublicKey: ByteArray): Either = either { val addresses = catch( block = { VisaUtilities.visaBlockchain.makeAddresses( walletPublicKey = walletPublicKey, pairPublicKey = null, - curve = VisaUtilities.mandatoryCurve, + curve = EllipticCurve.Secp256k1, ) }, catch = { raise(Error.FailedToCreateAddress) }, @@ -44,12 +45,12 @@ object VisaWalletPublicKeyUtility { addresses.firstOrNull { it.type == AddressType.Default } ?: raise(Error.FailedToCreateAddress) } - private fun findWalletOnVisaCurve(card: CardDTO): Either = either { - card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: raise(Error.MissingWalletOnTargetCurve) + private fun findWalletOnSecp256k1(card: CardDTO): Either = either { + card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: raise(Error.MissingWalletOnTargetCurve) } private fun validatePublicKey(targetAddress: String, publicKey: ByteArray): Either = either { - val address = generateAddressOnVisaCurve(publicKey).bind() + val address = generateAddressOnSecp256k1(publicKey).bind() if (address.value != targetAddress) { raise(Error.AddressNotMatched) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt index 41a398c3a4..9ab73f41eb 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt @@ -80,6 +80,10 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( } init { + componentScope.launch { + model.onDone.collect { params.onDone() } + } + // sets title and stepper value childStack.observe(lifecycle) { stack -> val currentRoute = stack.active.configuration @@ -138,20 +142,14 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( onEvent = { event -> model.navigateFromChooseWallet(route, event) }, ), ) - OnboardingVisaRoute.InProgress -> OnboardingVisaInProgressComponent( + is OnboardingVisaRoute.InProgress -> OnboardingVisaInProgressComponent( appComponentContext = factoryContext, config = OnboardingVisaInProgressComponent.Config( scanResponse = model.currentScanResponse.value, ), params = OnboardingVisaInProgressComponent.Params( childParams = childParams, - onDone = { - model.stackNavigation.push( - OnboardingVisaRoute.PinCode( - activationOrderInfo = TODO(), // TODO [REDACTED_TASK_KEY] [Visa] In progress screen after pin code - ), - ) - }, + onDone = { model.navigateFromInProgress(it) }, ), ) is OnboardingVisaRoute.OtherWalletApproveOption -> OnboardingVisaOtherWalletComponent( @@ -163,11 +161,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( params = OnboardingVisaOtherWalletComponent.Params( childParams = childParams, onDone = { - model.stackNavigation.push( - OnboardingVisaRoute.PinCode( - activationOrderInfo = TODO(), - ), - ) + model.stackNavigation.push(OnboardingVisaRoute.PinCode(activationOrderInfo = it)) }, ), ) @@ -179,7 +173,11 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( ), params = OnboardingVisaPinCodeComponent.Params( childParams = childParams, - onDone = { params.onDone() }, + onDone = { + model.stackNavigation.push( + OnboardingVisaRoute.InProgress(from = OnboardingVisaRoute.InProgress.From.PinCode), + ) + }, ), ) is OnboardingVisaRoute.TangemWalletApproveOption -> OnboardingVisaApproveComponent( @@ -191,7 +189,11 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( ), params = OnboardingVisaApproveComponent.Params( childParams = childParams, - onDone = { model.stackNavigation.push(OnboardingVisaRoute.InProgress) }, + onDone = { + model.stackNavigation.push( + OnboardingVisaRoute.InProgress(from = OnboardingVisaRoute.InProgress.From.Approve), + ) + }, ), ) } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/OnboardingVisaInProgressComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/OnboardingVisaInProgressComponent.kt index 6e8787f75a..6aaf63c0bc 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/OnboardingVisaInProgressComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/OnboardingVisaInProgressComponent.kt @@ -11,6 +11,7 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.model.OnboardingVisaInProgressModel import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.ui.OnboardingVisaInProgress +import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -24,7 +25,7 @@ internal class OnboardingVisaInProgressComponent( init { model.onDone - .onEach { params.onDone() } + .onEach { params.onDone(it) } .launchIn(componentScope) } @@ -43,6 +44,12 @@ internal class OnboardingVisaInProgressComponent( data class Params( val childParams: DefaultOnboardingVisaComponent.ChildParams, - val onDone: () -> Unit, - ) + val onDone: (DoneEvent) -> Unit, + ) { + + sealed class DoneEvent { + data class NavigateTo(val route: OnboardingVisaRoute) : DoneEvent() + data object Activated : DoneEvent() + } + } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt index 6e25512a2f..48ac5a404d 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt @@ -5,47 +5,116 @@ import com.tangem.common.extensions.toHexString 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.visa.model.VisaActivationRemoteState -import com.tangem.domain.visa.model.VisaCardId +import com.tangem.datasource.local.visa.VisaAuthTokenStorage +import com.tangem.datasource.local.visa.VisaOTPStorage +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.visa.model.* import com.tangem.domain.visa.repository.VisaActivationRepository -import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent +import com.tangem.domain.visa.repository.VisaAuthRepository +import com.tangem.domain.wallets.builder.UserWalletBuilder +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.visa.impl.child.inprogress.OnboardingVisaInProgressComponent.Config +import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent.Params +import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject +@Suppress("LongParameterList") @Stable @ComponentScoped internal class OnboardingVisaInProgressModel @Inject constructor( paramsContainer: ParamsContainer, visaActivationRepositoryFactory: VisaActivationRepository.Factory, override val dispatchers: CoroutineDispatcherProvider, + private val visaAuthRepository: VisaAuthRepository, + private val visaAuthTokenStorage: VisaAuthTokenStorage, + private val otpStorage: VisaOTPStorage, + private val generateWalletNameUseCase: GenerateWalletNameUseCase, + private val userWalletsListManager: UserWalletsListManager, ) : Model() { - private val params = paramsContainer.require() + private val params = paramsContainer.require() private val visaActivationRepository = visaActivationRepositoryFactory.create( VisaCardId( cardId = params.scanResponse.card.cardId, cardPublicKey = params.scanResponse.card.cardPublicKey.toHexString(), ), ) - val onDone = MutableSharedFlow() + val onDone = MutableSharedFlow() init { modelScope.launch { while (true) { val result = runCatching { visaActivationRepository.getActivationRemoteStateLongPoll() - }.getOrNull() + }.getOrNull() ?: continue - if (result is VisaActivationRemoteState.WaitingPinCode) { - onDone.emit(Unit) - break + when (result) { + is VisaActivationRemoteState.CardWalletSignatureRequired, + VisaActivationRemoteState.BlockedForActivation, + -> { + // TODO show alert inconsistent state + return@launch + } + is VisaActivationRemoteState.CustomerWalletSignatureRequired, + VisaActivationRemoteState.PaymentAccountDeploying, + VisaActivationRemoteState.WaitingForActivationFinishing, + -> { + } + is VisaActivationRemoteState.WaitingPinCode -> { + navigateToPinCode(result.activationOrderInfo) + return@launch + } + VisaActivationRemoteState.Activated -> { + finishActivation() + return@launch + } } delay(timeMillis = 1000) } } } + + private suspend fun navigateToPinCode(activationOrderInfo: VisaActivationOrderInfo) { + onDone.emit(Params.DoneEvent.NavigateTo(OnboardingVisaRoute.PinCode(activationOrderInfo))) + } + + private suspend fun finishActivation() { + val authTokens = visaAuthTokenStorage.get(params.scanResponse.card.cardId) + ?: error("Auth tokens are not found. This should not happen.") + + val newTokens = runCatching { + visaAuthRepository.refreshAccessTokens(authTokens.refreshToken) + }.getOrElse { + // TODO show alert + return + } + + val userWallet = createUserWallet(params.scanResponse, newTokens) + userWalletsListManager.save(userWallet) + visaAuthTokenStorage.remove(params.scanResponse.card.cardId) + otpStorage.removeOTP(params.scanResponse.card.cardId) + + onDone.emit(Params.DoneEvent.Activated) + } + + private suspend fun createUserWallet(scanResponse: ScanResponse, authTokens: VisaAuthTokens): UserWallet = + withContext(dispatchers.io) { + val newActivationStatus = VisaCardActivationStatus.Activated(visaAuthTokens = authTokens) + + requireNotNull( + value = UserWalletBuilder( + scanResponse.copy(visaCardActivationStatus = newActivationStatus), + generateWalletNameUseCase, + ).build(), + lazyMessage = { "User wallet not created" }, + ) + } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/OnboardingVisaOtherWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/OnboardingVisaOtherWalletComponent.kt index 7c8916e9e3..ee95c01524 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/OnboardingVisaOtherWalletComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/OnboardingVisaOtherWalletComponent.kt @@ -10,6 +10,7 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.visa.model.VisaActivationOrderInfo import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.model.OnboardingVisaOtherWalletModel import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.ui.OnboardingVisaOtherWallet @@ -27,7 +28,7 @@ internal class OnboardingVisaOtherWalletComponent( init { model.onDone - .onEach { params.onDone() } + .onEach { params.onDone(it) } .launchIn(componentScope) } @@ -50,6 +51,6 @@ internal class OnboardingVisaOtherWalletComponent( data class Params( val childParams: DefaultOnboardingVisaComponent.ChildParams, - val onDone: () -> Unit, + val onDone: (VisaActivationOrderInfo) -> Unit, ) } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/model/OnboardingVisaOtherWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/model/OnboardingVisaOtherWalletModel.kt index 77bfa10953..887cd4759f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/model/OnboardingVisaOtherWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/model/OnboardingVisaOtherWalletModel.kt @@ -7,6 +7,7 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.url.UrlOpener +import com.tangem.domain.visa.model.VisaActivationOrderInfo import com.tangem.domain.visa.model.VisaActivationRemoteState import com.tangem.domain.visa.model.VisaCardId import com.tangem.domain.visa.repository.VisaActivationRepository @@ -40,7 +41,7 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor( private val _uiState = MutableStateFlow(getInitialState()) val uiState = _uiState.asStateFlow() - val onDone = MutableSharedFlow() + val onDone = MutableSharedFlow() init { modelScope.launch { @@ -50,7 +51,7 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor( }.getOrNull() if (result is VisaActivationRemoteState.WaitingPinCode) { - onDone.emit(Unit) + onDone.emit(result.activationOrderInfo) break } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt index b740819d0a..ca2b727f7c 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt @@ -1,9 +1,11 @@ package com.tangem.features.onboarding.v2.visa.impl.model import androidx.compose.runtime.Stable +import com.arkivanov.decompose.ExperimentalDecomposeApi import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.pop import com.arkivanov.decompose.router.stack.push +import com.arkivanov.decompose.router.stack.pushNew import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -16,13 +18,16 @@ import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.onboarding.v2.visa.api.OnboardingVisaComponent import com.tangem.features.onboarding.v2.visa.impl.OnboardingVisaInnerNavigationState import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.OnboardingVisaChooseWalletComponent +import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent import com.tangem.features.onboarding.v2.visa.impl.common.ActivationReadyEvent import com.tangem.features.onboarding.v2.visa.impl.common.PreparationDataForApprove import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute import com.tangem.features.onboarding.v2.visa.impl.route.stepNum import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch import javax.inject.Inject @Stable @@ -49,6 +54,8 @@ internal class OnboardingVisaModel @Inject constructor( val innerNavigationState = _innerNavigationState.asStateFlow() + val onDone = MutableSharedFlow() + fun updateStepForNewRoute(route: OnboardingVisaRoute) { _innerNavigationState.value = innerNavigationState.value.copy( stackSize = route.stepNum(), @@ -77,6 +84,18 @@ internal class OnboardingVisaModel @Inject constructor( ) } + @OptIn(ExperimentalDecomposeApi::class) + fun navigateFromInProgress(event: OnboardingVisaInProgressComponent.Params.DoneEvent) { + when (event) { + OnboardingVisaInProgressComponent.Params.DoneEvent.Activated -> { + modelScope.launch { onDone.emit(Unit) } + } + is OnboardingVisaInProgressComponent.Params.DoneEvent.NavigateTo -> { + stackNavigation.pushNew(event.route) + } + } + } + fun navigateFromChooseWallet( route: OnboardingVisaRoute.ChooseWallet, event: OnboardingVisaChooseWalletComponent.Params.Event, @@ -115,7 +134,7 @@ internal class OnboardingVisaModel @Inject constructor( is OnboardingVisaRoute.WelcomeBack, is OnboardingVisaRoute.Welcome, is OnboardingVisaRoute.ChooseWallet, - OnboardingVisaRoute.InProgress, + is OnboardingVisaRoute.InProgress, is OnboardingVisaRoute.PinCode, -> { } @@ -123,22 +142,27 @@ internal class OnboardingVisaModel @Inject constructor( } private fun initializeRoute(): OnboardingVisaRoute { - val activationStatus = params.scanResponse.visaCardActivationStatus - - return when (activationStatus) { + return when (val activationStatus = params.scanResponse.visaCardActivationStatus) { is VisaCardActivationStatus.ActivationStarted -> { when (val remoteState = activationStatus.remoteState) { - is VisaActivationRemoteState.CardWalletSignatureRequired -> OnboardingVisaRoute.WelcomeBack( - activationInput = activationStatus.activationInput, - dataToSignByCardWalletRequest = remoteState.request, - ) - is VisaActivationRemoteState.CustomerWalletSignatureRequired -> + is VisaActivationRemoteState.CardWalletSignatureRequired -> { + OnboardingVisaRoute.WelcomeBack( + activationInput = activationStatus.activationInput, + dataToSignByCardWalletRequest = remoteState.request, + ) + } + is VisaActivationRemoteState.CustomerWalletSignatureRequired -> { remoteState.getRoute(activationStatus) - VisaActivationRemoteState.PaymentAccountDeploying -> OnboardingVisaRoute.InProgress // TODO - VisaActivationRemoteState.WaitingForActivationFinishing -> OnboardingVisaRoute.InProgress // TODO - is VisaActivationRemoteState.WaitingPinCode -> OnboardingVisaRoute.PinCode( - activationOrderInfo = remoteState.activationOrderInfo, - ) + } + VisaActivationRemoteState.PaymentAccountDeploying -> { + OnboardingVisaRoute.InProgress(from = OnboardingVisaRoute.InProgress.From.Approve) + } + VisaActivationRemoteState.WaitingForActivationFinishing -> { + OnboardingVisaRoute.InProgress(from = OnboardingVisaRoute.InProgress.From.PinCode) + } + is VisaActivationRemoteState.WaitingPinCode -> { + OnboardingVisaRoute.PinCode(activationOrderInfo = remoteState.activationOrderInfo) + } VisaActivationRemoteState.Activated, VisaActivationRemoteState.BlockedForActivation, -> error("Activation status is not correct for onboarding flow") diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/OnboardingVisaRoute.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/OnboardingVisaRoute.kt index 8022e8db39..9dca6484e9 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/OnboardingVisaRoute.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/OnboardingVisaRoute.kt @@ -39,7 +39,13 @@ internal sealed class OnboardingVisaRoute { ) : OnboardingVisaRoute() @Serializable - data object InProgress : OnboardingVisaRoute() + data class InProgress( + val from: From, + ) : OnboardingVisaRoute() { + enum class From { + Approve, PinCode + } + } @Serializable data class PinCode(val activationOrderInfo: VisaActivationOrderInfo) : OnboardingVisaRoute() diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Step.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Step.kt index 5c43e9cdc9..79deb3d03a 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Step.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Step.kt @@ -9,8 +9,13 @@ internal fun OnboardingVisaRoute.stepNum() = when (this) { is OnboardingVisaRoute.ChooseWallet -> 3 is OnboardingVisaRoute.OtherWalletApproveOption -> 4 is OnboardingVisaRoute.TangemWalletApproveOption -> 4 - OnboardingVisaRoute.InProgress -> 5 is OnboardingVisaRoute.PinCode -> 6 + is OnboardingVisaRoute.InProgress -> { + when (this.from) { + OnboardingVisaRoute.InProgress.From.Approve -> 5 + OnboardingVisaRoute.InProgress.From.PinCode -> 7 + } + } } -internal const val ONBOARDING_VISA_STEPS_COUNT = 7 \ No newline at end of file +internal const val ONBOARDING_VISA_STEPS_COUNT = 8 \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Titles.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Titles.kt index d103b8f6a7..5da3a52e3d 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Titles.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Titles.kt @@ -6,7 +6,7 @@ import com.tangem.core.ui.extensions.TextReference internal fun OnboardingVisaRoute.screenTitle(): TextReference = when (this) { OnboardingVisaRoute.AccessCode -> TextReference.Str("Access code") is OnboardingVisaRoute.ChooseWallet -> TextReference.Str("Account activation") - OnboardingVisaRoute.InProgress -> TextReference.Str("In progress") + is OnboardingVisaRoute.InProgress -> TextReference.Str("In progress") is OnboardingVisaRoute.OtherWalletApproveOption -> TextReference.Str("Wallet connection") is OnboardingVisaRoute.PinCode -> TextReference.Str("PIN code") is OnboardingVisaRoute.TangemWalletApproveOption -> TextReference.Str("Wallet connection")