Updated on 2026-08-14

This commit is contained in:
Tangem 2024-05-23 14:02:59 +05:00
parent f49a2bc054
commit d630852c29
28 changed files with 406 additions and 10 deletions

View file

@ -185,6 +185,7 @@ internal object TokensDomainModule {
@ViewModelScoped
fun provideGetCryptoCurrencyActionsUseCase(
rampStateManager: RampStateManager,
walletManagersFacade: WalletManagersFacade,
marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
@ -193,6 +194,7 @@ internal object TokensDomainModule {
): GetCryptoCurrencyActionsUseCase {
return GetCryptoCurrencyActionsUseCase(
rampManager = rampStateManager,
walletManagersFacade = walletManagersFacade,
marketCryptoCurrencyRepository = marketCryptoCurrencyRepository,
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,

View file

@ -2,6 +2,8 @@ package com.tangem.tap.di.domain
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.usecase.*
@ -40,6 +42,22 @@ internal object TransactionDomainModule {
)
}
@Provides
@ViewModelScoped
fun provideAssociateAssetUseCase(
cardSdkConfigRepository: CardSdkConfigRepository,
walletManagersFacade: WalletManagersFacade,
currenciesRepository: CurrenciesRepository,
networksRepository: NetworksRepository,
): AssociateAssetUseCase {
return AssociateAssetUseCase(
cardSdkConfigRepository = cardSdkConfigRepository,
walletManagersFacade = walletManagersFacade,
currenciesRepository = currenciesRepository,
networksRepository = networksRepository,
)
}
@Provides
@ViewModelScoped
fun provideCreateTransactionUseCase(transactionRepository: TransactionRepository): CreateTransactionUseCase {

View file

@ -17,6 +17,7 @@ dependencies {
implementation(projects.domain.demo)
implementation(projects.domain.models)
implementation(projects.domain.tokens.models)
implementation(projects.domain.transaction.models)
implementation(projects.domain.txhistory.models)
implementation(projects.domain.wallets.models)
/** Tangem libraries */

View file

@ -13,6 +13,7 @@ import com.tangem.blockchain.common.address.EstimationFeeAddressFactory
import com.tangem.blockchain.common.pagination.Page
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.common.trustlines.AssetRequirementsManager
import com.tangem.blockchain.common.txhistory.TransactionHistoryRequest
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
@ -26,6 +27,7 @@ import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.txhistory.models.PaginationWrapper
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryState
@ -57,6 +59,8 @@ class DefaultWalletManagersFacade(
private val txHistoryStateConverter by lazy { SdkTransactionHistoryStateConverter() }
private val txHistoryItemConverter by lazy { SdkTransactionHistoryItemConverter(assetReader, moshi) }
private val sdkPageConverter by lazy { SdkPageConverter() }
private val cryptoCurrencyTypeConverter by lazy { CryptoCurrencyTypeConverter() }
private val requirementsConditionConverter by lazy { SdkRequirementsConditionConverter() }
private val estimationFeeAddressFactory by lazy { EstimationFeeAddressFactory() }
override suspend fun update(
@ -575,6 +579,34 @@ class DefaultWalletManagersFacade(
)
}
override suspend fun getAssetRequirements(
userWalletId: UserWalletId,
currency: CryptoCurrency,
): AssetRequirementsCondition? {
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
val currencyType = cryptoCurrencyTypeConverter.convert(currency)
if (walletManager !is AssetRequirementsManager || !walletManager.hasRequirements(currencyType)) return null
val condition = walletManager.requirementsCondition(currencyType) ?: return null
return requirementsConditionConverter.convert(condition)
}
override suspend fun associateAsset(
userWalletId: UserWalletId,
currency: CryptoCurrency,
signer: CommonSigner,
): SimpleResult {
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
val currencyType = cryptoCurrencyTypeConverter.convert(currency)
if (walletManager !is AssetRequirementsManager) {
return SimpleResult.Failure(
BlockchainSdkError.CustomError("WalletManager is not implemented AssetRequirementsManager"),
)
}
return walletManager.fulfillRequirements(currencyType, signer)
}
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set<CryptoCurrency.Token>) {
if (tokens.isEmpty()) return

View file

@ -13,6 +13,7 @@ import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.txhistory.models.PaginationWrapper
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryState
@ -240,4 +241,16 @@ interface WalletManagersFacade {
decimals: Int,
id: String? = null,
): BigDecimal
/**
* Get requirements for asset(currency)
* @return null if there's no requirement, otherwise [AssetRequirementsCondition].
*/
suspend fun getAssetRequirements(userWalletId: UserWalletId, currency: CryptoCurrency): AssetRequirementsCondition?
suspend fun associateAsset(
userWalletId: UserWalletId,
currency: CryptoCurrency,
signer: CommonSigner,
): SimpleResult
}

View file

@ -0,0 +1,23 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.common.CryptoCurrencyType
import com.tangem.blockchain.common.Token
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.utils.converter.Converter
internal class CryptoCurrencyTypeConverter : Converter<CryptoCurrency, CryptoCurrencyType> {
override fun convert(value: CryptoCurrency): CryptoCurrencyType {
return when (value) {
is CryptoCurrency.Coin -> CryptoCurrencyType.Coin
is CryptoCurrency.Token -> CryptoCurrencyType.Token(
info = Token(
name = value.name,
symbol = value.symbol,
contractAddress = value.contractAddress,
decimals = value.decimals,
id = value.id.rawCurrencyId,
),
)
}
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.common.trustlines.AssetRequirementsCondition as SdkRequirementsCondition
internal class SdkRequirementsConditionConverter : Converter<SdkRequirementsCondition, AssetRequirementsCondition> {
override fun convert(value: SdkRequirementsCondition): AssetRequirementsCondition {
return when (value) {
SdkRequirementsCondition.PaidTransaction -> AssetRequirementsCondition.PaidTransaction
is SdkRequirementsCondition.PaidTransactionWithFee -> AssetRequirementsCondition.PaidTransactionWithFee(
feeAmount = requireNotNull(value.feeAmount.value),
feeCurrencySymbol = value.feeAmount.currencySymbol,
decimals = value.feeAmount.decimals,
)
}
}
}

View file

@ -17,6 +17,7 @@ dependencies {
implementation(projects.libs.blockchainSdk)
implementation(projects.domain.tokens.models)
implementation(projects.domain.txhistory.models)
implementation(projects.domain.transaction.models)
implementation(projects.domain.wallets.models)
implementation(projects.domain.appCurrency.models)
implementation(projects.domain.settings)

View file

@ -0,0 +1,18 @@
package com.tangem.domain.tokens.model.warnings
import com.tangem.domain.tokens.model.CryptoCurrency
import java.math.BigDecimal
sealed class HederaWarnings : CryptoCurrencyWarning() {
abstract val currency: CryptoCurrency
data class AssociateWarning(override val currency: CryptoCurrency) : HederaWarnings()
data class AssociateWarningWithFee(
override val currency: CryptoCurrency,
val fee: BigDecimal,
val feeCurrencySymbol: String,
val feeCurrencyDecimals: Int,
) : HederaWarnings()
}

View file

@ -72,4 +72,9 @@ sealed class TokenScreenAnalyticsEvent(
event = "Token Bought",
params = mapOf("Token" to token),
)
class Associate(tokenSymbol: String, blockchain: String) : TokenScreenAnalyticsEvent(
event = "Button - Token Trustline",
params = mapOf("Token" to tokenSymbol, "Blockchain" to blockchain),
)
}

View file

@ -8,6 +8,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.isNullOrZero
@ -22,6 +23,7 @@ import kotlinx.coroutines.flow.*
@Suppress("LongParameterList")
class GetCryptoCurrencyActionsUseCase(
private val rampManager: RampStateManager,
private val walletManagersFacade: WalletManagersFacade,
private val marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
private val currenciesRepository: CurrenciesRepository,
private val quotesRepository: QuotesRepository,
@ -30,7 +32,10 @@ class GetCryptoCurrencyActionsUseCase(
) {
@OptIn(ExperimentalCoroutinesApi::class)
operator fun invoke(userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus): Flow<TokenActionsState> {
suspend operator fun invoke(
userWallet: UserWallet,
cryptoCurrencyStatus: CryptoCurrencyStatus,
): Flow<TokenActionsState> {
val operations = CurrenciesStatusesOperations(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
@ -38,7 +43,7 @@ class GetCryptoCurrencyActionsUseCase(
userWalletId = userWallet.walletId,
)
val networkId = cryptoCurrencyStatus.currency.network.id
val requirements = walletManagersFacade.getAssetRequirements(userWallet.walletId, cryptoCurrencyStatus.currency)
return flow {
val networkFlow = if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
operations.getNetworkCoinForSingleWalletWithTokenFlow(networkId)
@ -53,6 +58,7 @@ class GetCryptoCurrencyActionsUseCase(
userWallet = userWallet,
coinStatus = maybeCoinStatus.getOrNull(),
cryptoCurrencyStatus = cryptoCurrencyStatus,
needAssociateAsset = requirements != null,
)
}
@ -64,6 +70,7 @@ class GetCryptoCurrencyActionsUseCase(
userWallet: UserWallet,
coinStatus: CryptoCurrencyStatus?,
cryptoCurrencyStatus: CryptoCurrencyStatus,
needAssociateAsset: Boolean,
): TokenActionsState {
return TokenActionsState(
walletId = userWallet.walletId,
@ -72,6 +79,7 @@ class GetCryptoCurrencyActionsUseCase(
userWallet = userWallet,
coinStatus = coinStatus,
cryptoCurrencyStatus = cryptoCurrencyStatus,
needAssociateAsset = needAssociateAsset,
),
)
}
@ -85,13 +93,14 @@ class GetCryptoCurrencyActionsUseCase(
userWallet: UserWallet,
coinStatus: CryptoCurrencyStatus?,
cryptoCurrencyStatus: CryptoCurrencyStatus,
needAssociateAsset: Boolean,
): List<TokenActionsState.ActionState> {
val cryptoCurrency = cryptoCurrencyStatus.currency
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.MissedDerivation) {
return listOf(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None))
}
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Unreachable) {
return getActionsForUnreachableCurrency(cryptoCurrencyStatus)
return getActionsForUnreachableCurrency(cryptoCurrencyStatus, needAssociateAsset)
}
val activeList = mutableListOf<TokenActionsState.ActionState>()
@ -104,7 +113,12 @@ class GetCryptoCurrencyActionsUseCase(
// receive
if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) {
activeList.add(TokenActionsState.ActionState.Receive(ScenarioUnavailabilityReason.None))
val scenario = if (needAssociateAsset) {
ScenarioUnavailabilityReason.UnassociatedAsset
} else {
ScenarioUnavailabilityReason.None
}
activeList.add(TokenActionsState.ActionState.Receive(scenario))
}
// send
@ -190,6 +204,7 @@ class GetCryptoCurrencyActionsUseCase(
private fun getActionsForUnreachableCurrency(
cryptoCurrencyStatus: CryptoCurrencyStatus,
needAssociateAsset: Boolean,
): List<TokenActionsState.ActionState> {
val actionsList = mutableListOf<TokenActionsState.ActionState>()
@ -212,7 +227,12 @@ class GetCryptoCurrencyActionsUseCase(
actionsList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.Unreachable))
if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) {
actionsList.add(TokenActionsState.ActionState.Receive(ScenarioUnavailabilityReason.None))
val scenario = if (needAssociateAsset) {
ScenarioUnavailabilityReason.UnassociatedAsset
} else {
ScenarioUnavailabilityReason.None
}
actionsList.add(TokenActionsState.ActionState.Receive(scenario))
}
actionsList.add(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None))
return actionsList

View file

@ -6,8 +6,10 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.FeePaidCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.tokens.model.warnings.HederaWarnings
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.*
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.swap.domain.api.SwapRepository
@ -76,6 +78,7 @@ class GetCurrencyWarningsUseCase(
getNetworkUnavailableWarning(currencyStatus),
getNetworkNoAccountWarning(currencyStatus),
getBeaconChainShutdownWarning(currency.network.id),
getAssetRequirementsWarning(userWalletId = userWalletId, currency = currency),
)
}.flowOn(dispatchers.io)
}
@ -267,6 +270,22 @@ class GetCurrencyWarningsUseCase(
return if (BlockchainUtils.isBeaconChain(networkId.value)) CryptoCurrencyWarning.BeaconChainShutdown else null
}
private suspend fun getAssetRequirementsWarning(
userWalletId: UserWalletId,
currency: CryptoCurrency,
): 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,
)
null -> null
}
}
private fun BigDecimal?.isZero(): Boolean {
return this?.signum() == 0
}

View file

@ -21,6 +21,8 @@ sealed class ScenarioUnavailabilityReason {
data object Unreachable : ScenarioUnavailabilityReason()
data object UnassociatedAsset : ScenarioUnavailabilityReason()
enum class WithdrawalScenario {
SELL, SEND
}

1
domain/transaction/models/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,4 @@
plugins {
alias(deps.plugins.kotlin.jvm)
id("configuration")
}

View file

@ -0,0 +1,20 @@
package com.tangem.domain.transaction.models
import java.math.BigDecimal
sealed class AssetRequirementsCondition {
/**
* The exact value of the fee for this type of condition is unknown.
*/
data object PaidTransaction : AssetRequirementsCondition()
/**
* The exact value of the fee for this type of condition is stored in `feeAmount`.
*/
data class PaidTransactionWithFee(
val feeAmount: BigDecimal,
val feeCurrencySymbol: String,
val decimals: Int,
) : AssetRequirementsCondition()
}

View file

@ -0,0 +1,9 @@
package com.tangem.domain.transaction.error
import com.tangem.domain.tokens.model.CryptoCurrency
sealed class AssociateAssetError {
data class NotEnoughBalance(val feeCurrency: CryptoCurrency) : AssociateAssetError()
data class DataError(val message: String?) : AssociateAssetError()
}

View file

@ -0,0 +1,63 @@
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.tokens.model.CryptoCurrencyAmountStatus
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.transaction.error.AssociateAssetError
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.isNullOrZero
class AssociateAssetUseCase(
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val walletManagersFacade: WalletManagersFacade,
private val currenciesRepository: CurrenciesRepository,
private val networksRepository: NetworksRepository,
) {
suspend operator fun invoke(
userWalletId: UserWalletId,
currency: CryptoCurrency,
): Either<AssociateAssetError, Unit> {
return either {
val networkCoin = currenciesRepository.getNetworkCoin(
userWalletId = userWalletId,
networkId = currency.network.id,
derivationPath = currency.network.derivationPath,
)
if (isBalanceZero(userWalletId, networkCoin)) {
raise(AssociateAssetError.NotEnoughBalance(networkCoin))
}
val signer = cardSdkConfigRepository.getCommonSigner(cardId = null)
catch(
block = {
when (val result = walletManagersFacade.associateAsset(userWalletId, currency, signer)) {
is SimpleResult.Failure -> raise(AssociateAssetError.DataError(result.error.message))
SimpleResult.Success -> Unit
}
},
catch = { error -> AssociateAssetError.DataError(error.message) },
)
}
}
private suspend fun isBalanceZero(userWalletId: UserWalletId, currency: CryptoCurrency): Boolean {
val networkStatus = networksRepository.getNetworkStatusesSync(
userWalletId = userWalletId,
networks = setOf(currency.network),
).find { it.network == currency.network }
val networkCoinAmountStatus = (networkStatus?.value as? NetworkStatus.Verified)
?.amounts
?.get(currency.id)
return networkCoinAmountStatus is CryptoCurrencyAmountStatus.Loaded &&
networkCoinAmountStatus.value.isNullOrZero()
}
}

View file

@ -74,6 +74,7 @@ dependencies {
implementation(projects.domain.wallets.models)
implementation(projects.domain.balanceHiding)
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.transaction)
/** Temp dependency to swap domain */
implementation(projects.features.swap.domain)

View file

@ -42,6 +42,7 @@ internal class TokenDetailsNotificationsAnalyticsSender(
is TokenDetailsNotification.RentInfo,
is TokenDetailsNotification.SwapPromo,
is TokenDetailsNotification.NetworkShutdown,
is TokenDetailsNotification.HederaAssociateWarning,
-> null
}
}

View file

@ -94,5 +94,22 @@ internal data class TokenDetailsDialogConfig(
onClick = onConfirmClick,
)
}
data class ErrorDialogConfig(
val text: TextReference,
val onConfirmClick: () -> Unit,
) : DialogContentConfig() {
override val title = null
override val message: TextReference = text
override val cancelButtonConfig = null
override val confirmButtonConfig: ButtonConfig = ButtonConfig(
text = TextReference.Res(R.string.common_ok),
onClick = onConfirmClick,
)
}
}
}

View file

@ -117,8 +117,7 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
),
),
iconResId = currency.networkIconResId,
buttonsState =
NotificationConfig.ButtonsState.SecondaryButtonConfig(
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(
id = R.string.common_buy_currency,
formatArgs = wrappedList(
@ -176,4 +175,26 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
title = title,
subtitle = subtitle,
)
data class HederaAssociateWarning(
private val currency: CryptoCurrency,
private val fee: String?,
private val feeCurrencySymbol: String?,
private val onAssociateClick: () -> Unit,
) : Warning(
title = resourceReference(R.string.warning_hedera_missing_token_association_title),
subtitle = if (fee != null && feeCurrencySymbol != null) {
resourceReference(
id = R.string.warning_hedera_missing_token_association_message,
formatArgs = wrappedList(fee, feeCurrencySymbol),
)
} else {
resourceReference(R.string.warning_hedera_missing_token_association_message_brief)
},
iconResId = currency.networkIconResId,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.warning_hedera_missing_token_association_button_title),
onClick = onAssociateClick,
),
)
}

View file

@ -6,6 +6,7 @@ import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.extensions.resourceReference
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.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.*
@ -30,6 +31,13 @@ internal class TokenDetailsNotificationConverter(
return newNotifications.toImmutableList()
}
fun removeHederaAssociateWarning(currentState: TokenDetailsState): ImmutableList<TokenDetailsNotification> {
val newNotifications = currentState.notifications.toMutableList()
newNotifications.removeBy { it is HederaAssociateWarning }
return newNotifications.toImmutableList()
}
@Suppress("LongMethod")
private fun mapToNotification(warning: CryptoCurrencyWarning): TokenDetailsNotification {
return when (warning) {
is CryptoCurrencyWarning.BalanceNotEnoughForFee -> NetworkFeeWithBuyButton(
@ -86,6 +94,23 @@ internal class TokenDetailsNotificationConverter(
title = resourceReference(R.string.warning_beacon_chain_retirement_title),
subtitle = resourceReference(R.string.warning_beacon_chain_retirement_content),
)
is HederaWarnings.AssociateWarning -> HederaAssociateWarning(
currency = warning.currency,
fee = null,
feeCurrencySymbol = null,
onAssociateClick = clickIntents::onAssociateClick,
)
is HederaWarnings.AssociateWarningWithFee -> HederaAssociateWarning(
currency = warning.currency,
fee = BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = warning.fee,
cryptoCurrency = "",
decimals = warning.feeCurrencyDecimals,
),
feeCurrencySymbol = warning.feeCurrencySymbol,
onAssociateClick = clickIntents::onAssociateClick,
)
}
}

