Updated on 2026-08-14
This commit is contained in:
commit
721258e093
39 changed files with 469 additions and 109 deletions
|
|
@ -58,6 +58,28 @@ internal object TransactionDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRetryTransactionUseCase(
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
): RetryIncompleteTransactionUseCase {
|
||||
return RetryIncompleteTransactionUseCase(
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDismissIncompleteTransactionUseCase(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
): DismissIncompleteTransactionUseCase {
|
||||
return DismissIncompleteTransactionUseCase(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCreateTransactionUseCase(transactionRepository: TransactionRepository): CreateTransactionUseCase {
|
||||
|
|
|
|||
|
|
@ -212,7 +212,6 @@ internal class MainViewModel @Inject constructor(
|
|||
|
||||
override fun onDismissBottomSheet() {
|
||||
listenToFlipsUseCase.changeUpdateEnabled(true)
|
||||
router.pop()
|
||||
stateHolder.updateWithoutModalNotification()
|
||||
stateHolder.updateWithHiddenBalancesToast(true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,9 +228,13 @@ sealed class NotificationUM(val config: NotificationConfig) {
|
|||
),
|
||||
)
|
||||
|
||||
data class OnrampErrorNotification(val onRefresh: () -> Unit) : Warning(
|
||||
data class OnrampErrorNotification(val errorCode: String?, val onRefresh: () -> Unit) : Warning(
|
||||
title = resourceReference(R.string.common_error),
|
||||
subtitle = resourceReference(R.string.common_unknown_error),
|
||||
subtitle = if (errorCode != null) {
|
||||
resourceReference(R.string.express_error_code, wrappedList(errorCode))
|
||||
} else {
|
||||
resourceReference(R.string.common_unknown_error)
|
||||
},
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.warning_button_refresh),
|
||||
onClick = onRefresh,
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ object NotificationsFactory {
|
|||
if (dustValue == null) return
|
||||
val isExceedsLimit = checkDustLimits(
|
||||
feeAmount = feeValue,
|
||||
receivedAmount = sendingAmount,
|
||||
sendingAmount = sendingAmount,
|
||||
dustValue = dustValue,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
feeCurrencyStatus = feeCurrencyStatus,
|
||||
|
|
@ -352,7 +352,7 @@ object NotificationsFactory {
|
|||
|
||||
private fun checkDustLimits(
|
||||
feeAmount: BigDecimal,
|
||||
receivedAmount: BigDecimal,
|
||||
sendingAmount: BigDecimal,
|
||||
dustValue: BigDecimal,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
feeCurrencyStatus: CryptoCurrencyStatus?,
|
||||
|
|
@ -360,7 +360,7 @@ object NotificationsFactory {
|
|||
val change = when (cryptoCurrencyStatus.currency) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||
balance - (feeAmount + receivedAmount)
|
||||
balance - (feeAmount + sendingAmount)
|
||||
}
|
||||
is CryptoCurrency.Token -> {
|
||||
val balance = feeCurrencyStatus?.value?.amount ?: BigDecimal.ZERO
|
||||
|
|
@ -369,6 +369,9 @@ object NotificationsFactory {
|
|||
}
|
||||
|
||||
val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO
|
||||
return receivedAmount < dustValue || isChangeLowerThanDust
|
||||
return when (cryptoCurrencyStatus.currency) {
|
||||
is CryptoCurrency.Coin -> sendingAmount < dustValue || isChangeLowerThanDust
|
||||
is CryptoCurrency.Token -> isChangeLowerThanDust
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -222,13 +222,23 @@ private fun Buttons(state: NotificationButtonsState?, isEnabled: Boolean = true)
|
|||
|
||||
@Composable
|
||||
private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButtonConfig, isEnabled: Boolean = true) {
|
||||
SecondaryButton(
|
||||
text = config.text.resolveReference(),
|
||||
onClick = config.onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = TangemButtonSize.WideAction,
|
||||
enabled = isEnabled,
|
||||
)
|
||||
if (config.iconResId != null) {
|
||||
SecondaryButtonIconEnd(
|
||||
text = config.text.resolveReference(),
|
||||
onClick = config.onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
iconResId = config.iconResId,
|
||||
enabled = isEnabled,
|
||||
)
|
||||
} else {
|
||||
SecondaryButton(
|
||||
text = config.text.resolveReference(),
|
||||
onClick = config.onClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = TangemButtonSize.WideAction,
|
||||
enabled = isEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
9
core/ui/src/main/res/drawable/ic_alert_circle_red_20.xml
Normal file
9
core/ui/src/main/res/drawable/ic_alert_circle_red_20.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:pathData="M10.833,10.833H9.167V5.833H10.833M10.833,14.167H9.167V12.5H10.833M10,1.667C8.906,1.667 7.822,1.882 6.811,2.301C5.8,2.72 4.881,3.334 4.107,4.108C2.545,5.67 1.667,7.79 1.667,10C1.667,12.21 2.545,14.33 4.107,15.893C4.881,16.667 5.8,17.28 6.811,17.699C7.822,18.118 8.906,18.333 10,18.333C12.21,18.333 14.33,17.455 15.892,15.893C17.455,14.33 18.333,12.21 18.333,10C18.333,8.906 18.118,7.822 17.699,6.811C17.28,5.8 16.666,4.881 15.892,4.108C15.119,3.334 14.2,2.72 13.189,2.301C12.178,1.882 11.094,1.667 10,1.667Z"
|
||||
android:fillColor="#FF3333"/>
|
||||
</vector>
|
||||
|
|
@ -52,6 +52,7 @@ internal class DefaultCustomTokensRepository(
|
|||
Blockchain.Unknown,
|
||||
Blockchain.Binance,
|
||||
Blockchain.BinanceTestnet,
|
||||
Blockchain.Kaspa,
|
||||
-> true
|
||||
Blockchain.Cardano -> blockchain.validateContractAddress(contractAddress)
|
||||
else -> blockchain.validateAddress(contractAddress)
|
||||
|
|
|
|||
|
|
@ -304,10 +304,11 @@ internal class DefaultMarketsTokenRepository(
|
|||
}
|
||||
|
||||
private fun createListErrorEvent(error: ApiResponseError): MarketsDataAnalyticsEvent.List.Error {
|
||||
return createErrorEvent(error) { errorType, errorCode ->
|
||||
return createErrorEvent(error) { errorType, errorCode, errorMessage ->
|
||||
MarketsDataAnalyticsEvent.List.Error(
|
||||
errorType = errorType,
|
||||
errorCode = errorCode,
|
||||
errorMessage = errorMessage,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -317,10 +318,11 @@ internal class DefaultMarketsTokenRepository(
|
|||
request: MarketsDataAnalyticsEvent.Details.Error.Request,
|
||||
tokenSymbol: String,
|
||||
): MarketsDataAnalyticsEvent.Details.Error {
|
||||
return createErrorEvent(error) { errorType, errorCode ->
|
||||
return createErrorEvent(error) { errorType, errorCode, errorMessage ->
|
||||
MarketsDataAnalyticsEvent.Details.Error(
|
||||
errorType = errorType,
|
||||
errorCode = errorCode,
|
||||
errorMessage = errorMessage,
|
||||
request = request,
|
||||
tokenSymbol = tokenSymbol,
|
||||
)
|
||||
|
|
@ -329,20 +331,20 @@ internal class DefaultMarketsTokenRepository(
|
|||
|
||||
private inline fun <T> createErrorEvent(
|
||||
error: ApiResponseError,
|
||||
createEvent: (MarketsDataAnalyticsEvent.Type, Int?) -> T,
|
||||
createEvent: (MarketsDataAnalyticsEvent.Type, Int?, String) -> T,
|
||||
): T {
|
||||
return when (error) {
|
||||
is ApiResponseError.HttpException -> {
|
||||
createEvent(MarketsDataAnalyticsEvent.Type.Http, error.code.code)
|
||||
createEvent(MarketsDataAnalyticsEvent.Type.Http, error.code.code, error.message.orEmpty())
|
||||
}
|
||||
is ApiResponseError.TimeoutException -> {
|
||||
createEvent(MarketsDataAnalyticsEvent.Type.Timeout, null)
|
||||
createEvent(MarketsDataAnalyticsEvent.Type.Timeout, null, error.message.orEmpty())
|
||||
}
|
||||
is ApiResponseError.NetworkException -> {
|
||||
createEvent(MarketsDataAnalyticsEvent.Type.Network, null)
|
||||
createEvent(MarketsDataAnalyticsEvent.Type.Network, null, error.message.orEmpty())
|
||||
}
|
||||
is ApiResponseError.UnknownException -> {
|
||||
createEvent(MarketsDataAnalyticsEvent.Type.Unknown, null)
|
||||
createEvent(MarketsDataAnalyticsEvent.Type.Unknown, null, error.message.orEmpty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.data.markets.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
|
||||
sealed interface MarketsDataAnalyticsEvent {
|
||||
|
||||
|
|
@ -12,11 +13,13 @@ sealed interface MarketsDataAnalyticsEvent {
|
|||
data class Error(
|
||||
val errorType: Type,
|
||||
val errorCode: Int? = null,
|
||||
val errorMessage: String,
|
||||
) : List(
|
||||
event = "Data Error",
|
||||
params = buildMap {
|
||||
put("Error Type", errorType.value)
|
||||
errorCode?.let { put("Error Code", it.toString()) }
|
||||
put(AnalyticsParam.ERROR_TYPE, errorType.value)
|
||||
put(AnalyticsParam.ERROR_CODE, errorCode?.toString() ?: IS_NOT_HTTP_ERROR)
|
||||
put(AnalyticsParam.ERROR_MESSAGE, errorMessage)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -31,13 +34,15 @@ sealed interface MarketsDataAnalyticsEvent {
|
|||
val tokenSymbol: String,
|
||||
val errorType: Type,
|
||||
val errorCode: Int? = null,
|
||||
val errorMessage: String,
|
||||
) : Details(
|
||||
event = "Data Error",
|
||||
params = buildMap {
|
||||
put("Source", request.source)
|
||||
put("Token", tokenSymbol)
|
||||
errorCode?.let { put("Error Code", it.toString()) }
|
||||
put("Error Type", errorType.value)
|
||||
put(AnalyticsParam.ERROR_TYPE, errorType.value)
|
||||
put(AnalyticsParam.ERROR_CODE, errorCode?.toString() ?: IS_NOT_HTTP_ERROR)
|
||||
put(AnalyticsParam.ERROR_MESSAGE, errorMessage)
|
||||
},
|
||||
) {
|
||||
|
||||
|
|
@ -63,9 +68,9 @@ sealed interface MarketsDataAnalyticsEvent {
|
|||
event = "Data Error",
|
||||
params = buildMap {
|
||||
put("Request path", requestPath)
|
||||
errorCode?.let { put("Error Code", it.toString()) }
|
||||
put("Error Type", errorType.value)
|
||||
put("Error Description", "Chart data contains null values from the API")
|
||||
put(AnalyticsParam.ERROR_TYPE, errorType.value)
|
||||
put(AnalyticsParam.ERROR_CODE, errorCode?.toString() ?: IS_NOT_HTTP_ERROR)
|
||||
put(AnalyticsParam.ERROR_MESSAGE, "Chart data contains null values from the API")
|
||||
},
|
||||
),
|
||||
MarketsDataAnalyticsEvent
|
||||
|
|
@ -77,4 +82,8 @@ sealed interface MarketsDataAnalyticsEvent {
|
|||
Custom("Custom"),
|
||||
Unknown("Unknown"),
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val IS_NOT_HTTP_ERROR = "Is not http error"
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import com.tangem.blockchain.common.ReserveAmountProvider
|
|||
import com.tangem.blockchain.common.UtxoAmountLimitProvider
|
||||
import com.tangem.data.tokens.converters.UtxoConverter
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.CurrencyAmount
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -24,6 +25,7 @@ internal class DefaultCurrencyChecksRepository(
|
|||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val coroutineDispatchers: CoroutineDispatcherProvider,
|
||||
) : CurrencyChecksRepository {
|
||||
|
||||
override suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? {
|
||||
val manager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -108,6 +110,7 @@ internal class DefaultCurrencyChecksRepository(
|
|||
override suspend fun checkUtxoAmountLimit(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
currency: CryptoCurrency,
|
||||
amount: BigDecimal,
|
||||
fee: BigDecimal,
|
||||
): UtxoAmountLimit? {
|
||||
|
|
|
|||
|
|
@ -594,7 +594,7 @@ class DefaultWalletManagersFacade(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun associateAsset(
|
||||
override suspend fun fulfillRequirements(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
signer: TransactionSigner,
|
||||
|
|
@ -613,6 +613,21 @@ class DefaultWalletManagersFacade(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun discardRequirements(userWalletId: UserWalletId, currency: CryptoCurrency): SimpleResult {
|
||||
return withContext(dispatchers.io) {
|
||||
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
|
||||
val currencyType = cryptoCurrencyTypeConverter.convert(currency)
|
||||
|
||||
if (walletManager !is AssetRequirementsManager) {
|
||||
return@withContext SimpleResult.Failure(
|
||||
BlockchainSdkError.CustomError("WalletManager is not implemented AssetRequirementsManager"),
|
||||
)
|
||||
}
|
||||
|
||||
walletManager.discardRequirements(currencyType)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun checkUtxoConsolidationAvailability(userWalletId: UserWalletId, network: Network): Boolean {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val walletManager = getOrCreateWalletManager(
|
||||
|
|
|
|||
|
|
@ -233,12 +233,14 @@ interface WalletManagersFacade {
|
|||
*/
|
||||
suspend fun getAssetRequirements(userWalletId: UserWalletId, currency: CryptoCurrency): AssetRequirementsCondition?
|
||||
|
||||
suspend fun associateAsset(
|
||||
suspend fun fulfillRequirements(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
signer: TransactionSigner,
|
||||
): SimpleResult
|
||||
|
||||
suspend fun discardRequirements(userWalletId: UserWalletId, currency: CryptoCurrency): SimpleResult
|
||||
|
||||
/**
|
||||
* Indicates UTXO consolidation availability
|
||||
*
|
||||
|
|
|
|||
|
|
@ -7,13 +7,20 @@ import com.tangem.blockchain.common.trustlines.AssetRequirementsCondition as Sdk
|
|||
internal class SdkRequirementsConditionConverter : Converter<SdkRequirementsCondition, AssetRequirementsCondition> {
|
||||
override fun convert(value: SdkRequirementsCondition): AssetRequirementsCondition {
|
||||
return when (value) {
|
||||
SdkRequirementsCondition.PaidTransaction -> AssetRequirementsCondition.PaidTransaction
|
||||
is SdkRequirementsCondition.PaidTransaction -> AssetRequirementsCondition.PaidTransaction
|
||||
is SdkRequirementsCondition.PaidTransactionWithFee -> AssetRequirementsCondition.PaidTransactionWithFee(
|
||||
feeAmount = requireNotNull(value.feeAmount.value),
|
||||
feeCurrencySymbol = value.feeAmount.currencySymbol,
|
||||
decimals = value.feeAmount.decimals,
|
||||
)
|
||||
is SdkRequirementsCondition.IncompleteTransaction -> TODO()
|
||||
is SdkRequirementsCondition.IncompleteTransaction -> AssetRequirementsCondition.IncompleteTransaction(
|
||||
amount = requireNotNull(value.amount.value),
|
||||
currencySymbol = value.amount.currencySymbol,
|
||||
currencyDecimals = value.amount.decimals,
|
||||
feeAmount = requireNotNull(value.feeAmount.value),
|
||||
feeCurrencySymbol = value.feeAmount.currencySymbol,
|
||||
feeCurrencyDecimals = value.feeAmount.decimals,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.domain.tokens.model.warnings
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class KaspaWarnings : CryptoCurrencyWarning() {
|
||||
|
||||
abstract val currency: CryptoCurrency
|
||||
|
||||
data class IncompleteTransaction(
|
||||
override val currency: CryptoCurrency,
|
||||
val amount: BigDecimal,
|
||||
val currencySymbol: String,
|
||||
val currencyDecimals: Int,
|
||||
) : KaspaWarnings()
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
|
|
@ -22,7 +23,8 @@ class GetCurrencyCheckUseCase(
|
|||
recipientAddress: String? = null,
|
||||
): CryptoCurrencyCheck {
|
||||
return withContext(dispatchers.io) {
|
||||
val network = currencyStatus.currency.network
|
||||
val currency = currencyStatus.currency
|
||||
val network = currency.network
|
||||
val dustValue = currencyChecksRepository.getDustValue(userWalletId, network)
|
||||
val reserveAmount = currencyChecksRepository.getReserveAmount(userWalletId, network)
|
||||
val minimumSendAmount = currencyChecksRepository.getMinimumSendAmount(userWalletId, network)
|
||||
|
|
@ -39,10 +41,11 @@ class GetCurrencyCheckUseCase(
|
|||
recipientAddress,
|
||||
)
|
||||
} ?: false
|
||||
val utxoAmountLimit = if (amount != null && fee != null) {
|
||||
val utxoAmountLimit = if (currency is CryptoCurrency.Coin && amount != null && fee != null) {
|
||||
currencyChecksRepository.checkUtxoAmountLimit(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
currency = currencyStatus.currency,
|
||||
amount = amount,
|
||||
fee = fee,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.staking.repositories.StakingRepository
|
|||
import com.tangem.domain.tokens.model.*
|
||||
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.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.repository.*
|
||||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
|
|
@ -20,7 +21,7 @@ import com.tangem.utils.isNullOrZero
|
|||
import kotlinx.coroutines.flow.*
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
class GetCurrencyWarningsUseCase(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
|
|
@ -79,7 +80,17 @@ class GetCurrencyWarningsUseCase(
|
|||
getAssetRequirementsWarning(userWalletId = userWalletId, currency = currency),
|
||||
getMigrationFromMaticToPolWarning(currency),
|
||||
)
|
||||
}.flowOn(dispatchers.io)
|
||||
}
|
||||
.onEmpty {
|
||||
setOfNotNull(
|
||||
getNetworkUnavailableWarning(currencyStatus),
|
||||
getNetworkNoAccountWarning(currencyStatus),
|
||||
getBeaconChainShutdownWarning(currency.network.id),
|
||||
getAssetRequirementsWarning(userWalletId = userWalletId, currency = currency),
|
||||
getMigrationFromMaticToPolWarning(currency),
|
||||
)
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
private suspend fun getSwapPromoNotificationWarning(
|
||||
|
|
@ -298,12 +309,22 @@ class GetCurrencyWarningsUseCase(
|
|||
): CryptoCurrencyWarning? {
|
||||
return when (val requirements = walletManagersFacade.getAssetRequirements(userWalletId, currency)) {
|
||||
is AssetRequirementsCondition.PaidTransaction -> HederaWarnings.AssociateWarning(currency = currency)
|
||||
is AssetRequirementsCondition.PaidTransactionWithFee -> HederaWarnings.AssociateWarningWithFee(
|
||||
currency = currency,
|
||||
fee = requirements.feeAmount,
|
||||
feeCurrencySymbol = requirements.feeCurrencySymbol,
|
||||
feeCurrencyDecimals = requirements.decimals,
|
||||
)
|
||||
is AssetRequirementsCondition.PaidTransactionWithFee -> {
|
||||
HederaWarnings.AssociateWarningWithFee(
|
||||
currency = currency,
|
||||
fee = requirements.feeAmount,
|
||||
feeCurrencySymbol = requirements.feeCurrencySymbol,
|
||||
feeCurrencyDecimals = requirements.decimals,
|
||||
)
|
||||
}
|
||||
is AssetRequirementsCondition.IncompleteTransaction ->
|
||||
KaspaWarnings.IncompleteTransaction(
|
||||
currency = currency,
|
||||
amount = requirements.amount,
|
||||
currencySymbol = requirements.currencySymbol,
|
||||
currencyDecimals = requirements.currencyDecimals,
|
||||
)
|
||||
|
||||
null -> null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,7 +167,10 @@ internal class CurrenciesStatusesLceOperations(
|
|||
private fun getQuotes(tokensIds: NonEmptySet<CryptoCurrency.ID>): Flow<Either<TokenListError, Set<Quote>>> {
|
||||
return quotesRepository.getQuotesUpdates(tokensIds)
|
||||
.map<Set<Quote>, Either<TokenListError, Set<Quote>>> { it.right() }
|
||||
.catch { emit(TokenListError.DataError(it).left()) }
|
||||
.retryWhen { cause, _ ->
|
||||
emit(TokenListError.DataError(cause).left())
|
||||
true
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -348,8 +348,9 @@ internal class CurrenciesStatusesOperations(
|
|||
.map<Set<Quote>, Either<Error, Set<Quote>>> { quotes ->
|
||||
if (quotes.isEmpty()) Error.EmptyQuotes.left() else quotes.right()
|
||||
}
|
||||
.catch {
|
||||
emit(Error.DataError(it).left())
|
||||
.retryWhen { cause, _ ->
|
||||
emit(Error.DataError(cause).left())
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.tokens.repository
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.CurrencyAmount
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -38,6 +39,7 @@ interface CurrencyChecksRepository {
|
|||
suspend fun checkUtxoAmountLimit(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
currency: CryptoCurrency,
|
||||
amount: BigDecimal,
|
||||
fee: BigDecimal,
|
||||
): UtxoAmountLimit?
|
||||
|
|
|
|||
|
|
@ -17,4 +17,16 @@ sealed class AssetRequirementsCondition {
|
|||
val feeCurrencySymbol: String,
|
||||
val decimals: Int,
|
||||
) : AssetRequirementsCondition()
|
||||
|
||||
/**
|
||||
* The exact value of the fee for this type of condition is stored in `feeAmount`.
|
||||
*/
|
||||
data class IncompleteTransaction(
|
||||
val amount: BigDecimal,
|
||||
val currencySymbol: String,
|
||||
val currencyDecimals: Int,
|
||||
val feeAmount: BigDecimal,
|
||||
val feeCurrencySymbol: String,
|
||||
val feeCurrencyDecimals: Int,
|
||||
) : AssetRequirementsCondition()
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.domain.transaction.error
|
||||
|
||||
sealed class IncompleteTransactionError {
|
||||
data class DataError(val message: String?) : IncompleteTransactionError()
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ class AssociateAssetUseCase(
|
|||
|
||||
catch(
|
||||
block = {
|
||||
when (val result = walletManagersFacade.associateAsset(userWalletId, currency, signer)) {
|
||||
when (val result = walletManagersFacade.fulfillRequirements(userWalletId, currency, signer)) {
|
||||
is SimpleResult.Failure -> raise(AssociateAssetError.DataError(result.error.customMessage))
|
||||
SimpleResult.Success -> Unit
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.transaction.error.IncompleteTransactionError
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
class DismissIncompleteTransactionUseCase(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
): Either<IncompleteTransactionError, Unit> {
|
||||
return either {
|
||||
catch(
|
||||
block = {
|
||||
when (val result = walletManagersFacade.discardRequirements(userWalletId, currency)) {
|
||||
is SimpleResult.Failure -> raise(
|
||||
IncompleteTransactionError.DataError(result.error.customMessage),
|
||||
)
|
||||
SimpleResult.Success -> Unit
|
||||
}
|
||||
},
|
||||
catch = { error -> IncompleteTransactionError.DataError(error.message) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.transaction.error.IncompleteTransactionError
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
class RetryIncompleteTransactionUseCase(
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
): Either<IncompleteTransactionError, Unit> {
|
||||
return either {
|
||||
val signer = cardSdkConfigRepository.getCommonSigner(cardId = null)
|
||||
|
||||
catch(
|
||||
block = {
|
||||
when (val result = walletManagersFacade.fulfillRequirements(userWalletId, currency, signer)) {
|
||||
is SimpleResult.Failure -> raise(
|
||||
IncompleteTransactionError.DataError(result.error.customMessage),
|
||||
)
|
||||
SimpleResult.Success -> Unit
|
||||
}
|
||||
},
|
||||
catch = { error -> IncompleteTransactionError.DataError(error.message) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -49,9 +49,8 @@ internal class OnrampStateFactory(
|
|||
fun getOnrampErrorState(onrampError: OnrampError): OnrampMainComponentUM {
|
||||
return when (onrampError) {
|
||||
OnrampError.PairsNotFound -> getNoPairsErrorState()
|
||||
is OnrampError.DataError,
|
||||
is OnrampError.DomainError,
|
||||
-> getErrorState()
|
||||
is OnrampError.DataError -> getErrorState(onrampError.code)
|
||||
is OnrampError.DomainError -> getErrorState()
|
||||
is OnrampError.AmountError.TooBigError,
|
||||
is OnrampError.AmountError.TooSmallError,
|
||||
OnrampError.RedirectError.VerificationFailed,
|
||||
|
|
@ -75,7 +74,7 @@ internal class OnrampStateFactory(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getErrorState(): OnrampMainComponentUM {
|
||||
private fun getErrorState(errorCode: String? = null): OnrampMainComponentUM {
|
||||
val state = currentStateProvider()
|
||||
val endButton = state.topBarConfig.endButtonUM.copy(enabled = true)
|
||||
|
||||
|
|
@ -87,10 +86,16 @@ internal class OnrampStateFactory(
|
|||
amountFieldModel = state.amountBlockState.amountFieldModel.copy(isError = true),
|
||||
),
|
||||
providerBlockState = OnrampProviderBlockUM.Empty,
|
||||
errorNotification = NotificationUM.Warning.OnrampErrorNotification(onrampIntents::onRefresh),
|
||||
errorNotification = NotificationUM.Warning.OnrampErrorNotification(
|
||||
errorCode = errorCode,
|
||||
onRefresh = onrampIntents::onRefresh,
|
||||
),
|
||||
)
|
||||
is OnrampMainComponentUM.InitialLoading -> state.copy(
|
||||
errorNotification = NotificationUM.Warning.OnrampErrorNotification(onrampIntents::onRefresh),
|
||||
errorNotification = NotificationUM.Warning.OnrampErrorNotification(
|
||||
errorCode = errorCode,
|
||||
onRefresh = onrampIntents::onRefresh,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,6 +202,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
val bestProvider = quote as? OnrampQuote.Data
|
||||
val isMultipleQuotes = !quotes.isSingleItem()
|
||||
val isOtherQuotesHasData = quotes
|
||||
.filter { it.paymentMethod == quote.paymentMethod }
|
||||
.filterNot { it == bestProvider }
|
||||
.any { it is OnrampQuote.Data }
|
||||
val hasBestProvider = isMultipleQuotes && isOtherQuotesHasData
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ internal class UpdateTokenItemsTransformer(
|
|||
private fun createAvailableTokenItemStateConverter(): TokenItemStateConverter {
|
||||
return TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
subtitleStateProvider = { createSubtitleState(status = it, isAvailable = true) },
|
||||
subtitle2StateProvider = ::createSubtitle2State,
|
||||
fiatAmountStateProvider = { createFiatAmountStateProvider(status = it, isAvailable = true) },
|
||||
onItemClick = onItemClick,
|
||||
|
|
@ -87,22 +88,24 @@ internal class UpdateTokenItemsTransformer(
|
|||
isAvailable = false,
|
||||
)
|
||||
},
|
||||
subtitleStateProvider = {
|
||||
when (it.value) {
|
||||
CryptoCurrencyStatus.Loading -> TokenItemState.SubtitleState.Loading
|
||||
else -> {
|
||||
TokenItemState.SubtitleState.TextContent(
|
||||
value = stringReference(value = it.currency.symbol),
|
||||
isAvailable = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
subtitleStateProvider = { createSubtitleState(status = it, isAvailable = false) },
|
||||
subtitle2StateProvider = ::createSubtitle2State,
|
||||
fiatAmountStateProvider = { createFiatAmountStateProvider(status = it, isAvailable = false) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSubtitleState(status: CryptoCurrencyStatus, isAvailable: Boolean): TokenItemState.SubtitleState {
|
||||
return when (status.value) {
|
||||
CryptoCurrencyStatus.Loading -> TokenItemState.SubtitleState.Loading
|
||||
else -> {
|
||||
TokenItemState.SubtitleState.TextContent(
|
||||
value = stringReference(value = status.currency.symbol),
|
||||
isAvailable = isAvailable,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSubtitle2State(status: CryptoCurrencyStatus): TokenItemState.Subtitle2State? {
|
||||
return when (status.value) {
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer
|
|||
import com.tangem.features.onramp.utils.UpdateSearchBarCallbacksTransformer
|
||||
import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
|
@ -133,17 +136,25 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun List<CryptoCurrencyStatus>.filterByAvailability(): Map<Boolean, List<CryptoCurrencyStatus>> {
|
||||
return groupBy { status ->
|
||||
val isAvailable = checkAvailabilityByOperation(status = status)
|
||||
val isNotMissedDerivation = status.value !is CryptoCurrencyStatus.MissedDerivation
|
||||
return coroutineScope {
|
||||
map { status ->
|
||||
async {
|
||||
val isAvailable = checkAvailabilityByOperation(status = status)
|
||||
val isNotMissedDerivation = status.value !is CryptoCurrencyStatus.MissedDerivation
|
||||
val isNotLoading = status.value !is CryptoCurrencyStatus.Loading
|
||||
|
||||
val isNotUnreachable = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> true // unreachable state is available for Buy operation
|
||||
OnrampOperation.SELL -> status.value !is CryptoCurrencyStatus.Unreachable
|
||||
OnrampOperation.SWAP -> status.value !is CryptoCurrencyStatus.Unreachable
|
||||
val isNotUnreachable = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> true // unreachable state is available for Buy operation
|
||||
OnrampOperation.SELL -> status.value !is CryptoCurrencyStatus.Unreachable
|
||||
OnrampOperation.SWAP -> status.value !is CryptoCurrencyStatus.Unreachable
|
||||
}
|
||||
|
||||
status to (isAvailable && isNotMissedDerivation && isNotLoading && isNotUnreachable)
|
||||
}
|
||||
}
|
||||
|
||||
isAvailable && isNotMissedDerivation && isNotUnreachable
|
||||
.awaitAll()
|
||||
.groupBy(Pair<CryptoCurrencyStatus, Boolean>::second)
|
||||
.mapValues { it.value.map(Pair<CryptoCurrencyStatus, Boolean>::first) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,9 +30,7 @@ internal class SwapRouter(
|
|||
* If select token screen is not in stack, then just pop to previous screen.
|
||||
* Otherwise, pop to previous screen that was before select token screen.
|
||||
*/
|
||||
if (selectTokensIndex == null) {
|
||||
router.pop()
|
||||
} else {
|
||||
if (currentScreen == SwapNavScreen.Success && selectTokensIndex != null) {
|
||||
// find previous screen that was before select token
|
||||
val prevRoute = router.stack.getOrNull(index = selectTokensIndex - 1)
|
||||
|
||||
|
|
@ -41,6 +39,8 @@ internal class SwapRouter(
|
|||
} else {
|
||||
router.pop()
|
||||
}
|
||||
} else {
|
||||
router.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,12 @@ interface TokenDetailsClickIntents {
|
|||
|
||||
fun onAssociateClick()
|
||||
|
||||
fun onRetryIncompleteTransactionClick()
|
||||
|
||||
fun onDismissIncompleteTransactionClick()
|
||||
|
||||
fun onConfirmDismissIncompleteTransactionClick()
|
||||
|
||||
fun onStakeBannerClick()
|
||||
|
||||
fun onBalanceSelect(config: TokenBalanceSegmentedButtonConfig)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ 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.transformers.CloseBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.DisableActionTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
|
|
@ -59,7 +58,6 @@ import kotlinx.coroutines.flow.take
|
|||
import kotlinx.coroutines.launch
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
interface WalletCurrencyActionsClickIntents {
|
||||
|
||||
|
|
@ -456,8 +454,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
val userCountry = getUserCountryUseCase().getOrNull()
|
||||
if (userCountry is UserCountry.Russia) {
|
||||
handleError(
|
||||
userWalletId = userWalletId,
|
||||
actionKClass = WalletManageButton.Sell::class,
|
||||
alertState = WalletAlertState.SellingRegionalRestriction,
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSell,
|
||||
)
|
||||
|
|
@ -468,8 +464,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
val selectedWallet = stateHolder.getSelectedWallet().walletCardState as? WalletCardState.Content
|
||||
if (selectedWallet?.isZeroBalance == true) {
|
||||
handleError(
|
||||
userWalletId = userWalletId,
|
||||
actionKClass = WalletManageButton.Sell::class,
|
||||
alertState = WalletAlertState.InsufficientBalanceForSelling,
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSell,
|
||||
)
|
||||
|
|
@ -478,10 +472,8 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
|
||||
onMultiWalletActionClick(
|
||||
userWalletId = userWalletId,
|
||||
statusFlow = rampStateManager.getSellInitializationStatus(),
|
||||
route = AppRoute.SellCrypto(userWalletId = userWalletId),
|
||||
actionKClass = WalletManageButton.Sell::class,
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSell,
|
||||
)
|
||||
}
|
||||
|
|
@ -493,8 +485,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
if (tokenListState.items.count { it is TokensListItemUM.Token } < 2) {
|
||||
handleError(
|
||||
userWalletId = userWalletId,
|
||||
actionKClass = WalletManageButton.Swap::class,
|
||||
alertState = WalletAlertState.InsufficientTokensCountForSwapping,
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSwap,
|
||||
)
|
||||
|
|
@ -503,20 +493,16 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
|
||||
onMultiWalletActionClick(
|
||||
userWalletId = userWalletId,
|
||||
statusFlow = rampStateManager.getSwapInitializationStatus(userWalletId),
|
||||
route = AppRoute.SwapCrypto(userWalletId = userWalletId),
|
||||
actionKClass = WalletManageButton.Swap::class,
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSwap,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onMultiWalletBuyClick(userWalletId: UserWalletId) {
|
||||
onMultiWalletActionClick(
|
||||
userWalletId = userWalletId,
|
||||
statusFlow = rampStateManager.getBuyInitializationStatus(),
|
||||
route = AppRoute.BuyCrypto(userWalletId = userWalletId),
|
||||
actionKClass = WalletManageButton.Buy::class,
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonBuy,
|
||||
)
|
||||
}
|
||||
|
|
@ -620,22 +606,14 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onMultiWalletActionClick(
|
||||
userWalletId: UserWalletId,
|
||||
statusFlow: Flow<Lce<Throwable, Any>>,
|
||||
route: AppRoute,
|
||||
actionKClass: KClass<out WalletManageButton>,
|
||||
eventCreator: (AnalyticsParam.Status) -> MainScreenAnalyticsEvent,
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
statusFlow.foldStatus(
|
||||
onContent = { handleContent(route, eventCreator) },
|
||||
onError = {
|
||||
handleError(
|
||||
userWalletId = userWalletId,
|
||||
actionKClass = actionKClass,
|
||||
eventCreator = eventCreator,
|
||||
)
|
||||
},
|
||||
onError = { handleError(eventCreator = eventCreator) },
|
||||
onLoading = { handleLoading(eventCreator) },
|
||||
)
|
||||
}
|
||||
|
|
@ -662,17 +640,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleError(
|
||||
userWalletId: UserWalletId,
|
||||
actionKClass: KClass<out WalletManageButton>,
|
||||
alertState: WalletAlertState = WalletAlertState.UnavailableOperation,
|
||||
eventCreator: (AnalyticsParam.Status) -> MainScreenAnalyticsEvent,
|
||||
) {
|
||||
analyticsEventHandler.send(event = eventCreator(AnalyticsParam.Status.Error))
|
||||
|
||||
stateHolder.update(
|
||||
transformer = DisableActionTransformer(userWalletId = userWalletId, actionClass = actionKClass),
|
||||
)
|
||||
|
||||
walletEventSender.send(event = WalletEvent.ShowAlert(state = alertState))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ markdownComposeView = "0.5.4"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "release-app_5.19-881"
|
||||
tangemBlockchainSdk = "release-app_5.19-882"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "release-app_5.19-414"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
|
|
@ -21,13 +21,15 @@ internal class DefaultBlockchainDataStorage(
|
|||
return appPreferencesStore.getSyncOrNull(key = stringPreferencesKey(name = key))
|
||||
}
|
||||
|
||||
override suspend fun remove(key: String) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun store(key: String, value: String) {
|
||||
appPreferencesStore.edit {
|
||||
it[stringPreferencesKey(key)] = value
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun remove(key: String) {
|
||||
appPreferencesStore.edit {
|
||||
it.remove(stringPreferencesKey(key))
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue