Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-24 17:31:01 +05:00
parent f84d6a1261
commit d9cf8d5aef
19 changed files with 573 additions and 2 deletions

View file

@ -313,6 +313,16 @@
<category android:name="android.intent.category.DEFAULT" />
<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
android:host="news"
android:scheme="tangem" />

View file

@ -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,

View file

@ -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)

View file

@ -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<OnboardVirtualAccountsDeepLinkHandler.Factory>(relaxed = true) {
every { create(any()) } returns mockk()
}
private val tangemPayMainDeepLink = mockk<TangemPayMainDeepLinkHandler.Factory>(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,

View file

@ -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")

View file

@ -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"
}

View file

@ -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")

View file

@ -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)
}

View file

@ -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>
}

View file

@ -0,0 +1,10 @@
package com.tangem.features.virtualaccount.onboarding.deeplink
import android.net.Uri
interface OnboardVirtualAccountsDeepLinkHandler {
interface Factory {
fun create(uri: Uri): OnboardVirtualAccountsDeepLinkHandler
}
}

View file

@ -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)
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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 = {},
),
),
)

View file

@ -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