Updated on 2026-08-14
This commit is contained in:
commit
a2b8adb47e
34 changed files with 282 additions and 248 deletions
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -77,6 +77,9 @@ enum class NetworkTypeDTO {
|
|||
@Json(name = "canto")
|
||||
CANTO,
|
||||
|
||||
@Json(name = "cardano")
|
||||
CARDANO,
|
||||
|
||||
@Json(name = "chihuahua")
|
||||
CHIHUAHUA,
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ enum class NetworkType {
|
|||
BAND_PROTOCOL,
|
||||
BITSONG,
|
||||
CANTO,
|
||||
CARDANO,
|
||||
CHIHUAHUA,
|
||||
COMDEX,
|
||||
COREUM,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 = "",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) },
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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,
|
||||
) {
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
) {
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue