Updated on 2026-08-14
This commit is contained in:
parent
d73ab1aee6
commit
3c5c53ce92
17 changed files with 193 additions and 128 deletions
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,5 +33,4 @@ class SwapPairInfoConverter : Converter<SwapPair, SwapPairDomain> {
|
|||
rateTypes = swapPairProvider.rateTypes.map { rateTypeConverter.convert(it) },
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@ import java.math.BigDecimal
|
|||
|
||||
data class ExchangeQuote(
|
||||
val toAmount: BigDecimal,
|
||||
val allowanceContract: String?
|
||||
val allowanceContract: String?,
|
||||
)
|
||||
|
|
@ -17,7 +17,7 @@ data class SwapPairLeast(
|
|||
|
||||
data class CryptoCurrencySwapInfo(
|
||||
val currencyStatus: CryptoCurrencyStatus,
|
||||
val providers: List<SwapProvider>
|
||||
val providers: List<SwapProvider>,
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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 = ""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue