Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-25 12:48:32 +03:00
parent 0ff4768da9
commit b9bb9e1086
21 changed files with 789 additions and 3 deletions

View file

@ -1,28 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="90dp"
android:height="24dp"
android:viewportWidth="90"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:fillType="evenOdd"
android:pathData="M20,7.5H4V5.5C4,3.843 5.343,2.5 7,2.5H17C18.657,2.5 20,3.843 20,5.5V7.5ZM9,21.5H7C5.343,21.5 4,20.157 4,18.5V11.5H9V21.5ZM15,21.5H17C18.657,21.5 20,20.157 20,18.5V11.5H15V21.5Z" />
<path
android:fillColor="#000000"
android:pathData="M32.063,18.693C31.148,18.693 30.447,18.452 29.96,17.97C29.485,17.488 29.247,16.795 29.247,15.892V10.723H28V9.078H29.247V7H31.404V9.078H33.453V10.723H31.404V15.693C31.404,16.5 31.76,16.904 32.473,16.904C32.889,16.904 33.239,16.843 33.525,16.723V18.458C33.109,18.615 32.622,18.693 32.063,18.693Z" />
<path
android:fillColor="#000000"
android:pathData="M37.642,18.711C36.727,18.711 35.978,18.482 35.396,18.024C34.814,17.554 34.523,16.849 34.523,15.91C34.523,14.873 34.957,14.115 35.824,13.632C36.691,13.151 37.85,12.91 39.299,12.91H40.457V12.422C40.457,11.771 40.327,11.295 40.065,10.994C39.816,10.681 39.382,10.524 38.764,10.524C37.683,10.524 37.089,11.018 36.982,12.006H34.897C34.98,10.958 35.384,10.175 36.109,9.657C36.846,9.139 37.772,8.88 38.889,8.88C41.372,8.88 42.614,10.03 42.614,12.331V18.53H40.493V17.374C39.828,18.265 38.877,18.711 37.642,18.711ZM38.159,17.139C38.836,17.139 39.388,16.97 39.816,16.632C40.244,16.283 40.457,15.813 40.457,15.223V14.283H39.353C37.558,14.283 36.661,14.795 36.661,15.819C36.661,16.699 37.16,17.139 38.159,17.139Z" />
<path
android:fillColor="#000000"
android:pathData="M44.729,18.53V9.078H46.885V10.578C47.123,10.084 47.503,9.681 48.026,9.367C48.549,9.042 49.172,8.88 49.897,8.88C50.883,8.88 51.661,9.187 52.232,9.801C52.802,10.404 53.087,11.331 53.087,12.584V18.53H50.931V12.783C50.931,12.072 50.782,11.548 50.485,11.211C50.188,10.873 49.719,10.705 49.077,10.705C48.436,10.705 47.907,10.904 47.491,11.301C47.087,11.687 46.885,12.241 46.885,12.964V18.53H44.729Z" />
<path
android:fillColor="#000000"
android:pathData="M59.189,22C57.799,22 56.736,21.717 55.999,21.151C55.263,20.584 54.829,19.819 54.698,18.855H56.872C57.062,19.843 57.829,20.337 59.171,20.337C60.906,20.337 61.773,19.488 61.773,17.789V16.398C61.5,16.879 61.084,17.295 60.526,17.645C59.979,17.982 59.379,18.151 58.726,18.151C57.538,18.151 56.551,17.735 55.767,16.904C54.983,16.072 54.591,14.988 54.591,13.651V13.524C54.591,12.199 54.989,11.096 55.785,10.217C56.581,9.325 57.579,8.88 58.779,8.88C60.157,8.88 61.155,9.428 61.773,10.524V9.078H63.93V17.825C63.918,19.187 63.49,20.223 62.646,20.934C61.815,21.645 60.662,22 59.189,22ZM59.26,16.47C60.021,16.47 60.639,16.211 61.114,15.693C61.601,15.175 61.844,14.47 61.844,13.578V13.452C61.844,12.56 61.619,11.861 61.167,11.355C60.716,10.849 60.104,10.596 59.332,10.596C58.548,10.596 57.93,10.861 57.478,11.392C57.027,11.922 56.801,12.62 56.801,13.488V13.632C56.801,14.488 57.033,15.175 57.496,15.693C57.959,16.211 58.548,16.47 59.26,16.47Z" />
<path
android:fillColor="#000000"
android:pathData="M70.235,18.711C68.845,18.711 67.71,18.277 66.831,17.41C65.952,16.542 65.512,15.373 65.512,13.904V13.759C65.512,12.313 65.946,11.139 66.813,10.235C67.692,9.331 68.803,8.88 70.146,8.88C71.405,8.88 72.451,9.277 73.282,10.072C74.126,10.868 74.548,12.06 74.548,13.651V14.283H67.722C67.758,15.187 67.995,15.88 68.435,16.361C68.874,16.843 69.492,17.084 70.288,17.084C71.548,17.084 72.261,16.602 72.427,15.639H74.512C74.357,16.626 73.906,17.385 73.158,17.916C72.409,18.446 71.435,18.711 70.235,18.711ZM67.758,12.819H72.409C72.302,11.253 71.548,10.47 70.146,10.47C69.504,10.47 68.97,10.681 68.542,11.102C68.126,11.512 67.865,12.084 67.758,12.819Z" />
<path
android:fillColor="#000000"
android:pathData="M76.117,18.53V9.078H78.274V10.524C78.511,10.054 78.88,9.663 79.379,9.349C79.878,9.036 80.454,8.88 81.107,8.88C82.414,8.88 83.293,9.446 83.745,10.578C84.089,10.024 84.547,9.602 85.117,9.313C85.687,9.024 86.281,8.88 86.899,8.88C87.814,8.88 88.557,9.187 89.127,9.801C89.709,10.404 90,11.319 90,12.548V18.53H87.844V12.693C87.844,11.368 87.291,10.705 86.186,10.705C85.616,10.705 85.129,10.898 84.725,11.283C84.333,11.657 84.137,12.187 84.137,12.873V18.53H81.98V12.693C81.98,11.368 81.428,10.705 80.323,10.705C79.753,10.705 79.266,10.898 78.862,11.283C78.47,11.657 78.274,12.187 78.274,12.873V18.53H76.117Z" />
</vector>

1
features/welcome/api/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,22 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
id("configuration")
}
android {
namespace = "com.tangem.features.welcome.api"
}
dependencies {
/** Core */
implementation(projects.core.decompose)
implementation(projects.core.ui)
implementation(projects.common.routing)
/** Domain models */
implementation(projects.domain.wallets.models)
/** Compose */
implementation(deps.compose.runtime)
}

View file

@ -0,0 +1,14 @@
package com.tangem.features.welcome
import com.tangem.common.routing.entity.SerializableIntent
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
interface WelcomeComponent : ComposableContentComponent {
data class Params(
val intent: SerializableIntent?,
)
interface Factory : ComponentFactory<Params, WelcomeComponent>
}

1
features/welcome/impl/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,57 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.serialization)
alias(deps.plugins.kotlin.kapt)
alias(deps.plugins.hilt.android)
id("configuration")
}
android {
namespace = "com.tangem.features.welcome.impl"
}
dependencies {
implementation(projects.features.welcome.api)
/** Core */
implementation(projects.core.configToggles)
implementation(projects.core.decompose)
implementation(projects.core.ui)
implementation(projects.common.routing)
implementation(projects.common.ui)
/** Domain models */
implementation(projects.domain.appCurrency.models)
implementation(projects.domain.models)
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)
/** Domain */
implementation(projects.domain.appCurrency)
implementation(projects.domain.wallets)
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
/** AndroidX */
implementation(deps.androidx.activity.compose)
/** Compose */
implementation(deps.compose.accompanist.systemUiController)
implementation(deps.compose.coil)
implementation(deps.compose.foundation)
implementation(deps.compose.material3)
implementation(deps.compose.ui)
implementation(deps.compose.ui.tooling)
/** Other */
implementation(deps.arrow.core)
implementation(deps.decompose)
implementation(deps.decompose.ext.compose)
implementation(deps.kotlin.immutable.collections)
implementation(deps.timber)
implementation(tangemDeps.card.core)
implementation(tangemDeps.blockchain)
}

View file

@ -0,0 +1,37 @@
package com.tangem.features.welcome.impl
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.welcome.WelcomeComponent
import com.tangem.features.welcome.impl.model.WelcomeModel
import com.tangem.features.welcome.impl.ui.Welcome
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultWelcomeComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted params: WelcomeComponent.Params,
) : WelcomeComponent, AppComponentContext by context {
private val model: WelcomeModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
Welcome(
modifier = modifier,
state = state,
)
}
@AssistedFactory
interface Factory : WelcomeComponent.Factory {
override fun create(context: AppComponentContext, params: WelcomeComponent.Params): DefaultWelcomeComponent
}
}

