Updated on 2026-08-14

This commit is contained in:
Tangem 2024-12-11 13:14:38 +05:00
parent 466b6e578b
commit df4288e70a
27 changed files with 381 additions and 31 deletions

View file

@ -48,6 +48,7 @@ internal class TokenDetailsNotificationsAnalyticsSender(
is TokenDetailsNotification.RentInfo,
is TokenDetailsNotification.NetworkShutdown,
is TokenDetailsNotification.HederaAssociateWarning,
is TokenDetailsNotification.KaspaIncompleteTransactionWarning,
is TokenDetailsNotification.KoinosMana,
is TokenDetailsNotification.MigrationMaticToPol,
-> null

View file

@ -96,6 +96,27 @@ internal data class TokenDetailsDialogConfig(
)
}
data class RemoveIncompleteTransactionConfirmDialogConfig(
val onConfirmClick: () -> Unit,
val onCancelClick: () -> Unit,
) : DialogContentConfig() {
override val title = null
override val message: TextReference = TextReference.Res(
id = R.string.warning_kaspa_unfinished_token_transaction_discard_message,
)
override val cancelButtonConfig: ButtonConfig = ButtonConfig(
text = TextReference.Res(R.string.common_cancel),
onClick = onCancelClick,
)
override val confirmButtonConfig: ButtonConfig = ButtonConfig(
text = TextReference.Res(R.string.common_yes),
onClick = onConfirmClick,
)
}
data class ErrorDialogConfig(
val text: TextReference,
val onConfirmClick: () -> Unit,

View file

@ -19,12 +19,14 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
subtitle: TextReference,
iconResId: Int = R.drawable.img_attention_20,
buttonsState: NotificationConfig.ButtonsState? = null,
onCloseClick: (() -> Unit)? = null,
) : TokenDetailsNotification(
config = NotificationConfig(
title = title,
subtitle = subtitle,
iconResId = iconResId,
buttonsState = buttonsState,
onCloseClick = onCloseClick,
),
)
@ -191,6 +193,27 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
),
)
data class KaspaIncompleteTransactionWarning(
private val currency: CryptoCurrency,
private val amount: String,
private val currencySymbol: String,
private val onRetryIncompleteTransactionClick: () -> Unit,
private val onDismissIncompleteTransactionClick: () -> Unit,
) : Warning(
title = resourceReference(R.string.warning_kaspa_unfinished_token_transaction_title),
subtitle = resourceReference(
id = R.string.warning_kaspa_unfinished_token_transaction_message,
formatArgs = wrappedList(amount, currencySymbol),
),
iconResId = R.drawable.ic_alert_circle_red_20,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.alert_button_try_again),
onClick = onRetryIncompleteTransactionClick,
iconResId = R.drawable.ic_tangem_24,
),
onCloseClick = onDismissIncompleteTransactionClick,
)
data class KoinosMana(
val manaBalanceAmount: String,
val maxManaBalanceAmount: String,

View file

@ -9,6 +9,7 @@ import com.tangem.core.ui.format.bigdecimal.shorted
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.tokens.model.warnings.HederaWarnings
import com.tangem.domain.tokens.model.warnings.KaspaWarnings
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification.*
@ -41,6 +42,14 @@ internal class TokenDetailsNotificationConverter(
return newNotifications.toImmutableList()
}
fun removeKaspaIncompleteTransactionWarning(
currentState: TokenDetailsState,
): ImmutableList<TokenDetailsNotification> {
val newNotifications = currentState.notifications.toMutableList()
newNotifications.removeBy { it is KaspaIncompleteTransactionWarning }
return newNotifications.toImmutableList()
}
@Suppress("LongMethod", "CyclomaticComplexMethod")
private fun mapToNotification(warning: CryptoCurrencyWarning): TokenDetailsNotification {
return when (warning) {
@ -109,6 +118,13 @@ internal class TokenDetailsNotificationConverter(
feeCurrencySymbol = warning.feeCurrencySymbol,
onAssociateClick = clickIntents::onAssociateClick,
)
is KaspaWarnings.IncompleteTransaction -> KaspaIncompleteTransactionWarning(
currency = warning.currency,
amount = warning.amount.format { crypto(symbol = "", decimals = warning.currencyDecimals) },
currencySymbol = warning.currencySymbol,
onRetryIncompleteTransactionClick = clickIntents::onRetryIncompleteTransactionClick,
onDismissIncompleteTransactionClick = clickIntents::onDismissIncompleteTransactionClick,
)
is CryptoCurrencyWarning.FeeResourceInfo -> KoinosMana(
manaBalanceAmount = formatMana(warning.amount),
maxManaBalanceAmount = warning.maxAmount?.let {

View file

@ -199,6 +199,19 @@ internal class TokenDetailsStateFactory(
)
}
fun getStateWithDismissIncompleteTransactionConfirmDialog(): TokenDetailsState {
return currentStateProvider().copy(
dialogConfig = TokenDetailsDialogConfig(
isShow = true,
onDismissRequest = clickIntents::onDismissDialog,
content = TokenDetailsDialogConfig.DialogContentConfig.RemoveIncompleteTransactionConfirmDialogConfig(
onConfirmClick = clickIntents::onConfirmDismissIncompleteTransactionClick,
onCancelClick = clickIntents::onDismissDialog,
),
),
)
}
fun getStateWithActionButtonErrorDialog(unavailabilityReason: ScenarioUnavailabilityReason): TokenDetailsState {
return currentStateProvider().copy(
dialogConfig = TokenDetailsDialogConfig(
@ -300,6 +313,14 @@ internal class TokenDetailsStateFactory(
return state.copy(notifications = notificationConverter.removeHederaAssociateWarning(state))
}
fun getStateWithRemovedKaspaIncompleteTransactionNotification(): TokenDetailsState {
val state = currentStateProvider()
return state.copy(
notifications = notificationConverter.removeKaspaIncompleteTransactionWarning(state),
dialogConfig = state.dialogConfig?.copy(isShow = false),
)
}
fun getStateAndTriggerEvent(
state: TokenDetailsState,
errorMessage: TextReference,

View file

@ -59,6 +59,12 @@ interface TokenDetailsClickIntents {
fun onAssociateClick()
fun onRetryIncompleteTransactionClick()
fun onDismissIncompleteTransactionClick()
fun onConfirmDismissIncompleteTransactionClick()
fun onStakeBannerClick()
fun onBalanceSelect(config: TokenBalanceSegmentedButtonConfig)

View file

@ -50,7 +50,10 @@ import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.toReasonAnalyticsText
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent
import com.tangem.domain.transaction.error.AssociateAssetError
import com.tangem.domain.transaction.error.IncompleteTransactionError
import com.tangem.domain.transaction.usecase.AssociateAssetUseCase
import com.tangem.domain.transaction.usecase.DismissIncompleteTransactionUseCase
import com.tangem.domain.transaction.usecase.RetryIncompleteTransactionUseCase
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
@ -108,6 +111,8 @@ internal class TokenDetailsViewModel @Inject constructor(
private val networkHasDerivationUseCase: NetworkHasDerivationUseCase,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val associateAssetUseCase: AssociateAssetUseCase,
private val retryIncompleteTransactionUseCase: RetryIncompleteTransactionUseCase,
private val dismissIncompleteTransactionUseCase: DismissIncompleteTransactionUseCase,
private val reduxStateHolder: ReduxStateHolder,
private val analyticsEventsHandler: AnalyticsEventHandler,
private val vibratorHapticManager: VibratorHapticManager,
@ -861,6 +866,58 @@ internal class TokenDetailsViewModel @Inject constructor(
return resourceReference(R.string.wallet_notification_address_copied)
}
override fun onRetryIncompleteTransactionClick() {
viewModelScope.launch {
retryIncompleteTransactionUseCase(
userWalletId = userWalletId,
currency = cryptoCurrency,
).fold(
ifLeft = { e ->
when (e) {
is IncompleteTransactionError.DataError -> {
internalUiState.value = stateFactory.getStateWithErrorDialog(
stringReference(e.message.orEmpty()),
)
Timber.e(e.message)
}
}
},
ifRight = {
internalUiState.value = stateFactory.getStateWithRemovedKaspaIncompleteTransactionNotification()
},
)
}
}
override fun onDismissIncompleteTransactionClick() {
viewModelScope.launch {
internalUiState.value = stateFactory.getStateWithDismissIncompleteTransactionConfirmDialog()
}
}
override fun onConfirmDismissIncompleteTransactionClick() {
viewModelScope.launch {
dismissIncompleteTransactionUseCase(
userWalletId = userWalletId,
currency = cryptoCurrency,
).fold(
ifLeft = { e ->
when (e) {
is IncompleteTransactionError.DataError -> {
internalUiState.value = stateFactory.getStateWithErrorDialog(
stringReference(e.message.orEmpty()),
)
Timber.e(e.message)
}
}
},
ifRight = {
internalUiState.value = stateFactory.getStateWithRemovedKaspaIncompleteTransactionNotification()
},
)
}
}
override fun onAssociateClick() {
analyticsEventsHandler.send(
TokenScreenAnalyticsEvent.Associate(