Updated on 2026-08-14

This commit is contained in:
Tangem 2025-03-13 14:37:46 +05:00
parent 56164d0b8b
commit b469c49178
19 changed files with 738 additions and 26 deletions

View file

@ -40,7 +40,6 @@ sealed class AppRoute(val path: String) : Route {
val isTosAccepted: Boolean,
) : AppRoute(path = "/disclaimer${if (isTosAccepted) "/tos_accepted" else ""}")
@Deprecated("Do not use! Should be replaced by an implementation in onboarding component")
@Serializable
data object OnboardingNote : AppRoute(path = "/onboarding/note")

View file

@ -0,0 +1,26 @@
package com.tangem.features.onboarding.v2.common.ui
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.tangem.features.onboarding.v2.impl.R
@Composable
internal fun WalletCard(url: String?, modifier: Modifier = Modifier) {
AsyncImage(
modifier = modifier,
model = ImageRequest.Builder(LocalContext.current)
.data(url)
.crossfade(true)
.build(),
placeholder = painterResource(R.drawable.card_placeholder_black),
error = painterResource(R.drawable.card_placeholder_black),
fallback = painterResource(R.drawable.card_placeholder_black),
contentScale = ContentScale.Fit,
contentDescription = null,
)
}

View file

@ -15,19 +15,14 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
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
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.zIndex
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.common.ui.WalletCard
import kotlinx.coroutines.delay
import java.util.concurrent.TimeUnit
@ -244,22 +239,6 @@ private fun AnimatedWalletCards(
}
}
@Composable
private fun WalletCard(url: String?, modifier: Modifier = Modifier) {
AsyncImage(
modifier = modifier,
model = ImageRequest.Builder(LocalContext.current)
.data(url)
.crossfade(true)
.build(),
placeholder = painterResource(R.drawable.card_placeholder_black),
error = painterResource(R.drawable.card_placeholder_black),
fallback = painterResource(R.drawable.card_placeholder_black),
contentScale = ContentScale.Fit,
contentDescription = null,
)
}
private fun WalletArtworksState.toTransitionSetState(maxWidthDp: Float, maxHeightDp: Float, density: Float) =
when (this) {
WalletArtworksState.Hidden -> listOf()

View file

@ -1,11 +1,12 @@
package com.tangem.features.onboarding.v2.note.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.decompose.navigation.inner.InnerNavigationHolder
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.features.onboarding.v2.TitleProvider
interface OnboardingNoteComponent : ComposableContentComponent {
interface OnboardingNoteComponent : ComposableContentComponent, InnerNavigationHolder {
data class Params(
val titleProvider: TitleProvider,

View file

@ -1,21 +1,128 @@
package com.tangem.features.onboarding.v2.note.impl
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.extensions.compose.stack.Children
import com.arkivanov.decompose.extensions.compose.stack.animation.slide
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.push
import com.arkivanov.decompose.value.Value
import com.arkivanov.decompose.value.subscribe
import com.arkivanov.essenty.instancekeeper.getOrCreateSimple
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.decompose.navigation.inner.InnerNavigation
import com.tangem.core.decompose.navigation.inner.InnerNavigationState
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.note.api.OnboardingNoteComponent
import com.tangem.features.onboarding.v2.note.impl.child.create.OnboardingNoteCreateWalletComponent
import com.tangem.features.onboarding.v2.note.impl.child.topup.OnboardingNoteTopUpComponent
import com.tangem.features.onboarding.v2.note.impl.model.OnboardingNoteModel
import com.tangem.features.onboarding.v2.note.impl.route.ONBOARDING_NOTE_STEPS_COUNT
import com.tangem.features.onboarding.v2.note.impl.route.OnboardingNoteRoute
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
internal class DefaultOnboardingNoteComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted val params: OnboardingNoteComponent.Params,
) : OnboardingNoteComponent, AppComponentContext by context {
private val model: OnboardingNoteModel = getOrCreateModel(params)
private val currentChildBackEventHandle = instanceKeeper.getOrCreateSimple(key = "currentChildBackEventHandle") {
MutableSharedFlow<Unit>()
}
private val childParams = ChildParams(
onBack = ::onChildBack,
parentBackEvent = currentChildBackEventHandle,
)
private val childStack: Value<ChildStack<OnboardingNoteRoute, ComposableContentComponent>> =
childStack(
key = "innerStack",
source = model.stackNavigation,
serializer = null,
initialConfiguration = model.initialRoute,
handleBackButton = true,
childFactory = { configuration, factoryContext ->
createChild(
configuration,
childByContext(factoryContext),
)
},
)
override val innerNavigation: InnerNavigation = object : InnerNavigation {
override val state: StateFlow<InnerNavigationState> = model.innerNavigationState
override fun pop(onComplete: (Boolean) -> Unit) {
// TODO [REDACTED_TASK_KEY]
}
}
init {
componentScope.launch {
model.onDone.collect { params.onDone() }
}
// sets title and stepper value
childStack.subscribe(lifecycle) { stack ->
val currentRoute = stack.active.configuration
params.titleProvider.changeTitle(TextReference.Res(R.string.onboarding_title))
model.updateStepForNewRoute(currentRoute)
}
}
private fun createChild(
route: OnboardingNoteRoute,
factoryContext: AppComponentContext,
): ComposableContentComponent {
return when (route) {
OnboardingNoteRoute.CreateWallet -> OnboardingNoteCreateWalletComponent(
appComponentContext = factoryContext,
params = OnboardingNoteCreateWalletComponent.Params(
childParams = childParams,
onDone = { model.stackNavigation.push(OnboardingNoteRoute.TopUp) },
),
)
OnboardingNoteRoute.TopUp -> OnboardingNoteTopUpComponent(
appComponentContext = factoryContext,
params = OnboardingNoteTopUpComponent.Params(
childParams = childParams,
onDone = { model.onDone.tryEmit(Unit) },
),
)
}
}
private fun onChildBack() = model.onChildBack(
currentRoute = childStack.value.active.configuration,
)
@Composable
override fun Content(modifier: Modifier) {
// TODO [REDACTED_TASK_KEY]
val stackState by childStack.subscribeAsState()
Children(
stack = stackState,
animation = stackAnimation(slide()),
) {
it.instance.Content(modifier)
}
}
@AssistedFactory
@ -25,4 +132,17 @@ internal class DefaultOnboardingNoteComponent @AssistedInject constructor(
params: OnboardingNoteComponent.Params,
): DefaultOnboardingNoteComponent
}
}
data class ChildParams(
val onBack: () -> Unit,
val parentBackEvent: SharedFlow<Unit>,
)
}
internal data class OnboardingNoteInnerNavigationState(
override val stackSize: Int,
) : InnerNavigationState {
override val stackMaxSize: Int = ONBOARDING_NOTE_STEPS_COUNT
}
internal const val ALL_STEPS_TOP_CONTAINER_WEIGHT = 0.48f

View file

@ -0,0 +1,39 @@
package com.tangem.features.onboarding.v2.note.impl.child.create
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
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.core.ui.decompose.ComposableContentComponent
import com.tangem.features.onboarding.v2.note.impl.DefaultOnboardingNoteComponent
import com.tangem.features.onboarding.v2.note.impl.child.create.model.OnboardingNoteCreateWalletModel
import com.tangem.features.onboarding.v2.note.impl.child.create.ui.OnboardingNoteCreateWallet
internal class OnboardingNoteCreateWalletComponent(
appComponentContext: AppComponentContext,
private val params: Params,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
private val model: OnboardingNoteCreateWalletModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
BackHandler(onBack = remember(this) { { params.childParams.onBack() } })
OnboardingNoteCreateWallet(
modifier = modifier,
state = state,
)
}
data class Params(
val childParams: DefaultOnboardingNoteComponent.ChildParams,
val onDone: () -> Unit,
)
}

View file

@ -0,0 +1,26 @@
package com.tangem.features.onboarding.v2.note.impl.child.create.model
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.features.onboarding.v2.note.impl.child.create.ui.state.OnboardingNoteCreateWalletUM
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@Suppress("UnusedPrivateMember")
@ModelScoped
internal class OnboardingNoteCreateWalletModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val tangemSdkManager: TangemSdkManager,
) : Model() {
private val _uiState = MutableStateFlow(
OnboardingNoteCreateWalletUM(),
)
val uiState: StateFlow<OnboardingNoteCreateWalletUM> = _uiState
}

View file

@ -0,0 +1,92 @@
package com.tangem.features.onboarding.v2.note.impl.child.create.ui
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButtonIconEnd
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.WalletArtworks
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.WalletArtworksState
import com.tangem.features.onboarding.v2.note.impl.ALL_STEPS_TOP_CONTAINER_WEIGHT
import com.tangem.features.onboarding.v2.note.impl.child.create.ui.state.OnboardingNoteCreateWalletUM
@Composable
internal fun OnboardingNoteCreateWallet(state: OnboardingNoteCreateWalletUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxSize()
.navigationBarsPadding(),
verticalArrangement = Arrangement.Bottom,
) {
WalletArtworks(
url1 = state.artworkUrl,
url2 = null,
url3 = null,
modifier = Modifier
.padding(horizontal = 34.dp)
.padding(top = 56.dp)
.weight(ALL_STEPS_TOP_CONTAINER_WEIGHT)
.fillMaxWidth(),
state = WalletArtworksState.Folded,
)
Column(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(32.dp)
.weight(1.0f),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResource(R.string.onboarding_create_wallet_header),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 16.dp),
)
Text(
text = stringResource(R.string.onboarding_create_wallet_body),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 12.dp),
)
}
PrimaryButtonIconEnd(
modifier = Modifier
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
.fillMaxWidth(),
iconResId = R.drawable.ic_tangem_24,
text = stringResourceSafe(R.string.onboarding_create_wallet_button_create_wallet),
onClick = state.onCreateClick,
showProgress = state.createWalletInProgress,
)
}
}
@Preview(showBackground = true)
@Composable
private fun OnboardingNoteCreatePreview() {
TangemThemePreview {
OnboardingNoteCreateWallet(
state = OnboardingNoteCreateWalletUM(
artworkUrl = null,
createWalletInProgress = false,
onCreateClick = {},
),
)
}
}

View file

@ -0,0 +1,7 @@
package com.tangem.features.onboarding.v2.note.impl.child.create.ui.state
data class OnboardingNoteCreateWalletUM(
val artworkUrl: String? = null,
val createWalletInProgress: Boolean = false,
val onCreateClick: () -> Unit = {},
)

View file

@ -0,0 +1,39 @@
package com.tangem.features.onboarding.v2.note.impl.child.topup
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
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.core.ui.decompose.ComposableContentComponent
import com.tangem.features.onboarding.v2.note.impl.DefaultOnboardingNoteComponent
import com.tangem.features.onboarding.v2.note.impl.child.topup.model.OnboardingNoteTopUpModel
import com.tangem.features.onboarding.v2.note.impl.child.topup.ui.OnboardingNoteTopUp
internal class OnboardingNoteTopUpComponent(
appComponentContext: AppComponentContext,
private val params: Params,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
private val model: OnboardingNoteTopUpModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
BackHandler(onBack = remember(this) { { params.childParams.onBack() } })
OnboardingNoteTopUp(
modifier = modifier,
state = state,
)
}
data class Params(
val childParams: DefaultOnboardingNoteComponent.ChildParams,
val onDone: () -> Unit,
)
}

View file

@ -0,0 +1,24 @@
package com.tangem.features.onboarding.v2.note.impl.child.topup.model
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.features.onboarding.v2.note.impl.child.topup.ui.state.OnboardingNoteTopUpUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@Suppress("UnusedPrivateMember")
@ModelScoped
internal class OnboardingNoteTopUpModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
private val _uiState = MutableStateFlow(
OnboardingNoteTopUpUM(),
)
val uiState: StateFlow<OnboardingNoteTopUpUM> = _uiState
}

View file

@ -0,0 +1,125 @@
package com.tangem.features.onboarding.v2.note.impl.child.topup.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.components.SpacerHMax
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.onboarding.v2.common.ui.WalletCard
import com.tangem.features.onboarding.v2.impl.R
@Composable
fun OnboardinNoteTopUpHeader(
balance: String?,
cardArtworkUrl: String?,
isRefreshing: Boolean,
onRefreshBalanceClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
Box(
modifier = Modifier
.padding(vertical = 24.dp, horizontal = 32.dp)
.fillMaxSize()
.background(
TangemTheme.colors.button.secondary,
shape = TangemTheme.shapes.roundedCornersMedium,
),
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(horizontal = 32.dp),
) {
WalletCard(
modifier = Modifier.width(120.dp),
url = cardArtworkUrl,
)
SpacerHMax()
Text(
text = stringResourceSafe(R.string.common_balance_title),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
)
SpacerH8()
Text(
text = balance.orEmpty(),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
SpacerHMax()
RefreshButton(
isRefreshing = isRefreshing,
onRefreshBalanceClick = onRefreshBalanceClick,
)
}
}
}
@Composable
private fun RefreshButton(isRefreshing: Boolean, onRefreshBalanceClick: () -> Unit, modifier: Modifier = Modifier) {
Box(
modifier = modifier
.size(TangemTheme.dimens.size48)
.shadow(elevation = 2.dp, shape = CircleShape)
.background(TangemTheme.colors.background.action)
.clickable(
enabled = !isRefreshing,
onClick = onRefreshBalanceClick,
indication = ripple(),
interactionSource = remember { MutableInteractionSource() },
),
) {
if (isRefreshing) {
Box {
CircularProgressIndicator(
modifier = Modifier.padding(TangemTheme.dimens.spacing12),
color = TangemTheme.colors.icon.primary1,
strokeWidth = TangemTheme.dimens.size2,
)
}
} else {
Icon(
painter = painterResource(id = R.drawable.ic_refresh_24),
contentDescription = null,
tint = TangemTheme.colors.text.disabled,
modifier = Modifier.padding(TangemTheme.dimens.spacing12),
)
}
}
}
@Preview(showBackground = true)
@Composable
private fun OnboardinNoteTopUpHeaderPreview() {
TangemThemePreview {
OnboardinNoteTopUpHeader(
balance = "0.00000001 BTC",
cardArtworkUrl = "",
onRefreshBalanceClick = {},
isRefreshing = false,
)
}
}

View file

