diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt deleted file mode 100644 index 06dd58d367..0000000000 --- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.tangem.tap.common.extensions - -import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.blockchain.common.Wallet -import com.tangem.blockchain.common.WalletManager -import com.tangem.blockchainsdk.utils.amountToCreateAccount -import com.tangem.common.services.Result -import com.tangem.tap.common.TestActions -import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.getFirstToken -import com.tangem.tap.proxy.redux.DaggerGraphState -import com.tangem.tap.store -import com.tangem.utils.logging.TangemLogger -import kotlinx.coroutines.delay - -/** -[REDACTED_AUTHOR] - */ -@Deprecated( - message = "Use WalletStoresManager.fetch({userWalletId}, refresh = true) (to update all user wallet tokens)" + - "or WalletCurrenciesManager.update(...) (to update only one user wallet blockchain and its tokens) instead", -) -@Suppress("MagicNumber") -suspend fun WalletManager.safeUpdate(isDemoCard: Boolean): Result = try { - if (isDemoCard || TestActions.isTestAmountInjectionForWalletManagerEnabled) { - delay(500) - TestActions.isTestAmountInjectionForWalletManagerEnabled = false - Result.Success(wallet) - } else { - update() - Result.Success(wallet) - } -} catch (exception: Exception) { - TangemLogger.e("Error", exception) - - val networkConnectionManager = store.inject(DaggerGraphState::networkConnectionManager) - if (!networkConnectionManager.isOnline) { - Result.Failure(TapError.NoInternetConnection()) - } else { - val blockchain = wallet.blockchain - val amountToCreateAccount = blockchain.amountToCreateAccount(this, wallet.getFirstToken()) - - if (exception is BlockchainSdkError.AccountNotFound && amountToCreateAccount != null) { - Result.Failure(TapError.WalletManager.NoAccountError(amountToCreateAccount.toString())) - } else { - when (exception) { - is BlockchainSdkError -> Result.Failure(exception) - else -> { - val message = exception.cause?.localizedMessage ?: "Unknown error" - Result.Failure(TapError.WalletManager.InternalError(message)) - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index e3e54d1699..bc3f263741 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -4,7 +4,6 @@ import com.tangem.datasource.api.moonpay.MoonPayApi import com.tangem.datasource.local.config.environment.EnvironmentConfig import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository -import com.tangem.utils.coroutines.AppCoroutineScope import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.express.ExpressServiceFetcher import com.tangem.domain.tokens.repository.CurrenciesRepository @@ -14,8 +13,7 @@ import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository import com.tangem.tap.network.exchangeServices.DefaultRampManager import com.tangem.tap.network.exchangeServices.SellService import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService -import com.tangem.tap.proxy.AppStateHolder -import com.tangem.utils.Provider +import com.tangem.utils.coroutines.AppCoroutineScope import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -45,13 +43,13 @@ internal object ActivityModule { @Provides @Singleton fun provideDefaultRampManager( - appStateHolder: AppStateHolder, + sellService: SellService, expressServiceFetcher: ExpressServiceFetcher, currenciesRepository: CurrenciesRepository, dispatchers: CoroutineDispatcherProvider, ): RampStateManager { return DefaultRampManager( - sellService = Provider { requireNotNull(appStateHolder.sellService) }, + sellService = sellService, expressServiceFetcher = expressServiceFetcher, currenciesRepository = currenciesRepository, dispatchers = dispatchers, diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index e0b447a00d..f409d2dd20 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -2,8 +2,8 @@ package com.tangem.tap.di.domain import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.account.repository.AccountsCRUDRepository +import com.tangem.domain.common.wallets.UserWalletSelectedHandler import com.tangem.domain.common.wallets.UserWalletsListRepository -import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.transaction.WalletAddressServiceRepository import com.tangem.domain.transaction.usecase.ParseSharedAddressUseCase import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase @@ -24,6 +24,7 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyApyUpdateUseCase import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSyncUseCase import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase import com.tangem.operations.attestation.CardArtworksProvider +import com.tangem.tap.domain.DefaultUserWalletSelectedHandler import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -151,14 +152,14 @@ internal object WalletsDomainModule { @Provides @Singleton - fun providesSelectWalletUseCase( - userWalletsListRepository: UserWalletsListRepository, - reduxStateHolder: ReduxStateHolder, - ): SelectWalletUseCase { - return SelectWalletUseCase( - userWalletsListRepository = userWalletsListRepository, - reduxStateHolder = reduxStateHolder, - ) + fun providesSelectWalletUseCase(userWalletsListRepository: UserWalletsListRepository): SelectWalletUseCase { + return SelectWalletUseCase(userWalletsListRepository = userWalletsListRepository) + } + + @Provides + @Singleton + fun providesUserWalletSelectedHandler(handler: DefaultUserWalletSelectedHandler): UserWalletSelectedHandler { + return handler } @Provides diff --git a/app/src/main/java/com/tangem/tap/domain/DefaultUserWalletSelectedHandler.kt b/app/src/main/java/com/tangem/tap/domain/DefaultUserWalletSelectedHandler.kt new file mode 100644 index 0000000000..31ce9a9df3 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/DefaultUserWalletSelectedHandler.kt @@ -0,0 +1,61 @@ +package com.tangem.tap.domain + +import com.tangem.core.analytics.utils.TrackingContextProxy +import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.common.wallets.UserWalletSelectedHandler +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.sdk.api.TangemSdkManager +import com.tangem.utils.coroutines.AppCoroutineScope +import com.tangem.utils.coroutines.JobHolder +import com.tangem.utils.coroutines.saveInAndJoin +import kotlinx.coroutines.launch +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Default implementation of [UserWalletSelectedHandler]. + * + * Runs three side effects on every selection: updates the analytics tracking context, updates the + * Tangem SDK displayed card-id numbers count (cold wallets only), and recomputes the access code + * request policy (cold wallets only). Hot wallets trigger only the tracking-context update. + * + * Invocations are serialised via [JobHolder]: if a new [invoke] arrives while the previous one is + * still running, the previous load is cancelled and the new one replaces it. The method suspends + * until the newly launched load completes. + */ +@Singleton +internal class DefaultUserWalletSelectedHandler @Inject constructor( + private val trackingContextProxy: TrackingContextProxy, + private val tangemSdkManager: TangemSdkManager, + private val settingsRepository: SettingsRepository, + private val cardSdkConfigRepository: CardSdkConfigRepository, + private val appCoroutineScope: AppCoroutineScope, +) : UserWalletSelectedHandler { + + private val loadUserWalletDataJob: JobHolder = JobHolder() + + override suspend fun invoke(userWallet: UserWallet) { + appCoroutineScope.launch { loadUserWalletData(userWallet) } + .saveInAndJoin(loadUserWalletDataJob) + } + + private suspend fun loadUserWalletData(userWallet: UserWallet) { + trackingContextProxy.setContext(userWallet) + + if (userWallet is UserWallet.Cold) { + val scanResponse = userWallet.scanResponse + tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) + updateAccessCodeRequestPolicy(scanResponse) + } + } + + private suspend fun updateAccessCodeRequestPolicy(scanResponse: ScanResponse) { + val shouldSaveAccessCodes = settingsRepository.shouldSaveAccessCodes() + + cardSdkConfigRepository.setAccessCodeRequestPolicy( + isBiometricsRequestPolicy = shouldSaveAccessCodes && scanResponse.card.isAccessCodeSet, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListRepositoryModule.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListRepositoryModule.kt index d7d9089767..f9323c265d 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListRepositoryModule.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListRepositoryModule.kt @@ -9,6 +9,7 @@ import com.tangem.common.services.secure.SecureStorage import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.utils.TrackingContextProxy import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.domain.common.wallets.UserWalletSelectedHandler import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.hotwallet.repository.HotWalletRepository import com.tangem.domain.models.scan.serialization.* @@ -32,6 +33,7 @@ import com.tangem.tap.tangemSdkManager import com.tangem.utils.Provider import com.tangem.utils.ProviderSuspend import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import dagger.Lazy import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -56,6 +58,7 @@ internal object UserWalletsListRepositoryModule { analyticsEventHandler: AnalyticsEventHandler, hotWalletRepository: HotWalletRepository, mobileWalletPromoRepository: MobileWalletPromoRepository, + userWalletSelectedHandler: Lazy, ): UserWalletsListRepository { val moshi = buildMoshi() val secureStorage = buildSecureStorage(applicationContext = applicationContext) @@ -107,6 +110,7 @@ internal object UserWalletsListRepositoryModule { analyticsEventHandler = analyticsEventHandler, hotWalletRepository = hotWalletRepository, mobileWalletPromoRepository = mobileWalletPromoRepository, + userWalletSelectedHandler = userWalletSelectedHandler, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt index 80cbe137b9..c464e78a94 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt @@ -13,6 +13,7 @@ import com.tangem.core.analytics.utils.TrackingContextProxy import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getSyncOrDefault +import com.tangem.domain.common.wallets.UserWalletSelectedHandler import com.tangem.domain.common.wallets.UserWalletTransformAction import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.common.wallets.UserWalletsListRepository.LockMethod @@ -35,6 +36,7 @@ import com.tangem.tap.domain.userWalletList.utils.* import com.tangem.utils.Provider import com.tangem.utils.ProviderSuspend import com.tangem.utils.coroutines.runSuspendCatching +import dagger.Lazy import com.tangem.utils.extensions.addOrReplace import com.tangem.utils.extensions.indexOfFirstOrNull import kotlinx.coroutines.NonCancellable @@ -60,6 +62,7 @@ internal class DefaultUserWalletsListRepository( private val analyticsEventHandler: AnalyticsEventHandler, private val hotWalletRepository: HotWalletRepository, private val mobileWalletPromoRepository: MobileWalletPromoRepository, + private val userWalletSelectedHandler: Lazy, ) : UserWalletsListRepository { override val userWallets = MutableStateFlow?>(null) @@ -88,15 +91,13 @@ internal class DefaultUserWalletsListRepository( .map { wallets.updateWith(it) } } .doOnSuccess { loadedWallets -> - userWallets.update { _ -> - val selectedUserWalletId = selectedUserWalletRepository.get() - selectedUserWallet.value = loadedWallets.firstOrNull { it.walletId == selectedUserWalletId } - ?: loadedWallets.firstOrNull()?.also { - selectedUserWalletRepository.set(it.walletId) - } - - loadedWallets - } + val selectedUserWalletId = selectedUserWalletRepository.get() + val initialSelection = loadedWallets.firstOrNull { it.walletId == selectedUserWalletId } + ?: loadedWallets.firstOrNull()?.also { + selectedUserWalletRepository.set(it.walletId) + } + userWallets.value = loadedWallets + setSelectedUserWallet(initialSelection) } } } @@ -117,7 +118,7 @@ internal class DefaultUserWalletsListRepository( val userWallet = userWallets.value?.find { it.walletId == userWalletId } ?: raise(SelectWalletError.UnableToSelectUserWallet) selectedUserWalletRepository.set(userWalletId) - selectedUserWallet.value = userWallet + setSelectedUserWallet(userWallet) userWallet } @@ -160,7 +161,7 @@ internal class DefaultUserWalletsListRepository( // update the selectedUserWallet state if it is the only wallet if (userWallets.value?.size == 1) { selectedUserWalletRepository.set(userWallet.walletId) - selectedUserWallet.value = userWallet + setSelectedUserWallet(userWallet) } userWallet @@ -223,20 +224,24 @@ internal class DefaultUserWalletsListRepository( removeHotWalletsFromSDKAndRepos(userWalletIds) - userWallets.update { currentWallets -> - val updatedWallets = currentWallets?.filter { userWalletIds.contains(it.walletId).not() } - selectedUserWallet.update { currentSelected -> - if (currentSelected == null) return@update null - val newSelected = updatedWallets?.findAvailableUserWallet( - currentWallets.indexOfFirstOrNull { it.walletId == currentSelected.walletId } ?: 0, - ) - if (newSelected == null) { - onAllWalletsDeleted() - } - selectedUserWalletRepository.set(newSelected?.walletId) - newSelected + val currentWallets = userWallets.value + val currentSelected = selectedUserWallet.value + val updatedWallets = currentWallets?.filter { userWalletIds.contains(it.walletId).not() } + val newSelected = if (currentSelected == null) { + null + } else { + updatedWallets?.findAvailableUserWallet( + currentWallets.indexOfFirstOrNull { it.walletId == currentSelected.walletId } ?: 0, + ) + } + + userWallets.value = updatedWallets + if (currentSelected != null) { + if (newSelected == null) { + onAllWalletsDeleted() } - updatedWallets + selectedUserWalletRepository.set(newSelected?.walletId) + setSelectedUserWallet(newSelected) } } @@ -523,6 +528,18 @@ internal class DefaultUserWalletsListRepository( return tangemSdkManagerProvider.invoke().canUseBiometry && isBiometricAuthenticationUsed } + /** + * Writes [userWallet] into [selectedUserWallet] and invokes [userWalletSelectedHandler] when + * the selected [UserWalletId] actually changes. Same-id refreshes stay silent. + */ + private suspend fun setSelectedUserWallet(userWallet: UserWallet?) { + val previousId = selectedUserWallet.value?.walletId + selectedUserWallet.value = userWallet + if (userWallet != null && previousId != userWallet.walletId) { + userWalletSelectedHandler.get().invoke(userWallet) + } + } + private fun updateWallets(block: (List?) -> List?) { userWallets.update { wallets -> val updated = block(wallets) diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt index 3f8d2e44cb..8f93a43df5 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt @@ -2,7 +2,6 @@ package com.tangem.tap.features.demo import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.models.scan.ScanResponse -import com.tangem.tap.common.redux.AppState object DemoHelper { val config = DemoConfig @@ -12,15 +11,4 @@ object DemoHelper { fun isTestDemoCard(scanResponse: ScanResponse): Boolean = config.isTestDemoCardId(scanResponse.card.cardId) fun isDemoCardId(cardId: String): Boolean = config.isDemoCardId(cardId) - - fun tryHandle(appState: () -> AppState?): Boolean { - val scanResponse = getScanResponse(appState) ?: return false - if (!scanResponse.isDemoCard()) return false - - return false - } - - private fun getScanResponse(appState: () -> AppState?): ScanResponse? { - return appState()?.globalState?.scanResponse - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt b/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt deleted file mode 100644 index 4805a4263a..0000000000 --- a/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.tap.features.demo - -import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.models.scan.ScanResponse - -/** -[REDACTED_AUTHOR] - */ -fun ScanResponse.isDemoCard(): Boolean = DemoHelper.isDemoCardId(card.cardId) -fun CardDTO.isDemoCard(): Boolean = DemoHelper.isDemoCardId(cardId) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/DefaultAccessCodeRecoveryComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/DefaultAccessCodeRecoveryComponent.kt index fd75548292..8384a3bf29 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/DefaultAccessCodeRecoveryComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/DefaultAccessCodeRecoveryComponent.kt @@ -7,10 +7,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.common.routing.AppRouter import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.features.details.ui.cardsettings.coderecovery.api.AccessCodeRecoveryComponent import com.tangem.tap.features.details.ui.cardsettings.coderecovery.model.AccessCodeRecoveryModel -import com.tangem.tap.store import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -19,6 +17,7 @@ import dagger.assisted.AssistedInject internal class DefaultAccessCodeRecoveryComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, @Assisted params: Unit, + private val appRouter: AppRouter, ) : AccessCodeRecoveryComponent, AppComponentContext by appComponentContext { private val model: AccessCodeRecoveryModel = getOrCreateModel() @@ -29,7 +28,7 @@ internal class DefaultAccessCodeRecoveryComponent @AssistedInject constructor( AccessCodeRecoveryScreen( state = state, - onBackClick = { store.dispatchNavigationAction(AppRouter::pop) }, + onBackClick = { appRouter.pop() }, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/model/AccessCodeRecoveryModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/model/AccessCodeRecoveryModel.kt index ac083db4c6..56092cb9b6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/model/AccessCodeRecoveryModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/model/AccessCodeRecoveryModel.kt @@ -10,11 +10,9 @@ import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings -import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.features.details.ui.cardsettings.coderecovery.AccessCodeRecoveryScreenState import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor import com.tangem.tap.features.details.ui.common.utils.isAccessCodeRecoveryEnabled -import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update @@ -27,6 +25,7 @@ internal class AccessCodeRecoveryModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val tangemSdkManager: TangemSdkManager, private val cardSettingsInteractor: CardSettingsInteractor, + private val appRouter: AppRouter, ) : Model() { private val scannedScanResponse = cardSettingsInteractor.scannedScanResponse.value @@ -73,7 +72,7 @@ internal class AccessCodeRecoveryModel @Inject constructor( ) } - store.dispatchNavigationAction(AppRouter::pop) + appRouter.pop() } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt index ecce5aaf75..3178d30e40 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt @@ -27,13 +27,11 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings -import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.features.details.ui.cardsettings.CardInfo import com.tangem.tap.features.details.ui.cardsettings.CardSettingsScreenState import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor import com.tangem.tap.features.details.ui.common.utils.* -import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.addIf import com.tangem.utils.logging.TangemLogger @@ -56,6 +54,7 @@ internal class CardSettingsModel @Inject constructor( private val settingsRepository: SettingsRepository, private val onboardingRepository: OnboardingRepository, private val uiMessageSender: UiMessageSender, + private val appRouter: AppRouter, ) : Model() { private val params = paramsContainer.require() @@ -188,12 +187,10 @@ internal class CardSettingsModel @Inject constructor( } is CardInfo.SecurityMode -> { Analytics.send(Settings.CardSettings.ButtonChangeSecurityMode()) - store.dispatchNavigationAction { - push(route = AppRoute.DetailsSecurity(userWalletId)) - } + appRouter.push(route = AppRoute.DetailsSecurity(userWalletId)) } is CardInfo.AccessCodeRecovery -> { - store.dispatchNavigationAction { push(AppRoute.AccessCodeRecovery) } + appRouter.push(AppRoute.AccessCodeRecovery) } else -> {} } @@ -205,30 +202,26 @@ internal class CardSettingsModel @Inject constructor( } if (scanResponse.cardTypesResolver.isTangemTwins()) { - store.dispatchNavigationAction { - push( - AppRoute.Onboarding( - scanResponse = scanResponse, - mode = AppRoute.Onboarding.Mode.RecreateWalletTwin, - ), - ) - } + appRouter.push( + AppRoute.Onboarding( + scanResponse = scanResponse, + mode = AppRoute.Onboarding.Mode.RecreateWalletTwin, + ), + ) } else { val card = scanResponse.card modelScope.launch { val hasTangemPay = onboardingRepository.hasTangemPayInWallet(userWalletId).getOrNull() == true - store.dispatchNavigationAction { - push( - route = AppRoute.ResetToFactory( - userWalletId = userWalletId, - cardId = card.cardId, - isActiveBackupStatus = card.backupStatus?.isActive == true, - backupCardsCount = scanResponse.getBackupCardsCount() ?: 0, - hasTangemPay = hasTangemPay, - ), - ) - } + appRouter.push( + route = AppRoute.ResetToFactory( + userWalletId = userWalletId, + cardId = card.cardId, + isActiveBackupStatus = card.backupStatus?.isActive == true, + backupCardsCount = scanResponse.getBackupCardsCount() ?: 0, + hasTangemPay = hasTangemPay, + ), + ) } } } @@ -252,6 +245,6 @@ internal class CardSettingsModel @Inject constructor( private fun onBackClick() { cardSettingsInteractor.clear() - store.dispatchNavigationAction(AppRouter::pop) + appRouter.pop() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/DefaultResetCardComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/DefaultResetCardComponent.kt index 16000cb7bb..92fcf9b4b9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/DefaultResetCardComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/DefaultResetCardComponent.kt @@ -7,10 +7,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.common.routing.AppRouter import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent import com.tangem.tap.features.details.ui.resetcard.model.ResetCardModel -import com.tangem.tap.store import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -18,6 +16,7 @@ import dagger.assisted.AssistedInject internal class DefaultResetCardComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, @Assisted params: ResetCardComponent.Params, + private val appRouter: AppRouter, ) : ResetCardComponent, AppComponentContext by appComponentContext { private val model: ResetCardModel = getOrCreateModel(params) @@ -29,7 +28,7 @@ internal class DefaultResetCardComponent @AssistedInject constructor( ResetCardScreen( modifier = modifier, state = state, - onBackClick = { store.dispatchNavigationAction(AppRouter::pop) }, + onBackClick = { appRouter.pop() }, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt index 10ec357e6b..d96d1fcce0 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/model/ResetCardModel.kt @@ -3,6 +3,7 @@ package com.tangem.tap.features.details.ui.resetcard.model import androidx.compose.runtime.Stable import arrow.core.getOrElse import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.AppRouter import com.tangem.common.routing.utils.popTo import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped @@ -18,14 +19,11 @@ import com.tangem.domain.wallets.usecase.DeleteWalletUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.tap.common.analytics.events.Settings -import com.tangem.tap.common.extensions.dispatchNavigationAction -import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor import com.tangem.tap.features.details.ui.common.utils.getResetToFactoryDescription import com.tangem.tap.features.details.ui.resetcard.ResetCardDialog import com.tangem.tap.features.details.ui.resetcard.ResetCardScreenState import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent -import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE import com.tangem.utils.logging.TangemLogger @@ -51,6 +49,7 @@ internal class ResetCardModel @Inject constructor( private val deleteWalletUseCase: DeleteWalletUseCase, private val analyticsEventHandler: AnalyticsEventHandler, private val cardSettingsInteractor: CardSettingsInteractor, + private val appRouter: AppRouter, ) : Model() { private val params = paramsContainer.require() @@ -189,19 +188,11 @@ internal class ResetCardModel @Inject constructor( modelScope.launch { resetCardUseCase(cardId = primaryCardId, params = currentUserCodeParams).onRight { deleteSavedAccessCodesUseCase(cardId = primaryCardId) - val hasUserWallets = deleteWalletUseCase(userWalletId = currentUserWalletId).getOrElse { error -> + deleteWalletUseCase(userWalletId = currentUserWalletId).getOrElse { error -> TangemLogger.e("Unable to delete user wallet: $error") return@launch } - if (hasUserWallets) { - val newSelectedWallet = getSelectedWalletSyncUseCase().getOrElse { error -> - error("Failed to get selected wallet: $error") - } - - store.onUserWalletSelected(newSelectedWallet) - } - delay(DELAY_SDK_DIALOG_CLOSE) checkRemainingBackupCards() @@ -270,9 +261,9 @@ internal class ResetCardModel @Inject constructor( val newSelectedWallet = getSelectedWalletSyncUseCase.invoke().getOrNull() if (newSelectedWallet != null) { - store.dispatchNavigationAction { popTo() } + appRouter.popTo() } else { - store.dispatchNavigationAction { replaceAll(AppRoute.Home()) } + appRouter.replaceAll(AppRoute.Home()) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/DefaultSecurityModeComponent.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/DefaultSecurityModeComponent.kt index 151b4c123d..98ae5b6c16 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/DefaultSecurityModeComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/DefaultSecurityModeComponent.kt @@ -7,10 +7,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.common.routing.AppRouter import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent import com.tangem.tap.features.details.ui.securitymode.model.SecurityModeModel -import com.tangem.tap.store import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -18,6 +16,7 @@ import dagger.assisted.AssistedInject internal class DefaultSecurityModeComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, @Assisted params: SecurityModeComponent.Params, + private val appRouter: AppRouter, ) : SecurityModeComponent, AppComponentContext by appComponentContext { private val model: SecurityModeModel = getOrCreateModel(params) @@ -29,7 +28,7 @@ internal class DefaultSecurityModeComponent @AssistedInject constructor( SecurityModeScreen( modifier = modifier, state = state, - onBackClick = { store.dispatchNavigationAction(AppRouter::pop) }, + onBackClick = { appRouter.pop() }, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/model/SecurityModeModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/model/SecurityModeModel.kt index 36fdf29787..b6682fe212 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/model/SecurityModeModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/model/SecurityModeModel.kt @@ -13,13 +13,11 @@ import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.analytics.events.TangemSdkErrorEvent -import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.features.details.redux.SecurityOption import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor import com.tangem.tap.features.details.ui.common.utils.getAllowedSecurityOptions import com.tangem.tap.features.details.ui.common.utils.getCurrentSecurityOption import com.tangem.tap.features.details.ui.securitymode.SecurityModeScreenState -import com.tangem.tap.store import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update @@ -34,6 +32,7 @@ internal class SecurityModeModel @Inject constructor( private val cardSettingsInteractor: CardSettingsInteractor, private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsErrorHandler: AnalyticsErrorHandler, + private val appRouter: AppRouter, ) : Model() { private val scannedScanResponse = cardSettingsInteractor.scannedScanResponse.value @@ -91,7 +90,7 @@ internal class SecurityModeModel @Inject constructor( is CompletionResult.Success -> { analyticsEventHandler.send(Settings.CardSettings.SecurityModeChanged(paramValue)) - store.dispatchNavigationAction(AppRouter::pop) + appRouter.pop() } is CompletionResult.Failure -> { val error = result.error @@ -99,7 +98,6 @@ internal class SecurityModeModel @Inject constructor( analyticsErrorHandler.sendErrorEvent(TangemSdkErrorEvent(error)) } } - else -> Unit } } } diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index ffca01be8a..b90a6fc412 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -21,7 +21,6 @@ import com.tangem.domain.balancehiding.BalanceHidingSettings import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.balancehiding.ListenToFlipsUseCase import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase -import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.notifications.ClearApplicationIdUseCase import com.tangem.domain.notifications.GetApplicationIdUseCase import com.tangem.domain.notifications.SendPushTokenUseCase @@ -37,7 +36,6 @@ import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase import com.tangem.domain.staking.FetchStakingOptionsUseCase import com.tangem.domain.wallets.usecase.AssociateWalletsWithApplicationIdUseCase import com.tangem.domain.wallets.usecase.GetSavedWalletsCountUseCase -import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.domain.wallets.usecase.UpdateRemoteWalletsInfoUseCase import com.tangem.feature.swap.analytics.StoriesEvents import com.tangem.security.DeviceSecurityInfoProvider @@ -80,7 +78,6 @@ internal class MainViewModel @Inject constructor( private val apiConfigsManager: ApiConfigsManager, private val multiQuoteUpdater: MultiQuoteUpdater, private val appStateHolder: AppStateHolder, - private val getSelectedWalletUseCase: GetSelectedWalletUseCase, private val appRouterConfig: AppRouterConfig, private val sellService: SellService, private val deviceSecurityInfoProvider: DeviceSecurityInfoProvider, @@ -143,8 +140,6 @@ internal class MainViewModel @Inject constructor( launch { fetchUserCountry() } } - subscribeToSelectedWallet() - // await while initial route stack is initialized appRouterConfig.initializedState.first { it } @@ -173,20 +168,6 @@ internal class MainViewModel @Inject constructor( } } - private fun subscribeToSelectedWallet() { - getSelectedWalletUseCase.invoke() - .mapLeft { emptyFlow() } - .onRight { wallet -> - wallet.distinctUntilChanged() - .onEach { - // FIXME Do not remove this call without checking implications !!! - appStateHolder.onUserWalletSelected(it) - } - .flowOn(dispatchers.io) - .launchIn(viewModelScope) - } - } - private suspend fun fetchStakingOptions() { fetchStakingOptionsUseCase() .onLeft { TangemLogger.e("Unable to fetch staking options: $it") } diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index 198f3b63d8..aed385bef1 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -17,7 +17,6 @@ import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.transaction.models.AssetRequirementsCondition -import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.runCatching import com.tangem.utils.coroutines.runSuspendCatching @@ -26,7 +25,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull internal class DefaultRampManager( - private val sellService: Provider, + private val sellService: SellService, private val expressServiceFetcher: ExpressServiceFetcher, private val currenciesRepository: CurrenciesRepository, private val dispatchers: CoroutineDispatcherProvider, @@ -53,7 +52,7 @@ internal class DefaultRampManager( block = { val serviceCurrency = CryptoCurrencyConverter.convert(status.currency) - sellService().availableForSell(currency = serviceCurrency) + sellService.availableForSell(currency = serviceCurrency) }, catch = { raise(ScenarioUnavailabilityReason.NotSupportedBySellService(status.currency.name)) }, ) @@ -92,12 +91,12 @@ internal class DefaultRampManager( } override fun getSellInitializationStatus(): Flow { - return sellService.invoke().initializationStatus + return sellService.initializationStatus } override suspend fun fetchSellServiceData() { runCatching(dispatchers.io) { - sellService.invoke().update() + sellService.update() } } diff --git a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt index d922ce744d..921beac813 100644 --- a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt @@ -40,18 +40,16 @@ import com.tangem.hot.sdk.android.create import com.tangem.sdk.api.BackupServiceHolder import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.analytics.events.Onboarding -import com.tangem.tap.features.scanfails.ScanFailsComponent -import com.tangem.tap.features.scanfails.ScanFailsRequesterProxy -import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.hot.TangemHotSDKProxy import com.tangem.tap.features.root.RootDetectedWarningComponent +import com.tangem.tap.features.scanfails.ScanFailsComponent +import com.tangem.tap.features.scanfails.ScanFailsRequesterProxy import com.tangem.tap.routing.RootContent import com.tangem.tap.routing.component.RoutingComponent import com.tangem.tap.routing.component.RoutingComponent.Child import com.tangem.tap.routing.configurator.AppRouterConfig import com.tangem.tap.routing.utils.ChildFactory import com.tangem.tap.routing.utils.DeepLinkFactory -import com.tangem.tap.store import com.tangem.utils.logging.TangemLogger import com.tangem.wallet.R import dagger.assisted.Assisted @@ -270,7 +268,6 @@ internal class DefaultRoutingComponent @AssistedInject constructor( } private fun checkForUnfinishedBackup() { - if (DemoHelper.tryHandle { store.state }) return componentScope.launch(dispatchers.main) { val scanResponse = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch messageSender.send(unfinishedBackupFoundDialog(scanResponse)) diff --git a/app/src/test/kotlin/com/tangem/tap/domain/DefaultUserWalletSelectedHandlerTest.kt b/app/src/test/kotlin/com/tangem/tap/domain/DefaultUserWalletSelectedHandlerTest.kt new file mode 100644 index 0000000000..ee434cb45c --- /dev/null +++ b/app/src/test/kotlin/com/tangem/tap/domain/DefaultUserWalletSelectedHandlerTest.kt @@ -0,0 +1,147 @@ +package com.tangem.tap.domain + +import com.google.common.truth.Truth.assertThat +import com.tangem.common.test.TestAppCoroutineScope +import com.tangem.common.test.domain.card.MockScanResponseFactory +import com.tangem.common.test.domain.wallet.MockUserWalletFactory +import com.tangem.core.analytics.utils.TrackingContextProxy +import com.tangem.domain.card.configs.GenericCardConfig +import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.sdk.api.TangemSdkManager +import io.mockk.* +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +internal class DefaultUserWalletSelectedHandlerTest { + + private val trackingContextProxy = mockk(relaxed = true) + private val tangemSdkManager = mockk(relaxed = true) + private val settingsRepository = mockk() + private val cardSdkConfigRepository = mockk(relaxed = true) + private val appScope = TestAppCoroutineScope() + + private lateinit var handler: DefaultUserWalletSelectedHandler + + @BeforeEach + fun setup() { + clearMocks(trackingContextProxy, tangemSdkManager, settingsRepository, cardSdkConfigRepository) + handler = DefaultUserWalletSelectedHandler( + trackingContextProxy = trackingContextProxy, + tangemSdkManager = tangemSdkManager, + settingsRepository = settingsRepository, + cardSdkConfigRepository = cardSdkConfigRepository, + appCoroutineScope = appScope, + ) + } + + @Test + fun `cold wallet with access code and save-codes enabled applies biometric policy`() = runTest { + val userWallet = coldWalletWith(isAccessCodeSet = true) + coEvery { settingsRepository.shouldSaveAccessCodes() } returns true + + handler(userWallet) + + verify(exactly = 1) { trackingContextProxy.setContext(userWallet) } + verify(exactly = 1) { tangemSdkManager.changeDisplayedCardIdNumbersCount(userWallet.scanResponse) } + verify(exactly = 1) { cardSdkConfigRepository.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = true) } + } + + @Test + fun `cold wallet without access code keeps biometric policy off even when save-codes enabled`() = runTest { + assertThat(capturePolicyFor(shouldSaveAccessCodes = true, isAccessCodeSet = false)).isFalse() + } + + @Test + fun `cold wallet with access code keeps biometric policy off when save-codes disabled`() = runTest { + assertThat(capturePolicyFor(shouldSaveAccessCodes = false, isAccessCodeSet = true)).isFalse() + } + + @Test + fun `cold wallet without access code and save-codes disabled keeps biometric policy off`() = runTest { + assertThat(capturePolicyFor(shouldSaveAccessCodes = false, isAccessCodeSet = false)).isFalse() + } + + @Test + fun `hot wallet only updates tracking context`() = runTest { + val hotWallet = mockk() + + handler(hotWallet) + + verify(exactly = 1) { trackingContextProxy.setContext(hotWallet) } + verify(exactly = 0) { tangemSdkManager.changeDisplayedCardIdNumbersCount(any()) } + coVerify(exactly = 0) { settingsRepository.shouldSaveAccessCodes() } + verify(exactly = 0) { cardSdkConfigRepository.setAccessCodeRequestPolicy(any()) } + } + + @Test + fun `consecutive invocations both run policy update`() = runTest { + val firstWallet = coldWalletWith(isAccessCodeSet = true) + val secondWallet = coldWalletWith(isAccessCodeSet = false) + coEvery { settingsRepository.shouldSaveAccessCodes() } returns true + + handler(firstWallet) + handler(secondWallet) + + verify(exactly = 1) { cardSdkConfigRepository.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = true) } + verify(exactly = 1) { cardSdkConfigRepository.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = false) } + } + + @Test + fun `new invocation cancels in-flight job so only latest side effects are applied`() = runTest { + val firstWallet = coldWalletWith(isAccessCodeSet = true) + val secondWallet = coldWalletWith(isAccessCodeSet = false) + + val firstCallGate = CompletableDeferred() + var callIndex = 0 + coEvery { settingsRepository.shouldSaveAccessCodes() } coAnswers { + callIndex++ + if (callIndex == 1) firstCallGate.await() else true + } + + val firstHandlerJob = launch { handler(firstWallet) } + runCurrent() + + handler(secondWallet) + + firstCallGate.complete(true) + firstHandlerJob.join() + + verify(exactly = 0) { cardSdkConfigRepository.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = true) } + verify(exactly = 1) { cardSdkConfigRepository.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = false) } + verify(exactly = 1) { trackingContextProxy.setContext(secondWallet) } + } + + private suspend fun capturePolicyFor(shouldSaveAccessCodes: Boolean, isAccessCodeSet: Boolean): Boolean { + val userWallet = coldWalletWith(isAccessCodeSet = isAccessCodeSet) + coEvery { settingsRepository.shouldSaveAccessCodes() } returns shouldSaveAccessCodes + val captured = slot() + + handler(userWallet) + + verify { cardSdkConfigRepository.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = capture(captured)) } + return captured.captured + } + + private fun coldWalletWith(isAccessCodeSet: Boolean): UserWallet.Cold { + val baseScanResponse = MockScanResponseFactory.create( + cardConfig = GenericCardConfig(maxWalletCount = 2), + derivedKeys = emptyMap(), + ) + val scanResponse: ScanResponse = baseScanResponse.copy( + card = baseScanResponse.card.copy( + cardId = if (isAccessCodeSet) "CARD-WITH-CODE" else "CARD-NO-CODE", + isAccessCodeSet = isAccessCodeSet, + ), + ) + return MockUserWalletFactory.create(scanResponse = scanResponse) + } +} \ No newline at end of file diff --git a/domain/common/src/main/java/com/tangem/domain/common/wallets/UserWalletSelectedHandler.kt b/domain/common/src/main/java/com/tangem/domain/common/wallets/UserWalletSelectedHandler.kt new file mode 100644 index 0000000000..7b5a37e76a --- /dev/null +++ b/domain/common/src/main/java/com/tangem/domain/common/wallets/UserWalletSelectedHandler.kt @@ -0,0 +1,15 @@ +package com.tangem.domain.common.wallets + +import com.tangem.domain.models.wallet.UserWallet + +/** + * Handler invoked when a user wallet becomes the active one. + * + * Side effects (analytics tracking context, Tangem SDK display config, access code request policy, etc.) + * follow switch-latest semantics: if a new selection arrives while a previous one is still being processed, + * the in-flight job is cancelled and only the latest selection is applied. + */ +interface UserWalletSelectedHandler { + + suspend operator fun invoke(userWallet: UserWallet) +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SelectWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SelectWalletUseCase.kt index 239ada3255..98f3317583 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SelectWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SelectWalletUseCase.kt @@ -5,25 +5,23 @@ import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.common.wallets.error.SelectWalletError import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.redux.ReduxStateHolder /** - * Use case for selecting wallet + * Use case for selecting wallet. + * + * Side effects tied to selection (analytics tracking context, Tangem SDK display config, access + * code request policy) are fired from the repository itself when the selected [UserWalletId] + * changes — see the implementation of [UserWalletsListRepository.select]. * * @property userWalletsListRepository repository for getting list of user wallets - * @property reduxStateHolder redux state holder * [REDACTED_AUTHOR] */ class SelectWalletUseCase( private val userWalletsListRepository: UserWalletsListRepository, - private val reduxStateHolder: ReduxStateHolder, ) { suspend operator fun invoke(userWalletId: UserWalletId): Either { - return userWalletsListRepository.select(userWalletId).map { - reduxStateHolder.onUserWalletSelected(it) - it - } + return userWalletsListRepository.select(userWalletId) } } \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt index 3e81799932..977bb4042c 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt @@ -21,7 +21,6 @@ import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.common.wallets.error.SaveWalletError import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.wallets.builder.ColdUserWalletBuilder import com.tangem.domain.wallets.usecase.SaveWalletUseCase import com.tangem.features.details.impl.R @@ -37,7 +36,6 @@ internal class UserWalletSaver @Inject constructor( private val scanCardProcessor: ScanCardProcessor, private val saveWalletUseCase: SaveWalletUseCase, private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory, - private val reduxStateHolder: ReduxStateHolder, private val messageSender: UiMessageSender, private val router: Router, ) { @@ -94,9 +92,6 @@ internal class UserWalletSaver @Inject constructor( } }, transform = { - // call only if wallet is successfully saved - reduxStateHolder.onUserWalletSelected(userWallet) - router.popTo() }, ) diff --git a/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt b/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt index 97c6d2a93c..3e871cd61e 100644 --- a/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt +++ b/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt @@ -27,7 +27,6 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.common.wallets.error.SaveWalletError import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.models.UserCountry @@ -69,7 +68,6 @@ internal class HomeModel @Inject constructor( private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase, private val urlOpener: UrlOpener, private val userWalletsListRepository: UserWalletsListRepository, - private val reduxStateHolder: ReduxStateHolder, private val shouldShowMobileWalletPromoUseCase: ShouldShowMobileWalletPromoUseCase, @GlobalUiMessageSender private val uiMessageSender: UiMessageSender, ) : Model() { @@ -212,7 +210,6 @@ internal class HomeModel @Inject constructor( } }, ifRight = { - reduxStateHolder.onUserWalletSelected(userWallet) setLoading(false) sendSignedInCardAnalyticsEvent(scanResponse, userWallet.isImported) appRouter.replaceAll(AppRoute.Wallet)