diff --git a/app/src/main/java/com/tangem/tap/features/welcome/component/DefaultWelcomeComponent.kt b/app/src/main/java/com/tangem/tap/features/welcome/component/DefaultWelcomeComponent.kt new file mode 100644 index 0000000000..b90ec7a20f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/component/DefaultWelcomeComponent.kt @@ -0,0 +1,36 @@ +package com.tangem.tap.features.welcome.component + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.tap.features.welcome.model.WelcomeModel +import com.tangem.tap.features.welcome.ui.components.WelcomeScreen +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultWelcomeComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted params: WelcomeComponent.Params, +) : WelcomeComponent, AppComponentContext by context { + + private val model: WelcomeModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.state.collectAsStateWithLifecycle() + + WelcomeScreen( + modifier = modifier, + state = state, + ) + } + + @AssistedFactory + interface Factory : WelcomeComponent.Factory { + override fun create(context: AppComponentContext, params: WelcomeComponent.Params): DefaultWelcomeComponent + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/di/ComponentModule.kt b/app/src/main/java/com/tangem/tap/features/welcome/di/ComponentModule.kt new file mode 100644 index 0000000000..68f1c6121f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/di/ComponentModule.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.features.welcome.di + +import com.tangem.tap.features.welcome.component.DefaultWelcomeComponent +import com.tangem.tap.features.welcome.component.WelcomeComponent +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface ComponentModule { + + @Binds + @Singleton + fun bindWelcomeComponentFactory(factory: DefaultWelcomeComponent.Factory): WelcomeComponent.Factory +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/di/ModelModule.kt b/app/src/main/java/com/tangem/tap/features/welcome/di/ModelModule.kt new file mode 100644 index 0000000000..1a90bd0bc5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/di/ModelModule.kt @@ -0,0 +1,20 @@ +package com.tangem.tap.features.welcome.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.tap.features.welcome.model.WelcomeModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(DecomposeComponent::class) +internal interface ModelModule { + + @Binds + @IntoMap + @ClassKey(WelcomeModel::class) + fun bindWelcomeModel(model: WelcomeModel): Model +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/model/WelcomeModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/model/WelcomeModel.kt new file mode 100644 index 0000000000..143654b18d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/model/WelcomeModel.kt @@ -0,0 +1,137 @@ +package com.tangem.tap.features.welcome.model + +import com.tangem.common.core.TangemError +import com.tangem.core.analytics.Analytics +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.navigation.finisher.AppFinisher +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.component.WelcomeComponent +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.store +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import org.rekotlin.StoreSubscriber +import javax.inject.Inject + +// FIXME: Remove redux: [REDACTED_JIRA] +internal class WelcomeModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val appFinisher: AppFinisher, + paramsContainer: ParamsContainer, +) : Model(), StoreSubscriber { + + private val params: WelcomeComponent.Params = paramsContainer.require() + private val initialState: WelcomeScreenState = WelcomeScreenState( + onPopBack = appFinisher::finish, + onUnlockClick = this::unlockWallets, + onScanCardClick = this::scanCard, + onCloseError = this::closeError, + ) + + val state: MutableStateFlow = MutableStateFlow(initialState) + + init { + store.dispatch(WelcomeAction.SetCoroutineScope(modelScope)) + + subscribeToStoreChanges() + initGlobalState() + + val welcomeAction = if (params.intent != null) { + WelcomeAction.ProceedWithIntent(params.intent.toIntent()) + } else { + WelcomeAction.ProceedWithBiometrics() + } + + store.dispatch(welcomeAction) + } + + private fun unlockWallets() { + Analytics.send(SignIn.ButtonBiometricSignIn()) + store.dispatch(WelcomeAction.ProceedWithBiometrics()) + } + + private fun scanCard() { + Analytics.send(SignIn.ButtonCardSignIn()) + store.dispatch(WelcomeAction.ProceedWithCard) + } + + private fun closeError() { + store.dispatch(WelcomeAction.CloseError) + } + + override fun newState(state: WelcomeState) { + val warning = createWarningIfNeeded(state.error) + + this.state.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() { + store.dispatch(WelcomeAction.ClearCoroutineScope) + store.unsubscribe(subscriber = this) + + super.onDestroy() + } + + 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() { + state.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) + } +} \ No newline at end of file