From b9bb9e1086f78b60f992c0b8dfbe2a695415a191 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 25 Jun 2025 12:48:32 +0300 Subject: [PATCH] Updated on 2026-08-14 --- app/build.gradle.kts | 2 + .../common/ui/userwallet/UserWalletItem.kt | 2 +- .../core/ui/components/fields/PinTextField.kt | 6 + .../res/drawable/img_tangem_logo_90_24.xml | 0 features/welcome/api/.gitignore | 1 + features/welcome/api/build.gradle.kts | 22 ++ .../features/welcome/WelcomeComponent.kt | 14 ++ features/welcome/impl/.gitignore | 1 + features/welcome/impl/build.gradle.kts | 57 +++++ .../welcome/impl/DefaultWelcomeComponent.kt | 37 +++ .../features/welcome/impl/di/FeatureModule.kt | 31 +++ .../welcome/impl/model/WelcomeModel.kt | 18 ++ .../welcome/impl/ui/AddWalletBottomSheet.kt | 89 +++++++ .../features/welcome/impl/ui/Welcome.kt | 89 +++++++ .../welcome/impl/ui/WelcomeEnterAccessCode.kt | 94 +++++++ .../features/welcome/impl/ui/WelcomePlain.kt | 29 +++ .../welcome/impl/ui/WelcomeSelectWallet.kt | 230 ++++++++++++++++++ .../ui/state/AddWalletBottomSheetContentUM.kt | 14 ++ .../welcome/impl/ui/state/WalletUM.kt | 22 ++ .../welcome/impl/ui/state/WelcomeUM.kt | 27 ++ settings.gradle.kts | 7 +- 21 files changed, 789 insertions(+), 3 deletions(-) rename {features/wallet/impl => core/ui}/src/main/res/drawable/img_tangem_logo_90_24.xml (100%) create mode 100644 features/welcome/api/.gitignore create mode 100644 features/welcome/api/build.gradle.kts create mode 100644 features/welcome/api/src/main/kotlin/com/tangem/features/welcome/WelcomeComponent.kt create mode 100644 features/welcome/impl/.gitignore create mode 100644 features/welcome/impl/build.gradle.kts create mode 100644 features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/DefaultWelcomeComponent.kt create mode 100644 features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/di/FeatureModule.kt create mode 100644 features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt create mode 100644 features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/AddWalletBottomSheet.kt create mode 100644 features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/Welcome.kt create mode 100644 features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/WelcomeEnterAccessCode.kt create mode 100644 features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/WelcomePlain.kt create mode 100644 features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/WelcomeSelectWallet.kt create mode 100644 features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/state/AddWalletBottomSheetContentUM.kt create mode 100644 features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/state/WalletUM.kt create mode 100644 features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/state/WelcomeUM.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1c4d33f627..85b539e159 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -224,6 +224,8 @@ dependencies { implementation(projects.features.hotWallet.impl) implementation(projects.features.kyc.api) implementation(projects.features.kyc.impl) + implementation(projects.features.welcome.api) + implementation(projects.features.welcome.impl) /** AndroidX libraries */ implementation(deps.androidx.core.ktx) diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt index 5d2c226351..698ec1ccf5 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt @@ -164,7 +164,7 @@ private fun NameAndInfo( @Suppress("MagicNumber") @Composable -private fun CardImage(imageState: UserWalletItemUM.ImageState, modifier: Modifier = Modifier) { +fun CardImage(imageState: UserWalletItemUM.ImageState, modifier: Modifier = Modifier) { val imageModifier = modifier .width(TangemTheme.dimens.size36) .height(TangemTheme.dimens.size24) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt index bc6d664fb1..7c7285b37d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.unit.sp import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.utils.StringsSigns.PASSWORD_VISUAL_CHAR +import kotlinx.coroutines.delay import kotlin.random.Random @Composable @@ -74,6 +75,11 @@ fun PinTextField( ) }, ) + + LaunchedEffect(Unit) { + delay(timeMillis = 200) + focusRequester.requestFocus() + } } @Suppress("MagicNumber") diff --git a/features/wallet/impl/src/main/res/drawable/img_tangem_logo_90_24.xml b/core/ui/src/main/res/drawable/img_tangem_logo_90_24.xml similarity index 100% rename from features/wallet/impl/src/main/res/drawable/img_tangem_logo_90_24.xml rename to core/ui/src/main/res/drawable/img_tangem_logo_90_24.xml diff --git a/features/welcome/api/.gitignore b/features/welcome/api/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/features/welcome/api/.gitignore @@ -0,0 +1 @@ +/build diff --git a/features/welcome/api/build.gradle.kts b/features/welcome/api/build.gradle.kts new file mode 100644 index 0000000000..92d3e16d53 --- /dev/null +++ b/features/welcome/api/build.gradle.kts @@ -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) +} \ No newline at end of file diff --git a/features/welcome/api/src/main/kotlin/com/tangem/features/welcome/WelcomeComponent.kt b/features/welcome/api/src/main/kotlin/com/tangem/features/welcome/WelcomeComponent.kt new file mode 100644 index 0000000000..50b914548b --- /dev/null +++ b/features/welcome/api/src/main/kotlin/com/tangem/features/welcome/WelcomeComponent.kt @@ -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 +} \ No newline at end of file diff --git a/features/welcome/impl/.gitignore b/features/welcome/impl/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/features/welcome/impl/.gitignore @@ -0,0 +1 @@ +/build diff --git a/features/welcome/impl/build.gradle.kts b/features/welcome/impl/build.gradle.kts new file mode 100644 index 0000000000..499c0ab82e --- /dev/null +++ b/features/welcome/impl/build.gradle.kts @@ -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) +} \ No newline at end of file diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/DefaultWelcomeComponent.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/DefaultWelcomeComponent.kt new file mode 100644 index 0000000000..7bc6a1d982 --- /dev/null +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/DefaultWelcomeComponent.kt @@ -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 + } +} \ No newline at end of file diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/di/FeatureModule.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/di/FeatureModule.kt new file mode 100644 index 0000000000..fa56c72481 --- /dev/null +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/di/FeatureModule.kt @@ -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 +} \ No newline at end of file diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt new file mode 100644 index 0000000000..da1f8c9c21 --- /dev/null +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt @@ -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 + field = MutableStateFlow(WelcomeUM.Plain) +} \ No newline at end of file diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/AddWalletBottomSheet.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/AddWalletBottomSheet.kt new file mode 100644 index 0000000000..4331461929 --- /dev/null +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/AddWalletBottomSheet.kt @@ -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( + 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(), + ), + ) + } +} \ No newline at end of file diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/Welcome.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/Welcome.kt new file mode 100644 index 0000000000..caf6ecd9bd --- /dev/null +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/Welcome.kt @@ -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.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") + } + } + } +} \ No newline at end of file diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/WelcomeEnterAccessCode.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/WelcomeEnterAccessCode.kt new file mode 100644 index 0000000000..3cd6085b58 --- /dev/null +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/WelcomeEnterAccessCode.kt @@ -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, + ) + } +} \ No newline at end of file diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/WelcomePlain.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/WelcomePlain.kt new file mode 100644 index 0000000000..13aaae14db --- /dev/null +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/WelcomePlain.kt @@ -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, + ) + } +} \ No newline at end of file diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/WelcomeSelectWallet.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/WelcomeSelectWallet.kt new file mode 100644 index 0000000000..ed41fe5ad4 --- /dev/null +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/WelcomeSelectWallet.kt @@ -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>(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, + ) + } + } +} \ No newline at end of file diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/state/AddWalletBottomSheetContentUM.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/state/AddWalletBottomSheetContentUM.kt new file mode 100644 index 0000000000..40a14eb1bb --- /dev/null +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/state/AddWalletBottomSheetContentUM.kt @@ -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, + } +} \ No newline at end of file diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/state/WalletUM.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/state/WalletUM.kt new file mode 100644 index 0000000000..216a28f7d2 --- /dev/null +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/state/WalletUM.kt @@ -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() + } +} \ No newline at end of file diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/state/WelcomeUM.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/state/WelcomeUM.kt new file mode 100644 index 0000000000..1e2e509d58 --- /dev/null +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/ui/state/WelcomeUM.kt @@ -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 = 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() +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index ba875e3bb9..faba1334a8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -254,6 +254,9 @@ include(":features:kyc:impl") include(":features:create-wallet-selection:api") include(":features:create-wallet-selection:impl") + +include(":features:welcome:api") +include(":features:welcome:impl") // endregion Feature modules // region Domain modules @@ -341,6 +344,6 @@ include(":data:onramp") include(":data:quotes") include(":data:notifications") include(":data:blockaid") -// endregion Data modules include(":data:swap") -include(":data:express") \ No newline at end of file +include(":data:express") +// endregion Data modules \ No newline at end of file