Updated on 2026-08-14
This commit is contained in:
commit
6d0b295ca3
219 changed files with 3720 additions and 1687 deletions
|
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.tangem.feature.swap.presentation" />
|
||||
|
|
@ -27,8 +27,8 @@ sealed class ProviderState {
|
|||
val subtitle: TextReference,
|
||||
val selectionType: SelectionType,
|
||||
val additionalBadge: AdditionalBadge,
|
||||
val percentLowerThenBest: PercentDifference = PercentDifference.Empty,
|
||||
val namePrefix: PrefixType,
|
||||
val percentLowerThenBest: PercentLowerThanBest = PercentLowerThanBest.Empty,
|
||||
override val onProviderClick: (String) -> Unit,
|
||||
) : ProviderState()
|
||||
|
||||
|
|
@ -61,9 +61,9 @@ sealed class ProviderState {
|
|||
}
|
||||
|
||||
@Immutable
|
||||
sealed class PercentLowerThanBest {
|
||||
data class Value(val value: Float) : PercentLowerThanBest()
|
||||
object Empty : PercentLowerThanBest()
|
||||
sealed class PercentDifference {
|
||||
data class Value(val value: Float) : PercentDifference()
|
||||
object Empty : PercentDifference()
|
||||
}
|
||||
|
||||
object ProviderPercentDiffComparator : Comparator<ProviderState> {
|
||||
|
|
@ -77,14 +77,14 @@ object ProviderPercentDiffComparator : Comparator<ProviderState> {
|
|||
if (o1 is ProviderState.Content && o2 is ProviderState.Content) {
|
||||
val o1Percent = o1.percentLowerThenBest
|
||||
val o2Percent = o2.percentLowerThenBest
|
||||
if (o1Percent is PercentLowerThanBest.Value && o2Percent !is PercentLowerThanBest.Value) {
|
||||
if (o1Percent is PercentDifference.Value && o2Percent !is PercentDifference.Value) {
|
||||
return -1
|
||||
}
|
||||
if (o1Percent !is PercentLowerThanBest.Value && o2Percent is PercentLowerThanBest.Value) {
|
||||
if (o1Percent !is PercentDifference.Value && o2Percent is PercentDifference.Value) {
|
||||
return 1
|
||||
}
|
||||
return if (o1Percent is PercentLowerThanBest.Value && o2Percent is PercentLowerThanBest.Value) {
|
||||
o1Percent.value.compareTo(o2Percent.value)
|
||||
return if (o1Percent is PercentDifference.Value && o2Percent is PercentDifference.Value) {
|
||||
o2Percent.value.compareTo(o1Percent.value)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
|||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig
|
||||
import com.tangem.feature.swap.models.states.PercentLowerThanBest
|
||||
import com.tangem.feature.swap.models.states.PercentDifference
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -114,7 +114,7 @@ private fun ChooseProviderBottomSheet_Preview() {
|
|||
iconUrl = "",
|
||||
subtitle = stringReference("1 000 000"),
|
||||
additionalBadge = ProviderState.AdditionalBadge.BestTrade,
|
||||
percentLowerThenBest = PercentLowerThanBest.Value(-1.0f),
|
||||
percentLowerThenBest = PercentDifference.Value(-1.0f),
|
||||
selectionType = ProviderState.SelectionType.SELECT,
|
||||
namePrefix = ProviderState.PrefixType.NONE,
|
||||
onProviderClick = {},
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import com.tangem.core.ui.components.RectangleShimmer
|
|||
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.states.PercentLowerThanBest
|
||||
import com.tangem.feature.swap.models.states.PercentDifference
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
|
||||
/**
|
||||
|
|
@ -94,11 +94,8 @@ private fun ProviderContentState(
|
|||
.padding(start = TangemTheme.dimens.spacing12)
|
||||
.size(size = TangemTheme.dimens.size40)
|
||||
.clip(TangemTheme.shapes.roundedCorners8),
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(state.iconUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(false)
|
||||
.build(),
|
||||
model = ImageRequest.Builder(context = LocalContext.current).data(state.iconUrl)
|
||||
.crossfade(enable = true).allowHardware(false).build(),
|
||||
loading = { RectangleShimmer(radius = TangemTheme.dimens.radius8) },
|
||||
error = {
|
||||
ErrorProviderIcon(
|
||||
|
|
@ -138,10 +135,12 @@ private fun ProviderContentState(
|
|||
)
|
||||
}
|
||||
when (state.additionalBadge) {
|
||||
ProviderState.AdditionalBadge.BestTrade ->
|
||||
BestTradeItem(Modifier.padding(start = TangemTheme.dimens.spacing4))
|
||||
ProviderState.AdditionalBadge.PermissionRequired ->
|
||||
PermissionBadgeItem(Modifier.padding(start = TangemTheme.dimens.spacing4))
|
||||
ProviderState.AdditionalBadge.BestTrade -> BestTradeItem(
|
||||
Modifier.padding(start = TangemTheme.dimens.spacing4),
|
||||
)
|
||||
ProviderState.AdditionalBadge.PermissionRequired -> PermissionBadgeItem(
|
||||
Modifier.padding(start = TangemTheme.dimens.spacing4),
|
||||
)
|
||||
ProviderState.AdditionalBadge.Empty -> {
|
||||
// no-op
|
||||
}
|
||||
|
|
@ -162,14 +161,19 @@ private fun ProviderContentState(
|
|||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
if (state.percentLowerThenBest is PercentLowerThanBest.Value &&
|
||||
state.percentLowerThenBest.value > 0
|
||||
if (state.percentLowerThenBest is PercentDifference.Value &&
|
||||
state.percentLowerThenBest.value != 0f
|
||||
) {
|
||||
val textColor = if (state.percentLowerThenBest.value > 0) {
|
||||
TangemTheme.colors.icon.accent
|
||||
} else {
|
||||
TangemTheme.colors.text.warning
|
||||
}
|
||||
AnimatedContent(targetState = state.percentLowerThenBest.value, label = "") {
|
||||
Text(
|
||||
text = "-$it%",
|
||||
text = if (it > 0) "+$it%" else "$it%",
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.warning,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing4),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
|
|
@ -198,11 +202,8 @@ private fun ProviderUnavailableState(
|
|||
.padding(start = TangemTheme.dimens.spacing12)
|
||||
.size(size = TangemTheme.dimens.size40)
|
||||
.clip(TangemTheme.shapes.roundedCorners8),
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(state.iconUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(false)
|
||||
.build(),
|
||||
model = ImageRequest.Builder(context = LocalContext.current).data(state.iconUrl)
|
||||
.crossfade(enable = true).allowHardware(false).build(),
|
||||
loading = { RectangleShimmer(radius = TangemTheme.dimens.radius8) },
|
||||
error = {
|
||||
ErrorProviderIcon(
|
||||
|
|
@ -298,8 +299,7 @@ private fun ProviderLoadingState(modifier: Modifier = Modifier) {
|
|||
@Composable
|
||||
private fun BoxScope.ProviderChevron(selectionType: ProviderState.SelectionType, isSelected: Boolean) {
|
||||
when (selectionType) {
|
||||
ProviderState.SelectionType.NONE -> {
|
||||
/* no-op */
|
||||
ProviderState.SelectionType.NONE -> { /* no-op */
|
||||
}
|
||||
ProviderState.SelectionType.CLICK -> {
|
||||
Icon(
|
||||
|
|
@ -345,11 +345,10 @@ private fun BaseContainer(modifier: Modifier = Modifier, content: @Composable Bo
|
|||
@Composable
|
||||
private fun ErrorProviderIcon(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.secondary,
|
||||
shape = TangemTheme.shapes.roundedCorners8,
|
||||
),
|
||||
modifier = modifier.background(
|
||||
color = TangemTheme.colors.background.secondary,
|
||||
shape = TangemTheme.shapes.roundedCorners8,
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
|
|
@ -369,7 +368,7 @@ private fun BestTradeItem(modifier: Modifier = Modifier) {
|
|||
),
|
||||
) {
|
||||
Text(
|
||||
text = "Best rate",
|
||||
text = stringResource(R.string.express_provider_best_rate),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.icon.accent,
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing6),
|
||||
|
|
@ -432,7 +431,7 @@ private class ProviderItemParameterProvider : CollectionPreviewParameterProvider
|
|||
iconUrl = "",
|
||||
subtitle = stringReference(value = "0,64554846 DAI ≈ 1 MATIC"),
|
||||
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
||||
percentLowerThenBest = PercentLowerThanBest.Empty,
|
||||
percentLowerThenBest = PercentDifference.Value(value = 12.0f),
|
||||
selectionType = ProviderState.SelectionType.SELECT,
|
||||
namePrefix = ProviderState.PrefixType.PROVIDED_BY,
|
||||
onProviderClick = {},
|
||||
|
|
@ -440,7 +439,7 @@ private class ProviderItemParameterProvider : CollectionPreviewParameterProvider
|
|||
val contentState2 = contentState.copy(
|
||||
subtitle = stringReference(value = "1 132,46 MATIC"),
|
||||
additionalBadge = ProviderState.AdditionalBadge.PermissionRequired,
|
||||
percentLowerThenBest = PercentLowerThanBest.Value(value = 5f),
|
||||
percentLowerThenBest = PercentDifference.Value(value = 5f),
|
||||
)
|
||||
val unavailableState = ProviderState.Unavailable(
|
||||
id = "1",
|
||||
|
|
|
|||
|
|
@ -693,6 +693,32 @@ internal class StateBuilder(
|
|||
)
|
||||
}
|
||||
|
||||
fun updateSendCurrencyBalance(
|
||||
uiState: SwapStateHolder,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): SwapStateHolder {
|
||||
if (uiState.sendCardData !is SwapCardState.SwapCardData) return uiState
|
||||
|
||||
return uiState.copy(
|
||||
sendCardData = uiState.sendCardData.copy(
|
||||
balance = cryptoCurrencyStatus.getFormattedAmount(isNeedSymbol = false),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun updateReceiveCurrencyBalance(
|
||||
uiState: SwapStateHolder,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): SwapStateHolder {
|
||||
if (uiState.receiveCardData !is SwapCardState.SwapCardData) return uiState
|
||||
|
||||
return uiState.copy(
|
||||
receiveCardData = uiState.receiveCardData.copy(
|
||||
balance = cryptoCurrencyStatus.getFormattedAmount(isNeedSymbol = false),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun updateBalanceHiddenState(uiState: SwapStateHolder, isBalanceHidden: Boolean): SwapStateHolder {
|
||||
if (uiState.sendCardData !is SwapCardState.SwapCardData) return uiState
|
||||
if (uiState.receiveCardData !is SwapCardState.SwapCardData) return uiState
|
||||
|
|
@ -998,6 +1024,7 @@ internal class StateBuilder(
|
|||
fun showSelectProviderBottomSheet(
|
||||
uiState: SwapStateHolder,
|
||||
selectedProviderId: String,
|
||||
bestRatedProviderId: String,
|
||||
pricesLowerBest: Map<String, Float>,
|
||||
providersStates: Map<SwapProvider, SwapState>,
|
||||
unavailableProviders: List<SwapProvider>,
|
||||
|
|
@ -1005,7 +1032,7 @@ internal class StateBuilder(
|
|||
): SwapStateHolder {
|
||||
val availableProvidersStates = providersStates.entries
|
||||
.mapNotNull {
|
||||
it.convertToProviderBottomSheetState(pricesLowerBest, actions.onProviderSelect)
|
||||
it.convertToProviderBottomSheetState(pricesLowerBest, bestRatedProviderId, actions.onProviderSelect)
|
||||
}
|
||||
.sortedWith(ProviderPercentDiffComparator)
|
||||
val unavailableProviderStates = unavailableProviders.map {
|
||||
|
|
@ -1046,8 +1073,8 @@ internal class StateBuilder(
|
|||
it.copy(
|
||||
subtitle = stringReference(rateString),
|
||||
percentLowerThenBest = pricesLowerBest[it.id]?.let { percent ->
|
||||
PercentLowerThanBest.Value(percent)
|
||||
} ?: PercentLowerThanBest.Empty,
|
||||
PercentDifference.Value(percent)
|
||||
} ?: PercentDifference.Value(0f),
|
||||
)
|
||||
} else {
|
||||
it
|
||||
|
|
@ -1093,7 +1120,7 @@ internal class StateBuilder(
|
|||
},
|
||||
readMoreUrl = buildReadMoreUrl(),
|
||||
feeItems = txFeeState.toFeeItemState(),
|
||||
readMore = resourceReference(R.string.common_fee_selector_link_description),
|
||||
readMore = resourceReference(R.string.common_read_more),
|
||||
onReadMoreClick = actions.onFeeReadMoreClick,
|
||||
)
|
||||
return uiState.copy(
|
||||
|
|
@ -1153,18 +1180,21 @@ internal class StateBuilder(
|
|||
|
||||
private fun Map.Entry<SwapProvider, SwapState>.convertToProviderBottomSheetState(
|
||||
pricesLowerBest: Map<String, Float>,
|
||||
bestRatedProviderId: String,
|
||||
onProviderSelect: (String) -> Unit,
|
||||
): ProviderState? {
|
||||
val provider = this.key
|
||||
return when (val state = this.value) {
|
||||
is SwapState.EmptyAmountState -> null
|
||||
is SwapState.QuotesLoadedState -> provider.convertToContentSelectableProviderState(
|
||||
isBestRate = false, // not show best rate in bottom sheet
|
||||
state = state,
|
||||
onProviderClick = onProviderSelect,
|
||||
pricesLowerBest = pricesLowerBest,
|
||||
selectionType = ProviderState.SelectionType.SELECT,
|
||||
)
|
||||
is SwapState.QuotesLoadedState -> {
|
||||
provider.convertToContentSelectableProviderState(
|
||||
isBestRate = bestRatedProviderId == provider.providerId,
|
||||
state = state,
|
||||
onProviderClick = onProviderSelect,
|
||||
pricesLowerBest = pricesLowerBest,
|
||||
selectionType = ProviderState.SelectionType.SELECT,
|
||||
)
|
||||
}
|
||||
is SwapState.SwapError -> getProviderStateForError(
|
||||
swapProvider = provider,
|
||||
fromToken = state.fromTokenInfo.cryptoCurrencyStatus.currency,
|
||||
|
|
@ -1211,7 +1241,7 @@ internal class StateBuilder(
|
|||
private fun createNetworkFeeCoverageNotificationConfig(): NotificationConfig {
|
||||
return NotificationConfig(
|
||||
title = resourceReference(R.string.send_network_fee_warning_title),
|
||||
subtitle = resourceReference(R.string.send_network_fee_warning_content),
|
||||
subtitle = resourceReference(R.string.swapping_network_fee_warning_content),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
)
|
||||
}
|
||||
|
|
@ -1256,7 +1286,7 @@ internal class StateBuilder(
|
|||
subtitle = stringReference(rateString),
|
||||
additionalBadge = badge,
|
||||
selectionType = selectionType,
|
||||
percentLowerThenBest = PercentLowerThanBest.Empty,
|
||||
percentLowerThenBest = PercentDifference.Empty,
|
||||
namePrefix = ProviderState.PrefixType.PROVIDED_BY,
|
||||
onProviderClick = onProviderClick,
|
||||
)
|
||||
|
|
@ -1287,8 +1317,8 @@ internal class StateBuilder(
|
|||
additionalBadge = additionalBadge,
|
||||
selectionType = selectionType,
|
||||
percentLowerThenBest = pricesLowerBest[this.providerId]?.let { percent ->
|
||||
PercentLowerThanBest.Value(percent)
|
||||
} ?: PercentLowerThanBest.Value(0f),
|
||||
PercentDifference.Value(percent)
|
||||
} ?: PercentDifference.Value(0f),
|
||||
namePrefix = ProviderState.PrefixType.NONE,
|
||||
onProviderClick = onProviderClick,
|
||||
)
|
||||
|
|
@ -1323,7 +1353,7 @@ internal class StateBuilder(
|
|||
selectionType = selectionType,
|
||||
subtitle = alertText,
|
||||
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
||||
percentLowerThenBest = PercentLowerThanBest.Empty,
|
||||
percentLowerThenBest = PercentDifference.Empty,
|
||||
namePrefix = if (selectionType != ProviderState.SelectionType.SELECT) {
|
||||
ProviderState.PrefixType.PROVIDED_BY
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.*
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
|
|
@ -13,11 +14,13 @@ 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.GetCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.swap.analytics.SwapEvents
|
||||
import com.tangem.feature.swap.domain.BlockchainInteractor
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
|
|
@ -46,7 +49,6 @@ import java.text.DecimalFormat
|
|||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
typealias SuccessLoadedSwapData = Map<SwapProvider, SwapState.QuotesLoadedState>
|
||||
|
|
@ -62,6 +64,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase,
|
||||
private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase,
|
||||
private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver {
|
||||
|
||||
|
|
@ -104,6 +107,9 @@ internal class SwapViewModel @Inject constructor(
|
|||
(it.error is DataError.ExchangeTooSmallAmountError || it.error is DataError.ExchangeTooBigAmountError)
|
||||
}
|
||||
|
||||
private val fromTokenBalanceJobHolder = JobHolder()
|
||||
private val toTokenBalanceJobHolder = JobHolder()
|
||||
|
||||
val currentScreen: SwapNavScreen
|
||||
get() = swapRouter.currentScreen
|
||||
|
||||
|
|
@ -173,6 +179,23 @@ internal class SwapViewModel @Inject constructor(
|
|||
state,
|
||||
),
|
||||
)
|
||||
|
||||
val userWalletId = swapInteractor.getSelectedWallet()?.walletId ?: return@launch
|
||||
(dataState.fromCryptoCurrency?.currency as? CryptoCurrency.Coin)?.let {
|
||||
subscribeToCoinBalanceUpdates(
|
||||
userWalletId = userWalletId,
|
||||
coin = it,
|
||||
isFromCurrency = true,
|
||||
)
|
||||
}
|
||||
|
||||
(dataState.toCryptoCurrency?.currency as? CryptoCurrency.Coin)?.let {
|
||||
subscribeToCoinBalanceUpdates(
|
||||
userWalletId = userWalletId,
|
||||
coin = it,
|
||||
isFromCurrency = false,
|
||||
)
|
||||
}
|
||||
}.onFailure {
|
||||
Timber.tag(loggingTag).e(it)
|
||||
|
||||
|
|
@ -301,7 +324,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
val (provider, state) = updateLoadedQuotes(providersState)
|
||||
setupLoadedState(provider, state, fromToken)
|
||||
val successStates = providersState.getLastLoadedSuccessStates()
|
||||
val pricesLowerBest = getPricesLowerBest(successStates)
|
||||
val pricesLowerBest = getPricesLowerBest(provider.providerId, successStates)
|
||||
uiState = stateBuilder.updateProvidersBottomSheetContent(
|
||||
uiState = uiState,
|
||||
pricesLowerBest = pricesLowerBest,
|
||||
|
|
@ -617,7 +640,6 @@ internal class SwapViewModel @Inject constructor(
|
|||
unavailable = unavailable,
|
||||
afterSearch = true,
|
||||
),
|
||||
|
||||
)
|
||||
} else {
|
||||
tokenDataState.copy(
|
||||
|
|
@ -647,15 +669,35 @@ internal class SwapViewModel @Inject constructor(
|
|||
analyticsEventHandler.send(SwapEvents.ChooseTokenScreenResult(tokenChosen = true, token = it))
|
||||
}
|
||||
|
||||
val userWalletId = swapInteractor.getSelectedWallet()?.walletId
|
||||
|
||||
if (foundToken != null) {
|
||||
val fromToken: CryptoCurrencyStatus
|
||||
val toToken: CryptoCurrencyStatus
|
||||
if (isOrderReversed) {
|
||||
fromToken = foundToken.currencyStatus
|
||||
toToken = initialCryptoCurrencyStatus
|
||||
|
||||
val newToken = fromToken.currency as? CryptoCurrency.Coin
|
||||
if (userWalletId != null && newToken != null) {
|
||||
subscribeToCoinBalanceUpdates(
|
||||
userWalletId = userWalletId,
|
||||
coin = newToken,
|
||||
isFromCurrency = true,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
fromToken = initialCryptoCurrencyStatus
|
||||
toToken = foundToken.currencyStatus
|
||||
|
||||
val newToken = toToken.currency as? CryptoCurrency.Coin
|
||||
if (userWalletId != null && newToken != null) {
|
||||
subscribeToCoinBalanceUpdates(
|
||||
userWalletId = userWalletId,
|
||||
coin = newToken,
|
||||
isFromCurrency = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
dataState = dataState.copy(
|
||||
fromCryptoCurrency = fromToken,
|
||||
|
|
@ -673,6 +715,36 @@ internal class SwapViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun subscribeToCoinBalanceUpdates(
|
||||
userWalletId: UserWalletId,
|
||||
coin: CryptoCurrency.Coin,
|
||||
isFromCurrency: Boolean,
|
||||
) {
|
||||
Timber.d("Subscribe to ${coin.id} balance updates")
|
||||
|
||||
getCurrencyStatusUpdatesUseCase(
|
||||
userWalletId = userWalletId,
|
||||
currencyId = coin.id,
|
||||
isSingleWalletWithTokens = false,
|
||||
)
|
||||
.mapNotNull { (it as? Either.Right)?.value }
|
||||
.distinctUntilChanged { old, new -> old.value.amount == new.value.amount } // Check only balance changes
|
||||
.onEach {
|
||||
Timber.d("${coin.id} balance is ${it.value.amount}")
|
||||
|
||||
uiState = if (isFromCurrency) {
|
||||
dataState = dataState.copy(fromCryptoCurrency = it)
|
||||
stateBuilder.updateSendCurrencyBalance(uiState, it)
|
||||
} else {
|
||||
dataState = dataState.copy(toCryptoCurrency = it)
|
||||
stateBuilder.updateReceiveCurrencyBalance(uiState, it)
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(viewModelScope)
|
||||
.saveIn(if (isFromCurrency) fromTokenBalanceJobHolder else toTokenBalanceJobHolder)
|
||||
}
|
||||
|
||||
private fun onChangeCardsClicked() {
|
||||
val newFromToken = dataState.toCryptoCurrency
|
||||
val newToToken = dataState.fromCryptoCurrency
|
||||
|
|
@ -835,13 +907,14 @@ internal class SwapViewModel @Inject constructor(
|
|||
onProviderClick = { providerId ->
|
||||
analyticsEventHandler.send(SwapEvents.ProviderClicked)
|
||||
val states = dataState.lastLoadedSwapStates.getLastLoadedSuccessStates()
|
||||
val pricesLowerBest = getPricesLowerBest(states)
|
||||
val pricesLowerBest = getPricesLowerBest(providerId, states)
|
||||
val unavailableProviders = getUnavailableProvidersFor(dataState.lastLoadedSwapStates)
|
||||
uiState = stateBuilder.showSelectProviderBottomSheet(
|
||||
uiState = uiState,
|
||||
selectedProviderId = providerId,
|
||||
pricesLowerBest = pricesLowerBest,
|
||||
unavailableProviders = unavailableProviders,
|
||||
bestRatedProviderId = findBestQuoteProvider(states)?.providerId ?: providerId,
|
||||
providersStates = dataState.lastLoadedSwapStates,
|
||||
) { uiState = stateBuilder.dismissBottomSheet(uiState) }
|
||||
},
|
||||
|
|
@ -950,17 +1023,18 @@ internal class SwapViewModel @Inject constructor(
|
|||
}?.key
|
||||
}
|
||||
|
||||
private fun getPricesLowerBest(state: SuccessLoadedSwapData): Map<String, Float> {
|
||||
val bestRateEntry = state.maxByOrNull { it.value.toTokenInfo.tokenAmount.value } ?: return emptyMap()
|
||||
val bestRate = bestRateEntry.value.toTokenInfo.tokenAmount.value
|
||||
private fun getPricesLowerBest(selectedProviderId: String, state: SuccessLoadedSwapData): Map<String, Float> {
|
||||
val selectedProviderEntry = state.filter { it.key.providerId == selectedProviderId }.entries.firstOrNull()
|
||||
?: return emptyMap()
|
||||
val selectedProviderRate = selectedProviderEntry.value.toTokenInfo.tokenAmount.value
|
||||
val hundredPercent = BigDecimal("100")
|
||||
return state.entries.mapNotNull {
|
||||
if (it.key != bestRateEntry.key) {
|
||||
if (it.key != selectedProviderEntry.key) {
|
||||
val amount = it.value.toTokenInfo.tokenAmount.value
|
||||
val percentDiff = BigDecimal.ONE.minus(
|
||||
amount.divide(bestRate, RoundingMode.HALF_UP),
|
||||
selectedProviderRate.divide(amount, RoundingMode.HALF_UP),
|
||||
).multiply(hundredPercent)
|
||||
it.key.providerId to percentDiff.setScale(2, RoundingMode.HALF_UP).toFloat().absoluteValue
|
||||
it.key.providerId to percentDiff.setScale(2, RoundingMode.HALF_UP).toFloat()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue