diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/provider/ProviderTypeFilterPicker.kt b/core/ui/src/main/java/com/tangem/core/ui/components/provider/ProviderTypeFilterPicker.kt index af6a9062f1..b41decbdab 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/provider/ProviderTypeFilterPicker.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/provider/ProviderTypeFilterPicker.kt @@ -1,7 +1,7 @@ package com.tangem.core.ui.components.provider import androidx.compose.runtime.Composable -import androidx.compose.runtime.key +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import com.tangem.core.ui.R import com.tangem.domain.express.models.ProviderFilterType @@ -20,31 +20,31 @@ fun ProviderTypeFilterPicker( onFilterSelect: (ProviderFilterType) -> Unit, modifier: Modifier = Modifier, ) { - val segments = availableFilters.map { filter -> - TangemSegmentUM( - id = filter.name, - title = when (filter) { - ProviderFilterType.ALL -> resourceReference(R.string.common_all) - ProviderFilterType.CEX -> TextReference.Str("CEX") - ProviderFilterType.DEX -> TextReference.Str("DEX") - }, - ) - }.toImmutableList() - val selectedSegment = segments.firstOrNull { it.id == selectedFilter.name } - TangemThemeRedesign { - // key() forces recomposition when selectedFilter changes to re-seed initialSelectedItem, - // because TangemSegmentedPicker owns its selection state internally via remember. - key(selectedFilter) { - TangemSegmentedPicker( - items = segments, - initialSelectedItem = selectedSegment, - isFixed = true, - modifier = modifier, - onClick = { segment -> - val filterType = availableFilters.firstOrNull { it.name == segment.id } - if (filterType != null) onFilterSelect(filterType) + val segments = remember(availableFilters) { + availableFilters.map { filter -> + TangemSegmentUM( + id = filter.name, + title = when (filter) { + ProviderFilterType.ALL -> resourceReference(R.string.common_all) + ProviderFilterType.CEX -> TextReference.Str("CEX") + ProviderFilterType.DEX -> TextReference.Str("DEX") }, ) - } + }.toImmutableList() + } + val selectedSegment = remember(segments, selectedFilter) { + segments.firstOrNull { it.id == selectedFilter.name } + } + TangemThemeRedesign { + TangemSegmentedPicker( + items = segments, + initialSelectedItem = selectedSegment, + isFixed = true, + modifier = modifier, + onClick = { segment -> + val filterType = availableFilters.firstOrNull { it.name == segment.id } + if (filterType != null) onFilterSelect(filterType) + }, + ) } } \ No newline at end of file diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/converters/SwapProviderStateBuilder.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/converters/SwapProviderStateBuilder.kt index eccf09da43..5c84a44a72 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/converters/SwapProviderStateBuilder.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/converters/SwapProviderStateBuilder.kt @@ -74,6 +74,8 @@ internal object SwapProviderStateBuilder { permissionState: PermissionDataState, pricesLowerBest: Map, selectionType: ProviderState.SelectionType, + isBestRate: Boolean = false, + isNeedBestRateBadge: Boolean = false, needApplyFCARestrictions: Boolean, onProviderClick: (String) -> Unit, ): ProviderState.Content { @@ -83,6 +85,8 @@ internal object SwapProviderStateBuilder { provider = provider, needApplyFCARestrictions = needApplyFCARestrictions, permissionState = permissionState, + isBestRate = isBestRate, + isNeedBestRateBadge = isNeedBestRateBadge, ), selectionType = selectionType, percentLowerThenBest = pricesLowerBest[provider.providerId] diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index e5cf890a31..975da73307 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -1698,12 +1698,15 @@ internal class SwapModel @Inject constructor( analyticsEventHandler.send(SwapEvents.ProviderClicked()) val states = dataState.lastLoadedSwapStates.getLastLoadedSuccessStates() val pricesLowerBest = getPricesLowerBest(providerId, states) + val bestRatedProviderId = findBestQuoteProvider(states)?.providerId ?: providerId uiState = stateBuilder.showSelectProviderBottomSheet( uiState = uiState, selectedProviderId = providerId, pricesLowerBest = pricesLowerBest, providersStates = dataState.lastLoadedSwapStates, needApplyFCARestrictions = userCountry.needApplyFCARestrictions(), + bestRatedProviderId = bestRatedProviderId, + isNeedBestRateBadge = dataState.lastLoadedSwapStates.consideredProvidersStates().size > 1, ) { uiState = stateBuilder.dismissBottomSheet(uiState) } }, onProviderSelect = { providerId -> diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 7991737bec..c2cce01561 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -1068,6 +1068,8 @@ internal class StateBuilder( pricesLowerBest: Map, providersStates: Map, needApplyFCARestrictions: Boolean, + bestRatedProviderId: String, + isNeedBestRateBadge: Boolean, onDismiss: () -> Unit, ): SwapStateHolder { val availableProvidersStates = providersStates.entries @@ -1076,6 +1078,8 @@ internal class StateBuilder( pricesLowerBest = pricesLowerBest, onProviderSelect = actions.onProviderSelect, needApplyFCARestrictions = needApplyFCARestrictions, + bestRatedProviderId = bestRatedProviderId, + isNeedBestRateBadge = isNeedBestRateBadge, ) } .sortedWith(ProviderPercentDiffComparator) @@ -1180,6 +1184,8 @@ internal class StateBuilder( pricesLowerBest: Map, onProviderSelect: (String) -> Unit, needApplyFCARestrictions: Boolean, + bestRatedProviderId: String, + isNeedBestRateBadge: Boolean, ): ProviderState? { val provider = this.key return when (val state = this.value) { @@ -1192,6 +1198,8 @@ internal class StateBuilder( pricesLowerBest = pricesLowerBest, selectionType = ProviderState.SelectionType.SELECT, needApplyFCARestrictions = needApplyFCARestrictions, + isBestRate = bestRatedProviderId == provider.providerId && !state.priceImpact.shouldShowWarning(), + isNeedBestRateBadge = isNeedBestRateBadge, onProviderClick = onProviderSelect, ) } diff --git a/features/swap/impl/src/test/java/com/tangem/feature/swap/converters/SwapProviderStateBuilderTest.kt b/features/swap/impl/src/test/java/com/tangem/feature/swap/converters/SwapProviderStateBuilderTest.kt index 6b21165ffe..1e93944a6c 100644 --- a/features/swap/impl/src/test/java/com/tangem/feature/swap/converters/SwapProviderStateBuilderTest.kt +++ b/features/swap/impl/src/test/java/com/tangem/feature/swap/converters/SwapProviderStateBuilderTest.kt @@ -220,7 +220,7 @@ internal class SwapProviderStateBuilderTest { } @Test - fun `GIVEN best rate badge inputs WHEN buildContentSelectable THEN BestTrade badge is never set`() { + fun `GIVEN best rate AND no FCA AND no permission WHEN buildContentSelectable THEN BestTrade badge`() { val provider = provider(id = "any", isRecommended = false) val info = tokenInfo(symbol = "USDT", decimals = 6, amount = BigDecimal("100")) @@ -231,6 +231,48 @@ internal class SwapProviderStateBuilderTest { pricesLowerBest = emptyMap(), selectionType = ProviderState.SelectionType.SELECT, needApplyFCARestrictions = false, + isBestRate = true, + isNeedBestRateBadge = true, + onProviderClick = onProviderClick, + ) + + assertThat(result.additionalBadge).isEqualTo(ProviderState.AdditionalBadge.BestTrade) + } + + @Test + fun `GIVEN isNeedBestRateBadge false WHEN buildContentSelectable THEN no BestTrade badge`() { + val provider = provider(id = "any", isRecommended = false) + val info = tokenInfo(symbol = "USDT", decimals = 6, amount = BigDecimal("100")) + + val result = SwapProviderStateBuilder.buildContentSelectable( + provider = provider, + toTokenInfo = info, + permissionState = PermissionDataState.Empty, + pricesLowerBest = emptyMap(), + selectionType = ProviderState.SelectionType.SELECT, + needApplyFCARestrictions = false, + isBestRate = true, + isNeedBestRateBadge = false, + onProviderClick = onProviderClick, + ) + + assertThat(result.additionalBadge).isEqualTo(ProviderState.AdditionalBadge.Empty) + } + + @Test + fun `GIVEN isBestRate false AND badge enabled WHEN buildContentSelectable THEN no BestTrade badge`() { + val provider = provider(id = "any", isRecommended = false) + val info = tokenInfo(symbol = "USDT", decimals = 6, amount = BigDecimal("100")) + + val result = SwapProviderStateBuilder.buildContentSelectable( + provider = provider, + toTokenInfo = info, + permissionState = PermissionDataState.Empty, + pricesLowerBest = emptyMap(), + selectionType = ProviderState.SelectionType.SELECT, + needApplyFCARestrictions = false, + isBestRate = false, + isNeedBestRateBadge = true, onProviderClick = onProviderClick, )