From 41dea25009771d79c85aaa408509760d5c8e17c9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Nov 2022 18:26:31 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../com/tangem/tap/ActivityResultCaller.kt | 8 ++ .../tangem/tap/ForegroundActivityObserver.kt | 32 ++++- .../java/com/tangem/tap/TapApplication.kt | 2 + .../com/tangem/tap/common/redux/AppReducer.kt | 2 + .../com/tangem/tap/common/redux/AppState.kt | 2 + .../redux/navigation/NavigationAction.kt | 2 + .../redux/navigation/NavigationMiddleware.kt | 26 ++++ .../com/tangem/tap/domain/TangemSdkManager.kt | 42 ++++-- .../com/tangem/tap/domain/model/UserWallet.kt | 3 + .../saveWallet/redux/SaveWalletAction.kt | 11 ++ .../saveWallet/redux/SaveWalletMiddleware.kt | 124 ++++++++++++++++++ .../saveWallet/redux/SaveWalletReducer.kt | 51 +++++++ .../saveWallet/redux/SaveWalletState.kt | 15 ++- .../ui/SaveWalletBottomSheetFragment.kt | 3 +- .../saveWallet/ui/SaveWalletScreenState.kt | 4 +- .../saveWallet/ui/SaveWalletViewModel.kt | 23 +++- .../tap/persistence/PreferencesStorage.kt | 21 +++ 17 files changed, 348 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/ActivityResultCaller.kt create mode 100644 app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt create mode 100644 app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt diff --git a/app/src/main/java/com/tangem/tap/ActivityResultCaller.kt b/app/src/main/java/com/tangem/tap/ActivityResultCaller.kt new file mode 100644 index 0000000000..16a05117ac --- /dev/null +++ b/app/src/main/java/com/tangem/tap/ActivityResultCaller.kt @@ -0,0 +1,8 @@ +package com.tangem.tap + +import android.content.Intent +import androidx.activity.result.ActivityResultLauncher + +interface ActivityResultCaller { + val activityResultLauncher: ActivityResultLauncher? +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt b/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt index 806769df5f..9664f38966 100644 --- a/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt +++ b/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt @@ -2,11 +2,18 @@ package com.tangem.tap import android.app.Activity import android.app.Application.ActivityLifecycleCallbacks +import android.content.Intent import android.os.Bundle -import java.util.WeakHashMap +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import java.util.* import kotlin.reflect.KClass -class ForegroundActivityObserver { +class ForegroundActivityObserver : ActivityResultCaller { + override var activityResultLauncher: ActivityResultLauncher? = null + private set + private val activities = WeakHashMap, Activity>() val foregroundActivity: Activity? @@ -15,25 +22,38 @@ class ForegroundActivityObserver { .firstOrNull() ?.value - val callbacks get() = Callbacks() + internal val callbacks: ActivityLifecycleCallbacks + get() = Callbacks() + + internal inner class Callbacks : ActivityLifecycleCallbacks { + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + activityResultLauncher = (activity as? AppCompatActivity)?.registerForActivityResult( + ActivityResultContracts.StartActivityForResult(), + ) { + /* no-op */ + } + } - inner class Callbacks : ActivityLifecycleCallbacks { override fun onActivityResumed(activity: Activity) { activities[activity::class] = activity } override fun onActivityDestroyed(activity: Activity) { activities.remove(activity::class) + if (activities.isEmpty()) { + activityResultLauncher = null + } } - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { - } override fun onActivityStarted(activity: Activity) { } + override fun onActivityPaused(activity: Activity) { } + override fun onActivityStopped(activity: Activity) { } + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { } } diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 287f2d7a1e..4b801de36a 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -53,6 +53,7 @@ import javax.inject.Inject lateinit var store: Store lateinit var foregroundActivityObserver: ForegroundActivityObserver +lateinit var activityResultCaller: ActivityResultCaller lateinit var preferencesStorage: PreferencesStorage lateinit var walletConnectRepository: WalletConnectRepository lateinit var shopService: TangemShopService @@ -82,6 +83,7 @@ class TapApplication : Application(), ImageLoaderFactory { } foregroundActivityObserver = ForegroundActivityObserver() + activityResultCaller = foregroundActivityObserver registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks) initMoshiConverter() diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt index 6e55404733..6d2bf6af62 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt @@ -10,6 +10,7 @@ import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteRedu import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsReducer import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsReducer import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer +import com.tangem.tap.features.saveWallet.redux.SaveWalletReducer import com.tangem.tap.features.send.redux.reducers.SendScreenReducer import com.tangem.tap.features.shop.redux.ShopReducer import com.tangem.tap.features.tokens.redux.TokensReducer @@ -38,6 +39,7 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder) walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState), shopState = ShopReducer.reduce(action, state.shopState), welcomeState = WelcomeReducer.reduce(action, state), + saveWalletState = SaveWalletReducer.reduce(action, state), ) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index b8b4bf689a..d85b69df46 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -25,6 +25,7 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayMiddleware +import com.tangem.tap.features.saveWallet.redux.SaveWalletMiddleware import com.tangem.tap.features.saveWallet.redux.SaveWalletState import com.tangem.tap.features.send.redux.middlewares.SendMiddleware import com.tangem.tap.features.send.redux.states.SendState @@ -86,6 +87,7 @@ data class AppState( BackupMiddleware().backupMiddleware, ShopMiddleware().shopMiddleware, WelcomeMiddleware().middleware, + SaveWalletMiddleware().middleware, ) } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt index e397d78d8a..df26b85edc 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt @@ -18,6 +18,8 @@ sealed class NavigationAction : Action { data class OpenDocument(val url: Uri) : NavigationAction() + object OpenBiometricsSettings : NavigationAction() + data class Share(val data: String) : NavigationAction() data class ActivityCreated(val activity: WeakReference) : NavigationAction() diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index 97d4c27134..da9430b1e1 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -1,6 +1,10 @@ package com.tangem.tap.common.redux.navigation import android.content.Intent +import android.hardware.biometrics.BiometricManager +import android.os.Build +import android.provider.Settings +import com.tangem.tap.activityResultCaller import com.tangem.tap.common.CustomTabsManager import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.openFragment @@ -48,6 +52,28 @@ val navigationMiddleware: Middleware = { _, state -> intent.data = action.url navState?.activity?.get()?.startActivity(intent) } + is NavigationAction.OpenBiometricsSettings -> { + val settingsAction = when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> { + Settings.ACTION_BIOMETRIC_ENROLL + } + Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> { + Settings.ACTION_FINGERPRINT_ENROLL + } + else -> { + Settings.ACTION_SETTINGS + } + } + val intent = Intent(settingsAction).apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + putExtra( + Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, + BiometricManager.Authenticators.BIOMETRIC_STRONG, + ) + } + } + activityResultCaller.activityResultLauncher?.launch(intent) + } is NavigationAction.Share -> { navState?.activity?.get()?.shareText(action.data) } diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index b1f24b354d..b7a54c7d6d 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -5,19 +5,12 @@ import androidx.annotation.StringRes import com.tangem.Message import com.tangem.TangemSdk import com.tangem.blockchain.common.Blockchain -import com.tangem.common.CardFilter -import com.tangem.common.CompletionResult -import com.tangem.common.SuccessResponse -import com.tangem.common.UserCodeType +import com.tangem.common.* import com.tangem.common.card.FirmwareVersion -import com.tangem.common.core.CardIdDisplayFormat -import com.tangem.common.core.CardSessionRunnable -import com.tangem.common.core.Config -import com.tangem.common.core.TangemSdkError -import com.tangem.common.core.UserCodeRequestPolicy +import com.tangem.common.core.* import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.hdWallet.DerivationPath -import com.tangem.common.map +import com.tangem.common.usersCode.UserCodeRepository import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.operations.CommandResponse @@ -43,6 +36,11 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) { + val canUseBiometry: Boolean + get() = tangemSdk.biometricManager.canAuthenticate || canEnrollBiometrics + + val canEnrollBiometrics: Boolean + get() = tangemSdk.biometricManager.canEnrollBiometrics suspend fun scanProduct( userTokensRepository: UserTokensRepository, @@ -104,6 +102,16 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co .map { CardDTO(it) } } + suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult { + return createUserCodeRepository().save( + cardIds = cardsIds, + userCode = UserCode( + type = UserCodeType.AccessCode, + stringValue = accessCode, + ), + ) + } + suspend fun setPasscode(cardId: String?): CompletionResult { return runTaskAsyncReturnOnMain( SetUserCodeCommand.changePasscode(null), @@ -136,9 +144,14 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co ) } - suspend fun scanCard(): CompletionResult { + suspend fun scanCard( + cardId: String? = null, + useBiometricsForAccessCode: Boolean = false, + ): CompletionResult { + setAccessCodeRequestPolicy(useBiometricsForAccessCode) return runTaskAsyncReturnOnMain( runnable = ScanTask(), + cardId = cardId, initialMessage = Message(context.getString(R.string.initial_message_tap_header)), ) .map { CardDTO(it) } @@ -190,6 +203,13 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } } + private fun createUserCodeRepository() = with(tangemSdk) { + UserCodeRepository( + biometricManager = biometricManager, + secureStorage = secureStorage, + ) + } + companion object { val config = Config( linkedTerminal = true, diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt index f649f4919f..6ce60fae85 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt @@ -15,6 +15,9 @@ data class UserWallet( val cardId: String get() = scanResponse.card.cardId + val hasAccessCode: Boolean + get() = scanResponse.card.isAccessCodeSet + companion object { suspend operator fun invoke( scanResponse: ScanResponse, diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt index 3e0bbe4a7e..46581d4432 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt @@ -1,9 +1,16 @@ package com.tangem.tap.features.saveWallet.redux import com.tangem.common.core.TangemError +import com.tangem.domain.common.ScanResponse import org.rekotlin.Action internal sealed interface SaveWalletAction : Action { + data class ProvideAdditionalInfo( + val scanResponse: ScanResponse, + val accessCode: String?, + val backupCardsIds: Set?, + ) : SaveWalletAction + object Save : SaveWalletAction { object Success : SaveWalletAction data class Error(val error: TangemError) : SaveWalletAction @@ -12,6 +19,10 @@ internal sealed interface SaveWalletAction : Action { object Dismiss : SaveWalletAction object CloseError : SaveWalletAction + object EnrollBiometrics : SaveWalletAction { + object Enroll : SaveWalletAction + object Cancel : SaveWalletAction + } object SaveWalletWasShown : SaveWalletAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt new file mode 100644 index 0000000000..844b55b115 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -0,0 +1,124 @@ +package com.tangem.tap.features.saveWallet.redux + +import com.tangem.common.CompletionResult +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.common.flatMap +import com.tangem.common.map +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.onUserWalletSelected +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userWalletsListManager +import kotlinx.coroutines.launch +import org.rekotlin.Middleware + +internal class SaveWalletMiddleware { + val middleware: Middleware = { _, stateProvider -> + { next -> + { action -> + val state = stateProvider() + if (action is SaveWalletAction && state != null) { + handleAction(action, state.saveWalletState) + } + next(action) + } + } + } + + private fun handleAction(action: SaveWalletAction, state: SaveWalletState) { + when (action) { + is SaveWalletAction.Save -> saveWalletIfBiometricsEnrolled(state) + is SaveWalletAction.Save.Success -> popBack() + is SaveWalletAction.EnrollBiometrics.Enroll -> enrollBiometrics() + is SaveWalletAction.SaveWalletWasShown -> saveWalletWasShown() + is SaveWalletAction.ProvideAdditionalInfo, + is SaveWalletAction.Dismiss, + is SaveWalletAction.CloseError, + is SaveWalletAction.Save.Error, + is SaveWalletAction.EnrollBiometrics, + is SaveWalletAction.EnrollBiometrics.Cancel, + -> Unit + } + } + + private fun enrollBiometrics() { + store.dispatchOnMain(NavigationAction.OpenBiometricsSettings) + } + + private fun saveWalletIfBiometricsEnrolled(state: SaveWalletState) { + if (tangemSdkManager.canEnrollBiometrics) { + store.dispatchOnMain(SaveWalletAction.EnrollBiometrics) + } else { + saveWallet(state) + } + } + + private fun saveWallet(state: SaveWalletState) { + val scanResponse = state.additionalInfo?.scanResponse + ?: store.state.globalState.scanResponse + ?: return + + scope.launch { + val userWallet = UserWallet( + scanResponse = scanResponse, + backupCardsIds = state.additionalInfo?.backupCardsIds, + ) + + userWalletsListManager.save(userWallet) + .flatMap { + trySaveAccessCode( + userWallet = userWallet, + additionalInfo = state.additionalInfo, + ) + } + .doOnFailure { error -> + store.dispatchOnMain(SaveWalletAction.Save.Error(error)) + } + .doOnSuccess { + preferencesStorage.shouldSaveUserWallets = true + preferencesStorage.shouldSaveAccessCodes = true + store.dispatchOnMain(SaveWalletAction.Save.Success) + store.onUserWalletSelected(userWallet) + } + } + } + + private fun popBack() { + store.dispatchOnMain(NavigationAction.PopBackTo()) + } + + private fun saveWalletWasShown() { + preferencesStorage.shouldShowSaveWallet = false + } + + private suspend fun trySaveAccessCode( + userWallet: UserWallet, + additionalInfo: SaveWalletState.WalletAdditionalInfo?, + ): CompletionResult { + return when { + additionalInfo?.accessCode != null -> { + tangemSdkManager.saveAccessCode( + accessCode = additionalInfo.accessCode, + cardsIds = userWallet.cardsInWallet, + ) + } + userWallet.hasAccessCode -> { + tangemSdkManager + .scanCard( + cardId = userWallet.cardId, + useBiometricsForAccessCode = true, + ) + .map { /* no-op */ } + } + else -> { + CompletionResult.Success(Unit) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt new file mode 100644 index 0000000000..9907306ea6 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt @@ -0,0 +1,51 @@ +package com.tangem.tap.features.saveWallet.redux + +import com.tangem.tap.common.redux.AppState +import org.rekotlin.Action + +internal object SaveWalletReducer { + fun reduce(action: Action, state: AppState): SaveWalletState { + return if (action is SaveWalletAction) { + internalReduce(action, state.saveWalletState) + } else state.saveWalletState + } + + private fun internalReduce(action: SaveWalletAction, state: SaveWalletState): SaveWalletState { + return when (action) { + is SaveWalletAction.ProvideAdditionalInfo -> state.copy( + additionalInfo = SaveWalletState.WalletAdditionalInfo( + scanResponse = action.scanResponse, + accessCode = action.accessCode, + backupCardsIds = action.backupCardsIds, + ), + ) + is SaveWalletAction.Save -> state.copy( + isSaveInProgress = true, + ) + is SaveWalletAction.Save.Error -> state.copy( + error = action.error, + isSaveInProgress = false, + ) + is SaveWalletAction.Save.Success -> state.copy( + additionalInfo = null, + isSaveInProgress = false, + ) + is SaveWalletAction.Dismiss -> state.copy( + additionalInfo = null, + ) + is SaveWalletAction.CloseError -> state.copy( + error = null, + ) + is SaveWalletAction.EnrollBiometrics -> state.copy( + needEnrollBiometrics = true, + ) + is SaveWalletAction.EnrollBiometrics.Enroll, + is SaveWalletAction.EnrollBiometrics.Cancel, + -> state.copy( + needEnrollBiometrics = false, + isSaveInProgress = false, + ) + is SaveWalletAction.SaveWalletWasShown -> state + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt index d43ee7c96b..50cb237ff2 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt @@ -1,5 +1,18 @@ package com.tangem.tap.features.saveWallet.redux +import com.tangem.common.core.TangemError +import com.tangem.domain.common.ScanResponse import org.rekotlin.StateType -class SaveWalletState : StateType \ No newline at end of file +data class SaveWalletState( + val additionalInfo: WalletAdditionalInfo? = null, + val isSaveInProgress: Boolean = false, + val needEnrollBiometrics: Boolean = false, + val error: TangemError? = null, +) : StateType { + data class WalletAdditionalInfo( + val scanResponse: ScanResponse, + val accessCode: String?, + val backupCardsIds: Set?, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt index 6abc3c607e..65da3e8dc5 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt @@ -20,6 +20,7 @@ import com.tangem.core.ui.components.dialogs.EnrollBiometricsDialogContent import com.tangem.core.ui.fragments.ComposeBottomSheetFragment import com.tangem.core.ui.models.EnrollBiometricsDialog import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.details.ui.cardsettings.resolveReference import com.tangem.tap.features.saveWallet.ui.components.SaveWalletScreenContent internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment() { @@ -43,7 +44,7 @@ internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment - // TODO: Update screen state - prevState + prevState.copy( + showProgress = state.isSaveInProgress, + enrollBiometricsDialog = if (state.needEnrollBiometrics) createEnrollBiometricsDialog() else null, + error = state.error + ?.takeUnless { it.silent } + ?.let { error -> + error.messageResId?.let { TextReference.Res(it) } + ?: TextReference.Str(error.customMessage) + }, + ) } } @@ -48,4 +58,13 @@ internal class SaveWalletViewModel : ViewModel(), StoreSubscriber