Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-03 11:21:00 +04:00
parent dff5b1f27c
commit 978d7c1da4
20 changed files with 202 additions and 855 deletions

View file

@ -15,7 +15,6 @@ import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectList
import com.tangem.datasource.local.preferences.utils.getObjectListSync
import com.tangem.datasource.local.preferences.utils.getObjectMap
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.supplier.MultiAccountListSupplier
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
@ -30,7 +29,6 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
internal class DefaultSwapTransactionRepository(
@ -39,7 +37,6 @@ internal class DefaultSwapTransactionRepository(
private val networkFactory: NetworkFactory,
private val dispatchers: CoroutineDispatcherProvider,
private val multiAccountListSupplier: MultiAccountListSupplier,
private val accountsFeatureToggles: AccountsFeatureToggles,
) : SwapTransactionRepository {
private val listConverter by lazy(LazyThreadSafetyMode.NONE) {
@ -122,11 +119,7 @@ internal class DefaultSwapTransactionRepository(
flow2 = appPreferencesStore.getObjectMap<SwapStatusDTO>(
key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY,
),
flow3 = if (accountsFeatureToggles.isFeatureEnabled) {
multiAccountListSupplier()
} else {
flowOf(emptyList())
},
flow3 = multiAccountListSupplier(),
) { savedTransactions, txStatuses, multiAccountList ->
val currencyTxs = savedTransactions
?.filter { swapTxList ->

View file

@ -12,7 +12,6 @@ import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
import com.tangem.datasource.crypto.DataSignatureVerifier
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.supplier.MultiAccountListSupplier
import com.tangem.domain.express.ExpressRepository
import com.tangem.domain.quotes.single.SingleQuoteStatusFetcher
@ -71,7 +70,6 @@ internal object SwapDataModule {
responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
networkFactory: NetworkFactory,
multiAccountListSupplier: MultiAccountListSupplier,
accountsFeatureToggles: AccountsFeatureToggles,
dispatchers: CoroutineDispatcherProvider,
): SwapTransactionRepository {
return DefaultSwapTransactionRepository(
@ -79,7 +77,6 @@ internal object SwapDataModule {
responseCryptoCurrenciesFactory = responseCryptoCurrenciesFactory,
networkFactory = networkFactory,
multiAccountListSupplier = multiAccountListSupplier,
accountsFeatureToggles = accountsFeatureToggles,
dispatchers = dispatchers,
)
}

View file

@ -6,7 +6,6 @@ import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.navigation.Route
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.features.feed.components.earn.DefaultEarnComponent
import com.tangem.features.feed.components.feed.DefaultFeedComponent
import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent
@ -20,7 +19,6 @@ import javax.inject.Inject
internal class FeedEntryChildFactory @Inject constructor(
private val analyticsEventHandler: AnalyticsEventHandler,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val portfolioComponentFactory: MarketsPortfolioComponent.Factory,
private val addToPortfolioPreselectedDataComponent: AddToPortfolioPreselectedDataComponent.Factory,
) {
@ -66,7 +64,6 @@ internal class FeedEntryChildFactory @Inject constructor(
appComponentContext = appComponentContext,
params = child.params,
analyticsEventHandler = analyticsEventHandler,
accountsFeatureToggles = accountsFeatureToggles,
portfolioComponentFactory = portfolioComponentFactory,
)
}

View file

@ -15,7 +15,6 @@ import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.models.currency.CryptoCurrency
@ -33,7 +32,6 @@ internal class DefaultMarketsTokenDetailsComponent(
appComponentContext: AppComponentContext,
val params: Params,
analyticsEventHandler: AnalyticsEventHandler,
private val accountsFeatureToggles: AccountsFeatureToggles,
portfolioComponentFactory: MarketsPortfolioComponent.Factory,
) : ComposableModularBottomSheetContentComponent, AppComponentContext by appComponentContext {
@ -117,7 +115,6 @@ internal class DefaultMarketsTokenDetailsComponent(
modifier = modifier,
backgroundColor = LocalMainBottomSheetColor.current.value,
state = state,
isAccountEnabled = accountsFeatureToggles.isFeatureEnabled,
portfolioBlock = portfolioComponent?.let { component ->
{ blockModifier ->
component.Content(blockModifier)

View file

@ -42,7 +42,6 @@ import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.markets.PriceChangeInterval
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.market.detailed.components.*
import com.tangem.core.ui.R as CoreR
import com.tangem.features.feed.ui.market.detailed.preview.MarketsTokenDetailsPreview
import com.tangem.features.feed.ui.market.detailed.state.ExchangesBottomSheetContent
import com.tangem.features.feed.ui.market.detailed.state.InfoBottomSheetContent
@ -50,13 +49,13 @@ import com.tangem.features.feed.ui.market.detailed.state.MarketsTokenDetailsUM
import com.tangem.features.feed.ui.market.detailed.state.SecurityScoreBottomSheetContent
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.distinctUntilChanged
import com.tangem.core.ui.R as CoreR
@Suppress("LongParameterList")
@Composable
internal fun MarketsTokenDetailsContent(
state: MarketsTokenDetailsUM,
backgroundColor: Color,
isAccountEnabled: Boolean,
modifier: Modifier = Modifier,
portfolioBlock: @Composable ((Modifier) -> Unit)?,
) {
@ -65,7 +64,6 @@ internal fun MarketsTokenDetailsContent(
backgroundColor = backgroundColor,
state = state,
portfolioBlock = portfolioBlock,
isAccountEnabled = isAccountEnabled,
)
when (state.bottomSheetConfig.content) {
@ -80,7 +78,6 @@ internal fun MarketsTokenDetailsContent(
private fun Content(
state: MarketsTokenDetailsUM,
backgroundColor: Color,
isAccountEnabled: Boolean,
modifier: Modifier = Modifier,
portfolioBlock: @Composable ((Modifier) -> Unit)?,
) {
@ -134,7 +131,6 @@ private fun Content(
tokenMarketDetailsBody(
state = state.body,
isAccountEnabled = isAccountEnabled,
portfolioBlock = portfolioBlock,
relatedNews = state.relatedNews,
)
@ -322,7 +318,6 @@ private fun MarketsTokenDetailsContent_Preview(
state = params,
backgroundColor = TangemTheme.colors.background.tertiary,
portfolioBlock = {},
isAccountEnabled = true,
)
}
}

View file

@ -20,10 +20,9 @@ import com.tangem.features.feed.ui.feed.state.NewsSliderConfig
import com.tangem.features.feed.ui.market.detailed.state.MarketsTokenDetailsUM
import com.tangem.features.feed.ui.market.detailed.state.MarketsTokenDetailsUM.RelatedNews
@Suppress("CanBeNonNullable") // TODO will be removed after [REDACTED_JIRA]
@Suppress("CanBeNonNullable")
internal fun LazyListScope.tokenMarketDetailsBody(
state: MarketsTokenDetailsUM.Body,
isAccountEnabled: Boolean,
portfolioBlock: @Composable ((Modifier) -> Unit)?,
relatedNews: RelatedNews,
) {
@ -39,9 +38,7 @@ internal fun LazyListScope.tokenMarketDetailsBody(
}
}
if (isAccountEnabled) {
aboutCoinHeader()
}
aboutCoinHeader()
loadingInfoBlocks()
}
@ -60,9 +57,7 @@ internal fun LazyListScope.tokenMarketDetailsBody(
relatedNews(relatedNews)
}
if (isAccountEnabled) {
aboutCoinHeader()
}
aboutCoinHeader()
infoBlocksList(state.infoBlocks)
}

View file

@ -5,7 +5,6 @@ import arrow.core.Either
import arrow.core.getOrElse
import arrow.core.left
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.navigationButtons.NavigationUM
@ -16,29 +15,23 @@ 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
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
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
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.models.TransactionFeeExtended
import com.tangem.domain.transaction.usecase.CreateTransferTransactionUseCase
@ -55,7 +48,6 @@ import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.entity.PredefinedValues
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponent
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.common.CommonSendRoute.*
import com.tangem.features.send.v2.common.SendConfirmAlertFactory
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
@ -74,7 +66,6 @@ import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import kotlin.properties.Delegates
import com.tangem.features.send.v2.api.entity.FeeSelectorUM as FeeSelectorUMRedesigned
internal interface SendComponentCallback :
SendAmountComponent.ModelCallback,
@ -90,7 +81,6 @@ internal class SendModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
@ -108,7 +98,6 @@ internal class SendModel @Inject constructor(
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()
@ -254,7 +243,7 @@ internal class SendModel @Inject constructor(
showAlertError()
}
suspend fun prepareTransferTransaction(): Either<Throwable, TransactionData> {
private suspend fun prepareTransferTransaction(): Either<Throwable, TransactionData> {
val predefinedValues = predefinedValues
val cryptoCurrencyStatus = cryptoCurrencyStatusFlow.value
return if (predefinedValues is PredefinedValues.Content.Deeplink) {
@ -360,34 +349,24 @@ internal class SendModel @Inject constructor(
ifRight = { wallet ->
userWallet = wallet
if (accountsFeatureToggles.isFeatureEnabled) {
getAccountCurrencyStatusUseCase(
getAccountCurrencyStatusUseCase(
userWalletId = params.userWalletId,
currency = cryptoCurrency,
).onEach { (account, cryptoCurrencyStatus) ->
isAccountModeFlow.value = isAccountsModeEnabledUseCase.invokeSync()
accountFlow.value = account
cryptoCurrencyStatusFlow.value = cryptoCurrencyStatus
feeCryptoCurrencyStatusFlow.value = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = params.userWalletId,
currency = cryptoCurrency,
).onEach { (account, cryptoCurrencyStatus) ->
isAccountModeFlow.value = isAccountsModeEnabledUseCase.invokeSync()
accountFlow.value = account
cryptoCurrencyStatus = cryptoCurrencyStatus,
).getOrNull() ?: cryptoCurrencyStatus
cryptoCurrencyStatusFlow.value = cryptoCurrencyStatus
feeCryptoCurrencyStatusFlow.value = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = params.userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
).getOrNull() ?: cryptoCurrencyStatus
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,
)
}
if (params.amount != null) {
router.replaceAll(Confirm)
}
}.flowOn(dispatchers.default)
.launchIn(modelScope)
},
ifLeft = { error ->
Timber.w(error.toString())
@ -409,77 +388,6 @@ internal class SendModel @Inject constructor(
.saveIn(balanceHidingJobHolder)
}
private fun getCurrenciesStatusUpdates(isSingleWalletWithToken: Boolean, isMultiCurrency: Boolean) {
getCurrencyStatus(
isSingleWalletWithToken = isSingleWalletWithToken,
isMultiCurrency = isMultiCurrency,
).onEach { maybeCryptoCurrency ->
maybeCryptoCurrency.fold(
ifRight = { cryptoCurrencyStatus ->
cryptoCurrencyStatusFlow.value = cryptoCurrencyStatus
feeCryptoCurrencyStatusFlow.value = getFeeCurrencyStatus(cryptoCurrencyStatus, isMultiCurrency)
if (params.amount != null) {
router.replaceAll(CommonSendRoute.Confirm)
}
},
ifLeft = {
sendConfirmAlertFactory.getGenericErrorState(
onFailedTxEmailClick = {
onFailedTxEmailClick(it.toString())
},
popBack = router::pop,
)
},
)
}.flowOn(dispatchers.default)
.launchIn(modelScope)
}
private fun getCurrencyStatus(
isSingleWalletWithToken: Boolean,
isMultiCurrency: Boolean,
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
return when {
isSingleWalletWithToken -> getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
userWalletId = params.userWalletId,
currencyId = cryptoCurrency.id,
isSingleWalletWithTokens = true,
)
isMultiCurrency -> getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
userWalletId = params.userWalletId,
currencyId = cryptoCurrency.id,
isSingleWalletWithTokens = false,
)
else -> getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWalletId = params.userWalletId)
}
}
fun getSelectedFeeToken(): CryptoCurrency {
val feeUMV2 = uiState.value.feeSelectorUM as? FeeSelectorUMRedesigned.Content
val feeExtended = feeUMV2?.feeExtraInfo?.transactionFeeExtended
val isFeeInTokenCurrency = feeExtended?.transactionFee?.normal is Fee.Ethereum.TokenCurrency
return if (isFeeInTokenCurrency) {
feeUMV2.feeExtraInfo.feeCryptoCurrencyStatus.currency
} else {
feeCryptoCurrencyStatusFlow.value.currency
}
}
private suspend fun getFeeCurrencyStatus(
cryptoCurrencyStatus: CryptoCurrencyStatus,
isMultiCurrency: Boolean,
): CryptoCurrencyStatus {
return if (isMultiCurrency) {
getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = params.userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
).getOrNull() ?: cryptoCurrencyStatus
} else {
cryptoCurrencyStatus
}
}
private fun subscribeOnQRScannerResult() {
listenToQrScanningUseCase(SourceType.SEND)
.getOrElse { emptyFlow() }

View file

@ -11,12 +11,10 @@ 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
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
@ -27,7 +25,6 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.transaction.error.GetFeeError
@ -66,7 +63,6 @@ internal class NFTSendModel @Inject constructor(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
private val createNFTTransferTransactionUseCase: CreateNFTTransferTransactionUseCase,
private val getFeeUseCase: GetFeeUseCase,
@ -77,7 +73,6 @@ internal class NFTSendModel @Inject constructor(
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()
@ -186,31 +181,24 @@ internal class NFTSendModel @Inject constructor(
?.firstOrNull { it is CryptoCurrency.Coin && it.network == nftAsset.network }
?: return@launch
if (accountsFeatureToggles.isFeatureEnabled) {
getAccountCurrencyStatusUseCase(
userWalletId,
cryptoCurrency,
).onEach { (maybeAccount, cryptoStatus) ->
account = maybeAccount
isAccountsMode = isAccountsModeEnabledUseCase.invokeSync()
getAccountCurrencyStatusUseCase(
userWalletId,
cryptoCurrency,
).onEach { (maybeAccount, cryptoStatus) ->
account = maybeAccount
isAccountsMode = isAccountsModeEnabledUseCase.invokeSync()
cryptoCurrencyStatus = cryptoStatus
feeCryptoCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = userWalletId,
cryptoCurrencyStatus = cryptoStatus,
).getOrNull() ?: cryptoStatus
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(),
)
}
if (uiState.value.destinationUM is DestinationUM.Empty) {
router.replaceAll(Destination(isEditMode = false))
}
}.flowOn(dispatchers.default)
.launchIn(modelScope)
},
ifLeft = {
alertFactory.getGenericErrorState(::onFailedTxEmailClick)
@ -246,34 +234,6 @@ internal class NFTSendModel @Inject constructor(
}
}
private fun getCurrenciesStatusUpdates(isSingleWalletWithToken: Boolean) {
getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
userWalletId = userWalletId,
currencyId = cryptoCurrency.id,
isSingleWalletWithTokens = isSingleWalletWithToken,
).onEach { maybeCryptoCurrency ->
maybeCryptoCurrency.fold(
ifRight = { cryptoStatus ->
cryptoCurrencyStatus = cryptoStatus
feeCryptoCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = userWalletId,
cryptoCurrencyStatus = cryptoStatus,
).getOrNull() ?: cryptoStatus
if (uiState.value.destinationUM is DestinationUM.Empty) {
router.replaceAll(Destination(isEditMode = false))
}
},
ifLeft = {
alertFactory.getGenericErrorState(
onFailedTxEmailClick = { onFailedTxEmailClick(it.toString()) },
popBack = { router.pop() },
)
},
)
}.launchIn(modelScope)
}
private fun initialState(): NFTSendUM = NFTSendUM(
destinationUM = DestinationUM.Empty(),
feeSelectorUM = FeeSelectorUM.Loading,

View file

@ -11,18 +11,14 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.models.account.filterCryptoPortfolio
import com.tangem.domain.models.network.CryptoCurrencyAddress
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
import com.tangem.domain.tokens.GetNetworkAddressesUseCase
import com.tangem.domain.transaction.usecase.IsSelfSendAvailableUseCase
import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase
@ -63,7 +59,6 @@ internal class SendDestinationModel @Inject constructor(
private val validateWalletAddressUseCase: ValidateWalletAddressUseCase,
private val validateWalletMemoUseCase: ValidateWalletMemoUseCase,
private val getWalletsUseCase: GetWalletsUseCase,
private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase,
private val getNetworkAddressesUseCase: GetNetworkAddressesUseCase,
private val getFixedTxHistoryItemsUseCase: GetFixedTxHistoryItemsUseCase,
private val isSelfSendAvailableUseCase: IsSelfSendAvailableUseCase,
@ -71,7 +66,6 @@ internal class SendDestinationModel @Inject constructor(
private val parseQrCodeUseCase: ParseQrCodeUseCase,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
) : Model(), SendDestinationClickIntents {
private val params: SendDestinationComponentParams = paramsContainer.require()
@ -191,15 +185,7 @@ internal class SendDestinationModel @Inject constructor(
private fun getWalletsAndRecent() {
combine(
flow = if (accountsFeatureToggles.isFeatureEnabled) {
getAddedAddresses()
} else {
getWalletsUseCase().conflate().map {
waitForDelay(RECENT_LOAD_DELAY) {
it.toAvailableWallets()
}
}
},
flow = getAddedAddresses(),
flow2 = getFixedTxHistoryItemsUseCase(
userWalletId = userWalletId,
currency = cryptoCurrency,
@ -226,48 +212,6 @@ internal class SendDestinationModel @Inject constructor(
}.flowOn(dispatchers.default).launchIn(modelScope)
}
private suspend fun List<UserWallet>.toAvailableWallets(): List<DestinationWalletUM> {
return coroutineScope {
val cryptoCurrencyNetwork = cryptoCurrency.network
return@coroutineScope filterNot { it.isLocked }
.map { wallet ->
async {
val addresses = if (!wallet.isMultiCurrency) {
getCryptoCurrencyUseCase(wallet.walletId).getOrNull()?.let { cryptoCurrency ->
if (cryptoCurrency.network.rawId == cryptoCurrencyNetwork.rawId) {
getNetworkAddressesUseCase.invokeSync(
userWalletId = wallet.walletId,
networkRawId = cryptoCurrency.network.id.rawId,
)
} else {
null
}
}
} else {
getNetworkAddressesUseCase.invokeSync(
userWalletId = wallet.walletId,
networkRawId = cryptoCurrencyNetwork.id.rawId,
)
}
wallet to addresses
}
}.awaitAll()
.asSequence()
.mapNotNull { (wallet, addresses) ->
addresses?.map { (cryptoCurrency, address) ->
DestinationWalletUM(
name = wallet.name,
address = address,
cryptoCurrency = cryptoCurrency,
userWalletId = wallet.walletId,
)
}
}.flatten()
.toList()
}
}
private fun getAddedAddresses(): Flow<List<DestinationWalletUM>> {
return combine(
flow = getWalletsUseCase().conflate(),

View file

@ -1,29 +1,23 @@
package com.tangem.features.swap.v2.impl.sendviaswap.model
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.common.ui.navigationButtons.NavigationUM
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
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
@ -49,7 +43,6 @@ import kotlin.properties.Delegates
internal class SendWithSwapModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
@ -57,7 +50,6 @@ internal class SendWithSwapModel @Inject constructor(
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val swapAlertFactory: SwapAlertFactory,
private val accountsFeatureToggles: AccountsFeatureToggles,
paramsContainer: ParamsContainer,
) : Model(),
SwapAmountComponent.ModelCallback,
@ -202,79 +194,20 @@ internal class SendWithSwapModel @Inject constructor(
}
private fun getPrimaryCurrencyStatusUpdates(cryptoCurrency: CryptoCurrency) {
val wallet = userWallet
val isMultiCurrency = wallet.isMultiCurrency
val isSingleWalletWithToken = wallet is UserWallet.Cold &&
wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
getAccountCurrencyStatusUseCase(
userWalletId = params.userWalletId,
currency = cryptoCurrency,
).onEach { (account, cryptoCurrencyStatus) ->
accountFlow.value = account
isAccountModeFlow.value = isAccountsModeEnabledUseCase.invokeSync()
if (accountsFeatureToggles.isFeatureEnabled) {
getAccountCurrencyStatusUseCase(
primaryCryptoCurrencyStatusFlow.value = cryptoCurrencyStatus
primaryFeePaidCurrencyStatusFlow.value = getFeePaidCryptoCurrencyStatusSyncUseCase(
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(
cryptoCurrency: CryptoCurrency,
isSingleWalletWithToken: Boolean,
isMultiCurrency: Boolean,
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
return when {
isSingleWalletWithToken -> getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
userWalletId = params.userWalletId,
currencyId = cryptoCurrency.id,
isSingleWalletWithTokens = true,
)
isMultiCurrency -> getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
userWalletId = params.userWalletId,
currencyId = cryptoCurrency.id,
isSingleWalletWithTokens = false,
)
else -> getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWalletId = params.userWalletId)
}
cryptoCurrencyStatus = cryptoCurrencyStatus,
).getOrNull() ?: cryptoCurrencyStatus
}.flowOn(dispatchers.default)
.launchIn(modelScope)
}
private fun subscribeOnBalanceHidden() {

View file

@ -8,7 +8,6 @@ import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectList
import com.tangem.datasource.local.preferences.utils.getObjectListSync
import com.tangem.datasource.local.preferences.utils.getObjectMap
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.producer.SingleAccountListProducer
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.models.account.Account
@ -23,14 +22,12 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
internal class DefaultSwapTransactionRepository(
private val appPreferencesStore: AppPreferencesStore,
private val dispatchers: CoroutineDispatcherProvider,
private val singleAccountListSupplier: SingleAccountListSupplier,
private val accountsFeatureToggles: AccountsFeatureToggles,
responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
networkFactory: NetworkFactory,
) : SwapTransactionRepository {
@ -110,11 +107,7 @@ internal class DefaultSwapTransactionRepository(
flow2 = appPreferencesStore.getObjectMap<ExchangeStatusModel>(
key = PreferencesKeys.SWAP_TRANSACTIONS_STATUSES_KEY,
),
flow3 = if (accountsFeatureToggles.isFeatureEnabled) {
singleAccountListSupplier(SingleAccountListProducer.Params(userWallet.walletId))
} else {
flowOf(null)
},
flow3 = singleAccountListSupplier(SingleAccountListProducer.Params(userWallet.walletId)),
) { savedTransactions, txStatuses, accountList ->
val currencyToTxs = savedTransactions?.filter { savedTx ->

View file

@ -8,7 +8,6 @@ import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
import com.tangem.datasource.crypto.DataSignatureVerifier
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.walletmanager.WalletManagersFacade
@ -59,7 +58,6 @@ internal class SwapDataModule {
responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
networkFactory: NetworkFactory,
singleAccountListSupplier: SingleAccountListSupplier,
accountsFeatureToggles: AccountsFeatureToggles,
dispatcherProvider: CoroutineDispatcherProvider,
): SwapTransactionRepository {
return DefaultSwapTransactionRepository(
@ -67,7 +65,6 @@ internal class SwapDataModule {
responseCryptoCurrenciesFactory = responseCryptoCurrenciesFactory,
networkFactory = networkFactory,
singleAccountListSupplier = singleAccountListSupplier,
accountsFeatureToggles = accountsFeatureToggles,
dispatchers = dispatcherProvider,
)
}

View file

@ -1,13 +1,11 @@
package com.tangem.feature.swap.domain
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.swap.domain.models.ui.AccountSwapCurrency
import com.tangem.feature.swap.domain.models.ui.TokensDataStateExpress
import com.tangem.feature.swap.domain.models.ui.getGroupWithReverse
import com.tangem.utils.extensions.orZero
import java.math.BigDecimal
internal class DefaultInitialToCurrencyResolver(
private val swapTransactionRepository: SwapTransactionRepository,
@ -18,22 +16,6 @@ internal class DefaultInitialToCurrencyResolver(
initialCryptoCurrency: CryptoCurrency,
state: TokensDataStateExpress,
isReverseFromTo: Boolean,
): CryptoCurrencyStatus? {
val id = swapTransactionRepository.getLastSwappedCryptoCurrencyId(userWallet.walletId) ?: return null
return if (id != initialCryptoCurrency.id.value) {
val group = state.getGroupWithReverse(isReverseFromTo)
group.available.find { it.currencyStatus.currency.id.value == id }?.currencyStatus
} else {
null
}
}
override suspend fun tryGetFromCacheV2(
userWallet: UserWallet,
initialCryptoCurrency: CryptoCurrency,
state: TokensDataStateExpress,
isReverseFromTo: Boolean,
): AccountSwapCurrency? {
val id = swapTransactionRepository.getLastSwappedCryptoCurrencyId(userWallet.walletId) ?: return null
@ -48,14 +30,7 @@ internal class DefaultInitialToCurrencyResolver(
}
}
override fun tryGetWithMaxAmount(state: TokensDataStateExpress, isReverseFromTo: Boolean): CryptoCurrencyStatus? {
val group = state.getGroupWithReverse(isReverseFromTo)
return group.available.maxByOrNull {
it.currencyStatus.value.fiatAmount ?: BigDecimal.ZERO
}?.currencyStatus
}
override fun tryGetWithMaxAmountV2(state: TokensDataStateExpress, isReverseFromTo: Boolean): AccountSwapCurrency? {
override fun tryGetWithMaxAmount(state: TokensDataStateExpress, isReverseFromTo: Boolean): AccountSwapCurrency? {
val group = state.getGroupWithReverse(isReverseFromTo)
return group.accountCurrencyList.firstNotNullOfOrNull { (_, currencyList) ->
currencyList.maxByOrNull { swapAccountCurrency ->

View file

@ -1,7 +1,6 @@
package com.tangem.feature.swap.domain
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.swap.domain.models.ui.AccountSwapCurrency
import com.tangem.feature.swap.domain.models.ui.TokensDataStateExpress
@ -13,16 +12,7 @@ interface InitialToCurrencyResolver {
initialCryptoCurrency: CryptoCurrency,
state: TokensDataStateExpress,
isReverseFromTo: Boolean,
): CryptoCurrencyStatus?
fun tryGetWithMaxAmount(state: TokensDataStateExpress, isReverseFromTo: Boolean): CryptoCurrencyStatus?
suspend fun tryGetFromCacheV2(
userWallet: UserWallet,
initialCryptoCurrency: CryptoCurrency,
state: TokensDataStateExpress,
isReverseFromTo: Boolean,
): AccountSwapCurrency?
fun tryGetWithMaxAmountV2(state: TokensDataStateExpress, isReverseFromTo: Boolean): AccountSwapCurrency?
fun tryGetWithMaxAmount(state: TokensDataStateExpress, isReverseFromTo: Boolean): AccountSwapCurrency?
}

View file

@ -105,12 +105,6 @@ interface SwapInteractor {
*/
fun getTokenBalance(token: CryptoCurrencyStatus): SwapAmount
suspend fun getInitialCurrencyToSwap(
initialCryptoCurrency: CryptoCurrency,
state: TokensDataStateExpress,
isReverseFromTo: Boolean,
): CryptoCurrencyStatus?
/**
* Returns initial currency to swap as AccountSwapCurrency
*
@ -118,7 +112,7 @@ interface SwapInteractor {
* @param state current tokens data state
* @param isReverseFromTo flag indicating the direction of the swap
*/
suspend fun getInitialCurrencyToSwapV2(
suspend fun getInitialCurrencyToSwap(
initialCryptoCurrency: CryptoCurrency,
state: TokensDataStateExpress,
isReverseFromTo: Boolean,

View file

@ -19,7 +19,6 @@ import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
@ -75,7 +74,6 @@ import java.math.RoundingMode
internal class SwapInteractorImpl @AssistedInject constructor(
private val repository: SwapRepository,
private val allowPermissionsHandler: AllowPermissionsHandler,
private val getMultiCryptoCurrencyStatusUseCase: GetMultiCryptoCurrencyStatusUseCase,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
private val sendTransactionUseCase: SendTransactionUseCase,
private val createTransactionUseCase: CreateTransactionUseCase,
@ -106,7 +104,6 @@ internal class SwapInteractorImpl @AssistedInject constructor(
private val rampStateManager: RampStateManager,
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val walletManagersFacade: WalletManagersFacade,
@Assisted private val userWalletId: UserWalletId,
) : SwapInteractor {
@ -123,58 +120,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
}
override suspend fun getTokensDataState(currency: CryptoCurrency): TokensDataStateExpress {
return if (accountsFeatureToggles.isFeatureEnabled) {
getAccountCurrencyTokensDataState(currency)
} else {
getCurrencyTokensDataState(currency)
}
}
private suspend fun getCurrencyTokensDataState(currency: CryptoCurrency): TokensDataStateExpress {
val walletCurrencyStatuses = getMultiCryptoCurrencyStatusUseCase
.invokeMultiWalletSync(userWalletId)
.getOrElse { emptyList() }
val walletCurrencyStatusesExceptInitial = walletCurrencyStatuses
.filter { status ->
val isDifferentCurrency = status.currency.network.backendId != currency.network.backendId ||
status.currency.getContractAddress() != currency.getContractAddress()
val hasValidStatus =
status.value is CryptoCurrencyStatus.Loaded || status.value is CryptoCurrencyStatus.NoAccount
val isNotCustomToken = !status.currency.isCustom
hasValidStatus && isDifferentCurrency && isNotCustomToken
}
if (walletCurrencyStatusesExceptInitial.isEmpty()) {
return TokensDataStateExpress.EMPTY
}
val pairsLeast = getPairs(
userWallet = userWallet,
initialCurrency = LeastTokenInfo(
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress ?: "0",
network = currency.network.backendId,
),
currenciesList = walletCurrencyStatusesExceptInitial.map { it.currency },
)
return TokensDataStateExpress(
fromGroup = getToCurrenciesGroup(
currency = currency,
leastPairs = pairsLeast.pairs,
cryptoCurrenciesList = walletCurrencyStatusesExceptInitial,
tokenInfoForFilter = { it.to },
tokenInfoForAvailable = { it.from },
),
toGroup = getToCurrenciesGroup(
currency = currency,
leastPairs = pairsLeast.pairs,
cryptoCurrenciesList = walletCurrencyStatusesExceptInitial,
tokenInfoForFilter = { it.from },
tokenInfoForAvailable = { it.to },
),
allProviders = pairsLeast.allProviders,
)
return getAccountCurrencyTokensDataState(currency)
}
private suspend fun getAccountCurrencyTokensDataState(currency: CryptoCurrency): TokensDataStateExpress {
@ -212,14 +158,14 @@ internal class SwapInteractorImpl @AssistedInject constructor(
)
return TokensDataStateExpress(
fromGroup = getToCurrenciesGroupV2(
fromGroup = getToCurrenciesGroup(
currency = currency,
leastPairs = pairsLeast.pairs,
cryptoCurrenciesList = walletAccountCurrencyStatusesExceptInitial,
tokenInfoForFilter = { it.to },
tokenInfoForAvailable = { it.from },
),
toGroup = getToCurrenciesGroupV2(
toGroup = getToCurrenciesGroup(
currency = currency,
leastPairs = pairsLeast.pairs,
cryptoCurrenciesList = walletAccountCurrencyStatusesExceptInitial,
@ -242,39 +188,6 @@ internal class SwapInteractorImpl @AssistedInject constructor(
}
private suspend fun getToCurrenciesGroup(
currency: CryptoCurrency,
leastPairs: List<SwapPairLeast>,
cryptoCurrenciesList: List<CryptoCurrencyStatus>,
tokenInfoForFilter: (SwapPairLeast) -> LeastTokenInfo,
tokenInfoForAvailable: (SwapPairLeast) -> LeastTokenInfo,
): CurrenciesGroup {
val filteredPairs = leastPairs.filter { pair ->
tokenInfoForFilter(pair).contractAddress == currency.getContractAddress() &&
tokenInfoForFilter(pair).network == currency.network.backendId
}
val availableCryptoCurrencies = cryptoCurrenciesList.mapNotNull { cryptoCurrencyStatus ->
val providers = findProvidersForPair(cryptoCurrencyStatus, filteredPairs, tokenInfoForAvailable)
if (providers != null) {
CryptoCurrencySwapInfo(cryptoCurrencyStatus, providers)
} else {
null
}
}
val unavailableCryptoCurrencies = cryptoCurrenciesList - availableCryptoCurrencies
.map { it.currencyStatus }
.toSet()
return CurrenciesGroup(
available = availableCryptoCurrencies,
unavailable = unavailableCryptoCurrencies.map { CryptoCurrencySwapInfo(it, emptyList()) },
accountCurrencyList = emptyList(),
isAfterSearch = false,
)
}
private suspend fun getToCurrenciesGroupV2(
currency: CryptoCurrency,
leastPairs: List<SwapPairLeast>,
cryptoCurrenciesList: Map<Account, List<CryptoCurrencyStatus>>,
@ -1323,7 +1236,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
initialCryptoCurrency: CryptoCurrency,
state: TokensDataStateExpress,
isReverseFromTo: Boolean,
): CryptoCurrencyStatus? {
): AccountSwapCurrency? {
val group = state.getGroupWithReverse(isReverseFromTo)
return initialToCurrencyResolver.tryGetFromCache(
userWallet = userWallet,
@ -1332,22 +1245,6 @@ internal class SwapInteractorImpl @AssistedInject constructor(
isReverseFromTo = isReverseFromTo,
)
?: initialToCurrencyResolver.tryGetWithMaxAmount(state = state, isReverseFromTo = isReverseFromTo)
?: group.available.firstOrNull()?.currencyStatus
}
override suspend fun getInitialCurrencyToSwapV2(
initialCryptoCurrency: CryptoCurrency,
state: TokensDataStateExpress,
isReverseFromTo: Boolean,
): AccountSwapCurrency? {
val group = state.getGroupWithReverse(isReverseFromTo)
return initialToCurrencyResolver.tryGetFromCacheV2(
userWallet = userWallet,
initialCryptoCurrency = initialCryptoCurrency,
state = state,
isReverseFromTo = isReverseFromTo,
)
?: initialToCurrencyResolver.tryGetWithMaxAmountV2(state = state, isReverseFromTo = isReverseFromTo)
?: group.accountCurrencyList.firstNotNullOfOrNull { accountSwapAvailability ->
accountSwapAvailability.currencyList.firstOrNull { accountSwapCurrency ->
accountSwapCurrency.isAvailable

View file

@ -1,113 +1,80 @@
package com.tangem.feature.swap.converters
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.*
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.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.feature.swap.domain.models.domain.CryptoCurrencySwapInfo
import com.tangem.feature.swap.models.CurrenciesGroupWithFromCurrency
import com.tangem.feature.swap.domain.models.ui.CurrenciesGroup
import com.tangem.feature.swap.models.SwapSelectTokenStateHolder
import com.tangem.feature.swap.models.TokenBalanceData
import com.tangem.feature.swap.models.SwapStateHolder
import com.tangem.feature.swap.models.TokenListUMData
import com.tangem.feature.swap.models.TokenToSelectState
import com.tangem.feature.swap.presentation.R
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.plus
import kotlinx.collections.immutable.toPersistentList
internal class TokensDataConverter(
private val onSearchEntered: (String) -> Unit,
private val onTokenSelected: (String) -> Unit,
private val isBalanceHiddenProvider: Provider<Boolean>,
private val appCurrencyProvider: Provider<AppCurrency>,
) : Converter<CurrenciesGroupWithFromCurrency, SwapSelectTokenStateHolder> {
private val tokensDataState: CurrenciesGroup,
private val isBalanceHidden: Boolean,
private val isAccountsMode: Boolean,
appCurrencyProvider: Provider<AppCurrency>,
) : Transformer<SwapStateHolder> {
override fun convert(value: CurrenciesGroupWithFromCurrency): SwapSelectTokenStateHolder {
val group = value.group
val availableTitle = TokenToSelectState.Title(
resourceReference(R.string.exchange_tokens_available_tokens_header),
)
val allTokens = group.available + group.unavailable
return SwapSelectTokenStateHolder(
availableTokens = allTokens.map { tokenWithBalanceToTokenToSelect(it, true) }
.toMutableList()
.apply {
if (this.isNotEmpty()) {
this.add(0, availableTitle)
private val accountListItemConverter = AccountTokenItemConverter(
appCurrency = appCurrencyProvider(),
unavailableErrorText = resourceReference(R.string.tokens_list_unavailable_to_swap_source_header),
onItemClick = onTokenSelected,
)
override fun transform(prevState: SwapStateHolder): SwapStateHolder {
val accountList = tokensDataState.accountCurrencyList
val currentMarketsState = prevState.selectTokenState?.marketsState
return prevState.copy(
selectTokenState = SwapSelectTokenStateHolder(
availableTokens = persistentListOf(),
unavailableTokens = persistentListOf(),
tokensListData = if (isAccountsMode) {
val portfolioList = accountListItemConverter.convertList(accountList).toPersistentList()
val totalTokensCount = portfolioList.sumOf { it.tokens.size }
if (totalTokensCount > 0) {
TokenListUMData.AccountList(
tokensList = portfolioList,
totalTokensCount = totalTokensCount,
)
} else {
TokenListUMData.EmptyList
}
}
.toImmutableList(),
unavailableTokens = persistentListOf(),
tokensListData = TokenListUMData.EmptyList,
onSearchEntered = onSearchEntered,
onTokenSelected = onTokenSelected,
isBalanceHidden = isBalanceHiddenProvider(),
isAfterSearch = group.isAfterSearch,
)
}
} else {
val tokensList = accountList.flatMap { (_, currencyList) ->
currencyList.asSequence().map { accountSwapCurrency ->
accountListItemConverter.createAvailableItemConverter()
.convert(accountSwapCurrency.cryptoCurrencyStatus)
}.map(TokensListItemUM::Token).toPersistentList()
}.toPersistentList()
private fun tokenWithBalanceToTokenToSelect(
cryptoCurrencySwapInfo: CryptoCurrencySwapInfo,
isAvailable: Boolean,
): TokenToSelectState {
val cryptoCurrencyStatus = cryptoCurrencySwapInfo.currencyStatus
return TokenToSelectState.TokenToSelect(
id = cryptoCurrencyStatus.currency.id.value,
name = cryptoCurrencyStatus.currency.name,
symbol = cryptoCurrencyStatus.currency.symbol,
isAvailable = isAvailable,
tokenIcon = convertIcon(cryptoCurrencyStatus.currency, isAvailable),
addedTokenBalanceData = TokenBalanceData(
amount = formatCryptoAmount(cryptoCurrencyStatus),
amountEquivalent = formatFiatAmount(cryptoCurrencyStatus, appCurrencyProvider.invoke()),
isBalanceHidden = isBalanceHiddenProvider.invoke(),
if (tokensList.isNotEmpty()) {
TokenListUMData.TokenList(
tokensList = persistentListOf(
TokensListItemUM.GroupTitle(
id = "available_tokens_title",
text = resourceReference(R.string.exchange_tokens_available_tokens_header),
),
) + tokensList,
totalTokensCount = tokensList.size,
)
} else {
TokenListUMData.EmptyList
}
},
marketsState = currentMarketsState,
onSearchEntered = onSearchEntered,
onTokenSelected = onTokenSelected,
isBalanceHidden = isBalanceHidden,
isAfterSearch = tokensDataState.isAfterSearch,
),
)
}
private fun convertIcon(currency: CryptoCurrency, isAvailable: Boolean): CurrencyIconState {
return when (currency) {
is CryptoCurrency.Coin -> {
CurrencyIconState.CoinIcon(
url = currency.iconUrl,
fallbackResId = currency.networkIconResId,
isGrayscale = !isAvailable,
shouldShowCustomBadge = currency.isCustom,
)
}
is CryptoCurrency.Token -> {
val isGrayscale = currency.network.isTestnet
val background = currency.tryGetBackgroundForTokenIcon(isGrayscale)
val tint = getTintForTokenIcon(background)
CurrencyIconState.TokenIcon(
url = currency.iconUrl,
isGrayscale = !isAvailable,
shouldShowCustomBadge = currency.isCustom,
topBadgeIconResId = currency.networkIconResId,
fallbackTint = tint,
fallbackBackground = background,
)
}
}
}
private fun formatCryptoAmount(cryptoCurrencyStatus: CryptoCurrencyStatus): String {
return cryptoCurrencyStatus.value.amount.format {
crypto(cryptoCurrencyStatus.currency)
}
}
private fun formatFiatAmount(cryptoCurrencyStatus: CryptoCurrencyStatus, appCurrency: AppCurrency): String {
return cryptoCurrencyStatus.value.fiatAmount.format {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
}
}
}

View file

@ -1,80 +0,0 @@
package com.tangem.feature.swap.converters
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.feature.swap.domain.models.ui.CurrenciesGroup
import com.tangem.feature.swap.models.SwapSelectTokenStateHolder
import com.tangem.feature.swap.models.SwapStateHolder
import com.tangem.feature.swap.models.TokenListUMData
import com.tangem.feature.swap.presentation.R
import com.tangem.utils.Provider
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.plus
import kotlinx.collections.immutable.toPersistentList
internal class TokensDataConverterV2(
private val onSearchEntered: (String) -> Unit,
private val onTokenSelected: (String) -> Unit,
private val tokensDataState: CurrenciesGroup,
private val isBalanceHidden: Boolean,
private val isAccountsMode: Boolean,
appCurrencyProvider: Provider<AppCurrency>,
) : Transformer<SwapStateHolder> {
private val accountListItemConverter = AccountTokenItemConverter(
appCurrency = appCurrencyProvider(),
unavailableErrorText = resourceReference(R.string.tokens_list_unavailable_to_swap_source_header),
onItemClick = onTokenSelected,
)
override fun transform(prevState: SwapStateHolder): SwapStateHolder {
val accountList = tokensDataState.accountCurrencyList
val currentMarketsState = prevState.selectTokenState?.marketsState
return prevState.copy(
selectTokenState = SwapSelectTokenStateHolder(
availableTokens = persistentListOf(),
unavailableTokens = persistentListOf(),
tokensListData = if (isAccountsMode) {
val portfolioList = accountListItemConverter.convertList(accountList).toPersistentList()
val totalTokensCount = portfolioList.sumOf { it.tokens.size }
if (totalTokensCount > 0) {
TokenListUMData.AccountList(
tokensList = portfolioList,
totalTokensCount = totalTokensCount,
)
} else {
TokenListUMData.EmptyList
}
} else {
val tokensList = accountList.flatMap { (_, currencyList) ->
currencyList.asSequence().map { accountSwapCurrency ->
accountListItemConverter.createAvailableItemConverter()
.convert(accountSwapCurrency.cryptoCurrencyStatus)
}.map(TokensListItemUM::Token).toPersistentList()
}.toPersistentList()
if (tokensList.isNotEmpty()) {
TokenListUMData.TokenList(
tokensList = persistentListOf(
TokensListItemUM.GroupTitle(
id = "available_tokens_title",
text = resourceReference(R.string.exchange_tokens_available_tokens_header),
),
) + tokensList,
totalTokensCount = tokensList.size,
)
} else {
TokenListUMData.EmptyList
}
},
marketsState = currentMarketsState,
onSearchEntered = onSearchEntered,
onTokenSelected = onTokenSelected,
isBalanceHidden = isBalanceHidden,
isAfterSearch = tokensDataState.isAfterSearch,
),
)
}
}

View file

@ -14,8 +14,6 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState.InProgress.getApproveTypeOrNull
import com.tangem.features.approval.api.GiveApprovalComponent
import com.tangem.features.approval.api.GiveApprovalFeatureToggles
import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.core.analytics.api.AnalyticsErrorHandler
import com.tangem.core.analytics.api.AnalyticsEventHandler
@ -36,7 +34,6 @@ import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.utils.InputNumberFormatter
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
@ -103,6 +100,8 @@ import com.tangem.feature.swap.router.SwapNavScreen
import com.tangem.feature.swap.router.SwapRouter
import com.tangem.feature.swap.ui.StateBuilder
import com.tangem.feature.swap.utils.formatToUIRepresentation
import com.tangem.features.approval.api.GiveApprovalComponent
import com.tangem.features.approval.api.GiveApprovalFeatureToggles
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioComponent
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioManager
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
@ -114,8 +113,6 @@ import com.tangem.utils.Provider
import com.tangem.utils.TangemBlogUrlBuilder.RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP
import com.tangem.utils.coroutines.*
import com.tangem.utils.isNullOrZero
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import timber.log.Timber
@ -156,7 +153,6 @@ internal class SwapModel @Inject constructor(
router: AppRouter,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val getTangemPayCurrencyStatusUseCase: GetTangemPayCurrencyStatusUseCase,
private val tangemPayWithdrawUseCase: TangemPayWithdrawUseCase,
private val iGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork,
@ -244,12 +240,12 @@ internal class SwapModel @Inject constructor(
private var toAccountCurrencyStatus: AccountCryptoCurrencyStatus? = null
/**
* If accountsFeatureToggles is off OR user came from Tangem Pay -> fromAccountCurrencyStatus == null
* If accountsFeatureToggles is on AND user didn't come from Tangem Pay -> fromAccountCurrencyStatus != null
* If user came from Tangem Pay -> fromAccountCurrencyStatus == null
* If user didn't come from Tangem Pay -> fromAccountCurrencyStatus != null
*
* Remove when accounts are integrated into Tangem Pay
*/
private val canUseFromAccountCurrencyStatus = accountsFeatureToggles.isFeatureEnabled && tangemPayInput == null
private val canUseFromAccountCurrencyStatus = tangemPayInput == null
private val isUserResolvableError: (SwapState) -> Boolean = { swapState ->
swapState is SwapState.SwapError &&
@ -514,25 +510,25 @@ internal class SwapModel @Inject constructor(
}.onSuccess { state ->
updateTokensState(state)
val (selectedCurrency, selectedAccount) = if (accountsFeatureToggles.isFeatureEnabled) {
val selectedAccountCurrency = toAccountCurrencyStatus ?: swapInteractor.getInitialCurrencyToSwapV2(
initialCryptoCurrency = initialCurrencyFrom,
state = state,
isReverseFromTo = isReverseFromTo,
)?.let { accountSwapCurrency ->
AccountCryptoCurrencyStatus(
account = accountSwapCurrency.account,
status = accountSwapCurrency.cryptoCurrencyStatus,
val (selectedCurrency, selectedAccount) = run {
var selectedAccountCurrency = toAccountCurrencyStatus
if (selectedAccountCurrency == null) {
val amountSwapCurrency = swapInteractor.getInitialCurrencyToSwap(
initialCryptoCurrency = initialCurrencyFrom,
state = state,
isReverseFromTo = isReverseFromTo,
)
if (amountSwapCurrency != null) {
selectedAccountCurrency = AccountCryptoCurrencyStatus(
account = amountSwapCurrency.account,
status = amountSwapCurrency.cryptoCurrencyStatus,
)
}
}
selectedAccountCurrency?.status to selectedAccountCurrency?.account
} else {
val selectedCurrency = initialToStatus ?: swapInteractor.getInitialCurrencyToSwap(
initialCryptoCurrency = initialCurrencyFrom,
state = state,
isReverseFromTo = isReverseFromTo,
)
selectedCurrency to null
}
applyInitialTokenChoice(
@ -695,19 +691,11 @@ internal class SwapModel @Inject constructor(
private fun updateTokensState(tokenDataState: TokensDataStateExpress) {
val tokensDataState = if (isOrderReversed) tokenDataState.fromGroup else tokenDataState.toGroup
uiState = if (accountsFeatureToggles.isFeatureEnabled) {
stateBuilder.addTokensToStateV2(
uiState = uiState,
tokensDataState = tokensDataState,
isAccountsMode = isAccountsMode,
)
} else {
stateBuilder.addTokensToState(
uiState = uiState,
tokensDataState = tokensDataState,
fromToken = dataState.fromCryptoCurrency?.currency ?: initialCurrencyFrom,
)
}
uiState = stateBuilder.addTokensToStateV2(
uiState = uiState,
tokensDataState = tokensDataState,
isAccountsMode = isAccountsMode,
)
latestMarketsState?.let(::applyMarketsState)
}
@ -1366,11 +1354,7 @@ internal class SwapModel @Inject constructor(
fromToken = foundToken
fromAccount = foundAccount
toToken = initialFromStatus
toAccount = if (accountsFeatureToggles.isFeatureEnabled) {
fromAccountCurrencyStatus?.account
} else {
null
}
toAccount = fromAccountCurrencyStatus?.account
val newToken = fromToken.currency as? CryptoCurrency.Coin
if (newToken != null) {
@ -1384,11 +1368,7 @@ internal class SwapModel @Inject constructor(
}
} else {
fromToken = initialFromStatus
fromAccount = if (accountsFeatureToggles.isFeatureEnabled) {
fromAccountCurrencyStatus?.account
} else {
null
}
fromAccount = fromAccountCurrencyStatus?.account
toToken = foundToken
toAccount = foundAccount
@ -1446,26 +1426,16 @@ internal class SwapModel @Inject constructor(
tokens: TokensDataStateExpress,
id: String,
): Pair<CryptoCurrencyStatus?, Account.CryptoPortfolio?> {
return if (accountsFeatureToggles.isFeatureEnabled) {
val accountCryptoCurrencyStatus = if (isOrderReversed) {
tokens.fromGroup
} else {
tokens.toGroup
}.accountCurrencyList.firstNotNullOfOrNull { accountSwapAvailability ->
accountSwapAvailability.currencyList.firstOrNull { accountSwapCurrency ->
accountSwapCurrency.cryptoCurrencyStatus.currency.id.value == id
}
}
accountCryptoCurrencyStatus?.cryptoCurrencyStatus to accountCryptoCurrencyStatus?.account
val accountCryptoCurrencyStatus = if (isOrderReversed) {
tokens.fromGroup
} else {
if (isOrderReversed) {
tokens.fromGroup
} else {
tokens.toGroup
}.available.firstOrNull { swapAvailability ->
swapAvailability.currencyStatus.currency.id.value == id
}?.currencyStatus to null
tokens.toGroup
}.accountCurrencyList.firstNotNullOfOrNull { accountSwapAvailability ->
accountSwapAvailability.currencyList.firstOrNull { accountSwapCurrency ->
accountSwapCurrency.cryptoCurrencyStatus.currency.id.value == id
}
}
return accountCryptoCurrencyStatus?.cryptoCurrencyStatus to accountCryptoCurrencyStatus?.account
}
@Suppress("LongMethod", "CyclomaticComplexMethod")
@ -1476,79 +1446,44 @@ internal class SwapModel @Inject constructor(
) {
Timber.d("Subscribe to ${coin.id} balance updates")
if (accountsFeatureToggles.isFeatureEnabled) {
getAccountCurrencyStatusUseCase(
userWalletId = userWalletId,
currency = coin,
).distinctUntilChanged { old, new -> old.status.value.amount == new.status.value.amount } // Check only balance changes
.onEach { (account, currencyStatus) ->
Timber.d("${coin.id} balance is ${currencyStatus.value.amount ?: "null"}")
getAccountCurrencyStatusUseCase(
userWalletId = userWalletId,
currency = coin,
).distinctUntilChanged { old, new -> old.status.value.amount == new.status.value.amount } // Check only balance changes
.onEach { (account, currencyStatus) ->
Timber.d("${coin.id} balance is ${currencyStatus.value.amount ?: "null"}")
if (isFromCurrency) {
dataState = dataState.copy(
feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = userWalletId,
cryptoCurrencyStatus = currencyStatus,
).getOrNull() ?: currencyStatus,
)
}
uiState = when {
isFromCurrency && currencyStatus.currency.id == dataState.fromCryptoCurrency?.currency?.id -> {
dataState = dataState.copy(
fromCryptoCurrency = currencyStatus,
fromAccount = account,
)
stateBuilder.updateSendCurrencyBalance(uiState, currencyStatus)
}
!isFromCurrency && currencyStatus.currency.id == dataState.toCryptoCurrency?.currency?.id -> {
dataState = dataState.copy(
toCryptoCurrency = currencyStatus,
toAccount = account,
)
stateBuilder.updateReceiveCurrencyBalance(uiState, currencyStatus)
}
else -> {
uiState
}
}
startLoadingQuotesFromLastState(isSilent = true)
if (isFromCurrency) {
dataState = dataState.copy(
feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = userWalletId,
cryptoCurrencyStatus = currencyStatus,
).getOrNull() ?: currencyStatus,
)
}
} else {
getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
userWalletId = userWalletId,
currencyId = coin.id,
isSingleWalletWithTokens = false,
).mapNotNull { either -> (either as? Either.Right)?.value }
.distinctUntilChanged { old, new -> old.value.amount == new.value.amount } // Check only balance changes
.onEach { status ->
Timber.d("${coin.id} balance is ${status.value.amount ?: "null"}")
if (isFromCurrency) {
uiState = when {
isFromCurrency && currencyStatus.currency.id == dataState.fromCryptoCurrency?.currency?.id -> {
dataState = dataState.copy(
feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = userWalletId,
cryptoCurrencyStatus = status,
).getOrNull() ?: status,
fromCryptoCurrency = currencyStatus,
fromAccount = account,
)
stateBuilder.updateSendCurrencyBalance(uiState, currencyStatus)
}
uiState = when {
isFromCurrency && status.currency.id == dataState.fromCryptoCurrency?.currency?.id -> {
dataState = dataState.copy(fromCryptoCurrency = status)
stateBuilder.updateSendCurrencyBalance(uiState, status)
}
!isFromCurrency && status.currency.id == dataState.toCryptoCurrency?.currency?.id -> {
dataState = dataState.copy(toCryptoCurrency = status)
stateBuilder.updateReceiveCurrencyBalance(uiState, status)
}
else -> {
uiState
}
!isFromCurrency && currencyStatus.currency.id == dataState.toCryptoCurrency?.currency?.id -> {
dataState = dataState.copy(
toCryptoCurrency = currencyStatus,
toAccount = account,
)
stateBuilder.updateReceiveCurrencyBalance(uiState, currencyStatus)
}
else -> {
uiState
}
startLoadingQuotesFromLastState(isSilent = true)
}
}.flowOn(dispatchers.main)
startLoadingQuotesFromLastState(isSilent = true)
}
.flowOn(dispatchers.main)
.launchIn(modelScope)
.saveIn(if (isFromCurrency) fromTokenBalanceJobHolder else toTokenBalanceJobHolder)
}
@ -2027,18 +1962,12 @@ internal class SwapModel @Inject constructor(
toToken.currency.id.value
}
return if (accountsFeatureToggles.isFeatureEnabled) {
groupToFind.accountCurrencyList.firstNotNullOfOrNull { (_, currencyList) ->
currencyList.find { accountSwapCurrency ->
idToFind == accountSwapCurrency.cryptoCurrencyStatus.currency.id.value &&
accountSwapCurrency.isAvailable
}
}?.providers
} else {
groupToFind.available.find { swapAvailability ->
idToFind == swapAvailability.currencyStatus.currency.id.value
}?.providers
}
return groupToFind.accountCurrencyList.firstNotNullOfOrNull { (_, currencyList) ->
currencyList.find { accountSwapCurrency ->
idToFind == accountSwapCurrency.cryptoCurrencyStatus.currency.id.value &&
accountSwapCurrency.isAvailable
}
}?.providers
?.filterForTangemPayWithdrawal()
.orEmpty()
}
@ -2082,16 +2011,10 @@ internal class SwapModel @Inject constructor(
val group = if (isReverseFromTo) state.fromGroup else state.toGroup
val idToFind = selectedCurrency.currency.id.value
return if (accountsFeatureToggles.isFeatureEnabled) {
group.accountCurrencyList.any { (_, currencyList) ->
currencyList.any { accountSwapCurrency ->
idToFind == accountSwapCurrency.cryptoCurrencyStatus.currency.id.value &&
accountSwapCurrency.isAvailable
}
}
} else {
group.available.any { swapAvailability ->
idToFind == swapAvailability.currencyStatus.currency.id.value
return group.accountCurrencyList.any { (_, currencyList) ->
currencyList.any { accountSwapCurrency ->
idToFind == accountSwapCurrency.cryptoCurrencyStatus.currency.id.value &&
accountSwapCurrency.isAvailable
}
}
}
@ -2128,14 +2051,10 @@ internal class SwapModel @Inject constructor(
val chosen = if (isOrderReversed) from else to
return if (accountsFeatureToggles.isFeatureEnabled) {
currenciesGroup.accountCurrencyList.flatMap { accountSwapAvailability ->
accountSwapAvailability.currencyList.map { accountSwapCurrency ->
accountSwapCurrency.cryptoCurrencyStatus
}
return currenciesGroup.accountCurrencyList.flatMap { accountSwapAvailability ->
accountSwapAvailability.currencyList.map { accountSwapCurrency ->
accountSwapCurrency.cryptoCurrencyStatus
}
} else {
currenciesGroup.available.map { swapAvailability -> swapAvailability.currencyStatus }
}.map { currencyStatus -> currencyStatus.currency }.contains(chosen.currency)
}

View file

@ -9,6 +9,7 @@ import com.tangem.common.ui.bottomsheet.permission.state.*
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.common.ui.swapStoriesScreen.SwapStoriesFactory
import com.tangem.common.ui.userwallet.ext.walletInterationIcon
import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.*
@ -20,13 +21,11 @@ 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.core.ui.HoldToConfirmButtonFeatureToggles
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isHotWallet
import com.tangem.domain.promo.models.StoryContent
import com.tangem.domain.transaction.usecase.gasless.IsGaslessFeeSupportedForNetwork
import com.tangem.feature.swap.converters.TokensDataConverter
import com.tangem.feature.swap.converters.TokensDataConverterV2
import com.tangem.feature.swap.domain.models.ExpressDataError
import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType
@ -62,7 +61,7 @@ internal class StateBuilder(
private val appCurrencyProvider: Provider<AppCurrency>,
private val isAccountsModeProvider: Provider<Boolean>,
private val iGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork,
private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles,
holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles,
) {
private val isHoldToConfirmEnabled: Boolean =
@ -70,13 +69,6 @@ internal class StateBuilder(
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
private val tokensDataConverter = TokensDataConverter(
onSearchEntered = actions.onSearchEntered,
onTokenSelected = actions.onTokenSelected,
isBalanceHiddenProvider = isBalanceHiddenProvider,
appCurrencyProvider = appCurrencyProvider,
)
private val notificationsFactory by lazy(LazyThreadSafetyMode.NONE) {
SwapNotificationsFactory(actions, iGaslessFeeSupportedForNetwork)
}
@ -677,28 +669,12 @@ internal class StateBuilder(
)
}
fun addTokensToState(
uiState: SwapStateHolder,
fromToken: CryptoCurrency,
tokensDataState: CurrenciesGroup,
): SwapStateHolder {
val currentMarketsState = uiState.selectTokenState?.marketsState
return uiState.copy(
selectTokenState = tokensDataConverter.convert(
value = CurrenciesGroupWithFromCurrency(
fromCurrency = fromToken,
group = tokensDataState,
),
).copy(marketsState = currentMarketsState),
)
}
fun addTokensToStateV2(
uiState: SwapStateHolder,
tokensDataState: CurrenciesGroup,
isAccountsMode: Boolean,
): SwapStateHolder {
return TokensDataConverterV2(
return TokensDataConverter(
onSearchEntered = actions.onSearchEntered,
onTokenSelected = actions.onTokenSelected,
appCurrencyProvider = appCurrencyProvider,