Updated on 2026-08-14

This commit is contained in:
Tangem 2025-03-26 20:10:59 +03:00
parent af29fec09f
commit 9e108e45d1
4 changed files with 9 additions and 222 deletions

View file

@ -14,6 +14,7 @@ import com.tangem.tap.features.welcome.redux.WelcomeAction
import com.tangem.tap.features.welcome.redux.WelcomeState
import com.tangem.tap.features.welcome.ui.WelcomeScreenState
import com.tangem.tap.features.welcome.ui.model.WarningModel
import com.tangem.tap.mainScope
import com.tangem.tap.store
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
@ -39,7 +40,7 @@ internal class WelcomeModel @Inject constructor(
val state: MutableStateFlow<WelcomeScreenState> = MutableStateFlow(initialState)
init {
store.dispatch(WelcomeAction.SetCoroutineScope(modelScope))
store.dispatch(WelcomeAction.SetCoroutineScope(mainScope))
subscribeToStoreChanges()
initGlobalState()

View file

@ -1,85 +0,0 @@
package com.tangem.tap.features.welcome.ui
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
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.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.analytics.Analytics
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.analytics.events.SignIn
import com.tangem.tap.common.extensions.eraseContext
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
import com.tangem.tap.features.welcome.ui.components.WarningDialog
import com.tangem.tap.features.welcome.ui.components.WelcomeScreenContent
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
internal class WelcomeFragment : ComposeFragment() {
@Inject
override lateinit var uiDependencies: UiDependencies
override fun onStart() {
super.onStart()
Analytics.eraseContext()
Analytics.send(SignIn.ScreenOpened())
}
@Composable
override fun ScreenContent(modifier: Modifier) {
val viewModel = hiltViewModel<WelcomeViewModel>()
LocalLifecycleOwner.current.lifecycle.addObserver(viewModel)
val state by viewModel.state.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference())
val warning by rememberUpdatedState(newValue = state.warning)
BackHandler {
requireActivity().finish()
}
Box(
modifier = modifier
.background(TangemTheme.colors.background.primary)
.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,
)
}
WarningDialog(warning)
LaunchedEffect(key1 = errorMessage) {
errorMessage?.let {
snackbarHostState.showSnackbar(it)
viewModel.closeError()
}
}
}
}

View file

@ -1,134 +0,0 @@
package com.tangem.tap.features.welcome.ui
import android.os.Bundle
import androidx.lifecycle.*
import com.tangem.common.core.TangemError
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.bundle.unbundle
import com.tangem.common.routing.entity.SerializableIntent
import com.tangem.core.analytics.Analytics
import com.tangem.domain.wallets.legacy.UserWalletsListError
import com.tangem.tap.common.analytics.events.SignIn
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.details.ui.cardsettings.TextReference
import com.tangem.tap.features.welcome.redux.WelcomeAction
import com.tangem.tap.features.welcome.redux.WelcomeState
import com.tangem.tap.features.welcome.ui.model.WarningModel
import com.tangem.tap.store
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import org.rekotlin.StoreSubscriber
import javax.inject.Inject
@HiltViewModel
internal class WelcomeViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
) : ViewModel(),
StoreSubscriber<WelcomeState>,
DefaultLifecycleObserver {
private val initialIntent: SerializableIntent? = savedStateHandle.get<Bundle>(AppRoute.Welcome.INITIAL_INTENT_KEY)
?.unbundle(SerializableIntent.serializer())
private val stateInternal = MutableStateFlow(WelcomeScreenState())
val state: StateFlow<WelcomeScreenState> = stateInternal
override fun onCreate(owner: LifecycleOwner) {
store.dispatch(WelcomeAction.SetCoroutineScope(viewModelScope))
subscribeToStoreChanges()
initGlobalState()
val welcomeAction = if (initialIntent != null) {
WelcomeAction.ProceedWithIntent(initialIntent.toIntent())
} else {
WelcomeAction.ProceedWithBiometrics()
}
store.dispatch(welcomeAction)
}
fun unlockWallets() {
Analytics.send(SignIn.ButtonBiometricSignIn())
store.dispatch(WelcomeAction.ProceedWithBiometrics())
}
fun scanCard() {
Analytics.send(SignIn.ButtonCardSignIn())
store.dispatch(WelcomeAction.ProceedWithCard)
}
fun closeError() {
store.dispatch(WelcomeAction.CloseError)
}
// TODO: Refactor errors handling
override fun newState(state: WelcomeState) {
val warning = createWarningIfNeeded(state.error)
stateInternal.update { prevState ->
prevState.copy(
showUnlockWithBiometricsProgress = state.isUnlockWithBiometricsInProgress,
showUnlockWithCardProgress = state.isUnlockWithCardInProgress,
warning = warning,
error = state.error
?.takeIf { !it.silent && warning == null }
?.let { e ->
e.messageResId?.let { TextReference.Res(it) }
?: TextReference.Str(e.customMessage)
},
)
}
}
override fun onDestroy(owner: LifecycleOwner) {
store.dispatch(WelcomeAction.ClearCoroutineScope)
store.unsubscribe(this)
super.onDestroy(owner)
}
private fun createWarningIfNeeded(error: TangemError?): WarningModel? {
return when (error) {
is UserWalletsListError.BiometricsAuthenticationLockout -> WarningModel.BiometricsLockoutWarning(
isPermanent = error.isPermanent,
onDismiss = this::dismissWarning,
)
is UserWalletsListError.AllKeysInvalidated,
is UserWalletsListError.NoUserWalletSelected,
-> WarningModel.KeyInvalidatedWarning(
onDismiss = this::dismissWarning,
)
is UserWalletsListError.BiometricsAuthenticationDisabled -> WarningModel.BiometricsDisabledWarning(
onDismiss = this::clearUserWallets,
)
else -> null
}
}
private fun dismissWarning() {
stateInternal.update { prevState ->
prevState.copy(
warning = null,
)
}
closeError()
}
private fun clearUserWallets() {
store.dispatch(WelcomeAction.ClearUserWallets)
}
private fun subscribeToStoreChanges() {
store.subscribe(this) { appState ->
appState.skip { old, new -> old.welcomeState == new.welcomeState }
.select { it.welcomeState }
}
}
private fun initGlobalState() {
store.dispatch(GlobalAction.RestoreAppCurrency)
store.dispatch(GlobalAction.ExchangeManager.Init)
}
}

View file

@ -34,7 +34,6 @@ import com.tangem.tap.features.onboarding.products.twins.ui.OnboardingTwinsFragm
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
import com.tangem.tap.features.welcome.component.WelcomeComponent
import com.tangem.tap.features.welcome.ui.WelcomeFragment
import com.tangem.tap.routing.component.RoutingComponent.Child
import com.tangem.tap.routing.toggle.RoutingFeatureToggles
import com.tangem.utils.Provider
@ -549,7 +548,13 @@ internal class ChildFactory @Inject constructor(
)
}
is AppRoute.Welcome -> {
route.asFragmentChild(Provider { WelcomeFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = WelcomeComponent.Params(
intent = route.intent,
),
componentFactory = welcomeComponentFactory,
)
}
is AppRoute.TesterMenu -> {
Child.LegacyIntent(testerRouter.getEntryIntent())