From e3215d3b302a9db7d086460153c23c30c2bcffb1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 5 Dec 2024 20:06:38 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../onramp/main/DefaultOnrampMainComponent.kt | 1 + .../entity/OnrampMainBottomSheetConfig.kt | 5 +- .../main/entity/OnrampProviderBlockUM.kt | 1 + .../amount/OnrampAmountStateFactory.kt | 2 + .../main/model/OnrampMainComponentModel.kt | 1 + .../ui/SelectPaymentMethodBottomSheet.kt | 10 +-- .../providers/SelectProviderComponent.kt | 1 + .../providers/entity/ProviderListItemUM.kt | 2 + .../providers/model/SelectProviderModel.kt | 15 +++- .../previewData/SelectProviderPreviewData.kt | 48 ++++++++++ .../providers/ui/SelectProviderBottomSheet.kt | 88 ++++++++++++++++--- .../features/onramp/utils/ModifierExt.kt | 24 +++++ 12 files changed, 173 insertions(+), 25 deletions(-) create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/previewData/SelectProviderPreviewData.kt create mode 100644 features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/ModifierExt.kt diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt index e84a3a122a..e75146579a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt @@ -70,6 +70,7 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor( params = SelectProviderComponent.Params( onProviderClick = model::onProviderSelected, onDismiss = model.bottomSheetNavigation::dismiss, + selectedProviderId = config.selectedProviderId, selectedPaymentMethod = config.selectedPaymentMethod, cryptoCurrency = params.cryptoCurrency, ), diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainBottomSheetConfig.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainBottomSheetConfig.kt index 0a6f2b8fa1..2b0cff2f34 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainBottomSheetConfig.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainBottomSheetConfig.kt @@ -13,5 +13,8 @@ internal sealed interface OnrampMainBottomSheetConfig { data object CurrenciesList : OnrampMainBottomSheetConfig @Serializable - data class ProvidersList(val selectedPaymentMethod: OnrampPaymentMethod) : OnrampMainBottomSheetConfig + data class ProvidersList( + val selectedProviderId: String, + val selectedPaymentMethod: OnrampPaymentMethod, + ) : OnrampMainBottomSheetConfig } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampProviderBlockUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampProviderBlockUM.kt index 10469ddea3..6f62be5aed 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampProviderBlockUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampProviderBlockUM.kt @@ -6,6 +6,7 @@ sealed class OnrampProviderBlockUM { data object Empty : OnrampProviderBlockUM() data object Loading : OnrampProviderBlockUM() data class Content( + val providerId: String, val paymentMethod: OnrampPaymentMethod, val providerName: String, val isBestRate: Boolean, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt index 4c1add554e..5b86c98d6e 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt @@ -107,6 +107,7 @@ internal class OnrampAmountStateFactory( ), providerBlockState = OnrampProviderBlockUM.Content( paymentMethod = quoteWithProvider.paymentMethod, + providerId = quoteWithProvider.provider.id, providerName = quoteWithProvider.provider.info.name, isBestRate = isBestRate, onClick = onrampIntents::openProviders, @@ -121,6 +122,7 @@ internal class OnrampAmountStateFactory( private fun OnrampQuote.toProviderBlockState(isBestRate: Boolean): OnrampProviderBlockUM { return OnrampProviderBlockUM.Content( paymentMethod = paymentMethod, + providerId = provider.id, providerName = provider.info.name, isBestRate = isBestRate, onClick = onrampIntents::openProviders, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt index f3e4f8ab61..b628a97b08 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt @@ -231,6 +231,7 @@ internal class OnrampMainComponentModel @Inject constructor( bottomSheetNavigation.activate( OnrampMainBottomSheetConfig.ProvidersList( selectedPaymentMethod = providerContentState.paymentMethod, + selectedProviderId = providerContentState.providerId, ), ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/ui/SelectPaymentMethodBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/ui/SelectPaymentMethodBottomSheet.kt index a1fc730184..27096e2eb8 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/ui/SelectPaymentMethodBottomSheet.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/paymentmethod/ui/SelectPaymentMethodBottomSheet.kt @@ -1,7 +1,5 @@ package com.tangem.features.onramp.paymentmethod.ui -import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -20,6 +18,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.paymentmethod.entity.PaymentMethodUM import com.tangem.features.onramp.paymentmethod.entity.PaymentMethodsBottomSheetConfig +import com.tangem.features.onramp.utils.selectedBorder import kotlinx.collections.immutable.ImmutableList @Composable @@ -63,12 +62,7 @@ private fun SelectPaymentMethodBottomSheetContent( val itemModifier = if (isSelected) { Modifier .fillMaxWidth() - .border( - width = TangemTheme.dimens.size1, - color = TangemTheme.colors.text.accent, - shape = RoundedCornerShape(TangemTheme.dimens.radius16), - ) - .clip(RoundedCornerShape(TangemTheme.dimens.radius16)) + .selectedBorder() .clickable(onClick = item.onSelect) .padding( start = TangemTheme.dimens.spacing12, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/SelectProviderComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/SelectProviderComponent.kt index 99d4238e86..fb44d2510a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/SelectProviderComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/SelectProviderComponent.kt @@ -12,6 +12,7 @@ internal interface SelectProviderComponent : ComposableBottomSheetComponent { val onProviderClick: (OnrampProviderWithQuote.Data, Boolean) -> Unit, val onDismiss: () -> Unit, val selectedPaymentMethod: OnrampPaymentMethod, + val selectedProviderId: String, val cryptoCurrency: CryptoCurrency, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListItemUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListItemUM.kt index 9a1105f0f9..79ec50e05c 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListItemUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/ProviderListItemUM.kt @@ -11,6 +11,8 @@ internal sealed interface ProviderListItemUM { val imageUrl: String, val name: String, val rate: String, + val isBestRate: Boolean, + val diffRate: TextReference, val isSelected: Boolean, val onClick: () -> Unit, ) : ProviderListItemUM diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt index bcdbd2fb04..df9a610646 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt @@ -12,6 +12,7 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.domain.onramp.GetOnrampPaymentMethodsUseCase import com.tangem.domain.onramp.GetOnrampProviderWithQuoteUseCase import com.tangem.domain.onramp.GetOnrampSelectedPaymentMethodUseCase @@ -26,12 +27,14 @@ import com.tangem.features.onramp.providers.entity.ProviderListBottomSheetConfig import com.tangem.features.onramp.providers.entity.ProviderListItemUM import com.tangem.features.onramp.providers.entity.ProviderListPaymentMethodUM import com.tangem.features.onramp.providers.entity.ProviderListUM +import com.tangem.utils.StringsSigns.MINUS import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber +import java.math.BigDecimal import javax.inject.Inject @Suppress("LongParameterList") @@ -126,20 +129,26 @@ internal class SelectProviderModel @Inject constructor( private fun List.toProvidersListItems(): ImmutableList { val sorted = sortByRate() - val isQuoteExist = any { it is OnrampProviderWithQuote.Data } - val bestProvider = sorted.firstOrNull().takeIf { isQuoteExist } + val bestProvider = sorted.firstOrNull() as? OnrampProviderWithQuote.Data return sorted.mapIndexed { index, quote -> when (quote) { is OnrampProviderWithQuote.Data -> { val rate = quote.toAmount.value.format { crypto(symbol = quote.toAmount.symbol, decimals = quote.toAmount.decimals) } + val rateDiff = bestProvider?.toAmount?.value?.let { bestRate -> + BigDecimal.ONE - quote.toAmount.value / bestRate + } + val isSelectedProvider = quote.provider.id == params.selectedProviderId + val isSelectedPayment = quote.paymentMethod.id == params.selectedPaymentMethod.id ProviderListItemUM.Available( providerId = quote.provider.id, imageUrl = quote.provider.info.imageLarge, name = quote.provider.info.name, rate = rate, - isSelected = index == 0, + isSelected = isSelectedProvider && isSelectedPayment, + isBestRate = quote == bestProvider, + diffRate = stringReference("$MINUS${rateDiff.format { percent() }}"), onClick = { analyticsEventHandler.send( OnrampAnalyticsEvent.OnProviderChosen( diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/previewData/SelectProviderPreviewData.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/previewData/SelectProviderPreviewData.kt new file mode 100644 index 0000000000..daf5e0ad78 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/previewData/SelectProviderPreviewData.kt @@ -0,0 +1,48 @@ +package com.tangem.features.onramp.providers.model.previewData + +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.features.onramp.providers.entity.ProviderListItemUM +import com.tangem.features.onramp.providers.entity.ProviderListPaymentMethodUM +import com.tangem.features.onramp.providers.entity.ProviderListUM +import kotlinx.collections.immutable.persistentListOf + +internal object SelectProviderPreviewData { + + val state = ProviderListUM( + paymentMethod = ProviderListPaymentMethodUM( + id = "card", + name = "Card", + imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png", + onClick = {}, + ), + providers = persistentListOf( + ProviderListItemUM.Available( + providerId = "mercuryo", + imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/mercuryo1024.png", + name = "Mercuryo", + rate = "1 BTC", + isSelected = true, + isBestRate = true, + diffRate = TextReference.EMPTY, + onClick = {}, + ), + ProviderListItemUM.Available( + providerId = "mercuryo", + imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/mercuryo1024.png", + name = "Mercuryo", + rate = "1 BTC", + isSelected = false, + isBestRate = false, + diffRate = stringReference("-0.03%"), + onClick = {}, + ), + ProviderListItemUM.Unavailable( + providerId = "mercuryo", + imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/mercuryo1024.png", + name = "Mercuryo", + subtitle = stringReference("Avaiable from 0,00413 BTC"), + ), + ), + ) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt index 10a8c79121..952221d9c2 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/ui/SelectProviderBottomSheet.kt @@ -1,5 +1,6 @@ package com.tangem.features.onramp.providers.ui +import android.content.res.Configuration import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -15,6 +16,10 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest @@ -25,11 +30,14 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.paymentmethod.ui.PaymentMethodIcon import com.tangem.features.onramp.providers.entity.ProviderListItemUM import com.tangem.features.onramp.providers.entity.ProviderListPaymentMethodUM import com.tangem.features.onramp.providers.entity.ProviderListUM +import com.tangem.features.onramp.providers.model.previewData.SelectProviderPreviewData +import com.tangem.features.onramp.utils.selectedBorder @Composable internal fun SelectProviderBottomSheet(config: TangemBottomSheetConfig, content: @Composable () -> Unit) { @@ -43,7 +51,9 @@ internal fun SelectProviderBottomSheet(config: TangemBottomSheetConfig, content: @Composable internal fun SelectProviderBottomSheetContent(state: ProviderListUM, modifier: Modifier = Modifier) { Column( - modifier = modifier.verticalScroll(rememberScrollState()), + modifier = modifier + .background(TangemTheme.colors.background.primary) + .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, ) { Text( @@ -103,13 +113,11 @@ private fun PaymentMethodBlock(state: ProviderListPaymentMethodUM, modifier: Mod private fun ProviderItem(state: ProviderListItemUM, modifier: Modifier = Modifier) { when (state) { is ProviderListItemUM.Available -> AvailableProviderItem( - modifier = modifier - .clickable(onClick = state.onClick) - .padding(all = TangemTheme.dimens.spacing12), + modifier = modifier, state = state, ) is ProviderListItemUM.Unavailable -> UnavailableProviderItem( - modifier = modifier.padding(all = TangemTheme.dimens.spacing12), + modifier = modifier, state = state, ) } @@ -118,7 +126,16 @@ private fun ProviderItem(state: ProviderListItemUM, modifier: Modifier = Modifie @Composable private fun AvailableProviderItem(state: ProviderListItemUM.Available, modifier: Modifier = Modifier) { Row( - modifier = modifier, + modifier = modifier + .then( + if (state.isSelected) { + Modifier.selectedBorder() + } else { + Modifier.clip(RoundedCornerShape(16.dp)) + }, + ) + .clickable(onClick = state.onClick) + .padding(all = TangemTheme.dimens.spacing12), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { @@ -140,11 +157,34 @@ private fun AvailableProviderItem(state: ProviderListItemUM.Available, modifier: style = TangemTheme.typography.subtitle2, color = if (state.isSelected) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.secondary, ) - Text( - text = state.rate, - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.primary1, - ) + Column( + horizontalAlignment = Alignment.End, + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = state.rate, + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + ) + if (state.isBestRate) { + Text( + text = stringResource(R.string.express_provider_best_rate), + style = TangemTheme.typography.caption1, + color = TangemTheme.colors.text.constantWhite, + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .background(TangemTheme.colors.icon.accent) + .padding(vertical = 1.dp, horizontal = 6.dp), + ) + } else { + Text( + text = state.diffRate.resolveReference(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.warning, + modifier = Modifier.padding(vertical = 1.dp), + ) + } + } } } @@ -153,7 +193,7 @@ private const val UNAVAILABLE_ALPHA = 0.4F @Composable private fun UnavailableProviderItem(state: ProviderListItemUM.Unavailable, modifier: Modifier = Modifier) { Row( - modifier = modifier, + modifier = modifier.padding(all = TangemTheme.dimens.spacing12), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { @@ -183,4 +223,26 @@ private fun UnavailableProviderItem(state: ProviderListItemUM.Unavailable, modif ) } } -} \ No newline at end of file +} + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun SelectProviderBottomSheetContent_Preview( + @PreviewParameter(SelectProviderBottomSheetContentPreviewProvider::class) + data: ProviderListUM, +) { + TangemThemePreview { + SelectProviderBottomSheetContent(data) + } +} + +private class SelectProviderBottomSheetContentPreviewProvider : + PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + SelectProviderPreviewData.state, + ) +} +// endregion \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/ModifierExt.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/ModifierExt.kt new file mode 100644 index 0000000000..8e38b54575 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/ModifierExt.kt @@ -0,0 +1,24 @@ +package com.tangem.features.onramp.utils + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.res.TangemTheme + +@Composable +internal fun Modifier.selectedBorder() = border( + width = 2.5.dp, + color = TangemTheme.colors.text.accent.copy(alpha = 0.1f), + shape = RoundedCornerShape(18.dp), +) + .padding(2.5.dp) + .border( + width = 1.dp, + color = TangemTheme.colors.text.accent, + shape = RoundedCornerShape(16.dp), + ) + .clip(RoundedCornerShape(16.dp)) \ No newline at end of file