diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d4fb854892..efa92ac9ff 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 7697824250..5fc86d29bc 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 76978242504ab90803b99e2b0575f7ebe8e69335 +Subproject commit 5fc86d29bc2c0dc7c057ab0242b2cfa3e9f48daf diff --git a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt index 0280493a9a..08a4bc7a3b 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt @@ -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) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/model/WelcomeModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/model/WelcomeModel.kt index 0d6b4cf157..fcfcc02601 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/model/WelcomeModel.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/model/WelcomeModel.kt @@ -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 { @@ -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) } diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt index 997da53ad1..668943b4c7 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt @@ -108,6 +108,13 @@ object NotificationsFactory { } } + // Must be called last – shown only when no other notifications exist + fun MutableList.addHighFeeNotificationIfNoOther(shouldShowHighFeeNotification: Boolean) { + if (shouldShowHighFeeNotification && this.isEmpty()) { + add(NotificationUM.Info.YieldSupplyHighNetworkFee) + } + } + fun MutableList.addReserveAmountErrorNotification( reserveAmount: BigDecimal?, sendingAmount: BigDecimal, diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt index 8408463879..dd92464d2a 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt @@ -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 = emptyMap(), - private val stakingApyMap: Map> = emptyMap(), + private val stakingApyMap: Map = 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, - stakingApyMap: Map>, + stakingApyMap: Map, 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, - stakingApyMap: Map>, + stakingApyMap: Map, ): 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>, + stakingApyMap: Map, ): 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? = 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( diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/BalanceResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/BalanceResponse.kt index 935c7f23e2..caccad1737 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/BalanceResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/BalanceResponse.kt @@ -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) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/NetworkTypeDTO.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/NetworkTypeDTO.kt index 77bfadc65c..9cb0b84276 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/NetworkTypeDTO.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/model/NetworkTypeDTO.kt @@ -77,6 +77,9 @@ enum class NetworkTypeDTO { @Json(name = "canto") CANTO, + @Json(name = "cardano") + CARDANO, + @Json(name = "chihuahua") CHIHUAHUA, diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/StakingNetworkTypeConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/StakingNetworkTypeConverter.kt index adc411b5e5..4990e03bbf 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/StakingNetworkTypeConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/StakingNetworkTypeConverter.kt @@ -33,6 +33,7 @@ object StakingNetworkTypeConverter : TwoWayConverter 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.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 diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt index 7d14d15efb..1a52fae358 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt @@ -68,6 +68,27 @@ fun CoroutineScope.launchOnCancellation(block: suspend () -> Unit) { } } +@Suppress("LongParameterList", "MagicNumber") +inline fun combine6( + flow1: Flow, + flow2: Flow, + flow3: Flow, + flow4: Flow, + flow5: Flow, + flow6: Flow, + crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R, +): Flow = 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 combine7( flow1: Flow, diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt index d2059f3785..c3ab4f66c0 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt @@ -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, ) diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt index fc619e65ab..6bb55b562c 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt @@ -52,20 +52,29 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor( private val storePollingMutex = Mutex() override suspend fun getCardBalance(userWalletId: UserWalletId): Either { - 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 { @@ -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 catchException(throwable: Throwable): Either { + Timber.tag(TAG).e(throwable) + return errorConverter.convert(throwable).left() + } + private companion object { const val MAX_POLLING_RETRIES = 3 } diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/staking/NetworkType.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/staking/NetworkType.kt index 3e0921c351..e26a393e18 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/staking/NetworkType.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/staking/NetworkType.kt @@ -28,6 +28,7 @@ enum class NetworkType { BAND_PROTOCOL, BITSONG, CANTO, + CARDANO, CHIHUAHUA, COMDEX, COREUM, diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/usecase/StakingApyFlowUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/usecase/StakingApyFlowUseCase.kt deleted file mode 100644 index 22f83164b2..0000000000 --- a/domain/staking/src/main/java/com/tangem/domain/staking/usecase/StakingApyFlowUseCase.kt +++ /dev/null @@ -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>> { - 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 - } - } - } -} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/usecase/StakingAvailabilityListUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/usecase/StakingAvailabilityListUseCase.kt new file mode 100644 index 0000000000..e38ee7233c --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/usecase/StakingAvailabilityListUseCase.kt @@ -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, + ): Map { + return coroutineScope { + cryptoCurrencyList.map { cryptoCurrency -> + async { + cryptoCurrency to stakingRepository.getStakingAvailabilitySync( + userWalletId = userWalletId, + cryptoCurrency = cryptoCurrency, + ) + } + }.awaitAll().toMap() + } + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt index fd67cc140c..a24e274c2e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt @@ -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, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt index 6ef20b93a9..d229b97f60 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt @@ -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): 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 { +internal fun withStubUnstakeAction( + networkId: String, + pendingActions: List, + activeStake: BalanceState, +): ImmutableList { return if (isStubUnstakeAction(networkId) && activeStake.type != BalanceType.REWARDS) { - activeStake.pendingActions.plus( + pendingActions.plus( PendingAction( type = StakingActionType.UNSTAKE, passthrough = "", diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt index a8b31086f1..37663aa1f0 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt @@ -41,7 +41,6 @@ internal sealed class TangemPayDetailsBalanceBlockState { data class Content( override val actionButtons: ImmutableList, - val cryptoBalance: String, val fiatBalance: String, val isBalanceFlickering: Boolean, ) : TangemPayDetailsBalanceBlockState() diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/DetailsBalanceTransformer.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/DetailsBalanceTransformer.kt index 857920d456..d2da85c3d4 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/DetailsBalanceTransformer.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/DetailsBalanceTransformer.kt @@ -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) } - } } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt index 6addbcf3d6..255f5c37a5 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt @@ -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 = emptyMap(), - private val stakingApyMap: Map> = emptyMap(), + private val stakingAvailabilityMap: Map = 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) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt index 257e68fc6b..2520491085 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt @@ -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, - private val stakingApyMap: Map>, + private val stakingAvailabilityMap: Map, private val shouldShowMainPromo: Boolean, ) : Converter { @@ -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) }, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt index d1cbb38064..5fb22bdc2b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt @@ -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> { return yieldSupplyApyFlowUseCase().distinctUntilChanged() } - private fun stakingApyFlow(): Flow>> { - return stakingApyFlowUseCase().distinctUntilChanged() - } - private fun yieldSupplyGetShouldShowMainPromoFlow(): Flow { return yieldSupplyGetShouldShowMainPromoUseCase().distinctUntilChanged() } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt index ae2648f6d7..f4b38a9290 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt @@ -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, isAccountMode: Boolean, yieldSupplyApyMap: Map = emptyMap(), - stakingApyMap: Map> = emptyMap(), + stakingAvailabilityMap: Map = 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 = emptyMap(), - stakingApyMap: Map> = emptyMap(), + stakingAvailabilityMap: Map = 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 = emptyMap(), - stakingApyMap: Map> = emptyMap(), + stakingAvailabilityMap: Map = 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, ), ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index 36de78ef22..3fbfb3592d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -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, - stakingApyMap: Map>, + stakingAvailabilityMap: Map, 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> = yieldSupplyApyFlowUseCase() .distinctUntilChanged() - private fun stakingApyFlow(): Flow>> = stakingApyFlowUseCase() - .distinctUntilChanged() - private fun yieldSupplyGetShouldShowMainPromoFlow(): Flow = yieldSupplyGetShouldShowMainPromoUseCase() .distinctUntilChanged() } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index d75d637dad..c3159ba551 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -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, ) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt index 9e148dcf24..af21e47dc4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt @@ -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, ) { diff --git a/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt b/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt index 0b1c498d8d..6d5f950820 100644 --- a/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt +++ b/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt @@ -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"), diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/notifications/YieldSupplyNotificationsComponent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/notifications/YieldSupplyNotificationsComponent.kt index 0e5ade929b..533ecd122d 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/notifications/YieldSupplyNotificationsComponent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/notifications/YieldSupplyNotificationsComponent.kt @@ -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( diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/notifications/model/YieldSupplyNotificationsModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/notifications/model/YieldSupplyNotificationsModel.kt index 2e81b51d3f..9dc753015c 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/notifications/model/YieldSupplyNotificationsModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/notifications/model/YieldSupplyNotificationsModel.kt @@ -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, 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(