Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-19 10:47:54 +03:00
commit a2b8adb47e
34 changed files with 282 additions and 248 deletions

View file

@ -277,8 +277,8 @@ dependencies {
debugImplementation(projects.features.kyc.impl)
internalImplementation(projects.features.kyc.impl)
mockedImplementation(projects.features.kyc.impl)
releaseImplementation(projects.features.kyc.mock)
externalImplementation(projects.features.kyc.mock)
releaseImplementation(projects.features.kyc.impl)
externalImplementation(projects.features.kyc.impl)
implementation(projects.features.welcome.api)
implementation(projects.features.welcome.impl)
implementation(projects.features.createWalletSelection.api)

@ -1 +1 @@
Subproject commit 76978242504ab90803b99e2b0575f7ebe8e69335
Subproject commit 5fc86d29bc2c0dc7c057ab0242b2cfa3e9f48daf

View file

@ -1,15 +1,9 @@
package com.tangem.tap.di.domain
import com.tangem.domain.staking.*
import com.tangem.domain.staking.repositories.P2PEthPoolRepository
import com.tangem.domain.staking.repositories.StakeKitActionRepository
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakeKitRepository
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.staking.repositories.StakeKitTransactionHashRepository
import com.tangem.domain.staking.toggles.StakingFeatureToggles
import com.tangem.domain.staking.repositories.*
import com.tangem.domain.staking.single.SingleStakingBalanceFetcher
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import dagger.Module
import dagger.Provides
@ -238,10 +232,7 @@ internal object StakingDomainModule {
@Provides
@Singleton
fun provideStakingApyFlowUseCase(
stakeKitRepository: StakeKitRepository,
stakingFeatureToggles: StakingFeatureToggles,
): StakingApyFlowUseCase {
return StakingApyFlowUseCase(stakeKitRepository, stakingFeatureToggles)
fun provideStakingApyFlowUseCase(stakingRepository: StakingRepository): StakingAvailabilityListUseCase {
return StakingAvailabilityListUseCase(stakingRepository)
}
}

View file

@ -2,7 +2,7 @@ package com.tangem.tap.features.welcome.model
import com.tangem.common.core.TangemError
import com.tangem.common.routing.entity.InitScreenLaunchMode
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -28,6 +28,7 @@ import javax.inject.Inject
internal class WelcomeModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val appFinisher: AppFinisher,
private val analyticsEventsHandler: AnalyticsEventHandler,
paramsContainer: ParamsContainer,
) : Model(), StoreSubscriber<WelcomeState> {
@ -54,12 +55,12 @@ internal class WelcomeModel @Inject constructor(
}
private fun unlockWallets() {
Analytics.send(SignIn.ButtonBiometricSignIn())
analyticsEventsHandler.send(SignIn.ButtonBiometricSignIn())
store.dispatch(WelcomeAction.ProceedWithBiometrics)
}
private fun scanCard() {
Analytics.send(SignIn.ButtonCardSignIn())
analyticsEventsHandler.send(SignIn.ButtonCardSignIn())
store.dispatch(WelcomeAction.ProceedWithCard)
}

View file

@ -108,6 +108,13 @@ object NotificationsFactory {
}
}
// Must be called last shown only when no other notifications exist
fun MutableList<NotificationUM>.addHighFeeNotificationIfNoOther(shouldShowHighFeeNotification: Boolean) {
if (shouldShowHighFeeNotification && this.isEmpty()) {
add(NotificationUM.Info.YieldSupplyHighNetworkFee)
}
}
fun MutableList<NotificationUM>.addReserveAmountErrorNotification(
reserveAmount: BigDecimal?,
sendingAmount: BigDecimal,

View file

@ -1,6 +1,5 @@
package com.tangem.common.ui.tokens
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.ui.R
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
@ -22,7 +21,8 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.currency.yieldSupplyKey
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.staking.model.isStakingSupported
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingOption
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
import com.tangem.lib.crypto.BlockchainUtils
@ -44,7 +44,7 @@ import java.math.BigDecimal
class TokenItemStateConverter(
private val appCurrency: AppCurrency,
private val yieldModuleApyMap: Map<String, BigDecimal> = emptyMap(),
private val stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
private val stakingApyMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
private val yieldSupplyPromoBannerKey: String? = null,
private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = {
CryptoCurrencyToIconStateConverter().convert(it)
@ -177,7 +177,7 @@ class TokenItemStateConverter(
private fun createTitleState(
currencyStatus: CryptoCurrencyStatus,
yieldModuleApyMap: Map<String, BigDecimal>,
stakingApyMap: Map<String, List<Yield.Validator>>,
stakingApyMap: Map<CryptoCurrency, StakingAvailability>,
onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?,
): TokenItemState.TitleState {
return when (val value = currencyStatus.value) {
@ -217,7 +217,7 @@ class TokenItemStateConverter(
private fun resolveEarnApy(
cryptoCurrencyStatus: CryptoCurrencyStatus,
yieldModuleApyMap: Map<String, BigDecimal>,
stakingApyMap: Map<String, List<Yield.Validator>>,
stakingApyMap: Map<CryptoCurrency, StakingAvailability>,
): EarnApyInfo? {
val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token
if (token != null && yieldModuleApyMap.isNotEmpty()) {
@ -272,49 +272,46 @@ class TokenItemStateConverter(
private fun findStakingRate(
currencyStatus: CryptoCurrencyStatus,
stakingApyMap: Map<String, List<Yield.Validator>>,
stakingApyMap: Map<CryptoCurrency, StakingAvailability>,
): StakingLocalInfo {
val stakingKey = currencyStatus.currency.stakingKey()
val validators = stakingApyMap[stakingKey]
val stakingAvailability = stakingApyMap[currencyStatus.currency] as? StakingAvailability.Available
?: return StakingLocalInfo(rate = null, isActive = false, rewardType = null)
val stakingBalance = currencyStatus.value.stakingBalance as? StakingBalance.Data
val stakeKitBalance = stakingBalance as? StakingBalance.Data.StakeKit
val rateInfo: Pair<BigDecimal, Yield.RewardType?>? = if (stakeKitBalance != null) {
// StakeKit-specific: try to find rate from validator address
val validatorsByAddress = validators.associateBy { it.address }
stakeKitBalance.balance.items
.mapNotNull { it.validatorAddress }
.firstNotNullOfOrNull { address ->
val validator = validatorsByAddress[address]
validator?.rewardInfo?.rate?.let { rate ->
rate to validator.rewardInfo?.type
}
}
?: validators
val rateInfo = when (val stakingOptions = stakingAvailability.option) {
is StakingOption.P2P -> {
// P2P or no balance: use preferred validators
// TODO add p2p logic
null
}
is StakingOption.StakeKit -> if (stakeKitBalance != null) {
val validatorsByAddress = stakingOptions.yield.validators.associateBy { it.address }
stakeKitBalance.balance.items
.mapNotNull { it.validatorAddress }
.firstNotNullOfOrNull { address ->
validatorsByAddress[address]?.rewardInfo
} ?: stakingOptions.yield.validators
.filter { it.preferred }
.mapNotNull { validator ->
validator.rewardInfo?.rate?.let { rate -> rate to validator.rewardInfo?.type }
validator.rewardInfo
}
.maxByOrNull { it.first }
} else {
// P2P or no balance: use preferred validators
// TODO p2p
validators
.filter { it.preferred }
.mapNotNull { validator ->
validator.rewardInfo?.rate?.let { rate ->
rate to validator.rewardInfo?.type
.maxByOrNull { it.rate }
} else {
stakingOptions.yield.validators
.filter { it.preferred }
.mapNotNull { validator ->
validator.rewardInfo
}
}
.maxByOrNull { it.first }
.maxByOrNull { it.rate }
}
}
return StakingLocalInfo(
rate = rateInfo?.first,
isActive = stakingBalance != null,
rewardType = rateInfo?.second,
rate = rateInfo?.rate,
isActive = stakeKitBalance != null, // todo add p2p check
rewardType = rateInfo?.type,
)
}
@ -453,18 +450,6 @@ class TokenItemStateConverter(
}
fun CryptoCurrencyStatus.Value.isFlickering(): Boolean = sources.total == StatusSource.CACHE
private fun CryptoCurrency.stakingKey(): String {
if (this is CryptoCurrency.Coin && !network.isStakingSupported) return ""
if (network.isStakingSupported && this !is CryptoCurrency.Coin) {
val isPolygonTokenOnEthereum = this is CryptoCurrency.Token &&
this.network.id.rawId.value == Blockchain.Ethereum.id &&
this.symbol == Blockchain.Polygon.currency
if (!isPolygonTokenOnEthereum) return ""
}
return "${id.rawCurrencyId}_$symbol"
}
}
private data class StakingLocalInfo(

View file

@ -6,9 +6,9 @@ import java.math.BigDecimal
@JsonClass(generateAdapter = true)
data class BalanceResponse(
@Json(name = "fiat") val fiat: FiatBalance,
@Json(name = "crypto") val crypto: CryptoBalance,
@Json(name = "available_for_withdrawal") val availableForWithdrawal: AvailableForWithdrawal,
@Json(name = "fiat") val fiat: FiatBalance?,
@Json(name = "crypto") val crypto: CryptoBalance?,
@Json(name = "available_for_withdrawal") val availableForWithdrawal: AvailableForWithdrawal?,
)
@JsonClass(generateAdapter = true)

View file

@ -77,6 +77,9 @@ enum class NetworkTypeDTO {
@Json(name = "canto")
CANTO,
@Json(name = "cardano")
CARDANO,
@Json(name = "chihuahua")
CHIHUAHUA,

View file

@ -33,6 +33,7 @@ object StakingNetworkTypeConverter : TwoWayConverter<NetworkTypeDTO, NetworkType
NetworkTypeDTO.BAND_PROTOCOL -> NetworkType.BAND_PROTOCOL
NetworkTypeDTO.BITSONG -> NetworkType.BITSONG
NetworkTypeDTO.CANTO -> NetworkType.CANTO
NetworkTypeDTO.CARDANO -> NetworkType.CARDANO
NetworkTypeDTO.CHIHUAHUA -> NetworkType.CHIHUAHUA
NetworkTypeDTO.COMDEX -> NetworkType.COMDEX
NetworkTypeDTO.COREUM -> NetworkType.COREUM
@ -106,6 +107,7 @@ object StakingNetworkTypeConverter : TwoWayConverter<NetworkTypeDTO, NetworkType
NetworkType.BAND_PROTOCOL -> NetworkTypeDTO.BAND_PROTOCOL
NetworkType.BITSONG -> NetworkTypeDTO.BITSONG
NetworkType.CANTO -> NetworkTypeDTO.CANTO
NetworkType.CARDANO -> NetworkTypeDTO.CARDANO
NetworkType.CHIHUAHUA -> NetworkTypeDTO.CHIHUAHUA
NetworkType.COMDEX -> NetworkTypeDTO.COMDEX
NetworkType.COREUM -> NetworkTypeDTO.COREUM

View file

@ -68,6 +68,27 @@ fun CoroutineScope.launchOnCancellation(block: suspend () -> Unit) {
}
}
@Suppress("LongParameterList", "MagicNumber")
inline fun <T1, T2, T3, T4, T5, T6, R> combine6(
flow1: Flow<T1>,
flow2: Flow<T2>,
flow3: Flow<T3>,
flow4: Flow<T4>,
flow5: Flow<T5>,
flow6: Flow<T6>,
crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R,
): Flow<R> = combine(flow1, flow2, flow3, flow4, flow5, flow6) { arr ->
@Suppress("UNCHECKED_CAST")
transform(
arr[0] as T1,
arr[1] as T2,
arr[2] as T3,
arr[3] as T4,
arr[4] as T5,
arr[5] as T6,
)
}
@Suppress("LongParameterList", "MagicNumber")
inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine7(
flow1: Flow<T1>,

View file

@ -138,13 +138,13 @@ internal class DefaultOnboardingRepository @Inject constructor(
response: CustomerMeResponse.Result?,
): CustomerInfo {
val card = response?.card
val balance = response?.balance
val fiatBalance = response?.balance?.fiat
val paymentAccount = response?.paymentAccount
val cardInfo = if (paymentAccount != null && card != null && balance != null) {
val cardInfo = if (paymentAccount != null && card != null && fiatBalance != null) {
CardInfo(
lastFourDigits = card.cardNumberEnd,
balance = balance.fiat.availableBalance,
currencyCode = balance.fiat.currency,
balance = fiatBalance.availableBalance,
currencyCode = fiatBalance.currency,
customerWalletAddress = paymentAccount.customerWalletAddress,
depositAddress = response.depositAddress,
)

View file

@ -52,20 +52,29 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
private val storePollingMutex = Mutex()
override suspend fun getCardBalance(userWalletId: UserWalletId): Either<UniversalError, TangemPayCardBalance> {
return requestHelper.runWithErrorLogs(TAG) {
val result = requestHelper.request(userWalletId) { authHeader ->
tangemPayApi.getCardBalance(authHeader)
}.result ?: error("Cannot get card balance")
TangemPayCardBalance(
fiatBalance = result.fiat.availableBalance,
currencyCode = result.fiat.currency,
cryptoBalance = result.crypto.balance,
availableForWithdrawal = result.availableForWithdrawal.amount,
chainId = result.crypto.chainId,
depositAddress = result.crypto.depositAddress,
contractAddress = result.crypto.tokenContractAddress,
)
}
return catch(
block = {
val response = requestHelper.performRequest(userWalletId) { authHeader ->
tangemPayApi.getCardBalance(authHeader)
}.getOrNull()
val fiatBalance = requireNotNull(response?.result?.fiat) { "Cannot get card balance fiat" }
val cryptoBalance = requireNotNull(response.result?.crypto) { "Cannot get card balance crypto" }
val withdrawalAmount = requireNotNull(response.result?.availableForWithdrawal) {
"Cannot get card balance availableForWithdrawal"
}
TangemPayCardBalance(
fiatBalance = fiatBalance.availableBalance,
currencyCode = fiatBalance.currency,
cryptoBalance = cryptoBalance.balance,
availableForWithdrawal = withdrawalAmount.amount,
chainId = cryptoBalance.chainId,
depositAddress = cryptoBalance.depositAddress,
contractAddress = cryptoBalance.tokenContractAddress,
).right()
},
catch = ::catchException,
)
}
override suspend fun revealCardDetails(userWalletId: UserWalletId): Either<UniversalError, TangemPayCardDetails> {
@ -100,7 +109,7 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
expirationMonth = result.expirationMonth,
).right()
},
catch = { errorConverter.convert(it).left() },
catch = ::catchException,
)
}
@ -286,6 +295,11 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
}
}
private fun <T> catchException(throwable: Throwable): Either<UniversalError, T> {
Timber.tag(TAG).e(throwable)
return errorConverter.convert(throwable).left()
}
private companion object {
const val MAX_POLLING_RETRIES = 3
}

View file

@ -28,6 +28,7 @@ enum class NetworkType {
BAND_PROTOCOL,
BITSONG,
CANTO,
CARDANO,
CHIHUAHUA,
COMDEX,
COREUM,

View file

@ -1,36 +0,0 @@
package com.tangem.domain.staking.usecase
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.toCoinId
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.repositories.StakeKitRepository
import com.tangem.domain.staking.toggles.StakingFeatureToggles
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/**
* Emits a map of Validators values per currency for staking.
*
* Return map:
* - key: currency staking key (network.backendId + "_" + symbol)
* - value: validators
*/
class StakingApyFlowUseCase(
private val stakeKitRepository: StakeKitRepository,
private val stakingFeatureToggles: StakingFeatureToggles,
) {
operator fun invoke(): Flow<Map<String, List<Yield.Validator>>> {
return stakeKitRepository.getEnabledYields()
.map { yields ->
yields.filterNot { yield ->
val isCardanoYield = yield.token.coinGeckoId == Blockchain.Cardano.toCoinId()
isCardanoYield && !stakingFeatureToggles.isCardanoStakingEnabled
}.associate { yield ->
val key = "${yield.token.coinGeckoId}_${yield.token.symbol}"
val apy = yield.validators
key to apy
}
}
}
}

View file

@ -0,0 +1,37 @@
package com.tangem.domain.staking.usecase
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.repositories.StakingRepository
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
/**
* Returns staking availability for a list of crypto currencies for a specific user wallet
*
* Return map:
* - key: crypto currency
* - value: staking availability for the currency
*/
class StakingAvailabilityListUseCase(
private val stakingRepository: StakingRepository,
) {
suspend fun invokeSync(
userWalletId: UserWalletId,
cryptoCurrencyList: List<CryptoCurrency>,
): Map<CryptoCurrency, StakingAvailability> {
return coroutineScope {
cryptoCurrencyList.map { cryptoCurrency ->
async {
cryptoCurrency to stakingRepository.getStakingAvailabilitySync(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
)
}
}.awaitAll().toMap()
}
}
}

View file

@ -93,6 +93,7 @@ import com.tangem.utils.extensions.isSingleItem
import com.tangem.utils.extensions.orZero
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@ -601,10 +602,20 @@ internal class StakingModel @Inject constructor(
override fun onActiveStake(activeStake: BalanceState) {
val networkId = cryptoCurrencyStatus.currency.network.rawId
if (isSingleAction(networkId, activeStake)) {
val preferredValidators = yield.validators.filter { it.preferred }
val pendingActions = activeStake.pendingActions.mapNotNull { action ->
if (action.type in listOf(StakingActionType.RESTAKE, StakingActionType.STAKE) &&
preferredValidators.isSingleItem()
) {
null
} else {
action
}
}.toImmutableList()
if (isSingleAction(networkId, pendingActions)) {
prepareForConfirmation(
balanceType = activeStake.type,
pendingActions = activeStake.pendingActions,
pendingActions = pendingActions,
balanceState = activeStake,
validator = activeStake.validator,
amountValue = activeStake.cryptoValue,
@ -613,7 +624,7 @@ internal class StakingModel @Inject constructor(
} else {
stateController.update(
ShowActionSelectorBottomSheetTransformer(
pendingActions = withStubUnstakeAction(networkId, activeStake),
pendingActions = withStubUnstakeAction(networkId, pendingActions, activeStake),
onActionSelect = { action ->
prepareForConfirmation(
balanceType = activeStake.type,

View file

@ -37,17 +37,21 @@ internal fun StakingActionType?.getPendingActionTitle(): TextReference = when (t
null -> TextReference.EMPTY
}
internal fun isSingleAction(networkId: String, activeStake: BalanceState): Boolean {
val isSingleAction = activeStake.pendingActions.size <= 1 // Either single or none pending actions
val isCompositePendingActions = isCompositePendingActions(networkId, activeStake.pendingActions)
val isRestake = activeStake.pendingActions.any { it.type.isRestake }
internal fun isSingleAction(networkId: String, pendingActions: List<PendingAction>): Boolean {
val isSingleAction = pendingActions.size <= 1 // Either single or none pending actions
val isCompositePendingActions = isCompositePendingActions(networkId, pendingActions.toPersistentList())
val isRestake = pendingActions.any { it.type.isRestake }
return isSingleAction && !isRestake || isCompositePendingActions
}
internal fun withStubUnstakeAction(networkId: String, activeStake: BalanceState): ImmutableList<PendingAction> {
internal fun withStubUnstakeAction(
networkId: String,
pendingActions: List<PendingAction>,
activeStake: BalanceState,
): ImmutableList<PendingAction> {
return if (isStubUnstakeAction(networkId) && activeStake.type != BalanceType.REWARDS) {
activeStake.pendingActions.plus(
pendingActions.plus(
PendingAction(
type = StakingActionType.UNSTAKE,
passthrough = "",

View file

@ -41,7 +41,6 @@ internal sealed class TangemPayDetailsBalanceBlockState {
data class Content(
override val actionButtons: ImmutableList<ActionButtonConfig>,
val cryptoBalance: String,
val fiatBalance: String,
val isBalanceFlickering: Boolean,
) : TangemPayDetailsBalanceBlockState()

View file

@ -2,10 +2,8 @@ package com.tangem.features.tangempay.model.transformers
import arrow.core.Either
import com.tangem.core.error.UniversalError
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.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory
import com.tangem.domain.pay.model.TangemPayCardBalance
@ -13,7 +11,6 @@ import com.tangem.features.tangempay.entity.TangemPayDetailsBalanceBlockState
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
import java.util.Currency
internal class DetailsBalanceTransformer(
@ -37,7 +34,6 @@ internal class DetailsBalanceTransformer(
TangemPayDetailsBalanceBlockState.Content(
isBalanceFlickering = false,
fiatBalance = getFiatBalanceText(balance.value),
cryptoBalance = getCryptoBalanceText(balance.value.cryptoBalance, cryptoCurrency),
actionButtons = prevState.balanceBlockState.actionButtons,
)
}
@ -52,8 +48,4 @@ internal class DetailsBalanceTransformer(
fiat(fiatCurrencyCode = currency.currencyCode, fiatCurrencySymbol = currency.symbol)
}
}
private fun getCryptoBalanceText(cryptoBalance: BigDecimal, cryptoCurrency: CryptoCurrency): String {
return cryptoBalance.format { crypto(cryptoCurrency = cryptoCurrency) }
}
}

View file

@ -175,11 +175,6 @@ private fun TangemPayDetailsBalanceBlock(
state = state,
isBalanceHidden = isBalanceHidden,
)
CryptoBalance(
modifier = Modifier.padding(start = 12.dp, top = 4.dp),
state = state,
isBalanceHidden = isBalanceHidden,
)
if (state.actionButtons.isNotEmpty()) {
HorizontalActionChips(
modifier = Modifier.padding(top = 12.dp),
@ -220,37 +215,6 @@ private fun FiatBalance(
)
}
}
@Suppress("UnusedPrivateMember")
@Composable
private fun CryptoBalance(
state: TangemPayDetailsBalanceBlockState,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
) {
when (state) {
is TangemPayDetailsBalanceBlockState.Loading -> RectangleShimmer(
modifier = modifier.size(
width = TangemTheme.dimens.size70,
height = TangemTheme.dimens.size16,
),
)
is TangemPayDetailsBalanceBlockState.Content -> Text(
modifier = modifier,
text = state.cryptoBalance.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.caption2.applyBladeBrush(
isEnabled = state.isBalanceFlickering,
textColor = TangemTheme.colors.text.tertiary,
),
)
is TangemPayDetailsBalanceBlockState.Error -> Text(
modifier = modifier,
text = DASH_SIGN.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
// endregion
@OptIn(ExperimentalMaterial3Api::class)
@ -345,7 +309,6 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
onClick = {},
),
),
cryptoBalance = "1234.56 USDT",
fiatBalance = "$1234.56",
isBalanceFlickering = false,
),

View file

@ -5,7 +5,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.nft.GetNFTCollectionsUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.repository.WalletsRepository
@ -45,7 +45,7 @@ internal class MultiWalletContentLoader(
private val walletsRepository: WalletsRepository,
private val currenciesRepository: CurrenciesRepository,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
@ -64,7 +64,7 @@ internal class MultiWalletContentLoader(
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingApyFlowUseCase = stakingApyFlowUseCase,
stakingAvailabilityListUseCase = stakingAvailabilityListUseCase,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
).let(::add)

View file

@ -5,7 +5,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.nft.GetNFTCollectionsUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.repository.WalletsRepository
@ -44,7 +44,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
private val currenciesRepository: CurrenciesRepository,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
@ -70,7 +70,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
currenciesRepository = currenciesRepository,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingApyFlowUseCase = stakingApyFlowUseCase,
stakingAvailabilityListUseCase = stakingAvailabilityListUseCase,
hotWalletFeatureToggles = hotWalletFeatureToggles,
tangemPayFeatureToggles = tangemPayFeatureToggles,
tangemPayMainSubscriberFactory = tangemPayMainSubscriberFactory,

View file

@ -3,7 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
@ -34,7 +34,7 @@ internal class SingleWalletWithTokenContentLoader(
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
) : WalletContentLoader(id = userWallet.walletId) {
@ -50,7 +50,7 @@ internal class SingleWalletWithTokenContentLoader(
tokenListStore = tokenListStore,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingApyFlowUseCase = stakingApyFlowUseCase,
stakingAvailabilityListUseCase = stakingAvailabilityListUseCase,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
).let(::add)
MultiWalletWarningsSubscriber(

View file

@ -4,7 +4,7 @@ import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
@ -35,7 +35,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
) {
@ -55,7 +55,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
getStoryContentUseCase = getStoryContentUseCase,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingApyFlowUseCase = stakingApyFlowUseCase,
stakingAvailabilityListUseCase = stakingAvailabilityListUseCase,
hotWalletFeatureToggles = hotWalletFeatureToggles,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
)

View file

@ -1,8 +1,9 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
@ -19,7 +20,7 @@ internal class SetTokenListTransformer(
private val appCurrency: AppCurrency,
private val clickIntents: WalletClickIntents,
private val yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
private val stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
private val stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
private val shouldShowMainPromo: Boolean,
) : WalletStateTransformer(userWallet.walletId) {
@ -64,7 +65,7 @@ internal class SetTokenListTransformer(
appCurrency = appCurrency,
clickIntents = clickIntents,
yieldModuleApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo,
).convert(value = this)
}

View file

@ -14,11 +14,12 @@ import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.tokenlist.TokenList.GroupedByNetwork.NetworkGroup
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
@ -38,7 +39,7 @@ internal class TokenListStateConverter(
private val selectedWallet: UserWallet,
private val clickIntents: WalletClickIntents,
private val yieldModuleApyMap: Map<String, BigDecimal>,
private val stakingApyMap: Map<String, List<Yield.Validator>>,
private val stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability>,
private val shouldShowMainPromo: Boolean,
) : Converter<WalletTokensListState, WalletTokensListState> {
@ -72,7 +73,7 @@ internal class TokenListStateConverter(
appCurrency = appCurrency,
yieldModuleApyMap = yieldModuleApyMap,
yieldSupplyPromoBannerKey = yieldSupplyPromoBannerKeyConverter.convert(params),
stakingApyMap = stakingApyMap,
stakingApyMap = stakingAvailabilityMap,
onItemClick = { _, status -> onTokenClick(accountId, status) },
onItemLongClick = { _, status -> onTokenLongClick(accountId, status) },
onApyLabelClick = { status, apySource, apy -> onApyLabelClick(status, apySource, apy) },

View file

@ -1,15 +1,15 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.utils.coroutines.combine7
import com.tangem.utils.coroutines.combine6
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -31,29 +31,36 @@ internal class AccountListSubscriber @AssistedInject constructor(
override val stateController: WalletStateController,
override val clickIntents: WalletClickIntents,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
) : BasicAccountListSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<*> = combine7(
override fun create(coroutineScope: CoroutineScope): Flow<*> = combine6(
flow1 = getAccountStatusListFlow(),
flow2 = getAppCurrencyFlow(),
flow3 = accountDependencies.expandedAccountsHolder.expandedAccounts(userWallet),
flow4 = accountDependencies.isAccountsModeEnabledUseCase(),
flow5 = yieldSupplyApyFlow(),
flow6 = stakingApyFlow(),
flow7 = yieldSupplyGetShouldShowMainPromoFlow(),
transform = ::updateState,
)
flow6 = yieldSupplyGetShouldShowMainPromoFlow(),
) { accountList, appCurrency, expandedAccounts, isAccountMode, yieldSupplyApyMap, shouldShowMainPromo ->
updateState(
accountList = accountList,
appCurrency = appCurrency,
expandedAccounts = expandedAccounts,
isAccountMode = isAccountMode,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingAvailabilityMap = stakingAvailabilityListUseCase.invokeSync(
userWalletId = userWallet.walletId,
cryptoCurrencyList = accountList.flattenCurrencies().map(CryptoCurrencyStatus::currency),
),
shouldShowMainPromo = shouldShowMainPromo,
)
}
private fun yieldSupplyApyFlow(): Flow<Map<String, BigDecimal>> {
return yieldSupplyApyFlowUseCase().distinctUntilChanged()
}
private fun stakingApyFlow(): Flow<Map<String, List<Yield.Validator>>> {
return stakingApyFlowUseCase().distinctUntilChanged()
}
private fun yieldSupplyGetShouldShowMainPromoFlow(): Flow<Boolean> {
return yieldSupplyGetShouldShowMainPromoUseCase().distinctUntilChanged()
}

View file

@ -8,8 +8,9 @@ import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies
@ -48,7 +49,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
expandedAccounts: Set<AccountId>,
isAccountMode: Boolean,
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
shouldShowMainPromo: Boolean = false,
) {
val mainAccount = accountList.mainAccount
@ -67,7 +68,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
appCurrency = appCurrency,
portfolioId = PortfolioId(mainAccount.accountId),
yieldSupplyApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo,
)
}
@ -77,7 +78,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
params = convertParams,
appCurrency = appCurrency,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo,
)
}
@ -89,7 +90,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
appCurrency: AppCurrency,
portfolioId: PortfolioId,
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
shouldShowMainPromo: Boolean,
) {
val tokenList = maybeTokenList.getOrElse(
@ -119,7 +120,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
params = TokenConverterParams.Wallet(portfolioId, tokenList),
appCurrency = appCurrency,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo,
)
}
@ -128,7 +129,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
params: TokenConverterParams,
appCurrency: AppCurrency,
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
shouldShowMainPromo: Boolean,
) {
stateController.update(
@ -138,7 +139,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
appCurrency = appCurrency,
clickIntents = clickIntents,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo,
),
)

View file

@ -8,11 +8,12 @@ import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
@ -41,7 +42,7 @@ internal abstract class BasicTokenListSubscriber(
private val walletWithFundsChecker: WalletWithFundsChecker,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
) : WalletSubscriber() {
@ -71,9 +72,8 @@ internal abstract class BasicTokenListSubscriber(
},
flow2 = appCurrencyFlow(),
flow3 = yieldSupplyApyFlow(),
flow4 = stakingApyFlow(),
flow5 = yieldSupplyGetShouldShowMainPromoFlow(),
transform = { maybeTokenList, appCurrency, yieldSupplyApyMap, stakingApyMap, shouldShowMainPromo ->
flow4 = yieldSupplyGetShouldShowMainPromoFlow(),
transform = { maybeTokenList, appCurrency, yieldSupplyApyMap, shouldShowMainPromo ->
val tokenList = maybeTokenList.getOrElse(
ifLoading = { maybeContent ->
val isRefreshing = stateHolder.getWalletState(userWallet.walletId)
@ -101,7 +101,10 @@ internal abstract class BasicTokenListSubscriber(
params = TokenConverterParams.Wallet(PortfolioId(userWallet.walletId), tokenList),
appCurrency = appCurrency,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
stakingAvailabilityMap = stakingAvailabilityListUseCase.invokeSync(
userWalletId = userWallet.walletId,
cryptoCurrencyList = tokenList.flattenCurrencies().map(CryptoCurrencyStatus::currency),
),
shouldShowMainPromo = shouldShowMainPromo,
)
@ -128,7 +131,7 @@ internal abstract class BasicTokenListSubscriber(
params: TokenConverterParams,
appCurrency: AppCurrency,
yieldSupplyApyMap: Map<String, BigDecimal>,
stakingApyMap: Map<String, List<Yield.Validator>>,
stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability>,
shouldShowMainPromo: Boolean,
) {
stateHolder.update(
@ -138,7 +141,7 @@ internal abstract class BasicTokenListSubscriber(
appCurrency = appCurrency,
clickIntents = clickIntents,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo,
),
)
@ -156,9 +159,6 @@ internal abstract class BasicTokenListSubscriber(
private fun yieldSupplyApyFlow(): Flow<Map<String, BigDecimal>> = yieldSupplyApyFlowUseCase()
.distinctUntilChanged()
private fun stakingApyFlow(): Flow<Map<String, List<Yield.Validator>>> = stakingApyFlowUseCase()
.distinctUntilChanged()
private fun yieldSupplyGetShouldShowMainPromoFlow(): Flow<Boolean> = yieldSupplyGetShouldShowMainPromoUseCase()
.distinctUntilChanged()
}

View file

@ -8,7 +8,7 @@ import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
@ -32,7 +32,7 @@ internal class MultiWalletTokenListSubscriber(
walletWithFundsChecker: WalletWithFundsChecker,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
stakingApyFlowUseCase: StakingApyFlowUseCase,
stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
) : BasicTokenListSubscriber(
userWallet = userWallet,
@ -42,7 +42,7 @@ internal class MultiWalletTokenListSubscriber(
walletWithFundsChecker = walletWithFundsChecker,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingApyFlowUseCase = stakingApyFlowUseCase,
stakingAvailabilityListUseCase = stakingAvailabilityListUseCase,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
) {

View file

@ -5,7 +5,7 @@ import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
@ -27,7 +27,7 @@ internal class SingleWalletWithTokenListSubscriber(
walletWithFundsChecker: WalletWithFundsChecker,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
stakingApyFlowUseCase: StakingApyFlowUseCase,
stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
) : BasicTokenListSubscriber(
userWallet = userWallet,
@ -37,7 +37,7 @@ internal class SingleWalletWithTokenListSubscriber(
walletWithFundsChecker = walletWithFundsChecker,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingApyFlowUseCase = stakingApyFlowUseCase,
stakingAvailabilityListUseCase = stakingAvailabilityListUseCase,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
) {

View file

@ -204,6 +204,17 @@ sealed class YieldSupplyAnalytics(
),
)
data class NoticeHighFee(
val token: String,
val blockchain: String,
) : YieldSupplyAnalytics(
event = "Notice - High Network Fee",
params = mapOf(
TOKEN_PARAM to token,
BLOCKCHAIN to blockchain,
),
)
enum class Action(val value: String) {
Start("Start"),
Approve("Approve"),

View file

@ -1,6 +1,7 @@
package com.tangem.features.yield.supply.impl.subcomponents.notifications
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@ -35,6 +36,7 @@ internal class YieldSupplyNotificationsComponent(
modifier = modifier
.fillMaxWidth()
.animateContentSize(),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
state.forEachIndexed { index, item ->
Notification(

View file

@ -5,6 +5,7 @@ import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.common.ui.notifications.NotificationsFactory.addExceedsBalanceNotification
import com.tangem.common.ui.notifications.NotificationsFactory.addFeeUnreachableNotification
import com.tangem.common.ui.notifications.NotificationsFactory.addHighFeeNotificationIfNoOther
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
@ -75,26 +76,41 @@ internal class YieldSupplyNotificationsModel @Inject constructor(
onReload = params.callback::onFeeReload,
)
if (data.shouldShowHighFeeNotification) {
add(NotificationUM.Info.YieldSupplyHighNetworkFee)
}
}
if (notifications.any { it is NotificationUM.Error.TokenExceedsBalance }) {
analyticsEventHandler.send(
YieldSupplyAnalytics.NoticeNotEnoughFee(
token = cryptoCurrencyStatus.currency.symbol,
blockchain = cryptoCurrencyStatus.currency.network.name,
),
addHighFeeNotificationIfNoOther(
shouldShowHighFeeNotification = data.shouldShowHighFeeNotification,
)
}
sendAnalytics(notifications, cryptoCurrencyStatus.currency)
uiState.update { notifications.toPersistentList() }
yieldSupplyNotificationsUpdateListener.callbackHasError(notifications.any())
// Business requirement that YieldSupplyHighNetworkFee is not an error and doesn't block the button
val hasError = notifications.any { it !is NotificationUM.Info.YieldSupplyHighNetworkFee }
yieldSupplyNotificationsUpdateListener.callbackHasError(hasError)
}.launchIn(modelScope)
}
private fun sendAnalytics(notifications: List<NotificationUM>, currency: CryptoCurrency) {
if (notifications.any { it is NotificationUM.Error.TokenExceedsBalance }) {
analyticsEventHandler.send(
YieldSupplyAnalytics.NoticeNotEnoughFee(
token = currency.symbol,
blockchain = currency.network.name,
),
)
}
if (notifications.any { it is NotificationUM.Info.YieldSupplyHighNetworkFee }) {
analyticsEventHandler.send(
YieldSupplyAnalytics.NoticeHighFee(
token = currency.symbol,
blockchain = currency.network.name,
),
)
}
}
private fun openTokenDetails(cryptoCurrency: CryptoCurrency) {
appRouter.push(
AppRoute.CurrencyDetails(