From 8f71e460a2f343ca08dd913e4bfc91c130da71cf Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 28 Feb 2023 14:44:37 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 34 +++++++++---- .../com/tangem/tap/common/extensions/Store.kt | 24 +++++++-- .../tap/common/redux/global/GlobalAction.kt | 3 ++ .../tap/common/redux/global/GlobalReducer.kt | 6 +++ .../tap/common/redux/global/GlobalState.kt | 4 +- .../com/tangem/tap/domain/TapWalletManager.kt | 3 ++ .../di/UserWalletsListManagerProvider.kt | 2 - .../details/redux/DetailsMiddleware.kt | 51 +++++++++++++++---- .../tap/features/home/redux/HomeMiddleware.kt | 43 ++++++++-------- .../features/onboarding/OnboardingHelper.kt | 27 ++++++++-- .../saveWallet/redux/SaveWalletMiddleware.kt | 42 ++++++++++++--- .../tap/features/wallet/redux/WalletAction.kt | 4 ++ .../tap/features/wallet/redux/WalletState.kt | 1 + .../redux/middlewares/WalletMiddleware.kt | 17 +++++-- .../wallet/redux/reducers/WalletReducer.kt | 5 ++ .../tap/features/wallet/ui/WalletFragment.kt | 16 +++--- .../tap/features/wallet/ui/WalletViewModel.kt | 51 ++++++++++++++++--- 17 files changed, 256 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index c3507df132..14d367046b 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -19,6 +19,7 @@ import com.tangem.tap.common.OnActivityResultCallback import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.NotificationsHandler +import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.common.shop.googlepay.GooglePayService @@ -27,6 +28,7 @@ import com.tangem.tap.common.shop.googlepay.GooglePayUtil.createPaymentsClient import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation +import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction import com.tangem.tap.features.shop.redux.ShopAction import com.tangem.tap.features.welcome.redux.WelcomeAction @@ -45,11 +47,8 @@ import kotlin.coroutines.CoroutineContext lateinit var tangemSdk: TangemSdk lateinit var tangemSdkManager: TangemSdkManager lateinit var backupService: BackupService -lateinit var userWalletsListManager: UserWalletsListManager internal var lockUserWalletsTimer: LockUserWalletsTimer? = null private set -var userWalletsListManagerSafe: UserWalletsListManager? = null - private set var notificationsHandler: NotificationsHandler? = null private val coroutineContext: CoroutineContext @@ -60,6 +59,12 @@ private val mainCoroutineContext: CoroutineContext get() = Job() + Dispatchers.Main + FeatureCoroutineExceptionHandler.create("mainScope") val mainScope = CoroutineScope(mainCoroutineContext) +// TODO: Move to DI +val userWalletsListManagerSafe: UserWalletsListManager? + get() = store.state.globalState.userWalletsListManager +val userWalletsListManager: UserWalletsListManager + get() = userWalletsListManagerSafe!! + @AndroidEntryPoint class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbackHolder { @@ -83,14 +88,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac appStateHolder.tangemSdkManager = tangemSdkManager appStateHolder.tangemSdk = tangemSdk backupService = BackupService.init(tangemSdk, this) - userWalletsListManager = UserWalletsListManager.provideBiometricImplementation( - context = applicationContext, - tangemSdkManager = tangemSdkManager, - ) - appStateHolder.userWalletsListManager = userWalletsListManager - userWalletsListManagerSafe = userWalletsListManager lockUserWalletsTimer = LockUserWalletsTimer(owner = this) + initUserWalletsListManager() + store.dispatch( ShopAction.CheckIfGooglePayAvailable( GooglePayService(createPaymentsClient(this), this), @@ -98,6 +99,18 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac ) } + private fun initUserWalletsListManager() { + val manager = if (preferencesStorage.shouldSaveUserWallets) { + UserWalletsListManager.provideBiometricImplementation( + context = applicationContext, + tangemSdkManager = tangemSdkManager, + ) + } else { + UserWalletsListManager.provideRuntimeImplementation() + } + store.dispatch(GlobalAction.UpdateUserWalletsListManager(manager)) + } + private fun systemActions() { WindowCompat.setDecorFitsSystemWindows(window, false) @@ -200,11 +213,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac backStackIsEmpty -> { navigateToInitialScreen(intent) } + else -> Unit } } private fun navigateToInitialScreen(intent: Intent?) { - if (userWalletsListManager.hasUserWallets) { + if (store.state.globalState.userWalletsListManager?.hasUserWallets == true) { store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome)) store.dispatchOnMain(WelcomeAction.HandleIntentIfNeeded(intent)) } else { diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index 69c5f1c085..77ed58315d 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -2,6 +2,7 @@ package com.tangem.tap.common.extensions import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext +import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.NavigationAction @@ -16,10 +17,25 @@ import org.rekotlin.Store /** * Dispatch action with creating the new coroutine with the Main dispatcher + * + * @see dispatchWithMain */ fun Store<*>.dispatchOnMain(action: Action) { scope.launch(Dispatchers.Main) { - store.dispatch(action) + dispatch(action) + } +} + +/** + * Dispatch action on the Main coroutine context + * + * @param action [Action] to be dispatched + * + * @see dispatchOnMain + * */ +suspend fun Store<*>.dispatchWithMain(action: Action) { + withMainContext { + dispatch(action) } } @@ -27,9 +43,8 @@ fun Store<*>.dispatchNotification(resId: Int) { dispatchOnMain(GlobalAction.ShowNotification(resId)) } -@Suppress("UnusedReceiverParameter") -suspend fun Store<*>.onUserWalletSelected(userWallet: UserWallet, refresh: Boolean = false) { - store.state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh) +suspend fun Store.onUserWalletSelected(userWallet: UserWallet, refresh: Boolean = false) { + state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh) } fun Store<*>.dispatchToastNotification(resId: Int) { @@ -77,6 +92,7 @@ suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse) { fun Store<*>.dispatchOpenUrl(url: String) { store.dispatch(NavigationAction.OpenUrl(url)) } + fun Store<*>.dispatchShare(url: String) { store.dispatch(NavigationAction.Share(url)) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index c4c8a33527..f0f6e8244c 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -18,6 +18,7 @@ import com.tangem.tap.common.redux.ToastNotificationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager +import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.features.details.redux.SecurityOption import org.rekotlin.Action @@ -105,4 +106,6 @@ sealed class GlobalAction : Action { object FetchUserCountry : GlobalAction() { data class Success(val countryCode: String) : GlobalAction() } + + data class UpdateUserWalletsListManager(val manager: UserWalletsListManager) : GlobalAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 7934a195f6..3ed3751c74 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -84,6 +84,12 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde is GlobalAction.FetchUserCountry.Success -> globalState.copy( userCountryCode = action.countryCode, ) + is GlobalAction.UpdateUserWalletsListManager -> { + appStateHolder.userWalletsListManager = action.manager + globalState.copy( + userWalletsListManager = action.manager, + ) + } else -> globalState } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index 845715809d..56059c6799 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -1,13 +1,14 @@ package com.tangem.tap.common.redux.global +import com.tangem.datasource.config.ConfigManager import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.domain.PayIdManager import com.tangem.tap.domain.TapWalletManager -import com.tangem.datasource.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager +import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.features.onboarding.OnboardingManager import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import org.rekotlin.StateType @@ -26,6 +27,7 @@ data class GlobalState( val dialog: StateDialog? = null, val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(), val userCountryCode: String? = null, + val userWalletsListManager: UserWalletsListManager? = null, ) : StateType typealias CryptoCurrencyName = String diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 70a1526ce3..4b34bbb8e7 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -36,6 +36,7 @@ import com.tangem.tap.features.wallet.models.toBlockchainNetworks import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.middlewares.handleBasicAnalyticsEvent import com.tangem.tap.network.NetworkConnectivity +import com.tangem.tap.preferencesStorage import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.tap.userTokensRepository @@ -113,8 +114,10 @@ class TapWalletManager { store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(scanResponse) updateConfigManager(scanResponse) withMainContext { + // Order is important store.dispatch(DisclaimerAction.SetDisclaimer(card.createDisclaimer())) store.dispatch(WalletAction.UserWalletChanged(userWallet)) + store.dispatch(WalletAction.UpdateCanSaveUserWallets(preferencesStorage.shouldSaveUserWallets)) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) store.dispatch(WalletConnectAction.ResetState) store.dispatch(GlobalAction.SaveScanResponse(scanResponse)) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt index 163c0cdc60..108547e8b2 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt @@ -71,8 +71,6 @@ fun UserWalletsListManager.Companion.provideBiometricImplementation( ) } -// TODO: Will be used in further MR's -@Suppress("unused") fun UserWalletsListManager.Companion.provideRuntimeImplementation(): UserWalletsListManager { return RuntimeUserWalletsListManager() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index d5032c7ce7..4ed3ac44b3 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -4,6 +4,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess +import com.tangem.common.extensions.guard import com.tangem.common.flatMap import com.tangem.core.analytics.Analytics import com.tangem.domain.common.ScanResponse @@ -12,6 +13,7 @@ import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState @@ -20,11 +22,16 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.model.builders.UserWalletIdBuilder +import com.tangem.tap.domain.userWalletList.UserWalletsListManager +import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation +import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation import com.tangem.tap.domain.userWalletList.isLockedSync import com.tangem.tap.domain.userWalletList.lockIfLockable import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.foregroundActivityObserver import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store @@ -236,7 +243,7 @@ class DetailsMiddleware { delay(timeMillis = 100) } } - store.dispatchOnMain( + store.dispatchWithMain( DetailsAction.AppSettings.BiometricsStatusChanged( needEnrollBiometrics = tangemSdkManager.needEnrollBiometrics, ), @@ -252,13 +259,13 @@ class DetailsMiddleware { private fun toggleSaveWallets(state: DetailsState, enable: Boolean) = scope.launch { // Nothing to change if (preferencesStorage.shouldSaveUserWallets == enable) { - store.dispatchOnMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) + store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) return@launch } toggleSaveWallets(state.scanResponse, enable) .doOnFailure { - store.dispatchOnMain( + store.dispatchWithMain( DetailsAction.AppSettings.SwitchPrivacySetting.Failure( prevState = !enable, setting = AppSetting.SaveWallets, @@ -266,7 +273,7 @@ class DetailsMiddleware { ) } .doOnSuccess { - store.dispatchOnMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) + store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) } } @@ -284,13 +291,13 @@ class DetailsMiddleware { private fun toggleSaveAccessCodes(state: DetailsState, enable: Boolean) = scope.launch { // Nothing to change if (preferencesStorage.shouldSaveAccessCodes == enable) { - store.dispatchOnMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) + store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) return@launch } toggleSaveAccessCodes(state.scanResponse, state.appSettingsState.saveWallets, enable) .doOnFailure { - store.dispatchOnMain( + store.dispatchWithMain( DetailsAction.AppSettings.SwitchPrivacySetting.Failure( prevState = !enable, setting = AppSetting.SaveAccessCode, @@ -298,7 +305,7 @@ class DetailsMiddleware { ) } .doOnSuccess { - store.dispatchOnMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) + store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success) } } @@ -327,6 +334,8 @@ class DetailsMiddleware { TangemSdkError.ExceptionError(IllegalStateException("scanResponse is null")), ) + updateUserWalletsListManager(enableUserWalletsSaving = true) + return userWalletsListManager.save(userWallet) .flatMap { if (enableAccessCodesSaving) { @@ -341,7 +350,7 @@ class DetailsMiddleware { preferencesStorage.shouldShowSaveUserWalletScreen = false preferencesStorage.shouldSaveUserWallets = true - store.onUserWalletSelected(userWallet) + store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true)) } .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") @@ -354,9 +363,11 @@ class DetailsMiddleware { .doOnSuccess { Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off)) deleteSavedAccessCodes() + updateUserWalletsListManager(enableUserWalletsSaving = false) preferencesStorage.shouldSaveUserWallets = false - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) + store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true)) + store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home)) } .doOnFailure { error -> Timber.e(error, "Unable to delete saved wallets") @@ -388,5 +399,27 @@ class DetailsMiddleware { Timber.e(error, "Unable to delete saved access codes") } } + + private suspend fun updateUserWalletsListManager(enableUserWalletsSaving: Boolean) { + val manager = if (enableUserWalletsSaving) { + createBiometricsUserWalletsManager() ?: return + } else { + UserWalletsListManager.provideRuntimeImplementation() + } + + store.dispatchWithMain(GlobalAction.UpdateUserWalletsListManager(manager)) + } + + private fun createBiometricsUserWalletsManager(): UserWalletsListManager? { + val context = foregroundActivityObserver.foregroundActivity?.applicationContext.guard { + Timber.e(IllegalStateException("No activities in foreground")) + return null + } + + return UserWalletsListManager.provideBiometricImplementation( + context = context, + tangemSdkManager = tangemSdkManager, + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 72c24acdb4..0ea4b412cf 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -5,13 +5,13 @@ import com.tangem.common.doOnResult import com.tangem.common.doOnSuccess import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.analytics.events.Shop import com.tangem.tap.common.entities.IndeterminateProgressButton import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.eraseContext -import com.tangem.tap.common.extensions.onCardScanned import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -94,29 +94,32 @@ private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch { changeButtonState(ButtonState.ENABLED) }, onSuccess = { scanResponse -> - scope.launch { - if (preferencesStorage.shouldSaveUserWallets) { - val userWallet = UserWalletBuilder(scanResponse).build() ?: return@launch - userWalletsListManager.save(userWallet) - .doOnFailure { error -> - Timber.e(error, "Unable to save user wallet") - store.onCardScanned(scanResponse) - } - .doOnSuccess { - scope.launch { store.onUserWalletSelected(userWallet) } - } - .doOnResult { - navigateTo(AppScreen.Wallet) - } - } else { - store.onCardScanned(scanResponse) - navigateTo(AppScreen.Wallet) - } - } + proceedWithScanResponse(scanResponse) }, ) } +fun proceedWithScanResponse(scanResponse: ScanResponse) { + scope.launch { + val userWallet = UserWalletBuilder(scanResponse).build() + if (userWallet == null) { + Timber.e("User wallet not created") + return@launch + } + + userWalletsListManager.save(userWallet) + .doOnFailure { error -> + Timber.e(error, "Unable to save user wallet") + } + .doOnSuccess { + scope.launch { store.onUserWalletSelected(userWallet) } + } + .doOnResult { + navigateTo(AppScreen.Wallet) + } + } +} + private suspend fun navigateTo(appScreen: AppScreen) { store.dispatchOnMain(NavigationAction.NavigateTo(appScreen)) delay(timeMillis = 200) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index e0d51ccbf7..426d274352 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -1,21 +1,27 @@ package com.tangem.tap.features.onboarding +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.onCardScanned +import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.extensions.removeContext import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.features.saveWallet.redux.SaveWalletAction 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.delay import kotlinx.coroutines.launch +import timber.log.Timber /** [REDACTED_AUTHOR] @@ -75,7 +81,7 @@ object OnboardingHelper { // then open save wallet screen tangemSdkManager.canUseBiometry && preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch { - store.onCardScanned(scanResponse) + proceedWithScanResponse(scanResponse) delay(timeMillis = 1_200) @@ -90,7 +96,7 @@ object OnboardingHelper { } // If device has no biometry and save wallet screen has been shown, then go through old scenario else -> scope.launch { - store.onCardScanned(scanResponse) + proceedWithScanResponse(scanResponse) } } @@ -100,4 +106,19 @@ object OnboardingHelper { fun onInterrupted() { Analytics.removeContext() } + + private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) { + val userWallet = UserWalletBuilder(scanResponse).build().guard { + Timber.e("User wallet not created") + return + } + + userWalletsListManager.save(userWallet) + .doOnFailure { error -> + Timber.e(error, "Unable to save user wallet") + } + .doOnSuccess { + scope.launch { store.onUserWalletSelected(userWallet) } + } + } } \ 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 index 5eaa50d8bc..921894565b 100644 --- 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 @@ -1,19 +1,26 @@ package com.tangem.tap.features.saveWallet.redux import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess +import com.tangem.common.extensions.guard import com.tangem.common.flatMap import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.onUserWalletSelected +import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder +import com.tangem.tap.domain.userWalletList.UserWalletsListManager +import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.foregroundActivityObserver import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store @@ -21,6 +28,7 @@ import com.tangem.tap.tangemSdkManager import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.launch import org.rekotlin.Middleware +import timber.log.Timber internal class SaveWalletMiddleware { val middleware: Middleware = { _, stateProvider -> @@ -86,16 +94,19 @@ internal class SaveWalletMiddleware { } scope.launch { - val userWallet = UserWalletBuilder(scanResponse) - .backupCardsIds(state.backupInfo?.backupCardsIds) - .build() ?: return@launch + val userWallet = userWalletsListManager.selectedUserWalletSync + ?: UserWalletBuilder(scanResponse) + .backupCardsIds(state.backupInfo?.backupCardsIds) + .build() ?: return@launch + + provideBiometricUserWalletsListManager() val isFirstSavedWallet = !userWalletsListManager.hasUserWallets saveAccessCodeIfNeeded(state.backupInfo?.accessCode, userWallet.cardsInWallet) .flatMap { userWalletsListManager.save(userWallet, canOverride = true) } .doOnFailure { error -> - store.dispatchOnMain(SaveWalletAction.Save.Error(error)) + store.dispatchWithMain(SaveWalletAction.Save.Error(error)) } .doOnSuccess { preferencesStorage.shouldSaveUserWallets = true @@ -104,14 +115,29 @@ internal class SaveWalletMiddleware { preferencesStorage.shouldSaveAccessCodes = isFirstSavedWallet || preferencesStorage.shouldSaveAccessCodes - store.dispatchOnMain(SaveWalletAction.Save.Success) + store.dispatchWithMain(SaveWalletAction.Save.Success) - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(userWallet) + store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true)) } } } + private suspend fun provideBiometricUserWalletsListManager() { + val context = foregroundActivityObserver.foregroundActivity?.applicationContext.guard { + val error = IllegalStateException("No activities in foreground") + Timber.e(error) + store.dispatchWithMain(SaveWalletAction.Save.Error(TangemSdkError.ExceptionError(error))) + return + } + val manager = UserWalletsListManager.provideBiometricImplementation( + context = context, + tangemSdkManager = tangemSdkManager, + ) + + store.dispatchWithMain(GlobalAction.UpdateUserWalletsListManager(manager)) + } + private fun dismiss(state: SaveWalletState) { if (state.backupInfo != null) { // TODO: Remove after onboarding refactoring diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index f839eb03fe..a305989a17 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -24,6 +24,10 @@ import java.math.BigDecimal sealed class WalletAction : Action { + object PopBackToInitialScreen : WalletAction() + + data class UpdateCanSaveUserWallets(val canSaveUserWallets: Boolean) : WalletAction() + data class ResetState(val newCard: CardDTO) : WalletAction() data class SetIfTestnetCard(val isTestnet: Boolean) : WalletAction() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 973966bc4f..23c86b2a7e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -45,6 +45,7 @@ data class WalletState( val derivationsCheckIsScheduled: Boolean = false, val loadingUserTokens: Boolean = false, val walletCardsCount: Int? = null, + val canSaveUserWallets: Boolean = false, ) : StateType { val walletsDataFromStores: List diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index b1d86b23f1..d62cd3d034 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -38,6 +38,7 @@ import com.tangem.tap.domain.loadedRates import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.UserWalletIdBuilder +import com.tangem.tap.domain.userWalletList.lockIfLockable import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.send.redux.PrepareSendScreen @@ -300,7 +301,7 @@ class WalletMiddleware { showSaveWalletIfNeeded() } is WalletAction.ChangeWallet -> { - changeWallet() + changeWallet(walletState) } is WalletAction.UserWalletChanged -> Unit is WalletAction.WalletStoresChanged -> { @@ -311,6 +312,16 @@ class WalletMiddleware { tryToShowAppRatingWarning(action.walletStores) } is WalletAction.TotalFiatBalanceChanged -> Unit + is WalletAction.PopBackToInitialScreen -> { + userWalletsListManager.lockIfLockable() + val screen = if (walletState.canSaveUserWallets) { + AppScreen.Welcome + } else { + AppScreen.Home + } + + store.dispatchOnMain(NavigationAction.PopBackTo(screen)) + } } } @@ -375,9 +386,9 @@ class WalletMiddleware { } } - private fun changeWallet() { + private fun changeWallet(state: WalletState) { when { - userWalletsListManager.hasUserWallets -> { + state.canSaveUserWallets -> { Analytics.send(MainScreen.ButtonMyWallets()) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletSelector)) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index c1157544cb..d79d7a6339 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -367,6 +367,11 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS ), ) } + is WalletAction.UpdateCanSaveUserWallets -> { + newState = newState.copy( + canSaveUserWallets = action.canSaveUserWallets, + ) + } else -> Unit } appStateHolder.walletState = newState diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index e35f31191d..3e99ac95ed 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -32,7 +32,6 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.statePrinter.printScanResponseState import com.tangem.tap.domain.statePrinter.printWalletState -import com.tangem.tap.domain.userWalletList.lockIfLockable import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.wallet.redux.ErrorType import com.tangem.tap.features.wallet.redux.ProgressState @@ -44,7 +43,6 @@ import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView import com.tangem.tap.features.wallet.ui.wallet.WalletView import com.tangem.tap.features.wallet.ui.wallet.saltPay.SaltPayWalletView import com.tangem.tap.store -import com.tangem.tap.userWalletsListManager import com.tangem.wallet.BuildConfig import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentWalletBinding @@ -82,13 +80,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { + private var observeWalletStoresUpdatesJob: Job? = null + set(value) { + field?.cancel() + field = value + } + + init { + subscribeToUserWalletsListManagerUpdates() + } + + override fun onCleared() { + store.unsubscribe(this) + } + + override fun newState(state: UserWalletsListManager?) { + // Restarting observing of wallet store updates when the manager changes + if (state != null) { + bootstrapSelectedWalletStoresChanges(state) + } + } -internal class WalletViewModel : ViewModel() { fun launch() { - bootstrapSelectedWalletStoresChanges() + val manager = store.state.globalState.userWalletsListManager + if (manager != null) { + bootstrapSelectedWalletStoresChanges(manager) + } bootstrapShowSaveWalletIfNeeded() } - private fun bootstrapSelectedWalletStoresChanges() { - userWalletsListManager.selectedUserWallet + private fun bootstrapSelectedWalletStoresChanges(manager: UserWalletsListManager) { + observeWalletStoresUpdatesJob = manager.selectedUserWallet .map { it.walletId } - .distinctUntilChanged() .flatMapLatest { selectedUserWalletId -> walletStoresManager.get(selectedUserWalletId) } @@ -40,4 +67,14 @@ internal class WalletViewModel : ViewModel() { store.dispatchOnMain(WalletAction.ShowSaveWalletIfNeeded) } } + + private fun subscribeToUserWalletsListManagerUpdates() { + store.subscribe(this) { appState -> + appState + .skip { old, new -> + old.globalState.userWalletsListManager == new.globalState.userWalletsListManager + } + .select { it.globalState.userWalletsListManager } + } + } } \ No newline at end of file