Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-22 10:09:11 +02:00
commit e4754fed3b
483 changed files with 9315 additions and 4084 deletions

View file

@ -7,6 +7,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
@ -77,6 +78,7 @@ internal class WalletComponent @AssistedInject constructor(
params = PushNotificationsParams(
isBottomSheet = true,
modelCallbacks = model.askForPushNotificationsModelCallbacks,
source = AppRoute.PushNotification.Source.Main,
),
)
is WalletDialogConfig.TokenReceive -> {

View file

@ -13,6 +13,7 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase
import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase
@ -84,7 +85,7 @@ internal class WalletModel @Inject constructor(
private val getWalletsListForEnablingUseCase: GetWalletsForAutomaticallyPushEnablingUseCase,
private val setNotificationsEnabledUseCase: SetNotificationsEnabledUseCase,
private val notificationsFeatureToggles: NotificationsFeatureToggles,
private val getIsBiometryIsEnabledUseCase: GetIsBiometricsEnabledUseCase,
private val shouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase,
private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase,
val screenLifecycleProvider: ScreenLifecycleProvider,
val innerWalletRouter: InnerWalletRouter,
@ -212,9 +213,15 @@ internal class WalletModel @Inject constructor(
modelScope.launch {
val shouldAskPermission = shouldAskPermissionUseCase(PUSH_PERMISSION)
val afterUpdate = notificationsRepository.shouldShowSubscribeOnNotificationsAfterUpdate()
val isBiometricsEnabled = getIsBiometryIsEnabledUseCase()
val isBiometricsEnabled = shouldSaveUserWalletsSyncUseCase()
val isHuaweiDevice = getIsHuaweiDeviceWithoutGoogleServicesUseCase()
val shouldShowBottomSheet = shouldAskPermission || afterUpdate
Timber.d(
"push BS afterUpdate: $afterUpdate," +
"shouldAskPermission $shouldAskPermission," +
"isBiometricsEnabled $isBiometricsEnabled," +
"isHuaweiDevice $isHuaweiDevice",
)
if (!isBiometricsEnabled) return@launch
if (isHuaweiDevice) return@launch
if (!shouldShowBottomSheet) return@launch
@ -383,9 +390,11 @@ internal class WalletModel @Inject constructor(
val otherWallets = action.wallets.minus(action.selectedWallet)
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
otherWallets.onEach { userWallet ->
modelScope.launch { walletContentFetcher(userWalletId = userWallet.walletId) }
}
otherWallets
.filterNot(UserWallet::isLocked)
.onEach { userWallet ->
modelScope.launch { walletContentFetcher(userWalletId = userWallet.walletId) }
}
}
if (action.wallets.size > 1 && isWalletsScrollPreviewEnabled()) {
@ -500,6 +509,10 @@ internal class WalletModel @Inject constructor(
clickIntents = clickIntents,
coroutineScope = modelScope,
)
action.unlockedWallets.onEach { userWallet ->
modelScope.launch { fetchWalletContent(userWallet = userWallet) }
}
}
private fun demonstrateWalletsScrollPreview(direction: Direction) {
@ -541,6 +554,8 @@ internal class WalletModel @Inject constructor(
private suspend fun fetchWalletContent(userWallet: UserWallet) {
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
if (userWallet.isLocked) return
/*
* Updating the balance of the current wallet is an essential part of InitializationWallets,
* so the coroutine is launched in the current context

View file

@ -5,13 +5,14 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.extenstions.unwrap
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.onramp.FetchHotCryptoUseCase
import com.tangem.domain.settings.NeverToShowWalletsScrollPreview
import com.tangem.domain.tokens.FetchCardTokenListUseCase
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.FetchTokenListUseCase
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
@ -87,7 +88,7 @@ internal class WalletClickIntents @Inject constructor(
stateHolder.update { it.copy(selectedWalletIndex = index) }
maybeUserWallet.onRight {
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled && !it.isLocked) {
launch { walletContentFetcher(userWalletId = it.walletId) }
}

View file

@ -2,29 +2,44 @@ package com.tangem.feature.wallet.presentation.wallet.domain
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.models.wallet.copy
import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
import timber.log.Timber
class WalletNameMigrationUseCase(
private val userWalletsListManager: UserWalletsListManager,
private val userWalletsListRepository: UserWalletsListRepository,
private val useNewListRepository: Boolean,
private val walletNamesMigrationRepository: WalletNamesMigrationRepository,
) {
suspend operator fun invoke() {
val wallets = userWalletsListManager.userWalletsSync
if (walletNamesMigrationRepository.isMigrationDone()) {
return
}
val existingNames: MutableSet<String> = mutableSetOf()
wallets.indices.forEach { i ->
val defaultName = wallets[i].name
val suggestedWalletName = suggestedWalletName(defaultName, existingNames)
if (defaultName != suggestedWalletName) {
userWalletsListManager.update(wallets[i].walletId) { it.copy(name = suggestedWalletName) }
if (useNewListRepository) {
val wallets = userWalletsListRepository.userWalletsSync()
val existingNames: MutableSet<String> = mutableSetOf()
wallets.forEach {
val defaultName = it.name
val suggestedWalletName = suggestedWalletName(defaultName, existingNames)
if (defaultName != suggestedWalletName) {
userWalletsListRepository.saveWithoutLock(it.copy(name = suggestedWalletName), canOverride = true)
}
Timber.tag("Migrated names").e(it.walletId.toString() + " " + suggestedWalletName)
}
} else {
val wallets = userWalletsListManager.userWalletsSync
val existingNames: MutableSet<String> = mutableSetOf()
wallets.indices.forEach { i ->
val defaultName = wallets[i].name
val suggestedWalletName = suggestedWalletName(defaultName, existingNames)
if (defaultName != suggestedWalletName) {
userWalletsListManager.update(wallets[i].walletId) { it.copy(name = suggestedWalletName) }
}
Timber.tag("Migrated names").e(i.toString() + " " + suggestedWalletName)
}
Timber.tag("Migrated names").e(i.toString() + " " + suggestedWalletName)
}
walletNamesMigrationRepository.setMigrationDone()

View file

@ -4,11 +4,13 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.marketprice.PriceChangeState
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.percent
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.format.bigdecimal.uncapped
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.converter.Converter
internal class SingleWalletMarketPriceConverter(
@ -47,17 +49,18 @@ internal class SingleWalletMarketPriceConverter(
}
private fun formatPrice(status: CryptoCurrencyStatus.Value, appCurrency: AppCurrency): String {
val fiatRate = status.fiatRate ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
val fiatRate = status.fiatRate ?: return DASH_SIGN
return BigDecimalFormatter.formatFiatAmountUncapped(
fiatAmount = fiatRate,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
return fiatRate.format {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
).uncapped()
}
}
private fun formatPriceChange(status: CryptoCurrencyStatus.Value): String {
val priceChange = status.priceChange ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
val priceChange = status.priceChange ?: return DASH_SIGN
return priceChange.format { percent() }
}

View file

@ -2,13 +2,13 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert
import com.tangem.core.ui.extensions.capitalize
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.domain.visa.model.VisaTxDetails
import com.tangem.feature.wallet.presentation.wallet.state.model.VisaTxDetailsBottomSheetConfig
import com.tangem.feature.wallet.child.wallet.model.intents.VisaWalletIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.VisaTxDetailsBottomSheetConfig
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toImmutableList
import org.joda.time.DateTimeZone
@ -71,11 +71,12 @@ internal class VisaTxDetailsBottomSheetConverter(
}
private fun formatFiatAmount(amount: BigDecimal, fiatCurrency: Currency): String {
return BigDecimalFormatter.formatFiatAmount(
fiatAmount = amount,
fiatCurrencyCode = fiatCurrency.currencyCode,
fiatCurrencySymbol = fiatCurrency.symbol,
)
return amount.format {
fiat(
fiatCurrencyCode = fiatCurrency.currencyCode,
fiatCurrencySymbol = fiatCurrency.symbol,
)
}
}
private companion object {

View file

@ -4,13 +4,13 @@ import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.extensions.capitalize
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.domain.visa.model.VisaTxHistoryItem
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.child.wallet.model.intents.VisaWalletIntents
import com.tangem.feature.wallet.impl.R
import com.tangem.utils.StringsSigns
import com.tangem.utils.converter.Converter
import org.joda.time.DateTimeZone
@ -29,11 +29,12 @@ internal class VisaTxHistoryItemStateConverter(
txHash = value.id,
amount = value.amount.format { crypto(visaCurrency.symbol, visaCurrency.decimals) },
// Show tx fiat amount instead of tx time
time = BigDecimalFormatter.formatFiatAmount(
fiatAmount = value.fiatAmount,
fiatCurrencyCode = value.fiatCurrency.currencyCode,
fiatCurrencySymbol = value.fiatCurrency.symbol,
),
time = value.fiatAmount.format {
fiat(
fiatCurrencyCode = value.fiatCurrency.currencyCode,
fiatCurrencySymbol = value.fiatCurrency.symbol,
)
},
status = TransactionState.Content.Status.Confirmed,
direction = TransactionState.Content.Direction.INCOMING,
iconRes = R.drawable.ic_arrow_up_24,

View file

@ -43,7 +43,8 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor(
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
@Assisted private val onWalletClick: (UserWalletId) -> Unit,
@Assisted private val messageSender: UiMessageSender,
@Assisted private val onlyMultiCurrency: Boolean,
@Assisted("onlyMultiCurrency") private val onlyMultiCurrency: Boolean,
@Assisted("authMode") private val authMode: Boolean,
private val getCardImageUseCase: GetCardImageUseCase,
dispatchers: CoroutineDispatcherProvider,
) : UserWalletsFetcher {
@ -54,7 +55,10 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor(
@OptIn(ExperimentalCoroutinesApi::class)
override val userWallets: Flow<ImmutableList<UserWalletItemUM>> = walletsFlow.transformLatest { wallets ->
val uiModels = UserWalletItemUMConverter(onClick = onWalletClick).convertList(wallets)
val uiModels = UserWalletItemUMConverter(
onClick = onWalletClick,
authMode = authMode,
).convertList(wallets)
.toImmutableList()
emit(uiModels)
@ -132,6 +136,7 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor(
balance = balance,
isBalanceHidden = balanceHidingSettings.isBalanceHidden,
artwork = artworks[userWallet.walletId],
authMode = authMode,
)
.convert(userWallet)
}
@ -149,7 +154,8 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor(
interface Factory : UserWalletsFetcher.Factory {
override fun create(
messageSender: UiMessageSender,
onlyMultiCurrency: Boolean,
@Assisted("onlyMultiCurrency") onlyMultiCurrency: Boolean,
@Assisted("authMode") authMode: Boolean,
onWalletClick: (UserWalletId) -> Unit,
): DefaultUserWalletsFetcher
}