View file

@ -10,7 +10,9 @@ import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.CardTypesResolver
@ -35,7 +37,6 @@ import com.tangem.utils.Provider
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import java.lang.IllegalArgumentException
@Suppress("TooManyFunctions", "LargeClass")
internal class TokenDetailsStateFactory(
@ -187,6 +188,19 @@ internal class TokenDetailsStateFactory(
)
}
fun getStateWithErrorDialog(text: TextReference): TokenDetailsState {
return currentStateProvider().copy(
dialogConfig = TokenDetailsDialogConfig(
isShow = true,
onDismissRequest = clickIntents::onDismissDialog,
content = TokenDetailsDialogConfig.DialogContentConfig.ErrorDialogConfig(
text = text,
onConfirmClick = clickIntents::onDismissDialog,
),
),
)
}
fun getRefreshingState(): TokenDetailsState {
return refreshStateConverter.convert(true)
}
@ -256,6 +270,11 @@ internal class TokenDetailsStateFactory(
return state.copy(notifications = notificationConverter.removeRentInfo(state))
}
fun getStateWithRemovedHederaAssociateNotification(): TokenDetailsState {
val state = currentStateProvider()
return state.copy(notifications = notificationConverter.removeHederaAssociateWarning(state))
}
fun getStateWithExchangeStatusBottomSheet(swapTxState: SwapTransactionsState): TokenDetailsState {
return currentStateProvider().copy(
bottomSheetConfig = TangemBottomSheetConfig(
@ -379,6 +398,9 @@ internal class TokenDetailsStateFactory(
id = R.string.token_button_unavailability_generic_description,
)
}
ScenarioUnavailabilityReason.UnassociatedAsset -> resourceReference(
id = R.string.warning_receive_blocked_hedera_token_association_required_message,
)
ScenarioUnavailabilityReason.None -> {
throw IllegalArgumentException("The unavailability reason must be other than None")
}

View file

@ -53,4 +53,6 @@ interface TokenDetailsClickIntents {
fun onGenerateExtendedKey()
fun onCopyAddress(): TextReference?
fun onAssociateClick()
}

View file

@ -17,6 +17,7 @@ import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.haptic.HapticManager
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.datasource.local.swaptx.SwapTransactionStatusStore
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
@ -38,6 +39,8 @@ import com.tangem.domain.tokens.models.analytics.TokenReceiveAnalyticsEvent
import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent
import com.tangem.domain.tokens.models.analytics.TokenSwapPromoAnalyticsEvent
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.transaction.error.AssociateAssetError
import com.tangem.domain.transaction.usecase.AssociateAssetUseCase
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
@ -99,6 +102,7 @@ internal class TokenDetailsViewModel @Inject constructor(
private val quotesRepository: QuotesRepository,
private val swapTransactionStatusStore: SwapTransactionStatusStore,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val associateAssetUseCase: AssociateAssetUseCase,
private val reduxStateHolder: ReduxStateHolder,
private val analyticsEventsHandler: AnalyticsEventHandler,
private val hapticManager: HapticManager,
@ -216,7 +220,7 @@ internal class TokenDetailsViewModel @Inject constructor(
.launchIn(viewModelScope)
}
private fun updateButtons(currencyStatus: CryptoCurrencyStatus) {
private suspend fun updateButtons(currencyStatus: CryptoCurrencyStatus) {
getCryptoCurrencyActionsUseCase(
userWallet = userWallet,
cryptoCurrencyStatus = currencyStatus,
@ -709,6 +713,36 @@ internal class TokenDetailsViewModel @Inject constructor(
return resourceReference(R.string.wallet_notification_address_copied)
}
override fun onAssociateClick() {
analyticsEventsHandler.send(
TokenScreenAnalyticsEvent.Associate(
tokenSymbol = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
),
)
viewModelScope.launch(dispatchers.io) {
associateAssetUseCase(
userWalletId = userWalletId,
currency = cryptoCurrency,
).fold(
ifLeft = { e ->
when (e) {
is AssociateAssetError.NotEnoughBalance -> {
uiState = stateFactory.getStateWithErrorDialog(
resourceReference(
id = R.string.warning_hedera_token_association_not_enough_hbar_message,
formatArgs = wrappedList(e.feeCurrency.symbol),
),
)
}
is AssociateAssetError.DataError -> Timber.e(e.message)
}
},
ifRight = { uiState = stateFactory.getStateWithRemovedHederaAssociateNotification() },
)
}
}
private fun handleUnavailabilityReason(unavailabilityReason: ScenarioUnavailabilityReason): Boolean {
if (unavailabilityReason == ScenarioUnavailabilityReason.None) return false

View file

@ -492,6 +492,9 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
id = R.string.token_button_unavailability_generic_description,
)
}
ScenarioUnavailabilityReason.UnassociatedAsset -> resourceReference(
id = R.string.warning_receive_blocked_hedera_token_association_required_message,
)
ScenarioUnavailabilityReason.None -> {
throw IllegalArgumentException("The unavailability reason must be other than None")
}

View file

@ -180,6 +180,7 @@ include(":domain:app-theme:models")
include(":domain:balance-hiding")
include(":domain:balance-hiding:models")
include(":domain:transaction")
include(":domain:transaction:models")
include(":domain:analytics")
include(":domain:visa")
include(":domain:onboarding")