Updated on 2026-08-14

This commit is contained in:
Tangem 2024-12-05 20:06:38 +05:00
parent 59e90854e6
commit e3215d3b30
12 changed files with 173 additions and 25 deletions

View file

@ -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,
),

View file

@ -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
}

View file

@ -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,

View file

@ -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,

View file

@ -231,6 +231,7 @@ internal class OnrampMainComponentModel @Inject constructor(
bottomSheetNavigation.activate(
OnrampMainBottomSheetConfig.ProvidersList(
selectedPaymentMethod = providerContentState.paymentMethod,
selectedProviderId = providerContentState.providerId,
),
)
}

View file

@ -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,

View file

@ -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,
)

View file

@ -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

View file

@ -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<OnrampProviderWithQuote>.toProvidersListItems(): ImmutableList<ProviderListItemUM> {
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(

View file

@ -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"),
),
),
)
}

View file

@ -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
)
}
}
}
}
// 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<ProviderListUM> {
override val values: Sequence<ProviderListUM>
get() = sequenceOf(
SelectProviderPreviewData.state,
)
}
// endregion

View file

@ -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))