diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 36cca67394..71831c5102 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -313,6 +313,16 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt
index 559a3bef90..13e28e17e8 100644
--- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt
+++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt
@@ -43,6 +43,7 @@ import com.tangem.features.tangempay.components.TangemPayHotWalletOnboardingComp
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.*
import com.tangem.features.tokendetails.TokenDetailsComponent
+import com.tangem.features.virtualaccount.onboarding.component.VirtualAccountOnboardingComponent
import com.tangem.features.wallet.WalletEntryComponent
import com.tangem.features.walletconnect.components.WalletConnectEntryComponent
import com.tangem.features.yield.supply.api.YieldSupplyEntryComponent
@@ -113,6 +114,7 @@ internal class ChildFactory @Inject constructor(
private val tangemPayDetailsContainerComponentFactory: TangemPayDetailsContainerComponent.Factory,
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
private val tangemPayWalletOnboardingComponentFactory: TangemPayHotWalletOnboardingComponent.Factory,
+ private val virtualAccountOnboardingComponentFactory: VirtualAccountOnboardingComponent.Factory,
private val kycComponentFactory: KycComponent.Factory,
private val surveyComponentFactory: SurveyComponent.Factory,
private val yieldSupplyEntryComponentFactory: YieldSupplyEntryComponent.Factory,
@@ -696,6 +698,23 @@ internal class ChildFactory @Inject constructor(
componentFactory = tangemPayWalletOnboardingComponentFactory,
)
}
+ is AppRoute.VirtualAccountOnboarding -> {
+ createComponentChild(
+ context = context,
+ params = when (val mode = route.mode) {
+ is AppRoute.VirtualAccountOnboarding.Mode.Deeplink ->
+ VirtualAccountOnboardingComponent.Params.Deeplink(
+ userWalletId = mode.userWalletId,
+ deeplink = mode.deeplink,
+ )
+ is AppRoute.VirtualAccountOnboarding.Mode.FromMain ->
+ VirtualAccountOnboardingComponent.Params.FromMain(userWalletId = mode.userWalletId)
+ is AppRoute.VirtualAccountOnboarding.Mode.FromDetailsScreen ->
+ VirtualAccountOnboardingComponent.Params.FromDetailsScreen(userWalletId = mode.userWalletId)
+ },
+ componentFactory = virtualAccountOnboardingComponentFactory,
+ )
+ }
is AppRoute.Kyc -> {
createComponentChild(
context = context,
diff --git a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt
index 378e230a37..c30d377259 100644
--- a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt
+++ b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt
@@ -21,6 +21,7 @@ import com.tangem.features.send.api.deeplink.SellRedirectDeepLinkHandler
import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler
import com.tangem.features.survey.deeplink.SurveyDeepLinkHandler
import com.tangem.features.tangempay.deeplink.OnboardVisaDeepLinkHandler
+import com.tangem.features.virtualaccount.onboarding.deeplink.OnboardVirtualAccountsDeepLinkHandler
import com.tangem.features.tangempay.deeplink.TangemPayMainDeepLinkHandler
import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler
import com.tangem.features.wallet.deeplink.PromoDeeplinkHandler
@@ -57,6 +58,7 @@ internal class DeepLinkFactory @Inject constructor(
private val swapDeepLink: SwapDeepLinkHandler.Factory,
private val promoDeepLink: PromoDeeplinkHandler.Factory,
private val onboardVisaDeepLink: OnboardVisaDeepLinkHandler.Factory,
+ private val onboardVirtualAccountsDeepLink: OnboardVirtualAccountsDeepLinkHandler.Factory,
private val marketsTokenExchangesDeepLink: MarketsTokenExchangesDeepLinkHandler.Factory,
private val tangemPayMainDeepLink: TangemPayMainDeepLinkHandler.Factory,
private val newsDetailsDeepLink: NewsDetailsDeepLinkHandler.Factory,
@@ -173,6 +175,7 @@ internal class DeepLinkFactory @Inject constructor(
DeepLinkRoute.WalletConnect.host -> walletConnectDeepLink.create(deeplinkUri)
DeepLinkRoute.Promo.host -> promoDeepLink.create(coroutineScope, queryParams)
DeepLinkRoute.OnboardVisa.host -> onboardVisaDeepLink.create(deeplinkUri)
+ DeepLinkRoute.OnboardVirtualAccounts.host -> onboardVirtualAccountsDeepLink.create(deeplinkUri)
DeepLinkRoute.News.host -> newsDeepLink.create(queryParams)
DeepLinkRoute.Earn.host -> earnDeepLink.create(queryParams)
DeepLinkRoute.Yield.host -> yieldDeepLink.create(coroutineScope, queryParams)
diff --git a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt
index c6cfb8973c..c6744e1b77 100644
--- a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt
+++ b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt
@@ -19,6 +19,7 @@ import com.tangem.features.onramp.deeplink.SwapDeepLinkHandler
import com.tangem.features.send.api.deeplink.SellRedirectDeepLinkHandler
import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler
import com.tangem.features.tangempay.deeplink.OnboardVisaDeepLinkHandler
+import com.tangem.features.virtualaccount.onboarding.deeplink.OnboardVirtualAccountsDeepLinkHandler
import com.tangem.features.tangempay.deeplink.TangemPayMainDeepLinkHandler
import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler
import com.tangem.features.wallet.deeplink.PromoDeeplinkHandler
@@ -84,6 +85,10 @@ class DeepLinkFactoryTest {
every { create(any()) } returns mockk()
}
+ private val onboardVirtualAccountsDeepLink = mockk(relaxed = true) {
+ every { create(any()) } returns mockk()
+ }
+
private val tangemPayMainDeepLink = mockk(relaxed = true) {
every { create(any(), any()) } returns mockk()
}
@@ -140,6 +145,7 @@ class DeepLinkFactoryTest {
swapDeepLink = swapDeepLinkFactory,
promoDeepLink = promoDeepLinkFactory,
onboardVisaDeepLink = onboardVisaDeepLink,
+ onboardVirtualAccountsDeepLink = onboardVirtualAccountsDeepLink,
tangemPayMainDeepLink = tangemPayMainDeepLink,
newsDetailsDeepLink = newsDeeplink,
newsDeepLink = newsDeepLinkFactory,
diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt
index 90449eab97..93c4629c98 100644
--- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt
+++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt
@@ -511,6 +511,24 @@ sealed class AppRoute(val path: String) : Route {
@Serializable
data class Kyc(val userWalletId: UserWalletId) : AppRoute(path = "/kyc")
+ @Serializable
+ data class VirtualAccountOnboarding(
+ val mode: Mode,
+ ) : AppRoute(path = "/virtual_account_onboarding/$mode") {
+
+ @Serializable
+ sealed class Mode {
+ @Serializable
+ data class Deeplink(val userWalletId: UserWalletId, val deeplink: String) : Mode()
+
+ @Serializable
+ data class FromMain(val userWalletId: UserWalletId) : Mode()
+
+ @Serializable
+ data class FromDetailsScreen(val userWalletId: UserWalletId) : Mode()
+ }
+ }
+
@Serializable
data class Survey(val token: String, val displayId: String? = null) : AppRoute(path = "/survey")
diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt
index e2e31a626c..9abdcf8b09 100644
--- a/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt
+++ b/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt
@@ -68,6 +68,10 @@ sealed class DeepLinkRoute {
override val host: String = "onboard-visa"
}
+ data object OnboardVirtualAccounts : DeepLinkRoute() {
+ override val host: String = "onboard-virtual-account"
+ }
+
data object PayApp : DeepLinkRoute() {
override val host: String = "tangem.com"
}
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt
index da654f8d16..17de6c4cd0 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt
@@ -35,7 +35,17 @@ data class CustomerMeResponse(
@Json(name = "display_name") val displayName: String?,
@Json(name = "actual_card_limit") val actualCardLimit: CardLimit?,
@Json(name = "admin_card_limit") val adminCardLimit: CardLimit?,
+ @Json(name = "product_specification_data_type") val specificationDataType: SpecificationDataType,
) {
+ @JsonClass(generateAdapter = false)
+ enum class SpecificationDataType {
+ @Json(name = "ACCOUNT")
+ ACCOUNT,
+
+ @Json(name = "CARD")
+ CARD,
+ }
+
@JsonClass(generateAdapter = false)
enum class Status {
@Json(name = "NEW")
diff --git a/features/virtual-accounts/onboarding/api/build.gradle.kts b/features/virtual-accounts/onboarding/api/build.gradle.kts
index bd895bec0a..a409f095d3 100644
--- a/features/virtual-accounts/onboarding/api/build.gradle.kts
+++ b/features/virtual-accounts/onboarding/api/build.gradle.kts
@@ -9,4 +9,13 @@ android {
}
dependencies {
+ /** Core */
+ implementation(projects.core.decompose)
+ implementation(projects.core.ui)
+
+ /** Domain */
+ implementation(projects.domain.models)
+
+ /** Compose */
+ implementation(deps.compose.runtime)
}
\ No newline at end of file
diff --git a/features/virtual-accounts/onboarding/api/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/component/VirtualAccountOnboardingComponent.kt b/features/virtual-accounts/onboarding/api/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/component/VirtualAccountOnboardingComponent.kt
new file mode 100644
index 0000000000..5aac13f9ca
--- /dev/null
+++ b/features/virtual-accounts/onboarding/api/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/component/VirtualAccountOnboardingComponent.kt
@@ -0,0 +1,21 @@
+package com.tangem.features.virtualaccount.onboarding.component
+
+import com.tangem.core.decompose.factory.ComponentFactory
+import com.tangem.core.ui.decompose.ComposableContentComponent
+import com.tangem.domain.models.wallet.UserWalletId
+
+interface VirtualAccountOnboardingComponent : ComposableContentComponent {
+
+ sealed class Params {
+
+ abstract val userWalletId: UserWalletId
+
+ data class Deeplink(override val userWalletId: UserWalletId, val deeplink: String) : Params()
+
+ data class FromMain(override val userWalletId: UserWalletId) : Params()
+
+ data class FromDetailsScreen(override val userWalletId: UserWalletId) : Params()
+ }
+
+ interface Factory : ComponentFactory
+}
\ No newline at end of file
diff --git a/features/virtual-accounts/onboarding/api/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/deeplink/OnboardVirtualAccountsDeepLinkHandler.kt b/features/virtual-accounts/onboarding/api/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/deeplink/OnboardVirtualAccountsDeepLinkHandler.kt
new file mode 100644
index 0000000000..62350c86f4
--- /dev/null
+++ b/features/virtual-accounts/onboarding/api/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/deeplink/OnboardVirtualAccountsDeepLinkHandler.kt
@@ -0,0 +1,10 @@
+package com.tangem.features.virtualaccount.onboarding.deeplink
+
+import android.net.Uri
+
+interface OnboardVirtualAccountsDeepLinkHandler {
+
+ interface Factory {
+ fun create(uri: Uri): OnboardVirtualAccountsDeepLinkHandler
+ }
+}
\ No newline at end of file
diff --git a/features/virtual-accounts/onboarding/impl/build.gradle.kts b/features/virtual-accounts/onboarding/impl/build.gradle.kts
index b187abb29a..8ea2dc7f4d 100644
--- a/features/virtual-accounts/onboarding/impl/build.gradle.kts
+++ b/features/virtual-accounts/onboarding/impl/build.gradle.kts
@@ -11,11 +11,23 @@ android {
}
dependencies {
+ /** Core */
+ implementation(projects.core.decompose)
+ implementation(projects.core.error)
+ implementation(projects.core.ui)
+ implementation(projects.core.utils)
+
+ /** Common */
+ implementation(projects.common.routing)
+ implementation(projects.common.ui)
+
/** Api */
implementation(projects.features.virtualAccounts.onboarding.api)
- /** Core modules */
- implementation(projects.core.configToggles)
+ /** Domain */
+ implementation(projects.domain.common)
+ implementation(projects.domain.models)
+ implementation(projects.domain.visa)
/** Compose */
implementation(deps.compose.foundation)
@@ -27,4 +39,7 @@ dependencies {
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
+
+ /** Other */
+ implementation(deps.arrow.core)
}
\ No newline at end of file
diff --git a/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/component/DefaultVirtualAccountOnboardingComponent.kt b/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/component/DefaultVirtualAccountOnboardingComponent.kt
new file mode 100644
index 0000000000..253935756f
--- /dev/null
+++ b/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/component/DefaultVirtualAccountOnboardingComponent.kt
@@ -0,0 +1,35 @@
+package com.tangem.features.virtualaccount.onboarding.component
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.tangem.core.decompose.context.AppComponentContext
+import com.tangem.core.decompose.model.getOrCreateModel
+import com.tangem.features.virtualaccount.onboarding.model.VirtualAccountOnboardingModel
+import com.tangem.features.virtualaccount.onboarding.ui.VirtualAccountOnboardingScreen
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+internal class DefaultVirtualAccountOnboardingComponent @AssistedInject constructor(
+ @Assisted appComponentContext: AppComponentContext,
+ @Assisted params: VirtualAccountOnboardingComponent.Params,
+) : VirtualAccountOnboardingComponent, AppComponentContext by appComponentContext {
+
+ private val model: VirtualAccountOnboardingModel = getOrCreateModel(params)
+
+ @Composable
+ override fun Content(modifier: Modifier) {
+ val state by model.uiState.collectAsStateWithLifecycle()
+ VirtualAccountOnboardingScreen(modifier = modifier, state = state)
+ }
+
+ @AssistedFactory
+ interface Factory : VirtualAccountOnboardingComponent.Factory {
+ override fun create(
+ context: AppComponentContext,
+ params: VirtualAccountOnboardingComponent.Params,
+ ): DefaultVirtualAccountOnboardingComponent
+ }
+}
\ No newline at end of file
diff --git a/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/deeplink/DefaultOnboardVirtualAccountsDeepLinkHandler.kt b/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/deeplink/DefaultOnboardVirtualAccountsDeepLinkHandler.kt
new file mode 100644
index 0000000000..87dcb0729b
--- /dev/null
+++ b/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/deeplink/DefaultOnboardVirtualAccountsDeepLinkHandler.kt
@@ -0,0 +1,35 @@
+package com.tangem.features.virtualaccount.onboarding.deeplink
+
+import android.net.Uri
+import com.tangem.common.routing.AppRoute
+import com.tangem.common.routing.AppRouter
+import com.tangem.domain.common.wallets.UserWalletsListRepository
+import com.tangem.utils.logging.TangemLogger
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+internal class DefaultOnboardVirtualAccountsDeepLinkHandler @AssistedInject constructor(
+ @Assisted uri: Uri,
+ appRouter: AppRouter,
+ userWalletsListRepository: UserWalletsListRepository,
+) : OnboardVirtualAccountsDeepLinkHandler {
+
+ init {
+ val userWalletId = userWalletsListRepository.selectedUserWallet.value?.walletId
+ if (userWalletId == null) {
+ TangemLogger.e("Can not open virtual account onboarding deeplink: no selected wallet")
+ } else {
+ val mode = AppRoute.VirtualAccountOnboarding.Mode.Deeplink(
+ userWalletId = userWalletId,
+ deeplink = uri.toString(),
+ )
+ appRouter.push(AppRoute.VirtualAccountOnboarding(mode))
+ }
+ }
+
+ @AssistedFactory
+ interface Factory : OnboardVirtualAccountsDeepLinkHandler.Factory {
+ override fun create(uri: Uri): DefaultOnboardVirtualAccountsDeepLinkHandler
+ }
+}
\ No newline at end of file
diff --git a/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/di/VirtualAccountOnboardingFeatureModule.kt b/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/di/VirtualAccountOnboardingFeatureModule.kt
new file mode 100644
index 0000000000..3d1c743658
--- /dev/null
+++ b/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/di/VirtualAccountOnboardingFeatureModule.kt
@@ -0,0 +1,25 @@
+package com.tangem.features.virtualaccount.onboarding.di
+
+import com.tangem.features.virtualaccount.onboarding.component.DefaultVirtualAccountOnboardingComponent
+import com.tangem.features.virtualaccount.onboarding.component.VirtualAccountOnboardingComponent
+import com.tangem.features.virtualaccount.onboarding.deeplink.DefaultOnboardVirtualAccountsDeepLinkHandler
+import com.tangem.features.virtualaccount.onboarding.deeplink.OnboardVirtualAccountsDeepLinkHandler
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+internal interface VirtualAccountOnboardingFeatureModule {
+
+ @Binds
+ fun bindFactory(impl: DefaultVirtualAccountOnboardingComponent.Factory): VirtualAccountOnboardingComponent.Factory
+
+ @Binds
+ @Singleton
+ fun bindOnboardVirtualAccountsDeepLinkHandlerFactory(
+ impl: DefaultOnboardVirtualAccountsDeepLinkHandler.Factory,
+ ): OnboardVirtualAccountsDeepLinkHandler.Factory
+}
\ No newline at end of file
diff --git a/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/di/VirtualAccountOnboardingModelsModule.kt b/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/di/VirtualAccountOnboardingModelsModule.kt
new file mode 100644
index 0000000000..e14040c3ea
--- /dev/null
+++ b/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/di/VirtualAccountOnboardingModelsModule.kt
@@ -0,0 +1,20 @@
+package com.tangem.features.virtualaccount.onboarding.di
+
+import com.tangem.core.decompose.di.ModelComponent
+import com.tangem.core.decompose.model.Model
+import com.tangem.features.virtualaccount.onboarding.model.VirtualAccountOnboardingModel
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+@InstallIn(ModelComponent::class)
+internal interface VirtualAccountOnboardingModelsModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(VirtualAccountOnboardingModel::class)
+ fun bindVirtualAccountOnboardingModel(model: VirtualAccountOnboardingModel): Model
+}
\ No newline at end of file
diff --git a/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/model/VirtualAccountOnboardingModel.kt b/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/model/VirtualAccountOnboardingModel.kt
new file mode 100644
index 0000000000..9c30c6911e
--- /dev/null
+++ b/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/model/VirtualAccountOnboardingModel.kt
@@ -0,0 +1,96 @@
+package com.tangem.features.virtualaccount.onboarding.model
+
+import androidx.compose.runtime.Stable
+import com.tangem.core.decompose.di.ModelScoped
+import com.tangem.core.decompose.model.Model
+import com.tangem.core.decompose.model.ParamsContainer
+import com.tangem.core.decompose.navigation.Router
+import com.tangem.domain.pay.repository.OnboardingRepository
+import com.tangem.features.virtualaccount.onboarding.component.VirtualAccountOnboardingComponent
+import com.tangem.features.virtualaccount.onboarding.ui.VirtualAccountOnboardingUM
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@Stable
+@ModelScoped
+internal class VirtualAccountOnboardingModel @Inject constructor(
+ paramsContainer: ParamsContainer,
+ override val dispatchers: CoroutineDispatcherProvider,
+ private val router: Router,
+ private val onboardingRepository: OnboardingRepository,
+) : Model() {
+
+ private val params = paramsContainer.require()
+
+ val uiState: StateFlow
+ field = MutableStateFlow(VirtualAccountOnboardingUM.Loading(onBack = ::back))
+
+ init {
+ when (params) {
+ is VirtualAccountOnboardingComponent.Params.Deeplink -> validateDeeplinkAndShow(params.deeplink)
+ is VirtualAccountOnboardingComponent.Params.FromMain,
+ is VirtualAccountOnboardingComponent.Params.FromDetailsScreen,
+ -> showOnboarding()
+ }
+ }
+
+ private fun validateDeeplinkAndShow(deeplink: String) {
+ modelScope.launch {
+ onboardingRepository.validateDeeplink(deeplink)
+ .onRight { isValid -> if (isValid) showOnboarding() else back() }
+ .onLeft { back() }
+ }
+ }
+
+ private fun showOnboarding() {
+ uiState.update {
+ VirtualAccountOnboardingUM.Content(
+ onBack = ::back,
+ isLoading = false,
+ onGetCardClick = ::onGetCardClick,
+ onTermsClick = ::onTermsClick,
+ onPrivacyClick = ::onPrivacyClick,
+ )
+ }
+ }
+
+ private fun onTermsClick() {
+ // TODO([REDACTED_TASK_KEY]): open the provider Terms of Use link.
+ }
+
+ private fun onPrivacyClick() {
+ // TODO([REDACTED_TASK_KEY]): open the provider Privacy Policy link.
+ }
+
+ private fun onGetCardClick() {
+ modelScope.launch {
+ setLoading(isLoading = true)
+ delay(STUB_GET_CARD_DELAY_MS)
+ // TODO: create order and sign challenge [REDACTED_JIRA]
+ setLoading(isLoading = false)
+ }
+ }
+
+ private fun setLoading(isLoading: Boolean) {
+ uiState.update { state ->
+ when (state) {
+ is VirtualAccountOnboardingUM.Content -> state.copy(isLoading = isLoading)
+ is VirtualAccountOnboardingUM.Loading -> state
+ }
+ }
+ }
+
+ private fun back() {
+ router.pop()
+ }
+
+ private companion object {
+ // TODO([REDACTED_TASK_KEY]): remove the stub delay once create-order + sign-challenge is implemented.
+ const val STUB_GET_CARD_DELAY_MS = 3000L
+ }
+}
\ No newline at end of file
diff --git a/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/ui/VirtualAccountOnboardingScreen.kt b/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/ui/VirtualAccountOnboardingScreen.kt
new file mode 100644
index 0000000000..537d302e29
--- /dev/null
+++ b/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/ui/VirtualAccountOnboardingScreen.kt
@@ -0,0 +1,213 @@
+package com.tangem.features.virtualaccount.onboarding.ui
+
+import android.content.res.Configuration
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.LinkAnnotation
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.withLink
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
+import androidx.compose.ui.unit.dp
+import com.tangem.core.ui.ds.image.TangemIconUM
+import com.tangem.core.ui.ds2.button.Close
+import com.tangem.core.ui.ds2.button.TangemButton
+import com.tangem.core.ui.extensions.annotatedReference
+import com.tangem.core.ui.extensions.appendColored
+import com.tangem.core.ui.extensions.resolveAnnotatedReference
+import com.tangem.core.ui.extensions.stringReference
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreviewRedesign
+import com.tangem.features.virtualaccount.onboarding.impl.R
+
+private const val GRADIENT_TRANSPARENT_STOP = 0.45f
+private const val GRADIENT_OPAQUE_STOP = 0.72f
+
+private const val TERMS_LINK_TAG = "VA_TERMS"
+private const val PRIVACY_LINK_TAG = "VA_PRIVACY"
+
+@Composable
+internal fun VirtualAccountOnboardingScreen(state: VirtualAccountOnboardingUM, modifier: Modifier = Modifier) {
+ Box(
+ modifier = modifier
+ .fillMaxSize()
+ .background(TangemTheme.colors3.bg.primary),
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.bg_virtual_account_onboarding),
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ alignment = Alignment.TopCenter,
+ modifier = Modifier.fillMaxSize(),
+ )
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(
+ Brush.verticalGradient(
+ colorStops = arrayOf(
+ 0f to TangemTheme.colors3.bg.primary.copy(alpha = 0f),
+ GRADIENT_TRANSPARENT_STOP to TangemTheme.colors3.bg.primary.copy(alpha = 0f),
+ GRADIENT_OPAQUE_STOP to TangemTheme.colors3.bg.primary,
+ 1f to TangemTheme.colors3.bg.primary,
+ ),
+ ),
+ ),
+ )
+
+ when (state) {
+ is VirtualAccountOnboardingUM.Loading -> Loading(modifier = Modifier.fillMaxSize())
+ is VirtualAccountOnboardingUM.Content -> Content(state = state)
+ }
+
+ TangemButton.Close(
+ modifier = Modifier
+ .align(Alignment.TopEnd)
+ .statusBarsPadding()
+ .padding(top = 4.dp, end = 16.dp),
+ onClick = state.onBack,
+ )
+ }
+}
+
+@Composable
+private fun Loading(modifier: Modifier = Modifier) {
+ Box(modifier = modifier, contentAlignment = Alignment.Center) {
+ CircularProgressIndicator(color = TangemTheme.colors3.icon.primary)
+ }
+}
+
+@Composable
+private fun Content(state: VirtualAccountOnboardingUM.Content, modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .systemBarsPadding(),
+ ) {
+ Spacer(modifier = Modifier.weight(1f))
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 24.dp),
+ verticalArrangement = Arrangement.spacedBy(12.dp),
+ ) {
+ Text(
+ text = "Send USD from your bank. Receive USDC",
+ style = TangemTheme.typography3.heading.medium,
+ color = TangemTheme.colors3.text.primary,
+ )
+ Text(
+ text = "A dedicated account with US banking details — no deposit or maintenance fees",
+ style = TangemTheme.typography3.subheading.medium,
+ color = TangemTheme.colors3.text.secondary,
+ )
+ }
+
+ TermsCard(
+ modifier = Modifier.padding(top = 24.dp, start = 8.dp, end = 8.dp),
+ state = state,
+ )
+ }
+}
+
+@Composable
+private fun TermsCard(state: VirtualAccountOnboardingUM.Content, modifier: Modifier = Modifier) {
+ val shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp, bottomStart = 28.dp, bottomEnd = 28.dp)
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .clip(shape)
+ .background(TangemTheme.colors3.bg.opaque.primary)
+ .border(width = 1.dp, color = TangemTheme.colors3.border.secondary, shape = shape),
+ ) {
+ Text(
+ modifier = Modifier.padding(top = 12.dp, start = 16.dp, end = 16.dp),
+ text = buildTermsAndPolicy(
+ onTermsClick = state.onTermsClick,
+ onPrivacyClick = state.onPrivacyClick,
+ ).resolveAnnotatedReference(),
+ style = TangemTheme.typography3.caption.medium,
+ color = TangemTheme.colors3.text.secondary,
+ textAlign = TextAlign.Center,
+ )
+ TangemButton(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 8.dp, vertical = 12.dp),
+ variant = TangemButton.Variant.Primary,
+ size = TangemButton.Size.X12,
+ text = stringReference("Open account"),
+ iconEnd = TangemIconUM.Icon(R.drawable.ic_tangem_24),
+ isLoading = state.isLoading,
+ onClick = state.onGetCardClick,
+ )
+ }
+}
+
+@Composable
+private fun buildTermsAndPolicy(onTermsClick: () -> Unit, onPrivacyClick: () -> Unit) = annotatedReference {
+ val linkColor = TangemTheme.colors3.text.primary
+ append("By using service, you agree with provider ")
+ withLink(
+ link = LinkAnnotation.Clickable(
+ tag = TERMS_LINK_TAG,
+ linkInteractionListener = { onTermsClick() },
+ ),
+ block = { appendColored(text = "Terms of Use", color = linkColor) },
+ )
+ append(" and ")
+ withLink(
+ link = LinkAnnotation.Clickable(
+ tag = PRIVACY_LINK_TAG,
+ linkInteractionListener = { onPrivacyClick() },
+ ),
+ block = { appendColored(text = "Privacy Policy", color = linkColor) },
+ )
+}
+
+@Preview(showBackground = true)
+@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun VirtualAccountOnboardingScreenPreview(
+ @PreviewParameter(VirtualAccountOnboardingStateProvider::class)
+ state: VirtualAccountOnboardingUM,
+) {
+ TangemThemePreviewRedesign {
+ VirtualAccountOnboardingScreen(state = state, modifier = Modifier.fillMaxSize())
+ }
+}
+
+private class VirtualAccountOnboardingStateProvider :
+ CollectionPreviewParameterProvider(
+ listOf(
+ VirtualAccountOnboardingUM.Loading(onBack = {}),
+ VirtualAccountOnboardingUM.Content(
+ onBack = {},
+ isLoading = false,
+ onGetCardClick = {},
+ onTermsClick = {},
+ onPrivacyClick = {},
+ ),
+ VirtualAccountOnboardingUM.Content(
+ onBack = {},
+ isLoading = true,
+ onGetCardClick = {},
+ onTermsClick = {},
+ onPrivacyClick = {},
+ ),
+ ),
+ )
\ No newline at end of file
diff --git a/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/ui/VirtualAccountOnboardingUM.kt b/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/ui/VirtualAccountOnboardingUM.kt
new file mode 100644
index 0000000000..c5ab5b0a33
--- /dev/null
+++ b/features/virtual-accounts/onboarding/impl/src/main/kotlin/com/tangem/features/virtualaccount/onboarding/ui/VirtualAccountOnboardingUM.kt
@@ -0,0 +1,22 @@
+package com.tangem.features.virtualaccount.onboarding.ui
+
+import androidx.compose.runtime.Immutable
+
+/**
+ * UI model for the Virtual Account onboarding screen.
+ */
+@Immutable
+internal sealed class VirtualAccountOnboardingUM {
+
+ abstract val onBack: () -> Unit
+
+ data class Loading(override val onBack: () -> Unit) : VirtualAccountOnboardingUM()
+
+ data class Content(
+ override val onBack: () -> Unit,
+ val isLoading: Boolean,
+ val onGetCardClick: () -> Unit,
+ val onTermsClick: () -> Unit,
+ val onPrivacyClick: () -> Unit,
+ ) : VirtualAccountOnboardingUM()
+}
\ No newline at end of file
diff --git a/features/virtual-accounts/onboarding/impl/src/main/res/drawable/bg_virtual_account_onboarding.webp b/features/virtual-accounts/onboarding/impl/src/main/res/drawable/bg_virtual_account_onboarding.webp
new file mode 100644
index 0000000000..40030c0fd9
Binary files /dev/null and b/features/virtual-accounts/onboarding/impl/src/main/res/drawable/bg_virtual_account_onboarding.webp differ