View file

@ -0,0 +1,31 @@
package com.tangem.features.welcome.impl.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.welcome.WelcomeComponent
import com.tangem.features.welcome.impl.DefaultWelcomeComponent
import com.tangem.features.welcome.impl.model.WelcomeModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(SingletonComponent::class)
internal interface FeatureModule {
@Binds
fun bindComponentFactory(impl: DefaultWelcomeComponent.Factory): WelcomeComponent.Factory
}
@Module
@InstallIn(ModelComponent::class)
internal interface ModelModule {
@Binds
@IntoMap
@ClassKey(WelcomeModel::class)
fun provideModel(model: WelcomeModel): Model
}

View file

@ -0,0 +1,18 @@
package com.tangem.features.welcome.impl.model
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.features.welcome.impl.ui.state.WelcomeUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@ModelScoped
internal class WelcomeModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
val uiState: StateFlow<WelcomeUM>
field = MutableStateFlow(WelcomeUM.Plain)
}

View file

@ -0,0 +1,89 @@
package com.tangem.features.welcome.impl.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.inputrow.InputRowDefault
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.welcome.impl.ui.state.AddWalletBottomSheetContentUM
@Composable
fun AddWalletBottomSheet(config: TangemBottomSheetConfig) {
TangemBottomSheet<AddWalletBottomSheetContentUM>(
config = config,
titleText = TextReference.Str("Add Wallet"),
containerColor = TangemTheme.colors.background.tertiary,
content = { Content(it) },
)
}
@Composable
private fun Content(content: AddWalletBottomSheetContentUM) {
Column(
modifier = Modifier
.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing16,
),
) {
InputRowDefault(
text = TextReference.Str("Create New Wallet"),
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = 0,
lastIndex = 3,
addDefaultPadding = false,
)
.background(TangemTheme.colors.background.action)
.clickable { content.onOptionClick(AddWalletBottomSheetContentUM.Option.Create) },
)
InputRowDefault(
text = TextReference.Str("Add Existing Wallet"),
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = 1,
lastIndex = 2,
addDefaultPadding = false,
)
.background(TangemTheme.colors.background.action)
.clickable { content.onOptionClick(AddWalletBottomSheetContentUM.Option.Add) },
)
InputRowDefault(
text = TextReference.Str("Buy Tangem Wallet"),
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = 2,
lastIndex = 2,
addDefaultPadding = false,
)
.background(TangemTheme.colors.background.action)
.clickable { content.onOptionClick(AddWalletBottomSheetContentUM.Option.Buy) },
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun SecurityScoreBottomSheetPreview() {
TangemThemePreview {
AddWalletBottomSheet(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = {},
content = AddWalletBottomSheetContentUM(),
),
)
}
}

View file

@ -0,0 +1,89 @@
package com.tangem.features.welcome.impl.ui
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.welcome.impl.ui.state.WalletUM
import com.tangem.features.welcome.impl.ui.state.WelcomeUM
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun Welcome(state: WelcomeUM, modifier: Modifier = Modifier) {
Box(
modifier
.fillMaxSize()
.background(TangemTheme.colors.background.primary),
) {
AnimatedContent(
targetState = state,
contentKey = { it::class.java.simpleName },
) { st ->
when (st) {
is WelcomeUM.Plain -> WelcomePlain(modifier = modifier)
is WelcomeUM.SelectWallet -> WelcomeSelectWallet(
state = st,
modifier = modifier,
)
is WelcomeUM.EnterAccessCode -> WelcomeEnterAccessCode(
state = st,
modifier = modifier,
)
}
}
}
}
@Preview
@Composable
private fun Preview() {
TangemThemePreview {
val state = WelcomeUM.SelectWallet(
wallets = persistentListOf(
WalletUM(
name = TextReference.Str("Wallet 1"),
subtitle = TextReference.Str("3 cards"),
imageState = WalletUM.ImageState.Loading,
onClick = {},
),
WalletUM(
name = TextReference.Str("Wallet 1"),
subtitle = TextReference.Str("Mobile wallet"),
imageState = WalletUM.ImageState.MobileWallet,
onClick = {},
),
),
)
var currentState by remember { mutableStateOf<WelcomeUM>(WelcomeUM.EnterAccessCode()) }
Box {
Welcome(currentState)
Button(
modifier = Modifier.align(Alignment.BottomStart),
onClick = {
currentState = when (currentState) {
is WelcomeUM.Plain -> state
is WelcomeUM.SelectWallet -> WelcomeUM.EnterAccessCode(
value = "",
onValueChange = {},
)
is WelcomeUM.EnterAccessCode -> WelcomeUM.Plain
}
},
) {
Text("Switch")
}
}
}
}

View file

@ -0,0 +1,94 @@
package com.tangem.features.welcome.impl.ui
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.appbar.TopAppBarButton
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.fields.PinTextField
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.welcome.impl.ui.state.WelcomeUM
@Suppress("MagicNumber")
@Composable
internal fun AnimatedContentScope.WelcomeEnterAccessCode(
state: WelcomeUM.EnterAccessCode,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.fillMaxSize()
.statusBarsPadding(),
) {
Column {
TopAppBarButton(
modifier = Modifier
.padding(12.dp),
button = TopAppBarButtonUM.Back(onBackClicked = state.onBackClick),
tint = TangemTheme.colors.icon.primary1,
)
SpacerH(68.dp)
Text(
modifier = Modifier
.animateEnterExit(
enter = slideInVertically(
tween(delayMillis = 300),
initialOffsetY = { it + 200 },
) + fadeIn(tween(delayMillis = 300)),
exit = fadeOut(),
)
.align(Alignment.CenterHorizontally),
text = "Enter Access Code",
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
)
SpacerH24()
Box(
modifier = Modifier
.animateEnterExit(
enter = slideInVertically(
tween(delayMillis = 300),
initialOffsetY = { it + 200 },
) + fadeIn(tween(delayMillis = 300)),
exit = fadeOut(),
)
.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
PinTextField(
length = 6,
isPasswordVisual = true,
value = state.value,
onValueChange = state.onValueChange,
)
}
}
SecondaryButton(
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.padding(16.dp)
.navigationBarsPadding()
.imePadding()
.animateEnterExit(fadeIn(), fadeOut()),
text = "Log in with biometric",
onClick = state.onUnlockWithBiometricClick,
)
}
}

View file

@ -0,0 +1,29 @@
package com.tangem.features.welcome.impl.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.welcome.impl.R
@Composable
internal fun WelcomePlain(modifier: Modifier = Modifier) {
Box(
modifier = modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Icon(
modifier = Modifier.size(84.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_tangem_24),
tint = TangemTheme.colors.icon.primary1,
contentDescription = null,
)
}
}

View file

@ -0,0 +1,230 @@
package com.tangem.features.welcome.impl.ui
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.userwallet.CardImage
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.welcome.impl.R
import com.tangem.features.welcome.impl.ui.state.WalletUM
import com.tangem.features.welcome.impl.ui.state.WelcomeUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.delay
@Suppress("MagicNumber")
@Composable
internal fun AnimatedContentScope.WelcomeSelectWallet(state: WelcomeUM.SelectWallet, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxSize()
.statusBarsPadding(),
) {
TopBar(state)
TitleText()
SpacerH12()
var actualWallets by remember { mutableStateOf<ImmutableList<WalletUM>>(persistentListOf()) }
Box(modifier = Modifier.weight(1f)) {
LazyColumn(
modifier = Modifier
.animateEnterExit(
enter = slideInVertically(
tween(delayMillis = 300),
initialOffsetY = { it / 10 },
) + fadeIn(tween(delayMillis = 300)),
exit = fadeOut(),
)
.fillMaxHeight()
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
itemsIndexed(actualWallets) { index, walletState ->
WalletItem(
state = walletState,
modifier = Modifier,
)
}
}
BottomFade(modifier = Modifier.align(Alignment.BottomCenter))
SecondaryButton(
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.padding(16.dp)
.navigationBarsPadding()
.animateEnterExit(fadeIn(), fadeOut()),
text = "Unlock all with biometric",
onClick = state.onUnlockWithBiometricClick,
)
}
LaunchedEffect(state.wallets) {
delay(200)
actualWallets = state.wallets
}
}
AddWalletBottomSheet(state.addWalletBottomSheet)
}
@Suppress("MagicNumber")
@Composable
private fun AnimatedContentScope.TopBar(state: WelcomeUM.SelectWallet, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.animateEnterExit(
enter = slideInVertically(
tween(delayMillis = 100),
initialOffsetY = { it + 100 },
) + fadeIn(tween(delayMillis = 100)),
exit = fadeOut(),
)
.padding(
top = 16.dp,
bottom = 16.dp,
start = 16.dp,
)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.img_tangem_logo_90_24),
tint = TangemTheme.colors.icon.primary1,
contentDescription = null,
)
TextButton(
modifier = Modifier.clip(TangemTheme.shapes.roundedCornersLarge),
text = "Add Wallet",
colors = TangemButtonsDefaults.defaultTextButtonColors.copy(
contentColor = TangemTheme.colors.text.primary1,
),
textStyle = TangemTheme.typography.body1,
onClick = state.addWalletClick,
)
}
}
@Suppress("MagicNumber")
@Composable
private fun AnimatedContentScope.TitleText(modifier: Modifier = Modifier) {
Column(
modifier.padding(16.dp),
) {
Text(
modifier = Modifier.animateEnterExit(
enter = slideInVertically(
tween(delayMillis = 300),
initialOffsetY = { it + 200 },
) + fadeIn(tween(delayMillis = 300)),
exit = fadeOut(),
),
text = "Welcome back!",
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
)
SpacerH16()
Text(
modifier = Modifier.animateEnterExit(
enter = slideInVertically(
tween(delayMillis = 300),
initialOffsetY = { it + 200 },
) + fadeIn(tween(delayMillis = 300)),
exit = fadeOut(),
),
text = "Select a wallet to log in",
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.secondary,
)
}
}
@Suppress("MagicNumber")
@Composable
private fun WalletItem(state: WalletUM, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.secondary, TangemTheme.shapes.roundedCornersXMedium)
.clickable(onClick = state.onClick)
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
WalletImage(state.imageState)
SpacerW12()
Column(Modifier.weight(1f)) {
Text(
text = state.name.resolveReference(),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
Text(
text = state.subtitle.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
@Composable
private fun WalletImage(state: WalletUM.ImageState, modifier: Modifier = Modifier) {
when (state) {
WalletUM.ImageState.MobileWallet -> {
Box(
modifier = modifier
.size(36.dp)
.background(TangemTheme.colors.icon.accent.copy(alpha = 0.1f), CircleShape),
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_wallet_filled_24),
tint = TangemTheme.colors.icon.accent,
contentDescription = null,
)
}
}
else -> {
CardImage(
imageState = when (state) {
is WalletUM.ImageState.Image -> UserWalletItemUM.ImageState.Image(state.artwork)
WalletUM.ImageState.Loading -> UserWalletItemUM.ImageState.Loading
else -> error("")
},
modifier = modifier,
)
}
}
}

View file

@ -0,0 +1,14 @@
package com.tangem.features.welcome.impl.ui.state
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
internal data class AddWalletBottomSheetContentUM(
val onOptionClick: (Option) -> Unit = {},
) : TangemBottomSheetConfigContent {
enum class Option {
Create,
Add,
Buy,
}
}

View file

@ -0,0 +1,22 @@
package com.tangem.features.welcome.impl.ui.state
import com.tangem.core.ui.components.artwork.ArtworkUM
import com.tangem.core.ui.extensions.TextReference
import javax.annotation.concurrent.Immutable
internal data class WalletUM(
val name: TextReference,
val subtitle: TextReference,
val imageState: ImageState,
val onClick: () -> Unit,
) {
@Immutable
sealed class ImageState {
data object MobileWallet : ImageState()
data object Loading : ImageState()
data class Image(
val artwork: ArtworkUM,
) : ImageState()
}
}

View file

@ -0,0 +1,27 @@
package com.tangem.features.welcome.impl.ui.state
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@Immutable
internal sealed class WelcomeUM {
data object Plain : WelcomeUM()
data class SelectWallet(
val wallets: ImmutableList<WalletUM> = persistentListOf(),
val showUnlockWithBiometricButton: Boolean = false,
val addWalletBottomSheet: TangemBottomSheetConfig = TangemBottomSheetConfig.Empty,
val onUnlockWithBiometricClick: () -> Unit = {},
val addWalletClick: () -> Unit = {},
) : WelcomeUM()
data class EnterAccessCode(
val value: String = "",
val onUnlockWithBiometricClick: () -> Unit = {},
val onValueChange: (String) -> Unit = {},
val onBackClick: () -> Unit = {},
) : WelcomeUM()
}