Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-20 22:33:11 +03:00
parent 6e20ef6027
commit 616c0b2c05
18 changed files with 173 additions and 123 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,8 +4,8 @@ import com.tangem.datasource.api.tangemTech.models.QuotesResponse
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.datasource.local.quote.model.StoredQuote
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
internal class DefaultQuotesStore(
private val dataStore: StringKeyDataStore<StoredQuote>,
@ -13,20 +13,17 @@ internal class DefaultQuotesStore(
override fun get(currenciesIds: Set<CryptoCurrency.ID>): Flow<Set<StoredQuote>> {
return channelFlow {
val flows = currenciesIds.mapNotNull { currencyId ->
currencyId.rawCurrencyId?.let(dataStore::get)
}
if (dataStore.isEmpty() || flows.isEmpty()) {
if (dataStore.isEmpty()) {
send(emptySet())
}
merge(*flows.toTypedArray())
.scan<StoredQuote, Set<StoredQuote>>(emptySet()) { acc, quote ->
acc.addOrReplace(quote) { it.rawCurrencyId == quote.rawCurrencyId }
val quotes = currenciesIds.mapNotNull { currencyId ->
currencyId.rawCurrencyId?.let {
dataStore.getSyncOrNull(it)
}
.filter(Set<StoredQuote>::isNotEmpty)
.collect(::send)
}
send(quotes.toSet())
}
}

View file

@ -4,9 +4,7 @@ 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
@ -14,7 +12,7 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
class GetCryptoCurrencyStatusUseCase(
class GetCryptoCurrencyStatusesUseCase(
internal val currenciesRepository: CurrenciesRepository,
internal val quotesRepository: QuotesRepository,
internal val networksRepository: NetworksRepository,
@ -29,25 +27,9 @@ class GetCryptoCurrencyStatusUseCase(
networksRepository = networksRepository,
)
return operations.getCurrenciesStatusesFlow()
return operations.getCurrenciesStatusesMergedFlow()
.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)
}
}
}

View file

@ -67,6 +67,36 @@ internal class CurrenciesStatusesOperations(
}
}
fun getCurrenciesStatusesMergedFlow(): Flow<Either<Error, List<CryptoCurrencyStatus>>> {
return getMultiCurrencyWalletCurrencies().transformLatest { maybeCurrencies ->
val nonEmptyCurrencies = maybeCurrencies.fold(
ifLeft = { error ->
emit(error.left())
return@transformLatest
},
ifRight = List<CryptoCurrency>::toNonEmptyListOrNull,
)
if (nonEmptyCurrencies == null) {
val emptyCurrenciesStatuses = emptyList<CryptoCurrencyStatus>()
emit(emptyCurrenciesStatuses.right())
return@transformLatest
}
val (networks, currenciesIds) = getIds(nonEmptyCurrencies)
val currenciesFlow = combine(
getQuotes(currenciesIds),
getNetworksStatuses(networks),
) { maybeQuotes, maybeNetworksStatuses ->
createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes, maybeNetworksStatuses)
}
emitAll(currenciesFlow)
}
}
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.GetCryptoCurrencyStatusesUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.Network
@ -39,7 +39,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: GetCryptoCurrencyStatusesUseCase,
) : SwapInteractor {
// TODO: Move to DI
@ -58,7 +58,7 @@ internal class SwapInteractorImpl @Inject constructor(
ifRight = { it },
)
requireNotNull(selectedWallet)
requireNotNull(selectedWallet) { "No selected wallet" }
val walletCurrencyStatuses = getMultiCryptoCurrencyStatusUseCase(selectedWallet.walletId)
.first()
@ -112,12 +112,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 +139,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.GetCryptoCurrencyStatusesUseCase
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: GetCryptoCurrencyStatusesUseCase,
): SwapInteractor {
return SwapInteractorImpl(
transactionManager = transactionManager,
@ -81,8 +81,8 @@ class SwapDomainModule {
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): GetCryptoCurrencyStatusUseCase {
return GetCryptoCurrencyStatusUseCase(
): GetCryptoCurrencyStatusesUseCase {
return GetCryptoCurrencyStatusesUseCase(
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,80 @@ package com.tangem.feature.swap.converters
import com.tangem.common.Provider
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
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.core.ui.extensions.networkIconResId
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
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) }
.toMutableList()
.apply {
this.add(0, availableTitle)
}
.toImmutableList(),
unavailableTokens = value.unavailable.map { tokenWithBalanceToTokenToSelect(it) }
.toMutableList()
.apply {
this.add(0, unavailableTitle)
}
.toImmutableList(),
onSearchEntered = onSearchEntered,
onTokenSelected = onTokenSelected,
network = Network(network.name, network.blockchainId),
)
}
private fun tokenWithBalanceToTokenToSelect(
tokenWithBalance: TokenWithBalanceExpress,
): TokenToSelectState.TokenToSelect {
private fun tokenWithBalanceToTokenToSelect(cryptoCurrencySwapInfo: CryptoCurrencySwapInfo): 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
id = cryptoCurrencyStatus.currency.id.value,
name = cryptoCurrencyStatus.currency.name,
symbol = cryptoCurrencyStatus.currency.symbol,
tokenIcon = TokenIconState.CoinIcon(
url = "",
fallbackResId = 0,
url = cryptoCurrencyStatus.currency.iconUrl,
fallbackResId = cryptoCurrencyStatus.currency.networkIconResId,
isGrayscale = false,
showCustomBadge = false,
showCustomBadge = cryptoCurrencyStatus.currency.isCustom,
),
addedTokenBalanceData = TokenBalanceData(
amount = tokenWithBalance.tokenBalanceData?.amount,
amountEquivalent = tokenWithBalance.tokenBalanceData?.amountEquivalent,
amount = formatCryptoAmount(cryptoCurrencyStatus),
amountEquivalent = formatFiatAmount(cryptoCurrencyStatus, appCurrencyProvider.invoke()),
isBalanceHidden = isBalanceHiddenProvider.invoke(),
),
)
}
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(),
style = TangemTheme.typography.overline,
modifier = Modifier
.padding(
@ -220,7 +219,6 @@ private val token = TokenToSelectState.TokenToSelect(
id = "",
name = "USDC",
symbol = "USDC",
isNative = false,
addedTokenBalanceData = TokenBalanceData(
amount = "15 000 $",
amountEquivalent = "15 000 " +
@ -230,7 +228,7 @@ private val token = TokenToSelectState.TokenToSelect(
)
private val title = TokenToSelectState.Title(
title = "MY TOKENS",
title = stringReference("MY TOKENS"),
)
@Preview
@ -243,7 +241,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 = ""