Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-21 18:49:21 +03:00
parent 3c5c53ce92
commit 537543636b
19 changed files with 431 additions and 145 deletions

View file

@ -32,13 +32,17 @@ class TokensDataConverter(
availableTokens = value.available.map { tokenWithBalanceToTokenToSelect(it, true) }
.toMutableList()
.apply {
this.add(0, availableTitle)
if (this.isNotEmpty()) {
this.add(0, availableTitle)
}
}
.toImmutableList(),
unavailableTokens = value.unavailable.map { tokenWithBalanceToTokenToSelect(it, false) }
.toMutableList()
.apply {
this.add(0, unavailableTitle)
if (this.isNotEmpty()) {
this.add(0, unavailableTitle)
}
}
.toImmutableList(),
onSearchEntered = onSearchEntered,

View file

@ -0,0 +1,34 @@
package com.tangem.feature.swap.di
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.feature.swap.domain.*
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.scopes.ViewModelScoped
@Module
@InstallIn(ViewModelComponent::class)
class SwapPresentationModule {
@ViewModelScoped
@Provides
fun providesGetCryptoCurrenciesUseCase(
currenciesRepository: CurrenciesRepository,
dispatcherProvider: CoroutineDispatcherProvider,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
): GetCryptoCurrencyStatusSyncUseCase {
return GetCryptoCurrencyStatusSyncUseCase(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
dispatchers = dispatcherProvider,
)
}
}

View file

@ -7,8 +7,8 @@ import com.tangem.core.ui.components.states.SelectableItemsState
import com.tangem.feature.swap.domain.models.ui.TxFee
data class SwapStateHolder(
val sendCardData: SwapCardData,
val receiveCardData: SwapCardData,
val sendCardData: SwapCardState,
val receiveCardData: SwapCardState,
val networkCurrency: String,
val networkId: String,
val blockchainId: String, // not the same as networkId, its local id in app
@ -33,18 +33,28 @@ data class SwapStateHolder(
val onCancelPermissionBottomSheet: () -> Unit = {},
)
data class SwapCardData(
val type: TransactionCardType,
val amountEquivalent: String?,
val coinId: String?,
val amountTextFieldValue: TextFieldValue?,
val tokenIconUrl: String?,
val tokenCurrency: String,
val balance: String,
val isBalanceHidden: Boolean,
val isNotNativeToken: Boolean,
val canSelectAnotherToken: Boolean = false,
)
sealed class SwapCardState {
data class SwapCardData(
val type: TransactionCardType,
val amountEquivalent: String?,
val coinId: String?,
val amountTextFieldValue: TextFieldValue?,
val tokenIconUrl: String?,
val tokenCurrency: String,
val balance: String,
val isBalanceHidden: Boolean,
val isNotNativeToken: Boolean,
val canSelectAnotherToken: Boolean = false,
) : SwapCardState()
data class Empty(
val type: TransactionCardType,
val amountEquivalent: String?,
val amountTextFieldValue: TextFieldValue?,
val canSelectAnotherToken: Boolean = false,
) : SwapCardState()
}
data class SwapButton(
val enabled: Boolean,

View file

@ -8,9 +8,12 @@ import com.tangem.core.ui.components.states.Item
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.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.swap.converters.TokensDataConverter
import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
@ -28,7 +31,7 @@ import kotlinx.collections.immutable.toImmutableList
internal class StateBuilder(
private val actions: UiActions,
private val isBalanceHiddenProvider: Provider<Boolean>,
appCurrencyProvider: Provider<AppCurrency>,
private val appCurrencyProvider: Provider<AppCurrency>,
) {
private val tokensDataConverter = TokensDataConverter(
@ -42,7 +45,7 @@ internal class StateBuilder(
return SwapStateHolder(
networkId = initialCurrency.network.backendId,
blockchainId = networkInfo.blockchainId,
sendCardData = SwapCardData(
sendCardData = SwapCardState.SwapCardData(
type = TransactionCardType.SendCard(actions.onAmountChanged, actions.onAmountSelected),
amountEquivalent = null,
amountTextFieldValue = null,
@ -54,7 +57,7 @@ internal class StateBuilder(
balance = "",
isBalanceHidden = true,
),
receiveCardData = SwapCardData(
receiveCardData = SwapCardState.SwapCardData(
type = TransactionCardType.ReceiveCard(),
amountEquivalent = null,
tokenIconUrl = "",
@ -79,6 +82,53 @@ internal class StateBuilder(
)
}
fun createNoAvailableTokensToSwapState(
uiStateHolder: SwapStateHolder,
fromToken: CryptoCurrencyStatus,
): SwapStateHolder {
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
return uiStateHolder.copy(
sendCardData = SwapCardState.SwapCardData(
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard),
amountTextFieldValue = TextFieldValue(
text = "0",
),
amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}",
tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl,
coinId = uiStateHolder.sendCardData.coinId,
isNotNativeToken = uiStateHolder.sendCardData.isNotNativeToken,
tokenCurrency = uiStateHolder.sendCardData.tokenCurrency,
canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken,
balance = fromToken.getFormattedAmount(),
isBalanceHidden = isBalanceHiddenProvider(),
),
receiveCardData = SwapCardState.Empty(
type = TransactionCardType.ReceiveCard(),
amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}",
amountTextFieldValue = TextFieldValue(
text = "0",
),
canSelectAnotherToken = true,
),
warnings = listOf(
SwapWarning.NoAvailableTokensToSwap(
notificationConfig = NotificationConfig(
title = stringReference("No tokens"),
subtitle = stringReference("Swap tokens not available"),
iconResId = R.drawable.ic_alert_24,
),
),
),
fee = FeeState.Empty,
swapButton = SwapButton(
enabled = false,
loading = false,
onClick = { },
),
updateInProgress = false,
)
}
fun createQuotesLoadingState(
uiStateHolder: SwapStateHolder,
fromToken: CryptoCurrency,
@ -87,8 +137,10 @@ internal class StateBuilder(
): SwapStateHolder {
val canSelectSendToken = mainTokenId != fromToken.id.value // TODO look at id matching
val canSelectReceiveToken = mainTokenId != toToken.id.value // TODO look at id matching
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
return uiStateHolder.copy(
sendCardData = SwapCardData(
sendCardData = SwapCardState.SwapCardData(
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard),
amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue,
amountEquivalent = null,
@ -100,7 +152,7 @@ internal class StateBuilder(
balance = if (!canSelectSendToken) uiStateHolder.sendCardData.balance else "",
isBalanceHidden = isBalanceHiddenProvider(),
),
receiveCardData = SwapCardData(
receiveCardData = SwapCardState.SwapCardData(
type = TransactionCardType.ReceiveCard(),
amountTextFieldValue = null,
amountEquivalent = null,
@ -128,12 +180,15 @@ internal class StateBuilder(
* @param onFeeSetup callback for reset fee after auto update
* @return updated whole screen state
*/
@Suppress("LongMethod")
fun createQuotesLoadedState(
uiStateHolder: SwapStateHolder,
quoteModel: SwapState.QuotesLoadedState,
fromToken: CryptoCurrency,
onFeeSetup: (TxFee) -> Unit,
): SwapStateHolder {
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
val warnings = mutableListOf<SwapWarning>()
if (!quoteModel.preparedSwapConfigState.isAllowedToSpend &&
quoteModel.preparedSwapConfigState.isFeeEnough &&
@ -159,7 +214,7 @@ internal class StateBuilder(
}
val feeState = createFeeState(quoteModel, uiStateHolder, onFeeSetup)
return uiStateHolder.copy(
sendCardData = SwapCardData(
sendCardData = SwapCardState.SwapCardData(
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard),
amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue,
amountEquivalent = quoteModel.fromTokenInfo.tokenFiatBalance,
@ -171,7 +226,7 @@ internal class StateBuilder(
balance = quoteModel.fromTokenInfo.tokenWalletBalance,
isBalanceHidden = isBalanceHiddenProvider(),
),
receiveCardData = SwapCardData(
receiveCardData = SwapCardState.SwapCardData(
type = TransactionCardType.ReceiveCard(),
amountTextFieldValue = TextFieldValue(quoteModel.toTokenInfo.tokenAmount.formatToUIRepresentation()),
amountEquivalent = quoteModel.toTokenInfo.tokenFiatBalance,
@ -208,8 +263,10 @@ internal class StateBuilder(
uiStateHolder: SwapStateHolder,
emptyAmountState: SwapState.EmptyAmountState,
): SwapStateHolder {
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
return uiStateHolder.copy(
sendCardData = SwapCardData(
sendCardData = SwapCardState.SwapCardData(
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard),
amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue,
amountEquivalent = emptyAmountState.zeroAmountEquivalent,
@ -221,7 +278,7 @@ internal class StateBuilder(
balance = emptyAmountState.fromTokenWalletBalance,
isBalanceHidden = isBalanceHiddenProvider(),
),
receiveCardData = SwapCardData(
receiveCardData = SwapCardState.SwapCardData(
type = TransactionCardType.ReceiveCard(),
amountTextFieldValue = TextFieldValue("0"),
amountEquivalent = emptyAmountState.zeroAmountEquivalent,
@ -268,6 +325,7 @@ internal class StateBuilder(
}
fun updateSwapAmount(uiState: SwapStateHolder, amount: String): SwapStateHolder {
if (uiState.sendCardData !is SwapCardState.SwapCardData) return uiState
return uiState.copy(
sendCardData = uiState.sendCardData.copy(
amountTextFieldValue = TextFieldValue(
@ -279,6 +337,8 @@ internal class StateBuilder(
}
fun updateBalanceHiddenState(uiState: SwapStateHolder, isBalanceHidden: Boolean): SwapStateHolder {
if (uiState.sendCardData !is SwapCardState.SwapCardData) return uiState
if (uiState.receiveCardData !is SwapCardState.SwapCardData) return uiState
val patchedSendCardData = uiState.sendCardData.copy(
isBalanceHidden = isBalanceHidden,
)
@ -666,11 +726,26 @@ internal class StateBuilder(
return "$firstAddressPart...$secondAddressPart"
}
private fun CryptoCurrencyStatus.getFormattedAmount(): String {
val amount = value.amount ?: return UNKNOWN_AMOUNT_SIGN
return BigDecimalFormatter.formatCryptoAmount(amount, currency.symbol, currency.decimals)
}
@Suppress("UnusedPrivateMember")
private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String {
val fiatAmount = value.fiatAmount ?: return UNKNOWN_AMOUNT_SIGN
val appCurrency = appCurrencyProvider()
return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol)
}
private companion object {
const val ADDRESS_MIN_LENGTH = 11
const val ADDRESS_FIRST_PART_LENGTH = 7
const val ADDRESS_SECOND_PART_LENGTH = 4
private const val PRICE_IMPACT_THRESHOLD = 0.1
private const val HUNDRED_PERCENTS = 100
private const val UNKNOWN_AMOUNT_SIGN = ""
}
}

View file

@ -145,48 +145,24 @@ private fun MainInfo(state: SwapStateHolder) {
}
val (topCard, bottomCard, button) = createRefs()
val priceImpactWarning = state.warnings.filterIsInstance<SwapWarning.HighPriceImpact>().firstOrNull()
TransactionCard(
type = state.sendCardData.type,
balance = if (state.sendCardData.isBalanceHidden) {
STARS
} else {
state.sendCardData.balance
},
textFieldValue = state.sendCardData.amountTextFieldValue,
amountEquivalent = state.sendCardData.amountEquivalent,
tokenIconUrl = state.sendCardData.tokenIconUrl ?: "",
tokenCurrency = state.sendCardData.tokenCurrency,
priceImpact = priceImpactWarning,
networkIconRes = if (state.sendCardData.isNotNativeToken) networkIconRes else null,
iconPlaceholder = state.sendCardData.coinId?.let {
getActiveIconResByCoinId(it)
},
onChangeTokenClick = if (state.sendCardData.canSelectAnotherToken) state.onSelectTokenClick else null,
TransactionCardData(
priceImpactWarning = priceImpactWarning,
networkIconRes = networkIconRes,
swapCardState = state.sendCardData,
modifier = Modifier.constrainAs(topCard) {
top.linkTo(parent.top)
},
onSelectTokenClick = state.onSelectTokenClick,
)
val marginCard = TangemTheme.dimens.spacing16
TransactionCard(
type = state.receiveCardData.type,
balance = if (state.receiveCardData.isBalanceHidden) STARS else state.receiveCardData.balance,
textFieldValue = state.receiveCardData.amountTextFieldValue,
amountEquivalent = state.receiveCardData.amountEquivalent,
tokenIconUrl = state.receiveCardData.tokenIconUrl ?: "",
tokenCurrency = state.receiveCardData.tokenCurrency,
priceImpact = priceImpactWarning,
networkIconRes = if (state.receiveCardData.isNotNativeToken) networkIconRes else null,
iconPlaceholder = state.receiveCardData.coinId?.let {
getActiveIconResByCoinId(it)
},
onChangeTokenClick = if (state.receiveCardData.canSelectAnotherToken) {
state.onSelectTokenClick
} else {
null
},
TransactionCardData(
priceImpactWarning = priceImpactWarning,
networkIconRes = networkIconRes,
swapCardState = state.receiveCardData,
modifier = Modifier.constrainAs(bottomCard) {
top.linkTo(topCard.bottom, margin = marginCard)
},
onSelectTokenClick = state.onSelectTokenClick,
)
val marginButton = TangemTheme.dimens.spacing32
SwapButton(
@ -200,6 +176,48 @@ private fun MainInfo(state: SwapStateHolder) {
}
}
@Composable
private fun TransactionCardData(
priceImpactWarning: SwapWarning.HighPriceImpact?,
networkIconRes: Int?,
swapCardState: SwapCardState,
onSelectTokenClick: (() -> Unit)?,
modifier: Modifier = Modifier,
) {
when (swapCardState) {
is SwapCardState.Empty -> {
TransactionCardEmpty(
type = swapCardState.type,
amountEquivalent = swapCardState.amountEquivalent,
textFieldValue = swapCardState.amountTextFieldValue,
onChangeTokenClick = if (swapCardState.canSelectAnotherToken) onSelectTokenClick else null,
modifier = modifier,
)
}
is SwapCardState.SwapCardData -> {
TransactionCard(
type = swapCardState.type,
balance = if (swapCardState.isBalanceHidden) {
STARS
} else {
swapCardState.balance
},
textFieldValue = swapCardState.amountTextFieldValue,
amountEquivalent = swapCardState.amountEquivalent,
tokenIconUrl = swapCardState.tokenIconUrl ?: "",
tokenCurrency = swapCardState.tokenCurrency,
priceImpact = priceImpactWarning,
networkIconRes = if (swapCardState.isNotNativeToken) networkIconRes else null,
iconPlaceholder = swapCardState.coinId?.let {
getActiveIconResByCoinId(it)
},
onChangeTokenClick = if (swapCardState.canSelectAnotherToken) onSelectTokenClick else null,
modifier = modifier,
)
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun SwapButton(state: SwapStateHolder, modifier: Modifier = Modifier) {
@ -268,7 +286,8 @@ private fun FeeItem(feeState: FeeState, currency: String) {
}
}
is FeeState.Empty -> {
SmallInfoCard(startText = titleString, endText = "")
// show nothing
// SmallInfoCard(startText = titleString, endText = "")
}
}
}
@ -355,7 +374,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U
// region preview
private val sendCard = SwapCardData(
private val sendCard = SwapCardState.SwapCardData(
type = TransactionCardType.SendCard({}) {},
amountTextFieldValue = TextFieldValue(),
amountEquivalent = "1 000 000",
@ -368,7 +387,7 @@ private val sendCard = SwapCardData(
isBalanceHidden = false,
)
private val receiveCard = SwapCardData(
private val receiveCard = SwapCardState.SwapCardData(
type = TransactionCardType.ReceiveCard(),
amountTextFieldValue = TextFieldValue(),
amountEquivalent = "1 000 000",

View file

@ -68,7 +68,7 @@ fun TransactionCard(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start,
) {
Header(balance = balance, type = type)
Header(balance = stringResource(R.string.common_balance, balance), type = type)
Content(
type = type,
@ -106,6 +106,67 @@ fun TransactionCard(
}
}
@Composable
fun TransactionCardEmpty(
type: TransactionCardType,
amountEquivalent: String?,
textFieldValue: TextFieldValue?,
modifier: Modifier = Modifier,
onChangeTokenClick: (() -> Unit)? = null,
) {
Card(
shape = RoundedCornerShape(TangemTheme.dimens.radius12),
backgroundColor = TangemTheme.colors.background.primary,
elevation = TangemTheme.dimens.elevation2,
modifier = modifier,
) {
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.fillMaxWidth(),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start,
) {
Header(
balance = stringResource(id = R.string.swapping_token_not_available),
type = type,
)
Content(
type = type,
amountEquivalent = amountEquivalent,
textFieldValue = textFieldValue,
priceImpact = null,
)
}
Box(modifier = Modifier.align(Alignment.BottomEnd)) {
Token(
tokenIconUrl = "",
tokenCurrency = "",
iconPlaceholder = R.drawable.ic_no_token_44,
)
}
if (onChangeTokenClick != null) {
Box(modifier = Modifier.align(Alignment.CenterEnd)) {
ChangeTokenSelector()
}
Box(
Modifier
.align(Alignment.CenterEnd)
.height(TangemTheme.dimens.size116)
.width(TangemTheme.dimens.size102)
.clickable(
indication = rememberRipple(bounded = false),
interactionSource = remember { MutableInteractionSource() },
) { onChangeTokenClick() },
)
}
}
}
}
@Composable
private fun Header(type: TransactionCardType, balance: String, modifier: Modifier = Modifier) {
Row(
@ -134,7 +195,7 @@ private fun Header(type: TransactionCardType, balance: String, modifier: Modifie
SpacerW16()
if (balance.isNotBlank()) {
Text(
text = stringResource(R.string.common_balance, balance),
text = balance,
color = TangemTheme.colors.text.tertiary,
style = MaterialTheme.typography.body2,
modifier = Modifier

View file

@ -1,6 +1,6 @@
package com.tangem.feature.swap.viewmodels
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
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
@ -14,8 +14,8 @@ data class SwapProcessDataState(
val fromCurrency: Currency? = null,
@Deprecated("used in old swap mechanism")
val toCurrency: Currency? = null,
val fromCryptoCurrency: CryptoCurrency? = null,
val toCryptoCurrency: CryptoCurrency? = null,
val fromCryptoCurrency: CryptoCurrencyStatus? = null,
val toCryptoCurrency: CryptoCurrencyStatus? = null,
// Amount from input
val amount: String? = null,
val approveDataModel: RequestApproveStateData? = null,

View file

@ -11,7 +11,9 @@ 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.GetCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.swap.analytics.SwapEvents
import com.tangem.feature.swap.domain.BlockchainInteractor
import com.tangem.feature.swap.domain.SwapInteractor
@ -49,11 +51,13 @@ internal class SwapViewModel @Inject constructor(
private val analyticsEventHandler: AnalyticsEventHandler,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver {
private val initialCryptoCurrency: CryptoCurrency = savedStateHandle[SwapFragment.CURRENCY_BUNDLE_KEY]
?: error("no expected parameter CryptoCurrency found`")
private lateinit var initialCryptoCurrencyStatus: CryptoCurrencyStatus
private var isBalanceHidden = true
@ -88,11 +92,20 @@ internal class SwapViewModel @Inject constructor(
get() = swapRouter.currentScreen
init {
swapInteractor.initDerivationPathAndNetwork(
derivationPath = initialCryptoCurrency.network.derivationPath.value,
network = initialCryptoCurrency.network,
)
initTokens()
viewModelScope.launch(dispatchers.io) {
swapInteractor.getSelectedWallet()?.let {
initialCryptoCurrencyStatus =
requireNotNull(getCryptoCurrencyStatusUseCase(it.walletId, initialCryptoCurrency.id).getOrNull()) {
"Failed to get initial crypto currency status"
}
swapInteractor.initDerivationPathAndNetwork(
derivationPath = initialCryptoCurrency.network.derivationPath.value,
network = initialCryptoCurrency.network,
)
initTokens()
}
}
}
override fun onCreate(owner: LifecycleOwner) {
@ -137,44 +150,38 @@ internal class SwapViewModel @Inject constructor(
runCatching(dispatchers.io) {
swapInteractor.getTokensDataState(initialCryptoCurrency)
}.onSuccess { state ->
dataState = dataState.copy(
fromCryptoCurrency = initialCryptoCurrency,
toCryptoCurrency = state.toGroup.available.first().currencyStatus.currency,
tokensDataState = state,
)
updateTokensState(state)
startLoadingQuotes(
fromToken = initialCryptoCurrency,
toToken = state.toGroup.available.first().currencyStatus.currency,
amount = lastAmount.value,
)
applyInitialTokenChoice(state, selectInitialCurrencyToSwap(state))
}.onFailure {
Timber.tag(loggingTag).e(it)
}
}
}
// old flow
// viewModelScope.launch(dispatchers.main) {
// runCatching(dispatchers.io) {
// swapInteractor.initTokensToSwap(currency)
// }
// .onSuccess { state ->
// // dataState = dataState.copy(
// // fromCurrency = state.preselectTokens.fromToken,
// // toCurrency = state.preselectTokens.toToken,
// // )
// // updateTokensState(dataState = state.foundTokensState)
// // startLoadingQuotes(
// // fromToken = state.preselectTokens.fromToken,
// // toToken = state.preselectTokens.toToken,
// // amount = lastAmount.value,
// // )
// }
// .onFailure {
// Timber.tag(loggingTag).e(it)
// }
// }
private fun applyInitialTokenChoice(state: TokensDataStateExpress, selectedCurrency: CryptoCurrencyStatus?) {
val fromCurrencyStatus = initialCryptoCurrencyStatus
dataState = dataState.copy(
fromCryptoCurrency = fromCurrencyStatus,
toCryptoCurrency = selectedCurrency,
tokensDataState = state,
)
if (selectedCurrency == null) {
uiState = stateBuilder.createNoAvailableTokensToSwapState(
uiStateHolder = uiState,
fromToken = fromCurrencyStatus,
)
} else {
startLoadingQuotes(
fromToken = fromCurrencyStatus.currency,
toToken = selectedCurrency.currency,
amount = lastAmount.value,
)
}
}
private fun selectInitialCurrencyToSwap(state: TokensDataStateExpress): CryptoCurrencyStatus? {
// todo add algorithm to select initial currency
return state.toGroup.available.firstOrNull()?.currencyStatus
}
private fun updateTokensState(dataState: TokensDataStateExpress) {
@ -203,7 +210,7 @@ internal class SwapViewModel @Inject constructor(
val toCurrency = dataState.toCryptoCurrency
val amount = dataState.amount
if (fromCurrency != null && toCurrency != null && amount != null) {
startLoadingQuotes(fromCurrency, toCurrency, amount)
startLoadingQuotes(fromCurrency.currency, toCurrency.currency, amount)
}
}
@ -285,8 +292,8 @@ internal class SwapViewModel @Inject constructor(
swapInteractor.onSwap(
networkId = dataState.networkId,
swapStateData = requireNotNull(dataState.swapDataModel),
currencyToSend = requireNotNull(dataState.fromCryptoCurrency),
currencyToGet = requireNotNull(dataState.toCryptoCurrency),
currencyToSend = requireNotNull(dataState.fromCryptoCurrency?.currency),
currencyToGet = requireNotNull(dataState.toCryptoCurrency?.currency),
amountToSwap = requireNotNull(dataState.amount),
fee = requireNotNull(dataState.selectedFee),
)
@ -335,10 +342,10 @@ internal class SwapViewModel @Inject constructor(
approveData = requireNotNull(dataState.approveDataModel) {
"dataState.approveDataModel might not be null"
},
forTokenContractAddress = (dataState.fromCryptoCurrency as? CryptoCurrency.Token)
forTokenContractAddress = (dataState.fromCryptoCurrency?.currency as? CryptoCurrency.Token)
?.contractAddress
?: "",
fromToken = requireNotNull(dataState.fromCryptoCurrency) {
fromToken = requireNotNull(dataState.fromCryptoCurrency?.currency) {
"dataState.fromCurrency might not be null"
},
approveType = requireNotNull(uiState.permissionState as? SwapPermissionState.ReadyForRequest) {
@ -384,28 +391,34 @@ internal class SwapViewModel @Inject constructor(
private fun onTokenSelect(id: String) {
val tokens = dataState.tokensDataState ?: return
val foundToken = tokens.toGroup.available.firstOrNull {
it.currencyStatus.currency.id.value == id
val foundToken = if (isOrderReversed) {
tokens.fromGroup.available.firstOrNull {
it.currencyStatus.currency.id.value == id
}
} else {
tokens.toGroup.available.firstOrNull {
it.currencyStatus.currency.id.value == id
}
}
analyticsEventHandler.send(
event = SwapEvents.SearchTokenClicked(currencySymbol = foundToken?.currencyStatus?.currency?.symbol),
)
if (foundToken != null) {
val fromToken: CryptoCurrency
val toToken: CryptoCurrency
val fromToken: CryptoCurrencyStatus
val toToken: CryptoCurrencyStatus
if (isOrderReversed) {
fromToken = foundToken.currencyStatus.currency
toToken = initialCryptoCurrency
fromToken = foundToken.currencyStatus
toToken = initialCryptoCurrencyStatus
} else {
fromToken = initialCryptoCurrency
toToken = foundToken.currencyStatus.currency
fromToken = initialCryptoCurrencyStatus
toToken = foundToken.currencyStatus
}
dataState = dataState.copy(
fromCryptoCurrency = fromToken,
toCryptoCurrency = toToken,
)
startLoadingQuotes(fromToken, toToken, lastAmount.value)
startLoadingQuotes(fromToken.currency, toToken.currency, lastAmount.value)
swapRouter.openScreen(SwapNavScreen.Main)
}
}
@ -419,13 +432,13 @@ internal class SwapViewModel @Inject constructor(
toCryptoCurrency = newToToken,
)
isOrderReversed = !isOrderReversed
val decimals = blockchainInteractor.getTokenDecimals(newFromToken)
val decimals = newFromToken.currency.decimals
lastAmount.value = cutAmountWithDecimals(decimals, lastAmount.value)
uiState = stateBuilder.updateSwapAmount(
uiState,
inputNumberFormatter.formatWithThousands(lastAmount.value, decimals),
)
startLoadingQuotes(newFromToken, newToToken, lastAmount.value)
startLoadingQuotes(newFromToken.currency, newToToken.currency, lastAmount.value)
}
}
@ -433,20 +446,20 @@ internal class SwapViewModel @Inject constructor(
val fromToken = dataState.fromCryptoCurrency
val toToken = dataState.toCryptoCurrency
if (fromToken != null && toToken != null) {
val decimals = blockchainInteractor.getTokenDecimals(fromToken)
val decimals = fromToken.currency.decimals
val cutValue = cutAmountWithDecimals(decimals, value)
lastAmount.value = cutValue
uiState =
stateBuilder.updateSwapAmount(uiState, inputNumberFormatter.formatWithThousands(cutValue, decimals))
amountDebouncer.debounce(viewModelScope, DEBOUNCE_AMOUNT_DELAY) {
startLoadingQuotes(fromToken, toToken, lastAmount.value)
startLoadingQuotes(fromToken.currency, toToken.currency, lastAmount.value)
}
}
}
private fun onMaxAmountClicked() {
dataState.fromCryptoCurrency?.let {
val balance = swapInteractor.getTokenBalance(initialCryptoCurrency.network.id.value, it)
val balance = swapInteractor.getTokenBalance(initialCryptoCurrency.network.id.value, it.currency)
onAmountChanged(balance.formatToUIRepresentation())
}
}
@ -522,7 +535,7 @@ internal class SwapViewModel @Inject constructor(
dataState = dataState.copy(selectedFee = feeItem.data)
val spendAmount = dataState.amount?.let { amount ->
val fromToken = dataState.fromCryptoCurrency ?: return@let null
swapInteractor.getSwapAmountForToken(amount, fromToken)
swapInteractor.getSwapAmountForToken(amount, fromToken.currency)
} ?: dataState.approveDataModel?.fromTokenAmount
spendAmount ?: return@UiActions
val fromToken = dataState.fromCryptoCurrency ?: return@UiActions
@ -531,7 +544,7 @@ internal class SwapViewModel @Inject constructor(
fee = feeItem.data.feeValue,
spendAmount = spendAmount,
networkId = dataState.networkId,
fromToken = fromToken,
fromToken = fromToken.currency,
)
uiState = stateBuilder.updateFeeSelectedItem(uiState, feeItem, isFeeEnough)
}