Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-02 15:27:17 +03:00
parent f466395589
commit 8cff4c1bba
5 changed files with 97 additions and 5 deletions

View file

@ -62,7 +62,7 @@ internal class DefaultUserWalletsListRepository(
// If we don't save persistent information, we don't need to load user wallets // If we don't save persistent information, we don't need to load user wallets
// and we should clear any existing data // and we should clear any existing data
clearPersistentData() clearPersistentData()
userWallets.value = emptyList() updateWallets { emptyList() }
return return
} }
@ -118,7 +118,7 @@ internal class DefaultUserWalletsListRepository(
} }
// update the userWallets state and add if it doesn't exist // update the userWallets state and add if it doesn't exist
userWallets.update { currentWallets -> updateWallets { currentWallets ->
val wallets = currentWallets ?: emptyList() val wallets = currentWallets ?: emptyList()
if (wallets.any { it.walletId == userWallet.walletId }) { if (wallets.any { it.walletId == userWallet.walletId }) {
wallets.map { if (it.walletId == userWallet.walletId) userWallet else it } wallets.map { if (it.walletId == userWallet.walletId) userWallet else it }
@ -248,7 +248,7 @@ internal class DefaultUserWalletsListRepository(
removePasswordAttempts(userWallet) removePasswordAttempts(userWallet)
sensitiveInformationRepository.getAll(listOf(encryptionKey)) sensitiveInformationRepository.getAll(listOf(encryptionKey))
.doOnSuccess { sensitiveInfo -> userWallets.update { it?.updateWith(sensitiveInfo) } } .doOnSuccess { sensitiveInfo -> updateWallets { it?.updateWith(sensitiveInfo) } }
.doOnFailure { error -> .doOnFailure { error ->
raise(UnlockWalletError.UnableToUnlock) raise(UnlockWalletError.UnableToUnlock)
} }
@ -306,7 +306,7 @@ internal class DefaultUserWalletsListRepository(
sensitiveInformationRepository.getAll(allKeys) sensitiveInformationRepository.getAll(allKeys)
.doOnSuccess { sensitiveInfo -> .doOnSuccess { sensitiveInfo ->
userWallets.update { it?.updateWith(sensitiveInfo) } updateWallets { it?.updateWith(sensitiveInfo) }
} }
.doOnFailure { raise(UnlockWalletError.UnableToUnlock) } .doOnFailure { raise(UnlockWalletError.UnableToUnlock) }
} }
@ -318,7 +318,7 @@ internal class DefaultUserWalletsListRepository(
raise(LockWalletsError.NothingToLock) raise(LockWalletsError.NothingToLock)
} }
userWallets.update { updateWallets {
it?.map { it?.map {
if (it.walletId !in unsecuredWalletIds) { if (it.walletId !in unsecuredWalletIds) {
it.lock() it.lock()
@ -389,6 +389,15 @@ internal class DefaultUserWalletsListRepository(
return tangemSdkManagerProvider.invoke().canUseBiometry && useBiometricAuthentication return tangemSdkManagerProvider.invoke().canUseBiometry && useBiometricAuthentication
} }
private fun updateWallets(block: (List<UserWallet>?) -> List<UserWallet>?) {
userWallets.update(block)
selectedUserWallet.update { currentSelected ->
if (currentSelected == null) return@update null
userWallets.value?.find { it.walletId == currentSelected.walletId }
}
}
/** /**
* Find the nearest available wallet that can be selected * Find the nearest available wallet that can be selected
* *

View file

@ -363,6 +363,9 @@ internal class WalletModel @Inject constructor(
is WalletsUpdateActionResolver.Action.RenameWallets -> { is WalletsUpdateActionResolver.Action.RenameWallets -> {
stateHolder.update(transformer = RenameWalletsTransformer(renamedWallets = action.renamedWallets)) stateHolder.update(transformer = RenameWalletsTransformer(renamedWallets = action.renamedWallets))
} }
is WalletsUpdateActionResolver.Action.ReloadWarningsForWallets -> {
reloadWarnings(action)
}
WalletsUpdateActionResolver.Action.EmptyWallets -> { WalletsUpdateActionResolver.Action.EmptyWallets -> {
Timber.w("Wallets list is empty!") Timber.w("Wallets list is empty!")
} }
@ -372,6 +375,17 @@ internal class WalletModel @Inject constructor(
} }
} }
private fun reloadWarnings(action: WalletsUpdateActionResolver.Action.ReloadWarningsForWallets) {
action.wallets.forEach {
walletScreenContentLoader.load(
userWallet = it,
clickIntents = clickIntents,
coroutineScope = modelScope,
isRefresh = true,
)
}
}
private suspend fun initializeWallets(action: WalletsUpdateActionResolver.Action.InitializeWallets) { private suspend fun initializeWallets(action: WalletsUpdateActionResolver.Action.InitializeWallets) {
stateHolder.update( stateHolder.update(
transformer = InitializeWalletsTransformer( transformer = InitializeWalletsTransformer(

View file

@ -9,6 +9,7 @@ import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import timber.log.Timber import timber.log.Timber
@ -75,10 +76,20 @@ internal class WalletsUpdateActionResolver @Inject constructor(
isAnyWalletNameChanged(state, wallets) -> { isAnyWalletNameChanged(state, wallets) -> {
getRenameWalletsAction(state, wallets) getRenameWalletsAction(state, wallets)
} }
isAnyHotWalletBackedUp(state, wallets) -> {
getHotWalletsBackedUpAction(state, wallets)
}
else -> getUpdateSelectedWalletAction(state, wallets, selectedWallet) else -> getUpdateSelectedWalletAction(state, wallets, selectedWallet)
} }
} }
private fun isAnyHotWalletBackedUp(state: WalletScreenState, wallets: List<UserWallet>): Boolean {
val incompleteActivationWalletIds = state.incompleteActivationWalletIds()
return incompleteActivationWalletIds.mapNotNull { walletId ->
wallets.firstOrNull { it.walletId == walletId && it is UserWallet.Hot && it.backedUp }
}.isNotEmpty()
}
private fun isWalletsCountChanged(state: WalletScreenState, wallets: List<UserWallet>): Boolean { private fun isWalletsCountChanged(state: WalletScreenState, wallets: List<UserWallet>): Boolean {
val prevWalletsSize = state.wallets.size val prevWalletsSize = state.wallets.size
val walletsSize = wallets.size val walletsSize = wallets.size
@ -145,6 +156,20 @@ internal class WalletsUpdateActionResolver @Inject constructor(
return prevWalletsIds == newWalletsIds && isAnyNameChanged return prevWalletsIds == newWalletsIds && isAnyNameChanged
} }
private fun getHotWalletsBackedUpAction(
state: WalletScreenState,
wallets: List<UserWallet>,
): Action.ReloadWarningsForWallets {
val incompleteActivationWalletIds = state.incompleteActivationWalletIds()
val walletsToUpdate = wallets.filter {
it is UserWallet.Hot && it.backedUp &&
incompleteActivationWalletIds.contains(it.walletId)
}
return Action.ReloadWarningsForWallets(walletsToUpdate)
}
private fun getRenameWalletsAction(state: WalletScreenState, wallets: List<UserWallet>): Action.RenameWallets { private fun getRenameWalletsAction(state: WalletScreenState, wallets: List<UserWallet>): Action.RenameWallets {
val prevWallets = state.wallets.map { it.walletCardState.id to it.walletCardState.title } val prevWallets = state.wallets.map { it.walletCardState.id to it.walletCardState.title }
val newWallets = wallets.map { it.walletId to it.name } val newWallets = wallets.map { it.walletId to it.name }
@ -199,6 +224,16 @@ internal class WalletsUpdateActionResolver @Inject constructor(
?: error("Previous selected wallet is not found") ?: error("Previous selected wallet is not found")
} }
private fun WalletScreenState.incompleteActivationWalletIds(): List<UserWalletId> {
return wallets.mapNotNull {
if (it.warnings.any { it is WalletNotification.FinishWalletActivation }) {
it.walletCardState.id
} else {
null
}
}
}
private fun List<UserWallet>.indexOfWallet(id: UserWalletId): Int { private fun List<UserWallet>.indexOfWallet(id: UserWalletId): Int {
val selectedIndex = indexOfFirst { it.walletId == id } val selectedIndex = indexOfFirst { it.walletId == id }
@ -323,6 +358,10 @@ internal class WalletsUpdateActionResolver @Inject constructor(
} }
} }
data class ReloadWarningsForWallets(
val wallets: List<UserWallet>,
) : Action()
data object EmptyWallets : Action() data object EmptyWallets : Action()
data object Unknown : Action() data object Unknown : Action()

View file

@ -24,6 +24,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBot
import com.tangem.feature.wallet.presentation.wallet.state.transformers.OpenBottomSheetTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.OpenBottomSheetTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCurrencyActionsConverter import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCurrencyActionsConverter
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.take
@ -76,9 +77,16 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
private val reduxStateHolder: ReduxStateHolder, private val reduxStateHolder: ReduxStateHolder,
private val walletEventSender: WalletEventSender, private val walletEventSender: WalletEventSender,
private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsEventHandler: AnalyticsEventHandler,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) : BaseWalletClickIntents(), WalletContentClickIntents { ) : BaseWalletClickIntents(), WalletContentClickIntents {
override fun onDetailsClick() { override fun onDetailsClick() {
if (hotWalletFeatureToggles.isHotWalletEnabled) {
router.openDetailsScreen(stateHolder.getSelectedWalletId())
return
}
// Will be removed after Hot Wallet release
modelScope.launch(dispatchers.main) { modelScope.launch(dispatchers.main) {
val userWalletId = stateHolder.getSelectedWalletId() val userWalletId = stateHolder.getSelectedWalletId()
val userWallet = getUserWalletUseCase(userWalletId).getOrElse { val userWallet = getUserWalletUseCase(userWalletId).getOrElse {

View file

@ -12,6 +12,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase
import com.tangem.domain.card.SetCardWasScannedUseCase import com.tangem.domain.card.SetCardWasScannedUseCase
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
@ -44,6 +45,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBottomShe
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
@ -117,6 +119,8 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher, private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
private val stakingIdFactory: StakingIdFactory, private val stakingIdFactory: StakingIdFactory,
private val appRouter: AppRouter, private val appRouter: AppRouter,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val userWalletsListRepository: UserWalletsListRepository,
) : BaseWalletClickIntents(), WalletWarningsClickIntents { ) : BaseWalletClickIntents(), WalletWarningsClickIntents {
override fun onAddBackupCardClick() { override fun onAddBackupCardClick() {
@ -168,6 +172,22 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
override fun onOpenUnlockWalletsBottomSheetClick() { override fun onOpenUnlockWalletsBottomSheetClick() {
analyticsEventHandler.send(MainScreen.WalletUnlockTapped) analyticsEventHandler.send(MainScreen.WalletUnlockTapped)
if (hotWalletFeatureToggles.isHotWalletEnabled) {
modelScope.launch {
userWalletsListRepository.unlockAllWallets()
.onLeft {
val selectedUserWallet = getSelectedUserWallet() ?: return@onLeft
val method = when (selectedUserWallet) {
is UserWallet.Cold -> UserWalletsListRepository.UnlockMethod.Scan
is UserWallet.Hot -> UserWalletsListRepository.UnlockMethod.AccessCode
}
userWalletsListRepository.unlock(stateHolder.getSelectedWalletId(), method)
}
}
return
}
// Will be removed after hot wallet release
stateHolder.showBottomSheet( stateHolder.showBottomSheet(
WalletBottomSheetConfig.UnlockWallets( WalletBottomSheetConfig.UnlockWallets(
onUnlockClick = this::onUnlockWalletClick, onUnlockClick = this::onUnlockWalletClick,
@ -176,6 +196,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
) )
} }
@Deprecated("Will be removed with hot wallet release")
override fun onUnlockWalletClick() { override fun onUnlockWalletClick() {
analyticsEventHandler.send(MainScreen.UnlockAllWithBiometrics) analyticsEventHandler.send(MainScreen.UnlockAllWithBiometrics)
@ -203,6 +224,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
walletEventSender.send(event) walletEventSender.send(event)
} }
@Deprecated("Will be removed with hot wallet release")
override fun onScanToUnlockWalletClick() { override fun onScanToUnlockWalletClick() {
analyticsEventHandler.send(MainScreen.UnlockWithCardScan) analyticsEventHandler.send(MainScreen.UnlockWithCardScan)
openScanCardDialog() openScanCardDialog()