From df9a00188b8830771688a4b9c074edd80229d75e Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Dec 2023 16:26:53 +0300 Subject: [PATCH 1/4] Updated on 2026-08-14 --- .../java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 101200169e..356dde9be4 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -384,7 +384,7 @@ internal class SwapViewModel @Inject constructor( val stateSuccess = state.getLastLoadedSuccessStates() return if (stateSuccess.isNotEmpty()) { val currentSelected = dataState.selectedProvider - if (currentSelected != null && state.keys.contains(currentSelected)) { + if (currentSelected != null && stateSuccess.keys.contains(currentSelected)) { currentSelected } else { findBestQuoteProvider(stateSuccess) ?: stateSuccess.keys.first() From 6844163be4c78ed239417695e7c924626135cf9d Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Dec 2023 16:56:59 +0300 Subject: [PATCH 2/4] Updated on 2026-08-14 --- .../tokens/GetCryptoCurrencyActionsUseCase.kt | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index 87dee2d3d9..61962b7ca5 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -118,12 +118,12 @@ class GetCryptoCurrencyActionsUseCase( val isMulticurrencyWallet = cardTypesResolver.isTangemWallet() || cardTypesResolver.isWallet2() // swap - if (isMulticurrencyWallet && - marketCryptoCurrencyRepository.isExchangeable(userWalletId, cryptoCurrency) - ) { - activeList.add(TokenActionsState.ActionState.Swap(true)) - } else { - disabledList.add(TokenActionsState.ActionState.Swap(false)) + if (isMulticurrencyWallet) { + if (marketCryptoCurrencyRepository.isExchangeable(userWalletId, cryptoCurrency)) { + activeList.add(TokenActionsState.ActionState.Swap(true)) + } else { + disabledList.add(TokenActionsState.ActionState.Swap(false)) + } } // buy @@ -164,11 +164,13 @@ class GetCryptoCurrencyActionsUseCase( return activeList + disabledList } - private fun isSendDisabled(cryptoCurrencyStatus: CryptoCurrencyStatus, coinStatus: CryptoCurrencyStatus?): Boolean = - cryptoCurrencyStatus.value.amount.isNullOrZero() || - coinStatus?.value?.amount.isNullOrZero() || - currenciesRepository.hasPendingTransactions( - cryptoCurrencyStatus = cryptoCurrencyStatus, - coinStatus = coinStatus, - ) + private fun isSendDisabled( + cryptoCurrencyStatus: CryptoCurrencyStatus, + coinStatus: CryptoCurrencyStatus?, + ): Boolean = cryptoCurrencyStatus.value.amount.isNullOrZero() || + coinStatus?.value?.amount.isNullOrZero() || + currenciesRepository.hasPendingTransactions( + cryptoCurrencyStatus = cryptoCurrencyStatus, + coinStatus = coinStatus, + ) } \ No newline at end of file From de5bb054faffbfa79494e4b15d889daf9721e799 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Dec 2023 17:51:03 +0300 Subject: [PATCH 3/4] Updated on 2026-08-14 --- .../com/tangem/core/ui/components/Keyboard.kt | 25 +++---------------- .../com/tangem/feature/swap/ui/SwapScreen.kt | 12 +++++++++ .../feature/swap/ui/SwapScreenContent.kt | 7 ------ .../feature/swap/ui/SwapSelectTokenScreen.kt | 3 ++- 4 files changed, 18 insertions(+), 29 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Keyboard.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Keyboard.kt index 4baf118b61..a920fda100 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Keyboard.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Keyboard.kt @@ -1,13 +1,10 @@ package com.tangem.core.ui.components import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.exclude import androidx.compose.foundation.layout.ime -import androidx.compose.foundation.layout.navigationBars import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -25,23 +22,9 @@ sealed interface Keyboard { /** * Allows to subscribe to a soft keyboard to detect when it's open/closed */ -@Deprecated("Use Modifier.imePadding() on pure Compose screens (without XML layouts)") @Composable fun keyboardAsState(): State { - val density = LocalDensity.current - val imeInsets = WindowInsets.ime - .exclude(WindowInsets.navigationBars) - .getBottom(density) - - return remember(imeInsets) { - derivedStateOf { - if (imeInsets > 0) { - Keyboard.Opened( - height = with(density) { imeInsets.toDp() }, - ) - } else { - Keyboard.Closed - } - } - } + val bottom = WindowInsets.ime.getBottom(LocalDensity.current) + val isImeVisible = bottom > 0 + return rememberUpdatedState(if (isImeVisible) Keyboard.Opened(bottom.dp) else Keyboard.Closed) } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt index a70b6ce6f2..c25ea64c0b 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt @@ -2,21 +2,33 @@ package com.tangem.feature.swap.ui import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.material.* import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.models.SwapStateHolder import com.tangem.feature.swap.models.states.ChooseFeeBottomSheetConfig import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig import com.tangem.feature.swap.models.states.GivePermissionBottomSheetConfig +import com.tangem.feature.swap.presentation.R @Composable internal fun SwapScreen(stateHolder: SwapStateHolder) { BackHandler(onBack = stateHolder.onBackClicked) Scaffold( + modifier = Modifier.systemBarsPadding(), + topBar = { + AppBarWithBackButton( + text = stringResource(R.string.common_swap), + onBackClick = stateHolder.onBackClicked, + iconRes = R.drawable.ic_close_24, + ) + }, containerColor = TangemTheme.colors.background.secondary, ) { scaffoldPaddings -> diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 949ead0f64..ace1d19a31 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -17,7 +17,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.constraintlayout.compose.ConstraintLayout import com.tangem.common.Strings.STARS import com.tangem.core.ui.components.* -import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.getActiveIconResByCoinId @@ -40,12 +39,6 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi .background(color = TangemTheme.colors.background.secondary), ) { Column { - AppBarWithBackButton( - text = stringResource(R.string.common_swap), - onBackClick = state.onBackClicked, - iconRes = R.drawable.ic_close_24, - ) - Column( modifier = Modifier .fillMaxWidth() diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt index 82b05a7c86..4057aba9af 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt @@ -97,7 +97,8 @@ private fun ListOfTokens(state: SwapSelectTokenStateHolder, modifier: Modifier = LazyColumn( modifier = modifier .background(color = screenBackgroundColor) - .fillMaxSize(), + .fillMaxSize() + .imePadding(), horizontalAlignment = Alignment.CenterHorizontally, ) { item { SpacerH8() } From 2516b9e9b9fc1fc7b0f100205be30b59982f97c9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Dec 2023 19:07:22 +0300 Subject: [PATCH 4/4] Updated on 2026-08-14 --- .../swap/models/states/ProviderState.kt | 31 ++++++++++++++----- .../swap/ui/ChooseProviderBottomSheet.kt | 3 +- .../tangem/feature/swap/ui/ProviderItem.kt | 9 ++++-- .../tangem/feature/swap/ui/StateBuilder.kt | 19 +++++++----- .../feature/swap/viewmodels/SwapViewModel.kt | 17 +++++----- 5 files changed, 53 insertions(+), 26 deletions(-) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ProviderState.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ProviderState.kt index 84254a5716..265631000e 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ProviderState.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ProviderState.kt @@ -25,7 +25,7 @@ sealed class ProviderState { val subtitle: TextReference, val selectionType: SelectionType, val additionalBadge: AdditionalBadge, - val percentLowerThenBest: Float = 0f, + val percentLowerThenBest: PercentLowerThanBest = PercentLowerThanBest.Empty, override val onProviderClick: (String) -> Unit, ) : ProviderState() @@ -50,18 +50,35 @@ sealed class ProviderState { } } +sealed class PercentLowerThanBest { + data class Value(val value: Float) : PercentLowerThanBest() + object Empty : PercentLowerThanBest() +} + object ProviderPercentDiffComparator : Comparator { override fun compare(o1: ProviderState, o2: ProviderState): Int { if (o1 is ProviderState.Content && o2 !is ProviderState.Content) { - return 1 - } - if (o1 !is ProviderState.Content && o2 is ProviderState.Content) { return -1 } - return if (o1 is ProviderState.Content && o2 is ProviderState.Content) { - o1.percentLowerThenBest.compareTo(o2.percentLowerThenBest) + if (o1 !is ProviderState.Content && o2 is ProviderState.Content) { + return 1 + } + 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) { + return -1 + } + if (o1Percent !is PercentLowerThanBest.Value && o2Percent is PercentLowerThanBest.Value) { + return 1 + } + return if (o1Percent is PercentLowerThanBest.Value && o2Percent is PercentLowerThanBest.Value) { + o1Percent.value.compareTo(o2Percent.value) + } else { + 0 + } } else { - 0 + return 0 } } } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt index 2dd33c473f..363085e36c 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt @@ -17,6 +17,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.ProviderState import com.tangem.feature.swap.presentation.R import kotlinx.collections.immutable.toImmutableList @@ -89,7 +90,7 @@ private fun ChooseProviderBottomSheet_Preview() { iconUrl = "", subtitle = stringReference("1 000 000"), additionalBadge = ProviderState.AdditionalBadge.BestTrade, - percentLowerThenBest = -1.0f, + percentLowerThenBest = PercentLowerThanBest.Value(-1.0f), selectionType = ProviderState.SelectionType.SELECT, onProviderClick = {}, ), diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt index e5fe4ef2c9..51f6936885 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt @@ -24,6 +24,7 @@ import com.tangem.core.ui.components.SpacerH24 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.ProviderState /** @@ -151,8 +152,10 @@ private fun ProviderContentState( maxLines = 1, ) } - if (state.percentLowerThenBest > 0f) { - AnimatedContent(targetState = state.percentLowerThenBest, label = "") { + if (state.percentLowerThenBest is PercentLowerThanBest.Value && + state.percentLowerThenBest.value > 0 + ) { + AnimatedContent(targetState = state.percentLowerThenBest.value, label = "") { Text( text = "-$it%", style = TangemTheme.typography.body2, @@ -404,7 +407,7 @@ private fun ProviderItem_Content_Preview() { iconUrl = "", subtitle = stringReference("1 000 000"), additionalBadge = ProviderState.AdditionalBadge.PermissionRequired, - percentLowerThenBest = -1.0f, + percentLowerThenBest = PercentLowerThanBest.Value(-1.0f), selectionType = ProviderState.SelectionType.SELECT, onProviderClick = {}, ) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index bb1842d035..af2f4cb3db 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -789,7 +789,7 @@ internal class StateBuilder( fun showSelectProviderBottomSheet( uiState: SwapStateHolder, selectedProviderId: String, - pricesLowerBest: Map, + pricesLowerBest: Map, providersStates: Map, unavailableProviders: List, onDismiss: () -> Unit, @@ -820,6 +820,7 @@ internal class StateBuilder( fun updateProvidersBottomSheetContent( uiState: SwapStateHolder, + pricesLowerBest: Map, tokenSwapInfoForProviders: Map, ): SwapStateHolder { val config = uiState.bottomSheetConfig?.content as? ChooseProviderBottomSheetConfig @@ -835,6 +836,9 @@ internal class StateBuilder( .getFormattedCryptoAmount(tokenInfo.cryptoCurrencyStatus.currency) it.copy( subtitle = stringReference(rateString), + percentLowerThenBest = pricesLowerBest[it.id]?.let { percent -> + PercentLowerThanBest.Value(percent) + } ?: PercentLowerThanBest.Empty, ) } else { it @@ -928,7 +932,7 @@ internal class StateBuilder( } private fun Map.Entry.convertToProviderBottomSheetState( - pricesLowerBest: Map, + pricesLowerBest: Map, onProviderSelect: (String) -> Unit, ): ProviderState? { val provider = this.key @@ -1040,7 +1044,7 @@ internal class StateBuilder( subtitle = stringReference(rateString), additionalBadge = badge, selectionType = selectionType, - percentLowerThenBest = ZERO_PERCENT, + percentLowerThenBest = PercentLowerThanBest.Empty, onProviderClick = onProviderClick, ) } @@ -1049,7 +1053,7 @@ internal class StateBuilder( isBestRate: Boolean, state: SwapState.QuotesLoadedState, selectionType: ProviderState.SelectionType, - pricesLowerBest: Map, + pricesLowerBest: Map, onProviderClick: (String) -> Unit, ): ProviderState { val toTokenInfo = state.toTokenInfo @@ -1069,7 +1073,9 @@ internal class StateBuilder( subtitle = stringReference(rateString), additionalBadge = additionalBadge, selectionType = selectionType, - percentLowerThenBest = pricesLowerBest[this] ?: ZERO_PERCENT, + percentLowerThenBest = pricesLowerBest[this.providerId]?.let { percent -> + PercentLowerThanBest.Value(percent) + } ?: PercentLowerThanBest.Value(0f), onProviderClick = onProviderClick, ) } @@ -1103,7 +1109,7 @@ internal class StateBuilder( selectionType = selectionType, subtitle = alertText, additionalBadge = ProviderState.AdditionalBadge.Empty, - percentLowerThenBest = ZERO_PERCENT, + percentLowerThenBest = PercentLowerThanBest.Empty, onProviderClick = onProviderClick, ) } @@ -1148,6 +1154,5 @@ internal class StateBuilder( private const val HUNDRED_PERCENTS = 100 private const val UNKNOWN_AMOUNT_SIGN = "—" private const val MAX_DECIMALS_TO_SHOW = 8 - private const val ZERO_PERCENT = 0f } } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 356dde9be4..2f93839717 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.* import arrow.core.getOrElse -import arrow.core.mapNotNull import com.tangem.common.Provider import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.utils.InputNumberFormatter @@ -289,11 +288,13 @@ internal class SwapViewModel @Inject constructor( if (providersState.isNotEmpty()) { val (provider, state) = updateLoadedQuotes(providersState) setupLoadedState(provider, state, fromToken) + val successStates = providersState + .getLastLoadedSuccessStates() + val pricesLowerBest = getPricesLowerBest(successStates) uiState = stateBuilder.updateProvidersBottomSheetContent( uiState = uiState, - tokenSwapInfoForProviders = providersState - .getLastLoadedSuccessStates() - .entries + pricesLowerBest = pricesLowerBest, + tokenSwapInfoForProviders = successStates.entries .associate { it.key.providerId to it.value.toTokenInfo }, ) } else { @@ -869,21 +870,21 @@ internal class SwapViewModel @Inject constructor( }?.key } - private fun getPricesLowerBest(state: SuccessLoadedSwapData): Map { + private fun getPricesLowerBest(state: SuccessLoadedSwapData): Map { val bestRateEntry = state.maxByOrNull { it.value.toTokenInfo.tokenAmount.value } ?: return emptyMap() val bestRate = bestRateEntry.value.toTokenInfo.tokenAmount.value val hundredPercent = BigDecimal("100") - return state.mapNotNull { + return state.entries.mapNotNull { if (it.key != bestRateEntry.key) { val amount = it.value.toTokenInfo.tokenAmount.value val percentDiff = BigDecimal.ONE.minus( amount.divide(bestRate, RoundingMode.HALF_UP), ).multiply(hundredPercent) - percentDiff.setScale(2, RoundingMode.HALF_UP).toFloat().absoluteValue + it.key.providerId to percentDiff.setScale(2, RoundingMode.HALF_UP).toFloat().absoluteValue } else { null } - } + }.toMap() } private fun createSelectedAppCurrencyFlow(): StateFlow {