Updated on 2026-08-14
This commit is contained in:
parent
0ff4768da9
commit
b9bb9e1086
21 changed files with 789 additions and 3 deletions
|
|
@ -224,6 +224,8 @@ dependencies {
|
||||||
implementation(projects.features.hotWallet.impl)
|
implementation(projects.features.hotWallet.impl)
|
||||||
implementation(projects.features.kyc.api)
|
implementation(projects.features.kyc.api)
|
||||||
implementation(projects.features.kyc.impl)
|
implementation(projects.features.kyc.impl)
|
||||||
|
implementation(projects.features.welcome.api)
|
||||||
|
implementation(projects.features.welcome.impl)
|
||||||
|
|
||||||
/** AndroidX libraries */
|
/** AndroidX libraries */
|
||||||
implementation(deps.androidx.core.ktx)
|
implementation(deps.androidx.core.ktx)
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ private fun NameAndInfo(
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
@Composable
|
@Composable
|
||||||
private fun CardImage(imageState: UserWalletItemUM.ImageState, modifier: Modifier = Modifier) {
|
fun CardImage(imageState: UserWalletItemUM.ImageState, modifier: Modifier = Modifier) {
|
||||||
val imageModifier = modifier
|
val imageModifier = modifier
|
||||||
.width(TangemTheme.dimens.size36)
|
.width(TangemTheme.dimens.size36)
|
||||||
.height(TangemTheme.dimens.size24)
|
.height(TangemTheme.dimens.size24)
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import androidx.compose.ui.unit.sp
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.utils.StringsSigns.PASSWORD_VISUAL_CHAR
|
import com.tangem.utils.StringsSigns.PASSWORD_VISUAL_CHAR
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -74,6 +75,11 @@ fun PinTextField(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
delay(timeMillis = 200)
|
||||||
|
focusRequester.requestFocus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
|
|
|
||||||
1
features/welcome/api/.gitignore
vendored
Normal file
1
features/welcome/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
22
features/welcome/api/build.gradle.kts
Normal file
22
features/welcome/api/build.gradle.kts
Normal 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)
|
||||||
|
}
|
||||||
|
|
@ -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
1
features/welcome/impl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
57
features/welcome/impl/build.gradle.kts
Normal file
57
features/welcome/impl/build.gradle.kts
Normal 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)
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
@ -254,6 +254,9 @@ include(":features:kyc:impl")
|
||||||
|
|
||||||
include(":features:create-wallet-selection:api")
|
include(":features:create-wallet-selection:api")
|
||||||
include(":features:create-wallet-selection:impl")
|
include(":features:create-wallet-selection:impl")
|
||||||
|
|
||||||
|
include(":features:welcome:api")
|
||||||
|
include(":features:welcome:impl")
|
||||||
// endregion Feature modules
|
// endregion Feature modules
|
||||||
|
|
||||||
// region Domain modules
|
// region Domain modules
|
||||||
|
|
@ -341,6 +344,6 @@ include(":data:onramp")
|
||||||
include(":data:quotes")
|
include(":data:quotes")
|
||||||
include(":data:notifications")
|
include(":data:notifications")
|
||||||
include(":data:blockaid")
|
include(":data:blockaid")
|
||||||
// endregion Data modules
|
|
||||||
include(":data:swap")
|
include(":data:swap")
|
||||||
include(":data:express")
|
include(":data:express")
|
||||||
|
// endregion Data modules
|
||||||
Loading…
Add table
Add a link
Reference in a new issue