@ -0,0 +1,112 @@
package com.tangem.features.onboarding.v2.note.impl.child.topup.ui
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButtonIconEnd
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.note.impl.ALL_STEPS_TOP_CONTAINER_WEIGHT
import com.tangem.features.onboarding.v2.note.impl.child.topup.ui.state.OnboardingNoteTopUpUM
@Composable
fun OnboardingNoteTopUp(state: OnboardingNoteTopUpUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxSize()
.navigationBarsPadding(),
verticalArrangement = Arrangement.Bottom,
) {
OnboardinNoteTopUpHeader(
balance = state.balance,
cardArtworkUrl = state.cardArtworkUrl,
onRefreshBalanceClick = state.onRefreshBalanceClick,
isRefreshing = state.isRefreshing,
modifier = Modifier
.padding(horizontal = 34.dp)
.padding(top = 20.dp)
.weight(ALL_STEPS_TOP_CONTAINER_WEIGHT)
.fillMaxWidth(),
)
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(32.dp)
.weight(1.0f),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResourceSafe(R.string.onboarding_topup_title),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 16.dp),
)
Text(
text = stringResourceSafe(R.string.onboarding_top_up_body),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 12.dp),
)
}
BottomButtons(state)
}
}
@Composable
private fun BottomButtons(state: OnboardingNoteTopUpUM) {
if (state.availableForBuy) {
PrimaryButtonIconEnd(
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
iconResId = R.drawable.ic_tangem_24,
text = stringResourceSafe(R.string.onboarding_top_up_button_but_crypto),
onClick = state.onBuyCryptoClick,
)
SpacerH8()
SecondaryButton(
modifier = Modifier
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
.fillMaxWidth(),
text = stringResourceSafe(R.string.onboarding_top_up_button_show_wallet_address),
onClick = state.onShowWalletAddressClick,
)
} else {
PrimaryButtonIconEnd(
modifier = Modifier
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
.fillMaxWidth(),
iconResId = R.drawable.ic_tangem_24,
text = stringResourceSafe(R.string.onboarding_button_receive_crypto),
onClick = state.onShowWalletAddressClick,
)
}
}
@Preview(showBackground = true)
@Composable
private fun OnboardingNoteTopUpPreview() {
TangemThemePreview {
OnboardingNoteTopUp(
state = OnboardingNoteTopUpUM(
availableForBuy = true,
),
)
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.features.onboarding.v2.note.impl.child.topup.ui.state
data class OnboardingNoteTopUpUM(
val cardArtworkUrl: String? = null,
val availableForBuy: Boolean = true,
val balance: String? = null,
val isRefreshing: Boolean = false,
val amountToCreateAccount: String? = null,
val onBuyCryptoClick: () -> Unit = {},
val onShowWalletAddressClick: () -> Unit = {},
val onRefreshBalanceClick: () -> Unit = {},
)

View file

@ -1,11 +1,18 @@
package com.tangem.features.onboarding.v2.note.impl.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.onboarding.v2.note.api.OnboardingNoteComponent
import com.tangem.features.onboarding.v2.note.impl.DefaultOnboardingNoteComponent
import com.tangem.features.onboarding.v2.note.impl.child.create.model.OnboardingNoteCreateWalletModel
import com.tangem.features.onboarding.v2.note.impl.child.topup.model.OnboardingNoteTopUpModel
import com.tangem.features.onboarding.v2.note.impl.model.OnboardingNoteModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import javax.inject.Singleton
@Module
@ -15,4 +22,24 @@ internal interface ComponentModule {
@Binds
@Singleton
fun bindComponent(factory: DefaultOnboardingNoteComponent.Factory): OnboardingNoteComponent.Factory
}
@Module
@InstallIn(ModelComponent::class)
internal interface ModelModule {
@Binds
@IntoMap
@ClassKey(OnboardingNoteModel::class)
fun provideNoteModel(model: OnboardingNoteModel): Model
@Binds
@IntoMap
@ClassKey(OnboardingNoteCreateWalletModel::class)
fun provideNoteCreateWalletModel(model: OnboardingNoteCreateWalletModel): Model
@Binds
@IntoMap
@ClassKey(OnboardingNoteTopUpModel::class)
fun provideNoteTopUpModel(model: OnboardingNoteTopUpModel): Model
}

View file

@ -0,0 +1,58 @@
package com.tangem.features.onboarding.v2.note.impl.model
import com.arkivanov.decompose.router.stack.StackNavigation
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.features.onboarding.v2.note.api.OnboardingNoteComponent
import com.tangem.features.onboarding.v2.note.impl.OnboardingNoteInnerNavigationState
import com.tangem.features.onboarding.v2.note.impl.route.OnboardingNoteRoute
import com.tangem.features.onboarding.v2.note.impl.route.stepNum
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
@ModelScoped
internal class OnboardingNoteModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
@Suppress("UnusedPrivateMember")
private val params = paramsContainer.require<OnboardingNoteComponent.Params>()
private val _currentScanResponse = MutableStateFlow(params.scanResponse)
val initialRoute = OnboardingNoteRoute.CreateWallet // TODO [REDACTED_TASK_KEY]
private val _innerNavigationState =
MutableStateFlow(OnboardingNoteInnerNavigationState(stackSize = initialRoute.stepNum()))
val currentScanResponse = _currentScanResponse.asStateFlow()
val innerNavigationState = _innerNavigationState.asStateFlow()
val onDone = MutableSharedFlow<Unit>()
val stackNavigation = StackNavigation<OnboardingNoteRoute>()
private val _uiState = MutableStateFlow(
OnboardingNoteState(),
)
val uiState: StateFlow<OnboardingNoteState> = _uiState
fun updateStepForNewRoute(route: OnboardingNoteRoute) {
_innerNavigationState.value = innerNavigationState.value.copy(
stackSize = route.stepNum(),
)
}
@Suppress("UnusedPrivateMember")
fun onChildBack(currentRoute: OnboardingNoteRoute) {
// TODO [REDACTED_TASK_KEY]
}
}

View file

@ -0,0 +1,5 @@
package com.tangem.features.onboarding.v2.note.impl.model
data class OnboardingNoteState(
val cardArtworkUrl: String? = null,
)

View file

@ -0,0 +1,15 @@
package com.tangem.features.onboarding.v2.note.impl.route
import kotlinx.serialization.Serializable
@Serializable
internal sealed class OnboardingNoteRoute {
@Serializable
data object CreateWallet : OnboardingNoteRoute()
@Serializable
data object TopUp : OnboardingNoteRoute()
}
internal const val ONBOARDING_NOTE_STEPS_COUNT = 3

View file

@ -0,0 +1,6 @@
package com.tangem.features.onboarding.v2.note.impl.route
internal fun OnboardingNoteRoute.stepNum() = when (this) {
OnboardingNoteRoute.CreateWallet -> 1
OnboardingNoteRoute.TopUp -> 2
}