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