diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 97575ecd31..c33e466944 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -28,6 +28,7 @@ import com.tangem.tap.features.tokens.addCustomToken.AddCustomTokenFragment import com.tangem.tap.features.tokens.ui.AddTokensFragment import com.tangem.tap.features.wallet.ui.WalletDetailsFragment import com.tangem.tap.features.wallet.ui.WalletFragment +import com.tangem.tap.features.welcome.ui.WelcomeFragment import com.tangem.wallet.R import timber.log.Timber @@ -110,5 +111,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.WalletConnectSessions -> WalletConnectFragment() AppScreen.QrScan -> QrScanFragment() AppScreen.ReferralProgram -> ReferralFragment() + AppScreen.Welcome -> WelcomeFragment() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index af79cb81e1..db49b1a222 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -33,6 +33,7 @@ import com.tangem.tap.features.tokens.redux.TokensMiddleware import com.tangem.tap.features.tokens.redux.TokensState import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware +import com.tangem.tap.features.welcome.redux.WelcomeState import org.rekotlin.Middleware import org.rekotlin.StateType @@ -51,6 +52,7 @@ data class AppState( val tokensState: TokensState = TokensState(), val walletConnectState: WalletConnectState = WalletConnectState(), val shopState: ShopState = ShopState(), + val welcomeState: WelcomeState = WelcomeState(), ) : StateType { val domainState: DomainState @@ -81,5 +83,4 @@ data class AppState( ) } } -} - +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt index 6d5993cbc0..aa3765d085 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -22,5 +22,6 @@ enum class AppScreen( AddTokens, AddCustomToken, WalletConnectSessions, QrScan, - ReferralProgram + ReferralProgram, + Welcome } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt new file mode 100644 index 0000000000..f68e5122d7 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.features.welcome.redux + +import com.tangem.common.core.TangemError +import org.rekotlin.Action + +internal sealed interface WelcomeAction : Action { + object ProceedWithBiometrics : WelcomeAction { + object Success : WelcomeAction + data class Error(val error: TangemError) : WelcomeAction + } + + object ProceedWithCard : WelcomeAction { + object Success : WelcomeAction + data class Error(val error: TangemError) : WelcomeAction + } + + object CloseError : WelcomeAction +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt new file mode 100644 index 0000000000..2d89c4d902 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt @@ -0,0 +1,5 @@ +package com.tangem.tap.features.welcome.redux + +import org.rekotlin.StateType + +class WelcomeState : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt new file mode 100644 index 0000000000..b80c8ac31b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt @@ -0,0 +1,74 @@ +package com.tangem.tap.features.welcome.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material.SnackbarHost +import androidx.compose.material.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.fragment.app.viewModels +import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.fragments.ComposeFragment +import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.welcome.ui.components.WelcomeScreenContent +import com.tangem.wallet.R + +internal class WelcomeFragment : ComposeFragment() { + private val viewModel by viewModels() + + @Composable + override fun provideState(): State { + return viewModel.state.collectAsState() + } + + @Composable + override fun ScreenContent( + modifier: Modifier, + state: WelcomeScreenState, + ) { + val snackbarHostState = remember { SnackbarHostState() } + val errorMessage by rememberUpdatedState(newValue = state.error?.customMessage) + + val backgroundColor = colorResource(id = R.color.background_primary) + SystemBarsEffect { + setSystemBarsColor(color = backgroundColor) + } + + Box( + modifier = modifier + .systemBarsPadding(), + ) { + WelcomeScreenContent( + showUnlockProgress = state.showUnlockWithBiometricsProgress, + showScanCardProgress = state.showUnlockWithCardProgress, + onUnlockClick = viewModel::unlockWallets, + onScanCardClick = viewModel::scanCard, + ) + + SnackbarHost( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(vertical = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + hostState = snackbarHostState, + ) + } + + LaunchedEffect(key1 = errorMessage) { + errorMessage?.let { + snackbarHostState.showSnackbar(it) + viewModel.closeError() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt new file mode 100644 index 0000000000..7a741a391f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt @@ -0,0 +1,11 @@ +package com.tangem.tap.features.welcome.ui + +import androidx.compose.runtime.Immutable +import com.tangem.common.core.TangemError + +@Immutable +internal data class WelcomeScreenState( + val showUnlockWithBiometricsProgress: Boolean = false, + val showUnlockWithCardProgress: Boolean = false, + val error: TangemError? = null, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt new file mode 100644 index 0000000000..ab9e8e6673 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt @@ -0,0 +1,49 @@ +package com.tangem.tap.features.welcome.ui + +import androidx.lifecycle.ViewModel +import com.tangem.tap.features.welcome.redux.WelcomeAction +import com.tangem.tap.features.welcome.redux.WelcomeState +import com.tangem.tap.store +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import org.rekotlin.StoreSubscriber + +internal class WelcomeViewModel : ViewModel(), StoreSubscriber { + private val stateInternal = MutableStateFlow(WelcomeScreenState()) + val state: StateFlow = stateInternal + + init { + subscribeToStoreChanges() + } + + fun unlockWallets() { + store.dispatch(WelcomeAction.ProceedWithBiometrics) + } + + fun scanCard() { + store.dispatch(WelcomeAction.ProceedWithCard) + } + + fun closeError() { + store.dispatch(WelcomeAction.CloseError) + } + + override fun newState(state: WelcomeState) { + stateInternal.update { prevState -> + // TODO: Update screen state + prevState + } + } + + override fun onCleared() { + store.unsubscribe(this) + } + + private fun subscribeToStoreChanges() { + store.subscribe(this) { appState -> + appState.skip { old, new -> old.welcomeState == new.welcomeState } + .select { it.welcomeState } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt new file mode 100644 index 0000000000..f9543d5c97 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt @@ -0,0 +1,122 @@ +package com.tangem.tap.features.welcome.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.PrimaryButtonIconRight +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.components.SpacerH12 +import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.components.SpacerH32 +import com.tangem.core.ui.components.SpacerHMax +import com.tangem.core.ui.res.TangemTheme +import com.tangem.wallet.R + +@Composable +internal fun WelcomeScreenContent( + modifier: Modifier = Modifier, + showUnlockProgress: Boolean, + showScanCardProgress: Boolean, + onUnlockClick: () -> Unit, + onScanCardClick: () -> Unit, +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + SpacerHMax() + Icon( + modifier = Modifier.size(TangemTheme.dimens.size96), + painter = painterResource(id = R.drawable.img_tangem_logo_96), + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + ) + SpacerH32() + Text( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + text = stringResource(R.string.welcome_unlock_title), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + SpacerH12() + Text( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing44) + .fillMaxWidth(), + text = stringResource(R.string.welcome_unlock_description), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + ) + SpacerHMax() + SecondaryButton( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + text = stringResource(R.string.welcome_unlock_face_id), + showProgress = showUnlockProgress, + onClick = onUnlockClick, + ) + SpacerH12() + PrimaryButtonIconRight( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + text = stringResource(R.string.welcome_unlock_card), + showProgress = showScanCardProgress, + icon = painterResource(id = R.drawable.ic_tangem), + onClick = onScanCardClick, + ) + SpacerH16() + } +} + +// region Preview +@Composable +private fun WelcomeScreenContentSample( + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + WelcomeScreenContent( + showUnlockProgress = false, + showScanCardProgress = false, + onUnlockClick = { /* no-op */ }, + onScanCardClick = { /* no-op */ }, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun WelcomeScreenContentPreview_Light() { + TangemTheme { + WelcomeScreenContentSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun WelcomeScreenContentPreview_Dark() { + TangemTheme(isDark = true) { + WelcomeScreenContentSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/app/src/main/res/drawable/img_tangem_logo_96.xml b/app/src/main/res/drawable/img_tangem_logo_96.xml new file mode 100644 index 0000000000..93fe8d9c0f --- /dev/null +++ b/app/src/main/res/drawable/img_tangem_logo_96.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt index 8293073012..a6425652bb 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt @@ -3,6 +3,7 @@ package com.tangem.core.ui.components import androidx.annotation.DimenRes +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size @@ -58,6 +59,12 @@ fun SpacerH24(modifier: Modifier = Modifier) { fun SpacerH32(modifier: Modifier = Modifier) { SpacerH(32.dp, modifier) } + +// TODO: Refactor with context receivers +@Composable +fun ColumnScope.SpacerHMax(modifier: Modifier = Modifier) { + Spacer(modifier = modifier.weight(1f)) +} // endregion Horizontal // region Vertical diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index 4732b620be..a05eb630a4 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -42,6 +42,7 @@ data class TangemDimens internal constructor( val size56: Dp = 56.dp, val size84: Dp = 84.dp, val size86: Dp = 86.dp, + val size96: Dp = 96.dp, val size102: Dp = 102.dp, val size164: Dp = 164.dp, val size200: Dp = 200.dp, diff --git a/core/ui/src/main/res/values-ru/strings_final.xml b/core/ui/src/main/res/values-ru/strings_final.xml index 5b98f5cc07..092befb3cc 100644 --- a/core/ui/src/main/res/values-ru/strings_final.xml +++ b/core/ui/src/main/res/values-ru/strings_final.xml @@ -139,4 +139,8 @@ В этом случае вам будет необходимо начать процесс заново. Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. Внутренняя ошибка: не удается найти менеджер кошельков + C возвращением! + Используйте биометрическую аутентификацию или код доступа для входа в приложение + Войти с биометрией + Сканировать карту diff --git a/core/ui/src/main/res/values/strings_final.xml b/core/ui/src/main/res/values/strings_final.xml index 6f46bdd1be..8ded5c6e4a 100644 --- a/core/ui/src/main/res/values/strings_final.xml +++ b/core/ui/src/main/res/values/strings_final.xml @@ -141,4 +141,8 @@ In this case, you will need to start from the beginning. You have used a card from another wallet. Tap the card associated with this wallet Internal error: wallet manager not found + Welcome back! + Use biometric authentication or scan a card to access the app + Log in with biometric + Scan card