Updated on 2026-08-14
This commit is contained in:
parent
6b75b48f1e
commit
8e6eca1b4c
23 changed files with 327 additions and 216 deletions
|
|
@ -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<Wallet> = 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UserWalletSelectedHandler>,
|
||||
): UserWalletsListRepository {
|
||||
val moshi = buildMoshi()
|
||||
val secureStorage = buildSecureStorage(applicationContext = applicationContext)
|
||||
|
|
@ -107,6 +110,7 @@ internal object UserWalletsListRepositoryModule {
|
|||
analyticsEventHandler = analyticsEventHandler,
|
||||
hotWalletRepository = hotWalletRepository,
|
||||
mobileWalletPromoRepository = mobileWalletPromoRepository,
|
||||
userWalletSelectedHandler = userWalletSelectedHandler,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<UserWalletSelectedHandler>,
|
||||
) : UserWalletsListRepository {
|
||||
|
||||
override val userWallets = MutableStateFlow<List<UserWallet>?>(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 }
|
||||
val initialSelection = loadedWallets.firstOrNull { it.walletId == selectedUserWalletId }
|
||||
?: loadedWallets.firstOrNull()?.also {
|
||||
selectedUserWalletRepository.set(it.walletId)
|
||||
}
|
||||
|
||||
loadedWallets
|
||||
}
|
||||
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 currentWallets = userWallets.value
|
||||
val currentSelected = selectedUserWallet.value
|
||||
val updatedWallets = currentWallets?.filter { userWalletIds.contains(it.walletId).not() }
|
||||
selectedUserWallet.update { currentSelected ->
|
||||
if (currentSelected == null) return@update null
|
||||
val newSelected = updatedWallets?.findAvailableUserWallet(
|
||||
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()
|
||||
}
|
||||
selectedUserWalletRepository.set(newSelected?.walletId)
|
||||
newSelected
|
||||
}
|
||||
updatedWallets
|
||||
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<UserWallet>?) -> List<UserWallet>?) {
|
||||
userWallets.update { wallets ->
|
||||
val updated = block(wallets)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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() },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<CardSettingsComponent.Params>()
|
||||
|
|
@ -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,21 +202,18 @@ internal class CardSettingsModel @Inject constructor(
|
|||
}
|
||||
|
||||
if (scanResponse.cardTypesResolver.isTangemTwins()) {
|
||||
store.dispatchNavigationAction {
|
||||
push(
|
||||
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(
|
||||
appRouter.push(
|
||||
route = AppRoute.ResetToFactory(
|
||||
userWalletId = userWalletId,
|
||||
cardId = card.cardId,
|
||||
|
|
@ -231,7 +225,6 @@ internal class CardSettingsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeAccessCode() = modelScope.launch {
|
||||
val scanResponse = requireNotNull(cardSettingsInteractor.scannedScanResponse.value) { "Scan response is null" }
|
||||
|
|
@ -252,6 +245,6 @@ internal class CardSettingsModel @Inject constructor(
|
|||
|
||||
private fun onBackClick() {
|
||||
cardSettingsInteractor.clear()
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
appRouter.pop()
|
||||
}
|
||||
}
|
||||
|
|
@ -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() },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ResetCardComponent.Params>()
|
||||
|
|
@ -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<AppRoute.Wallet>() }
|
||||
appRouter.popTo<AppRoute.Wallet>()
|
||||
} else {
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home()) }
|
||||
appRouter.replaceAll(AppRoute.Home())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<UserWallet>() }
|
||||
.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") }
|
||||
|
|
|
|||
|
|
@ -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<SellService>,
|
||||
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<SellServiceInitializationStatus> {
|
||||
return sellService.invoke().initializationStatus
|
||||
return sellService.initializationStatus
|
||||
}
|
||||
|
||||
override suspend fun fetchSellServiceData() {
|
||||
runCatching(dispatchers.io) {
|
||||
sellService.invoke().update()
|
||||
sellService.update()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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<TrackingContextProxy>(relaxed = true)
|
||||
private val tangemSdkManager = mockk<TangemSdkManager>(relaxed = true)
|
||||
private val settingsRepository = mockk<SettingsRepository>()
|
||||
private val cardSdkConfigRepository = mockk<CardSdkConfigRepository>(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<UserWallet.Hot>()
|
||||
|
||||
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<Boolean>()
|
||||
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<Boolean>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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<SelectWalletError, UserWallet> {
|
||||
return userWalletsListRepository.select(userWalletId).map {
|
||||
reduxStateHolder.onUserWalletSelected(it)
|
||||
it
|
||||
}
|
||||
return userWalletsListRepository.select(userWalletId)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AppRoute.Wallet>()
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue