From 2fb2466db53a5ebb6ad8862e1823910be416b4c8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 19 Jan 2025 21:50:00 +0300 Subject: [PATCH 01/11] Updated on 2026-08-14 --- .../onboarding/v2/visa/impl/ui/OnboardingVisa.kt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/ui/OnboardingVisa.kt diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/ui/OnboardingVisa.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/ui/OnboardingVisa.kt new file mode 100644 index 0000000000..40a9a8597d --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/ui/OnboardingVisa.kt @@ -0,0 +1,9 @@ +package com.tangem.features.onboarding.v2.visa.impl.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +internal fun OnboardingVisa(modifier: Modifier = Modifier, childContent: @Composable (Modifier) -> Unit) { + childContent(modifier) +} \ No newline at end of file From b7abe2bf7afe260aabe7b81f8af5c6915c759ee6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 Jan 2025 16:33:02 +0300 Subject: [PATCH 02/11] Updated on 2026-08-14 --- .../onboarding/v2/visa/impl/ui/OnboardingVisa.kt | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/ui/OnboardingVisa.kt diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/ui/OnboardingVisa.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/ui/OnboardingVisa.kt deleted file mode 100644 index 40a9a8597d..0000000000 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/ui/OnboardingVisa.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.features.onboarding.v2.visa.impl.ui - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -@Composable -internal fun OnboardingVisa(modifier: Modifier = Modifier, childContent: @Composable (Modifier) -> Unit) { - childContent(modifier) -} \ No newline at end of file From 503c471e1d2de08d05233ecbeb9898ffd9a997e5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 Jan 2025 17:52:52 +0300 Subject: [PATCH 03/11] Updated on 2026-08-14 --- .../impl/DefaultOnboardingVisaComponent.kt | 56 ++++++++++++------- .../OnboardingVisaAccessCodeComponent.kt | 9 ++- .../approve/OnboardingVisaApproveComponent.kt | 5 +- .../OnboardingVisaChooseWalletComponent.kt | 5 +- .../OnboardingVisaInProgressComponent.kt | 7 ++- .../OnboardingVisaOtherWalletComponent.kt | 5 +- .../pincode/OnboardingVisaPinCodeComponent.kt | 5 +- .../welcome/OnboardingVisaWelcomeComponent.kt | 5 +- .../v2/visa/impl/model/OnboardingVisaModel.kt | 3 + .../onboarding/v2/visa/impl/route/Step.kt | 14 +++++ .../onboarding/v2/visa/impl/route/Titles.kt | 14 +++++ 11 files changed, 90 insertions(+), 38 deletions(-) create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Step.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Titles.kt 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 4f5b70b7a8..31ba359b56 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 @@ -9,6 +9,7 @@ import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackA import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState import com.arkivanov.decompose.router.stack.* import com.arkivanov.decompose.value.Value +import com.arkivanov.decompose.value.observe import com.arkivanov.essenty.instancekeeper.getOrCreateSimple import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.childByContext @@ -25,13 +26,14 @@ import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.OnboardingV import com.tangem.features.onboarding.v2.visa.impl.child.pincode.OnboardingVisaPinCodeComponent import com.tangem.features.onboarding.v2.visa.impl.child.welcome.OnboardingVisaWelcomeComponent import com.tangem.features.onboarding.v2.visa.impl.model.OnboardingVisaModel +import com.tangem.features.onboarding.v2.visa.impl.route.ONBOARDING_VISA_STEPS_COUNT import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute +import com.tangem.features.onboarding.v2.visa.impl.route.screenTitle +import com.tangem.features.onboarding.v2.visa.impl.route.stepNum import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @Suppress("UnusedPrivateMember") @@ -43,12 +45,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( private val model: OnboardingVisaModel = getOrCreateModel(params) private val innerNavigationState = instanceKeeper.getOrCreateSimple(key = "innerNavigationState") { - MutableStateFlow( - OnboardingVisaInnerNavigationState( - stackSize = 1, - stackMaxSize = 7, - ), - ) + MutableStateFlow(OnboardingVisaInnerNavigationState(stackSize = model.initialStepNum)) } private val currentChildBackEventHandle = instanceKeeper.getOrCreateSimple(key = "currentChildBackEventHandle") { @@ -84,6 +81,20 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( } } + init { + // sets title and stepper value + childStack.observe(lifecycle) { stack -> + val currentRoute = stack.active.configuration + params.titleProvider.changeTitle(currentRoute.screenTitle()) + innerNavigationState.update { it.copy(stackSize = currentRoute.stepNum()) } + } + } + + private val childParams = ChildParams( + onBack = ::onChildBack, + parentBackEvent = currentChildBackEventHandle, + ) + private fun createChild( route: OnboardingVisaRoute, factoryContext: AppComponentContext, @@ -93,22 +104,21 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( appComponentContext = factoryContext, params = OnboardingVisaWelcomeComponent.Params( isWelcomeBack = route.isWelcomeBack, - onBack = ::onChildBack, + childParams = childParams, onDone = { stackNavigation.push(OnboardingVisaRoute.AccessCode) }, ), ) OnboardingVisaRoute.AccessCode -> OnboardingVisaAccessCodeComponent( appComponentContext = factoryContext, params = OnboardingVisaAccessCodeComponent.Params( - onBack = ::onChildBack, - parentBackEvent = currentChildBackEventHandle, + childParams = childParams, onDone = { stackNavigation.push(OnboardingVisaRoute.ChooseWallet) }, ), ) OnboardingVisaRoute.ChooseWallet -> OnboardingVisaChooseWalletComponent( appComponentContext = factoryContext, params = OnboardingVisaChooseWalletComponent.Params( - onBack = ::onChildBack, + childParams = childParams, onEvent = { event -> stackNavigation.push( when (event) { @@ -124,28 +134,28 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( OnboardingVisaRoute.InProgress -> OnboardingVisaInProgressComponent( appComponentContext = factoryContext, params = OnboardingVisaInProgressComponent.Params( - onBack = ::onChildBack, + childParams = childParams, onDone = { stackNavigation.push(OnboardingVisaRoute.PinCode) }, ), ) OnboardingVisaRoute.OtherWalletApproveOption -> OnboardingVisaOtherWalletComponent( appComponentContext = factoryContext, params = OnboardingVisaOtherWalletComponent.Params( - onBack = ::onChildBack, + childParams = childParams, onDone = { stackNavigation.push(OnboardingVisaRoute.InProgress) }, ), ) OnboardingVisaRoute.PinCode -> OnboardingVisaPinCodeComponent( appComponentContext = factoryContext, params = OnboardingVisaPinCodeComponent.Params( - onBack = ::onChildBack, + childParams = childParams, onDone = { params.onDone() }, ), ) OnboardingVisaRoute.TangemWalletApproveOption -> OnboardingVisaApproveComponent( appComponentContext = factoryContext, params = OnboardingVisaApproveComponent.Params( - onBack = ::onChildBack, + childParams = childParams, onDone = { stackNavigation.push(OnboardingVisaRoute.InProgress) }, ), ) @@ -180,6 +190,11 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( } } + data class ChildParams( + val onBack: () -> Unit, + val parentBackEvent: SharedFlow, + ) + @AssistedFactory interface Factory : OnboardingVisaComponent.Factory { override fun create( @@ -189,7 +204,8 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( } } -data class OnboardingVisaInnerNavigationState( +internal data class OnboardingVisaInnerNavigationState( override val stackSize: Int, - override val stackMaxSize: Int?, -) : InnerNavigationState \ No newline at end of file +) : InnerNavigationState { + override val stackMaxSize: Int = ONBOARDING_VISA_STEPS_COUNT +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt index 06faafcbd7..8734fa0726 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt @@ -9,9 +9,9 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.security.DisableScreenshotsDisposableEffect +import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.model.OnboardingVisaAccessCodeModel import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui.OnboardingVisaAccessCode -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.launch internal class OnboardingVisaAccessCodeComponent( @@ -23,10 +23,10 @@ internal class OnboardingVisaAccessCodeComponent( init { componentScope.launch { - model.onBack.collect { params.onBack() } + model.onBack.collect { params.childParams.onBack() } } componentScope.launch { - params.parentBackEvent.collect { + params.childParams.parentBackEvent.collect { model.onBack() } } @@ -47,8 +47,7 @@ internal class OnboardingVisaAccessCodeComponent( } data class Params( - val parentBackEvent: SharedFlow, - val onBack: () -> Unit, + val childParams: DefaultOnboardingVisaComponent.ChildParams, val onDone: () -> 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/approve/OnboardingVisaApproveComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/OnboardingVisaApproveComponent.kt index 70a5560fc0..b7b5fc37ab 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/OnboardingVisaApproveComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/OnboardingVisaApproveComponent.kt @@ -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.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent import com.tangem.features.onboarding.v2.visa.impl.child.approve.model.OnboardingVisaApproveModel import com.tangem.features.onboarding.v2.visa.impl.child.approve.ui.OnboardingVisaApprove import kotlinx.coroutines.launch @@ -30,7 +31,7 @@ internal class OnboardingVisaApproveComponent( override fun Content(modifier: Modifier) { val state by model.uiState.collectAsStateWithLifecycle() - BackHandler(onBack = remember(this) { { params.onBack() } }) + BackHandler(onBack = remember(this) { { params.childParams.onBack() } }) OnboardingVisaApprove( state = state, @@ -39,7 +40,7 @@ internal class OnboardingVisaApproveComponent( } data class Params( - val onBack: () -> Unit, + val childParams: DefaultOnboardingVisaComponent.ChildParams, val onDone: () -> 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/choosewallet/OnboardingVisaChooseWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/choosewallet/OnboardingVisaChooseWalletComponent.kt index f8310b209e..3c8967551c 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/choosewallet/OnboardingVisaChooseWalletComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/choosewallet/OnboardingVisaChooseWalletComponent.kt @@ -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.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.model.OnboardingVisaChooseWalletModel import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.ui.OnboardingVisaChooseWallet import kotlinx.coroutines.flow.launchIn @@ -31,7 +32,7 @@ internal class OnboardingVisaChooseWalletComponent( override fun Content(modifier: Modifier) { val state by model.uiState.collectAsStateWithLifecycle() - BackHandler(onBack = remember(this) { { params.onBack() } }) + BackHandler(onBack = remember(this) { { params.childParams.onBack() } }) OnboardingVisaChooseWallet( modifier = modifier, @@ -40,7 +41,7 @@ internal class OnboardingVisaChooseWalletComponent( } data class Params( - val onBack: () -> Unit, + val childParams: DefaultOnboardingVisaComponent.ChildParams, val onEvent: (Event) -> Unit, ) { enum class Event { 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 f1fca1c49b..f8d10a865f 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 @@ -7,12 +7,13 @@ import androidx.compose.ui.Modifier import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableContentComponent +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 kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -class OnboardingVisaInProgressComponent( +internal class OnboardingVisaInProgressComponent( appComponentContext: AppComponentContext, private val params: Params, ) : ComposableContentComponent, AppComponentContext by appComponentContext { @@ -27,7 +28,7 @@ class OnboardingVisaInProgressComponent( @Composable override fun Content(modifier: Modifier) { - BackHandler(onBack = remember(this) { { params.onBack() } }) + BackHandler(onBack = remember(this) { { params.childParams.onBack() } }) OnboardingVisaInProgress( modifier = modifier, @@ -35,7 +36,7 @@ class OnboardingVisaInProgressComponent( } data class Params( - val onBack: () -> Unit, + val childParams: DefaultOnboardingVisaComponent.ChildParams, val onDone: () -> 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/OnboardingVisaOtherWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/OnboardingVisaOtherWalletComponent.kt index 50ca03977d..8b8784dd1d 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 @@ -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.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 import kotlinx.coroutines.flow.launchIn @@ -31,7 +32,7 @@ internal class OnboardingVisaOtherWalletComponent( override fun Content(modifier: Modifier) { val state by model.uiState.collectAsStateWithLifecycle() - BackHandler(onBack = remember(this) { { params.onBack() } }) + BackHandler(onBack = remember(this) { { params.childParams.onBack() } }) OnboardingVisaOtherWallet( modifier = modifier, @@ -40,7 +41,7 @@ internal class OnboardingVisaOtherWalletComponent( } data class Params( - val onBack: () -> Unit, + val childParams: DefaultOnboardingVisaComponent.ChildParams, val onDone: () -> 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/pincode/OnboardingVisaPinCodeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/OnboardingVisaPinCodeComponent.kt index 6df8920a6f..05aeba0384 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/OnboardingVisaPinCodeComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/OnboardingVisaPinCodeComponent.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.core.ui.security.DisableScreenshotsDisposableEffect +import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent import com.tangem.features.onboarding.v2.visa.impl.child.pincode.model.OnboardingVisaPinCodeModel import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.OnboardingVisaPinCode import kotlinx.coroutines.launch @@ -31,7 +32,7 @@ internal class OnboardingVisaPinCodeComponent( override fun Content(modifier: Modifier) { val state by model.uiState.collectAsStateWithLifecycle() - BackHandler(onBack = remember(this) { { params.onBack() } }) + BackHandler(onBack = remember(this) { { params.childParams.onBack() } }) DisableScreenshotsDisposableEffect() @@ -42,7 +43,7 @@ internal class OnboardingVisaPinCodeComponent( } data class Params( - val onBack: () -> Unit, + val childParams: DefaultOnboardingVisaComponent.ChildParams, val onDone: () -> 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/welcome/OnboardingVisaWelcomeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/OnboardingVisaWelcomeComponent.kt index 3180bdfba5..95264e1199 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/OnboardingVisaWelcomeComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/OnboardingVisaWelcomeComponent.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent import com.tangem.features.onboarding.v2.visa.impl.child.welcome.ui.OnboardingVisaWelcome import com.tangem.features.onboarding.v2.visa.impl.child.welcome.ui.state.OnboardingVisaWelcomeUM @@ -28,7 +29,7 @@ internal class OnboardingVisaWelcomeComponent( ) } - BackHandler(onBack = remember(this) { { params.onBack() } }) + BackHandler(onBack = remember(this) { { params.childParams.onBack() } }) OnboardingVisaWelcome( modifier = modifier, @@ -37,8 +38,8 @@ internal class OnboardingVisaWelcomeComponent( } data class Params( + val childParams: DefaultOnboardingVisaComponent.ChildParams, val isWelcomeBack: Boolean, - val onBack: () -> Unit, val onDone: () -> Unit, ) } \ No newline at end of file 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 f3d92c1ea3..fab035bcb3 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 @@ -6,6 +6,7 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.features.onboarding.v2.visa.api.OnboardingVisaComponent 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 javax.inject.Inject @@ -20,4 +21,6 @@ internal class OnboardingVisaModel @Inject constructor( private val params = paramsContainer.require() val initialRoute = OnboardingVisaRoute.Welcome(isWelcomeBack = false) + + val initialStepNum = initialRoute.stepNum() } \ No newline at end of file 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 new file mode 100644 index 0000000000..f82df04969 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Step.kt @@ -0,0 +1,14 @@ +package com.tangem.features.onboarding.v2.visa.impl.route + +@Suppress("MagicNumber") +internal fun OnboardingVisaRoute.stepNum() = when (this) { + is OnboardingVisaRoute.Welcome -> 1 + OnboardingVisaRoute.AccessCode -> 2 + OnboardingVisaRoute.ChooseWallet -> 3 + OnboardingVisaRoute.OtherWalletApproveOption -> 4 + OnboardingVisaRoute.TangemWalletApproveOption -> 4 + OnboardingVisaRoute.InProgress -> 5 + OnboardingVisaRoute.PinCode -> 6 +} + +internal const val ONBOARDING_VISA_STEPS_COUNT = 7 \ 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 new file mode 100644 index 0000000000..32a8e57a51 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Titles.kt @@ -0,0 +1,14 @@ +package com.tangem.features.onboarding.v2.visa.impl.route + +import com.tangem.core.ui.extensions.TextReference + +// TODO add translations [REDACTED_TASK_KEY] +internal fun OnboardingVisaRoute.screenTitle(): TextReference = when (this) { + OnboardingVisaRoute.AccessCode -> TextReference.Str("Access code") + OnboardingVisaRoute.ChooseWallet -> TextReference.Str("Account activation") + OnboardingVisaRoute.InProgress -> TextReference.Str("In progress") + OnboardingVisaRoute.OtherWalletApproveOption -> TextReference.Str("Wallet connection") + OnboardingVisaRoute.PinCode -> TextReference.Str("PIN code") + OnboardingVisaRoute.TangemWalletApproveOption -> TextReference.Str("Wallet connection") + is OnboardingVisaRoute.Welcome -> TextReference.Str("Getting started") +} \ No newline at end of file From 1388e0cd9e3565efbf626409c802a490d1800d90 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 19 Jan 2025 21:50:00 +0300 Subject: [PATCH 04/11] Updated on 2026-08-14 --- .../onboarding/v2/visa/impl/ui/OnboardingVisa.kt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/ui/OnboardingVisa.kt diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/ui/OnboardingVisa.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/ui/OnboardingVisa.kt new file mode 100644 index 0000000000..40a9a8597d --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/ui/OnboardingVisa.kt @@ -0,0 +1,9 @@ +package com.tangem.features.onboarding.v2.visa.impl.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +internal fun OnboardingVisa(modifier: Modifier = Modifier, childContent: @Composable (Modifier) -> Unit) { + childContent(modifier) +} \ No newline at end of file From 6fa81d165493668e99900b09a2256848a917c222 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 22 Jan 2025 20:41:34 +0300 Subject: [PATCH 05/11] Updated on 2026-08-14 --- .../sdk/impl/DefaultTangemSdkManager.kt | 14 ++ .../domain/sdk/impl/MockTangemSdkManager.kt | 10 + .../domain/tasks/product/ScanProductTask.kt | 18 +- .../tasks/visa/VisaCardActivationTask.kt | 2 +- .../visa/VisaCustomerWalletApproveTask.kt | 188 ++++++++++++++++++ .../visa/DefaultVisaActivationRepository.kt | 1 + .../domain/common/DerivationStyleProvider.kt | 4 +- .../domain/common/TangemCardTypesResolver.kt | 6 +- .../tangem/domain/common/TapWorkarounds.kt | 3 + .../domain/common/util/ScanResponseExt.kt | 8 +- .../domain/common/visa/VisaUtilities.kt | 4 +- .../common/visa/VisaWalletPublicKeyUtility.kt | 63 ++++++ .../domain/visa/model/VisaActivationError.kt | 1 + .../domain/visa/model/VisaDataForApprove.kt | 10 + features/onboarding-v2/impl/build.gradle.kts | 1 + .../impl/DefaultOnboardingVisaComponent.kt | 67 +++++-- .../OnboardingVisaAccessCodeComponent.kt | 18 +- .../model/OnboardingVisaAccessCodeModel.kt | 82 ++++++-- .../accesscode/ui/OnboardingVisaAccessCode.kt | 1 + .../ui/state/OnboardingVisaAccessCodeUM.kt | 1 + .../approve/OnboardingVisaApproveComponent.kt | 8 +- .../model/OnboardingVisaApproveModel.kt | 34 +++- .../child/approve/ui/OnboardingVisaApprove.kt | 1 + .../ui/state/OnboardingVisaApproveUM.kt | 1 + .../OnboardingVisaOtherWalletComponent.kt | 8 +- .../model/OnboardingVisaOtherWalletModel.kt | 5 + .../child/welcome/ui/OnboardingVisaWelcome.kt | 7 +- .../v2/visa/impl/route/OnboardingVisaRoute.kt | 10 +- .../onboarding/v2/visa/impl/route/Step.kt | 6 +- .../onboarding/v2/visa/impl/route/Titles.kt | 6 +- .../com/tangem/sdk/api/TangemSdkManager.kt | 4 + 31 files changed, 515 insertions(+), 77 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt create mode 100644 domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaWalletPublicKeyUtility.kt create mode 100644 domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaDataForApprove.kt diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt index 7ce6aa9702..ac325d7207 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt @@ -27,6 +27,7 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.visa.model.VisaActivationInput import com.tangem.domain.visa.model.VisaAuthChallenge import com.tangem.domain.visa.model.VisaCardActivationResponse +import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles import com.tangem.operations.ScanTask @@ -35,6 +36,7 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask import com.tangem.operations.derivation.DeriveWalletPublicKeyTask import com.tangem.operations.pins.SetUserCodeCommand import com.tangem.operations.preflightread.PreflightReadFilter +import com.tangem.operations.sign.SignHashResponse import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.sdk.api.CreateProductWalletTaskResponse @@ -45,6 +47,7 @@ import com.tangem.tap.domain.tasks.product.ResetBackupCardTask import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask +import com.tangem.tap.domain.tasks.visa.VisaCustomerWalletApproveTask import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask import com.tangem.tap.domain.twins.CreateSecondTwinWalletTask import com.tangem.tap.domain.twins.FinalizeTwinTask @@ -494,6 +497,17 @@ internal class DefaultTangemSdkManager( } } + override suspend fun visaCustomerWalletApprove( + visaDataForApprove: VisaDataForApprove, + ): CompletionResult { + return runTaskAsyncReturnOnMain( + runnable = VisaCustomerWalletApproveTask( + visaDataForApprove = visaDataForApprove, + ), + initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)), + ) + } + // endregion companion object { diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt index c0dd2c2c03..3e8dce1c1e 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt @@ -20,9 +20,11 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.visa.model.VisaActivationInput import com.tangem.domain.visa.model.VisaAuthChallenge import com.tangem.domain.visa.model.VisaCardActivationResponse +import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.preflightread.PreflightReadFilter +import com.tangem.operations.sign.SignHashResponse import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.sdk.api.CreateProductWalletTaskResponse import com.tangem.sdk.api.TangemSdkManager @@ -207,4 +209,12 @@ class MockTangemSdkManager( ): CompletionResult { error("Not implemented") } + + override suspend fun visaCustomerWalletApprove( + visaDataForApprove: VisaDataForApprove, + ): CompletionResult { + error("Not implemented") + } + + // endregion } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index bc2268c5a9..3437af2b04 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -45,6 +45,7 @@ import com.tangem.tap.scope import com.tangem.tap.store import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import timber.log.Timber import kotlin.collections.set internal class ScanProductTask( @@ -172,6 +173,7 @@ private class ScanWalletProcessor( ) { @Suppress("MagicNumber") if (card.firmwareVersion.doubleValue >= 4.39 && card.settings.maxWalletsCount == 1) { + Timber.tag("ASDASD").i("ScanWalletProcessor: ${card.firmwareVersion}") readFile(card, session, callback) return } @@ -190,6 +192,7 @@ private class ScanWalletProcessor( when (result) { is CompletionResult.Success -> { + Timber.tag("ASDASD").i("ReadFilesTask: ${result.data}") val file = result.data.firstOrNull() val counter = file?.counter val signature = file?.signature @@ -311,15 +314,12 @@ private class ScanWalletProcessor( } private fun getWalletProductType(card: CardDTO): ProductType { - if (RING_BATCH_IDS.contains(card.batchId) || card.batchId.startsWith(RING_BATCH_PREFIX)) { - return ProductType.Ring - } - return if (card.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available && - card.settings.isKeysImportAllowed - ) { - ProductType.Wallet2 - } else { - ProductType.Wallet + return when { + card.isVisa -> ProductType.Visa + RING_BATCH_IDS.contains(card.batchId) || card.batchId.startsWith(RING_BATCH_PREFIX) -> ProductType.Ring + card.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available && + card.settings.isKeysImportAllowed -> ProductType.Wallet2 + else -> ProductType.Wallet } } 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 d1efcf68c4..709be0263d 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 @@ -1,12 +1,12 @@ package com.tangem.tap.domain.tasks.visa -import com.reown.util.hexToBytes import com.tangem.common.CompletionResult import com.tangem.common.card.Card import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.CompletionCallback import com.tangem.common.core.TangemSdkError +import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString import com.tangem.common.map import com.tangem.datasource.local.visa.VisaAuthTokenStorage 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 new file mode 100644 index 0000000000..c9fb9dc1d0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt @@ -0,0 +1,188 @@ +package com.tangem.tap.domain.tasks.visa + +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.core.CardSession +import com.tangem.common.core.CardSessionRunnable +import com.tangem.common.core.CompletionCallback +import com.tangem.common.core.TangemSdkError +import com.tangem.common.extensions.hexToBytes +import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.domain.common.util.derivationStyleProvider +import com.tangem.domain.common.visa.VisaUtilities +import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility +import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.visa.model.VisaActivationError +import com.tangem.domain.visa.model.VisaDataForApprove +import com.tangem.operations.ScanTask +import com.tangem.operations.derivation.DeriveWalletPublicKeyTask +import com.tangem.operations.sign.SignHashCommand +import com.tangem.operations.sign.SignHashResponse + +class VisaCustomerWalletApproveTask( + private val visaDataForApprove: VisaDataForApprove, +) : CardSessionRunnable { + + override fun run(session: CardSession, callback: CompletionCallback) { + val card = session.environment.card ?: run { + callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead())) + return + } + + if (VisaUtilities.isVisaCard(card.firmwareVersion.doubleValue, card.batchId).not()) { + callback(CompletionResult.Failure(TangemSdkError.Underlying("Card is identified as not Visa"))) + return + } + + if (card.settings.isHDWalletAllowed) { + proceedApprove(card, session, callback) + } else { + proceedApproveWithLegacyCard(card, session, callback) + } + } + + private fun proceedApprove(card: Card, session: CardSession, callback: CompletionCallback) { + val cardDTO = CardDTO(card) + + val derivationStyle = cardDTO.derivationStyleProvider.getDerivationStyle() ?: run { + proceedApproveWithLegacyCard( + card = card, + session = session, + callback = callback, + ) + return + } + + val derivationPath = VisaUtilities.visaDefaultDerivationPath(derivationStyle) ?: run { + callback( + CompletionResult.Failure( + TangemSdkError.Underlying("Failed to generate derivation path with provided derivation style"), + ), + ) + return + } + + val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: run { + callback(CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.MissingWallet.message))) + return + } + + val derivationTask = DeriveWalletPublicKeyTask( + walletPublicKey = wallet.publicKey, + derivationPath = derivationPath, + ) + + derivationTask.run(session) { result -> + when (result) { + is CompletionResult.Success -> { + processDerivedKey( + wallet = wallet, + extendedPublicKey = result.data, + derivationPath = derivationPath, + session = session, + callback = callback, + ) + } + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(result.error)) + } + } + } + } + + private fun processDerivedKey( + wallet: CardWallet, + extendedPublicKey: ExtendedPublicKey, + derivationPath: DerivationPath, + session: CardSession, + callback: CompletionCallback, + ) { + val validationResult = VisaWalletPublicKeyUtility.validateExtendedPublicKey( + targetAddress = visaDataForApprove.targetAddress, + extendedPublicKey = extendedPublicKey, + ) + + validationResult.onLeft { + callback(CompletionResult.Failure(TangemSdkError.Underlying(it.message))) + return + } + + signApproveData( + targetWalletPublicKey = wallet.publicKey, + derivationPath = derivationPath, + session = session, + callback = callback, + ) + } + + private fun proceedApproveWithLegacyCard( + card: Card, + session: CardSession, + callback: CompletionCallback, + ) { + val publicKey = findKeyWithoutDerivation( + targetAddress = visaDataForApprove.targetAddress, + card = CardDTO(card), + ).getOrElse { + callback(CompletionResult.Failure(TangemSdkError.Underlying(it.message))) + return + } + + signApproveData( + targetWalletPublicKey = publicKey, + derivationPath = null, + session = session, + callback = callback, + ) + } + + private fun signApproveData( + targetWalletPublicKey: ByteArray, + derivationPath: DerivationPath?, + session: CardSession, + callback: CompletionCallback, + ) { + val signTask = SignHashCommand( + hash = visaDataForApprove.approveHash.hexToBytes(), + walletPublicKey = targetWalletPublicKey, + derivationPath = derivationPath, + ) + + signTask.run(session) { result -> + when (result) { + is CompletionResult.Success -> { + scanCard( + signHashResponse = result.data, + session = session, + callback = callback, + ) + } + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(result.error)) + } + } + } + } + + private fun scanCard( + signHashResponse: SignHashResponse, + session: CardSession, + callback: CompletionCallback, + ) { + val scanTask = ScanTask() + scanTask.run(session) { result -> + when (result) { + is CompletionResult.Success -> { + callback(CompletionResult.Success(signHashResponse)) + } + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(result.error)) + } + } + } + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt index 17862805b5..b11958c4f5 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt @@ -20,6 +20,7 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor( override suspend fun getActivationRemoteState(): VisaActivationRemoteState = withContext(dispatcherProvider.io) { visaActivationStatusConverter.convert(visaApi.getRemoteActivationStatus(cardId)) + VisaActivationRemoteState.CardWalletSignatureRequired // mock // TODO implement refreshing access token if it's expired } diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/DerivationStyleProvider.kt b/domain/legacy/src/main/java/com/tangem/domain/common/DerivationStyleProvider.kt index 570071b37a..e4ff4eea14 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/DerivationStyleProvider.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/DerivationStyleProvider.kt @@ -1,6 +1,7 @@ package com.tangem.domain.common import com.tangem.blockchain.common.derivation.DerivationStyle +import com.tangem.domain.common.TapWorkarounds.isWallet2 import com.tangem.domain.models.scan.CardDTO interface DerivationStyleProvider { @@ -8,14 +9,13 @@ interface DerivationStyleProvider { } internal class TangemDerivationStyleProvider( - private val cardTypesResolver: CardTypesResolver, private val card: CardDTO, ) : DerivationStyleProvider { override fun getDerivationStyle(): DerivationStyle? { return when { !card.settings.isHDWalletAllowed -> null firstBatchesOfWallet1(card) -> DerivationStyle.V1 - cardTypesResolver.isWallet2() -> DerivationStyle.V3 + card.isWallet2 -> DerivationStyle.V3 else -> DerivationStyle.V2 } } diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt b/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt index bcf606539a..46035bd37d 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt @@ -8,6 +8,7 @@ import com.tangem.common.card.WalletData import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TapWorkarounds.isTestCard +import com.tangem.domain.common.TapWorkarounds.isWallet2 import com.tangem.domain.common.visa.VisaUtilities import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ProductType @@ -35,10 +36,7 @@ internal class TangemCardTypesResolver( return walletData == null && card.firmwareVersion <= FirmwareVersion.HDWalletAvailable } - override fun isWallet2(): Boolean { - return card.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available && - card.settings.isKeysImportAllowed - } + override fun isWallet2(): Boolean = card.isWallet2 override fun isVisaWallet(): Boolean = productType == ProductType.Visa diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/TapWorkarounds.kt b/domain/legacy/src/main/java/com/tangem/domain/common/TapWorkarounds.kt index b369611972..493d724f8b 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/TapWorkarounds.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/TapWorkarounds.kt @@ -23,6 +23,9 @@ object TapWorkarounds { val CardDTO.isStart2Coin: Boolean get() = isStart2CoinIssuer(issuer.name) + val CardDTO.isWallet2: Boolean + get() = firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available && settings.isKeysImportAllowed + val CardDTO.isVisa: Boolean get() = VisaUtilities.isVisaCard(this) diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/util/ScanResponseExt.kt b/domain/legacy/src/main/java/com/tangem/domain/common/util/ScanResponseExt.kt index 94727f02cb..3c24fb103f 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/util/ScanResponseExt.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/util/ScanResponseExt.kt @@ -24,10 +24,10 @@ val ScanResponse.cardTypesResolver: CardTypesResolver ) val ScanResponse.derivationStyleProvider: DerivationStyleProvider - get() = TangemDerivationStyleProvider( - cardTypesResolver, - card, - ) + get() = card.derivationStyleProvider + +val CardDTO.derivationStyleProvider: DerivationStyleProvider + get() = TangemDerivationStyleProvider(this) val UserWallet.cardTypesResolver: CardTypesResolver get() = scanResponse.cardTypesResolver 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 32d4282b20..b84ed4f673 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 @@ -18,12 +18,14 @@ object VisaUtilities { val visaDefaultDerivationPath = visaBlockchain.derivationPath(DerivationStyle.V3) + fun visaDefaultDerivationPath(style: DerivationStyle) = visaBlockchain.derivationPath(style) + fun isVisaCard(card: CardDTO): Boolean { return isVisaCard(card.firmwareVersion.doubleValue, card.batchId) } fun isVisaCard(firmwareVersion: Double, batchId: String): Boolean { - return FirmwareVersion.visaRange.contains(firmwareVersion) && + return firmwareVersion in FirmwareVersion.visaRange && batchId.startsWith(VISA_BATCH_START) } } \ No newline at end of file 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 new file mode 100644 index 0000000000..60a095ca16 --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaWalletPublicKeyUtility.kt @@ -0,0 +1,63 @@ +package com.tangem.domain.common.visa + +import arrow.core.Either +import arrow.core.raise.catch +import arrow.core.raise.either +import com.tangem.blockchain.common.address.Address +import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey +import com.tangem.domain.models.scan.CardDTO + +object VisaWalletPublicKeyUtility { + + fun validateExtendedPublicKey(targetAddress: String, extendedPublicKey: ExtendedPublicKey): Either = + either { + validatePublicKey( + targetAddress = targetAddress, + publicKey = extendedPublicKey.publicKey, + ).bind() + } + + fun findKeyWithoutDerivation(targetAddress: String, card: CardDTO): Either = either { + val wallet = findWalletOnVisaCurve(card).bind() + + validatePublicKey( + targetAddress = targetAddress, + publicKey = wallet.publicKey, + ).bind() + + wallet.publicKey + } + + private fun findWalletOnVisaCurve(card: CardDTO): Either = either { + card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: raise(Error.MissingWalletOnTargetCurve) + } + + private fun validatePublicKey(targetAddress: String, publicKey: ByteArray): Either = either { + val address = generateAddressOnVisaCurve(publicKey).bind() + + if (address.value != targetAddress) { + raise(Error.AddressNotMatched) + } + } + + private fun generateAddressOnVisaCurve(walletPublicKey: ByteArray): Either = either { + val addresses = catch( + block = { + VisaUtilities.visaBlockchain.makeAddresses( + walletPublicKey = walletPublicKey, + pairPublicKey = null, + curve = VisaUtilities.mandatoryCurve, + ) + }, + catch = { raise(Error.FailedToCreateAddress) }, + ) + + addresses.firstOrNull() ?: raise(Error.FailedToCreateAddress) + } + + enum class Error(val message: String) { + AddressNotMatched("ValidationError: Address not matched"), + FailedToCreateAddress("ValidationError: Failed to create address"), + MissingWalletOnTargetCurve("ValidationError: Missing wallet on target curve"), + } +} \ No newline at end of file diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationError.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationError.kt index 11685b3866..f115a703f5 100644 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationError.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationError.kt @@ -4,5 +4,6 @@ enum class VisaActivationError(val message: String) { BlockedForActivation("Card is blocked for activation"), InvalidActivationState("Invalid activation state"), WrongCard("Wrong card tapped"), + MissingWallet("Missing wallet"), MissingRootOTP("Missing root OTP"), } \ No newline at end of file diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaDataForApprove.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaDataForApprove.kt new file mode 100644 index 0000000000..5a5fe7cb4d --- /dev/null +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaDataForApprove.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.visa.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class VisaDataForApprove( + @SerialName("targetAddress") val targetAddress: String, + @SerialName("approveHash") val approveHash: String, +) \ No newline at end of file diff --git a/features/onboarding-v2/impl/build.gradle.kts b/features/onboarding-v2/impl/build.gradle.kts index 6a64d3f440..3f1c7150fc 100644 --- a/features/onboarding-v2/impl/build.gradle.kts +++ b/features/onboarding-v2/impl/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { implementation(projects.domain.legacy) implementation(projects.domain.settings) implementation(projects.domain.onboarding) + implementation(projects.domain.visa) /** Tangem libraries */ implementation(projects.libs.tangemSdkApi) 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 31ba359b56..a174ae69e0 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 @@ -52,6 +52,11 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( MutableSharedFlow() } + private val childParams = ChildParams( + onBack = ::onChildBack, + parentBackEvent = currentChildBackEventHandle, + ) + private val stackNavigation = StackNavigation() private val childStack: Value> = @@ -76,7 +81,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( if (childStack.value.active.configuration is OnboardingVisaRoute.AccessCode) { componentScope.launch { currentChildBackEventHandle.emit(Unit) } } else { - stackNavigation.pop() + onChildBack() } } } @@ -90,11 +95,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( } } - private val childParams = ChildParams( - onBack = ::onChildBack, - parentBackEvent = currentChildBackEventHandle, - ) - + @Suppress("LongMethod") private fun createChild( route: OnboardingVisaRoute, factoryContext: AppComponentContext, @@ -112,10 +113,24 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( appComponentContext = factoryContext, params = OnboardingVisaAccessCodeComponent.Params( childParams = childParams, - onDone = { stackNavigation.push(OnboardingVisaRoute.ChooseWallet) }, + onDone = { + stackNavigation.push( + if (it.walletFound) { + OnboardingVisaRoute.TangemWalletApproveOption( + visaDataForApprove = it.visaDataForApprove, + allowNavigateBack = false, + ) + } else { + OnboardingVisaRoute.ChooseWallet(it.visaDataForApprove) + }, + ) + }, + ), + config = OnboardingVisaAccessCodeComponent.Config( + scanResponse = params.scanResponse, ), ) - OnboardingVisaRoute.ChooseWallet -> OnboardingVisaChooseWalletComponent( + is OnboardingVisaRoute.ChooseWallet -> OnboardingVisaChooseWalletComponent( appComponentContext = factoryContext, params = OnboardingVisaChooseWalletComponent.Params( childParams = childParams, @@ -123,9 +138,12 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( stackNavigation.push( when (event) { OnboardingVisaChooseWalletComponent.Params.Event.TangemWallet -> - OnboardingVisaRoute.TangemWalletApproveOption + OnboardingVisaRoute.TangemWalletApproveOption( + visaDataForApprove = route.visaDataForApprove, + allowNavigateBack = true, + ) OnboardingVisaChooseWalletComponent.Params.Event.OtherWallet -> - OnboardingVisaRoute.OtherWalletApproveOption + OnboardingVisaRoute.OtherWalletApproveOption(route.visaDataForApprove) }, ) }, @@ -138,8 +156,11 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( onDone = { stackNavigation.push(OnboardingVisaRoute.PinCode) }, ), ) - OnboardingVisaRoute.OtherWalletApproveOption -> OnboardingVisaOtherWalletComponent( + is OnboardingVisaRoute.OtherWalletApproveOption -> OnboardingVisaOtherWalletComponent( appComponentContext = factoryContext, + config = OnboardingVisaOtherWalletComponent.Config( + visaDataForApprove = route.visaDataForApprove, + ), params = OnboardingVisaOtherWalletComponent.Params( childParams = childParams, onDone = { stackNavigation.push(OnboardingVisaRoute.InProgress) }, @@ -152,8 +173,11 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( onDone = { params.onDone() }, ), ) - OnboardingVisaRoute.TangemWalletApproveOption -> OnboardingVisaApproveComponent( + is OnboardingVisaRoute.TangemWalletApproveOption -> OnboardingVisaApproveComponent( appComponentContext = factoryContext, + config = OnboardingVisaApproveComponent.Config( + visaDataForApprove = route.visaDataForApprove, + ), params = OnboardingVisaApproveComponent.Params( childParams = childParams, onDone = { stackNavigation.push(OnboardingVisaRoute.InProgress) }, @@ -163,15 +187,24 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( } private fun onChildBack() { - when (childStack.value.active.configuration) { + if (childStack.value.backStack.size == 1) { + // TODO show dialog + return + } + + when (val route = childStack.value.active.configuration) { OnboardingVisaRoute.AccessCode, - OnboardingVisaRoute.TangemWalletApproveOption, - OnboardingVisaRoute.OtherWalletApproveOption, + is OnboardingVisaRoute.OtherWalletApproveOption, -> stackNavigation.pop() + is OnboardingVisaRoute.TangemWalletApproveOption -> { + if (route.allowNavigateBack) { + stackNavigation.pop() + } + } + is OnboardingVisaRoute.Welcome, - OnboardingVisaRoute.ChooseWallet, + is OnboardingVisaRoute.ChooseWallet, OnboardingVisaRoute.InProgress, - OnboardingVisaRoute.OtherWalletApproveOption, OnboardingVisaRoute.PinCode, -> { } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt index 8734fa0726..dd7a6f2ed6 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt @@ -9,6 +9,8 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.security.DisableScreenshotsDisposableEffect +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.accesscode.model.OnboardingVisaAccessCodeModel import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui.OnboardingVisaAccessCode @@ -16,10 +18,11 @@ import kotlinx.coroutines.launch internal class OnboardingVisaAccessCodeComponent( appComponentContext: AppComponentContext, + config: Config, private val params: Params, ) : ComposableContentComponent, AppComponentContext by appComponentContext { - private val model: OnboardingVisaAccessCodeModel = getOrCreateModel() + private val model: OnboardingVisaAccessCodeModel = getOrCreateModel(config) init { componentScope.launch { @@ -31,7 +34,7 @@ internal class OnboardingVisaAccessCodeComponent( } } componentScope.launch { - model.onDone.collect { params.onDone() } + model.onDone.collect { params.onDone(it) } } } @@ -46,8 +49,17 @@ internal class OnboardingVisaAccessCodeComponent( OnboardingVisaAccessCode(state, modifier) } + data class Config( + val scanResponse: ScanResponse, + ) + data class Params( val childParams: DefaultOnboardingVisaComponent.ChildParams, - val onDone: () -> Unit, + val onDone: (DoneEvent) -> Unit, + ) + + data class DoneEvent( + val visaDataForApprove: VisaDataForApprove, + val walletFound: Boolean, ) } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt index 4281e33a1a..1186c22057 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt @@ -2,8 +2,15 @@ package com.tangem.features.onboarding.v2.visa.impl.child.accesscode.model import androidx.compose.runtime.Stable import androidx.compose.ui.text.input.TextFieldValue +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.VisaCardActivationStatus +import com.tangem.domain.visa.model.VisaDataForApprove +import com.tangem.domain.visa.repository.VisaAuthRepository +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.utils.coroutines.CoroutineDispatcherProvider @@ -14,15 +21,22 @@ import javax.inject.Inject @Stable @ComponentScoped internal class OnboardingVisaAccessCodeModel @Inject constructor( + paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, @Suppress("UnusedPrivateMember") private val tangemSdkManager: TangemSdkManager, + private val visaAuthRepository: VisaAuthRepository, ) : Model() { + private val params: OnboardingVisaAccessCodeComponent.Config = paramsContainer.require() + 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() - val onDone = MutableSharedFlow() + val onDone = MutableSharedFlow() fun onBack() { when (uiState.value.step) { @@ -97,27 +111,53 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor( @Suppress("UnusedPrivateMember") private fun startActivationProcess(accessCode: String) { - modelScope.launch { onDone.emit(Unit) } + loading(true) - // modelScope.launch { - // @Suppress("UnusedPrivateMember") - // val result = tangemSdkManager.activateVisaCard( - // accessCode = accessCode, - // challengeToSign = null, - // activationInput = TODO(), - // ) - // - // when (result) { - // is CompletionResult.Success -> { - // val response = result.data - // TODO() - // } - // is CompletionResult.Failure -> { - // // show alert - // TODO() - // } - // } - // } + modelScope.launch { + val challengeToSign = runCatching { + visaAuthRepository.getCardAuthChallenge( + cardId = activationStatus.activationInput.cardId, + cardPublicKey = activationStatus.activationInput.cardPublicKey.toHexString(), + ) + }.getOrElse { + loading(false) + // show alert + return@launch + } + + val result = tangemSdkManager.activateVisaCard( + accessCode = accessCode, + challengeToSign = challengeToSign, + activationInput = activationStatus.activationInput, + ) + // + when (result) { + is CompletionResult.Success -> { + // TODO load approve data from backend + // TODO try to find wallet in the app + + modelScope.launch { + onDone.emit( + OnboardingVisaAccessCodeComponent.DoneEvent( + visaDataForApprove = VisaDataForApprove( + targetAddress = "x9F65354e595284956599F2892fA4A4a87653D6E6", + approveHash = "approve hash", + ), + walletFound = false, // TODO + ), + ) + } + } + is CompletionResult.Failure -> { + loading(false) + // show alert + } + } + } + } + + private fun loading(state: Boolean) { + _uiState.update { it.copy(buttonLoading = state) } } private companion object { diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/OnboardingVisaAccessCode.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/OnboardingVisaAccessCode.kt index 6fefcee506..1557683bfe 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/OnboardingVisaAccessCode.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/OnboardingVisaAccessCode.kt @@ -65,6 +65,7 @@ internal fun OnboardingVisaAccessCode(state: OnboardingVisaAccessCodeUM, modifie }, onClick = state.onContinue, iconRes = R.drawable.ic_tangem_24, + showProgress = state.buttonLoading, isIconVisible = state.step == OnboardingVisaAccessCodeUM.Step.ReEnter, ), ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/state/OnboardingVisaAccessCodeUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/state/OnboardingVisaAccessCodeUM.kt index b707b9067c..35403457fd 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/state/OnboardingVisaAccessCodeUM.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/ui/state/OnboardingVisaAccessCodeUM.kt @@ -9,6 +9,7 @@ internal data class OnboardingVisaAccessCodeUM( val codesNotMatchError: Boolean = false, val atLeastMinCharsError: Boolean = false, val accessCodeHidden: Boolean = true, + val buttonLoading: Boolean = false, val onAccessCodeFirstChange: (TextFieldValue) -> Unit = {}, val onAccessCodeSecondChange: (TextFieldValue) -> Unit = {}, val onContinue: () -> Unit = {}, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/OnboardingVisaApproveComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/OnboardingVisaApproveComponent.kt index b7b5fc37ab..60141475ad 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/OnboardingVisaApproveComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/OnboardingVisaApproveComponent.kt @@ -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.visa.model.VisaDataForApprove import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent import com.tangem.features.onboarding.v2.visa.impl.child.approve.model.OnboardingVisaApproveModel import com.tangem.features.onboarding.v2.visa.impl.child.approve.ui.OnboardingVisaApprove @@ -16,10 +17,11 @@ import kotlinx.coroutines.launch internal class OnboardingVisaApproveComponent( appComponentContext: AppComponentContext, + config: Config, private val params: Params, ) : ComposableContentComponent, AppComponentContext by appComponentContext { - private val model: OnboardingVisaApproveModel = getOrCreateModel() + private val model: OnboardingVisaApproveModel = getOrCreateModel(config) init { componentScope.launch { @@ -39,6 +41,10 @@ internal class OnboardingVisaApproveComponent( ) } + data class Config( + val visaDataForApprove: VisaDataForApprove, + ) + data class Params( val childParams: DefaultOnboardingVisaComponent.ChildParams, val onDone: () -> Unit, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/model/OnboardingVisaApproveModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/model/OnboardingVisaApproveModel.kt index 11bd07d94b..a79fe4aa78 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/model/OnboardingVisaApproveModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/model/OnboardingVisaApproveModel.kt @@ -1,22 +1,30 @@ package com.tangem.features.onboarding.v2.visa.impl.child.approve.model import androidx.compose.runtime.Stable +import com.tangem.common.CompletionResult import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.features.onboarding.v2.visa.impl.child.approve.OnboardingVisaApproveComponent import com.tangem.features.onboarding.v2.visa.impl.child.approve.ui.state.OnboardingVisaApproveUM +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.asStateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @Stable @ComponentScoped internal class OnboardingVisaApproveModel @Inject constructor( + paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, + private val tangemSdkManager: TangemSdkManager, ) : Model() { + private val params = paramsContainer.require() private val _uiState = MutableStateFlow(getInitialState()) val uiState = _uiState.asStateFlow() @@ -29,7 +37,29 @@ internal class OnboardingVisaApproveModel @Inject constructor( } private fun onApproveClick() { - // TODO - modelScope.launch { onDone.emit(Unit) } + loading(true) + + 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 + } + } + } + } + + private fun loading(state: Boolean) { + _uiState.update { it.copy(approveButtonLoading = state) } } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/ui/OnboardingVisaApprove.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/ui/OnboardingVisaApprove.kt index bc1b5f0ff9..f93ebd7cd3 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/ui/OnboardingVisaApprove.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/ui/OnboardingVisaApprove.kt @@ -68,6 +68,7 @@ internal fun OnboardingVisaApprove(state: OnboardingVisaApproveUM, modifier: Mod textReference = TextReference.Str("Approve"), iconRes = R.drawable.ic_tangem_24, isIconVisible = true, + showProgress = state.approveButtonLoading, onClick = state.onApproveClick, ), ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/ui/state/OnboardingVisaApproveUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/ui/state/OnboardingVisaApproveUM.kt index 90bb2ef920..60bf3ff09d 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/ui/state/OnboardingVisaApproveUM.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/ui/state/OnboardingVisaApproveUM.kt @@ -1,5 +1,6 @@ package com.tangem.features.onboarding.v2.visa.impl.child.approve.ui.state internal data class OnboardingVisaApproveUM( + val approveButtonLoading: Boolean = false, val onApproveClick: () -> 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/OnboardingVisaOtherWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/OnboardingVisaOtherWalletComponent.kt index 8b8784dd1d..3442f47224 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 @@ -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.visa.model.VisaDataForApprove 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 @@ -17,10 +18,11 @@ import kotlinx.coroutines.flow.onEach internal class OnboardingVisaOtherWalletComponent( appComponentContext: AppComponentContext, + config: Config, private val params: Params, ) : ComposableContentComponent, AppComponentContext by appComponentContext { - private val model: OnboardingVisaOtherWalletModel = getOrCreateModel() + private val model: OnboardingVisaOtherWalletModel = getOrCreateModel(config) init { model.onDone @@ -40,6 +42,10 @@ internal class OnboardingVisaOtherWalletComponent( ) } + data class Config( + val visaDataForApprove: VisaDataForApprove, + ) + data class Params( val childParams: DefaultOnboardingVisaComponent.ChildParams, val onDone: () -> Unit, 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 42254b2c08..7e5ece076a 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 @@ -3,6 +3,8 @@ package com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.model import androidx.compose.runtime.Stable import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.OnboardingVisaOtherWalletComponent import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.ui.state.OnboardingVisaOtherWalletUM import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableSharedFlow @@ -13,9 +15,12 @@ import javax.inject.Inject @Stable @ComponentScoped internal class OnboardingVisaOtherWalletModel @Inject constructor( + paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, ) : Model() { + @Suppress("UnusedPrivateMember") + private val config = paramsContainer.require() private val _uiState = MutableStateFlow(getInitialState()) val uiState = _uiState.asStateFlow() diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/OnboardingVisaWelcome.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/OnboardingVisaWelcome.kt index 26516e4075..d21c1a2f43 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/OnboardingVisaWelcome.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/OnboardingVisaWelcome.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -95,16 +96,18 @@ fun Artwork(modifier: Modifier = Modifier) { Box(modifier) { val circleColor = TangemTheme.colors.background.secondary var cardHeightPx by remember { mutableIntStateOf(0) } + val circleRadiusPx = cardHeightPx / 2 + cardHeightPx / 8f + val circleSize = with(LocalDensity.current) { (circleRadiusPx * 2).toDp() } Canvas( modifier = Modifier .align(Alignment.Center) - .size(254.dp) + .size(circleSize) .fillMaxSize(), ) { drawCircle( color = circleColor, - radius = cardHeightPx / 2 + cardHeightPx / 8f, + radius = circleRadiusPx, center = center, ) } 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 8eba780fd5..64f7576d0f 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 @@ -1,5 +1,6 @@ package com.tangem.features.onboarding.v2.visa.impl.route +import com.tangem.domain.visa.model.VisaDataForApprove import kotlinx.serialization.Serializable @Serializable @@ -12,13 +13,16 @@ sealed class OnboardingVisaRoute { data object AccessCode : OnboardingVisaRoute() @Serializable - data object ChooseWallet : OnboardingVisaRoute() + data class ChooseWallet(val visaDataForApprove: VisaDataForApprove) : OnboardingVisaRoute() @Serializable - data object TangemWalletApproveOption : OnboardingVisaRoute() + data class TangemWalletApproveOption( + val visaDataForApprove: VisaDataForApprove, + val allowNavigateBack: Boolean, + ) : OnboardingVisaRoute() @Serializable - data object OtherWalletApproveOption : OnboardingVisaRoute() + data class OtherWalletApproveOption(val visaDataForApprove: VisaDataForApprove) : OnboardingVisaRoute() @Serializable data object InProgress : 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 f82df04969..a4deb7fcdb 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 @@ -4,9 +4,9 @@ package com.tangem.features.onboarding.v2.visa.impl.route internal fun OnboardingVisaRoute.stepNum() = when (this) { is OnboardingVisaRoute.Welcome -> 1 OnboardingVisaRoute.AccessCode -> 2 - OnboardingVisaRoute.ChooseWallet -> 3 - OnboardingVisaRoute.OtherWalletApproveOption -> 4 - OnboardingVisaRoute.TangemWalletApproveOption -> 4 + is OnboardingVisaRoute.ChooseWallet -> 3 + is OnboardingVisaRoute.OtherWalletApproveOption -> 4 + is OnboardingVisaRoute.TangemWalletApproveOption -> 4 OnboardingVisaRoute.InProgress -> 5 OnboardingVisaRoute.PinCode -> 6 } 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 32a8e57a51..89e6c7a044 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 @@ -5,10 +5,10 @@ import com.tangem.core.ui.extensions.TextReference // TODO add translations [REDACTED_TASK_KEY] internal fun OnboardingVisaRoute.screenTitle(): TextReference = when (this) { OnboardingVisaRoute.AccessCode -> TextReference.Str("Access code") - OnboardingVisaRoute.ChooseWallet -> TextReference.Str("Account activation") + is OnboardingVisaRoute.ChooseWallet -> TextReference.Str("Account activation") OnboardingVisaRoute.InProgress -> TextReference.Str("In progress") - OnboardingVisaRoute.OtherWalletApproveOption -> TextReference.Str("Wallet connection") + is OnboardingVisaRoute.OtherWalletApproveOption -> TextReference.Str("Wallet connection") OnboardingVisaRoute.PinCode -> TextReference.Str("PIN code") - OnboardingVisaRoute.TangemWalletApproveOption -> TextReference.Str("Wallet connection") + is OnboardingVisaRoute.TangemWalletApproveOption -> TextReference.Str("Wallet connection") is OnboardingVisaRoute.Welcome -> TextReference.Str("Getting started") } \ No newline at end of file diff --git a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt index d86b31b3d0..fd17f4da84 100644 --- a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt +++ b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt @@ -18,9 +18,11 @@ import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.visa.model.VisaActivationInput import com.tangem.domain.visa.model.VisaAuthChallenge import com.tangem.domain.visa.model.VisaCardActivationResponse +import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.preflightread.PreflightReadFilter +import com.tangem.operations.sign.SignHashResponse import com.tangem.operations.wallet.CreateWalletResponse @Suppress("TooManyFunctions") @@ -153,5 +155,7 @@ interface TangemSdkManager { activationInput: VisaActivationInput, ): CompletionResult + suspend fun visaCustomerWalletApprove(visaDataForApprove: VisaDataForApprove): CompletionResult + // endregion } \ No newline at end of file From 11c083481561c33a7f89eca400c6c2559c88fb87 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 27 Jan 2025 12:35:02 +0300 Subject: [PATCH 06/11] Updated on 2026-08-14 --- .../com/tangem/tap/domain/tasks/product/ScanProductTask.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 3437af2b04..fdc3898fbf 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -45,7 +45,6 @@ import com.tangem.tap.scope import com.tangem.tap.store import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import timber.log.Timber import kotlin.collections.set internal class ScanProductTask( @@ -173,7 +172,6 @@ private class ScanWalletProcessor( ) { @Suppress("MagicNumber") if (card.firmwareVersion.doubleValue >= 4.39 && card.settings.maxWalletsCount == 1) { - Timber.tag("ASDASD").i("ScanWalletProcessor: ${card.firmwareVersion}") readFile(card, session, callback) return } @@ -192,7 +190,6 @@ private class ScanWalletProcessor( when (result) { is CompletionResult.Success -> { - Timber.tag("ASDASD").i("ReadFilesTask: ${result.data}") val file = result.data.firstOrNull() val counter = file?.counter val signature = file?.signature From d27c57cd844749389c5c2a542fc491fcf5f719b2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 27 Jan 2025 12:23:42 +0300 Subject: [PATCH 07/11] Updated on 2026-08-14 --- .../tap/data/DefaultVisaAuthTokenStorage.kt | 4 + .../tasks/visa/VisaCardActivationTask.kt | 52 ++++++++---- .../tap/domain/visa/VisaCardScanHandler.kt | 20 ++--- .../datasource/api/visa/TangemVisaApi.kt | 8 ++ .../com/tangem/datasource/di/NetworkModule.kt | 4 +- .../local/visa/VisaAuthTokenStorage.kt | 2 + .../tangem/datasource/utils/HttpClientExt.kt | 18 ++--- .../visa/DefaultVisaActivationRepository.kt | 19 ++++- .../data/visa/DefaultVisaAuthRepository.kt | 79 +++++++++++-------- .../repository/VisaActivationRepository.kt | 2 + .../impl/DefaultOnboardingVisaComponent.kt | 9 ++- .../model/OnboardingVisaAccessCodeModel.kt | 5 +- .../OnboardingVisaInProgressComponent.kt | 8 +- .../model/OnboardingVisaInProgressModel.kt | 23 +++++- .../OnboardingVisaOtherWalletComponent.kt | 2 + .../model/OnboardingVisaOtherWalletModel.kt | 31 +++++++- .../pincode/OnboardingVisaPinCodeComponent.kt | 8 +- .../model/OnboardingVisaPinCodeModel.kt | 62 ++++++++++++++- .../child/pincode/ui/OnboardingVisaPinCode.kt | 14 +++- .../ui/state/OnboardingVisaPinCodeUM.kt | 2 + 20 files changed, 279 insertions(+), 93 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/data/DefaultVisaAuthTokenStorage.kt b/app/src/main/java/com/tangem/tap/data/DefaultVisaAuthTokenStorage.kt index 5adc5fd942..a6128ddfe6 100644 --- a/app/src/main/java/com/tangem/tap/data/DefaultVisaAuthTokenStorage.kt +++ b/app/src/main/java/com/tangem/tap/data/DefaultVisaAuthTokenStorage.kt @@ -48,5 +48,9 @@ internal class DefaultVisaAuthTokenStorage @Inject constructor( ?.let(tokensAdapter::fromJson) } + override fun remove(cardId: String) { + secureStorage.delete(createKey(cardId)) + } + private fun createKey(cardId: String): String = "visa_auth_tokens_$cardId" } \ No newline at end of file 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 709be0263d..9bd9bf2e48 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 @@ -1,7 +1,6 @@ package com.tangem.tap.domain.tasks.visa import com.tangem.common.CompletionResult -import com.tangem.common.card.Card import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.CompletionCallback @@ -9,8 +8,10 @@ import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString import com.tangem.common.map +import com.tangem.crypto.CryptoUtils import com.tangem.datasource.local.visa.VisaAuthTokenStorage import com.tangem.datasource.local.visa.VisaOTPStorage +import com.tangem.datasource.local.visa.hasSavedOTP import com.tangem.domain.common.visa.VisaUtilities import com.tangem.domain.visa.model.* import com.tangem.domain.visa.repository.VisaActivationRepository @@ -25,6 +26,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.* +import timber.log.Timber import kotlin.coroutines.resume import kotlin.jvm.Throws @@ -42,7 +44,7 @@ class VisaCardActivationTask @AssistedInject constructor( private class SessionContext( val visaActivationRepository: VisaActivationRepository, - val card: Card, + val cardId: String, val session: CardSession, ) @@ -63,7 +65,7 @@ class VisaCardActivationTask @AssistedInject constructor( val context = SessionContext( visaActivationRepository = visaActivationRepository, - card = card, + cardId = card.cardId, session = session, ) @@ -82,7 +84,7 @@ class VisaCardActivationTask @AssistedInject constructor( private suspend fun SessionContext.signAuthorizationChallenge( challengeToSign: VisaAuthChallenge.Card, ): CompletionResult { - val attestationCommand = AttestCardKeyCommand(challenge = challengeToSign.challenge.hexToBytes()) + val attestationCommand = AttestCardKeyCommand(challenge = CryptoUtils.generateRandomBytes(length = 16)) val result = suspendCancellableCoroutine { continuation -> attestationCommand.run(session = session) { attestationResponse -> continuation.resume(attestationResponse) @@ -91,6 +93,7 @@ class VisaCardActivationTask @AssistedInject constructor( return when (result) { is CompletionResult.Success -> { + Timber.tag("ASDASD").e("AttestCardKeyCommand success") processSignedAuthorizationChallenge( signedChallenge = challengeToSign.toSignedChallenge( signedChallenge = result.data.cardSignature.toHexString(), @@ -99,6 +102,7 @@ class VisaCardActivationTask @AssistedInject constructor( ) } is CompletionResult.Failure -> { + Timber.tag("ASDASD").e("AttestCardKeyCommand failure ${result.error}") CompletionResult.Failure(result.error) } } @@ -135,12 +139,12 @@ class VisaCardActivationTask @AssistedInject constructor( ) } - visaAuthTokenStorage.store(card.cardId, tokens) + visaAuthTokenStorage.store(cardId, tokens) return visaActivationRepository.getActivationOrderToSign() } - private suspend fun createWallet(session: CardSession): CompletionResult { + private suspend fun SessionContext.createWallet(session: CardSession): CompletionResult { coroutineScope { ensureActive() } val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) @@ -157,22 +161,21 @@ class VisaCardActivationTask @AssistedInject constructor( when (result) { is CompletionResult.Success -> { + Timber.tag("ASDASD").e("CreateWalletTask success") createOTP(session) } is CompletionResult.Failure -> { + Timber.tag("ASDASD").e("CreateWalletTask failure ${result.error}") CompletionResult.Failure(result.error) } } } } - private suspend fun createOTP(session: CardSession): CompletionResult { + private suspend fun SessionContext.createOTP(session: CardSession): CompletionResult { coroutineScope { ensureActive() } - val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) - - val otp = otpStorage.getOTP(card.cardId) - return if (otp != null) { + return if (otpStorage.hasSavedOTP(cardId)) { CompletionResult.Success(Unit) } else { val otpCommand = GenerateOTPCommand() @@ -184,10 +187,12 @@ class VisaCardActivationTask @AssistedInject constructor( when (result) { is CompletionResult.Success -> { - otpStorage.saveOTP(card.cardId, result.data.rootOTP) + Timber.tag("ASDASD").e("GenerateOTPCommand success") + otpStorage.saveOTP(cardId, result.data.rootOTP) CompletionResult.Success(Unit) } is CompletionResult.Failure -> { + Timber.tag("ASDASD").e("GenerateOTPCommand failure ${result.error}") CompletionResult.Failure(result.error) } } @@ -195,9 +200,18 @@ class VisaCardActivationTask @AssistedInject constructor( } private suspend fun SessionContext.signOrder(order: ActivationOrder): CompletionResult { + val card = + session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) val wallet = - card.wallets.firstOrNull() ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) - val task = SignHashCommand(order.hash.hexToBytes(), wallet.publicKey) + card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } + ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) + + val task = SignHashCommand( + hash = order.hash.hexToBytes(), + walletPublicKey = wallet.publicKey, + derivationPath = VisaUtilities.visaDefaultDerivationPath, + ) + val result = suspendCancellableCoroutine { continuation -> task.run(session) { signResult -> continuation.resume(signResult) @@ -206,12 +220,14 @@ class VisaCardActivationTask @AssistedInject constructor( return when (result) { is CompletionResult.Success -> { + Timber.tag("ASDASD").e("SignHashCommand success") handleSignedOrder( activationOrder = order, response = result.data, ) } is CompletionResult.Failure -> { + Timber.tag("ASDASD").e("SignHashCommand failure ${result.error}") CompletionResult.Failure(result.error) } } @@ -226,7 +242,7 @@ class VisaCardActivationTask @AssistedInject constructor( signature = response.signature.toHexString(), ) - val otp = otpStorage.getOTP(card.cardId) ?: return CompletionResult.Failure( + val otp = otpStorage.getOTP(cardId) ?: return CompletionResult.Failure( TangemSdkError.Underlying(VisaActivationError.MissingRootOTP.message), ) @@ -239,10 +255,14 @@ class VisaCardActivationTask @AssistedInject constructor( } private suspend fun SessionContext.setupAccessCode(session: CardSession): CompletionResult { + val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) + if (card.isAccessCodeSet) { return CompletionResult.Success(Unit) } + Timber.tag("ASDASD").e("Setting access code: $accessCode") + val task = SetUserCodeCommand.changeAccessCode(accessCode) val result = suspendCancellableCoroutine { continuation -> task.run(session) { setAccessCodeResult -> @@ -252,9 +272,11 @@ class VisaCardActivationTask @AssistedInject constructor( return when (result) { is CompletionResult.Success -> { + Timber.tag("ASDASD").e("SetUserCodeCommand success") CompletionResult.Success(Unit) } is CompletionResult.Failure -> { + Timber.tag("ASDASD").e("SetUserCodeCommand failure ${result.error}") CompletionResult.Failure(result.error) } } 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 a86f371d93..d1e883bcdd 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 @@ -31,7 +31,7 @@ internal class VisaCardScanHandler @Inject constructor( private class SessionContext( val visaActivationRepository: VisaActivationRepository, - val card: Card, + val cardId: String, val session: CardSession, ) @@ -47,7 +47,7 @@ internal class VisaCardScanHandler @Inject constructor( val context = SessionContext( visaActivationRepository = visaActivationRepository, - card = card, + cardId = card.cardId, session = session, ) @@ -103,6 +103,8 @@ 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 { Timber.e("Failed to find extended public key while handling wallet authorization") return CompletionResult.Failure( @@ -118,7 +120,6 @@ internal class VisaCardScanHandler @Inject constructor( } Timber.i("Requesting challenge for wallet authorization") - // Will be changed later after backend implementation val challengeResponse = runCatching { visaAuthRepository.getCustomerWalletAuthChallenge( cardId = card.cardId, @@ -152,8 +153,6 @@ internal class VisaCardScanHandler @Inject constructor( private suspend fun SessionContext.handleWalletAuthorizationTokens( signedChallenge: VisaAuthSignedChallenge, ): CompletionResult { - val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) - val authorizationTokensResponse = runCatching { visaAuthRepository.getAccessTokens(signedChallenge = signedChallenge) }.getOrElse { @@ -164,7 +163,7 @@ internal class VisaCardScanHandler @Inject constructor( } visaAuthTokenStorage.store( - cardId = card.cardId, + cardId = cardId, tokens = authorizationTokensResponse, ) @@ -266,14 +265,7 @@ internal class VisaCardScanHandler @Inject constructor( } } - return when (result) { - is CompletionResult.Success -> { - CompletionResult.Success(result.data) - } - is CompletionResult.Failure -> { - CompletionResult.Failure(result.error) - } - } + return result } private suspend fun SessionContext.signChallengeWithCard( diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt index 3e0b010bc8..57cb30dab0 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt @@ -1,8 +1,10 @@ package com.tangem.datasource.api.visa +import com.tangem.datasource.api.utils.ReadTimeout import com.tangem.datasource.api.visa.models.response.CardActivationRemoteStateResponse import retrofit2.http.GET import retrofit2.http.Header +import java.util.concurrent.TimeUnit interface TangemVisaApi { @@ -10,4 +12,10 @@ interface TangemVisaApi { suspend fun getRemoteActivationStatus( @Header("Authorization") authHeader: String, ): CardActivationRemoteStateResponse + + @ReadTimeout(duration = 20, TimeUnit.MINUTES) + @GET("activation-status") + suspend fun getRemoteActivationStatusLongPoll( + @Header("Authorization") authHeader: String, + ): CardActivationRemoteStateResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index ae75ab68d8..353f66d62e 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -187,7 +187,7 @@ internal object NetworkModule { clientBuilder = { addInterceptor( NetworkLogsSaveInterceptor(appLogsStore), - ) + ).applyTimeoutAnnotations() }, ) } @@ -208,7 +208,7 @@ internal object NetworkModule { clientBuilder = { addInterceptor( NetworkLogsSaveInterceptor(appLogsStore), - ) + ).applyTimeoutAnnotations() }, ) } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/visa/VisaAuthTokenStorage.kt b/core/datasource/src/main/java/com/tangem/datasource/local/visa/VisaAuthTokenStorage.kt index cceb0afdde..a91c2705a1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/visa/VisaAuthTokenStorage.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/visa/VisaAuthTokenStorage.kt @@ -7,4 +7,6 @@ interface VisaAuthTokenStorage { suspend fun store(cardId: String, tokens: VisaAuthTokens) suspend fun get(cardId: String): VisaAuthTokens? + + fun remove(cardId: String) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt b/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt index a4dd795246..c3b1cfb588 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt @@ -44,17 +44,13 @@ internal fun OkHttpClient.Builder.applyTimeoutAnnotations(): OkHttpClient.Builde val readTimeout = tag?.method()?.getAnnotation(ReadTimeout::class.java) val writeTimeout = tag?.method()?.getAnnotation(WriteTimeout::class.java) - chain - .apply { - connectionTimeout?.let { withConnectTimeout(timeout = it.duration, unit = it.unit) } - } - .apply { - readTimeout?.let { withReadTimeout(timeout = it.duration, unit = it.unit) } - } - .apply { - writeTimeout?.let { withWriteTimeout(timeout = it.duration, unit = it.unit) } - } - .proceed(request) + chain.run { + connectionTimeout?.let { withConnectTimeout(timeout = it.duration, unit = it.unit) } ?: this + }.run { + readTimeout?.let { withReadTimeout(timeout = it.duration, unit = it.unit) } ?: this + }.run { + writeTimeout?.let { withWriteTimeout(timeout = it.duration, unit = it.unit) } ?: this + }.proceed(request) }, ) } diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt index b11958c4f5..b4cf7f4d8f 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt @@ -1,6 +1,9 @@ package com.tangem.data.visa +import com.tangem.common.extensions.toHexString +import com.tangem.crypto.CryptoUtils import com.tangem.data.visa.converter.VisaActivationStatusConverter +import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider import com.tangem.datasource.api.visa.TangemVisaApi import com.tangem.domain.visa.model.ActivationOrder import com.tangem.domain.visa.model.VisaActivationRemoteState @@ -16,16 +19,26 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor( private val visaApi: TangemVisaApi, private val dispatcherProvider: CoroutineDispatcherProvider, private val visaActivationStatusConverter: VisaActivationStatusConverter, + private val visaAuthProvider: TangemVisaAuthProvider, ) : VisaActivationRepository { override suspend fun getActivationRemoteState(): VisaActivationRemoteState = withContext(dispatcherProvider.io) { - visaActivationStatusConverter.convert(visaApi.getRemoteActivationStatus(cardId)) + // visaActivationStatusConverter.convert(visaApi.getRemoteActivationStatus(visaAuthProvider.getAuthHeader(cardId))) VisaActivationRemoteState.CardWalletSignatureRequired // mock // TODO implement refreshing access token if it's expired } - override suspend fun getActivationOrderToSign(): ActivationOrder { - return ActivationOrder("TODO implement") + override suspend fun getActivationRemoteStateLongPoll(): VisaActivationRemoteState = + withContext(dispatcherProvider.io) { + // visaActivationStatusConverter.convert( + // visaApi.getRemoteActivationStatusLongPoll(visaAuthProvider.getAuthHeader(cardId)), + // ) + + VisaActivationRemoteState.WaitingPinCode + } + + override suspend fun getActivationOrderToSign(): ActivationOrder = withContext(dispatcherProvider.io) { + ActivationOrder(CryptoUtils.generateRandomBytes(32).toHexString()) } @AssistedFactory diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt index a0fb323d81..042d5d5518 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt @@ -1,5 +1,7 @@ package com.tangem.data.visa +import com.tangem.common.extensions.toHexString +import com.tangem.crypto.CryptoUtils import com.tangem.datasource.api.visa.TangemVisaAuthApi import com.tangem.domain.visa.model.VisaAuthChallenge import com.tangem.domain.visa.model.VisaAuthSession @@ -17,14 +19,19 @@ internal class DefaultVisaAuthRepository @Inject constructor( override suspend fun getCardAuthChallenge(cardId: String, cardPublicKey: String): VisaAuthChallenge.Card = withContext(dispatchers.io) { - val response = visaAuthApi.generateNonceByCard( - cardId = cardId, - cardPublicKey = cardPublicKey, - ) + // val response = visaAuthApi.generateNonceByCard( + // cardId = cardId, + // cardPublicKey = cardPublicKey, + // ) + // + // VisaAuthChallenge.Card( + // challenge = response.nonce, + // session = VisaAuthSession(response.sessionId), + // ) VisaAuthChallenge.Card( - challenge = response.nonce, - session = VisaAuthSession(response.sessionId), + challenge = CryptoUtils.generateRandomBytes(16).toHexString(), + session = VisaAuthSession("session"), ) } @@ -32,39 +39,47 @@ internal class DefaultVisaAuthRepository @Inject constructor( cardId: String, walletPublicKey: String, ): VisaAuthChallenge.Wallet = withContext(dispatchers.io) { - val response = visaAuthApi.generateNonceByWalletAddress( - customerId = cardId, - customerWalletAddress = walletPublicKey, - ) - + // val response = visaAuthApi.generateNonceByWalletAddress( + // customerId = cardId, + // customerWalletAddress = walletPublicKey, + // ) + // + // VisaAuthChallenge.Wallet( + // challenge = response.nonce, + // session = VisaAuthSession(response.sessionId), + // ) VisaAuthChallenge.Wallet( - challenge = response.nonce, - session = VisaAuthSession(response.sessionId), + challenge = CryptoUtils.generateRandomBytes(32).toHexString(), + session = VisaAuthSession("session"), ) } override suspend fun getAccessTokens(signedChallenge: VisaAuthSignedChallenge): VisaAuthTokens = withContext(dispatchers.io) { - val response = when (signedChallenge) { - is VisaAuthSignedChallenge.ByCardPublicKey -> { - visaAuthApi.getAccessToken( - sessionId = signedChallenge.challenge.session.sessionId, - signature = signedChallenge.signature, - salt = signedChallenge.salt, - ) - } - is VisaAuthSignedChallenge.ByWallet -> { - visaAuthApi.getAccessToken( - sessionId = signedChallenge.challenge.session.sessionId, - signature = signedChallenge.signature, - salt = null, - ) - } - } - + // val response = when (signedChallenge) { + // is VisaAuthSignedChallenge.ByCardPublicKey -> { + // visaAuthApi.getAccessToken( + // sessionId = signedChallenge.challenge.session.sessionId, + // signature = signedChallenge.signature, + // salt = signedChallenge.salt, + // ) + // } + // is VisaAuthSignedChallenge.ByWallet -> { + // visaAuthApi.getAccessToken( + // sessionId = signedChallenge.challenge.session.sessionId, + // signature = signedChallenge.signature, + // salt = null, + // ) + // } + // } + // + // VisaAuthTokens( + // accessToken = response.accessToken, + // refreshToken = response.refreshToken, + // ) VisaAuthTokens( - accessToken = response.accessToken, - refreshToken = response.refreshToken, + accessToken = "accessToken", + refreshToken = "refreshToken", ) } } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt index 810e422951..4dd591568f 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt @@ -7,6 +7,8 @@ interface VisaActivationRepository { suspend fun getActivationRemoteState(): VisaActivationRemoteState + suspend fun getActivationRemoteStateLongPoll(): VisaActivationRemoteState + suspend fun getActivationOrderToSign(): ActivationOrder interface Factory { 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 a174ae69e0..22ab0da110 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 @@ -151,6 +151,9 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( ) OnboardingVisaRoute.InProgress -> OnboardingVisaInProgressComponent( appComponentContext = factoryContext, + config = OnboardingVisaInProgressComponent.Config( + scanResponse = params.scanResponse, + ), params = OnboardingVisaInProgressComponent.Params( childParams = childParams, onDone = { stackNavigation.push(OnboardingVisaRoute.PinCode) }, @@ -159,15 +162,19 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( is OnboardingVisaRoute.OtherWalletApproveOption -> OnboardingVisaOtherWalletComponent( appComponentContext = factoryContext, config = OnboardingVisaOtherWalletComponent.Config( + scanResponse = params.scanResponse, visaDataForApprove = route.visaDataForApprove, ), params = OnboardingVisaOtherWalletComponent.Params( childParams = childParams, - onDone = { stackNavigation.push(OnboardingVisaRoute.InProgress) }, + onDone = { stackNavigation.push(OnboardingVisaRoute.PinCode) }, ), ) OnboardingVisaRoute.PinCode -> OnboardingVisaPinCodeComponent( appComponentContext = factoryContext, + config = OnboardingVisaPinCodeComponent.Config( + scanResponse = params.scanResponse, + ), params = OnboardingVisaPinCodeComponent.Params( childParams = childParams, onDone = { params.onDone() }, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt index 1186c22057..b621996c87 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt @@ -17,6 +17,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject +import kotlin.math.tan @Stable @ComponentScoped @@ -39,6 +40,8 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor( val onDone = MutableSharedFlow() fun onBack() { + if (uiState.value.buttonLoading) return + when (uiState.value.step) { OnboardingVisaAccessCodeUM.Step.Enter -> modelScope.launch { onBack.emit(Unit) } OnboardingVisaAccessCodeUM.Step.ReEnter -> @@ -141,7 +144,7 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor( OnboardingVisaAccessCodeComponent.DoneEvent( visaDataForApprove = VisaDataForApprove( targetAddress = "x9F65354e595284956599F2892fA4A4a87653D6E6", - approveHash = "approve hash", + approveHash = "48b55c482123a10ad9022f9f4c5dd95c", ), walletFound = false, // TODO ), 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 f8d10a865f..6e8787f75a 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 @@ -7,6 +7,7 @@ import androidx.compose.ui.Modifier 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.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 @@ -15,10 +16,11 @@ import kotlinx.coroutines.flow.onEach internal class OnboardingVisaInProgressComponent( appComponentContext: AppComponentContext, + config: Config, private val params: Params, ) : ComposableContentComponent, AppComponentContext by appComponentContext { - private val model: OnboardingVisaInProgressModel = getOrCreateModel() + private val model: OnboardingVisaInProgressModel = getOrCreateModel(config) init { model.onDone @@ -35,6 +37,10 @@ internal class OnboardingVisaInProgressComponent( ) } + data class Config( + val scanResponse: ScanResponse, + ) + data class Params( val childParams: DefaultOnboardingVisaComponent.ChildParams, val onDone: () -> Unit, 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 9639c4ddc8..94748a9a1a 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 @@ -3,6 +3,10 @@ package com.tangem.features.onboarding.v2.visa.impl.child.inprogress.model import androidx.compose.runtime.Stable 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.repository.VisaActivationRepository +import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow @@ -12,16 +16,29 @@ import javax.inject.Inject @Stable @ComponentScoped internal class OnboardingVisaInProgressModel @Inject constructor( + paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, + private val visaActivationRepositoryFactory: VisaActivationRepository.Factory, ) : Model() { + private val params = paramsContainer.require() + private val visaActivationRepository = visaActivationRepositoryFactory.create(params.scanResponse.card.cardId) val onDone = MutableSharedFlow() init { modelScope.launch { - // TODO check state - delay(timeMillis = 2000) - onDone.emit(Unit) + while (true) { + val result = runCatching { + visaActivationRepository.getActivationRemoteStateLongPoll() + }.getOrNull() + + if (result == VisaActivationRemoteState.WaitingPinCode) { + onDone.emit(Unit) + break + } + + delay(timeMillis = 1000) + } } } } \ 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 3442f47224..a9d2297b5f 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 @@ -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.otherwallet.model.OnboardingVisaOtherWalletModel @@ -43,6 +44,7 @@ internal class OnboardingVisaOtherWalletComponent( } data class Config( + val scanResponse: ScanResponse, val visaDataForApprove: VisaDataForApprove, ) 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 7e5ece076a..51036ad803 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 @@ -4,12 +4,18 @@ import androidx.compose.runtime.Stable 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.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 import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch import javax.inject.Inject @Stable @@ -17,15 +23,36 @@ import javax.inject.Inject internal class OnboardingVisaOtherWalletModel @Inject constructor( paramsContainer: ParamsContainer, 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() + private val visaActivationRepository = visaActivationRepositoryFactory.create(config.scanResponse.card.cardId) private val _uiState = MutableStateFlow(getInitialState()) val uiState = _uiState.asStateFlow() val onDone = MutableSharedFlow() + init { + modelScope.launch { + while (true) { + val result = runCatching { + visaActivationRepository.getActivationRemoteStateLongPoll() + }.getOrNull() + + if (result == VisaActivationRemoteState.WaitingPinCode) { + onDone.emit(Unit) + break + } + + delay(timeMillis = 1000) + } + } + } + private fun getInitialState(): OnboardingVisaOtherWalletUM { return OnboardingVisaOtherWalletUM( onShareClick = ::onShareClicked, @@ -34,10 +61,10 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor( } private fun onShareClicked() { - // TODO + shareManager.shareText("https://tangem.com/${config.visaDataForApprove.approveHash}") } private fun onOpenInBrowserClicked() { - // TODO + urlOpener.openUrl("https://tangem.com/${config.visaDataForApprove.approveHash}") } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/OnboardingVisaPinCodeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/OnboardingVisaPinCodeComponent.kt index 05aeba0384..edf60844f7 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/OnboardingVisaPinCodeComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/OnboardingVisaPinCodeComponent.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.core.ui.security.DisableScreenshotsDisposableEffect +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.pincode.model.OnboardingVisaPinCodeModel import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.OnboardingVisaPinCode @@ -17,10 +18,11 @@ import kotlinx.coroutines.launch internal class OnboardingVisaPinCodeComponent( appComponentContext: AppComponentContext, + config: Config, private val params: Params, ) : ComposableContentComponent, AppComponentContext by appComponentContext { - private val model: OnboardingVisaPinCodeModel = getOrCreateModel() + private val model: OnboardingVisaPinCodeModel = getOrCreateModel(config) init { componentScope.launch { @@ -42,6 +44,10 @@ internal class OnboardingVisaPinCodeComponent( ) } + data class Config( + val scanResponse: ScanResponse, + ) + data class Params( val childParams: DefaultOnboardingVisaComponent.ChildParams, val onDone: () -> Unit, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt index 19f65cfd96..38f42fad17 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt @@ -3,6 +3,15 @@ package com.tangem.features.onboarding.v2.visa.impl.child.pincode.model import androidx.compose.runtime.Stable import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.datasource.local.visa.VisaAuthTokenStorage +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.visa.model.VisaCardActivationStatus +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.pincode.OnboardingVisaPinCodeComponent import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.state.OnboardingVisaPinCodeUM import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableSharedFlow @@ -10,14 +19,21 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject @Stable @ComponentScoped internal class OnboardingVisaPinCodeModel @Inject constructor( + paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, + private val generateWalletNameUseCase: GenerateWalletNameUseCase, + private val userWalletsListManager: UserWalletsListManager, + private val authTokenStorage: VisaAuthTokenStorage, + private val otpStorage: VisaAuthTokenStorage, ) : Model() { + private val params = paramsContainer.require() private val _uiState = MutableStateFlow(getInitialState()) val uiState = _uiState.asStateFlow() @@ -31,21 +47,61 @@ internal class OnboardingVisaPinCodeModel @Inject constructor( } private fun onPinCodeChange(pin: String) { - if (pin.all { it.isDigit() }) _uiState.update { it.copy(pinCode = pin) } + if (pin.all { it.isDigit() }) { + _uiState.update { + it.copy( + pinCode = pin, + submitButtonEnabled = checkPinCode(pin), + ) + } + } } private fun onSubmitClick() { if (checkPinCode(_uiState.value.pinCode).not()) return - // TODO + modelScope.launch { + loading(true) - modelScope.launch { onDone.emit(Unit) } + // TODO + // make backend call + saveWallet() + + loading(false) + } } private fun checkPinCode(pin: String): Boolean { return pin.length == PIN_CODE_LENGTH } + private fun loading(state: Boolean) { + _uiState.update { it.copy(submitButtonLoading = state) } + } + + private suspend fun saveWallet() { + val userWallet = createUserWallet(params.scanResponse) + userWalletsListManager.save(userWallet) + authTokenStorage.remove(params.scanResponse.card.cardId) + otpStorage.remove(params.scanResponse.card.cardId) + onDone.emit(Unit) + } + + private suspend fun createUserWallet(scanResponse: ScanResponse): UserWallet = withContext(dispatchers.io) { + val newActivationStatus = VisaCardActivationStatus.Activated( + visaAuthTokens = authTokenStorage.get(scanResponse.card.cardId) + ?: error("Impossible state. Wrong feature implementation"), + ) + + requireNotNull( + value = UserWalletBuilder( + scanResponse.copy(visaCardActivationStatus = newActivationStatus), + generateWalletNameUseCase, + ).build(), + lazyMessage = { "User wallet not created" }, + ) + } + private companion object { const val PIN_CODE_LENGTH = 4 } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt index cf47762749..6db2601978 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt @@ -28,9 +28,11 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.PrimaryButton +import com.tangem.common.ui.navigationButtons.NavigationButton +import com.tangem.common.ui.navigationButtons.NavigationPrimaryButton import com.tangem.core.ui.components.SpacerH import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.state.OnboardingVisaPinCodeUM @@ -72,13 +74,17 @@ internal fun OnboardingVisaPinCode(state: OnboardingVisaPinCodeUM, modifier: Mod PinCodeSection(state) } - PrimaryButton( + NavigationPrimaryButton( modifier = Modifier .imePadding() .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) .fillMaxWidth(), - text = "Submit", - onClick = state.onSubmitClick, + primaryButton = NavigationButton( + textReference = TextReference.Str("Submit"), + onClick = state.onSubmitClick, + showProgress = state.submitButtonLoading, + isEnabled = state.submitButtonEnabled, + ), ) } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/state/OnboardingVisaPinCodeUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/state/OnboardingVisaPinCodeUM.kt index a8d26f44a3..9b17f93c5a 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/state/OnboardingVisaPinCodeUM.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/state/OnboardingVisaPinCodeUM.kt @@ -3,5 +3,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.state internal data class OnboardingVisaPinCodeUM( val pinCode: String = "", val onPinCodeChange: (String) -> Unit = {}, + val submitButtonLoading: Boolean = false, + val submitButtonEnabled: Boolean = true, val onSubmitClick: () -> Unit = {}, ) \ No newline at end of file From e00f5390d20905f84f0ee90c3adb490e515857e4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 27 Jan 2025 12:55:34 +0300 Subject: [PATCH 08/11] Updated on 2026-08-14 --- .../tasks/visa/VisaCardActivationTask.kt | 105 +++++++++++------- .../tap/domain/visa/VisaCardScanHandler.kt | 25 ++--- .../RealtimeMonotonicTimeSource.kt | 9 ++ .../visa/DefaultVisaActivationRepository.kt | 3 +- .../data/visa/DefaultVisaAuthRepository.kt | 5 +- .../domain/common/visa/VisaUtilities.kt | 3 +- .../OnboardingVisaAccessCodeComponent.kt | 3 +- .../model/OnboardingVisaAccessCodeModel.kt | 1 - 8 files changed, 92 insertions(+), 62 deletions(-) create mode 100644 common/src/main/kotlin/com/tangem/common/timemeasure/RealtimeMonotonicTimeSource.kt 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 9bd9bf2e48..f8b20f31d0 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 @@ -8,6 +8,7 @@ import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString import com.tangem.common.map +import com.tangem.common.timemeasure.RealtimeMonotonicTimeSource import com.tangem.crypto.CryptoUtils import com.tangem.datasource.local.visa.VisaAuthTokenStorage import com.tangem.datasource.local.visa.VisaOTPStorage @@ -29,6 +30,7 @@ import kotlinx.coroutines.* import timber.log.Timber import kotlin.coroutines.resume import kotlin.jvm.Throws +import kotlin.time.measureTimedValue @Suppress("LongParameterList") class VisaCardActivationTask @AssistedInject constructor( @@ -69,31 +71,38 @@ class VisaCardActivationTask @AssistedInject constructor( session = session, ) - return if (challengeToSign != null) { - context.signAuthorizationChallenge(challengeToSign) - } else { - val activationOrder = runCatching { visaActivationRepository.getActivationOrderToSign() } - .getOrElse { - return CompletionResult.Failure(TangemSdkError.Underlying(it.message ?: "")) - } + val timedResult = RealtimeMonotonicTimeSource.measureTimedValue { + if (challengeToSign != null) { + context.signAuthorizationChallenge(challengeToSign) + } else { + val activationOrder = runCatching { visaActivationRepository.getActivationOrderToSign() } + .getOrElse { + return CompletionResult.Failure(TangemSdkError.Underlying(it.message ?: "")) + } - context.signOrder(activationOrder) + context.signOrder(activationOrder) + } } + Timber.i("VisaCardActivationTask all time: ${timedResult.duration}") + return timedResult.value } private suspend fun SessionContext.signAuthorizationChallenge( challengeToSign: VisaAuthChallenge.Card, ): CompletionResult { val attestationCommand = AttestCardKeyCommand(challenge = CryptoUtils.generateRandomBytes(length = 16)) - val result = suspendCancellableCoroutine { continuation -> - attestationCommand.run(session = session) { attestationResponse -> - continuation.resume(attestationResponse) + val timedResult = RealtimeMonotonicTimeSource.measureTimedValue { + suspendCancellableCoroutine { continuation -> + attestationCommand.run(session = session) { attestationResponse -> + continuation.resume(attestationResponse) + } } } + Timber.i("AttestCardKeyCommand time: ${timedResult.duration}") - return when (result) { + return when (val result = timedResult.value) { is CompletionResult.Success -> { - Timber.tag("ASDASD").e("AttestCardKeyCommand success") + Timber.i("AttestCardKeyCommand success") processSignedAuthorizationChallenge( signedChallenge = challengeToSign.toSignedChallenge( signedChallenge = result.data.cardSignature.toHexString(), @@ -102,7 +111,7 @@ class VisaCardActivationTask @AssistedInject constructor( ) } is CompletionResult.Failure -> { - Timber.tag("ASDASD").e("AttestCardKeyCommand failure ${result.error}") + Timber.e("AttestCardKeyCommand failure ${result.error}") CompletionResult.Failure(result.error) } } @@ -153,19 +162,24 @@ class VisaCardActivationTask @AssistedInject constructor( createOTP(session) } else { val createWalletTask = CreateWalletTask(VisaUtilities.mandatoryCurve) - val result = suspendCancellableCoroutine { continuation -> - createWalletTask.run(session) { createWalletResult -> - continuation.resume(createWalletResult) + + val timedResult = RealtimeMonotonicTimeSource.measureTimedValue { + suspendCancellableCoroutine { continuation -> + createWalletTask.run(session) { createWalletResult -> + continuation.resume(createWalletResult) + } } } - when (result) { + Timber.i("CreateWalletTask time: ${timedResult.duration}") + + when (val result = timedResult.value) { is CompletionResult.Success -> { - Timber.tag("ASDASD").e("CreateWalletTask success") + Timber.i("CreateWalletTask success") createOTP(session) } is CompletionResult.Failure -> { - Timber.tag("ASDASD").e("CreateWalletTask failure ${result.error}") + Timber.e("CreateWalletTask failure ${result.error}") CompletionResult.Failure(result.error) } } @@ -179,20 +193,24 @@ class VisaCardActivationTask @AssistedInject constructor( CompletionResult.Success(Unit) } else { val otpCommand = GenerateOTPCommand() - val result = suspendCancellableCoroutine { continuation -> - otpCommand.run(session) { otpResult -> - continuation.resume(otpResult) + val timedResult = RealtimeMonotonicTimeSource.measureTimedValue { + suspendCancellableCoroutine { continuation -> + otpCommand.run(session) { otpResult -> + continuation.resume(otpResult) + } } } - when (result) { + Timber.i("GenerateOTPCommand time: ${timedResult.duration}") + + when (val result = timedResult.value) { is CompletionResult.Success -> { - Timber.tag("ASDASD").e("GenerateOTPCommand success") + Timber.i("GenerateOTPCommand success") otpStorage.saveOTP(cardId, result.data.rootOTP) CompletionResult.Success(Unit) } is CompletionResult.Failure -> { - Timber.tag("ASDASD").e("GenerateOTPCommand failure ${result.error}") + Timber.e("GenerateOTPCommand failure ${result.error}") CompletionResult.Failure(result.error) } } @@ -212,22 +230,26 @@ class VisaCardActivationTask @AssistedInject constructor( derivationPath = VisaUtilities.visaDefaultDerivationPath, ) - val result = suspendCancellableCoroutine { continuation -> - task.run(session) { signResult -> - continuation.resume(signResult) + val timedResult = RealtimeMonotonicTimeSource.measureTimedValue { + suspendCancellableCoroutine { continuation -> + task.run(session) { signResult -> + continuation.resume(signResult) + } } } - return when (result) { + Timber.i("SignHashCommand time: ${timedResult.duration}") + + return when (val result = timedResult.value) { is CompletionResult.Success -> { - Timber.tag("ASDASD").e("SignHashCommand success") + Timber.i("SignHashCommand success") handleSignedOrder( activationOrder = order, response = result.data, ) } is CompletionResult.Failure -> { - Timber.tag("ASDASD").e("SignHashCommand failure ${result.error}") + Timber.e("SignHashCommand failure ${result.error}") CompletionResult.Failure(result.error) } } @@ -261,22 +283,27 @@ class VisaCardActivationTask @AssistedInject constructor( return CompletionResult.Success(Unit) } - Timber.tag("ASDASD").e("Setting access code: $accessCode") + Timber.i("Setting access code") val task = SetUserCodeCommand.changeAccessCode(accessCode) - val result = suspendCancellableCoroutine { continuation -> - task.run(session) { setAccessCodeResult -> - continuation.resume(setAccessCodeResult) + + val timedResult = RealtimeMonotonicTimeSource.measureTimedValue { + suspendCancellableCoroutine { continuation -> + task.run(session) { setAccessCodeResult -> + continuation.resume(setAccessCodeResult) + } } } - return when (result) { + Timber.i("SetUserCodeCommand time: ${timedResult.duration}") + + return when (val result = timedResult.value) { is CompletionResult.Success -> { - Timber.tag("ASDASD").e("SetUserCodeCommand success") + Timber.i("SetUserCodeCommand success") CompletionResult.Success(Unit) } is CompletionResult.Failure -> { - Timber.tag("ASDASD").e("SetUserCodeCommand failure ${result.error}") + Timber.i("SetUserCodeCommand failure ${result.error}") CompletionResult.Failure(result.error) } } 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 d1e883bcdd..42e9b20d4a 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 @@ -1,10 +1,10 @@ package com.tangem.tap.domain.visa import com.tangem.common.CompletionResult -import com.tangem.common.card.Card import com.tangem.common.card.CardWallet import com.tangem.common.core.CardSession import com.tangem.common.core.TangemSdkError +import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey @@ -258,33 +258,26 @@ internal class VisaCardScanHandler @Inject constructor( derivationPath: DerivationPath, nonce: String, ): CompletionResult { - val signHashCommand = SignHashCommand(publicKey, nonce.toByteArray(), derivationPath) - val result = suspendCancellableCoroutine { + val signHashCommand = SignHashCommand( + hash = nonce.hexToBytes(), + walletPublicKey = publicKey, + derivationPath = derivationPath, + ) + return suspendCancellableCoroutine { signHashCommand.run(session) { result -> it.resume(result) } } - - return result } private suspend fun SessionContext.signChallengeWithCard( challenge: String, ): CompletionResult { - val signHashCommand = AttestCardKeyCommand(challenge = challenge.toByteArray()) - val result = suspendCancellableCoroutine { continuation -> + val signHashCommand = AttestCardKeyCommand(challenge = challenge.hexToBytes()) + return suspendCancellableCoroutine { continuation -> signHashCommand.run(session) { result -> continuation.resume(result) } } - - return when (result) { - is CompletionResult.Success -> { - CompletionResult.Success(result.data) - } - is CompletionResult.Failure -> { - CompletionResult.Failure(result.error) - } - } } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/tangem/common/timemeasure/RealtimeMonotonicTimeSource.kt b/common/src/main/kotlin/com/tangem/common/timemeasure/RealtimeMonotonicTimeSource.kt new file mode 100644 index 0000000000..0c2858265c --- /dev/null +++ b/common/src/main/kotlin/com/tangem/common/timemeasure/RealtimeMonotonicTimeSource.kt @@ -0,0 +1,9 @@ +package com.tangem.common.timemeasure + +import android.os.SystemClock +import kotlin.time.AbstractLongTimeSource +import kotlin.time.DurationUnit + +object RealtimeMonotonicTimeSource : AbstractLongTimeSource(DurationUnit.NANOSECONDS) { + override fun read(): Long = SystemClock.elapsedRealtimeNanos() +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt index b4cf7f4d8f..4384fc3d0e 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt @@ -14,6 +14,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.withContext +@Suppress("UnusedPrivateMember") internal class DefaultVisaActivationRepository @AssistedInject constructor( @Assisted private val cardId: String, private val visaApi: TangemVisaApi, @@ -38,7 +39,7 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor( } override suspend fun getActivationOrderToSign(): ActivationOrder = withContext(dispatcherProvider.io) { - ActivationOrder(CryptoUtils.generateRandomBytes(32).toHexString()) + ActivationOrder(CryptoUtils.generateRandomBytes(length = 32).toHexString()) } @AssistedFactory diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt index 042d5d5518..a4646bf7b6 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt @@ -12,6 +12,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext import javax.inject.Inject +@Suppress("UnusedPrivateMember") internal class DefaultVisaAuthRepository @Inject constructor( private val visaAuthApi: TangemVisaAuthApi, private val dispatchers: CoroutineDispatcherProvider, @@ -30,7 +31,7 @@ internal class DefaultVisaAuthRepository @Inject constructor( // ) VisaAuthChallenge.Card( - challenge = CryptoUtils.generateRandomBytes(16).toHexString(), + challenge = CryptoUtils.generateRandomBytes(length = 16).toHexString(), session = VisaAuthSession("session"), ) } @@ -49,7 +50,7 @@ internal class DefaultVisaAuthRepository @Inject constructor( // session = VisaAuthSession(response.sessionId), // ) VisaAuthChallenge.Wallet( - challenge = CryptoUtils.generateRandomBytes(32).toHexString(), + challenge = CryptoUtils.generateRandomBytes(length = 32).toHexString(), session = VisaAuthSession("session"), ) } 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 b84ed4f673..d62fffa929 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 @@ -16,7 +16,8 @@ object VisaUtilities { val visaBlockchain = Blockchain.Polygon - val visaDefaultDerivationPath = visaBlockchain.derivationPath(DerivationStyle.V3) + val visaDefaultDerivationPath + get() = visaBlockchain.derivationPath(DerivationStyle.V3) fun visaDefaultDerivationPath(style: DerivationStyle) = visaBlockchain.derivationPath(style) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt index dd7a6f2ed6..6508b14557 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt @@ -8,7 +8,6 @@ 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.core.ui.security.DisableScreenshotsDisposableEffect import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent @@ -44,7 +43,7 @@ internal class OnboardingVisaAccessCodeComponent( BackHandler(onBack = model::onBack) - DisableScreenshotsDisposableEffect() + // DisableScreenshotsDisposableEffect() OnboardingVisaAccessCode(state, modifier) } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt index b621996c87..01ed148a2f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt @@ -17,7 +17,6 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject -import kotlin.math.tan @Stable @ComponentScoped From 84c881b4ac58d418fb397bb451c3ac78c4b8dfdf Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 28 Jan 2025 10:50:00 +0300 Subject: [PATCH 09/11] Updated on 2026-08-14 --- .../kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt index a4646bf7b6..d29d54dad4 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt @@ -80,7 +80,7 @@ internal class DefaultVisaAuthRepository @Inject constructor( // ) VisaAuthTokens( accessToken = "accessToken", - refreshToken = "refreshToken", + refreshToken = "refreshToken2", ) } } \ No newline at end of file From 3406a353b025c5ca17e539836756684a9e925a6e Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 28 Jan 2025 10:53:26 +0300 Subject: [PATCH 10/11] Updated on 2026-08-14 --- .../kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt index d29d54dad4..a4646bf7b6 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt @@ -80,7 +80,7 @@ internal class DefaultVisaAuthRepository @Inject constructor( // ) VisaAuthTokens( accessToken = "accessToken", - refreshToken = "refreshToken2", + refreshToken = "refreshToken", ) } } \ No newline at end of file From 112d1e8a906baf066ccd5ae26c6d87dfbe441e21 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 28 Jan 2025 13:28:17 +0300 Subject: [PATCH 11/11] Updated on 2026-08-14 --- .../sdk/impl/DefaultTangemSdkManager.kt | 2 +- .../domain/sdk/impl/MockTangemSdkManager.kt | 2 +- .../tasks/visa/VisaCardActivationTask.kt | 18 ++- .../visa/model/VisaCardActivationResponse.kt | 6 - .../impl/DefaultOnboardingVisaComponent.kt | 84 +++---------- .../OnboardingVisaAccessCodeComponent.kt | 1 + .../model/OnboardingVisaAccessCodeModel.kt | 8 +- .../child/pincode/ui/OnboardingVisaPinCode.kt | 3 +- .../v2/visa/impl/model/OnboardingVisaModel.kt | 114 +++++++++++++++++- .../com/tangem/sdk/api/TangemSdkManager.kt | 2 +- .../api/visa/VisaCardActivationResponse.kt | 11 ++ 11 files changed, 164 insertions(+), 87 deletions(-) delete mode 100644 domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCardActivationResponse.kt create mode 100644 libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/visa/VisaCardActivationResponse.kt diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt index ac325d7207..4cda23bbe9 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt @@ -26,7 +26,6 @@ import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.visa.model.VisaActivationInput import com.tangem.domain.visa.model.VisaAuthChallenge -import com.tangem.domain.visa.model.VisaCardActivationResponse import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles @@ -41,6 +40,7 @@ import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.sdk.api.CreateProductWalletTaskResponse import com.tangem.sdk.api.TangemSdkManager +import com.tangem.sdk.api.visa.VisaCardActivationResponse import com.tangem.tap.derivationsFinder import com.tangem.tap.domain.tasks.product.CreateProductWalletTask import com.tangem.tap.domain.tasks.product.ResetBackupCardTask diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt index 3e8dce1c1e..b6291e7a12 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt @@ -19,7 +19,6 @@ import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.visa.model.VisaActivationInput import com.tangem.domain.visa.model.VisaAuthChallenge -import com.tangem.domain.visa.model.VisaCardActivationResponse import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.DerivationTaskResponse @@ -28,6 +27,7 @@ import com.tangem.operations.sign.SignHashResponse import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.sdk.api.CreateProductWalletTaskResponse import com.tangem.sdk.api.TangemSdkManager +import com.tangem.sdk.api.visa.VisaCardActivationResponse import com.tangem.tap.domain.sdk.mocks.MockProvider @Suppress("TooManyFunctions") 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 f8b20f31d0..920c63ac0e 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 @@ -14,6 +14,7 @@ import com.tangem.datasource.local.visa.VisaAuthTokenStorage import com.tangem.datasource.local.visa.VisaOTPStorage import com.tangem.datasource.local.visa.hasSavedOTP import com.tangem.domain.common.visa.VisaUtilities +import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.visa.model.* import com.tangem.domain.visa.repository.VisaActivationRepository import com.tangem.domain.visa.repository.VisaAuthRepository @@ -23,6 +24,7 @@ import com.tangem.operations.pins.SetUserCodeCommand import com.tangem.operations.sign.SignHashCommand import com.tangem.operations.sign.SignHashResponse import com.tangem.operations.wallet.CreateWalletTask +import com.tangem.sdk.api.visa.VisaCardActivationResponse import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -268,15 +270,19 @@ class VisaCardActivationTask @AssistedInject constructor( TangemSdkError.Underlying(VisaActivationError.MissingRootOTP.message), ) - val activationResponse = VisaCardActivationResponse( - signedActivationOrder = signedOrder, - rootOTP = VisaRootOTP(otp.toHexString()), - ) + return setupAccessCode().map { + val card = + session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) - return setupAccessCode(session).map { activationResponse } + VisaCardActivationResponse( + signedActivationOrder = signedOrder, + rootOTP = VisaRootOTP(otp.toHexString()), + newCardDTO = CardDTO(card), + ) + } } - private suspend fun SessionContext.setupAccessCode(session: CardSession): CompletionResult { + private suspend fun SessionContext.setupAccessCode(): CompletionResult { val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) if (card.isAccessCodeSet) { diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCardActivationResponse.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCardActivationResponse.kt deleted file mode 100644 index 79cc4c9a95..0000000000 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCardActivationResponse.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.domain.visa.model - -data class VisaCardActivationResponse( - val signedActivationOrder: SignedActivationOrder, - val rootOTP: VisaRootOTP, -) \ No newline at end of file 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 22ab0da110..da668753a6 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 @@ -29,7 +29,6 @@ import com.tangem.features.onboarding.v2.visa.impl.model.OnboardingVisaModel import com.tangem.features.onboarding.v2.visa.impl.route.ONBOARDING_VISA_STEPS_COUNT import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute import com.tangem.features.onboarding.v2.visa.impl.route.screenTitle -import com.tangem.features.onboarding.v2.visa.impl.route.stepNum import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -44,10 +43,6 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( private val model: OnboardingVisaModel = getOrCreateModel(params) - private val innerNavigationState = instanceKeeper.getOrCreateSimple(key = "innerNavigationState") { - MutableStateFlow(OnboardingVisaInnerNavigationState(stackSize = model.initialStepNum)) - } - private val currentChildBackEventHandle = instanceKeeper.getOrCreateSimple(key = "currentChildBackEventHandle") { MutableSharedFlow() } @@ -57,12 +52,10 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( parentBackEvent = currentChildBackEventHandle, ) - private val stackNavigation = StackNavigation() - private val childStack: Value> = childStack( key = "innerStack", - source = stackNavigation, + source = model.stackNavigation, serializer = null, initialConfiguration = model.initialRoute, handleBackButton = true, @@ -75,7 +68,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( ) override val innerNavigation: InnerNavigation = object : InnerNavigation { - override val state: StateFlow = innerNavigationState + override val state: StateFlow = model.innerNavigationState override fun pop(onComplete: (Boolean) -> Unit) { if (childStack.value.active.configuration is OnboardingVisaRoute.AccessCode) { @@ -91,7 +84,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( childStack.observe(lifecycle) { stack -> val currentRoute = stack.active.configuration params.titleProvider.changeTitle(currentRoute.screenTitle()) - innerNavigationState.update { it.copy(stackSize = currentRoute.stepNum()) } + model.updateStepForNewRoute(currentRoute) } } @@ -106,74 +99,51 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( params = OnboardingVisaWelcomeComponent.Params( isWelcomeBack = route.isWelcomeBack, childParams = childParams, - onDone = { stackNavigation.push(OnboardingVisaRoute.AccessCode) }, + onDone = { model.navigateFromWelcome(route) }, ), ) OnboardingVisaRoute.AccessCode -> OnboardingVisaAccessCodeComponent( appComponentContext = factoryContext, params = OnboardingVisaAccessCodeComponent.Params( childParams = childParams, - onDone = { - stackNavigation.push( - if (it.walletFound) { - OnboardingVisaRoute.TangemWalletApproveOption( - visaDataForApprove = it.visaDataForApprove, - allowNavigateBack = false, - ) - } else { - OnboardingVisaRoute.ChooseWallet(it.visaDataForApprove) - }, - ) - }, + onDone = { model.navigateFromAccessCode(it) }, ), config = OnboardingVisaAccessCodeComponent.Config( - scanResponse = params.scanResponse, + scanResponse = model.currentScanResponse.value, ), ) is OnboardingVisaRoute.ChooseWallet -> OnboardingVisaChooseWalletComponent( appComponentContext = factoryContext, params = OnboardingVisaChooseWalletComponent.Params( childParams = childParams, - onEvent = { event -> - stackNavigation.push( - when (event) { - OnboardingVisaChooseWalletComponent.Params.Event.TangemWallet -> - OnboardingVisaRoute.TangemWalletApproveOption( - visaDataForApprove = route.visaDataForApprove, - allowNavigateBack = true, - ) - OnboardingVisaChooseWalletComponent.Params.Event.OtherWallet -> - OnboardingVisaRoute.OtherWalletApproveOption(route.visaDataForApprove) - }, - ) - }, + onEvent = { event -> model.navigateFromChooseWallet(route, event) }, ), ) OnboardingVisaRoute.InProgress -> OnboardingVisaInProgressComponent( appComponentContext = factoryContext, config = OnboardingVisaInProgressComponent.Config( - scanResponse = params.scanResponse, + scanResponse = model.currentScanResponse.value, ), params = OnboardingVisaInProgressComponent.Params( childParams = childParams, - onDone = { stackNavigation.push(OnboardingVisaRoute.PinCode) }, + onDone = { model.stackNavigation.push(OnboardingVisaRoute.PinCode) }, ), ) is OnboardingVisaRoute.OtherWalletApproveOption -> OnboardingVisaOtherWalletComponent( appComponentContext = factoryContext, config = OnboardingVisaOtherWalletComponent.Config( - scanResponse = params.scanResponse, + scanResponse = model.currentScanResponse.value, visaDataForApprove = route.visaDataForApprove, ), params = OnboardingVisaOtherWalletComponent.Params( childParams = childParams, - onDone = { stackNavigation.push(OnboardingVisaRoute.PinCode) }, + onDone = { model.stackNavigation.push(OnboardingVisaRoute.PinCode) }, ), ) OnboardingVisaRoute.PinCode -> OnboardingVisaPinCodeComponent( appComponentContext = factoryContext, config = OnboardingVisaPinCodeComponent.Config( - scanResponse = params.scanResponse, + scanResponse = model.currentScanResponse.value, ), params = OnboardingVisaPinCodeComponent.Params( childParams = childParams, @@ -187,36 +157,16 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( ), params = OnboardingVisaApproveComponent.Params( childParams = childParams, - onDone = { stackNavigation.push(OnboardingVisaRoute.InProgress) }, + onDone = { model.stackNavigation.push(OnboardingVisaRoute.InProgress) }, ), ) } } - private fun onChildBack() { - if (childStack.value.backStack.size == 1) { - // TODO show dialog - return - } - - when (val route = childStack.value.active.configuration) { - OnboardingVisaRoute.AccessCode, - is OnboardingVisaRoute.OtherWalletApproveOption, - -> stackNavigation.pop() - is OnboardingVisaRoute.TangemWalletApproveOption -> { - if (route.allowNavigateBack) { - stackNavigation.pop() - } - } - - is OnboardingVisaRoute.Welcome, - is OnboardingVisaRoute.ChooseWallet, - OnboardingVisaRoute.InProgress, - OnboardingVisaRoute.PinCode, - -> { - } - } - } + private fun onChildBack() = model.onChildBack( + currentRoute = childStack.value.active.configuration, + lastRoute = childStack.value.backStack.size == 1, + ) @Composable override fun Content(modifier: Modifier) { diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt index 6508b14557..5a8b17a5c2 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt @@ -60,5 +60,6 @@ internal class OnboardingVisaAccessCodeComponent( data class DoneEvent( val visaDataForApprove: VisaDataForApprove, val walletFound: Boolean, + val newScanResponse: ScanResponse, ) } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt index 01ed148a2f..5c78636e38 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt @@ -14,7 +14,10 @@ import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.OnboardingVi import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui.state.OnboardingVisaAccessCodeUM import com.tangem.sdk.api.TangemSdkManager import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @@ -146,6 +149,9 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor( approveHash = "48b55c482123a10ad9022f9f4c5dd95c", ), walletFound = false, // TODO + newScanResponse = params.scanResponse.copy( + card = result.data.newCardDTO, + ), ), ) } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt index 6db2601978..c65e7afaeb 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/ui/OnboardingVisaPinCode.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color.Companion.Transparent import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalSoftwareKeyboardController @@ -160,7 +159,7 @@ private fun PinCode( Box( modifier = Modifier .background( - color = Color(color = 0xFFF5F5F5), + color = TangemTheme.colors.field.primary, shape = TangemTheme.shapes.roundedCornersXMedium, ) .padding( 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 fab035bcb3..f1eab1d0d0 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,13 +1,23 @@ package com.tangem.features.onboarding.v2.visa.impl.model import androidx.compose.runtime.Stable +import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.decompose.router.stack.pop +import com.arkivanov.decompose.router.stack.push 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.VisaCardActivationStatus 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.accesscode.OnboardingVisaAccessCodeComponent +import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.OnboardingVisaChooseWalletComponent 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.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import javax.inject.Inject @Stable @@ -20,7 +30,107 @@ internal class OnboardingVisaModel @Inject constructor( @Suppress("UnusedPrivateMember") private val params = paramsContainer.require() - val initialRoute = OnboardingVisaRoute.Welcome(isWelcomeBack = false) + private val _currentScanResponse = MutableStateFlow(params.scanResponse) - val initialStepNum = initialRoute.stepNum() + val initialRoute = initializeRoute() + + val stackNavigation = StackNavigation() + + private val _innerNavigationState = + MutableStateFlow(OnboardingVisaInnerNavigationState(stackSize = initialRoute.stepNum())) + + val currentScanResponse = _currentScanResponse.asStateFlow() + + val innerNavigationState = _innerNavigationState.asStateFlow() + + fun updateStepForNewRoute(route: OnboardingVisaRoute) { + _innerNavigationState.value = innerNavigationState.value.copy( + stackSize = route.stepNum(), + ) + } + + 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") + + when (activationStatus.remoteState) { + VisaActivationRemoteState.CardWalletSignatureRequired, + VisaActivationRemoteState.CustomerWalletSignatureRequired, + -> OnboardingVisaRoute.AccessCode + VisaActivationRemoteState.WaitingPinCode -> OnboardingVisaRoute.PinCode + VisaActivationRemoteState.WaitingForActivationFinishing -> OnboardingVisaRoute.InProgress + VisaActivationRemoteState.PaymentAccountDeploying -> OnboardingVisaRoute.InProgress + else -> error("Remote state is not correct for welcome back route") + } + } else { + stackNavigation.push(OnboardingVisaRoute.AccessCode) + } + } + + fun navigateFromAccessCode(result: OnboardingVisaAccessCodeComponent.DoneEvent) { + _currentScanResponse.value = result.newScanResponse + stackNavigation.push( + if (result.walletFound) { + OnboardingVisaRoute.TangemWalletApproveOption( + visaDataForApprove = result.visaDataForApprove, + allowNavigateBack = false, + ) + } else { + OnboardingVisaRoute.ChooseWallet(result.visaDataForApprove) + }, + ) + } + + fun navigateFromChooseWallet( + route: OnboardingVisaRoute.ChooseWallet, + event: OnboardingVisaChooseWalletComponent.Params.Event, + ) { + stackNavigation.push( + when (event) { + OnboardingVisaChooseWalletComponent.Params.Event.TangemWallet -> + OnboardingVisaRoute.TangemWalletApproveOption( + visaDataForApprove = route.visaDataForApprove, + allowNavigateBack = true, + ) + OnboardingVisaChooseWalletComponent.Params.Event.OtherWallet -> + OnboardingVisaRoute.OtherWalletApproveOption(route.visaDataForApprove) + }, + ) + } + + fun onChildBack(currentRoute: OnboardingVisaRoute, lastRoute: Boolean) { + if (lastRoute) { + // TODO show dialog + return + } + + when (currentRoute) { + OnboardingVisaRoute.AccessCode, + is OnboardingVisaRoute.OtherWalletApproveOption, + -> stackNavigation.pop() + is OnboardingVisaRoute.TangemWalletApproveOption -> { + if (currentRoute.allowNavigateBack) { + stackNavigation.pop() + } + } + is OnboardingVisaRoute.Welcome, + is OnboardingVisaRoute.ChooseWallet, + OnboardingVisaRoute.InProgress, + OnboardingVisaRoute.PinCode, + -> { + } + } + } + + private fun initializeRoute(): OnboardingVisaRoute { + val activationStatus = params.scanResponse.visaCardActivationStatus + + return when (activationStatus) { + is VisaCardActivationStatus.ActivationStarted -> OnboardingVisaRoute.Welcome(isWelcomeBack = true) + is VisaCardActivationStatus.NotStartedActivation -> OnboardingVisaRoute.Welcome(isWelcomeBack = false) + else -> error("Visa activation status is not correct for onboarding flow") + } + } } \ No newline at end of file diff --git a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt index fd17f4da84..754f3ea551 100644 --- a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt +++ b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt @@ -17,13 +17,13 @@ import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.visa.model.VisaActivationInput import com.tangem.domain.visa.model.VisaAuthChallenge -import com.tangem.domain.visa.model.VisaCardActivationResponse import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.domain.wallets.models.UserWalletId import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.preflightread.PreflightReadFilter import com.tangem.operations.sign.SignHashResponse import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.sdk.api.visa.VisaCardActivationResponse @Suppress("TooManyFunctions") interface TangemSdkManager { diff --git a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/visa/VisaCardActivationResponse.kt b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/visa/VisaCardActivationResponse.kt new file mode 100644 index 0000000000..50da653b5e --- /dev/null +++ b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/visa/VisaCardActivationResponse.kt @@ -0,0 +1,11 @@ +package com.tangem.sdk.api.visa + +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.visa.model.SignedActivationOrder +import com.tangem.domain.visa.model.VisaRootOTP + +data class VisaCardActivationResponse( + val signedActivationOrder: SignedActivationOrder, + val rootOTP: VisaRootOTP, + val newCardDTO: CardDTO, +) \ No newline at end of file