Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-11 13:35:31 +03:00
parent 19fb8f2bcd
commit ecdb72e55c
58 changed files with 1082 additions and 346 deletions

View file

@ -154,6 +154,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
appComponentContext = factoryContext,
config = OnboardingVisaApproveComponent.Config(
visaDataForApprove = route.visaDataForApprove,
scanResponse = model.currentScanResponse.value,
),
params = OnboardingVisaApproveComponent.Params(
childParams = childParams,

View file

@ -10,12 +10,16 @@ import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.common.visa.VisaUtilities
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility
import com.tangem.domain.visa.model.VisaCardActivationStatus
import com.tangem.domain.visa.model.VisaCardId
import com.tangem.domain.visa.model.VisaCustomerWalletDataToSignRequest
import com.tangem.domain.visa.model.VisaDataForApprove
import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.domain.visa.repository.VisaAuthRepository
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.OnboardingVisaAccessCodeComponent
import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui.state.OnboardingVisaAccessCodeUM
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.sdk.api.visa.VisaCardActivationTaskMode
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@ -28,6 +32,7 @@ import javax.inject.Inject
@ComponentScoped
internal class OnboardingVisaAccessCodeModel @Inject constructor(
paramsContainer: ParamsContainer,
visaActivationRepositoryFactory: VisaActivationRepository.Factory,
override val dispatchers: CoroutineDispatcherProvider,
@Suppress("UnusedPrivateMember")
private val tangemSdkManager: TangemSdkManager,
@ -36,11 +41,18 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
) : Model() {
private val params: OnboardingVisaAccessCodeComponent.Config = paramsContainer.require()
private val visaActivationRepository = visaActivationRepositoryFactory.create(
VisaCardId(
cardId = params.scanResponse.card.cardId,
cardPublicKey = params.scanResponse.card.cardPublicKey.toHexString(),
),
)
private val activationStatus =
params.scanResponse.visaCardActivationStatus as? VisaCardActivationStatus.NotStartedActivation
?: error("Visa activation status is not set or incorrect for this step")
private val _uiState = MutableStateFlow(getInitialState())
val uiState = _uiState.asStateFlow()
val onBack = MutableSharedFlow<Unit>()
val onDone = MutableSharedFlow<OnboardingVisaAccessCodeComponent.DoneEvent>()
@ -126,46 +138,65 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
val challengeToSign = runCatching {
visaAuthRepository.getCardAuthChallenge(
cardId = activationStatus.activationInput.cardId,
cardPublicKey = activationStatus.activationInput.cardPublicKey.toHexString(),
cardPublicKey = activationStatus.activationInput.cardPublicKey,
)
}.getOrElse {
loading(false)
// show alert
// TODO show alert
return@launch
}
val result = tangemSdkManager.activateVisaCard(
accessCode = accessCode,
challengeToSign = challengeToSign,
mode = VisaCardActivationTaskMode.Full(
accessCode = accessCode,
authorizationChallenge = challengeToSign,
),
activationInput = activationStatus.activationInput,
)
//
when (result) {
is CompletionResult.Success -> {
// TODO load approve data from backend
val targetAddress = "x9F65354e595284956599F2892fA4A4a87653D6E6"
val foundCardId = tryToFindExistingWalletCardId(targetAddress)
) as? CompletionResult.Success ?: run {
loading(false)
// TODO show alert
return@launch
}
modelScope.launch {
onDone.emit(
OnboardingVisaAccessCodeComponent.DoneEvent(
visaDataForApprove = VisaDataForApprove(
targetAddress = targetAddress,
approveHash = "48b55c482123a10ad9022f9f4c5dd95c",
customerWalletCardId = foundCardId,
),
walletFound = foundCardId != null,
newScanResponse = params.scanResponse.copy(
card = result.data.newCardDTO,
),
),
)
}
}
is CompletionResult.Failure -> {
loading(false)
// show alert
}
runCatching {
visaActivationRepository.activateCard(result.data.signedActivationData)
}.onFailure {
loading(false)
// TODO show alert
return@launch
}
// load data to sign by customer wallet for the next step
val dataToSign = runCatching {
visaActivationRepository.getCustomerWalletAcceptanceData(
VisaCustomerWalletDataToSignRequest(
orderId = result.data.signedActivationData.dataToSign.request.orderId,
cardWalletAddress = result.data.signedActivationData.cardWalletAddress,
),
)
}.getOrElse {
loading(false)
// TODO show alert
return@launch
}
val targetAddress = result.data.signedActivationData.dataToSign.request.customerWalletAddress
val foundCardId = tryToFindExistingWalletCardId(targetAddress)
modelScope.launch {
onDone.emit(
OnboardingVisaAccessCodeComponent.DoneEvent(
visaDataForApprove = VisaDataForApprove(
targetAddress = targetAddress,
customerWalletCardId = foundCardId,
dataToSign = dataToSign,
),
walletFound = foundCardId != null,
newScanResponse = params.scanResponse.copy(
card = result.data.newCardDTO,
),
),
)
}
}
}

View file

@ -9,6 +9,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
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.VisaDataForApprove
import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent
import com.tangem.features.onboarding.v2.visa.impl.child.approve.model.OnboardingVisaApproveModel
@ -43,6 +44,7 @@ internal class OnboardingVisaApproveComponent(
data class Config(
val visaDataForApprove: VisaDataForApprove,
val scanResponse: ScanResponse,
)
data class Params(

View file

@ -2,9 +2,12 @@ package com.tangem.features.onboarding.v2.visa.impl.child.approve.model
import androidx.compose.runtime.Stable
import com.tangem.common.CompletionResult
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.VisaCardId
import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.features.onboarding.v2.visa.impl.child.approve.OnboardingVisaApproveComponent
import com.tangem.features.onboarding.v2.visa.impl.child.approve.ui.state.OnboardingVisaApproveUM
import com.tangem.sdk.api.TangemSdkManager
@ -20,11 +23,19 @@ import javax.inject.Inject
@ComponentScoped
internal class OnboardingVisaApproveModel @Inject constructor(
paramsContainer: ParamsContainer,
visaActivationRepositoryFactory: VisaActivationRepository.Factory,
override val dispatchers: CoroutineDispatcherProvider,
private val tangemSdkManager: TangemSdkManager,
) : Model() {
private val params = paramsContainer.require<OnboardingVisaApproveComponent.Config>()
private val visaActivationRepository = visaActivationRepositoryFactory.create(
VisaCardId(
cardId = params.scanResponse.card.cardId,
cardPublicKey = params.scanResponse.card.cardPublicKey.toHexString(),
),
)
private val _uiState = MutableStateFlow(getInitialState())
val uiState = _uiState.asStateFlow()
@ -42,20 +53,21 @@ internal class OnboardingVisaApproveModel @Inject constructor(
modelScope.launch {
val result = tangemSdkManager.visaCustomerWalletApprove(
visaDataForApprove = params.visaDataForApprove,
)
when (result) {
is CompletionResult.Success -> {
// TODO make backend call
onDone.emit(Unit)
}
is CompletionResult.Failure -> {
loading(false)
// TODO show dialog
return@launch
}
) as? CompletionResult.Success ?: run {
loading(false)
// TODO show dialog
return@launch
}
runCatching {
visaActivationRepository.approveByCustomerWallet(result.data)
}.onFailure {
loading(false)
// TODO show dialog
return@launch
}
onDone.emit(Unit)
}
}

View file

@ -1,10 +1,12 @@
package com.tangem.features.onboarding.v2.visa.impl.child.inprogress.model
import androidx.compose.runtime.Stable
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.domain.visa.repository.VisaActivationRepository
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -17,12 +19,17 @@ import javax.inject.Inject
@ComponentScoped
internal class OnboardingVisaInProgressModel @Inject constructor(
paramsContainer: ParamsContainer,
visaActivationRepositoryFactory: VisaActivationRepository.Factory,
override val dispatchers: CoroutineDispatcherProvider,
private val visaActivationRepositoryFactory: VisaActivationRepository.Factory,
) : Model() {
private val params = paramsContainer.require<OnboardingVisaInProgressComponent.Config>()
private val visaActivationRepository = visaActivationRepositoryFactory.create(params.scanResponse.card.cardId)
private val visaActivationRepository = visaActivationRepositoryFactory.create(
VisaCardId(
cardId = params.scanResponse.card.cardId,
cardPublicKey = params.scanResponse.card.cardPublicKey.toHexString(),
),
)
val onDone = MutableSharedFlow<Unit>()
init {

View file

@ -1,12 +1,14 @@
package com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.model
import androidx.compose.runtime.Stable
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.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.visa.model.VisaActivationRemoteState
import com.tangem.domain.visa.model.VisaCardId
import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.OnboardingVisaOtherWalletComponent
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.ui.state.OnboardingVisaOtherWalletUM
@ -22,15 +24,19 @@ import javax.inject.Inject
@ComponentScoped
internal class OnboardingVisaOtherWalletModel @Inject constructor(
paramsContainer: ParamsContainer,
visaActivationRepositoryFactory: VisaActivationRepository.Factory,
override val dispatchers: CoroutineDispatcherProvider,
private val urlOpener: UrlOpener,
private val shareManager: ShareManager,
private val visaActivationRepositoryFactory: VisaActivationRepository.Factory,
) : Model() {
@Suppress("UnusedPrivateMember")
private val config = paramsContainer.require<OnboardingVisaOtherWalletComponent.Config>()
private val visaActivationRepository = visaActivationRepositoryFactory.create(config.scanResponse.card.cardId)
private val visaActivationRepository = visaActivationRepositoryFactory.create(
VisaCardId(
cardId = config.scanResponse.card.cardId,
cardPublicKey = config.scanResponse.card.cardPublicKey.toHexString(),
),
)
private val _uiState = MutableStateFlow(getInitialState())
val uiState = _uiState.asStateFlow()
@ -61,10 +67,10 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor(
}
private fun onShareClicked() {
shareManager.shareText("https://tangem.com/${config.visaDataForApprove.approveHash}")
shareManager.shareText("https://tangem.com/")
}
private fun onOpenInBrowserClicked() {
urlOpener.openUrl("https://tangem.com/${config.visaDataForApprove.approveHash}")
urlOpener.openUrl("https://tangem.com/")
}
}

View file

@ -52,12 +52,13 @@ internal class OnboardingVisaModel @Inject constructor(
fun navigateFromWelcome(route: OnboardingVisaRoute.Welcome) {
if (route.isWelcomeBack) {
val scanResponse = _currentScanResponse.value
val activationStatus = scanResponse.visaCardActivationStatus as? VisaCardActivationStatus
.ActivationStarted ?: error("Activation status is not correct for welcome back route")
val activationStatus = scanResponse.visaCardActivationStatus
as? VisaCardActivationStatus.ActivationStarted
?: error("Activation status is not correct for welcome back route")
when (activationStatus.remoteState) {
VisaActivationRemoteState.CardWalletSignatureRequired,
VisaActivationRemoteState.CustomerWalletSignatureRequired,
is VisaActivationRemoteState.CardWalletSignatureRequired,
is VisaActivationRemoteState.CustomerWalletSignatureRequired,
-> OnboardingVisaRoute.AccessCode
VisaActivationRemoteState.WaitingPinCode -> OnboardingVisaRoute.PinCode
VisaActivationRemoteState.WaitingForActivationFinishing -> OnboardingVisaRoute.InProgress