Updated on 2026-08-14
This commit is contained in:
commit
ffda552327
12 changed files with 223 additions and 191 deletions
|
|
@ -66,10 +66,12 @@ fun NotificationWithBackground(config: NotificationConfig, modifier: Modifier =
|
|||
Image(
|
||||
painter = painterResource(config.iconResId),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.constrainAs(iconRef) {
|
||||
start.linkTo(parent.start, spacing12)
|
||||
linkTo(titleRef.top, subtitleRef.bottom, bias = 0.1f)
|
||||
},
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size34)
|
||||
.constrainAs(iconRef) {
|
||||
start.linkTo(parent.start, spacing12)
|
||||
linkTo(titleRef.top, subtitleRef.bottom, bias = 0.1f)
|
||||
},
|
||||
)
|
||||
Text(
|
||||
text = config.title.resolveReference(),
|
||||
|
|
@ -147,7 +149,7 @@ private class NotificationWithBackgroundPreviewProvider : PreviewParameterProvid
|
|||
NotificationConfig(
|
||||
title = resourceReference(id = R.string.main_swap_promotion_title),
|
||||
subtitle = resourceReference(id = R.string.main_swap_promotion_message),
|
||||
iconResId = R.drawable.ic_swap_promo_34,
|
||||
iconResId = R.drawable.img_swap_promo,
|
||||
backgroundResId = R.drawable.img_swap_promo_banner_background,
|
||||
),
|
||||
NotificationConfig(
|
||||
|
|
@ -158,7 +160,7 @@ private class NotificationWithBackgroundPreviewProvider : PreviewParameterProvid
|
|||
"currencies between any chains you wish. Commission free period " +
|
||||
"till Dec 31.",
|
||||
),
|
||||
iconResId = R.drawable.ic_swap_promo_34,
|
||||
iconResId = R.drawable.img_swap_promo,
|
||||
backgroundResId = R.drawable.img_swap_promo_banner_background,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(id = R.string.token_swap_promotion_button),
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="34dp"
|
||||
android:height="34dp"
|
||||
android:viewportWidth="34"
|
||||
android:viewportHeight="34">
|
||||
<path
|
||||
android:pathData="M22,0C28.628,0 34,5.373 34,12C34,18.628 28.628,24 22,24C15.373,24 10,18.628 10,12C10,5.373 15.373,0 22,0Z"
|
||||
android:fillColor="#F5AC37"/>
|
||||
<path
|
||||
android:pathData="M22.442,12.847H27.002C27.099,12.847 27.145,12.847 27.152,12.72C27.189,12.256 27.189,11.79 27.152,11.325C27.152,11.235 27.107,11.198 27.01,11.198H17.935C17.823,11.198 17.792,11.235 17.792,11.34V12.675C17.792,12.847 17.792,12.847 17.972,12.847H22.442ZM26.643,9.638C26.656,9.604 26.656,9.566 26.643,9.533C26.567,9.367 26.477,9.209 26.372,9.06C26.214,8.807 26.028,8.573 25.817,8.363C25.717,8.236 25.601,8.122 25.472,8.025C24.823,7.473 24.051,7.082 23.221,6.885C22.803,6.791 22.375,6.746 21.947,6.75H17.919C17.807,6.75 17.792,6.795 17.792,6.893V9.555C17.792,9.667 17.792,9.698 17.934,9.698H26.589C26.589,9.698 26.664,9.682 26.679,9.638H26.642H26.643ZM26.643,14.407C26.515,14.393 26.387,14.393 26.26,14.407H17.942C17.83,14.407 17.792,14.407 17.792,14.558V17.16C17.792,17.28 17.792,17.31 17.942,17.31H21.782C21.966,17.325 22.149,17.312 22.329,17.273C22.887,17.233 23.435,17.112 23.958,16.913C24.147,16.847 24.331,16.761 24.504,16.658H24.557C25.457,16.19 26.188,15.455 26.649,14.552C26.649,14.552 26.702,14.438 26.643,14.408V14.407ZM16.285,18.66V18.615V16.868V16.275V14.513C16.285,14.415 16.285,14.4 16.165,14.4H14.538C14.447,14.4 14.41,14.4 14.41,14.281V12.856H16.15C16.247,12.856 16.285,12.856 16.285,12.728V11.318C16.285,11.228 16.285,11.206 16.165,11.206H14.538C14.447,11.206 14.41,11.206 14.41,11.086V9.766C14.41,9.683 14.41,9.661 14.53,9.661H16.142C16.255,9.661 16.285,9.661 16.285,9.519V5.476C16.285,5.356 16.285,5.326 16.435,5.326H22.06C22.468,5.342 22.874,5.387 23.275,5.461C24.102,5.614 24.896,5.909 25.622,6.331C26.104,6.615 26.547,6.957 26.942,7.351C27.239,7.66 27.507,7.993 27.744,8.349C27.98,8.709 28.177,9.094 28.33,9.496C28.349,9.601 28.45,9.672 28.555,9.654H29.897C30.069,9.654 30.069,9.654 30.077,9.819V11.049C30.077,11.169 30.032,11.199 29.912,11.199H28.876C28.772,11.199 28.741,11.199 28.749,11.334C28.79,11.791 28.79,12.25 28.749,12.706C28.749,12.834 28.749,12.849 28.892,12.849H30.076C30.129,12.917 30.076,12.984 30.076,13.052C30.084,13.139 30.084,13.227 30.076,13.314V14.222C30.076,14.349 30.039,14.387 29.926,14.387H28.509C28.41,14.368 28.314,14.431 28.291,14.53C27.954,15.407 27.413,16.194 26.716,16.824C26.461,17.054 26.193,17.27 25.913,17.469C25.613,17.642 25.321,17.822 25.013,17.965C24.447,18.219 23.853,18.407 23.243,18.527C22.664,18.63 22.077,18.677 21.488,18.669H16.283V18.662L16.285,18.66Z"
|
||||
android:fillColor="#FEFEFD"/>
|
||||
<path
|
||||
android:pathData="M8.063,10.661C3.37,12.29 0,16.751 0,22C0,28.627 5.373,34 12,34C17.249,34 21.71,30.631 23.339,25.937C22.898,25.979 22.452,26 22,26C14.269,26 8,19.732 8,12C8,11.548 8.021,11.101 8.063,10.661Z"
|
||||
android:fillColor="#8247E5"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M8.324,15.007C8.19,15.022 8.055,15.061 7.927,15.125L4.501,17.131C4.167,17.298 4,17.632 4,17.966V21.893C4,22.227 4.167,22.562 4.501,22.77L7.885,24.734C8.178,24.901 8.554,24.901 8.888,24.734L11.186,23.397L12.738,22.498C12.328,22.136 11.939,21.751 11.574,21.344L11.186,21.559L8.888,22.937C8.595,23.105 8.219,23.105 7.885,22.937L6.089,21.851C5.796,21.684 5.587,21.35 5.587,20.974V18.885C5.587,18.551 5.754,18.217 6.089,18.008L7.885,16.964C8.178,16.796 8.554,16.796 8.888,16.964L8.911,16.978C8.668,16.341 8.472,15.683 8.324,15.007ZM20,25.858C19.423,25.776 18.857,25.658 18.305,25.507C18.228,25.711 18.083,25.895 17.87,26.029L16.115,27.073C15.822,27.24 15.446,27.24 15.112,27.073L13.316,26.029C13.023,25.862 12.814,25.528 12.814,25.152V23.815L11.227,24.734V26.112C11.227,26.447 11.394,26.781 11.729,26.99L15.112,28.953C15.211,29.01 15.319,29.047 15.432,29.065C15.654,29.101 15.894,29.064 16.115,28.953L19.499,26.99C19.791,26.823 20,26.488 20,26.112V25.858Z"
|
||||
android:fillColor="#FEFEFD"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
BIN
core/ui/src/main/res/drawable/img_swap_promo.webp
Normal file
BIN
core/ui/src/main/res/drawable/img_swap_promo.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
|
|
@ -16,6 +16,8 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
|
@ -89,30 +91,36 @@ class GetCurrencyWarningsUseCase(
|
|||
cryptoStatuses.fold(
|
||||
ifLeft = { null },
|
||||
ifRight = { cryptoCurrencyStatuses ->
|
||||
val pairs = swapRepository.getPairs(
|
||||
LeastTokenInfo(
|
||||
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress ?: "0",
|
||||
network = currency.network.backendId,
|
||||
),
|
||||
cryptoCurrencyStatuses.map { it.currency },
|
||||
)
|
||||
val pairs = runCatching(dispatchers.io) {
|
||||
swapRepository.getPairs(
|
||||
LeastTokenInfo(
|
||||
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress ?: "0",
|
||||
network = currency.network.backendId,
|
||||
),
|
||||
cryptoCurrencyStatuses.map { it.currency },
|
||||
)
|
||||
}.getOrNull()?.pairs ?: emptyList()
|
||||
|
||||
val filteredCurrencies = cryptoCurrencyStatuses.filterNot {
|
||||
it.currency.network.backendId == currency.network.backendId
|
||||
it.currency.id == currency.id
|
||||
}
|
||||
val currencyPairs = pairs.pairs.filter {
|
||||
val currencyPairs = pairs.filter {
|
||||
it.from.network == currency.network.backendId ||
|
||||
it.to.network == currency.network.backendId
|
||||
}
|
||||
val isExchangeable = currencyPairs.any { pair ->
|
||||
val availablePair = if (currencyStatus.value.amount.isZero()) {
|
||||
filteredCurrencies.filterNot { it.value.amount.isZero() }
|
||||
val availablePair = if (currencyStatus.value.amount.isNullOrZero()) {
|
||||
filteredCurrencies.filterNot { it.value.amount.isNullOrZero() }
|
||||
} else {
|
||||
filteredCurrencies
|
||||
}
|
||||
availablePair
|
||||
.any {
|
||||
it.currency.network.backendId == pair.to.network ||
|
||||
it.currency.network.backendId == pair.from.network
|
||||
(
|
||||
it.currency.network.backendId == pair.to.network ||
|
||||
it.currency.network.backendId == pair.from.network
|
||||
) &&
|
||||
!it.value.amount.isNullOrZero()
|
||||
}
|
||||
}
|
||||
if (isExchangeable) {
|
||||
|
|
|
|||
|
|
@ -102,15 +102,15 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
currency = currency,
|
||||
leastPairs = pairsLeast.pairs,
|
||||
cryptoCurrenciesList = walletCurrencyStatusesExceptInitial,
|
||||
tokenInfoForFilter = { it.from },
|
||||
tokenInfoForAvailable = { it.to },
|
||||
tokenInfoForFilter = { it.to },
|
||||
tokenInfoForAvailable = { it.from },
|
||||
),
|
||||
toGroup = getToCurrenciesGroup(
|
||||
currency = currency,
|
||||
leastPairs = pairsLeast.pairs,
|
||||
cryptoCurrenciesList = walletCurrencyStatusesExceptInitial,
|
||||
tokenInfoForFilter = { it.to },
|
||||
tokenInfoForAvailable = { it.from },
|
||||
tokenInfoForFilter = { it.from },
|
||||
tokenInfoForAvailable = { it.to },
|
||||
),
|
||||
allProviders = pairsLeast.allProviders,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@ 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.extensions.*
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -15,6 +12,7 @@ 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.feature.swap.presentation.R
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
|
|
@ -26,8 +24,12 @@ class TokensDataConverter(
|
|||
) : Converter<CurrenciesGroup, 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
|
||||
val availableTitle = TokenToSelectState.Title(
|
||||
resourceReference(R.string.exchange_tokens_available_tokens_header),
|
||||
)
|
||||
val unavailableTitle = TokenToSelectState.Title(
|
||||
resourceReference(R.string.exchange_tokens_available_tokens_header),
|
||||
)
|
||||
return SwapSelectTokenStateHolder(
|
||||
availableTokens = value.available.map { tokenWithBalanceToTokenToSelect(it, true) }
|
||||
.toMutableList()
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ data class SwapStateHolder(
|
|||
val blockchainId: String, // not the same as networkId, its local id in app
|
||||
val warnings: List<SwapWarning> = emptyList(),
|
||||
val alert: SwapWarning.GenericWarning? = null,
|
||||
val updateInProgress: Boolean = false,
|
||||
val changeCardsButtonState: ChangeCardsButtonState = ChangeCardsButtonState.ENABLED,
|
||||
val providerState: ProviderState,
|
||||
|
||||
val fee: FeeItemState = FeeItemState.Empty,
|
||||
|
|
@ -131,4 +131,8 @@ sealed interface SwapWarning {
|
|||
|
||||
enum class GenericWarningType {
|
||||
NETWORK, OTHER
|
||||
}
|
||||
|
||||
enum class ChangeCardsButtonState {
|
||||
ENABLED, DISABLED, UPDATE_IN_PROGRESS
|
||||
}
|
||||
|
|
@ -84,7 +84,7 @@ internal class StateBuilder(
|
|||
onBackClicked = actions.onBackClicked,
|
||||
onChangeCardsClicked = actions.onChangeCardsClicked,
|
||||
onMaxAmountSelected = actions.onMaxAmountSelected,
|
||||
updateInProgress = true,
|
||||
changeCardsButtonState = ChangeCardsButtonState.UPDATE_IN_PROGRESS,
|
||||
onShowPermissionBottomSheet = actions.openPermissionBottomSheet,
|
||||
providerState = ProviderState.Empty(),
|
||||
)
|
||||
|
|
@ -138,7 +138,7 @@ internal class StateBuilder(
|
|||
loading = false,
|
||||
onClick = { },
|
||||
),
|
||||
updateInProgress = false,
|
||||
changeCardsButtonState = ChangeCardsButtonState.DISABLED,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +186,7 @@ internal class StateBuilder(
|
|||
swapButton = SwapButton(enabled = false, loading = true, onClick = {}),
|
||||
providerState = ProviderState.Loading(),
|
||||
permissionState = uiStateHolder.permissionState,
|
||||
updateInProgress = true,
|
||||
changeCardsButtonState = ChangeCardsButtonState.UPDATE_IN_PROGRESS,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -207,6 +207,7 @@ internal class StateBuilder(
|
|||
bestRatedProviderId: String,
|
||||
isManyProviders: Boolean,
|
||||
selectedFeeType: FeeType,
|
||||
isReverseSwapPossible: Boolean,
|
||||
): SwapStateHolder {
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
|
|
@ -257,7 +258,11 @@ internal class StateBuilder(
|
|||
loading = false,
|
||||
onClick = actions.onSwapClick,
|
||||
),
|
||||
updateInProgress = false,
|
||||
changeCardsButtonState = if (isReverseSwapPossible) {
|
||||
ChangeCardsButtonState.ENABLED
|
||||
} else {
|
||||
ChangeCardsButtonState.DISABLED
|
||||
},
|
||||
providerState = swapProvider.convertToContentClickableProviderState(
|
||||
isBestRate = bestRatedProviderId == swapProvider.providerId,
|
||||
fromTokenInfo = quoteModel.fromTokenInfo,
|
||||
|
|
@ -354,12 +359,14 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
fun createQuotesErrorState(
|
||||
uiStateHolder: SwapStateHolder,
|
||||
swapProvider: SwapProvider,
|
||||
fromToken: TokenSwapInfo,
|
||||
toToken: CryptoCurrencyStatus?,
|
||||
dataError: DataError,
|
||||
isReverseSwapPossible: Boolean,
|
||||
): SwapStateHolder {
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
|
|
@ -409,7 +416,11 @@ internal class StateBuilder(
|
|||
loading = false,
|
||||
onClick = actions.onSwapClick,
|
||||
),
|
||||
updateInProgress = false,
|
||||
changeCardsButtonState = if (isReverseSwapPossible) {
|
||||
ChangeCardsButtonState.ENABLED
|
||||
} else {
|
||||
ChangeCardsButtonState.DISABLED
|
||||
},
|
||||
providerState = providerState,
|
||||
)
|
||||
}
|
||||
|
|
@ -475,6 +486,7 @@ internal class StateBuilder(
|
|||
fun createQuotesEmptyAmountState(
|
||||
uiStateHolder: SwapStateHolder,
|
||||
emptyAmountState: SwapState.EmptyAmountState,
|
||||
isReverseSwapPossible: Boolean,
|
||||
): SwapStateHolder {
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
|
|
@ -514,7 +526,11 @@ internal class StateBuilder(
|
|||
loading = false,
|
||||
onClick = { },
|
||||
),
|
||||
updateInProgress = false,
|
||||
changeCardsButtonState = if (isReverseSwapPossible) {
|
||||
ChangeCardsButtonState.ENABLED
|
||||
} else {
|
||||
ChangeCardsButtonState.DISABLED
|
||||
},
|
||||
providerState = ProviderState.Empty(),
|
||||
)
|
||||
}
|
||||
|
|
@ -538,7 +554,7 @@ internal class StateBuilder(
|
|||
|
||||
fun createSilentLoadState(uiState: SwapStateHolder): SwapStateHolder {
|
||||
return uiState.copy(
|
||||
updateInProgress = true,
|
||||
changeCardsButtonState = ChangeCardsButtonState.UPDATE_IN_PROGRESS,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -713,7 +729,7 @@ internal class StateBuilder(
|
|||
onClick = onAlertClick,
|
||||
type = if (txState is TxState.NetworkError) GenericWarningType.NETWORK else GenericWarningType.OTHER,
|
||||
),
|
||||
updateInProgress = false,
|
||||
changeCardsButtonState = ChangeCardsButtonState.ENABLED,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -195,23 +195,34 @@ private fun SwapButton(state: SwapStateHolder, modifier: Modifier = Modifier) {
|
|||
contentColor = TangemTheme.colors.text.primary1,
|
||||
modifier = modifier.size(TangemTheme.dimens.size48),
|
||||
onClick = state.onChangeCardsClicked,
|
||||
enabled = !state.updateInProgress,
|
||||
enabled = state.changeCardsButtonState == ChangeCardsButtonState.ENABLED,
|
||||
) {
|
||||
if (state.updateInProgress) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size16)
|
||||
.padding(TangemTheme.dimens.spacing14),
|
||||
color = TangemTheme.colors.icon.primary1,
|
||||
strokeWidth = TangemTheme.dimens.size2,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_exchange_vertical_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier.padding(TangemTheme.dimens.spacing12),
|
||||
)
|
||||
when (state.changeCardsButtonState) {
|
||||
ChangeCardsButtonState.UPDATE_IN_PROGRESS -> {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size16)
|
||||
.padding(TangemTheme.dimens.spacing14),
|
||||
color = TangemTheme.colors.icon.primary1,
|
||||
strokeWidth = TangemTheme.dimens.size2,
|
||||
)
|
||||
}
|
||||
ChangeCardsButtonState.ENABLED -> {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_exchange_vertical_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier.padding(TangemTheme.dimens.spacing12),
|
||||
)
|
||||
}
|
||||
ChangeCardsButtonState.DISABLED -> {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_exchange_vertical_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.text.disabled,
|
||||
modifier = Modifier.padding(TangemTheme.dimens.spacing12),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ internal class SwapViewModel @Inject constructor(
|
|||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver {
|
||||
|
||||
private val initialCryptoCurrency: CryptoCurrency = savedStateHandle[SwapFragment.CURRENCY_BUNDLE_KEY]
|
||||
?: error("no expected parameter CryptoCurrency found`")
|
||||
private val initialCryptoCurrency: CryptoCurrency =
|
||||
savedStateHandle[SwapFragment.CURRENCY_BUNDLE_KEY] ?: error("no expected parameter CryptoCurrency found`")
|
||||
private lateinit var initialCryptoCurrencyStatus: CryptoCurrencyStatus
|
||||
|
||||
private var isBalanceHidden = true
|
||||
|
|
@ -114,15 +114,12 @@ internal class SwapViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
getBalanceHidingSettingsUseCase()
|
||||
.flowWithLifecycle(owner.lifecycle)
|
||||
.onEach {
|
||||
isBalanceHidden = it.isBalanceHidden
|
||||
withContext(dispatchers.main) {
|
||||
uiState = stateBuilder.updateBalanceHiddenState(uiState, isBalanceHidden)
|
||||
}
|
||||
getBalanceHidingSettingsUseCase().flowWithLifecycle(owner.lifecycle).onEach {
|
||||
isBalanceHidden = it.isBalanceHidden
|
||||
withContext(dispatchers.main) {
|
||||
uiState = stateBuilder.updateBalanceHiddenState(uiState, isBalanceHidden)
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
|
|
@ -175,19 +172,18 @@ internal class SwapViewModel @Inject constructor(
|
|||
selectedCurrency = null,
|
||||
)
|
||||
|
||||
uiState =
|
||||
stateBuilder.createInitialErrorState(
|
||||
uiState,
|
||||
(it as? ExpressException)?.dataError?.code ?: DataError.UnknownError.code,
|
||||
) {
|
||||
uiState = stateBuilder.createInitialLoadingState(
|
||||
initialCurrency = initialCryptoCurrency,
|
||||
networkInfo = blockchainInteractor.getBlockchainInfo(
|
||||
initialCryptoCurrency.network.backendId,
|
||||
),
|
||||
)
|
||||
initTokens()
|
||||
}
|
||||
uiState = stateBuilder.createInitialErrorState(
|
||||
uiState,
|
||||
(it as? ExpressException)?.dataError?.code ?: DataError.UnknownError.code,
|
||||
) {
|
||||
uiState = stateBuilder.createInitialLoadingState(
|
||||
initialCurrency = initialCryptoCurrency,
|
||||
networkInfo = blockchainInteractor.getBlockchainInfo(
|
||||
initialCryptoCurrency.network.backendId,
|
||||
),
|
||||
)
|
||||
initTokens()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -216,7 +212,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun updateTokensState(dataState: TokensDataStateExpress) {
|
||||
val tokensDataState = if (!isOrderReversed) dataState.toGroup else dataState.fromGroup
|
||||
val tokensDataState = if (isOrderReversed) dataState.fromGroup else dataState.toGroup
|
||||
uiState = stateBuilder.addTokensToState(
|
||||
uiState = uiState,
|
||||
tokensDataState = tokensDataState,
|
||||
|
|
@ -295,8 +291,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
if (providersState.isNotEmpty()) {
|
||||
val (provider, state) = updateLoadedQuotes(providersState)
|
||||
setupLoadedState(provider, state, fromToken)
|
||||
val successStates = providersState
|
||||
.getLastLoadedSuccessStates()
|
||||
val successStates = providersState.getLastLoadedSuccessStates()
|
||||
val pricesLowerBest = getPricesLowerBest(successStates)
|
||||
uiState = stateBuilder.updateProvidersBottomSheetContent(
|
||||
uiState = uiState,
|
||||
|
|
@ -329,6 +324,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
bestRatedProviderId = bestRatedProviderId,
|
||||
isManyProviders = dataState.lastLoadedSwapStates.size > 1,
|
||||
selectedFeeType = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
|
||||
isReverseSwapPossible = isReverseSwapPossible(),
|
||||
)
|
||||
if (uiState.warnings.any { it is SwapWarning.UnableToCoverFeeWarning }) {
|
||||
analyticsEventHandler.send(
|
||||
|
|
@ -343,6 +339,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
uiState = stateBuilder.createQuotesEmptyAmountState(
|
||||
uiStateHolder = uiState,
|
||||
emptyAmountState = state,
|
||||
isReverseSwapPossible = isReverseSwapPossible(),
|
||||
)
|
||||
}
|
||||
is SwapState.SwapError -> {
|
||||
|
|
@ -353,6 +350,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
fromToken = state.fromTokenInfo,
|
||||
toToken = dataState.toCryptoCurrency,
|
||||
dataError = state.error,
|
||||
isReverseSwapPossible = isReverseSwapPossible(),
|
||||
)
|
||||
sendErrorAnalyticsEvent(state.error, provider)
|
||||
}
|
||||
|
|
@ -419,16 +417,15 @@ internal class SwapViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun selectDefaultFee(state: SwapState.QuotesLoadedState): TxFee? {
|
||||
return dataState.selectedFee
|
||||
?: when (val txFee = state.txFee) {
|
||||
TxFeeState.Empty -> null
|
||||
is TxFeeState.MultipleFeeState -> {
|
||||
txFee.normalFee
|
||||
}
|
||||
is TxFeeState.SingleFeeState -> {
|
||||
txFee.fee
|
||||
}
|
||||
return dataState.selectedFee ?: when (val txFee = state.txFee) {
|
||||
TxFeeState.Empty -> null
|
||||
is TxFeeState.MultipleFeeState -> {
|
||||
txFee.normalFee
|
||||
}
|
||||
is TxFeeState.SingleFeeState -> {
|
||||
txFee.fee
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
|
|
@ -453,58 +450,56 @@ internal class SwapViewModel @Inject constructor(
|
|||
includeFeeInAmount = lastLoadedQuotesState.preparedSwapConfigState.includeFeeInAmount,
|
||||
fee = requireNotNull(dataState.selectedFee),
|
||||
)
|
||||
}
|
||||
.onSuccess {
|
||||
when (it) {
|
||||
is TxState.TxSent -> {
|
||||
val url = blockchainInteractor.getExplorerTransactionLink(
|
||||
networkId = dataState.networkId,
|
||||
txAddress = it.txAddress,
|
||||
)
|
||||
uiState = stateBuilder.createSuccessState(
|
||||
uiState = uiState,
|
||||
txState = it,
|
||||
dataState = dataState,
|
||||
txUrl = url,
|
||||
onExploreClick = {
|
||||
val txHash = it.txAddress
|
||||
if (txHash.isNotEmpty()) {
|
||||
swapRouter.openUrl(url)
|
||||
}
|
||||
}.onSuccess {
|
||||
when (it) {
|
||||
is TxState.TxSent -> {
|
||||
val url = blockchainInteractor.getExplorerTransactionLink(
|
||||
networkId = dataState.networkId,
|
||||
txAddress = it.txAddress,
|
||||
)
|
||||
uiState = stateBuilder.createSuccessState(
|
||||
uiState = uiState,
|
||||
txState = it,
|
||||
dataState = dataState,
|
||||
txUrl = url,
|
||||
onExploreClick = {
|
||||
val txHash = it.txAddress
|
||||
if (txHash.isNotEmpty()) {
|
||||
swapRouter.openUrl(url)
|
||||
}
|
||||
analyticsEventHandler.send(
|
||||
event = SwapEvents.ButtonExplore(initialCryptoCurrency.symbol),
|
||||
)
|
||||
},
|
||||
onStatusClick = {
|
||||
val txExternalUrl = it.txExternalUrl
|
||||
if (!txExternalUrl.isNullOrBlank()) {
|
||||
swapRouter.openUrl(txExternalUrl)
|
||||
analyticsEventHandler.send(
|
||||
event = SwapEvents.ButtonExplore(initialCryptoCurrency.symbol),
|
||||
event = SwapEvents.ButtonStatus(initialCryptoCurrency.symbol),
|
||||
)
|
||||
},
|
||||
onStatusClick = {
|
||||
val txExternalUrl = it.txExternalUrl
|
||||
if (!txExternalUrl.isNullOrBlank()) {
|
||||
swapRouter.openUrl(txExternalUrl)
|
||||
analyticsEventHandler.send(
|
||||
event = SwapEvents.ButtonStatus(initialCryptoCurrency.symbol),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
sendSuccessEvent()
|
||||
}
|
||||
},
|
||||
)
|
||||
sendSuccessEvent()
|
||||
|
||||
swapRouter.openScreen(SwapNavScreen.Success)
|
||||
}
|
||||
is TxState.UserCancelled -> {
|
||||
startLoadingQuotesFromLastState()
|
||||
}
|
||||
else -> {
|
||||
startLoadingQuotesFromLastState()
|
||||
uiState = stateBuilder.createErrorTransaction(uiState, it) {
|
||||
uiState = stateBuilder.clearAlert(uiState)
|
||||
}
|
||||
swapRouter.openScreen(SwapNavScreen.Success)
|
||||
}
|
||||
is TxState.UserCancelled -> {
|
||||
startLoadingQuotesFromLastState()
|
||||
}
|
||||
else -> {
|
||||
startLoadingQuotesFromLastState()
|
||||
uiState = stateBuilder.createErrorTransaction(uiState, it) {
|
||||
uiState = stateBuilder.clearAlert(uiState)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onFailure {
|
||||
Timber.e(it)
|
||||
startLoadingQuotesFromLastState()
|
||||
makeDefaultAlert()
|
||||
}
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
startLoadingQuotesFromLastState()
|
||||
makeDefaultAlert()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -540,8 +535,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
"dataState.approveDataModel might not be null"
|
||||
},
|
||||
forTokenContractAddress = (dataState.fromCryptoCurrency?.currency as? CryptoCurrency.Token)
|
||||
?.contractAddress
|
||||
?: "",
|
||||
?.contractAddress ?: "",
|
||||
fromToken = requireNotNull(dataState.fromCryptoCurrency?.currency) {
|
||||
"dataState.fromCurrency might not be null"
|
||||
},
|
||||
|
|
@ -554,25 +548,23 @@ internal class SwapViewModel @Inject constructor(
|
|||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
.onSuccess {
|
||||
when (it) {
|
||||
is TxState.TxSent -> {
|
||||
uiState = stateBuilder.loadingPermissionState(uiState)
|
||||
uiState = stateBuilder.dismissBottomSheet(uiState)
|
||||
startLoadingQuotesFromLastState(isSilent = true)
|
||||
}
|
||||
is TxState.UserCancelled -> Unit
|
||||
else -> {
|
||||
uiState = stateBuilder.createErrorTransaction(uiState, it) {
|
||||
uiState = stateBuilder.clearAlert(uiState)
|
||||
}
|
||||
}.onSuccess {
|
||||
when (it) {
|
||||
is TxState.TxSent -> {
|
||||
uiState = stateBuilder.loadingPermissionState(uiState)
|
||||
uiState = stateBuilder.dismissBottomSheet(uiState)
|
||||
startLoadingQuotesFromLastState(isSilent = true)
|
||||
}
|
||||
is TxState.UserCancelled -> Unit
|
||||
else -> {
|
||||
uiState = stateBuilder.createErrorTransaction(uiState, it) {
|
||||
uiState = stateBuilder.clearAlert(uiState)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onFailure {
|
||||
makeDefaultAlert()
|
||||
}
|
||||
}.onFailure {
|
||||
makeDefaultAlert()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -653,12 +645,17 @@ internal class SwapViewModel @Inject constructor(
|
|||
private fun onChangeCardsClicked() {
|
||||
val newFromToken = dataState.toCryptoCurrency
|
||||
val newToToken = dataState.fromCryptoCurrency
|
||||
|
||||
if (newFromToken != null && newToToken != null) {
|
||||
dataState = dataState.copy(
|
||||
fromCryptoCurrency = newFromToken,
|
||||
toCryptoCurrency = newToToken,
|
||||
)
|
||||
isOrderReversed = !isOrderReversed
|
||||
dataState.tokensDataState?.let {
|
||||
updateTokensState(it)
|
||||
}
|
||||
|
||||
val decimals = newFromToken.currency.decimals
|
||||
lastAmount.value = cutAmountWithDecimals(decimals, lastAmount.value)
|
||||
uiState = stateBuilder.updateSwapAmount(
|
||||
|
|
@ -783,8 +780,8 @@ internal class SwapViewModel @Inject constructor(
|
|||
},
|
||||
onClickFee = {
|
||||
val selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL
|
||||
val txFeeState = dataState.getCurrentLoadedSwapState()?.txFee as? TxFeeState.MultipleFeeState
|
||||
?: return@UiActions
|
||||
val txFeeState =
|
||||
dataState.getCurrentLoadedSwapState()?.txFee as? TxFeeState.MultipleFeeState ?: return@UiActions
|
||||
uiState = stateBuilder.showSelectFeeBottomSheet(
|
||||
uiState = uiState,
|
||||
selectedFee = selectedFee,
|
||||
|
|
@ -862,9 +859,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
private fun findBestQuoteProvider(state: SuccessLoadedSwapData): SwapProvider? {
|
||||
// finding best quotes
|
||||
return state.minByOrNull {
|
||||
if (!it.value.fromTokenInfo.amountFiat.isNullOrZero() &&
|
||||
!it.value.toTokenInfo.amountFiat.isNullOrZero()
|
||||
) {
|
||||
if (!it.value.fromTokenInfo.amountFiat.isNullOrZero() && !it.value.toTokenInfo.amountFiat.isNullOrZero()) {
|
||||
it.value.fromTokenInfo.amountFiat.divide(
|
||||
it.value.toTokenInfo.amountFiat,
|
||||
it.value.toTokenInfo.cryptoCurrencyStatus.currency.decimals,
|
||||
|
|
@ -894,15 +889,13 @@ 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,
|
||||
)
|
||||
return getSelectedAppCurrencyUseCase().map { maybeAppCurrency ->
|
||||
maybeAppCurrency.getOrElse { AppCurrency.Default }
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = AppCurrency.Default,
|
||||
)
|
||||
}
|
||||
|
||||
private fun findSwapProviders(fromToken: CryptoCurrencyStatus, toToken: CryptoCurrencyStatus): List<SwapProvider> {
|
||||
|
|
@ -931,11 +924,27 @@ internal class SwapViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun Map<SwapProvider, SwapState>.getLastLoadedSuccessStates(): SuccessLoadedSwapData {
|
||||
return this
|
||||
.filter { it.value is SwapState.QuotesLoadedState }
|
||||
return this.filter { it.value is SwapState.QuotesLoadedState }
|
||||
.mapValues { it.value as SwapState.QuotesLoadedState }
|
||||
}
|
||||
|
||||
private fun isReverseSwapPossible(): Boolean {
|
||||
val from = dataState.fromCryptoCurrency ?: return false
|
||||
val to = dataState.toCryptoCurrency ?: return false
|
||||
|
||||
val currenciesGroup = if (isOrderReversed) {
|
||||
dataState.tokensDataState?.toGroup
|
||||
} else {
|
||||
dataState.tokensDataState?.fromGroup
|
||||
} ?: return false
|
||||
|
||||
val chosen = if (isOrderReversed) from else to
|
||||
|
||||
return currenciesGroup.available
|
||||
.map { it.currencyStatus.currency }
|
||||
.contains(chosen.currency)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val loggingTag = "SwapViewModel"
|
||||
private const val INITIAL_AMOUNT = ""
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
|
|||
config = NotificationConfig(
|
||||
title = resourceReference(id = R.string.token_swap_promotion_title),
|
||||
subtitle = resourceReference(id = R.string.token_swap_promotion_message),
|
||||
iconResId = R.drawable.ic_swap_promo_34,
|
||||
iconResId = R.drawable.img_swap_promo,
|
||||
backgroundResId = R.drawable.img_swap_promo_banner_background,
|
||||
onCloseClick = onCloseClick,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
|||
config = NotificationConfig(
|
||||
title = resourceReference(id = R.string.main_swap_promotion_title),
|
||||
subtitle = resourceReference(id = R.string.main_swap_promotion_message),
|
||||
iconResId = R.drawable.ic_swap_promo_34,
|
||||
iconResId = R.drawable.img_swap_promo,
|
||||
backgroundResId = R.drawable.img_swap_promo_banner_background,
|
||||
onCloseClick = onCloseClick,
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue