diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 67cd08e295..fe2add9b71 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -95,7 +95,7 @@ sealed class AnalyticsParam { } companion object Key { - const val BLOCKCHAIN = "blockchain" + const val BLOCKCHAIN = "Blockchain" const val TOKEN = "Token" const val SOURCE = "Source" const val BALANCE = "Balance" diff --git a/app/src/main/java/com/tangem/tap/network/auth/DefaultExpressAuthProvider.kt b/app/src/main/java/com/tangem/tap/network/auth/DefaultExpressAuthProvider.kt index 452b90127d..5087ef7019 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/DefaultExpressAuthProvider.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/DefaultExpressAuthProvider.kt @@ -1,5 +1,6 @@ package com.tangem.tap.network.auth +import com.tangem.common.CardIdRangeDec import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getSyncOrDefault @@ -48,7 +49,12 @@ internal class DefaultExpressAuthProvider( } private fun isChangeNow(selectedUserWallet: UserWallet): Boolean { - return selectedUserWallet.scanResponse.card.batchId == BATCH_ID_CHANGENOW + val changeNowRange = CardIdRangeDec( + start = "AF99001800554008", + end = "AF99001800559994", + ) + val card = selectedUserWallet.scanResponse.card + return card.batchId == BATCH_ID_CHANGENOW || changeNowRange?.contains(card.cardId) == true } private fun isPartner(selectedUserWallet: UserWallet): Boolean { diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index 04e8d6b930..2e0518e079 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -10,6 +10,8 @@ import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRA import com.tangem.datasource.api.express.models.response.Asset import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader import com.tangem.datasource.local.token.ExpressAssetsStore +import com.tangem.domain.core.lce.Lce +import com.tangem.domain.exchange.ExchangeableState import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase @@ -76,10 +78,6 @@ internal class DefaultRampManager( catch = { raise(ScenarioUnavailabilityReason.NotSupportedBySellService(status.currency.name)) }, ) - ensure(condition = sellSupportedByService) { - ScenarioUnavailabilityReason.NotSupportedBySellService(status.currency.name) - } - val reason = getSendUnavailabilityReason(userWalletId, status) ensure(condition = reason is ScenarioUnavailabilityReason.None) { @@ -94,14 +92,19 @@ internal class DefaultRampManager( } } + ensure(condition = sellSupportedByService) { + ScenarioUnavailabilityReason.NotSupportedBySellService(status.currency.name) + } + Unit.right() } } - override suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean { - return runCatching { getExchangeableFlag(userWalletId, cryptoCurrency) && !cryptoCurrency.isCustom } - .getOrNull() - ?: false + override suspend fun availableForSwap( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + ): ExchangeableState { + return runCatching { getExchangeableState(userWalletId, cryptoCurrency) }.getOrNull() ?: ExchangeableState.Error } override fun getBuyInitializationStatus(): Flow { @@ -128,14 +131,29 @@ internal class DefaultRampManager( return expressServiceLoader.getInitializationStatus(userWalletId) } - private suspend fun getExchangeableFlag(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean { + private suspend fun getExchangeableState( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + ): ExchangeableState { return withContext(dispatchers.io) { - val asset = expressServiceLoader.getInitializationStatus(userWalletId) - .firstOrNull() - ?.getOrNull() - ?.find { cryptoCurrency.findAssetPredicate(it) } + val initializationStatus = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull() + ?: return@withContext ExchangeableState.Loading + when (initializationStatus) { + is Lce.Error -> ExchangeableState.Error + is Lce.Loading -> ExchangeableState.Loading + is Lce.Content -> { + val foundAsset = initializationStatus.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) } + foundAsset?.exchangeAvailable?.toExchangeableState() ?: ExchangeableState.AssetNotFound + } + } + } + } - asset?.exchangeAvailable ?: false + private fun Boolean.toExchangeableState(): ExchangeableState { + return if (this) { + ExchangeableState.Exchangeable + } else { + ExchangeableState.NotExchangeable } } diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenActionsUtils.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenActionsUtils.kt index baf5001db7..7337dccc5d 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenActionsUtils.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenActionsUtils.kt @@ -34,7 +34,11 @@ fun ScenarioUnavailabilityReason.getUnavailabilityReasonText(): TextReference { formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName), ) } - ScenarioUnavailabilityReason.Unreachable -> { + is ScenarioUnavailabilityReason.TokenNoQuotes, + ScenarioUnavailabilityReason.SingleWallet, + is ScenarioUnavailabilityReason.ExpressUnreachable, + ScenarioUnavailabilityReason.Unreachable, + -> { resourceReference( id = R.string.token_button_unavailability_generic_description, ) @@ -45,6 +49,21 @@ fun ScenarioUnavailabilityReason.getUnavailabilityReasonText(): TextReference { ScenarioUnavailabilityReason.UsedOutdatedData -> { resourceReference(id = R.string.token_button_unavailability_reason_out_of_date_balance) } + is ScenarioUnavailabilityReason.AssetNotFound -> resourceReference( + id = R.string.token_button_unavailability_reason_not_exchangeable, + formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName), + ) + is ScenarioUnavailabilityReason.CustomToken -> { + resourceReference( + id = R.string + .token_button_unavailability_reason_custom_token, + ) + } + is ScenarioUnavailabilityReason.DataLoading, + is ScenarioUnavailabilityReason.ExpressLoading, + -> { + resourceReference(id = R.string.token_button_unavailability_reason_loading) + } ScenarioUnavailabilityReason.None -> { throw IllegalArgumentException("The unavailability reason must be other than None") } diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index 38afb4a40f..31e79f809c 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -175,7 +175,7 @@ sealed class AnalyticsParam { } companion object Key { - const val BLOCKCHAIN = "blockchain" + const val BLOCKCHAIN = "Blockchain" const val TOKEN_PARAM = "Token" const val SOURCE = "Source" const val BALANCE = "Balance" @@ -210,5 +210,6 @@ sealed class AnalyticsParam { const val RESIDENCE = "Residence" const val PAYMENT_METHOD = "Payment Method" const val WATCHED = "Watched" + const val ACTION = "Action" } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt index fbe1a1435b..15acc9d99b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt @@ -3,6 +3,7 @@ package com.tangem.datasource.local.token import androidx.datastore.core.DataStore import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO import com.tangem.datasource.local.datastore.RuntimeSharedStore +import com.tangem.datasource.local.token.StakingBalanceStore.StakingID import com.tangem.datasource.local.token.converter.YieldBalanceConverter import com.tangem.domain.models.StatusSource import com.tangem.domain.staking.model.stakekit.YieldBalance @@ -26,10 +27,16 @@ internal class DefaultStakingBalanceStore( private val runtimeStore: RuntimeSharedStore, ) : StakingBalanceStore { - override fun get(userWalletId: UserWalletId): Flow> = channelFlow { + override fun get(userWalletId: UserWalletId, stakingIds: List): Flow> = channelFlow { val cachedBalances = persistenceStore.data .map { val wrappers = it[userWalletId.stringValue].orEmpty() + .filter { wrapper -> + stakingIds.any { id -> + id.address == wrapper.addresses.address && id.integrationId == wrapper.integrationId + } + } + YieldBalanceConverter(isCached = true).convertSet(input = wrappers) } .firstOrNull() @@ -40,33 +47,64 @@ internal class DefaultStakingBalanceStore( } runtimeStore.get() - .map { it[userWalletId].orEmpty() } + .map { + it[userWalletId].orEmpty().filter { balance -> + stakingIds.any { id -> + id.address == balance.address && id.integrationId == balance.integrationId + } + } + .toSet() + } .onEach { - val mergedBalances = mergeYieldBalances(cachedBalances = cachedBalances, runtimeBalances = it) + val mergedBalances = mergeYieldBalances( + stakingIds = stakingIds, + cachedBalances = cachedBalances, + runtimeBalances = it, + ) send(mergedBalances) } .launchIn(scope = this) } - override fun get(userWalletId: UserWalletId, address: String, integrationId: String): Flow { - return get(userWalletId).map { balances -> - balances.getBalance(address = address, integrationId = integrationId) + override fun get(userWalletId: UserWalletId, stakingID: StakingID): Flow { + return get(userWalletId = userWalletId, stakingIds = listOf(stakingID)).map { balances -> + balances.getBalance(stakingID = stakingID) } } override suspend fun getSyncOrNull(userWalletId: UserWalletId): Set? { - return runtimeStore.getSyncOrNull()?.getValue(userWalletId) + val runtimeBalances = runtimeStore.getSyncOrNull()?.getValue(userWalletId).orEmpty() + val cachedBalances = persistenceStore.data.firstOrNull()?.get(userWalletId.stringValue).orEmpty() + + if (runtimeBalances.isEmpty() && cachedBalances.isEmpty()) return null + + return cachedBalances.mapTo(hashSetOf()) { + val cached = YieldBalanceConverter(source = StatusSource.ONLY_CACHE).convert(value = it) + val runtime = runtimeBalances.getBalance(address = cached.address, integrationId = cached.integrationId) + + if (runtime == null || runtime is YieldBalance.Error) cached else runtime + } } - override suspend fun getSyncOrNull( - userWalletId: UserWalletId, - address: String, - integrationId: String, - ): YieldBalance? { - val balances = getSyncOrNull(userWalletId) ?: return null + override suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingIds: List): Set? { + val runtime = runtimeStore.getSyncOrNull()?.getValue(userWalletId) + val cached = persistenceStore.data.firstOrNull()?.get(userWalletId.stringValue) - return balances.getBalance(address, integrationId) + if (runtime.isNullOrEmpty() && cached.isNullOrEmpty()) return null + + return mergeYieldBalances( + cachedBalances = YieldBalanceConverter(source = StatusSource.ONLY_CACHE) + .convertSet(input = cached.orEmpty()), + runtimeBalances = runtime.orEmpty(), + stakingIds = stakingIds, + ) + } + + override suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingID: StakingID): YieldBalance? { + val balances = getSyncOrNull(userWalletId = userWalletId, stakingIds = listOf(stakingID)) ?: return null + + return balances.getBalance(stakingID = stakingID) } override suspend fun store(userWalletId: UserWalletId, items: Set) { @@ -88,34 +126,52 @@ internal class DefaultStakingBalanceStore( } } - override suspend fun refresh(userWalletId: UserWalletId, addressWithIntegrationIdMap: Map) { + override suspend fun refresh(userWalletId: UserWalletId, stakingIds: List) { updateRuntimeStore(userWalletId = userWalletId) { saved -> - saved.mapTo(hashSetOf()) { balance -> - val refreshIntegrationId = addressWithIntegrationIdMap[balance.address] + saved.mapTo(hashSetOf()) { + val yieldBalance = it.takeIf { balance -> + stakingIds.any { id -> balance.integrationId == id.integrationId && balance.address == id.address } + } - if (balance.integrationId == refreshIntegrationId) { - when (balance) { - is YieldBalance.Data -> balance.copy(source = StatusSource.CACHE) - is YieldBalance.Empty -> balance.copy(source = StatusSource.CACHE) - is YieldBalance.Error -> balance + if (yieldBalance != null) { + when (yieldBalance) { + is YieldBalance.Data -> yieldBalance.copy(source = StatusSource.CACHE) + is YieldBalance.Empty -> yieldBalance.copy(source = StatusSource.CACHE) + is YieldBalance.Error -> yieldBalance } } else { - balance + it } } } } - override suspend fun store( - userWalletId: UserWalletId, - integrationId: String, - address: String, - item: YieldBalanceWrapperDTO, - ) { + override suspend fun store(userWalletId: UserWalletId, stakingID: StakingID, item: YieldBalanceWrapperDTO) { coroutineScope { launch { - storeInRuntimeStore(userWalletId, integrationId, address, item) - storeInPersistenceStore(userWalletId, integrationId, address, item) + storeInRuntimeStore( + userWalletId = userWalletId, + integrationId = stakingID.integrationId, + address = stakingID.address, + item = item, + ) + + storeInPersistenceStore( + userWalletId = userWalletId, + integrationId = stakingID.integrationId, + address = stakingID.address, + item = item, + ) + } + } + } + + override suspend fun storeSingleYieldBalance(userWalletId: UserWalletId, item: YieldBalance) { + runtimeStore.update(default = emptyMap()) { saved -> + saved.toMutableMap().apply { + this[userWalletId] = saved[userWalletId] + ?.addOrReplace(item) { it.integrationId == item.integrationId && it.address == item.address } + ?: setOf(item) } } } @@ -178,28 +234,22 @@ internal class DefaultStakingBalanceStore( private fun mergeYieldBalances( cachedBalances: Set, runtimeBalances: Set, + stakingIds: List, ): Set { - if (runtimeBalances.isEmpty()) { - return cachedBalances.mapNotNullTo(hashSetOf()) { - when (it) { - is YieldBalance.Data -> it.copy(source = StatusSource.ONLY_CACHE) - is YieldBalance.Empty -> it.copy(source = StatusSource.ONLY_CACHE) - is YieldBalance.Error -> null - } + return stakingIds.mapTo(hashSetOf()) { id -> + val runtime = runtimeBalances.getBalance(stakingID = id) + + if (runtime == null || runtime is YieldBalance.Error) { + getCachedBalanceIfPossible(cachedBalances = cachedBalances, stakingID = id) + } else { + runtime } } - - return runtimeBalances - .map { runtime -> - runtime.takeIf { runtime !is YieldBalance.Error } - ?: getCachedBalanceIfPossible(cachedBalances, runtime) - } - .toSet() } - private fun getCachedBalanceIfPossible(cachedBalances: Set, runtime: YieldBalance): YieldBalance { - val cached = cachedBalances.getBalance(address = runtime.address, integrationId = runtime.integrationId) - ?: return runtime + private fun getCachedBalanceIfPossible(cachedBalances: Set, stakingID: StakingID): YieldBalance { + val cached = cachedBalances.getBalance(stakingID) + ?: return YieldBalance.Error(integrationId = stakingID.address, address = stakingID.integrationId) val updatedCached = when (cached) { is YieldBalance.Data -> cached.copy(source = StatusSource.ONLY_CACHE) @@ -207,7 +257,11 @@ internal class DefaultStakingBalanceStore( is YieldBalance.Error -> null } - return updatedCached ?: runtime + return updatedCached ?: YieldBalance.Error(integrationId = stakingID.address, address = stakingID.integrationId) + } + + private fun Set.getBalance(stakingID: StakingID): YieldBalance? { + return getBalance(address = stakingID.address, integrationId = stakingID.integrationId) } private fun Set.getBalance(address: String?, integrationId: String?): YieldBalance? { diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingBalanceStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingBalanceStore.kt index 7722a21c10..779536d430 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingBalanceStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingBalanceStore.kt @@ -9,23 +9,32 @@ import kotlinx.coroutines.flow.Flow /** Staking balance store */ interface StakingBalanceStore { - /** Get flow of [YieldBalanceList] by [userWalletId] */ - fun get(userWalletId: UserWalletId): Flow> + /** Get flow of [YieldBalanceList] by [userWalletId] and [stakingIds] */ + fun get(userWalletId: UserWalletId, stakingIds: List): Flow> - /** Get flow of [YieldBalance] by [userWalletId], [address] and [integrationId] */ - fun get(userWalletId: UserWalletId, address: String, integrationId: String): Flow + /** Get flow of [YieldBalance] by [userWalletId] and [stakingID] */ + fun get(userWalletId: UserWalletId, stakingID: StakingID): Flow - /** Get [YieldBalanceList] synchronously or null by [userWalletId] */ + /** Get all [YieldBalance] synchronously or null by [userWalletId] */ suspend fun getSyncOrNull(userWalletId: UserWalletId): Set? - /** Get [YieldBalance] synchronously or null by [userWalletId], [address] and [integrationId] */ - suspend fun getSyncOrNull(userWalletId: UserWalletId, address: String, integrationId: String): YieldBalance? + /** Get [YieldBalanceList] synchronously or null by [userWalletId] and [stakingIds] */ + suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingIds: List): Set? + + /** Get [YieldBalance] synchronously or null by [userWalletId] and [stakingID] */ + suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingID: StakingID): YieldBalance? /** Store [items] by [userWalletId] */ suspend fun store(userWalletId: UserWalletId, items: Set) - /** Store [item] by [userWalletId], [integrationId] and [address] */ - suspend fun store(userWalletId: UserWalletId, integrationId: String, address: String, item: YieldBalanceWrapperDTO) + /** Store [item] by [userWalletId] and [stakingID] */ + suspend fun store(userWalletId: UserWalletId, stakingID: StakingID, item: YieldBalanceWrapperDTO) - suspend fun refresh(userWalletId: UserWalletId, addressWithIntegrationIdMap: Map) + /** Store [item] by [userWalletId] */ + suspend fun storeSingleYieldBalance(userWalletId: UserWalletId, item: YieldBalance) + + /** Refresh balances of [stakingIds] by [userWalletId] */ + suspend fun refresh(userWalletId: UserWalletId, stakingIds: List) + + data class StakingID(val integrationId: String, val address: String) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/YieldBalanceConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/YieldBalanceConverter.kt index 9df25f3294..6fc465801a 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/YieldBalanceConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/converter/YieldBalanceConverter.kt @@ -7,14 +7,18 @@ import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.staking.model.stakekit.YieldBalanceItem import com.tangem.utils.converter.Converter -internal class YieldBalanceConverter(private val isCached: Boolean) : Converter { +internal class YieldBalanceConverter( + private val source: StatusSource, +) : Converter { + + constructor(isCached: Boolean) : this(source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL) override fun convert(value: YieldBalanceWrapperDTO): YieldBalance { return if (value.balances.isEmpty()) { YieldBalance.Empty( integrationId = value.integrationId, address = value.addresses.address, - source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL, + source = source, ) } else { YieldBalance.Data( @@ -40,7 +44,7 @@ internal class YieldBalanceConverter(private val isCached: Boolean) : Converter< .sortedWith(compareBy({ it.type }, { it.amount })), integrationId = value.integrationId, ), - source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL, + source = source, ) } } diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 003034b671..c5b765d329 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -6,7 +6,7 @@ Einige Netzwerke sind derzeit nicht erreichbar. Bitte versuche es später erneut. Die Daten wurden noch nicht geladen. Die Aktion ist derzeit nicht verfügbar. Bitte versuche es später erneut oder aktualisiere die Daten, indem Du auf dem Bildschirm nach unten wischst. - Schaltfläche ist nicht verfügbar + Aktion ist nicht verfügbar Wähle den Token Du kannst Deinen Token nicht finden? Gehe zum Bereich „Markt“ auf der Hauptseite und fügen diesen zum tauschen im Portfolio hinzu. Es gibt keine verfügbaren Token, die mit dem ausgewählten Token getauscht werden können. Bitte wähle einen anderen. @@ -781,6 +781,7 @@ %s geschätzter Profit Marktbewertung Metriken + Gemäß den Netzwerkregeln %1$s sind Ansprüche ab %2$s möglich. Die unten aufgeführten Beträge werden Deinem Konto nach der Aufhebung des Stakings gutgeschrieben. Mindestanforderungen Keine Belohnungen verfügbar Belohnungen beanspruchen @@ -827,6 +828,7 @@ Reinvestiert Deine verdienten Prämien in Deinen Einsatzbetrag und erhöht so den potenziellen Gewinn. Mit Restake kannst Du Dein Guthaben von einem Validator zu einem anderen verschieben, ohne dass Du den Stake aufheben musst. Du bist dabei, Dein gesamtes Guthaben zu staken. Wir empfehlen, einen kleinen Betrag übrig zu lassen, um die Netzwerkgebühren für die Aufhebung des Stakes oder das Einfordern von Prämien abzudecken. + Um mit dem Staking bei TON zu beginnen, führe zunächst eine ausgehende Transaktion in beliebiger Höhe durch – dadurch wird Deine Wallet aktiviert. Entsperre dein Geld, um es aus dem Staking-Prozess abzuheben. Das Freischalten nimmt %s. Nach Ablauf der 21-tägigen Bindungsfrist kannst Du über Dein Guthaben verfügen. Die Prämie wird zusammen mit dem ungestaketen Guthaben abgehoben. Deine Assets stehen Dir nach Ablauf der Frist für die Aufhebung der Bindung %s zur Verfügung. @@ -926,8 +928,10 @@ Rückgängig machen Dieser Vorgang ist derzeit nicht verfügbar. Bitte versuche es später noch einmal. Der Kauf von %s wird von aktuellen Anbietern nicht unterstützt, aber wir arbeiten daran, weitere Optionen hinzuzufügen. + Die Aktion ist für benutzerdefinierte Token nicht verfügbar. Du hast kein Guthaben um dies zu Verkaufen. Lade dein Konto auf, um Geld von dort zu verkaufen. Du hast kein Guthaben zum Versenden. Lade dein Konto auf, um Geld von dort aus senden zu können. + Die Daten wurden noch nicht geladen. Dies kann einige Sekunden dauern. Bitte versuche es später erneut. Das Tauschen von %s wird von aktuellen Anbietern nicht unterstützt, aber wir arbeiten daran, weitere Optionen hinzuzufügen. Der angezeigte Kontostand kann aufgrund der Zwischenspeicherung veraltet sein. Der Verkauf von Geldmitteln ist möglich, sobald die ausstehende(n) Transaktion(en) im %s -Netzwerk abgeschlossen ist/sind. @@ -976,6 +980,7 @@ Tangem Twin Diese Aktion ist unumkehrbar. Du hast keinen Zugriff mehr auf die alte Wallet. Tippe auf die Doppelkarte oder Ring mit der Nummer %s und entferne sie erst am Ende des Vorgangs. + Es ist ein Fehler aufgetreten. Fehlercode: %s. Bitte kontaktiere unseren Support. Verwende %s oder scanne eine Karte oder Ring, um Zugriff auf deine Wallet zu erhalten. Verbindung fehlgeschlagen: Diese dApp verwendet Wallet Connect Version 1.0, die nicht unterstützt wird. Bitte stelle sicher, dass die dApp Wallet Connect Version 2.0 unterstützt, um eine erfolgreiche Verbindung herzustellen. Bleib auf dem Laufenden mit den neuesten Funktionen und Neuigkeiten diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 5b203d5b9a..bd31c5f775 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -920,6 +920,7 @@ Comprar %s no está disponible con los proveedores actuales pero estamos trabajando para agregar más opciones. No tiene fondos para vender. Recargue su cuenta para poder vender fondos desde ella. No tienes fondos para enviar. Recarga tu cuenta para poder enviar fondos desde ella. + Los datos aún no se han cargado. Esto puede tardar unos segundos. Por favor, inténtelo de nuevo más tarde. El servicio de intercambio %s no está compatible con los proveedores actuales pero estamos trabajando para agregar más opciones. La venta de fondos estará disponible una vez que la(s) transacción(es) pendiente(s) en la red %s se complete El envío de fondos estará disponible una vez que se completen las transacciones pendientes en la red %s. @@ -939,7 +940,7 @@ Cambie este token por otro por una tarifa de servicio de %1$s del %2$s al %3$s de febrero. Intercambio con Changelly, %s tarifa Intercambie ahora - Crypto-monnaies populaire 🔥 + Criptomonedas populares 🔥 No disponible para compra No disponible para vender No disponible para cambio desde %s diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 05074ae06e..8adef0ab59 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -922,6 +922,7 @@ L\'achat de %s n\'est pas pris en charge par les fournisseurs actuels mais nous travaillons à ajouter davantage d\'options. Vous n\'avez pas de fonds à vendre. Renflouez votre compte pour pouvoir vendre des fonds à partir de celui-ci. Vous n\'avez pas de fonds à envoyer. Renflouez votre compte pour pouvoir envoyer des fonds à partir de celui-ci. + Die Daten wurden noch nicht geladen. Dies kann einige Sekunden dauern. Bitte versuchen Sie es später noch einmal. Le service d\'échange %s n\'est pas pris en charge par les fournisseurs actuels mais nous travaillons à ajouter plus d\'options. La vente de fonds sera disponible une fois que la ou les transactions en attente dans le réseau %s seront terminées L\'envoi de fonds sera disponible une fois la ou les transactions en attente dans le réseau %s terminées. diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 6abf715762..2ff3e6be5f 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -818,6 +818,7 @@ 獲得した報酬をステーキングに再投資し、潜在的な収益を増やします。 再ステーキングを使うと、ステーキングを解除することなく、あるバリデータから別のバリデータに資金を移動できます。 残高のすべてをステーキングしようとしています。ステーキング解除や報酬請求にかかるネットワーク手数料をカバーするために、少額を残しておくことをお勧めします。 + TONでステーキングを開始するには、まず任意の金額の送金を行ってください。これにより、ウォレットがアクティブになります。 資金をステーキングから引き出すには、ロックを解除してください。ロック解除には%sかかります。 資金は、21日間のロック解除期間後に使用可能になります。報酬は、ロック解除後の資金とともに引き出されます。 %sのステーキング解除期間後、資金はすぐ利用可能となります。 @@ -920,7 +921,7 @@ カスタムトークンでは使用できません。 売却できる資金がありません。アカウントに入金して、売却できるようにしてください。 送金する資金がありません。アカウントに入金して、送金できるようにしてください。 - データはまだ読み込まれていません。数秒かかる場合があります。しばらくしてからもう一度お試しください。 + 現在データを読み込んでいます。これには数秒かかる場合があります。しばらくしてからもう一度お試しください。 %sのスワップは、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。 表示残高はキャッシュにより古くなっている可能性があります。 %sネットワーク上の保留中の取引が完了すると、資金の売却が可能になります。 @@ -969,6 +970,7 @@ Tangemツイン この操作は元に戻せません。古いウォレットにはアクセスできなくなります。 番号%sのツインカードをタップし、操作が終了するまで取り外さないでください。 + エラーが発生しました。エラー コード:%s。サポートにお問い合わせください。 %sを使用するか、カード / リングをスキャンしてウォレットにアクセスしてください 接続に失敗しました:このdAppは、サポートされていないWallet Connectバージョン1.0を使用しています。正常に接続するには、dAppがWallet Connectバージョン2.0をサポートしていることを確認してください。 最新の機能とニュースをお届けします diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 33b0c3b4a9..b46553bb3b 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -841,6 +841,7 @@ Реинвестируйте свои заработанные награды в вашу застейканную сумму, увеличивая потенциальный доход Рестейк позволяет вам переместить средства из одного валидатора в другого без необходимости выхода из стейкинга. Вы собираетесь застейкать весь баланс, рекомендуем оставить небольшую сумму для оплаты комиссии сети при выходе из стейкинга или получении награды. + Чтобы начать стейкинг в TON, сначала осуществите исходящую транзакцию на любую сумму — это активирует ваш кошелек. Разблокируйте свои средства, чтобы вывести их из стейкинга. Разблокировка займёт %s. Ваши средства будут доступны для использования после 21-дневного периода отзыва. Награда будет выведена вместе с вашими выводими средствами. Ваши средства будут доступны после %s периода отзыва. @@ -941,6 +942,7 @@ Покупка %s в данный момент не поддерживается ни одним провайдером. Мы работаем над добавлением новых возможностей. Следите за нашими новостями. У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства. У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства. + Данные в процессе загрузки. Это может занять несколько секунд. Пожалуйста, попробуйте позже. Обмен %s в данный момент не поддерживается ни одним провайдером. Мы работаем над добавлением новых возможностей. Следите за нашими новостями. Продажа средств станет доступной после завершения транзакции(-ий) в сети %s Отправка средств станет доступной после завершения транзакции(-ий) в сети %s @@ -988,6 +990,7 @@ Tangem Twin Это действие необратимо. У вас не будет доступа к старому кошельку. Приложите twin-карту с номером %s и не убирайте до окончания операции + Произошла ошибка. Код ошибки: %s. Попробуйте, пожалуйста, снова. Если проблема будет продолжать возникать — обратитесь в службу поддержки. Используйте %s или отсканируйте карту/кольцо, чтобы получить доступ к своему кошельку Соединение не удалось: это dApp использует Wallet Connect версии 1.0, которая не поддерживается. Убедитесь, что dApp поддерживает Wallet Connect версии 2.0 для успешного подключения. Будьте в курсе новых функций и новостей diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 214896573f..1d760fbe95 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -940,6 +940,7 @@ Наразі купівля монети %s недоступна. Слідкуйте за нашими оновленнями. У вас немає коштів для продажу. Поповніть рахунок, щоб мати змогу продати з нього кошти. У вас немає коштів для відправлення. Поповніть рахунок, щоб мати змогу надіслати з нього кошти. + Дані ще не завантажені. Це може зайняти кілька секунд. Будь ласка, спробуйте пізніше. Наразі обмін монети %s недоступний. Слідкуйте за нашими оновленнями. Продаж коштів стане доступним після завершення транзакції(-ій) в мережі %s Надсилання коштів стане доступним після завершення транзакції(-ій) в мережі %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 16dff9ff2e..65eaf3453a 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -832,6 +832,7 @@ Reinvests your earned rewards in your staked amount, increasing potential earnings. Restake lets you move your funds from one validator to another without the need to unstake You’re about to stake your entire balance. We recommend leaving a small amount to cover network fees for unstaking or claiming rewards. + To start staking in TON, first make an outgoing transaction of any amount — this will activate your wallet. Unlock your money to withdraw it from staking process. Unlocking takes %s. Your funds will be available for use after the 21-day unbonding period. Reward will be withdrawn along with your unstaking funds. Your funds will be available for use after the %s unbonding period. @@ -934,7 +935,7 @@ Action is not available for custom tokens. You do not have funds to sell. Top up your account to be able to sell funds from it. You do not have funds to send. Top up your account to be able to send funds from it. - The data has not loaded yet. This may take a few seconds. Please try again later. + The data is currently loading. This may take a few seconds. Please try again later. Swapping %s is not supported by current providers, but we are working to add more options. The displayed balance might be outdated due to caching. Selling funds will be available once the pending transaction(s) on the %s network is complete. @@ -983,6 +984,7 @@ Tangem Twin This action is irreversible. You will not have access to the old wallet. Tap the twin card with number %s and do not remove until the end of the operation + We\'ve encountered an error. Error code: %s. Please contact our support. Use %s or scan a card/ring to have access to your wallet Connection failed: This dApp uses Wallet Connect version 1.0, which is not supported. Please ensure the dApp supports Wallet Connect version 2.0 to connect successfully. Stay up to date with the latest features and news diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index 9a69615f74..d08938d3fe 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -372,20 +372,33 @@ internal class DefaultStakingRepository( } val requestBody = getBalanceRequestData(address, integrationId) - val result = stakeKitApi.getSingleYieldBalance( - integrationId = requestBody.integrationId, - body = requestBody, - ).getOrThrow() - stakingBalanceStore.store( - userWalletId = userWalletId, - integrationId = requestBody.integrationId, - address = address, - item = YieldBalanceWrapperDTO( - balances = result, - integrationId = requestBody.integrationId, - addresses = requestBody.addresses, - ), + safeApiCall( + call = { + val result = stakeKitApi.getSingleYieldBalance( + integrationId = requestBody.integrationId, + body = requestBody, + ).bind() + + stakingBalanceStore.store( + userWalletId = userWalletId, + stakingID = StakingBalanceStore.StakingID( + integrationId = requestBody.integrationId, + address = address, + ), + item = YieldBalanceWrapperDTO( + balances = result, + integrationId = requestBody.integrationId, + addresses = requestBody.addresses, + ), + ) + }, + onError = { + stakingBalanceStore.storeSingleYieldBalance( + userWalletId = userWalletId, + item = YieldBalance.Error(integrationId = requestBody.integrationId, address = address), + ) + }, ) }, ) @@ -399,7 +412,11 @@ internal class DefaultStakingRepository( val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty() val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)] ?: error("Could not get integrationId") - stakingBalanceStore.get(userWalletId, address, integrationId) + + stakingBalanceStore.get( + userWalletId = userWalletId, + stakingID = StakingBalanceStore.StakingID(integrationId = integrationId, address = address), + ) .distinctUntilChanged() .collectLatest { if (it != null) { @@ -413,10 +430,7 @@ internal class DefaultStakingRepository( } withContext(dispatchers.io) { - fetchSingleYieldBalance( - userWalletId, - cryptoCurrency, - ) + fetchSingleYieldBalance(userWalletId = userWalletId, cryptoCurrency = cryptoCurrency) } }.cancellable() @@ -431,7 +445,10 @@ internal class DefaultStakingRepository( val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)] ?: error("Could not get integrationId") - stakingBalanceStore.getSyncOrNull(userWalletId, address, integrationId) + stakingBalanceStore.getSyncOrNull( + userWalletId = userWalletId, + stakingID = StakingBalanceStore.StakingID(integrationId = integrationId, address = address), + ) ?: YieldBalance.Error(integrationId, address) } @@ -444,21 +461,7 @@ internal class DefaultStakingRepository( if (refresh) { stakingBalanceStore.refresh( userWalletId = userWalletId, - addressWithIntegrationIdMap = cryptoCurrencies - .mapNotNull { currency -> - val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network) - val integrationId = integrationIdMap[getIntegrationKey(currency.id)] - - if (integrationId != null) { - addresses to integrationId - } else { - null - } - } - .flatMap { (addresses, integrationId) -> - addresses.map { address -> integrationId to address.value } - } - .toMap(), + stakingIds = cryptoCurrencies.mapStakingId(userWalletId), ) } @@ -523,20 +526,52 @@ internal class DefaultStakingRepository( ) } + private suspend fun List.mapStakingId( + userWalletId: UserWalletId, + ): List { + return this + .mapNotNull { currency -> + val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network) + val integrationId = integrationIdMap[getIntegrationKey(currency.id)] + + if (integrationId != null) { + addresses to integrationId + } else { + null + } + } + .flatMap { (addresses, integrationId) -> + addresses.map { address -> + StakingBalanceStore.StakingID( + integrationId = integrationId, + address = address.value, + ) + } + } + } + override fun getMultiYieldBalanceUpdates( userWalletId: UserWalletId, cryptoCurrencies: List, ): Flow { - return stakingBalanceStore.get(userWalletId) - .map(YieldBalanceListConverter::convert) - .flowOn(dispatchers.io) + return flow { + stakingBalanceStore.get( + userWalletId = userWalletId, + stakingIds = cryptoCurrencies.mapStakingId(userWalletId), + ) + .map(YieldBalanceListConverter::convert) + .collect { emit(it) } + } } override fun getMultiYieldBalanceUpdatesLegacy( userWalletId: UserWalletId, cryptoCurrencies: List, ): Flow = channelFlow { - stakingBalanceStore.get(userWalletId) + stakingBalanceStore.get( + userWalletId = userWalletId, + stakingIds = cryptoCurrencies.mapStakingId(userWalletId), + ) .onEach { val balances = YieldBalanceListConverter.convert(it) send(balances) @@ -554,7 +589,8 @@ internal class DefaultStakingRepository( ): YieldBalanceList = withContext(dispatchers.io) { fetchMultiYieldBalance(userWalletId, cryptoCurrencies) - stakingBalanceStore.getSyncOrNull(userWalletId)?.let(YieldBalanceListConverter::convert) + stakingBalanceStore.getSyncOrNull(userWalletId, cryptoCurrencies.mapStakingId(userWalletId)) + ?.let(YieldBalanceListConverter::convert) ?: YieldBalanceList.Error } @@ -698,6 +734,7 @@ internal class DefaultStakingRepository( } } + @Suppress("unused") private companion object { const val YIELDS_STORE_KEY = "yields" diff --git a/domain/legacy/src/main/java/com/tangem/domain/exchange/ExchangeableState.kt b/domain/legacy/src/main/java/com/tangem/domain/exchange/ExchangeableState.kt new file mode 100644 index 0000000000..9a4f140cf1 --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/exchange/ExchangeableState.kt @@ -0,0 +1,9 @@ +package com.tangem.domain.exchange + +sealed interface ExchangeableState { + data object Exchangeable : ExchangeableState + data object NotExchangeable : ExchangeableState + data object Loading : ExchangeableState + data object Error : ExchangeableState + data object AssetNotFound : ExchangeableState +} \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt index 3e6ad8a4e8..180a8b16b5 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt @@ -31,7 +31,7 @@ interface RampStateManager { status: CryptoCurrencyStatus, ): Either - suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean + suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): ExchangeableState suspend fun fetchBuyServiceData() diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/ValidateTokenFormUseCase.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/ValidateTokenFormUseCase.kt index 79a0481bc4..afa0efcf2b 100644 --- a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/ValidateTokenFormUseCase.kt +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/ValidateTokenFormUseCase.kt @@ -77,7 +77,7 @@ class ValidateTokenFormUseCase( } companion object { - const val MIN_DECIMALS = 1 + const val MIN_DECIMALS = 0 const val MAX_DECIMALS = 30 } } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt index adc1a8e4ca..02f95ce1e1 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt @@ -5,6 +5,8 @@ sealed class ScenarioUnavailabilityReason { data object UsedOutdatedData : ScenarioUnavailabilityReason() + data object DataLoading : ScenarioUnavailabilityReason() + // staking-specific data class StakingUnavailable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() @@ -19,8 +21,32 @@ sealed class ScenarioUnavailabilityReason { data class BuyUnavailable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() // swap-specific + /** + * False for this token in asset request + */ data class NotExchangeable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() + data class CustomToken(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() + + data class TokenNoQuotes(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() + + data object SingleWallet : ScenarioUnavailabilityReason() + + /** + * Express request failed + */ + data class ExpressUnreachable(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() + + /** + * Assets still loading + */ + data class ExpressLoading(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() + + /** + * No token in asset list returned from express + */ + data class AssetNotFound(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() + // sell-specific data class NotSupportedBySellService(val cryptoCurrencyName: String) : ScenarioUnavailabilityReason() diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt index dcea766313..9e8a010f52 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt @@ -1,6 +1,10 @@ package com.tangem.domain.tokens.model.analytics import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam.Key.ACTION +import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN +import com.tangem.core.analytics.models.AnalyticsParam.Key.STATUS +import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason /** @@ -34,29 +38,89 @@ sealed class TokenScreenAnalyticsEvent( params = mapOf("Token" to token), ) - class ButtonBuy(token: String) : TokenScreenAnalyticsEvent( - event = "Button - Buy", - params = mapOf("Token" to token), - ) + sealed class ButtonWithParams( + event: String, + token: String, + blockchain: String, + status: String?, + ) : TokenScreenAnalyticsEvent( + event = event, + params = buildMap { + put(TOKEN_PARAM, token) + put(BLOCKCHAIN, blockchain) + status?.let { put(STATUS, it) } + }, + ) { - class ButtonSell(token: String) : TokenScreenAnalyticsEvent( - event = "Button - Sell", - params = mapOf("Token" to token), - ) + class ButtonBuy( + token: String, + blockchain: String, + status: String?, + ) : ButtonWithParams( + event = "Button - Buy", + token = token, + status = status, + blockchain = blockchain, + ) - class ButtonExchange(token: String) : TokenScreenAnalyticsEvent( - event = "Button - Exchange", - params = mapOf("Token" to token), - ) + class ButtonSell( + token: String, + status: String, + blockchain: String, + ) : ButtonWithParams( + event = "Button - Sell", + token = token, + status = status, + blockchain = blockchain, + ) - class ButtonSend(token: String) : TokenScreenAnalyticsEvent( - event = "Button - Send", - params = mapOf("Token" to token), - ) + class ButtonExchange( + token: String, + status: String, + blockchain: String, + ) : ButtonWithParams( + event = "Button - Exchange", + token = token, + status = status, + blockchain = blockchain, + ) - class ButtonReceive(token: String) : TokenScreenAnalyticsEvent( - event = "Button - Receive", - params = mapOf("Token" to token), + class ButtonSend( + token: String, + status: String, + blockchain: String, + ) : ButtonWithParams( + event = "Button - Send", + token = token, + status = status, + blockchain = blockchain, + ) + + class ButtonReceive( + token: String, + status: String, + blockchain: String, + ) : ButtonWithParams( + event = "Button - Receive", + token = token, + status = status, + blockchain = blockchain, + ) + } + + class ActionButtonDisabled( + token: String, + status: String, + blockchain: String, + action: String, + ) : TokenScreenAnalyticsEvent( + event = "Action Button Disabled", + params = mapOf( + TOKEN_PARAM to token, + STATUS to status, + BLOCKCHAIN to blockchain, + ACTION to action, + ), ) class ButtonCopyAddress(token: String) : TokenScreenAnalyticsEvent( @@ -89,45 +153,43 @@ sealed class TokenScreenAnalyticsEvent( params = mapOf("Token" to token), ) - class NoticeActionInactive(token: String, tokenAction: TokenAction, reason: String) : TokenScreenAnalyticsEvent( - event = "Notice - Action Inactive", - params = buildMap { - put("Token", token) - put("Action", tokenAction.action) - if (reason.isNotEmpty()) { - put("Reason", reason) - } - }, - ) { - sealed class TokenAction(val action: String) { - data object BuyAction : TokenAction("Buy") - data object SellAction : TokenAction("Sell") - data object SwapAction : TokenAction("Swap") - data object SendAction : TokenAction("Send") - data object ReceiveAction : TokenAction("Receive") - } - } - companion object { + const val AVAILABLE = "Available" private const val UNAVAILABLE = "Unavailable" private const val EMPTY = "Empty" private const val PENDING = "Pending" private const val CACHING = "Caching" + private const val CUSTOM_TOKEN = "Custom Token" + private const val BLOCKCHAIN_UNREACHABLE = "Blockchain Unreachable" + private const val ASSET_NOT_FOUND = "Asset NotFound" + private const val ASSET_LOADING = "Assets Loading" + private const val ASSETS_ERROR = "Assets Error" + private const val NO_QUOTE = "Quotes Unavailable" + private const val SINGLE_WALLET = "Single Wallet" + private const val LOADING = "Loading" + private const val ASSET_REQUIREMENT = "AssetRequirement" + @Suppress("CyclomaticComplexMethod") fun ScenarioUnavailabilityReason.toReasonAnalyticsText(): String { return when (this) { is ScenarioUnavailabilityReason.BuyUnavailable, is ScenarioUnavailabilityReason.NotExchangeable, is ScenarioUnavailabilityReason.NotSupportedBySellService, is ScenarioUnavailabilityReason.StakingUnavailable, - ScenarioUnavailabilityReason.UnassociatedAsset, -> UNAVAILABLE + ScenarioUnavailabilityReason.UnassociatedAsset -> ASSET_REQUIREMENT is ScenarioUnavailabilityReason.EmptyBalance -> EMPTY is ScenarioUnavailabilityReason.PendingTransaction -> PENDING ScenarioUnavailabilityReason.UsedOutdatedData -> CACHING - ScenarioUnavailabilityReason.None, - ScenarioUnavailabilityReason.Unreachable, - -> "" + ScenarioUnavailabilityReason.None -> AVAILABLE + ScenarioUnavailabilityReason.Unreachable -> BLOCKCHAIN_UNREACHABLE + is ScenarioUnavailabilityReason.AssetNotFound -> ASSET_NOT_FOUND + is ScenarioUnavailabilityReason.CustomToken -> CUSTOM_TOKEN + is ScenarioUnavailabilityReason.ExpressLoading -> ASSET_LOADING + is ScenarioUnavailabilityReason.ExpressUnreachable -> ASSETS_ERROR + ScenarioUnavailabilityReason.SingleWallet -> SINGLE_WALLET + is ScenarioUnavailabilityReason.TokenNoQuotes -> NO_QUOTE + ScenarioUnavailabilityReason.DataLoading -> LOADING } } } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index 680c0148e3..981a56de13 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -1,6 +1,7 @@ package com.tangem.domain.tokens import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.exchange.ExchangeableState import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.models.StatusSource import com.tangem.domain.promo.PromoRepository @@ -24,7 +25,7 @@ import kotlinx.coroutines.withTimeoutOrNull * * @property rampManager Ramp manager to check ramp availability */ -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") class GetCryptoCurrencyActionsUseCase( private val rampManager: RampStateManager, private val walletManagersFacade: WalletManagersFacade, @@ -123,7 +124,7 @@ class GetCryptoCurrencyActionsUseCase( } if (cryptoCurrencyStatus.value.sources.total != StatusSource.ACTUAL) { - return getActionsForOutdatedData(userWallet, cryptoCurrencyStatus, requirements) + return getActionsForOutdatedData(userWallet, cryptoCurrencyStatus, requirements, isStakingAvailable) } val activeList = mutableListOf() @@ -147,27 +148,7 @@ class GetCryptoCurrencyActionsUseCase( } // staking - if (isStakingAvailable) { - val yield = kotlin.runCatching { - stakingRepository.getYield( - cryptoCurrencyId = cryptoCurrency.id, - symbol = cryptoCurrency.symbol, - ) - }.getOrNull() - activeList.add( - TokenActionsState.ActionState.Stake( - unavailabilityReason = ScenarioUnavailabilityReason.None, - yield = yield, - ), - ) - } else { - disabledList.add( - TokenActionsState.ActionState.Stake( - unavailabilityReason = ScenarioUnavailabilityReason.StakingUnavailable(cryptoCurrency.name), - yield = null, - ), - ) - } + addStakingActions(cryptoCurrency, isStakingAvailable, activeList, disabledList) // send val sendUnavailabilityReason = getSendUnavailabilityReason( @@ -181,26 +162,11 @@ class GetCryptoCurrencyActionsUseCase( } // swap - if (userWallet.isMultiCurrency) { - val isExchangeable = withTimeoutOrNull(REQUEST_EXCHANGE_DATA_TIMEOUT) { - rampManager.availableForSwap(userWallet.walletId, cryptoCurrency) - } ?: false - if (isExchangeable && cryptoCurrencyStatus.value !is CryptoCurrencyStatus.NoQuote) { - val swapStoriesEnabled = swapFeatureToggles.isPromoStoriesEnabled - activeList.add( - TokenActionsState.ActionState.Swap( - unavailabilityReason = ScenarioUnavailabilityReason.None, - showBadge = shouldShowSwapStories && swapStoriesEnabled, - ), - ) - } else { - disabledList.add( - TokenActionsState.ActionState.Swap( - unavailabilityReason = ScenarioUnavailabilityReason.NotExchangeable(cryptoCurrency.name), - showBadge = false, - ), - ) - } + val swapActionState = getSwapUnavailabilityReason(userWallet, cryptoCurrencyStatus, shouldShowSwapStories) + if (swapActionState.unavailabilityReason == ScenarioUnavailabilityReason.None) { + activeList.add(swapActionState) + } else { + disabledList.add(swapActionState) } // buy @@ -231,6 +197,35 @@ class GetCryptoCurrencyActionsUseCase( return activeList + disabledList } + private suspend fun addStakingActions( + cryptoCurrency: CryptoCurrency, + isStakingAvailable: Boolean, + activeList: MutableList, + disabledList: MutableList, + ) { + if (isStakingAvailable) { + val yield = kotlin.runCatching { + stakingRepository.getYield( + cryptoCurrencyId = cryptoCurrency.id, + symbol = cryptoCurrency.symbol, + ) + }.getOrNull() + activeList.add( + TokenActionsState.ActionState.Stake( + unavailabilityReason = ScenarioUnavailabilityReason.None, + yield = yield, + ), + ) + } else { + disabledList.add( + TokenActionsState.ActionState.Stake( + unavailabilityReason = ScenarioUnavailabilityReason.StakingUnavailable(cryptoCurrency.name), + yield = null, + ), + ) + } + } + private suspend fun getActionsForUnreachableCurrency( userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus, @@ -270,12 +265,16 @@ class GetCryptoCurrencyActionsUseCase( return actionsList } + @Suppress("LongMethod") private suspend fun getActionsForOutdatedData( userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus, requirements: AssetRequirementsCondition?, + isStakingAvailable: Boolean, ): List { val activeList = mutableListOf() + val disabledList = mutableListOf() + val cryptoCurrency = cryptoCurrencyStatus.currency // copy address if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) { @@ -285,30 +284,42 @@ class GetCryptoCurrencyActionsUseCase( // receive if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) { val scenario = getReceiveScenario(requirements) - activeList.add(TokenActionsState.ActionState.Receive(scenario)) + val action = TokenActionsState.ActionState.Receive(scenario) + if (scenario == ScenarioUnavailabilityReason.None) { + activeList.add(action) + } else { + disabledList.add(action) + } } // swap - val isSwapAvailable = with(cryptoCurrencyStatus.value.sources) { + val sources = cryptoCurrencyStatus.value.sources + val isSwapAvailable = with(sources) { quoteSource.isActual() && networkSource.isActual() } - activeList.add( - TokenActionsState.ActionState.Swap( - unavailabilityReason = if (isSwapAvailable) { - ScenarioUnavailabilityReason.None - } else { - ScenarioUnavailabilityReason.UsedOutdatedData - }, - showBadge = false, - ), + val swapAction = TokenActionsState.ActionState.Swap( + unavailabilityReason = if (isSwapAvailable) { + ScenarioUnavailabilityReason.None + } else if (sources.networkSource == StatusSource.ONLY_CACHE) { + ScenarioUnavailabilityReason.UsedOutdatedData + } else { + // CACHE source always when loading + ScenarioUnavailabilityReason.DataLoading + }, + showBadge = false, ) + if (swapAction.unavailabilityReason == ScenarioUnavailabilityReason.None) { + activeList.add(swapAction) + } else { + disabledList.add(swapAction) + } // buy - if (rampManager.availableForBuy(userWallet.scanResponse, userWallet.walletId, cryptoCurrencyStatus.currency)) { + if (rampManager.availableForBuy(userWallet.scanResponse, userWallet.walletId, cryptoCurrency)) { activeList.add(TokenActionsState.ActionState.Buy(ScenarioUnavailabilityReason.None)) } else { - activeList.add( + disabledList.add( TokenActionsState.ActionState.Buy( ScenarioUnavailabilityReason.BuyUnavailable( cryptoCurrencyName = cryptoCurrencyStatus.currency.name, @@ -318,29 +329,40 @@ class GetCryptoCurrencyActionsUseCase( } // staking - activeList.add(TokenActionsState.ActionState.Stake(ScenarioUnavailabilityReason.UsedOutdatedData, null)) + if (cryptoCurrencyStatus.value.sources.networkSource.isActual()) { + addStakingActions(cryptoCurrency, isStakingAvailable, activeList, disabledList) + } else { + disabledList.add(TokenActionsState.ActionState.Stake(ScenarioUnavailabilityReason.UsedOutdatedData, null)) + } // send val isSendAvailable = cryptoCurrencyStatus.value.sources.networkSource.isActual() - activeList.add( - TokenActionsState.ActionState.Send( - unavailabilityReason = if (isSendAvailable) { - ScenarioUnavailabilityReason.None - } else { - ScenarioUnavailabilityReason.UsedOutdatedData - }, - ), + val sendAction = TokenActionsState.ActionState.Send( + unavailabilityReason = if (isSendAvailable) { + ScenarioUnavailabilityReason.None + } else { + ScenarioUnavailabilityReason.UsedOutdatedData + }, ) + if (sendAction.unavailabilityReason == ScenarioUnavailabilityReason.None) { + activeList.add(sendAction) + } else { + disabledList.add(sendAction) + } // region sell - activeList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.UsedOutdatedData)) + if (isSendAvailable) { + activeList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.None)) + } else { + disabledList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.UsedOutdatedData)) + } // endregion // hide activeList.add(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None)) - return activeList + return activeList + disabledList } private fun getReceiveScenario(requirements: AssetRequirementsCondition?): ScenarioUnavailabilityReason { @@ -376,6 +398,54 @@ class GetCryptoCurrencyActionsUseCase( } } + private suspend fun getSwapUnavailabilityReason( + userWallet: UserWallet, + cryptoCurrencyStatus: CryptoCurrencyStatus, + shouldShowSwapStories: Boolean, + ): TokenActionsState.ActionState { + val cryptoCurrency = cryptoCurrencyStatus.currency + return if (userWallet.isMultiCurrency) { + if (cryptoCurrency.isCustom) { + return TokenActionsState.ActionState.Swap( + unavailabilityReason = ScenarioUnavailabilityReason.CustomToken(cryptoCurrency.name), + showBadge = false, + ) + } + if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.NoQuote) { + return TokenActionsState.ActionState.Swap( + unavailabilityReason = ScenarioUnavailabilityReason.TokenNoQuotes(cryptoCurrency.name), + showBadge = false, + ) + } + val exchangeableState = withTimeoutOrNull(REQUEST_EXCHANGE_DATA_TIMEOUT) { + rampManager.availableForSwap(userWallet.walletId, cryptoCurrency) + } ?: ExchangeableState.Loading + val swapStoriesEnabled = swapFeatureToggles.isPromoStoriesEnabled + val currencyName = cryptoCurrency.name + + val reason = when (exchangeableState) { + ExchangeableState.Exchangeable -> { + ScenarioUnavailabilityReason.None + } + ExchangeableState.AssetNotFound -> ScenarioUnavailabilityReason.AssetNotFound(currencyName) + ExchangeableState.Error -> ScenarioUnavailabilityReason.ExpressUnreachable(currencyName) + ExchangeableState.Loading -> ScenarioUnavailabilityReason.ExpressLoading(currencyName) + ExchangeableState.NotExchangeable -> ScenarioUnavailabilityReason.NotExchangeable(currencyName) + } + + val isShowBadge = reason == ScenarioUnavailabilityReason.None && shouldShowSwapStories && swapStoriesEnabled + TokenActionsState.ActionState.Swap( + unavailabilityReason = reason, + showBadge = isShowBadge, + ) + } else { + TokenActionsState.ActionState.Swap( + unavailabilityReason = ScenarioUnavailabilityReason.SingleWallet, + showBadge = false, + ) + } + } + private fun isAddressAvailable(networkAddress: NetworkAddress?): Boolean { return networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty() } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt index c06a0481bd..739ad63738 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt @@ -112,7 +112,13 @@ class CachedCurrenciesStatusesOperations( val rawCurrenciesIds = currenciesIds.mapNotNullTo(mutableSetOf()) { it.rawCurrencyId } quotesRepository.fetchQuotes(rawCurrenciesIds) }, - async { stakingRepository.fetchMultiYieldBalance(userWalletId, currencies) }, + async { + if (currencies.size == 1) { + stakingRepository.fetchSingleYieldBalance(userWalletId, currencies.first()) + } else { + stakingRepository.fetchMultiYieldBalance(userWalletId, currencies) + } + }, ) } .map { } @@ -251,6 +257,6 @@ class CachedCurrenciesStatusesOperations( } companion object { - private const val RETRY_DELAY = 2000L + internal const val RETRY_DELAY = 2000L } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt index e71d7a55f4..eeeb5b4e59 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt @@ -9,10 +9,12 @@ import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.tokens.model.Quote +import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations.Companion.RETRY_DELAY import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* class CurrenciesStatusesOperations( @@ -29,6 +31,7 @@ class CurrenciesStatusesOperations( } .retryWhen { cause, _ -> emit(Error.DataError(cause).left()) + delay(RETRY_DELAY) true } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index 62fa577e9d..76809b1485 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -10,6 +10,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.utils.getOrElse +import com.tangem.domain.exchange.ExchangeableState import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.models.UserCountry @@ -241,10 +242,21 @@ internal class OnrampTokenListModel @Inject constructor( val isAvailable = rampStateManager.availableForSwap( userWalletId = params.userWalletId, cryptoCurrency = status.currency, - ) + ).isSwapAvailable() && !status.currency.isCustom isAvailable && status.value !is CryptoCurrencyStatus.NoQuote } } } + + private fun ExchangeableState.isSwapAvailable(): Boolean { + return when (this) { + ExchangeableState.AssetNotFound, + ExchangeableState.Error, + ExchangeableState.Loading, + ExchangeableState.NotExchangeable, + -> false + ExchangeableState.Exchangeable -> true + } + } } \ 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 9ec36f1022..ebe6d7c95a 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 @@ -204,6 +204,7 @@ internal class StakingModel @Inject constructor( private val transactionsInProgress: CopyOnWriteArrayList = CopyOnWriteArrayList() + private var actionsJobHolder: JobHolder = JobHolder() private var approvalJobHolder: JobHolder = JobHolder() private var feeJobHolder: JobHolder = JobHolder() private var sendTransactionJobHolder = JobHolder() @@ -290,6 +291,7 @@ internal class StakingModel @Inject constructor( appCurrencyProvider = Provider { appCurrency }, feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, fee = gasEstimate, + cryptoCurrencyStatus = cryptoCurrencyStatus, ), ) updateNotifications() @@ -309,6 +311,7 @@ internal class StakingModel @Inject constructor( appCurrencyProvider = Provider { appCurrency }, feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, fee = fee, + cryptoCurrencyStatus = cryptoCurrencyStatus, ), ) updateNotifications() @@ -328,25 +331,26 @@ internal class StakingModel @Inject constructor( }, onConstructError = { error -> stakingEventFactory.createStakingErrorAlert(error) - stateController.update(SetConfirmationStateResetAssentTransformer) + stateController.update(SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus)) }, onSendSuccess = { txUrl -> stakingAnalyticSender.sendTransactionStakingAnalytics(stateController.value) transactionsInProgress.clear() - stateController.update(SetConfirmationStateCompletedTransformer(txUrl)) + stateController.update(SetConfirmationStateCompletedTransformer(txUrl, cryptoCurrencyStatus)) }, onSendError = { error -> analyticsEventHandler.send(StakingAnalyticsEvent.TransactionError) stakingEventFactory.createSendTransactionErrorAlert(error) - stateController.update(SetConfirmationStateResetAssentTransformer) + stateController.update(SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus)) }, onFeeIncreased = { increasedFee -> stateController.updateAll( - SetConfirmationStateResetAssentTransformer, + SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus), SetConfirmationStateAssentTransformer( appCurrencyProvider = Provider { appCurrency }, feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, fee = increasedFee, + cryptoCurrencyStatus = cryptoCurrencyStatus, ), ) stateController.updateEvent( @@ -371,7 +375,9 @@ internal class StakingModel @Inject constructor( } if (!isApprovalInProgress) { stakingStateRouter.onPrevClick() - stateController.update(SetConfirmationStateResetAssentTransformer) + stateController.update( + SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus = cryptoCurrencyStatus), + ) } } null, @@ -561,6 +567,7 @@ internal class StakingModel @Inject constructor( stateController.update(SetApprovalBottomSheetTypeChangeTransformer(approveType)) } + @Suppress("LongMethod") override fun onApprovalClick() { modelScope.launch { stateController.update( @@ -597,10 +604,13 @@ internal class StakingModel @Inject constructor( appCurrencyProvider = Provider { appCurrency }, feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, fee = TransactionFee.Single(fee), + cryptoCurrencyStatus = cryptoCurrencyStatus, ), ) stakingEventFactory.createGenericErrorAlert(error.message ?: error.toString()) - stateController.update(SetConfirmationStateResetAssentTransformer) + stateController.update( + SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus = cryptoCurrencyStatus), + ) return@launch }, ifRight = { it }, @@ -619,10 +629,13 @@ internal class StakingModel @Inject constructor( appCurrencyProvider = Provider { appCurrency }, feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, fee = TransactionFee.Single(fee), + cryptoCurrencyStatus = cryptoCurrencyStatus, ), ) stakingEventFactory.createSendTransactionErrorAlert(error) - stateController.update(SetConfirmationStateResetAssentTransformer) + stateController.update( + SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus = cryptoCurrencyStatus), + ) }, ifRight = { stakingAnalyticSender.sendTransactionApprovalAnalytics(tokenCryptoCurrency) @@ -856,10 +869,16 @@ internal class StakingModel @Inject constructor( }, ifLeft = { stakingEventFactory.createGenericErrorAlert(it.toString()) - stateController.update(SetConfirmationStateResetAssentTransformer) + stateController.update( + SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus = cryptoCurrencyStatus), + ) }, ) - getCurrencyStatusUpdatesUseCase(userWalletId, cryptoCurrencyId, false) + getCurrencyStatusUpdatesUseCase( + userWalletId = userWalletId, + currencyId = cryptoCurrencyId, + isSingleWalletWithTokens = false, + ) .conflate() .distinctUntilChangedBy { it.getOrNull()?.value?.yieldBalance } .filter { value.currentStep == StakingStep.InitialInfo } @@ -893,12 +912,14 @@ internal class StakingModel @Inject constructor( setupApprovalNeeded() setupIsAnyTokenStaked() checkIfSubtractAvailable() - subscribeOnActionsUpdates() - subscribeOnStepChanges() + subscribeOnActionsUpdates(status) + subscribeOnStepChanges(status) }, ifLeft = { error -> stakingEventFactory.createGenericErrorAlert(error.toString()) - stateController.update(SetConfirmationStateResetAssentTransformer) + stateController.update( + SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus = cryptoCurrencyStatus), + ) }, ) } @@ -928,13 +949,13 @@ internal class StakingModel @Inject constructor( .launchIn(modelScope) } - private fun subscribeOnStepChanges() { + private fun subscribeOnStepChanges(status: CryptoCurrencyStatus) { uiState .distinctUntilChangedBy { it.currentStep } .onEach { when { isInitState() -> { - updateInitialData() + updateInitialData(status) balanceUpdater.partialUpdate() } isAssentState() -> { @@ -955,32 +976,30 @@ internal class StakingModel @Inject constructor( .saveIn(stepChangesJobHolder) } - private fun subscribeOnActionsUpdates() { - getActionsUseCase( - userWalletId = userWalletId, - cryptoCurrencyId = cryptoCurrencyId, - ) + private fun subscribeOnActionsUpdates(status: CryptoCurrencyStatus) { + getActionsUseCase(userWalletId = userWalletId, cryptoCurrencyId = cryptoCurrencyId) .conflate() .distinctUntilChanged() .onEach { result -> result.getOrNull()?.let { actions -> processingActions = actions if (isInitState()) { - updateInitialData() + updateInitialData(status) } } } .flowOn(dispatchers.main) .launchIn(modelScope) + .saveIn(actionsJobHolder) } - private fun updateInitialData() { + private fun updateInitialData(status: CryptoCurrencyStatus) { stateController.updateAll( SetInitialDataStateTransformer( clickIntents = this@StakingModel, yield = yield, isAnyTokenStaked = isAnyTokenStaked, - cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, + cryptoCurrencyStatus = status, userWalletProvider = Provider { userWallet }, appCurrencyProvider = Provider { appCurrency }, balancesToShowProvider = Provider { balancesToShow }, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt index 8938130705..dc8db5482a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt @@ -21,13 +21,12 @@ import org.joda.time.DateTime import java.util.Calendar internal class BalanceItemConverter( - private val cryptoCurrencyStatusProvider: Provider, + private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val appCurrencyProvider: Provider, private val yield: Yield, ) : Converter { override fun convert(value: BalanceItem): BalanceState? { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val appCurrency = appCurrencyProvider() val cryptoCurrency = cryptoCurrencyStatus.currency diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt index 6661ff2715..4fefa8ca21 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt @@ -19,13 +19,11 @@ import kotlinx.collections.immutable.toPersistentList import java.math.BigDecimal internal class RewardsValidatorStateConverter( - private val cryptoCurrencyStatusProvider: Provider, + private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val appCurrencyProvider: Provider, private val yield: Yield, ) : Converter { override fun convert(value: Unit): StakingStates.RewardsValidatorsState { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - val yieldBalance = cryptoCurrencyStatus.value.yieldBalance return if (yieldBalance is YieldBalance.Data) { val balances = yieldBalance.balance.items diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt index 41c9881df8..ed5cb2cdad 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt @@ -15,18 +15,17 @@ import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toPersistentList internal class YieldBalancesConverter( - private val cryptoCurrencyStatusProvider: Provider, + private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val appCurrencyProvider: Provider, private val balancesToShowProvider: Provider>, private val yield: Yield, ) : Converter { private val balanceItemConverter by lazy(LazyThreadSafetyMode.NONE) { - BalanceItemConverter(cryptoCurrencyStatusProvider, appCurrencyProvider, yield) + BalanceItemConverter(cryptoCurrencyStatus, appCurrencyProvider, yield) } override fun convert(value: Unit): InnerYieldBalanceState { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val appCurrency = appCurrencyProvider() val cryptoCurrency = cryptoCurrencyStatus.currency @@ -68,7 +67,6 @@ internal class YieldBalancesConverter( .toPersistentList() private fun getRewardBlockType(): Pair { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val blockchainId = cryptoCurrencyStatus.currency.network.id.value val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data val rewards = yieldBalance?.balance?.items diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt index 6c26ecf208..d86e6cc5f3 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt @@ -1,7 +1,7 @@ package com.tangem.features.staking.impl.presentation.state.helpers -import com.tangem.domain.staking.FetchStakingYieldBalanceUseCase import com.tangem.domain.staking.FetchActionsUseCase +import com.tangem.domain.staking.FetchStakingYieldBalanceUseCase import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus import com.tangem.domain.tokens.FetchPendingTransactionsUseCase diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt index 85443b4096..56e01d3a83 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateAssentTransformer.kt @@ -13,6 +13,7 @@ internal class SetConfirmationStateAssentTransformer( private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?, private val fee: Fee, + private val cryptoCurrencyStatus: CryptoCurrencyStatus, ) : Transformer { override fun transform(prevState: StakingUiState): StakingUiState { @@ -32,7 +33,9 @@ internal class SetConfirmationStateAssentTransformer( appCurrency = appCurrencyProvider(), isFeeApproximate = false, ), - isPrimaryButtonEnabled = true, + isPrimaryButtonEnabled = with(cryptoCurrencyStatus.value) { + sources.yieldBalanceSource.isActual() && sources.networkSource.isActual() + }, ) } else { return this diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt index 4ecbf95344..7190ad5ae1 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateCompletedTransformer.kt @@ -1,12 +1,17 @@ package com.tangem.features.staking.impl.presentation.state.transformers import com.tangem.core.ui.extensions.TextReference -import com.tangem.features.staking.impl.presentation.state.* +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState +import com.tangem.features.staking.impl.presentation.state.StakingStates +import com.tangem.features.staking.impl.presentation.state.StakingUiState +import com.tangem.features.staking.impl.presentation.state.TransactionDoneState import com.tangem.utils.transformer.Transformer import kotlinx.collections.immutable.persistentListOf internal class SetConfirmationStateCompletedTransformer( private val txUrl: String, + private val cryptoCurrencyStatus: CryptoCurrencyStatus, ) : Transformer { override fun transform(prevState: StakingUiState): StakingUiState { @@ -18,7 +23,9 @@ internal class SetConfirmationStateCompletedTransformer( private fun StakingStates.ConfirmationState.copyWrapped(): StakingStates.ConfirmationState { return if (this is StakingStates.ConfirmationState.Data) { copy( - isPrimaryButtonEnabled = true, + isPrimaryButtonEnabled = with(cryptoCurrencyStatus.value) { + sources.yieldBalanceSource.isActual() && sources.networkSource.isActual() + }, innerState = InnerConfirmationStakingState.COMPLETED, footerText = TextReference.EMPTY, notifications = persistentListOf(), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt index dcc57f1a99..de56770b05 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt @@ -70,7 +70,9 @@ internal class SetConfirmationStateInitTransformer( actionType = actionType, balanceState = balanceState, confirmationState = StakingStates.ConfirmationState.Data( - isPrimaryButtonEnabled = false, + isPrimaryButtonEnabled = with(cryptoCurrencyStatus.value) { + sources.yieldBalanceSource.isActual() && sources.networkSource.isActual() + }, innerState = InnerConfirmationStakingState.ASSENT, feeState = FeeState.Loading, notifications = persistentListOf(), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateResetAssentTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateResetAssentTransformer.kt index 1c1c7103dc..327ec4ae8e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateResetAssentTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateResetAssentTransformer.kt @@ -1,17 +1,22 @@ package com.tangem.features.staking.impl.presentation.state.transformers +import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.state.StakingUiState import com.tangem.utils.transformer.Transformer -internal object SetConfirmationStateResetAssentTransformer : Transformer { +internal class SetConfirmationStateResetAssentTransformer( + private val cryptoCurrencyStatus: CryptoCurrencyStatus, +) : Transformer { override fun transform(prevState: StakingUiState): StakingUiState { val confirmationState = prevState.confirmationState return prevState.copy( confirmationState = if (confirmationState is StakingStates.ConfirmationState.Data) { confirmationState.copy( - isPrimaryButtonEnabled = true, + isPrimaryButtonEnabled = with(cryptoCurrencyStatus.value) { + sources.yieldBalanceSource.isActual() && sources.networkSource.isActual() + }, innerState = InnerConfirmationStakingState.ASSENT, ) } else { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index e168a4db08..45ee5db07e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -40,7 +40,7 @@ internal class SetInitialDataStateTransformer( private val clickIntents: StakingClickIntents, private val yield: Yield, private val isAnyTokenStaked: Boolean, - private val cryptoCurrencyStatusProvider: Provider, + private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val userWalletProvider: Provider, private val appCurrencyProvider: Provider, private val balancesToShowProvider: Provider>, @@ -49,20 +49,20 @@ internal class SetInitialDataStateTransformer( private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) private val rewardsValidatorStateConverter by lazy(LazyThreadSafetyMode.NONE) { - RewardsValidatorStateConverter(cryptoCurrencyStatusProvider, appCurrencyProvider, yield) + RewardsValidatorStateConverter(cryptoCurrencyStatus, appCurrencyProvider, yield) } private val yieldBalancesConverter by lazy(LazyThreadSafetyMode.NONE) { YieldBalancesConverter( - cryptoCurrencyStatusProvider, - appCurrencyProvider, - balancesToShowProvider, - yield, + cryptoCurrencyStatus = cryptoCurrencyStatus, + appCurrencyProvider = appCurrencyProvider, + balancesToShowProvider = balancesToShowProvider, + yield = yield, ) } override fun transform(prevState: StakingUiState): StakingUiState { - val cryptoCurrency = cryptoCurrencyStatusProvider().currency + val cryptoCurrency = cryptoCurrencyStatus.currency return prevState.copy( title = TextReference.EMPTY, cryptoCurrencyName = cryptoCurrency.name, @@ -80,8 +80,12 @@ internal class SetInitialDataStateTransformer( private fun createInitialInfoState(): StakingStates.InitialInfoState.Data { val yieldBalance = yieldBalancesConverter.convert(Unit) + + val status = cryptoCurrencyStatus.value return StakingStates.InitialInfoState.Data( - isPrimaryButtonEnabled = !cryptoCurrencyStatusProvider().value.amount.isNullOrZero(), + isPrimaryButtonEnabled = with(status) { + !amount.isNullOrZero() && sources.yieldBalanceSource.isActual() && sources.networkSource.isActual() + }, showBanner = !isAnyTokenStaked && yieldBalance == InnerYieldBalanceState.Empty, aprRange = getAprRange(yield.preferredValidators), infoItems = getInfoItems(), @@ -95,8 +99,6 @@ internal class SetInitialDataStateTransformer( } private fun getInfoItems(): PersistentList { - val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() - return listOfNotNull( createAnnualPercentageRateItem(), createAvailableItem(cryptoCurrencyStatus), @@ -191,7 +193,7 @@ internal class SetInitialDataStateTransformer( private fun createRewardScheduleItem(): RoundedListWithDividersItemData? { val endTextReference = getRewardScheduleText( rewardSchedule = yield.metadata.rewardSchedule, - networkId = cryptoCurrencyStatusProvider().currency.network.id.value, + networkId = cryptoCurrencyStatus.currency.network.id.value, decapitalize = false, ) ?: return null @@ -204,7 +206,7 @@ internal class SetInitialDataStateTransformer( } private fun createInitialAmountState(): AmountState { - val cryptoBalanceValue = cryptoCurrencyStatusProvider().value + val cryptoBalanceValue = cryptoCurrencyStatus.value val maxEnterAmount = EnterAmountBoundary( amount = cryptoBalanceValue.amount, fiatAmount = cryptoBalanceValue.fiatAmount, @@ -212,7 +214,7 @@ internal class SetInitialDataStateTransformer( ) return AmountStateConverter( clickIntents = clickIntents, - cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider, + cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, appCurrencyProvider = appCurrencyProvider, iconStateConverter = iconStateConverter, maxEnterAmount = maxEnterAmount, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetConfirmationStateAssentApprovalTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetConfirmationStateAssentApprovalTransformer.kt index 6f331795e4..0729792529 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetConfirmationStateAssentApprovalTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/SetConfirmationStateAssentApprovalTransformer.kt @@ -14,6 +14,7 @@ internal class SetConfirmationStateAssentApprovalTransformer( private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?, private val fee: TransactionFee, + private val cryptoCurrencyStatus: CryptoCurrencyStatus, ) : Transformer { override fun transform(prevState: StakingUiState): StakingUiState { @@ -35,7 +36,9 @@ internal class SetConfirmationStateAssentApprovalTransformer( appCurrency = appCurrencyProvider(), isFeeApproximate = false, ), - isPrimaryButtonEnabled = true, + isPrimaryButtonEnabled = with(cryptoCurrencyStatus.value) { + sources.yieldBalanceSource.isActual() && sources.networkSource.isActual() + }, isApprovalNeeded = true, ) } else { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt index c388dff746..9f84b072a1 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt @@ -111,6 +111,10 @@ internal class AddStakingNotificationsTransformer( ) }.toImmutableList() + val isActualSources = with(cryptoCurrencyStatus.value) { + sources.yieldBalanceSource.isActual() && sources.networkSource.isActual() + } + return prevState.copy( confirmationState = confirmationState.copy( notifications = notifications.toImmutableList(), @@ -119,7 +123,7 @@ internal class AddStakingNotificationsTransformer( it is NotificationUM.Error || it is NotificationUM.Warning.NetworkFeeUnreachable || it is StakingNotification.Warning.TransactionInProgress - }, + } && isActualSources, ), ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index 0aea80ff85..6e167e2833 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -44,10 +44,7 @@ import com.tangem.domain.staking.GetYieldUseCase import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.tokens.model.NetworkAddress -import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason +import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.toReasonAnalyticsText @@ -262,6 +259,7 @@ internal class TokenDetailsModel @Inject constructor( .conflate() .distinctUntilChanged() .onEach { + sendButtonsEvents(it.states) internalUiState.value = stateFactory.getManageButtonsState(actions = it.states) } .flowOn(dispatchers.main) @@ -269,6 +267,21 @@ internal class TokenDetailsModel @Inject constructor( .saveIn(buttonsJobHolder) } + private fun sendButtonsEvents(states: List) { + // for now send only for swap + val swapState = states.firstOrNull { it is TokenActionsState.ActionState.Swap } ?: return + if (swapState.unavailabilityReason != ScenarioUnavailabilityReason.None) { + analyticsEventsHandler.send( + TokenScreenAnalyticsEvent.ActionButtonDisabled( + token = cryptoCurrency.symbol, + status = swapState.unavailabilityReason.toReasonAnalyticsText(), + blockchain = cryptoCurrency.network.name, + action = "Swap", + ), + ) + } + } + private fun updateWarnings(cryptoCurrencyStatus: CryptoCurrencyStatus) { modelScope.launch(dispatchers.main) { getCurrencyWarningsUseCase.invoke( @@ -457,14 +470,15 @@ internal class TokenDetailsModel @Inject constructor( } override fun onBuyClick(unavailabilityReason: ScenarioUnavailabilityReason) { - analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonBuy(cryptoCurrency.symbol)) + analyticsEventsHandler.send( + TokenScreenAnalyticsEvent.ButtonWithParams.ButtonBuy( + token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + status = unavailabilityReason.toReasonAnalyticsText(), + ), + ) - if (handleUnavailabilityReason( - unavailabilityReason = unavailabilityReason, - tokenSymbol = cryptoCurrency.symbol, - tokenAction = TokenScreenAnalyticsEvent.NoticeActionInactive.TokenAction.BuyAction, - ) - ) { + if (handleUnavailabilityReason(unavailabilityReason = unavailabilityReason)) { return } @@ -497,7 +511,13 @@ internal class TokenDetailsModel @Inject constructor( } override fun onBuyCoinClick(cryptoCurrency: CryptoCurrency) { - analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonBuy(cryptoCurrency.symbol)) + analyticsEventsHandler.send( + TokenScreenAnalyticsEvent.ButtonWithParams.ButtonBuy( + token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + status = null, + ), + ) router.openTokenDetails(userWalletId = userWalletId, currency = cryptoCurrency) } @@ -513,14 +533,15 @@ internal class TokenDetailsModel @Inject constructor( } override fun onSendClick(unavailabilityReason: ScenarioUnavailabilityReason) { - analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonSend(cryptoCurrency.symbol)) + analyticsEventsHandler.send( + TokenScreenAnalyticsEvent.ButtonWithParams.ButtonSend( + token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + status = unavailabilityReason.toReasonAnalyticsText(), + ), + ) - if (handleUnavailabilityReason( - unavailabilityReason = unavailabilityReason, - tokenSymbol = cryptoCurrency.symbol, - tokenAction = TokenScreenAnalyticsEvent.NoticeActionInactive.TokenAction.SendAction, - ) - ) { + if (handleUnavailabilityReason(unavailabilityReason = unavailabilityReason)) { return } @@ -539,17 +560,19 @@ internal class TokenDetailsModel @Inject constructor( override fun onReceiveClick(unavailabilityReason: ScenarioUnavailabilityReason) { val networkAddress = cryptoCurrencyStatus?.value?.networkAddress ?: return - if (handleUnavailabilityReason( - unavailabilityReason = unavailabilityReason, - tokenSymbol = cryptoCurrency.symbol, - tokenAction = TokenScreenAnalyticsEvent.NoticeActionInactive.TokenAction.ReceiveAction, - ) - ) { + analyticsEventsHandler.send( + TokenScreenAnalyticsEvent.ButtonWithParams.ButtonReceive( + token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + status = unavailabilityReason.toReasonAnalyticsText(), + ), + ) + + if (handleUnavailabilityReason(unavailabilityReason = unavailabilityReason)) { return } - modelScope.launch(dispatchers.main) { - analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonReceive(cryptoCurrency.symbol)) + modelScope.launch { analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ReceiveScreenOpened(cryptoCurrency.symbol)) internalUiState.value = stateFactory.getStateWithReceiveBottomSheet( @@ -568,12 +591,7 @@ internal class TokenDetailsModel @Inject constructor( } override fun onStakeClick(unavailabilityReason: ScenarioUnavailabilityReason) { - if (handleUnavailabilityReason( - unavailabilityReason = unavailabilityReason, - tokenSymbol = cryptoCurrency.symbol, - tokenAction = null, - ) - ) { + if (handleUnavailabilityReason(unavailabilityReason = unavailabilityReason)) { return } @@ -604,14 +622,15 @@ internal class TokenDetailsModel @Inject constructor( } override fun onSellClick(unavailabilityReason: ScenarioUnavailabilityReason) { - analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonSell(cryptoCurrency.symbol)) + analyticsEventsHandler.send( + TokenScreenAnalyticsEvent.ButtonWithParams.ButtonSell( + token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + status = unavailabilityReason.toReasonAnalyticsText(), + ), + ) - if (handleUnavailabilityReason( - unavailabilityReason = unavailabilityReason, - tokenSymbol = cryptoCurrency.symbol, - tokenAction = TokenScreenAnalyticsEvent.NoticeActionInactive.TokenAction.SellAction, - ) - ) { + if (handleUnavailabilityReason(unavailabilityReason = unavailabilityReason)) { return } @@ -628,14 +647,15 @@ internal class TokenDetailsModel @Inject constructor( } override fun onSwapClick(unavailabilityReason: ScenarioUnavailabilityReason) { - analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonExchange(cryptoCurrency.symbol)) + analyticsEventsHandler.send( + TokenScreenAnalyticsEvent.ButtonWithParams.ButtonExchange( + token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + status = unavailabilityReason.toReasonAnalyticsText(), + ), + ) - if (handleUnavailabilityReason( - unavailabilityReason = unavailabilityReason, - tokenSymbol = cryptoCurrency.symbol, - tokenAction = TokenScreenAnalyticsEvent.NoticeActionInactive.TokenAction.SwapAction, - ) - ) { + if (handleUnavailabilityReason(unavailabilityReason = unavailabilityReason)) { return } @@ -957,21 +977,8 @@ internal class TokenDetailsModel @Inject constructor( internalUiState.value = stateFactory.getStateWithClosedBottomSheet() } - private fun handleUnavailabilityReason( - unavailabilityReason: ScenarioUnavailabilityReason, - tokenSymbol: String, - tokenAction: TokenScreenAnalyticsEvent.NoticeActionInactive.TokenAction?, - ): Boolean { + private fun handleUnavailabilityReason(unavailabilityReason: ScenarioUnavailabilityReason): Boolean { if (unavailabilityReason == ScenarioUnavailabilityReason.None) return false - if (tokenAction != null) { - analyticsEventsHandler.send( - TokenScreenAnalyticsEvent.NoticeActionInactive( - token = tokenSymbol, - tokenAction = tokenAction, - reason = unavailabilityReason.toReasonAnalyticsText(), - ), - ) - } internalUiState.value = stateFactory.getStateWithActionButtonErrorDialog(unavailabilityReason) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsBalanceSelectStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsBalanceSelectStateConverter.kt index 5ab5994819..3425cedb3d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsBalanceSelectStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsBalanceSelectStateConverter.kt @@ -21,9 +21,14 @@ internal class TokenDetailsBalanceSelectStateConverter( override fun convert(value: TokenBalanceSegmentedButtonConfig): TokenDetailsState { return with(currentStateProvider()) { - if (stakingBlocksState !is StakingBlockUM.Staked) return this val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() ?: return this + if (stakingBlocksState !is StakingBlockUM.Staked && + stakingBlocksState !is StakingBlockUM.TemporaryUnavailable + ) { + return this + } + val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data val stakingCryptoAmount = yieldBalance?.getTotalWithRewardsStakingBalance() val stakingFiatAmount = stakingCryptoAmount?.let { cryptoCurrencyStatus.value.fiatRate?.multiply(it) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt index 157976d4f6..d96b0968ad 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt @@ -40,6 +40,8 @@ import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent +import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.AVAILABLE +import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.toReasonAnalyticsText import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase @@ -133,7 +135,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return analyticsEventHandler.send( - event = TokenScreenAnalyticsEvent.ButtonSend(cryptoCurrencyStatus.currency.symbol), + event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonSend( + token = cryptoCurrencyStatus.currency.symbol, + status = unavailabilityReason.toReasonAnalyticsText(), + blockchain = cryptoCurrencyStatus.currency.network.name, + ), ) if (handleUnavailabilityReason(unavailabilityReason)) return @@ -151,7 +157,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( val userWalletId = stateHolder.getSelectedWalletId() analyticsEventHandler.send( - event = TokenScreenAnalyticsEvent.ButtonReceive(cryptoCurrencyStatus.currency.symbol), + event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonReceive( + token = cryptoCurrencyStatus.currency.symbol, + status = AVAILABLE, + blockchain = cryptoCurrencyStatus.currency.network.name, + ), ) analyticsEventHandler.send( @@ -290,7 +300,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( unavailabilityReason: ScenarioUnavailabilityReason, ) { analyticsEventHandler.send( - event = TokenScreenAnalyticsEvent.ButtonSell(cryptoCurrencyStatus.currency.symbol), + event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonSell( + token = cryptoCurrencyStatus.currency.symbol, + status = unavailabilityReason.toReasonAnalyticsText(), + blockchain = cryptoCurrencyStatus.currency.network.name, + ), ) if (handleUnavailabilityReason(unavailabilityReason)) return @@ -314,7 +328,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return analyticsEventHandler.send( - event = TokenScreenAnalyticsEvent.ButtonBuy(cryptoCurrencyStatus.currency.symbol), + event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonBuy( + token = cryptoCurrencyStatus.currency.symbol, + status = unavailabilityReason.toReasonAnalyticsText(), + blockchain = cryptoCurrencyStatus.currency.network.name, + ), ) if (handleUnavailabilityReason(unavailabilityReason)) return @@ -349,7 +367,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( unavailabilityReason: ScenarioUnavailabilityReason, ) { analyticsEventHandler.send( - event = TokenScreenAnalyticsEvent.ButtonExchange(cryptoCurrencyStatus.currency.symbol), + event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonExchange( + token = cryptoCurrencyStatus.currency.symbol, + status = unavailabilityReason.toReasonAnalyticsText(), + blockchain = cryptoCurrencyStatus.currency.network.name, + ), ) if (handleUnavailabilityReason(unavailabilityReason)) return diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 76cabfe4a7..7b378fad02 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -1,7 +1,7 @@ [versions] tangemBlockchainSdk = "develop-977" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-437" +tangemCardSdk = "develop-439" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem-developments8" #tangemVico = "0.0.1" # Keep it! - used for local builds ^