Updated on 2026-08-14
This commit is contained in:
parent
f84d6a1261
commit
d9cf8d5aef
19 changed files with 573 additions and 2 deletions
|
|
@ -313,6 +313,16 @@
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="onboard-virtual-account"
|
||||||
|
android:scheme="tangem" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data
|
||||||
android:host="news"
|
android:host="news"
|
||||||
android:scheme="tangem" />
|
android:scheme="tangem" />
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.*
|
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.*
|
||||||
import com.tangem.features.tokendetails.TokenDetailsComponent
|
import com.tangem.features.tokendetails.TokenDetailsComponent
|
||||||
|
import com.tangem.features.virtualaccount.onboarding.component.VirtualAccountOnboardingComponent
|
||||||
import com.tangem.features.wallet.WalletEntryComponent
|
import com.tangem.features.wallet.WalletEntryComponent
|
||||||
import com.tangem.features.walletconnect.components.WalletConnectEntryComponent
|
import com.tangem.features.walletconnect.components.WalletConnectEntryComponent
|
||||||
import com.tangem.features.yield.supply.api.YieldSupplyEntryComponent
|
import com.tangem.features.yield.supply.api.YieldSupplyEntryComponent
|
||||||
|
|
@ -113,6 +114,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
private val tangemPayDetailsContainerComponentFactory: TangemPayDetailsContainerComponent.Factory,
|
private val tangemPayDetailsContainerComponentFactory: TangemPayDetailsContainerComponent.Factory,
|
||||||
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
|
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
|
||||||
private val tangemPayWalletOnboardingComponentFactory: TangemPayHotWalletOnboardingComponent.Factory,
|
private val tangemPayWalletOnboardingComponentFactory: TangemPayHotWalletOnboardingComponent.Factory,
|
||||||
|
private val virtualAccountOnboardingComponentFactory: VirtualAccountOnboardingComponent.Factory,
|
||||||
private val kycComponentFactory: KycComponent.Factory,
|
private val kycComponentFactory: KycComponent.Factory,
|
||||||
private val surveyComponentFactory: SurveyComponent.Factory,
|
private val surveyComponentFactory: SurveyComponent.Factory,
|
||||||
private val yieldSupplyEntryComponentFactory: YieldSupplyEntryComponent.Factory,
|
private val yieldSupplyEntryComponentFactory: YieldSupplyEntryComponent.Factory,
|
||||||
|
|
@ -696,6 +698,23 @@ internal class ChildFactory @Inject constructor(
|
||||||
componentFactory = tangemPayWalletOnboardingComponentFactory,
|
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 -> {
|
is AppRoute.Kyc -> {
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import com.tangem.features.send.api.deeplink.SellRedirectDeepLinkHandler
|
||||||
import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler
|
import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler
|
||||||
import com.tangem.features.survey.deeplink.SurveyDeepLinkHandler
|
import com.tangem.features.survey.deeplink.SurveyDeepLinkHandler
|
||||||
import com.tangem.features.tangempay.deeplink.OnboardVisaDeepLinkHandler
|
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.tangempay.deeplink.TangemPayMainDeepLinkHandler
|
||||||
import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler
|
import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler
|
||||||
import com.tangem.features.wallet.deeplink.PromoDeeplinkHandler
|
import com.tangem.features.wallet.deeplink.PromoDeeplinkHandler
|
||||||
|
|
@ -57,6 +58,7 @@ internal class DeepLinkFactory @Inject constructor(
|
||||||
private val swapDeepLink: SwapDeepLinkHandler.Factory,
|
private val swapDeepLink: SwapDeepLinkHandler.Factory,
|
||||||
private val promoDeepLink: PromoDeeplinkHandler.Factory,
|
private val promoDeepLink: PromoDeeplinkHandler.Factory,
|
||||||
private val onboardVisaDeepLink: OnboardVisaDeepLinkHandler.Factory,
|
private val onboardVisaDeepLink: OnboardVisaDeepLinkHandler.Factory,
|
||||||
|
private val onboardVirtualAccountsDeepLink: OnboardVirtualAccountsDeepLinkHandler.Factory,
|
||||||
private val marketsTokenExchangesDeepLink: MarketsTokenExchangesDeepLinkHandler.Factory,
|
private val marketsTokenExchangesDeepLink: MarketsTokenExchangesDeepLinkHandler.Factory,
|
||||||
private val tangemPayMainDeepLink: TangemPayMainDeepLinkHandler.Factory,
|
private val tangemPayMainDeepLink: TangemPayMainDeepLinkHandler.Factory,
|
||||||
private val newsDetailsDeepLink: NewsDetailsDeepLinkHandler.Factory,
|
private val newsDetailsDeepLink: NewsDetailsDeepLinkHandler.Factory,
|
||||||
|
|
@ -173,6 +175,7 @@ internal class DeepLinkFactory @Inject constructor(
|
||||||
DeepLinkRoute.WalletConnect.host -> walletConnectDeepLink.create(deeplinkUri)
|
DeepLinkRoute.WalletConnect.host -> walletConnectDeepLink.create(deeplinkUri)
|
||||||
DeepLinkRoute.Promo.host -> promoDeepLink.create(coroutineScope, queryParams)
|
DeepLinkRoute.Promo.host -> promoDeepLink.create(coroutineScope, queryParams)
|
||||||
DeepLinkRoute.OnboardVisa.host -> onboardVisaDeepLink.create(deeplinkUri)
|
DeepLinkRoute.OnboardVisa.host -> onboardVisaDeepLink.create(deeplinkUri)
|
||||||
|
DeepLinkRoute.OnboardVirtualAccounts.host -> onboardVirtualAccountsDeepLink.create(deeplinkUri)
|
||||||
DeepLinkRoute.News.host -> newsDeepLink.create(queryParams)
|
DeepLinkRoute.News.host -> newsDeepLink.create(queryParams)
|
||||||
DeepLinkRoute.Earn.host -> earnDeepLink.create(queryParams)
|
DeepLinkRoute.Earn.host -> earnDeepLink.create(queryParams)
|
||||||
DeepLinkRoute.Yield.host -> yieldDeepLink.create(coroutineScope, queryParams)
|
DeepLinkRoute.Yield.host -> yieldDeepLink.create(coroutineScope, queryParams)
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import com.tangem.features.onramp.deeplink.SwapDeepLinkHandler
|
||||||
import com.tangem.features.send.api.deeplink.SellRedirectDeepLinkHandler
|
import com.tangem.features.send.api.deeplink.SellRedirectDeepLinkHandler
|
||||||
import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler
|
import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler
|
||||||
import com.tangem.features.tangempay.deeplink.OnboardVisaDeepLinkHandler
|
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.tangempay.deeplink.TangemPayMainDeepLinkHandler
|
||||||
import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler
|
import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler
|
||||||
import com.tangem.features.wallet.deeplink.PromoDeeplinkHandler
|
import com.tangem.features.wallet.deeplink.PromoDeeplinkHandler
|
||||||
|
|
@ -84,6 +85,10 @@ class DeepLinkFactoryTest {
|
||||||
every { create(any()) } returns mockk()
|
every { create(any()) } returns mockk()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val onboardVirtualAccountsDeepLink = mockk<OnboardVirtualAccountsDeepLinkHandler.Factory>(relaxed = true) {
|
||||||
|
every { create(any()) } returns mockk()
|
||||||
|
}
|
||||||
|
|
||||||
private val tangemPayMainDeepLink = mockk<TangemPayMainDeepLinkHandler.Factory>(relaxed = true) {
|
private val tangemPayMainDeepLink = mockk<TangemPayMainDeepLinkHandler.Factory>(relaxed = true) {
|
||||||
every { create(any(), any()) } returns mockk()
|
every { create(any(), any()) } returns mockk()
|
||||||
}
|
}
|
||||||
|
|
@ -140,6 +145,7 @@ class DeepLinkFactoryTest {
|
||||||
swapDeepLink = swapDeepLinkFactory,
|
swapDeepLink = swapDeepLinkFactory,
|
||||||
promoDeepLink = promoDeepLinkFactory,
|
promoDeepLink = promoDeepLinkFactory,
|
||||||
onboardVisaDeepLink = onboardVisaDeepLink,
|
onboardVisaDeepLink = onboardVisaDeepLink,
|
||||||
|
onboardVirtualAccountsDeepLink = onboardVirtualAccountsDeepLink,
|
||||||
tangemPayMainDeepLink = tangemPayMainDeepLink,
|
tangemPayMainDeepLink = tangemPayMainDeepLink,
|
||||||
newsDetailsDeepLink = newsDeeplink,
|
newsDetailsDeepLink = newsDeeplink,
|
||||||
newsDeepLink = newsDeepLinkFactory,
|
newsDeepLink = newsDeepLinkFactory,
|
||||||
|
|
|
||||||
|
|
@ -511,6 +511,24 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Kyc(val userWalletId: UserWalletId) : AppRoute(path = "/kyc")
|
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
|
@Serializable
|
||||||
data class Survey(val token: String, val displayId: String? = null) : AppRoute(path = "/survey")
|
data class Survey(val token: String, val displayId: String? = null) : AppRoute(path = "/survey")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,10 @@ sealed class DeepLinkRoute {
|
||||||
override val host: String = "onboard-visa"
|
override val host: String = "onboard-visa"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data object OnboardVirtualAccounts : DeepLinkRoute() {
|
||||||
|
override val host: String = "onboard-virtual-account"
|
||||||
|
}
|
||||||
|
|
||||||
data object PayApp : DeepLinkRoute() {
|
data object PayApp : DeepLinkRoute() {
|
||||||
override val host: String = "tangem.com"
|
override val host: String = "tangem.com"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,17 @@ data class CustomerMeResponse(
|
||||||
@Json(name = "display_name") val displayName: String?,
|
@Json(name = "display_name") val displayName: String?,
|
||||||
@Json(name = "actual_card_limit") val actualCardLimit: CardLimit?,
|
@Json(name = "actual_card_limit") val actualCardLimit: CardLimit?,
|
||||||
@Json(name = "admin_card_limit") val adminCardLimit: 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)
|
@JsonClass(generateAdapter = false)
|
||||||
enum class Status {
|
enum class Status {
|
||||||
@Json(name = "NEW")
|
@Json(name = "NEW")
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,13 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
/** Core */
|
||||||
|
implementation(projects.core.decompose)
|
||||||
|
implementation(projects.core.ui)
|
||||||
|
|
||||||
|
/** Domain */
|
||||||
|
implementation(projects.domain.models)
|
||||||
|
|
||||||
|
/** Compose */
|
||||||
|
implementation(deps.compose.runtime)
|
||||||
}
|
}
|
||||||
|
|
@ -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<Params, VirtualAccountOnboardingComponent>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.tangem.features.virtualaccount.onboarding.deeplink
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
|
||||||
|
interface OnboardVirtualAccountsDeepLinkHandler {
|
||||||
|
|
||||||
|
interface Factory {
|
||||||
|
fun create(uri: Uri): OnboardVirtualAccountsDeepLinkHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,11 +11,23 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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 */
|
/** Api */
|
||||||
implementation(projects.features.virtualAccounts.onboarding.api)
|
implementation(projects.features.virtualAccounts.onboarding.api)
|
||||||
|
|
||||||
/** Core modules */
|
/** Domain */
|
||||||
implementation(projects.core.configToggles)
|
implementation(projects.domain.common)
|
||||||
|
implementation(projects.domain.models)
|
||||||
|
implementation(projects.domain.visa)
|
||||||
|
|
||||||
/** Compose */
|
/** Compose */
|
||||||
implementation(deps.compose.foundation)
|
implementation(deps.compose.foundation)
|
||||||
|
|
@ -27,4 +39,7 @@ dependencies {
|
||||||
/** DI */
|
/** DI */
|
||||||
implementation(deps.hilt.android)
|
implementation(deps.hilt.android)
|
||||||
kapt(deps.hilt.kapt)
|
kapt(deps.hilt.kapt)
|
||||||
|
|
||||||
|
/** Other */
|
||||||
|
implementation(deps.arrow.core)
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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<VirtualAccountOnboardingComponent.Params>()
|
||||||
|
|
||||||
|
val uiState: StateFlow<VirtualAccountOnboardingUM>
|
||||||
|
field = MutableStateFlow<VirtualAccountOnboardingUM>(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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<VirtualAccountOnboardingUM>(
|
||||||
|
listOf(
|
||||||
|
VirtualAccountOnboardingUM.Loading(onBack = {}),
|
||||||
|
VirtualAccountOnboardingUM.Content(
|
||||||
|
onBack = {},
|
||||||
|
isLoading = false,
|
||||||
|
onGetCardClick = {},
|
||||||
|
onTermsClick = {},
|
||||||
|
onPrivacyClick = {},
|
||||||
|
),
|
||||||
|
VirtualAccountOnboardingUM.Content(
|
||||||
|
onBack = {},
|
||||||
|
isLoading = true,
|
||||||
|
onGetCardClick = {},
|
||||||
|
onTermsClick = {},
|
||||||
|
onPrivacyClick = {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
Loading…
Add table
Add a link
Reference in a new issue