Updated on 2026-08-14

This commit is contained in:
Tangem 2022-11-23 11:54:10 +03:00
parent 721010b489
commit 36a3e2f7d6
14 changed files with 317 additions and 3 deletions

View file

@ -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()
}
}

View file

@ -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(
)
}
}
}
}

View file

@ -22,5 +22,6 @@ enum class AppScreen(
AddTokens, AddCustomToken,
WalletConnectSessions,
QrScan,
ReferralProgram
ReferralProgram,
Welcome
}

View file

@ -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
}

View file

@ -0,0 +1,5 @@
package com.tangem.tap.features.welcome.redux
import org.rekotlin.StateType
class WelcomeState : StateType

View file

@ -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()
}
}
}
}

View file

@ -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,
)

View file

@ -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 }
}
}
}

View file

@ -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

View 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>

View file

@ -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

View file

@ -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,

View file

@ -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>

View file

@ -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>