From d77c682e677339291d68e444bd426f49a0f7f8e4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 25 Jun 2025 17:31:17 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../provider/ProviderChooseCrypto.kt | 117 +++++++++------- .../provider/entity/ProviderChooseUM.kt | 20 ++- .../SwapChooseProviderComponent.kt | 61 +++++++++ .../di/SwapChooseProviderModule.kt | 20 +++ .../SwapChooseProviderBottomSheetContent.kt | 16 +++ .../model/SwapChooseProviderModel.kt | 46 +++++++ .../SwapProviderListItemConverter.kt | 85 ++++++++++++ .../ui/SwapChooseProviderBottomSheet.kt | 124 +++++++++++++++++ .../ui/SwapChooseProviderContent.kt | 128 ++++++++++++++++++ .../SwapChooseProviderContentPreview.kt | 90 ++++++++++++ .../swap/v2/impl/common/entity/SwapQuoteUM.kt | 46 +++++++ 11 files changed, 700 insertions(+), 53 deletions(-) create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/SwapChooseProviderComponent.kt create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/di/SwapChooseProviderModule.kt create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/entity/SwapChooseProviderBottomSheetContent.kt create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/SwapChooseProviderModel.kt create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderListItemConverter.kt create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderBottomSheet.kt create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderContent.kt create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/SwapQuoteUM.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/provider/ProviderChooseCrypto.kt b/core/ui/src/main/java/com/tangem/core/ui/components/provider/ProviderChooseCrypto.kt index 1083b56912..4130c95004 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/provider/ProviderChooseCrypto.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/provider/ProviderChooseCrypto.kt @@ -54,7 +54,7 @@ fun ProviderChooseCrypto(providerChooseUM: ProviderChooseUM, onClick: () -> Unit .fillMaxWidth() .padding(all = 12.dp), ) { - val (iconRef, titleRef, badgeRef, infoRef, labelRef) = createRefs() + val (iconRef, titleRef, extraRef, infoRef, labelRef) = createRefs() val titleEndRef = createStartBarrier(infoRef, labelRef) IconContent( @@ -72,19 +72,19 @@ fun ProviderChooseCrypto(providerChooseUM: ProviderChooseUM, onClick: () -> Unit start.linkTo(iconRef.end, 12.dp) end.linkTo(titleEndRef, 4.dp) top.linkTo(parent.top) - bottom.linkTo(badgeRef.top) + bottom.linkTo(extraRef.top) width = Dimension.fillToConstraints }, ) - BadgeContent( + ExtraContent( providerChooseUM = providerChooseUM, - modifier = Modifier.constrainAs(badgeRef) { + modifier = Modifier.constrainAs(extraRef) { start.linkTo(iconRef.end, 12.dp) end.linkTo(labelRef.start, 4.dp) - top.linkTo(titleRef.bottom, 4.dp) + top.linkTo(titleRef.bottom) bottom.linkTo(parent.bottom) width = Dimension.fillToConstraints - visibility = if (providerChooseUM.badgeList.isEmpty()) { + visibility = if (providerChooseUM.extraUM is ProviderChooseUM.ExtraUM.Empty) { Visibility.Gone } else { Visibility.Visible @@ -165,16 +165,36 @@ private fun InfoContent(text: TextReference, modifier: Modifier = Modifier) { } @Composable -private fun BadgeContent(providerChooseUM: ProviderChooseUM, modifier: Modifier = Modifier) { +private fun ExtraContent(providerChooseUM: ProviderChooseUM, modifier: Modifier = Modifier) { Row( horizontalArrangement = Arrangement.spacedBy(4.dp), modifier = modifier, ) { - providerChooseUM.badgeList.fastForEach { badge -> - Badge( - iconRes = badge.iconRes, - text = badge.text, + when (val extraUM = providerChooseUM.extraUM) { + is ProviderChooseUM.ExtraUM.Action -> Text( + text = extraUM.text.resolveReference(), + style = TangemTheme.typography.caption1, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier + .padding(top = 4.dp) + .clip(RoundedCornerShape(4.dp)) + .background(TangemTheme.colors.button.secondary) + .padding(horizontal = 6.dp, vertical = 1.dp), ) + is ProviderChooseUM.ExtraUM.Badges -> extraUM.badgeList.fastForEach { badge -> + Badge( + iconRes = badge.iconRes, + text = badge.text, + modifier = Modifier.padding(top = 4.dp), + ) + } + is ProviderChooseUM.ExtraUM.Error -> Text( + text = extraUM.text.resolveReference(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier.padding(top = 6.dp), + ) + else -> Unit } } } @@ -219,18 +239,20 @@ private class ProviderChooseCryptoPreviewProvider : PreviewParameterProvider, + val extraUM: ExtraUM, val labelUM: LabelUM?, ) { @Immutable @@ -36,4 +36,20 @@ data class ProviderChooseUM( val text: TextReference, ) : LabelUM() } + + @Immutable + sealed class ExtraUM { + data object Empty : ExtraUM() + data class Badges( + val badgeList: ImmutableList, + ) : ExtraUM() + + data class Action( + val text: TextReference, + ) : ExtraUM() + + data class Error( + val text: TextReference, + ) : ExtraUM() + } } \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/SwapChooseProviderComponent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/SwapChooseProviderComponent.kt new file mode 100644 index 0000000000..744a8a3c91 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/SwapChooseProviderComponent.kt @@ -0,0 +1,61 @@ +package com.tangem.features.swap.v2.impl.chooseprovider + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.features.swap.v2.impl.chooseprovider.model.SwapChooseProviderModel +import com.tangem.features.swap.v2.impl.chooseprovider.ui.SwapChooseProviderBottomSheet +import com.tangem.features.swap.v2.impl.chooseprovider.ui.SwapChooseProviderContent +import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM +import kotlinx.collections.immutable.ImmutableList + +internal class SwapChooseProviderComponent( + context: AppComponentContext, + private val params: Params, +) : ComposableBottomSheetComponent, AppComponentContext by context { + + private val model: SwapChooseProviderModel = getOrCreateModel(params = params) + + override fun dismiss() { + params.onDismiss() + } + + @Composable + override fun BottomSheet() { + val state = model.uiState.collectAsStateWithLifecycle() + + val bottomSheetConfig = remember(key1 = this) { + TangemBottomSheetConfig( + isShown = true, + onDismissRequest = ::dismiss, + content = TangemBottomSheetConfigContent.Empty, + ) + } + + SwapChooseProviderBottomSheet(config = bottomSheetConfig) { + SwapChooseProviderContent( + providerList = state.value.providerList, + onProviderClick = model::onProviderClick, + ) + } + } + + data class Params( + val cryptoCurrency: CryptoCurrency, + val selectedProvider: ExpressProvider, + val providers: ImmutableList, + val callback: ModelCallback, + val onDismiss: () -> Unit, + ) + + interface ModelCallback { + fun onProviderResult(quoteUM: SwapQuoteUM) + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/di/SwapChooseProviderModule.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/di/SwapChooseProviderModule.kt new file mode 100644 index 0000000000..db602af2e5 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/di/SwapChooseProviderModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.swap.v2.impl.chooseprovider.di + +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.swap.v2.impl.chooseprovider.model.SwapChooseProviderModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@InstallIn(ModelComponent::class) +@Module +internal interface SwapChooseProviderModule { + + @Binds + @IntoMap + @ClassKey(SwapChooseProviderModel::class) + fun provideSwapChooseProviderModel(impl: SwapChooseProviderModel): Model +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/entity/SwapChooseProviderBottomSheetContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/entity/SwapChooseProviderBottomSheetContent.kt new file mode 100644 index 0000000000..ad5eb47e18 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/entity/SwapChooseProviderBottomSheetContent.kt @@ -0,0 +1,16 @@ +package com.tangem.features.swap.v2.impl.chooseprovider.entity + +import com.tangem.core.ui.components.provider.entity.ProviderChooseUM +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM +import kotlinx.collections.immutable.ImmutableList + +internal data class SwapChooseProviderBottomSheetContent( + val providerList: ImmutableList, + val selectedProvider: ExpressProvider, +) + +internal data class SwapProviderListItem( + val providerUM: ProviderChooseUM, + val quote: SwapQuoteUM, +) \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/SwapChooseProviderModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/SwapChooseProviderModel.kt new file mode 100644 index 0000000000..a4a4613f7f --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/SwapChooseProviderModel.kt @@ -0,0 +1,46 @@ +package com.tangem.features.swap.v2.impl.chooseprovider.model + +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.features.swap.v2.impl.chooseprovider.SwapChooseProviderComponent +import com.tangem.features.swap.v2.impl.chooseprovider.entity.SwapChooseProviderBottomSheetContent +import com.tangem.features.swap.v2.impl.chooseprovider.model.converter.SwapProviderListItemConverter +import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ModelScoped +internal class SwapChooseProviderModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, +) : Model() { + + private val params: SwapChooseProviderComponent.Params = paramsContainer.require() + + val uiState: StateFlow + field: MutableStateFlow = MutableStateFlow(getInitialState()) + + private val swapProviderListItemConverter by lazy(LazyThreadSafetyMode.NONE) { + SwapProviderListItemConverter( + cryptoCurrency = params.cryptoCurrency, + selectedProvider = params.selectedProvider, + ) + } + + fun onProviderClick(quoteUM: SwapQuoteUM) { + params.callback.onProviderResult(quoteUM) + } + + private fun getInitialState(): SwapChooseProviderBottomSheetContent { + return SwapChooseProviderBottomSheetContent( + providerList = swapProviderListItemConverter.convertList(params.providers) + .filterNotNull() + .toPersistentList(), + selectedProvider = params.selectedProvider, + ) + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderListItemConverter.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderListItemConverter.kt new file mode 100644 index 0000000000..75234f816b --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderListItemConverter.kt @@ -0,0 +1,85 @@ +package com.tangem.features.swap.v2.impl.chooseprovider.model.converter + +import com.tangem.core.ui.components.audits.AuditLabelUM +import com.tangem.core.ui.components.provider.entity.ProviderChooseUM +import com.tangem.core.ui.components.provider.entity.ProviderChooseUM.LabelUM +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +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.domain.express.models.ExpressError +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.features.swap.v2.impl.R +import com.tangem.features.swap.v2.impl.chooseprovider.entity.SwapProviderListItem +import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM +import com.tangem.utils.converter.Converter + +internal class SwapProviderListItemConverter( + private val cryptoCurrency: CryptoCurrency, + private val selectedProvider: ExpressProvider, +) : Converter { + override fun convert(value: SwapQuoteUM): SwapProviderListItem? { + val provider = value.provider ?: return null + + return SwapProviderListItem( + providerUM = ProviderChooseUM( + title = stringReference(provider.name), + subtitle = stringReference(provider.type.typeName), + infoText = when (value) { + is SwapQuoteUM.Content -> value.quoteAmountValue + else -> TextReference.EMPTY + }, + iconUrl = provider.imageLarge, + isSelected = provider.providerId == selectedProvider.providerId, + extraUM = getExtras(value = value), + labelUM = getLabel(value = value), + ), + quote = value, + ) + } + + private fun getExtras(value: SwapQuoteUM) = when (value) { + is SwapQuoteUM.Allowance -> ProviderChooseUM.ExtraUM.Action( + text = resourceReference(R.string.express_provider_permission_needed), + ) + is SwapQuoteUM.Error -> { + ProviderChooseUM.ExtraUM.Error( + when (val error = value.expressError) { + is ExpressError.AmountError.TooSmallError -> resourceReference( + id = R.string.express_provider_min_amount, + formatArgs = wrappedList( + error.amount.format { crypto(cryptoCurrency) }, + ), + ) + is ExpressError.AmountError.NotEnoughAllowanceError, + is ExpressError.AmountError.TooBigError, + -> resourceReference( + id = R.string.express_provider_max_amount, + formatArgs = wrappedList( + error.amount.format { crypto(cryptoCurrency) }, + ), + ) + else -> TextReference.EMPTY + }, + ) + } + SwapQuoteUM.Empty, + SwapQuoteUM.Loading, + is SwapQuoteUM.Content, + -> ProviderChooseUM.ExtraUM.Empty + } + + private fun getLabel(value: SwapQuoteUM) = when (val diff = (value as? SwapQuoteUM.Content)?.diffPercent) { + SwapQuoteUM.Content.DifferencePercent.Best -> LabelUM.Info( + AuditLabelUM( + text = resourceReference(R.string.express_provider_best_rate), + type = AuditLabelUM.Type.Info, + ), + ) + is SwapQuoteUM.Content.DifferencePercent.Diff -> LabelUM.Text(diff.percent) + else -> null + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderBottomSheet.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderBottomSheet.kt new file mode 100644 index 0000000000..1b2b8d2e04 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderBottomSheet.kt @@ -0,0 +1,124 @@ +package com.tangem.features.swap.v2.impl.chooseprovider.ui + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +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.fastForEachIndexed +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet +import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle +import com.tangem.core.ui.components.provider.ProviderChooseCrypto +import com.tangem.core.ui.extensions.conditional +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.swap.v2.impl.R +import com.tangem.features.swap.v2.impl.chooseprovider.entity.SwapChooseProviderBottomSheetContent +import com.tangem.features.swap.v2.impl.chooseprovider.entity.SwapProviderListItem +import com.tangem.features.swap.v2.impl.chooseprovider.ui.preview.SwapChooseProviderContentPreview +import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM +import kotlinx.collections.immutable.ImmutableList + +@Composable +internal fun SwapChooseProviderBottomSheet(config: TangemBottomSheetConfig, content: @Composable () -> Unit) { + TangemModalBottomSheet( + config = config, + title = { + TangemModalBottomSheetTitle( + title = resourceReference(R.string.express_choose_provider), + endIconRes = R.drawable.ic_close_24, + onEndClick = config.onDismissRequest, + ) + }, + ) { + content() + } +} + +@Composable +internal fun SwapChooseProviderContent( + providerList: ImmutableList, + onProviderClick: (SwapQuoteUM) -> Unit, + modifier: Modifier = Modifier, +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(horizontal = 13.dp), + ) { + providerList.fastForEachIndexed { index, provider -> + ProviderChooseCrypto( + providerChooseUM = provider.providerUM, + onClick = { onProviderClick(provider.quote) }, + modifier = modifier + .conditional(index == 0) { padding(top = 6.dp) }, + ) + } + Icon( + painter = painterResource(id = R.drawable.ic_lightning_16), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + modifier = Modifier.padding( + top = 16.dp, + start = 3.dp, + end = 3.dp, + ), + ) + Text( + text = stringResourceSafe(R.string.express_more_providers_soon), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.icon.informative, + modifier = Modifier + .padding( + top = 4.dp, + bottom = 24.dp, + start = 3.dp, + end = 3.dp, + ), + textAlign = TextAlign.Center, + ) + } +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun SwapChooseProviderContent_Preview( + @PreviewParameter(ProviderChooseCryptoPreviewProvider::class) params: SwapChooseProviderBottomSheetContent, +) { + TangemThemePreview { + SwapChooseProviderBottomSheet( + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = {}, + content = TangemBottomSheetConfigContent.Empty, + ), + ) { + SwapChooseProviderContent( + providerList = params.providerList, + onProviderClick = {}, + ) + } + } +} + +private class ProviderChooseCryptoPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + SwapChooseProviderContentPreview.state, + ) +} +// endregion \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderContent.kt new file mode 100644 index 0000000000..43c2d3c8f0 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderContent.kt @@ -0,0 +1,128 @@ +package com.tangem.features.swap.v2.impl.chooseprovider.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.ripple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.tangem.core.ui.components.SpacerWMax +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemColorPalette +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.express.models.ExpressProviderType +import com.tangem.domain.express.models.ExpressRateType +import com.tangem.features.swap.v2.impl.R + +@Composable +fun SwapChooseProviderContent(expressProvider: ExpressProvider?, onClick: () -> Unit, modifier: Modifier = Modifier) { + Column( + modifier = modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(), + onClick = onClick, + ), + ) { + HorizontalDivider( + thickness = 0.5.dp, + color = TangemTheme.colors.stroke.primary, + modifier = Modifier.padding(horizontal = 12.dp), + ) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier.padding(12.dp), + ) { + Icon( + painter = rememberVectorPainter( + ImageVector.vectorResource(R.drawable.ic_exchange_horizontal_24), + ), + tint = TangemTheme.colors.icon.accent, + contentDescription = null, + ) + Text( + text = stringResourceSafe(R.string.express_provider), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + modifier = Modifier.padding(start = 8.dp), + ) + SpacerWMax() + SubcomposeAsyncImage( + modifier = modifier + .size(20.dp) + .clip(RoundedCornerShape(4.dp)) + .background(TangemColorPalette.Light1), + model = ImageRequest.Builder(context = LocalContext.current) + .data(expressProvider?.imageLarge) + .crossfade(enable = true) + .allowHardware(false) + .build(), + contentDescription = null, + ) + Text( + text = expressProvider?.name.orEmpty(), // todo provider error + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier.padding(start = 6.dp), + ) + Icon( + painter = rememberVectorPainter( + ImageVector.vectorResource(R.drawable.ic_chevron_24), + ), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + modifier = Modifier.padding(start = 4.dp), + ) + } + } +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun SwapChooseProviderContent_Preview() { + TangemThemePreview { + Box( + modifier = Modifier.background(TangemTheme.colors.background.tertiary), + ) { + SwapChooseProviderContent( + expressProvider = ExpressProvider( + providerId = "changelly", + rateTypes = listOf(ExpressRateType.Fixed), + name = "Changelly", + type = ExpressProviderType.CEX, + imageLarge = "", + termsOfUse = "", + privacyPolicy = "", + isRecommended = false, + slippage = null, + ), + onClick = {}, + ) + } + } +} +// endregion \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt new file mode 100644 index 0000000000..6558e68f96 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt @@ -0,0 +1,90 @@ +package com.tangem.features.swap.v2.impl.chooseprovider.ui.preview + +import com.tangem.core.ui.components.audits.AuditLabelUM +import com.tangem.core.ui.components.provider.entity.ProviderChooseUM +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.express.models.ExpressProviderType +import com.tangem.domain.express.models.ExpressRateType +import com.tangem.features.swap.v2.impl.R +import com.tangem.features.swap.v2.impl.chooseprovider.entity.SwapChooseProviderBottomSheetContent +import com.tangem.features.swap.v2.impl.chooseprovider.entity.SwapProviderListItem +import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM +import kotlinx.collections.immutable.persistentListOf +import java.math.BigDecimal + +internal object SwapChooseProviderContentPreview { + + private val provider1 = ExpressProvider( + providerId = "changenow", + rateTypes = listOf(ExpressRateType.Float), + name = "ChangeNow", + type = ExpressProviderType.CEX, + imageLarge = "", + termsOfUse = "", + privacyPolicy = "", + isRecommended = true, + slippage = BigDecimal.ZERO, + ) + private val provider2 = provider1.copy( + providerId = "changelly", + name = "Changelly", + ) + + private val quote1 = SwapQuoteUM.Content( + provider = provider1, + quoteAmount = "123".toBigDecimal(), + quoteAmountValue = stringReference("123"), + diffPercent = SwapQuoteUM.Content.DifferencePercent.Best, + ) + + private val quote2 = SwapQuoteUM.Content( + provider = provider2, + quoteAmount = "13.12".toBigDecimal(), + quoteAmountValue = stringReference("13.12"), + diffPercent = SwapQuoteUM.Content.DifferencePercent.Empty, + ) + + val state = SwapChooseProviderBottomSheetContent( + providerList = persistentListOf( + SwapProviderListItem( + providerUM = ProviderChooseUM( + title = stringReference(provider1.name), + subtitle = stringReference(provider1.type.typeName), + infoText = stringReference("1800 POL"), + iconUrl = "", + isSelected = true, + extraUM = ProviderChooseUM.ExtraUM.Empty, + labelUM = ProviderChooseUM.LabelUM.Info( + AuditLabelUM( + text = resourceReference(R.string.express_provider_best_rate), + type = AuditLabelUM.Type.Info, + ), + ), + ), + quote = quote1, + ), + SwapProviderListItem( + providerUM = ProviderChooseUM( + title = stringReference(provider2.name), + subtitle = stringReference(provider2.type.typeName), + infoText = stringReference("1800 POL"), + iconUrl = "", + isSelected = false, + extraUM = ProviderChooseUM.ExtraUM.Action( + text = resourceReference(R.string.express_provider_permission_needed), + ), + labelUM = ProviderChooseUM.LabelUM.Info( + AuditLabelUM( + text = resourceReference(R.string.express_provider_best_rate), + type = AuditLabelUM.Type.Info, + ), + ), + ), + quote = quote2, + ), + ), + selectedProvider = provider1, + ) +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/SwapQuoteUM.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/SwapQuoteUM.kt new file mode 100644 index 0000000000..0b4abfd3c3 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/SwapQuoteUM.kt @@ -0,0 +1,46 @@ +package com.tangem.features.swap.v2.impl.common.entity + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.express.models.ExpressError +import com.tangem.domain.express.models.ExpressProvider +import java.math.BigDecimal + +@Immutable +internal sealed class SwapQuoteUM { + + abstract val provider: ExpressProvider? + + data object Empty : SwapQuoteUM() { + override val provider = null + } + + data object Loading : SwapQuoteUM() { + override val provider = null + } + + data class Error( + override val provider: ExpressProvider, + val expressError: ExpressError, + ) : SwapQuoteUM() + + data class Allowance( + override val provider: ExpressProvider, + val allowanceContract: String, + ) : SwapQuoteUM() + + data class Content( + override val provider: ExpressProvider, + val quoteAmount: BigDecimal, + val quoteAmountValue: TextReference, + val diffPercent: DifferencePercent, + ) : SwapQuoteUM() { + sealed class DifferencePercent { + data object Empty : DifferencePercent() + data object Best : DifferencePercent() + data class Diff( + val percent: TextReference, + ) : DifferencePercent() + } + } +} \ No newline at end of file