Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-13 21:16:46 +05:00
parent 8c9452f46c
commit 71380aec64
18 changed files with 312 additions and 139 deletions

View file

@ -14,6 +14,7 @@ dependencies {
implementation(projects.domain.models)
implementation(projects.domain.nft.models)
implementation(projects.domain.wallets.models)
implementation(projects.domain.account)
/* Project - Core */
implementation(projects.core.decompose)

View file

@ -68,6 +68,8 @@ dependencies {
implementation(projects.domain.nft)
implementation(projects.domain.notifications)
implementation(projects.domain.swap.models)
implementation(projects.domain.account)
implementation(projects.domain.account.status)
/** Compose libraries */

View file

@ -175,6 +175,8 @@ internal class DefaultSendComponent @AssistedInject constructor(
userWalletId = params.userWalletId,
cryptoCurrency = params.currency,
cryptoCurrencyStatusFlow = model.cryptoCurrencyStatusFlow,
accountFlow = model.accountFlow,
isAccountModeFlow = model.isAccountModeFlow,
callback = model,
predefinedValues = model.predefinedValues,
analyticsSendSource = model.analyticsSendSource,
@ -201,6 +203,8 @@ internal class DefaultSendComponent @AssistedInject constructor(
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
cryptoCurrencyStatusFlow = model.cryptoCurrencyStatusFlow,
feeCryptoCurrencyStatusFlow = model.feeCryptoCurrencyStatusFlow,
accountFlow = model.accountFlow,
isAccountModeFlow = model.isAccountModeFlow,
appCurrency = model.appCurrency,
callback = model,
predefinedValues = model.predefinedValues,

View file

@ -11,6 +11,7 @@ import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.transaction.error.GetFeeError
@ -73,6 +74,8 @@ internal class SendConfirmComponent(
cryptoCurrencyStatusFlow = params.cryptoCurrencyStatusFlow,
isBalanceHidingFlow = params.isBalanceHidingFlow,
analyticsSendSource = params.analyticsSendSource,
accountFlow = params.accountFlow,
isAccountModeFlow = params.isAccountModeFlow,
),
onResult = model::onAmountResult,
onClick = model::showEditAmount,
@ -152,6 +155,8 @@ internal class SendConfirmComponent(
val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
val feeCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
val accountFlow: StateFlow<Account.CryptoPortfolio?>,
val isAccountModeFlow: StateFlow<Boolean>,
val appCurrency: AppCurrency,
val callback: ModelCallback,
val currentRoute: Flow<CommonSendRoute>,

View file

@ -14,6 +14,9 @@ import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseBigDecimalOrNull
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
@ -23,6 +26,7 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isMultiCurrency
@ -91,8 +95,11 @@ internal class SendModel @Inject constructor(
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val createTransferTransactionUseCase: CreateTransferTransactionUseCase,
private val getFeeUseCase: GetFeeUseCase,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val sendAmountUpdateTrigger: SendAmountUpdateTrigger,
private val analyticsEventHandler: AnalyticsEventHandler,
private val accountsFeatureToggles: AccountsFeatureToggles,
) : Model(), SendComponentCallback {
private val params: SendComponent.Params = paramsContainer.require()
@ -115,21 +122,26 @@ internal class SendModel @Inject constructor(
val currentRoute = MutableStateFlow(initialRoute)
private val _cryptoCurrencyStatusFlow = MutableStateFlow(
CryptoCurrencyStatus(
params.currency,
value = CryptoCurrencyStatus.Loading,
),
)
val cryptoCurrencyStatusFlow = _cryptoCurrencyStatusFlow.asStateFlow()
val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
field = MutableStateFlow(
CryptoCurrencyStatus(
params.currency,
value = CryptoCurrencyStatus.Loading,
),
)
private val _feeCryptoCurrencyStatusFlow = MutableStateFlow(
CryptoCurrencyStatus(
params.currency,
value = CryptoCurrencyStatus.Loading,
),
)
val feeCryptoCurrencyStatusFlow = _feeCryptoCurrencyStatusFlow.asStateFlow()
val feeCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
field = MutableStateFlow(
CryptoCurrencyStatus(
params.currency,
value = CryptoCurrencyStatus.Loading,
),
)
val accountFlow: StateFlow<Account.CryptoPortfolio?>
field = MutableStateFlow(null)
val isAccountModeFlow: StateFlow<Boolean>
field = MutableStateFlow(false)
var userWallet: UserWallet by Delegates.notNull()
var appCurrency: AppCurrency = AppCurrency.Default
@ -317,13 +329,34 @@ internal class SendModel @Inject constructor(
ifRight = { wallet ->
userWallet = wallet
val isSingleWalletWithToken = wallet is UserWallet.Cold &&
wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
val isMultiCurrency = wallet.isMultiCurrency
getCurrenciesStatusUpdates(
isSingleWalletWithToken = isSingleWalletWithToken,
isMultiCurrency = isMultiCurrency,
)
if (accountsFeatureToggles.isFeatureEnabled) {
getAccountCurrencyStatusUseCase(
userWalletId = params.userWalletId,
currency = cryptoCurrency,
).onEach { (account, cryptoCurrencyStatus) ->
cryptoCurrencyStatusFlow.value = cryptoCurrencyStatus
feeCryptoCurrencyStatusFlow.value = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = params.userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
).getOrNull() ?: cryptoCurrencyStatus
isAccountModeFlow.value = isAccountsModeEnabledUseCase.invokeSync()
accountFlow.value = account
if (params.amount != null) {
router.replaceAll(Confirm)
}
}.flowOn(dispatchers.default)
.launchIn(modelScope)
} else {
val isSingleWalletWithToken = wallet is UserWallet.Cold &&
wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
val isMultiCurrency = wallet.isMultiCurrency
getCurrenciesStatusUpdates(
isSingleWalletWithToken = isSingleWalletWithToken,
isMultiCurrency = isMultiCurrency,
)
}
},
ifLeft = {
Timber.w(it.toString())
@ -352,10 +385,12 @@ internal class SendModel @Inject constructor(
).onEach { maybeCryptoCurrency ->
maybeCryptoCurrency.fold(
ifRight = { cryptoCurrencyStatus ->
onDataLoaded(
currencyStatus = cryptoCurrencyStatus,
feeCurrencyStatus = getFeeCurrencyStatus(cryptoCurrencyStatus, isMultiCurrency),
)
cryptoCurrencyStatusFlow.value = cryptoCurrencyStatus
feeCryptoCurrencyStatusFlow.value = getFeeCurrencyStatus(cryptoCurrencyStatus, isMultiCurrency)
if (params.amount != null) {
router.replaceAll(CommonSendRoute.Confirm)
}
},
ifLeft = {
sendConfirmAlertFactory.getGenericErrorState(
@ -366,7 +401,8 @@ internal class SendModel @Inject constructor(
)
},
)
}.launchIn(modelScope)
}.flowOn(dispatchers.default)
.launchIn(modelScope)
}
private fun getCurrencyStatus(
@ -402,15 +438,6 @@ internal class SendModel @Inject constructor(
}
}
private fun onDataLoaded(currencyStatus: CryptoCurrencyStatus, feeCurrencyStatus: CryptoCurrencyStatus) {
_cryptoCurrencyStatusFlow.value = currencyStatus
_feeCryptoCurrencyStatusFlow.value = feeCurrencyStatus
if (params.amount != null) {
router.replaceAll(CommonSendRoute.Confirm)
}
}
private fun subscribeOnQRScannerResult() {
listenToQrScanningUseCase(SourceType.SEND)
.getOrElse { emptyFlow() }

View file

@ -11,6 +11,9 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.datasource.local.nft.converter.NFTSdkAssetConverter
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.card.common.util.cardTypesResolver
@ -19,6 +22,7 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
@ -71,6 +75,9 @@ internal class NFTSendModel @Inject constructor(
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val alertFactory: SendConfirmAlertFactory,
private val nftSendSuccessTrigger: NFTSendSuccessTrigger,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
) : Model(), SendNFTComponentCallback, NFTSendSuccessComponent.ModelCallback {
val params: NFTSendComponent.Params = paramsContainer.require()
@ -94,6 +101,9 @@ internal class NFTSendModel @Inject constructor(
var feeCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
var appCurrency: AppCurrency = AppCurrency.Default
var account: Account.CryptoPortfolio? = null
var isAccountsMode: Boolean = false
init {
subscribeOnCurrencyStatusUpdates()
initAppCurrency()
@ -176,10 +186,31 @@ internal class NFTSendModel @Inject constructor(
?.firstOrNull { it is CryptoCurrency.Coin && it.network == nftAsset.network }
?: return@launch
getCurrenciesStatusUpdates(
isSingleWalletWithToken = wallet is UserWallet.Cold &&
wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(),
)
if (accountsFeatureToggles.isFeatureEnabled) {
getAccountCurrencyStatusUseCase(
userWalletId,
cryptoCurrency,
).onEach { (maybeAccount, cryptoStatus) ->
account = maybeAccount
isAccountsMode = isAccountsModeEnabledUseCase.invokeSync()
cryptoCurrencyStatus = cryptoStatus
feeCryptoCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = userWalletId,
cryptoCurrencyStatus = cryptoStatus,
).getOrNull() ?: cryptoStatus
if (uiState.value.destinationUM is DestinationUM.Empty) {
router.replaceAll(Destination(isEditMode = false))
}
}.flowOn(dispatchers.default)
.launchIn(modelScope)
} else {
getCurrenciesStatusUpdates(
isSingleWalletWithToken = wallet is UserWallet.Cold &&
wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(),
)
}
},
ifLeft = {
alertFactory.getGenericErrorState(::onFailedTxEmailClick)

View file

@ -2,6 +2,7 @@ package com.tangem.features.send.v2.subcomponents.amount
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
@ -23,6 +24,8 @@ internal sealed class SendAmountComponentParams {
abstract val cryptoCurrency: CryptoCurrency
abstract val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
abstract val isBalanceHidingFlow: StateFlow<Boolean>
abstract val accountFlow: StateFlow<Account.CryptoPortfolio?>
abstract val isAccountModeFlow: StateFlow<Boolean>
data class AmountParams(
override val state: AmountState,
@ -34,6 +37,8 @@ internal sealed class SendAmountComponentParams {
override val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
override val isBalanceHidingFlow: StateFlow<Boolean>,
override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
override val accountFlow: StateFlow<Account.CryptoPortfolio?>,
override val isAccountModeFlow: StateFlow<Boolean>,
val callback: ModelCallback,
val currentRoute: StateFlow<CommonSendRoute>,
) : SendAmountComponentParams()
@ -48,6 +53,8 @@ internal sealed class SendAmountComponentParams {
override val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
override val isBalanceHidingFlow: StateFlow<Boolean>,
override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
override val accountFlow: StateFlow<Account.CryptoPortfolio?>,
override val isAccountModeFlow: StateFlow<Boolean>,
val userWallet: UserWallet,
val blockClickEnableFlow: StateFlow<Boolean>,
) : SendAmountComponentParams()

View file

@ -21,6 +21,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isMultiCurrency
@ -118,42 +119,7 @@ internal class SendAmountModel @Inject constructor(
private fun subscribeOnBalanceHiddenUpdates() {
params.isBalanceHidingFlow.onEach { isBalanceHidden ->
_uiState.update(
AmountBoundaryUpdateTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
maxEnterAmount = maxAmountBoundary,
appCurrency = appCurrency,
isBalanceHidden = params.isBalanceHidingFlow.value,
),
)
}.launchIn(modelScope)
}
private fun subscribeOnCryptoCurrencyStatusFlow() {
params.cryptoCurrencyStatusFlow
.onEach { newCryptoCurrencyStatus ->
cryptoCurrencyStatus = newCryptoCurrencyStatus
maxAmountBoundary = MaxEnterAmountConverter().convert(cryptoCurrencyStatus)
initMinBoundary()
}
.launchIn(modelScope)
}
private fun initMinBoundary() {
modelScope.launch {
minAmountBoundary = getMinimumTransactionAmountSyncUseCase(
userWalletId = params.userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
).getOrNull()?.let {
EnterAmountBoundary(
amount = it,
fiatRate = cryptoCurrencyStatus.value.fiatRate.orZero(),
)
}
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
if (uiState.value is AmountState.Data) {
if (cryptoCurrencyStatus.value != CryptoCurrencyStatus.Loading) {
_uiState.update(
AmountBoundaryUpdateTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
@ -162,13 +128,61 @@ internal class SendAmountModel @Inject constructor(
isBalanceHidden = params.isBalanceHidingFlow.value,
),
)
} else {
initialState()
}
}.launchIn(modelScope)
}
private fun subscribeOnCryptoCurrencyStatusFlow() {
combine(
flow = params.cryptoCurrencyStatusFlow.distinctUntilChanged { old, new ->
old.value.amount == new.value.amount
}, // Check only balance changes,
flow2 = params.accountFlow,
flow3 = params.isAccountModeFlow,
) { newCryptoCurrencyStatus, account, isAccountsMode ->
cryptoCurrencyStatus = newCryptoCurrencyStatus
maxAmountBoundary = MaxEnterAmountConverter().convert(cryptoCurrencyStatus)
initMinBoundary(cryptoCurrencyStatus, account, isAccountsMode)
}.flowOn(dispatchers.default)
.launchIn(modelScope)
}
private suspend fun initMinBoundary(
cryptoCurrencyStatus: CryptoCurrencyStatus,
account: Account.CryptoPortfolio?,
isAccountsMode: Boolean,
) {
minAmountBoundary = getMinimumTransactionAmountSyncUseCase(
userWalletId = params.userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
).getOrNull()?.let {
EnterAmountBoundary(
amount = it,
fiatRate = cryptoCurrencyStatus.value.fiatRate.orZero(),
)
}
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
if (uiState.value is AmountState.Data) {
_uiState.update(
AmountBoundaryUpdateTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
maxEnterAmount = maxAmountBoundary,
appCurrency = appCurrency,
isBalanceHidden = params.isBalanceHidingFlow.value,
),
)
} else {
initialState(cryptoCurrencyStatus, account, isAccountsMode)
}
}
private fun initialState() {
private fun initialState(
cryptoCurrencyStatus: CryptoCurrencyStatus,
@Suppress("UnusedParameter") account: Account.CryptoPortfolio?,
@Suppress("UnusedParameter") isAccountsMode: Boolean,
) {
if (uiState.value is AmountState.Empty && userWallet != null) {
val isOnlyOneWallet = getWalletsUseCase.invokeSync().size == 1
_uiState.update {

View file

@ -68,6 +68,8 @@ dependencies {
implementation(projects.domain.notifications)
implementation(projects.domain.feedback.models)
implementation(projects.domain.feedback)
implementation(projects.domain.account)
implementation(projects.domain.account.status)
/** Compose */
implementation(deps.compose.foundation)

View file

@ -2,6 +2,7 @@ package com.tangem.features.swap.v2.impl.amount
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.express.models.ExpressProviderType
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
@ -23,6 +24,8 @@ internal sealed class SwapAmountComponentParams {
abstract val primaryCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
abstract val secondaryCryptoCurrency: CryptoCurrency?
abstract val filterProviderTypes: List<ExpressProviderType>
abstract val accountFlow: StateFlow<Account.CryptoPortfolio?>
abstract val isAccountModeFlow: StateFlow<Boolean>
data class AmountParams(
override val amountUM: SwapAmountUM,
@ -34,6 +37,8 @@ internal sealed class SwapAmountComponentParams {
override val secondaryCryptoCurrency: CryptoCurrency?,
override val filterProviderTypes: List<ExpressProviderType> = emptyList(),
override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
override val accountFlow: StateFlow<Account.CryptoPortfolio?>,
override val isAccountModeFlow: StateFlow<Boolean>,
val title: TextReference,
val callback: SwapAmountComponent.ModelCallback,
val currentRoute: Flow<SendWithSwapRoute>,
@ -49,6 +54,8 @@ internal sealed class SwapAmountComponentParams {
override val secondaryCryptoCurrency: CryptoCurrency?,
override val filterProviderTypes: List<ExpressProviderType> = emptyList(),
override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
override val accountFlow: StateFlow<Account.CryptoPortfolio?>,
override val isAccountModeFlow: StateFlow<Boolean>,
val blockClickEnableFlow: StateFlow<Boolean>,
) : SwapAmountComponentParams()
}

View file

@ -342,6 +342,8 @@ internal class SwapAmountModel @Inject constructor(
appCurrency = appCurrency,
swapDirection = swapDirection,
clickIntents = this,
isAccountsMode = params.isAccountModeFlow.value,
account = params.accountFlow.value,
),
)
}.launchIn(modelScope)
@ -367,6 +369,8 @@ internal class SwapAmountModel @Inject constructor(
isBalanceHidden = params.isBalanceHidingFlow.value,
showBestRateAnimation = showBestRateAnimation,
isSingleWallet = isOnlyOneWallet,
isAccountsMode = params.isAccountModeFlow.value,
account = params.accountFlow.value,
),
)
}
@ -384,38 +388,45 @@ internal class SwapAmountModel @Inject constructor(
}
private fun subscribeOnCryptoCurrencyStatusFlow() {
params.primaryCryptoCurrencyStatusFlow
.distinctUntilChanged { old, new -> old.value.amount == new.value.amount } // Check only balance changes
.onEach { primaryCurrencyStatus ->
val secondaryStatus = (uiState.value as? SwapAmountUM.Content)?.secondaryCryptoCurrencyStatus
initCurrencies(
primaryStatus = primaryCurrencyStatus,
secondaryStatus = secondaryStatus,
combine(
flow = params.primaryCryptoCurrencyStatusFlow.distinctUntilChanged { old, new ->
old.value.amount == new.value.amount
}, // Check only balance changes,
flow2 = params.accountFlow,
flow3 = params.isAccountModeFlow,
) { primaryCurrencyStatus, account, isAccountsMode ->
val secondaryStatus = (uiState.value as? SwapAmountUM.Content)?.secondaryCryptoCurrencyStatus
initCurrencies(
primaryStatus = primaryCurrencyStatus,
secondaryStatus = secondaryStatus,
)
if (secondaryStatus != null) {
uiState.transformerUpdate(
SwapAmountUpdateBalanceTransformer(
cryptoCurrencyStatus = primaryCurrencyStatus,
primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
),
)
if (secondaryStatus != null) {
uiState.transformerUpdate(
SwapAmountUpdateBalanceTransformer(
cryptoCurrencyStatus = primaryCurrencyStatus,
primaryMaximumAmountBoundary = primaryMaximumAmountBoundary,
primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
),
)
} else {
val isOnlyOneWallet = getWalletsUseCase.invokeSync().size == 1
uiState.transformerUpdate(
SwapAmountPrimaryReadyStateTransformer(
userWallet = userWallet,
primaryCryptoCurrencyStatus = primaryCurrencyStatus,
appCurrency = appCurrency,
swapDirection = swapDirection,
clickIntents = this,
isBalanceHidden = params.isBalanceHidingFlow.value,
showBestRateAnimation = showBestRateAnimation,
isSingleWallet = isOnlyOneWallet,
),
)
}
}.launchIn(modelScope)
} else {
val isOnlyOneWallet = getWalletsUseCase.invokeSync().size == 1
uiState.transformerUpdate(
SwapAmountPrimaryReadyStateTransformer(
userWallet = userWallet,
primaryCryptoCurrencyStatus = primaryCurrencyStatus,
appCurrency = appCurrency,
swapDirection = swapDirection,
clickIntents = this,
isBalanceHidden = params.isBalanceHidingFlow.value,
showBestRateAnimation = showBestRateAnimation,
isSingleWallet = isOnlyOneWallet,
isAccountsMode = isAccountsMode,
account = account,
),
)
}
}.flowOn(dispatchers.default)
.launchIn(modelScope)
}
private fun subscribeOnAmountUpdateTriggerUpdates() {
@ -523,6 +534,8 @@ internal class SwapAmountModel @Inject constructor(
isBalanceHidden = params.isBalanceHidingFlow.value,
showBestRateAnimation = showBestRateAnimation,
isSingleWallet = isOnlyOneWallet,
isAccountsMode = params.isAccountModeFlow.value,
account = params.accountFlow.value,
),
)
startLoadingQuotesTask(isSilentReload = false)

View file

@ -11,6 +11,7 @@ import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.swap.models.SwapDirection
@ -19,6 +20,7 @@ import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
import com.tangem.utils.StringsSigns.DOT
@Suppress("LongParameterList")
internal class SwapAmountFieldConverter(
private val swapDirection: SwapDirection,
private val isBalanceHidden: Boolean,
@ -26,6 +28,8 @@ internal class SwapAmountFieldConverter(
private val appCurrency: AppCurrency,
private val clickIntents: AmountScreenClickIntents,
private val isSingleWallet: Boolean,
@Suppress("UnusedPrivateProperty") private val isAccountsMode: Boolean,
@Suppress("UnusedPrivateProperty") private val account: Account.CryptoPortfolio?,
) {
private val iconStateConverter = CryptoCurrencyToIconStateConverter()

View file

@ -3,6 +3,7 @@ package com.tangem.features.swap.v2.impl.amount.model.transformers
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
@ -11,6 +12,7 @@ import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.model.converter.SwapAmountFieldConverter
import com.tangem.utils.transformer.Transformer
@Suppress("LongParameterList")
internal class SwapAmountBalanceHiddenTransformer(
private val isBalanceHidden: Boolean,
private val isSingleWallet: Boolean,
@ -18,6 +20,8 @@ internal class SwapAmountBalanceHiddenTransformer(
private val appCurrency: AppCurrency,
private val swapDirection: SwapDirection,
private val clickIntents: AmountScreenClickIntents,
private val isAccountsMode: Boolean,
private val account: Account.CryptoPortfolio?,
) : Transformer<SwapAmountUM> {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
@ -30,6 +34,8 @@ internal class SwapAmountBalanceHiddenTransformer(
appCurrency = appCurrency,
clickIntents = clickIntents,
isSingleWallet = isSingleWallet,
isAccountsMode = isAccountsMode,
account = account,
)
val recalculatedPrimary = amountFieldConverter.convert(

View file

@ -3,6 +3,7 @@ package com.tangem.features.swap.v2.impl.amount.model.transformers
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.express.models.ExpressRateType
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.swap.models.SwapCurrencies
@ -25,6 +26,8 @@ internal class SwapAmountPrimaryReadyStateTransformer(
private val isBalanceHidden: Boolean,
private val showBestRateAnimation: Boolean,
private val isSingleWallet: Boolean,
private val isAccountsMode: Boolean,
private val account: Account.CryptoPortfolio?,
) : Transformer<SwapAmountUM> {
private val amountFieldConverter = SwapAmountFieldConverter(
@ -34,6 +37,8 @@ internal class SwapAmountPrimaryReadyStateTransformer(
appCurrency = appCurrency,
clickIntents = clickIntents,
isSingleWallet = isSingleWallet,
isAccountsMode = isAccountsMode,
account = account,
)
override fun transform(prevState: SwapAmountUM): SwapAmountUM {

View file

@ -3,6 +3,7 @@ package com.tangem.features.swap.v2.impl.amount.model.transformers
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.express.models.ExpressRateType
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.swap.models.SwapCurrencies
@ -26,6 +27,8 @@ internal class SwapAmountSecondaryReadyStateTransformer(
private val isBalanceHidden: Boolean,
private val showBestRateAnimation: Boolean,
private val isSingleWallet: Boolean,
private val isAccountsMode: Boolean,
private val account: Account.CryptoPortfolio?,
) : Transformer<SwapAmountUM> {
private val amountFieldConverter = SwapAmountFieldConverter(
@ -35,6 +38,8 @@ internal class SwapAmountSecondaryReadyStateTransformer(
appCurrency = appCurrency,
clickIntents = clickIntents,
isSingleWallet = isSingleWallet,
isAccountsMode = isAccountsMode,
account = account,
)
override fun transform(prevState: SwapAmountUM): SwapAmountUM {

View file

@ -160,6 +160,8 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
userWallet = model.userWallet,
filterProviderTypes = SEND_WITH_SWAP_PROVIDER_TYPES,
analyticsSendSource = model.analyticsSendSource,
accountFlow = model.accountFlow,
isAccountModeFlow = model.isAccountModeFlow,
),
)
}
@ -199,6 +201,8 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
analyticsCategoryName = model.analyticCategoryName,
primaryCryptoCurrencyStatusFlow = model.primaryCryptoCurrencyStatusFlow,
primaryFeePaidCurrencyStatusFlow = model.primaryFeePaidCurrencyStatusFlow,
accountFlow = model.accountFlow,
isAccountModeFlow = model.isAccountModeFlow,
analyticsSendSource = model.analyticsSendSource,
swapDirection = SwapDirection.Direct,
),

View file

@ -10,6 +10,7 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
@ -62,6 +63,8 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor(
swapDirection = params.swapDirection,
filterProviderTypes = SEND_WITH_SWAP_PROVIDER_TYPES,
analyticsSendSource = params.analyticsSendSource,
accountFlow = params.accountFlow,
isAccountModeFlow = params.isAccountModeFlow,
),
onResult = model::onAmountResult,
onClick = model::showEditAmount,
@ -175,6 +178,8 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor(
val isBalanceHidingFlow: StateFlow<Boolean>,
val primaryCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
val primaryFeePaidCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
val accountFlow: StateFlow<Account.CryptoPortfolio?>,
val isAccountModeFlow: StateFlow<Boolean>,
val callback: ModelCallback,
)

View file

@ -7,11 +7,15 @@ import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
@ -50,7 +54,10 @@ internal class SendWithSwapModel @Inject constructor(
private val getUserWalletUseCase: GetUserWalletUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val swapAlertFactory: SwapAlertFactory,
private val accountsFeatureToggles: AccountsFeatureToggles,
paramsContainer: ParamsContainer,
) : Model(),
SwapAmountComponent.ModelCallback,
@ -89,6 +96,12 @@ internal class SendWithSwapModel @Inject constructor(
),
)
val accountFlow: StateFlow<Account.CryptoPortfolio?>
field = MutableStateFlow(null)
val isAccountModeFlow: StateFlow<Boolean>
field = MutableStateFlow(false)
init {
initUserWallet()
initAppCurrency()
@ -192,36 +205,54 @@ internal class SendWithSwapModel @Inject constructor(
val isSingleWalletWithToken = wallet is UserWallet.Cold &&
wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
getCurrencyStatus(
cryptoCurrency = cryptoCurrency,
isSingleWalletWithToken = isSingleWalletWithToken,
isMultiCurrency = isMultiCurrency,
).onEach { maybeCryptoCurrency ->
maybeCryptoCurrency.fold(
ifRight = { cryptoCurrencyStatus ->
primaryCryptoCurrencyStatusFlow.value = cryptoCurrencyStatus
primaryFeePaidCurrencyStatusFlow.value = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = params.userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
).getOrNull() ?: cryptoCurrencyStatus
},
ifLeft = { error ->
swapAlertFactory.getGenericErrorState(
expressError = ExpressError.UnknownError,
onFailedTxEmailClick = {
modelScope.launch {
swapAlertFactory.onFailedTxEmailClick(
userWallet = userWallet,
cryptoCurrency = params.currency,
errorMessage = error.toString(),
)
}
},
popBack = ::onBackClick,
)
},
)
}.launchIn(modelScope)
if (accountsFeatureToggles.isFeatureEnabled) {
getAccountCurrencyStatusUseCase(
userWalletId = params.userWalletId,
currency = cryptoCurrency,
).onEach { (account, cryptoCurrencyStatus) ->
accountFlow.value = account
isAccountModeFlow.value = isAccountsModeEnabledUseCase.invokeSync()
primaryCryptoCurrencyStatusFlow.value = cryptoCurrencyStatus
primaryFeePaidCurrencyStatusFlow.value = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = params.userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
).getOrNull() ?: cryptoCurrencyStatus
}.flowOn(dispatchers.default)
.launchIn(modelScope)
} else {
getCurrencyStatus(
cryptoCurrency = cryptoCurrency,
isSingleWalletWithToken = isSingleWalletWithToken,
isMultiCurrency = isMultiCurrency,
).onEach { maybeCryptoCurrency ->
maybeCryptoCurrency.fold(
ifRight = { cryptoCurrencyStatus ->
primaryCryptoCurrencyStatusFlow.value = cryptoCurrencyStatus
primaryFeePaidCurrencyStatusFlow.value = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = params.userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
).getOrNull() ?: cryptoCurrencyStatus
},
ifLeft = { error ->
swapAlertFactory.getGenericErrorState(
expressError = ExpressError.UnknownError,
onFailedTxEmailClick = {
modelScope.launch {
swapAlertFactory.onFailedTxEmailClick(
userWallet = userWallet,
cryptoCurrency = params.currency,
errorMessage = error.toString(),
)
}
},
popBack = ::onBackClick,
)
},
)
}.flowOn(dispatchers.default)
.launchIn(modelScope)
}
}
private fun getCurrencyStatus(