Updated on 2026-08-14
This commit is contained in:
parent
721010b489
commit
36a3e2f7d6
14 changed files with 317 additions and 3 deletions
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -82,4 +84,3 @@ data class AppState(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,5 +22,6 @@ enum class AppScreen(
|
|||
AddTokens, AddCustomToken,
|
||||
WalletConnectSessions,
|
||||
QrScan,
|
||||
ReferralProgram
|
||||
ReferralProgram,
|
||||
Welcome
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.tap.features.welcome.redux
|
||||
|
||||
import org.rekotlin.StateType
|
||||
|
||||
class WelcomeState : StateType
|
||||
|
|
@ -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<WelcomeScreenState>() {
|
||||
private val viewModel by viewModels<WelcomeViewModel>()
|
||||
|
||||
@Composable
|
||||
override fun provideState(): State<WelcomeScreenState> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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<WelcomeState> {
|
||||
private val stateInternal = MutableStateFlow(WelcomeScreenState())
|
||||
val state: StateFlow<WelcomeScreenState> = 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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
15
app/src/main/res/drawable/img_tangem_logo_96.xml
Normal file
15
app/src/main/res/drawable/img_tangem_logo_96.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="96dp"
|
||||
android:height="96dp"
|
||||
android:viewportWidth="96"
|
||||
android:viewportHeight="96">
|
||||
<path
|
||||
android:fillColor="#1E1E1E"
|
||||
android:pathData="M56.27,47.27V78H69.56C70.07,78 70.57,77.8 70.93,77.43C71.3,77.07 71.5,76.57 71.5,76.06V47.27H56.27Z" />
|
||||
<path
|
||||
android:fillColor="#1E1E1E"
|
||||
android:pathData="M25.5,76.06C25.5,76.57 25.71,77.07 26.07,77.43C26.43,77.8 26.93,78 27.44,78H40.73V47.27H25.5V76.06Z" />
|
||||
<path
|
||||
android:fillColor="#1E1E1E"
|
||||
android:pathData="M71.5,32.2V18.94C71.5,18.43 71.29,17.93 70.93,17.57C70.56,17.2 70.07,17 69.55,17H27.45C26.94,17 26.44,17.2 26.07,17.57C25.71,17.93 25.5,18.43 25.5,18.94V32.2H71.5Z" />
|
||||
</vector>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -139,4 +139,8 @@
|
|||
<string name="onboarding_exit_alert_message">В этом случае вам будет необходимо начать процесс заново.</string>
|
||||
<string name="error_wrong_wallet_tapped">Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком.</string>
|
||||
<string name="internal_error_wallet_manager_not_found">Внутренняя ошибка: не удается найти менеджер кошельков</string>
|
||||
<string name="welcome_unlock_title">C возвращением!</string>
|
||||
<string name="welcome_unlock_description">Используйте биометрическую аутентификацию или код доступа для входа в приложение</string>
|
||||
<string name="welcome_unlock_face_id">Войти с биометрией</string>
|
||||
<string name="welcome_unlock_card">Сканировать карту</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -141,4 +141,8 @@
|
|||
<string name="onboarding_exit_alert_message">In this case, you will need to start from the beginning.</string>
|
||||
<string name="error_wrong_wallet_tapped">You have used a card from another wallet. Tap the card associated with this wallet</string>
|
||||
<string name="internal_error_wallet_manager_not_found" translatable="false">Internal error: wallet manager not found</string>
|
||||
<string name="welcome_unlock_title">Welcome back!</string>
|
||||
<string name="welcome_unlock_description">Use biometric authentication or scan a card to access the app</string>
|
||||
<string name="welcome_unlock_face_id">Log in with biometric</string>
|
||||
<string name="welcome_unlock_card">Scan card</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue