diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressHideButton.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressHideButton.kt new file mode 100644 index 0000000000..16a1484d52 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressHideButton.kt @@ -0,0 +1,45 @@ +package com.tangem.common.ui.expressStatus + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.material3.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.R +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme + +@Composable +fun ColumnScope.ExpressHideButton(isTerminal: Boolean, isAutoDisposable: Boolean, onClick: () -> Unit) { + AnimatedVisibility( + visible = isTerminal && !isAutoDisposable, + label = "Hide button visibility animation", + ) { + Text( + text = stringResourceSafe(R.string.express_status_hide_button_text), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.button, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(start = 12.dp, end = 12.dp, top = 14.dp) + .clip(RoundedCornerShape(12.dp)) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(bounded = false), + onClick = onClick, + ) + .padding(vertical = 10.dp), + ) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusBottomSheetContent.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusBottomSheetContent.kt index 35905de8f7..fc888d6c64 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusBottomSheetContent.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusBottomSheetContent.kt @@ -59,6 +59,11 @@ fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM) { SpacerH12() ExpressStatusBlock(state = state.info.status) ExpressStatusNotificationBlock(state = state.info.notification) + ExpressHideButton( + isTerminal = state.activeStatus.isTerminal, + isAutoDisposable = state.activeStatus.isAutoDisposable, + onClick = state.info.onDisposeExpressStatus, + ) SpacerH24() } } \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionStateUM.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionStateUM.kt index 45138854f8..fb2489e331 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionStateUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionStateUM.kt @@ -30,6 +30,7 @@ data class ExpressTransactionStateInfoUM( val timestampFormatted: TextReference, val onGoToProviderClick: (String) -> Unit, val onClick: () -> Unit, + val onDisposeExpressStatus: () -> Unit, val iconState: ExpressTransactionStateIconUM, val toAmount: TextReference, val toFiatAmount: TextReference?, diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index ae312288dd..8a48bdcc9f 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -314,6 +314,9 @@ Gekauft %s Kauf %s Kauf %s… + Diese Transaktion ausblenden + Sobald dieser Bildschirm ausgeblendet wurde, kann er nicht mehr aufgerufen werden. + Bist Du sicher, dass Du die Transaktionsdetails ausblenden willst? Keine Token gefunden. Bitte versuche eine andere Anfrage ID: %s Transaktions-ID kopiert @@ -790,6 +793,8 @@ Migrieren Natives Staking Derzeit sind keine aktiven Validatoren für das Staking verfügbar. Bitte versuchen Sie es später erneut. + Beim Staking im Cardano Netzwerk wird das gesamte Guthaben genutzt. Zusätzlich werden 2 ADA reserviert und nach dem Unstaking zurückgegeben. Deine ADA sind während des Stakings nicht gesperrt. + Einzelheiten zum ADA-Staking Verdiente Belohnungen werden an Deine Wallet gesendet und stehen Dir sofort zur Verfügung Stake sicher und verdienen Belohnungen Sicher staken und tägliche Belohnungen verdienen. @@ -803,6 +808,8 @@ Verdiene Staking-Belohnungen Dein verbleibender Stakingbetrag ist zu niedrig, um den Einsatz aufzuheben. Du musst mehr Staken, um den Mindestbetrag zum Aufheben des Einsatzes zu erreichen. Niedriges Stakingguthaben + Für erneutes Staking sind mindestens %1$s %2$s erforderlich. Bitte lade Dein Guthaben auf. + Nicht genug %s Aufgrund von Netzwerkproblemen ist Staking derzeit nicht verfügbar. Bitte versuche es später erneut. Beim Staking im %1$s -Netzwerk mit einem neuen Validator werden alle zuvor eingesetzten Kryptos automatisch an diesen Validator übertragen Reinvestiert Deine verdienten Prämien in Deinen Einsatzbetrag und erhöht so den potenziellen Gewinn. diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 287efe4ff4..d7232f22fd 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -648,7 +648,7 @@ 工場出荷時設定にリセットすると、選択したカードやリングからウォレットが完全に削除されます。現在のウォレットを復元したり、カードやリングを使用してアクセスコードを復元することはできません。 工場出荷時の状態にリセットすると、選択したカードやリングからウォレットが完全に削除され、アプリから削除されます。現在のウォレットを復元することはできません。 Tangem Ringユーザーは、11/15日までChangelly経由でスワップを3回手数料ゼロで行えます! - 今すぐ手数料0%でスワップしましょう! + 今すぐ手数料0% でスワップしましょう! アプリにログインして、カードまたはリングをスキャンせずに残高を確認できます アプリにアクセスする 生体認証の使用を許可する @@ -780,6 +780,8 @@ 移行 ネイティブステーキング 現在、ステーキングに使用できるアクティブなバリデータは見つかりません。しばらくしてからもう一度お試しください。 + Cardanoネットワークでステーキングする場合、残高全体が使用されます。追加の2ADAが予約され、ステーキング解除後に返却されます。ステーキング中はADAはロックされません。 + ADAステーキングの詳細 獲得した報酬はあなたのアドレスに直接送られ、すぐ使用可能です。 安全にステーキングして報酬を獲得しましょう 安全にステーキングして、報酬を毎日獲得しましょう @@ -988,7 +990,7 @@ %sネットワーク アドレスがクリップボードにコピーされました インターネット接続がありません - 今すぐ10 %オフで購入 + 今すぐ10 % オフで購入 1.3万種類以上の暗号資産にアクセス。ワンタップで買付、売却、スワップ、ステーキングが可能です。\nバックアップ用に最大3枚のカードを連携できます。 Tangemウォレットを見る ウォレット設定 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 145dac2a4c..aa9e27fb7d 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -142,6 +142,7 @@ Synchronize addresses Go to provider Go to token + Hide hour Import In progress @@ -314,6 +315,9 @@ Bought %s Buying %s Buying %s... + Hide this transaction + Once hidden, the transaction status cannot be viewed again. You can simply swipe to dismiss instead. + Hide Transaction Status? No tokens found. Please try another request ID: %s Transaction ID copied @@ -792,6 +796,8 @@ Migrate Native staking No active validators available for staking at the moment. Please try again later. + When staking in the Cardano network, the entire balance is used. An additional 2 ADA will be reserved and returned after unstaking. Your ADA is not locked while staking. + ADA Staking Details Earned rewards will be sent to your wallet and available for use immediately Stake securely and start earning your rewards Stake securely and start earning daily rewards @@ -805,6 +811,8 @@ Earn staking rewards Your remaining staked balance will be too low to unstake. You’ll need to stake more to meet the minimum unstake amount. Low staked balance + A minimum of %1$s %2$s is required for restaking. Please top up your balance. + Not enough %s Staking is currently unavailable due to network conditions. Please try again later. Staking in the %1$s network with a new validator will automatically transfer all previously staked funds to this validator Reinvests your earned rewards in your staked amount, increasing potential earnings. diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt index 64aa740a5e..e116c0c085 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt @@ -49,6 +49,22 @@ data class OnrampStatus( -> false } + val isAutoDisposable: Boolean + get() = when (this) { + Finished, + -> true + Expired, + Failed, + Paused, + Created, + WaitingForPayment, + PaymentProcessing, + Verifying, + Paid, + Sending, + -> false + } + val isHidden: Boolean get() = when (this) { Created, diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt index ed7aa550c5..082f1b8ec9 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapTransactionRepository.kt @@ -1,6 +1,7 @@ package com.tangem.feature.swap import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.data.common.currency.UserTokensResponseFactory import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObjectList @@ -26,6 +27,7 @@ internal class DefaultSwapTransactionRepository( ) : SwapTransactionRepository { private val converter = SavedSwapTransactionListConverter(excludedBlockchains) + private val userTokensResponseFactory = UserTokensResponseFactory() override suspend fun storeTransaction( userWalletId: UserWalletId, @@ -33,7 +35,13 @@ internal class DefaultSwapTransactionRepository( toCryptoCurrency: CryptoCurrency, transaction: SavedSwapTransactionModel, ) { - transaction.status?.let { storeTransactionState(transaction.txId, it) } + transaction.status?.let { + storeTransactionState( + txId = transaction.txId, + status = it, + refundTokenCurrency = null, + ) + } appPreferencesStore.editData { mutablePreferences -> val savedTransactions: List? = mutablePreferences.getObjectList( key = PreferencesKeys.SWAP_TRANSACTIONS_KEY, @@ -154,14 +162,22 @@ internal class DefaultSwapTransactionRepository( } } - override suspend fun storeTransactionState(txId: String, status: ExchangeStatusModel) { + override suspend fun storeTransactionState( + txId: String, + status: ExchangeStatusModel, + refundTokenCurrency: CryptoCurrency?, + ) { appPreferencesStore.editData { mutablePreferences -> val savedMap = mutablePreferences.getObjectMap( key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, ) val updatesMap = savedMap.toMutableMap() - updatesMap[txId] = status + updatesMap[txId] = status.copy( + refundTokensResponse = refundTokenCurrency?.let { + userTokensResponseFactory.createResponseToken(refundTokenCurrency) + }, + ) mutablePreferences.setObjectMap( key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY, diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SavedSwapTransactionListConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SavedSwapTransactionListConverter.kt index d3d6093b01..ed4ce2cf2e 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SavedSwapTransactionListConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SavedSwapTransactionListConverter.kt @@ -53,7 +53,15 @@ internal class SavedSwapTransactionListConverter( return SavedSwapTransactionListModel( transactions = value.transactions.map { tx -> - tx.copy(status = txStatuses[tx.txId]) + val status = txStatuses[tx.txId] + val refundCurrency = status?.refundTokensResponse?.let { id -> + responseCryptoCurrenciesFactory.createCurrency( + responseToken = id, + scanResponse = scanResponse, + ) + } + val statusWithRefundCurrency = status?.copy(refundCurrency = refundCurrency) + tx.copy(status = statusWithRefundCurrency) }, userWalletId = value.userWalletId, fromCryptoCurrencyId = value.fromCryptoCurrencyId, diff --git a/features/swap/domain/build.gradle.kts b/features/swap/domain/build.gradle.kts index bbf66619e0..ed5fe567e7 100644 --- a/features/swap/domain/build.gradle.kts +++ b/features/swap/domain/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { /** Core modules */ implementation(projects.core.utils) implementation(projects.core.ui) + implementation(projects.core.datasource) /** Feature Apis */ implementation(projects.features.wallet.api) diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt index 161326f0bb..bd5e63b506 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt @@ -2,6 +2,8 @@ package com.tangem.feature.swap.domain.models.domain import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.domain.tokens.model.CryptoCurrency @JsonClass(generateAdapter = true) data class ExchangeStatusModel( @@ -19,6 +21,10 @@ data class ExchangeStatusModel( val refundNetwork: String? = null, @Json(name = "refundContractAddress") val refundContractAddress: String? = null, + @Json(name = "refundTokensResponse") + val refundTokensResponse: UserTokensResponse.Token? = null, + @Json(ignore = true) + val refundCurrency: CryptoCurrency? = null, ) @JsonClass(generateAdapter = false) @@ -64,4 +70,16 @@ enum class ExchangeStatus { @Json(name = "Paused") Paused, + + ; + + val isTerminal: Boolean + get() = this == Refunded || + this == Finished || + this == Cancelled || + this == TxFailed || + this == Unknown + + val isAutoDisposable: Boolean + get() = this == Finished } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt index f02a93f2ab..812499d47f 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapTransactionRepository.kt @@ -30,7 +30,7 @@ interface SwapTransactionRepository { txId: String, ) - suspend fun storeTransactionState(txId: String, status: ExchangeStatusModel) + suspend fun storeTransactionState(txId: String, status: ExchangeStatusModel, refundTokenCurrency: CryptoCurrency?) suspend fun storeLastSwappedCryptoCurrencyId(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt index ce5a9f3d49..7df750dad1 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt @@ -1,6 +1,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.components import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.features.tokendetails.impl.R @@ -133,5 +134,22 @@ internal data class TokenDetailsDialogConfig( onClick = onConfirmClick, ) } + + data class ConfirmExpressStatusHideDialogConfig( + val onConfirmClick: () -> Unit, + val onCancelClick: () -> Unit, + ) : DialogContentConfig() { + override val title: TextReference = resourceReference(R.string.express_status_hide_dialog_title) + override val message: TextReference = resourceReference(R.string.express_status_hide_dialog_text) + override val confirmButtonConfig: ButtonConfig = ButtonConfig( + text = resourceReference(R.string.common_hide), + onClick = onConfirmClick, + ) + + override val cancelButtonConfig: ButtonConfig = ButtonConfig( + text = resourceReference(R.string.common_cancel), + onClick = onCancelClick, + ) + } } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExchangeUM.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExchangeUM.kt index 4420f4be4a..ac23c537eb 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExchangeUM.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExchangeUM.kt @@ -15,7 +15,6 @@ internal data class ExchangeUM( val statuses: ImmutableList, val notification: ExchangeStatusNotifications? = null, val showProviderLink: Boolean, - val isRefundTerminalStatus: Boolean, val fromCryptoCurrency: CryptoCurrency, val toCryptoCurrency: CryptoCurrency, ) : ExpressTransactionStateUM \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt index 3b8d00da73..ec6b983685 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt @@ -94,6 +94,7 @@ internal class TokenDetailsOnrampTransactionStateConverter( clickIntents.onGoToProviderClick(it) }, onClick = { clickIntents.onExpressTransactionClick(value.txId) }, + onDisposeExpressStatus = clickIntents::onConfirmDisposeExpressStatus, ), providerName = value.providerName, providerImageUrl = value.providerImageUrl, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index d93b495c65..e88c1cc061 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -340,6 +340,22 @@ internal class TokenDetailsStateFactory( return balanceSelectStateConverter.convert(buttonConfig) } + fun getStateWithConfirmHideExpressStatus(): TokenDetailsState { + return currentStateProvider().copy( + dialogConfig = TokenDetailsDialogConfig( + isShow = true, + onDismissRequest = clickIntents::onDismissDialog, + content = TokenDetailsDialogConfig.DialogContentConfig.ConfirmExpressStatusHideDialogConfig( + onConfirmClick = { + clickIntents.onDisposeExpressStatus() + clickIntents.onDismissDialog() + }, + onCancelClick = clickIntents::onDismissDialog, + ), + ), + ) + } + private fun TokenDetailsAppBarMenuConfig.updateMenu( cardTypesResolver: CardTypesResolver, hasDerivations: Boolean, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt index 18fe759bac..ba05cd229d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt @@ -80,17 +80,20 @@ internal class TokenDetailsSwapTransactionsStateConverter( fromFiatAmount = quote.fiatRate.multiply(fromAmount) } } - val notifications = - getNotification(transaction.status?.status, transaction.status?.txExternalUrl, null) + val statusModel = transaction.status + val notifications = getNotification( + status = statusModel?.status, + txUrl = statusModel?.txExternalUrl, + refundToken = statusModel?.refundCurrency, + ) val showProviderLink = getShowProviderLink(notifications, transaction.status) result.add( ExchangeUM( provider = transaction.provider, - statuses = getStatuses(transaction.status?.status), + statuses = getStatuses(statusModel?.status), notification = notifications, - activeStatus = transaction.status?.status, + activeStatus = statusModel?.status, showProviderLink = showProviderLink, - isRefundTerminalStatus = true, fromCryptoCurrency = fromCryptoCurrency, toCryptoCurrency = toCryptoCurrency, info = createStateInfo( @@ -107,25 +110,19 @@ internal class TokenDetailsSwapTransactionsStateConverter( return result.toPersistentList() } - fun updateTxStatus( - tx: ExchangeUM, - statusModel: ExchangeStatusModel?, - refundToken: CryptoCurrency?, - isRefundTerminalStatus: Boolean, - ): ExchangeUM { + fun updateTxStatus(tx: ExchangeUM, statusModel: ExchangeStatusModel?): ExchangeUM { if (statusModel == null || tx.activeStatus == statusModel.status) { Timber.e("UpdateTxStatus isn't required. Current status isn't changed") return tx } val hasFailed = statusModel.status == ExchangeStatus.Failed - val notifications = getNotification(statusModel.status, statusModel.txExternalUrl, refundToken) + val notifications = getNotification(statusModel.status, statusModel.txExternalUrl, statusModel.refundCurrency) val showProviderLink = getShowProviderLink(notifications, statusModel) return tx.copy( activeStatus = statusModel.status, notification = notifications, statuses = getStatuses(statusModel.status, hasFailed), showProviderLink = showProviderLink, - isRefundTerminalStatus = isRefundTerminalStatus, info = tx.info.copy(txExternalUrl = statusModel.txExternalUrl), ) } @@ -164,7 +161,8 @@ internal class TokenDetailsSwapTransactionsStateConverter( }, iconState = getIconState(transaction.status?.status), status = getStatusState(), - notification = null, // fixme [REDACTED_JIRA] + notification = null, // fixme [REDACTED_JIRA], + onDisposeExpressStatus = clickIntents::onConfirmDisposeExpressStatus, ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt index b63aad6b70..3bf62b35de 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt @@ -79,13 +79,13 @@ internal class ExchangeStatusFactory @AssistedInject constructor( } } - suspend fun removeTransactionOnBottomSheetClosed(isForceTerminal: Boolean = false) { + suspend fun removeTransactionOnBottomSheetClosed(isForceDispose: Boolean = false) { val state = currentStateProvider() val bottomSheetConfig = state.bottomSheetConfig?.content as? ExpressStatusBottomSheetConfig ?: return val selectedTx = bottomSheetConfig.value as? ExchangeUM ?: return - val shouldTerminate = selectedTx.activeStatus.isTerminal(selectedTx.isRefundTerminalStatus) || isForceTerminal - if (shouldTerminate) { + val shouldDispose = selectedTx.activeStatus?.isAutoDisposable == true || isForceDispose + if (shouldDispose) { swapTransactionRepository.removeTransaction( userWalletId = userWalletId, fromCryptoCurrency = selectedTx.fromCryptoCurrency, @@ -96,20 +96,14 @@ internal class ExchangeStatusFactory @AssistedInject constructor( } suspend fun updateSwapTxStatus(swapTx: ExchangeUM): ExchangeUM { - return if (swapTx.activeStatus.isTerminal(swapTx.isRefundTerminalStatus)) { + return if (swapTx.activeStatus?.isTerminal == true) { swapTx } else { val statusModel = getExchangeStatus(swapTx.info.txId, swapTx.provider) - val isRefundTerminalStatus = statusModel?.refundNetwork == null && - statusModel?.refundContractAddress == null && - swapTx.provider.type != ExchangeProviderType.DEX_BRIDGE - val addedRefundToken = addRefundCurrencyIfNeeded(statusModel, swapTx.provider.type) swapTransactionsStateConverter.updateTxStatus( tx = swapTx, statusModel = statusModel, - refundToken = addedRefundToken, - isRefundTerminalStatus = isRefundTerminalStatus, ) } } @@ -120,8 +114,11 @@ internal class ExchangeStatusFactory @AssistedInject constructor( ifLeft = { null }, ifRight = { statusModel -> sendStatusUpdateAnalytics(statusModel, provider) - swapTransactionRepository.storeTransactionState(txId, statusModel) - statusModel + + val refundTokenCurrency = addRefundCurrencyIfNeeded(statusModel, provider.type) + + swapTransactionRepository.storeTransactionState(txId, statusModel, refundTokenCurrency) + statusModel.copy(refundCurrency = refundTokenCurrency) }, ) } @@ -174,15 +171,6 @@ internal class ExchangeStatusFactory @AssistedInject constructor( ) } - private fun ExchangeStatus?.isTerminal(isRefundTerminal: Boolean): Boolean { - val needTerminalRefund = this == ExchangeStatus.Refunded && isRefundTerminal - return needTerminalRefund || - this == ExchangeStatus.Finished || - this == ExchangeStatus.Cancelled || - this == ExchangeStatus.TxFailed || - this == ExchangeStatus.Unknown - } - private fun toAnalyticStatus(status: ExchangeStatus?): ExpressAnalyticsStatus? { return when (status) { ExchangeStatus.New, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt index 3c1633c710..5f1c18949f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt @@ -157,11 +157,13 @@ internal class ExpressStatusFactory @AssistedInject constructor( suspend fun removeTransactionOnBottomSheetClosed( expressState: ExpressTransactionStateUM, - isForceTerminal: Boolean = false, + isForceDispose: Boolean = false, ) { when (expressState) { - is ExchangeUM -> exchangeStatusFactory.removeTransactionOnBottomSheetClosed(isForceTerminal) - is ExpressTransactionStateUM.OnrampUM -> onrampStatusFactory.removeTransactionOnBottomSheetClosed() + is ExchangeUM -> exchangeStatusFactory.removeTransactionOnBottomSheetClosed(isForceDispose) + is ExpressTransactionStateUM.OnrampUM -> onrampStatusFactory.removeTransactionOnBottomSheetClosed( + isForceDispose, + ) } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt index 7ca14864da..79328454af 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt @@ -68,12 +68,12 @@ internal class OnrampStatusFactory @AssistedInject constructor( } } - suspend fun removeTransactionOnBottomSheetClosed() { + suspend fun removeTransactionOnBottomSheetClosed(isForceDispose: Boolean) { val state = currentStateProvider() val bottomSheetConfig = state.bottomSheetConfig?.content as? ExpressStatusBottomSheetConfig ?: return val selectedTx = bottomSheetConfig.value as? ExpressTransactionStateUM.OnrampUM ?: return - if (selectedTx.activeStatus.isTerminal) { + if (selectedTx.activeStatus.isAutoDisposable || isForceDispose) { onrampRemoveTransactionUseCase(externalTxId = selectedTx.info.txExternalId) } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt index 3c969fa8d2..f547861942 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import com.tangem.common.ui.expressStatus.ExpressEstimate +import com.tangem.common.ui.expressStatus.ExpressHideButton import com.tangem.common.ui.expressStatus.ExpressProvider import com.tangem.core.ui.R import com.tangem.core.ui.components.SpacerH10 @@ -70,6 +71,11 @@ internal fun ExchangeStatusBottomSheetContent(state: ExchangeUM) { if (state.notification != null) { Notification(state = state.notification, activeStatus = state.activeStatus) } + ExpressHideButton( + isTerminal = state.activeStatus?.isTerminal == true, + isAutoDisposable = state.activeStatus?.isAutoDisposable == true, + onClick = state.info.onDisposeExpressStatus, + ) SpacerH24() } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt index e4439079b5..8806affc45 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsClickIntents.kt @@ -72,4 +72,8 @@ interface TokenDetailsClickIntents { fun onGoToRefundedTokenClick(cryptoCurrency: CryptoCurrency) fun onOpenUrlClick(url: String) + + fun onConfirmDisposeExpressStatus() + + fun onDisposeExpressStatus() } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index cb86b9d766..744218305e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -784,16 +784,6 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onGoToRefundedTokenClick(cryptoCurrency: CryptoCurrency) { - val bottomSheetState = internalUiState.value.bottomSheetConfig?.content - if (bottomSheetState is ExpressStatusBottomSheetConfig) { - viewModelScope.launch { - expressStatusFactory.removeTransactionOnBottomSheetClosed( - expressState = bottomSheetState.value, - isForceTerminal = true, - ) - } - } - internalUiState.value = stateFactory.getStateWithClosedBottomSheet() router.openTokenDetails(userWalletId, cryptoCurrency) } @@ -950,6 +940,23 @@ internal class TokenDetailsViewModel @Inject constructor( internalUiState.value = stateFactory.getStateWithUpdatedBalanceSegmentedButtonConfig(config) } + override fun onConfirmDisposeExpressStatus() { + internalUiState.value = stateFactory.getStateWithConfirmHideExpressStatus() + } + + override fun onDisposeExpressStatus() { + val bottomSheetState = internalUiState.value.bottomSheetConfig?.content + if (bottomSheetState is ExpressStatusBottomSheetConfig) { + viewModelScope.launch { + expressStatusFactory.removeTransactionOnBottomSheetClosed( + expressState = bottomSheetState.value, + isForceDispose = true, + ) + } + } + internalUiState.value = stateFactory.getStateWithClosedBottomSheet() + } + private fun handleUnavailabilityReason( unavailabilityReason: ScenarioUnavailabilityReason, tokenSymbol: String, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt index 7686df2258..32450b5201 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt @@ -30,12 +30,12 @@ internal class OnrampStatusFactory @Inject constructor( private val dispatchers: CoroutineDispatcherProvider, ) { - suspend fun removeTransactionOnBottomSheetClosed() { + suspend fun removeTransactionOnBottomSheetClosed(forceDispose: Boolean) { val state = stateHolder.getSelectedWallet() val bottomSheetConfig = state.bottomSheetConfig?.content as? ExpressStatusBottomSheetConfig ?: return val selectedTx = bottomSheetConfig.value as? ExpressTransactionStateUM.OnrampUM ?: return - if (selectedTx.activeStatus.isTerminal) { + if (selectedTx.activeStatus.isAutoDisposable || forceDispose) { onrampRemoveTransactionUseCase(externalTxId = selectedTx.info.txExternalId) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertState.kt index a55311e803..7d8fe9b661 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletAlertState.kt @@ -16,6 +16,8 @@ internal sealed interface WalletAlertState { open val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok) open val isWarningConfirmButton: Boolean = false abstract val onConfirmClick: (() -> Unit)? + open val cancelButtonText: TextReference? = null + open val onCancelClick: (() -> Unit)? = null } @Immutable @@ -108,4 +110,14 @@ internal sealed interface WalletAlertState { override val onConfirmClick: (() -> Unit)? = null } + + data class ConfirmExpressStatusHide( + override val onConfirmClick: (() -> Unit), + override val onCancelClick: (() -> Unit), + ) : Basic() { + override val title: TextReference = resourceReference(R.string.express_status_hide_dialog_title) + override val message: TextReference = resourceReference(R.string.express_status_hide_dialog_text) + override val confirmButtonText: TextReference = resourceReference(R.string.common_hide) + override val cancelButtonText: TextReference = resourceReference(R.string.common_cancel) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletOnrampTransactionConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletOnrampTransactionConverter.kt index 606d0ed064..a5a0ee99b1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletOnrampTransactionConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletOnrampTransactionConverter.kt @@ -62,9 +62,7 @@ internal class SingleWalletOnrampTransactionConverter( value.timestamp.toTimeFormat(), ), ), - toAmount = stringReference( - value.toAmount.format { crypto(currency) }, - ), + toAmount = stringReference(value.toAmount.format { crypto(currency) }), toFiatAmount = stringReference( status.fiatRate?.multiply(value.toAmount).format { fiat( @@ -94,6 +92,7 @@ internal class SingleWalletOnrampTransactionConverter( analyticsEventHandler.send(TokenOnrampAnalyticsEvent.GoToProvider) clickIntents.onGoToProviderClick(it) }, + onDisposeExpressStatus = clickIntents::onConfirmDisposeExpressStatus, onClick = { val analyticEvent = TokenOnrampAnalyticsEvent.OnrampStatusOpened( tokenSymbol = currency.symbol, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletEventSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletEventSender.kt index 34859d54d9..710e54acde 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletEventSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletEventSender.kt @@ -21,7 +21,7 @@ internal class WalletEventSender @Inject constructor( stateHolder.update(transformer = SendEventTransformer(event = event, onConsume = ::onConsume)) } - private fun onConsume() { + fun onConsume() { stateHolder.update { it.copy(event = consumedEvent()) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt index 0b20b60c75..3d00a7564b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt @@ -15,12 +15,14 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory import com.tangem.feature.wallet.presentation.wallet.domain.unwrap import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.model.* import com.tangem.feature.wallet.presentation.wallet.state.model.ActionsBottomSheetConfig -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.OpenBottomSheetTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCurrencyActionsConverter +import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.coroutines.flow.collectLatest @@ -54,6 +56,10 @@ internal interface WalletContentClickIntents { fun onGoToProviderClick(externalTxId: String) fun onExpressTransactionClick(txId: String) + + fun onConfirmDisposeExpressStatus() + + fun onDisposeExpressStatus() } @Suppress("LongParameterList") @@ -70,6 +76,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( private val shouldShowMarketsTooltipUseCase: ShouldShowMarketsTooltipUseCase, private val dispatchers: CoroutineDispatcherProvider, private val reduxStateHolder: ReduxStateHolder, + private val walletEventSender: WalletEventSender, ) : BaseWalletClickIntents(), WalletContentClickIntents { override fun onBackClick() = router.popBackStack() @@ -185,7 +192,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( val userWalletId = stateHolder.getSelectedWalletId() if (stateHolder.getSelectedWallet().bottomSheetConfig?.content is ExpressStatusBottomSheetConfig) { viewModelScope.launch(dispatchers.main) { - onrampStatusFactory.removeTransactionOnBottomSheetClosed() + onrampStatusFactory.removeTransactionOnBottomSheetClosed(forceDispose = false) } } stateHolder.update(CloseBottomSheetTransformer(userWalletId)) @@ -211,4 +218,28 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( ) } } + + override fun onConfirmDisposeExpressStatus() { + walletEventSender.send( + WalletEvent.ShowAlert( + WalletAlertState.ConfirmExpressStatusHide( + onConfirmClick = { + walletEventSender.onConsume() + onDisposeExpressStatus() + }, + onCancelClick = walletEventSender::onConsume, + ), + ), + ) + } + + override fun onDisposeExpressStatus() { + val userWalletId = stateHolder.getSelectedWalletId() + if (stateHolder.getSelectedWallet().bottomSheetConfig?.content is ExpressStatusBottomSheetConfig) { + viewModelScope.launch(dispatchers.main) { + onrampStatusFactory.removeTransactionOnBottomSheetClosed(forceDispose = true) + } + } + stateHolder.update(CloseBottomSheetTransformer(userWalletId)) + } } \ No newline at end of file