Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-21 12:42:58 +03:00
parent d73ab1aee6
commit 3c5c53ce92
17 changed files with 193 additions and 128 deletions

View file

@ -36,8 +36,6 @@ import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Suppress("LargeClass")
class TradeCryptoMiddleware {
@ -259,7 +257,7 @@ class TradeCryptoMiddleware {
private fun openSwap(currency: CryptoCurrency) {
val bundle = bundleOf(
SwapFragment.CURRENCY_BUNDLE_KEY to currency
SwapFragment.CURRENCY_BUNDLE_KEY to currency,
)
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Swap, bundle = bundle))

View file

@ -4,24 +4,21 @@ import arrow.core.Either
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.error.mapper.mapToTokenListError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.operations.TokenListOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
class GetCryptoCurrencyStatusUseCase(
class GetCryptoCurrencyStatusesSyncUseCase(
internal val currenciesRepository: CurrenciesRepository,
internal val quotesRepository: QuotesRepository,
internal val networksRepository: NetworksRepository,
internal val dispatchers: CoroutineDispatcherProvider,
) {
operator fun invoke(userWalletId: UserWalletId): Flow<Either<TokenListError, List<CryptoCurrencyStatus>>> {
suspend operator fun invoke(userWalletId: UserWalletId): Either<TokenListError, List<CryptoCurrencyStatus>> {
val operations = CurrenciesStatusesOperations(
userWalletId = userWalletId,
currenciesRepository = currenciesRepository,
@ -29,25 +26,7 @@ class GetCryptoCurrencyStatusUseCase(
networksRepository = networksRepository,
)
return operations.getCurrenciesStatusesFlow()
.map { maybeCurrenciesStatuses ->
maybeCurrenciesStatuses.mapLeft(CurrenciesStatusesOperations.Error::mapToTokenListError)
}
}
@Suppress("UnusedPrivateMember")
private fun createTokenList(
userWalletId: UserWalletId,
tokens: List<CryptoCurrencyStatus>,
): Flow<Either<TokenListError, TokenList>> {
val operations = TokenListOperations(
userWalletId = userWalletId,
tokens = tokens,
currenciesRepository = currenciesRepository,
)
return operations.getTokenListFlow().map { maybeTokenList ->
maybeTokenList.mapLeft(TokenListOperations.Error::mapToTokenListError)
}
return operations.getCurrenciesStatusesSync()
.mapLeft { error -> error.mapToTokenListError() }
}
}

View file

@ -67,6 +67,24 @@ internal class CurrenciesStatusesOperations(
}
}
suspend fun getCurrenciesStatusesSync(): Either<Error, List<CryptoCurrencyStatus>> {
return either {
catch(
block = {
val nonEmptyCurrencies =
currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId).toNonEmptyListOrNull()
?: return emptyList<CryptoCurrencyStatus>().right()
val (networks, currenciesIds) = getIds(nonEmptyCurrencies)
val quotes = quotesRepository.getQuotesSync(currenciesIds, false).right()
val networkStatuses =
networksRepository.getNetworkStatusesSync(userWalletId, networks, false).right()
return createCurrenciesStatuses(nonEmptyCurrencies, quotes, networkStatuses)
},
catch = { raise(Error.DataError(it)) },
)
}
}
fun getCardCurrenciesStatusesFlow(): Flow<Either<Error, List<CryptoCurrencyStatus>>> {
return flow {
val nonEmptyCurrencies = recover(

View file

@ -265,7 +265,7 @@ internal class SwapRepositoryImpl @Inject constructor(
toNetwork: String,
fromAmount: BigDecimal,
providerId: Int,
rateType: RateType
rateType: RateType,
): ExchangeQuote {
val response = tangemExpressApi.getExchangeQuote(
fromContractAddress,
@ -274,12 +274,12 @@ internal class SwapRepositoryImpl @Inject constructor(
toNetwork,
fromAmount,
providerId,
rateTypeConverter.convertBack(rateType)
rateTypeConverter.convertBack(rateType),
).getOrThrow()
return ExchangeQuote(
toAmount = response.toAmount,
allowanceContract = response.allowanceContract
allowanceContract = response.allowanceContract,
)
}

View file

@ -33,5 +33,4 @@ class SwapPairInfoConverter : Converter<SwapPair, SwapPairDomain> {
rateTypes = swapPairProvider.rateTypes.map { rateTypeConverter.convert(it) },
)
}
}

View file

@ -2,7 +2,7 @@ package com.tangem.feature.swap.domain
import arrow.core.getOrElse
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.Network
@ -22,7 +22,6 @@ import com.tangem.lib.crypto.UserWalletManager
import com.tangem.lib.crypto.models.*
import com.tangem.lib.crypto.models.transactions.SendTxResult
import com.tangem.utils.toFiatString
import kotlinx.coroutines.flow.first
import timber.log.Timber
import java.math.BigDecimal
import java.math.RoundingMode
@ -39,7 +38,7 @@ internal class SwapInteractorImpl @Inject constructor(
private val networksRepository: NetworksRepository,
private val walletFeatureToggles: WalletFeatureToggles,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusUseCase,
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
) : SwapInteractor {
// TODO: Move to DI
@ -58,10 +57,9 @@ internal class SwapInteractorImpl @Inject constructor(
ifRight = { it },
)
requireNotNull(selectedWallet)
requireNotNull(selectedWallet) { "No selected wallet" }
val walletCurrencyStatuses = getMultiCryptoCurrencyStatusUseCase(selectedWallet.walletId)
.first()
.getOrElse { emptyList() }
val walletCurrencyStatusesExceptInitial = walletCurrencyStatuses.filter {
@ -112,12 +110,13 @@ internal class SwapInteractorImpl @Inject constructor(
status?.let { CryptoCurrencySwapInfo(it, pair.providers) }
}
val unavailableCryptoCurrencies =
(cryptoCurrenciesList - availableCryptoCurrencies.map { it.currencyStatus }.toSet())
val unavailableCryptoCurrencies = cryptoCurrenciesList - availableCryptoCurrencies
.map { it.currencyStatus }
.toSet()
return CurrenciesGroup(
available = availableCryptoCurrencies,
unavailable = unavailableCryptoCurrencies.map { CryptoCurrencySwapInfo(it, emptyList()) }
unavailable = unavailableCryptoCurrencies.map { CryptoCurrencySwapInfo(it, emptyList()) },
)
}
@ -138,14 +137,10 @@ internal class SwapInteractorImpl @Inject constructor(
}
}
private fun Currency.getContractAddress(): String {
return when (this) {
is Currency.NativeToken -> "0"
is Currency.NonNativeToken -> this.contractAddress
}
}
suspend fun getPairs(initialCurrency: LeastTokenInfo, currenciesList: List<CryptoCurrency>): List<SwapPairLeast> {
private suspend fun getPairs(
initialCurrency: LeastTokenInfo,
currenciesList: List<CryptoCurrency>,
): List<SwapPairLeast> {
return repository.getPairs(initialCurrency, currenciesList)
}

View file

@ -62,6 +62,7 @@ interface SwapRepository {
amount: BigDecimal?,
): String
@Suppress("LongParameterList")
suspend fun getExchangeQuote(
fromContractAddress: String,
fromNetwork: String,
@ -70,5 +71,5 @@ interface SwapRepository {
fromAmount: BigDecimal,
providerId: Int,
rateType: RateType,
) : ExchangeQuote
): ExchangeQuote
}

View file

@ -2,7 +2,7 @@ package com.tangem.feature.swap.domain.di
import com.tangem.domain.tokens.GetCardTokensListUseCase
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
@ -35,7 +35,7 @@ class SwapDomainModule {
networksRepository: NetworksRepository,
walletFeatureToggles: WalletFeatureToggles,
@SwapScope getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
@SwapScope getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusUseCase,
@SwapScope getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
): SwapInteractor {
return SwapInteractorImpl(
transactionManager = transactionManager,
@ -81,8 +81,8 @@ class SwapDomainModule {
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): GetCryptoCurrencyStatusUseCase {
return GetCryptoCurrencyStatusUseCase(
): GetCryptoCurrencyStatusesSyncUseCase {
return GetCryptoCurrencyStatusesSyncUseCase(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,

View file

@ -4,5 +4,5 @@ import java.math.BigDecimal
data class ExchangeQuote(
val toAmount: BigDecimal,
val allowanceContract: String?
val allowanceContract: String?,
)

View file

@ -17,7 +17,7 @@ data class SwapPairLeast(
data class CryptoCurrencySwapInfo(
val currencyStatus: CryptoCurrencyStatus,
val providers: List<SwapProvider>
val providers: List<SwapProvider>,
)
/**

View file

@ -17,6 +17,8 @@ dependencies {
implementation(projects.common)
/** Domain modules **/
implementation(projects.domain.appCurrency)
implementation(projects.domain.appCurrency.models)
implementation(projects.domain.balanceHiding)
implementation(projects.domain.balanceHiding.models)
@ -28,6 +30,7 @@ dependencies {
implementation(deps.androidx.browser)
/** Compose */
implementation(deps.arrow.core)
implementation(deps.compose.foundation)
implementation(deps.compose.material)
implementation(deps.compose.ui.tooling)

View file

@ -2,52 +2,108 @@ package com.tangem.feature.swap.converters
import com.tangem.common.Provider
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.extensions.getTintForTokenIcon
import com.tangem.core.ui.extensions.networkIconResId
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.tryGetBackgroundForTokenIcon
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
import com.tangem.feature.swap.domain.models.ui.FoundTokensStateExpress
import com.tangem.feature.swap.domain.models.ui.TokenWithBalanceExpress
import com.tangem.feature.swap.models.Network
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.swap.domain.models.domain.CryptoCurrencySwapInfo
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.TokenToSelectState
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toImmutableList
class TokensDataConverter(
private val onSearchEntered: (String) -> Unit,
private val onTokenSelected: (String) -> Unit,
private val isBalanceHiddenProvider: Provider<Boolean>,
) {
private val appCurrencyProvider: Provider<AppCurrency>,
) : Converter<CurrenciesGroup, SwapSelectTokenStateHolder> {
fun convertWithNetwork(value: FoundTokensStateExpress, network: NetworkInfo): SwapSelectTokenStateHolder {
override fun convert(value: CurrenciesGroup): SwapSelectTokenStateHolder {
val availableTitle = TokenToSelectState.Title(stringReference("My tokens")) // todo replace with resource
val unavailableTitle = TokenToSelectState.Title(stringReference("My tokens")) // todo replace with resource
return SwapSelectTokenStateHolder(
availableTokens = value.tokensInWallet.map { tokenWithBalanceToTokenToSelect(it) }.toImmutableList(),
unavailableTokens = value.loadedTokens.map { tokenWithBalanceToTokenToSelect(it) }.toImmutableList(),
availableTokens = value.available.map { tokenWithBalanceToTokenToSelect(it, true) }
.toMutableList()
.apply {
this.add(0, availableTitle)
}
.toImmutableList(),
unavailableTokens = value.unavailable.map { tokenWithBalanceToTokenToSelect(it, false) }
.toMutableList()
.apply {
this.add(0, unavailableTitle)
}
.toImmutableList(),
onSearchEntered = onSearchEntered,
onTokenSelected = onTokenSelected,
network = Network(network.name, network.blockchainId),
)
}
private fun tokenWithBalanceToTokenToSelect(
tokenWithBalance: TokenWithBalanceExpress,
): TokenToSelectState.TokenToSelect {
cryptoCurrencySwapInfo: CryptoCurrencySwapInfo,
isAvailable: Boolean,
): TokenToSelectState {
val cryptoCurrencyStatus = cryptoCurrencySwapInfo.currencyStatus
return TokenToSelectState.TokenToSelect(
id = tokenWithBalance.token.id.value,
name = tokenWithBalance.token.name,
symbol = tokenWithBalance.token.symbol,
isNative = tokenWithBalance.token is CryptoCurrency.Coin,
// todo replace converting
tokenIcon = TokenIconState.CoinIcon(
url = "",
fallbackResId = 0,
isGrayscale = false,
showCustomBadge = false,
),
id = cryptoCurrencyStatus.currency.id.value,
name = cryptoCurrencyStatus.currency.name,
symbol = cryptoCurrencyStatus.currency.symbol,
available = isAvailable,
tokenIcon = convertIcon(cryptoCurrencyStatus.currency, isAvailable),
addedTokenBalanceData = TokenBalanceData(
amount = tokenWithBalance.tokenBalanceData?.amount,
amountEquivalent = tokenWithBalance.tokenBalanceData?.amountEquivalent,
amount = formatCryptoAmount(cryptoCurrencyStatus),
amountEquivalent = formatFiatAmount(cryptoCurrencyStatus, appCurrencyProvider.invoke()),
isBalanceHidden = isBalanceHiddenProvider.invoke(),
),
)
}
private fun convertIcon(currency: CryptoCurrency, isAvailable: Boolean): TokenIconState {
return when (currency) {
is CryptoCurrency.Coin -> {
TokenIconState.CoinIcon(
url = currency.iconUrl,
fallbackResId = currency.networkIconResId,
isGrayscale = !isAvailable,
showCustomBadge = currency.isCustom,
)
}
is CryptoCurrency.Token -> {
val isGrayscale = currency.network.isTestnet
val background = currency.tryGetBackgroundForTokenIcon(isGrayscale)
val tint = getTintForTokenIcon(background)
TokenIconState.TokenIcon(
url = currency.iconUrl,
isGrayscale = !isAvailable,
showCustomBadge = currency.isCustom,
networkBadgeIconResId = currency.networkIconResId,
fallbackTint = tint,
fallbackBackground = background,
)
}
}
}
private fun formatCryptoAmount(cryptoCurrencyStatus: CryptoCurrencyStatus): String {
return BigDecimalFormatter.formatCryptoAmount(
cryptoCurrencyStatus.value.amount,
cryptoCurrencyStatus.currency.symbol,
cryptoCurrencyStatus.currency.decimals,
)
}
private fun formatFiatAmount(cryptoCurrencyStatus: CryptoCurrencyStatus, appCurrency: AppCurrency): String {
return BigDecimalFormatter.formatFiatAmount(
fiatAmount = cryptoCurrencyStatus.value.fiatAmount,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
}
}

View file

@ -1,30 +1,24 @@
package com.tangem.feature.swap.models
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
data class SwapSelectTokenStateHolder(
val availableTokens: ImmutableList<TokenToSelectState>,
val unavailableTokens: ImmutableList<TokenToSelectState>,
val network: Network,
val onSearchEntered: (String) -> Unit,
val onTokenSelected: (String) -> Unit,
)
data class Network(
val name: String,
val blockchainId: String,
)
sealed class TokenToSelectState {
data class Title(val title: String) : TokenToSelectState()
data class Title(val title: TextReference) : TokenToSelectState()
data class TokenToSelect(
val id: String,
val name: String,
val symbol: String,
val isNative: Boolean,
val tokenIcon: TokenIconState,
val available: Boolean = true,
val addedTokenBalanceData: TokenBalanceData? = null,

View file

@ -9,6 +9,7 @@ import com.tangem.core.ui.components.states.SelectableItemsState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.converters.TokensDataConverter
import com.tangem.feature.swap.domain.models.DataError
@ -24,12 +25,17 @@ import kotlinx.collections.immutable.toImmutableList
* State builder creates a specific states for SwapScreen
*/
@Suppress("LargeClass")
internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider: Provider<Boolean>) {
internal class StateBuilder(
private val actions: UiActions,
private val isBalanceHiddenProvider: Provider<Boolean>,
appCurrencyProvider: Provider<AppCurrency>,
) {
private val tokensDataConverter = TokensDataConverter(
onSearchEntered = actions.onSearchEntered,
onTokenSelected = actions.onTokenSelected,
isBalanceHiddenProvider = isBalanceHiddenProvider,
appCurrencyProvider = appCurrencyProvider,
)
fun createInitialLoadingState(initialCurrency: CryptoCurrency, networkInfo: NetworkInfo): SwapStateHolder {
@ -247,15 +253,10 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider:
)
}
fun addTokensToState(
uiState: SwapStateHolder,
dataState: FoundTokensStateExpress,
networkInfo: NetworkInfo,
): SwapStateHolder {
fun addTokensToState(uiState: SwapStateHolder, tokensDataState: CurrenciesGroup): SwapStateHolder {
return uiState.copy(
selectTokenState = tokensDataConverter.convertWithNetwork(
value = dataState,
network = networkInfo,
selectTokenState = tokensDataConverter.convert(
value = tokensDataState,
),
)
}

View file

@ -11,7 +11,6 @@ import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.common.Strings
@ -20,7 +19,8 @@ import com.tangem.core.ui.components.appbar.ExpandableSearchView
import com.tangem.core.ui.components.currency.tokenicon.TokenIcon
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.swap.models.*
import com.tangem.feature.swap.presentation.R
@ -43,8 +43,7 @@ fun SwapSelectTokenScreen(state: SwapSelectTokenStateHolder, onBack: () -> Unit)
placeholderSearchText = stringResource(id = R.string.common_search_tokens),
onSearchChange = state.onSearchEntered,
onSearchDisplayClose = { state.onSearchEntered("") },
subtitle = state.network.name,
icon = painterResource(id = getActiveIconRes(state.network.blockchainId)),
subtitle = "", // todo add title
)
},
)
@ -112,7 +111,7 @@ private fun TitleHeader(item: TokenToSelectState.Title, modifier: Modifier = Mod
.background(TangemTheme.colors.background.action),
) {
Text(
text = item.title,
text = item.title.resolveReference().uppercase(),
style = TangemTheme.typography.overline,
modifier = Modifier
.padding(
@ -134,7 +133,10 @@ private fun TokenItem(
modifier = modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size72)
.clickable(onClick = onTokenClick)
.clickable(
enabled = token.available,
onClick = onTokenClick,
)
.padding(
vertical = TangemTheme.dimens.spacing14,
horizontal = TangemTheme.dimens.spacing16,
@ -170,13 +172,7 @@ private fun TokenItem(
Spacer(modifier = Modifier.weight(1f))
if (!token.available) {
Text(
text = stringResource(id = R.string.swapping_token_not_available),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
} else if (token.addedTokenBalanceData != null) {
if (token.addedTokenBalanceData != null) {
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.Center,
@ -191,7 +187,11 @@ private fun TokenItem(
token.addedTokenBalanceData.amountEquivalent.orEmpty()
},
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
color = if (token.available) {
TangemTheme.colors.text.primary1
} else {
TangemTheme.colors.text.tertiary
},
)
SpacerW2()
Text(
@ -220,7 +220,6 @@ private val token = TokenToSelectState.TokenToSelect(
id = "",
name = "USDC",
symbol = "USDC",
isNative = false,
addedTokenBalanceData = TokenBalanceData(
amount = "15 000 $",
amountEquivalent = "15 000 " +
@ -230,7 +229,7 @@ private val token = TokenToSelectState.TokenToSelect(
)
private val title = TokenToSelectState.Title(
title = "MY TOKENS",
title = stringReference("MY TOKENS"),
)
@Preview
@ -243,7 +242,6 @@ private fun TokenScreenPreview() {
unavailableTokens = listOf(title, token, token, token).toImmutableList(),
onSearchEntered = {},
onTokenSelected = {},
network = Network("Ethereum", "ETH"),
),
onBack = {},
)

View file

@ -4,6 +4,7 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.domain.models.domain.Currency
import com.tangem.feature.swap.domain.models.ui.RequestApproveStateData
import com.tangem.feature.swap.domain.models.ui.SwapStateData
import com.tangem.feature.swap.domain.models.ui.TokensDataStateExpress
import com.tangem.feature.swap.domain.models.ui.TxFee
data class SwapProcessDataState(
@ -20,4 +21,5 @@ data class SwapProcessDataState(
val approveDataModel: RequestApproveStateData? = null,
val swapDataModel: SwapStateData? = null,
val selectedFee: TxFee? = null,
val tokensDataState: TokensDataStateExpress? = null,
)

View file

@ -4,9 +4,12 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.*
import arrow.core.getOrElse
import com.tangem.common.Provider
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.utils.InputNumberFormatter
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.analytics.SwapEvents
@ -27,11 +30,9 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.Debouncer
import com.tangem.utils.coroutines.runCatching
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import timber.log.Timber
import java.text.DecimalFormat
import java.text.NumberFormat
@ -47,6 +48,7 @@ internal class SwapViewModel @Inject constructor(
private val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver {
@ -55,9 +57,12 @@ internal class SwapViewModel @Inject constructor(
private var isBalanceHidden = true
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
private val stateBuilder = StateBuilder(
actions = createUiActions(),
isBalanceHiddenProvider = Provider { isBalanceHidden },
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
)
private val inputNumberFormatter =
@ -87,7 +92,7 @@ internal class SwapViewModel @Inject constructor(
derivationPath = initialCryptoCurrency.network.derivationPath.value,
network = initialCryptoCurrency.network,
)
initTokens(initialCryptoCurrency)
initTokens()
}
override fun onCreate(owner: LifecycleOwner) {
@ -126,7 +131,7 @@ internal class SwapViewModel @Inject constructor(
}
@Suppress("UnusedPrivateMember")
private fun initTokens(currency: CryptoCurrency) {
private fun initTokens() {
// new flow
viewModelScope.launch(dispatchers.main) {
runCatching(dispatchers.io) {
@ -134,10 +139,11 @@ internal class SwapViewModel @Inject constructor(
}.onSuccess { state ->
dataState = dataState.copy(
fromCryptoCurrency = initialCryptoCurrency,
toCryptoCurrency = state.toGroup.available.first().currencyStatus.currency
toCryptoCurrency = state.toGroup.available.first().currencyStatus.currency,
tokensDataState = state,
)
// updateTokensState(dataState = state.foundTokensState)
updateTokensState(state)
startLoadingQuotes(
fromToken = initialCryptoCurrency,
toToken = state.toGroup.available.first().currencyStatus.currency,
@ -171,11 +177,11 @@ internal class SwapViewModel @Inject constructor(
// }
}
private fun updateTokensState(dataState: FoundTokensStateExpress) {
private fun updateTokensState(dataState: TokensDataStateExpress) {
val tokensDataState = if (!isOrderReversed) dataState.toGroup else dataState.fromGroup
uiState = stateBuilder.addTokensToState(
uiState = uiState,
dataState = dataState,
networkInfo = blockchainInteractor.getBlockchainInfo(initialCryptoCurrency.network.backendId),
tokensDataState = tokensDataState,
)
}
@ -369,28 +375,31 @@ internal class SwapViewModel @Inject constructor(
swapInteractor.searchTokens(dataState.networkId, searchQuery)
}
.onSuccess {
updateTokensState(it)
// updateTokensState(it)
}
.onFailure { }
}
}
private fun onTokenSelect(id: String) {
val foundToken = swapInteractor.findTokenById(id)
val tokens = dataState.tokensDataState ?: return
val foundToken = tokens.toGroup.available.firstOrNull {
it.currencyStatus.currency.id.value == id
}
analyticsEventHandler.send(
event = SwapEvents.SearchTokenClicked(currencySymbol = foundToken?.symbol),
event = SwapEvents.SearchTokenClicked(currencySymbol = foundToken?.currencyStatus?.currency?.symbol),
)
if (foundToken != null) {
val fromToken: CryptoCurrency
val toToken: CryptoCurrency
if (isOrderReversed) {
fromToken = foundToken
fromToken = foundToken.currencyStatus.currency
toToken = initialCryptoCurrency
} else {
fromToken = initialCryptoCurrency
toToken = foundToken
toToken = foundToken.currencyStatus.currency
}
dataState = dataState.copy(
fromCryptoCurrency = fromToken,
@ -532,6 +541,18 @@ internal class SwapViewModel @Inject constructor(
)
}
private fun createSelectedAppCurrencyFlow(): StateFlow<AppCurrency> {
return getSelectedAppCurrencyUseCase()
.map { maybeAppCurrency ->
maybeAppCurrency.getOrElse { AppCurrency.Default }
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = AppCurrency.Default,
)
}
companion object {
private const val loggingTag = "SwapViewModel"
private const val INITIAL_AMOUNT = ""