Updated on 2026-08-14
This commit is contained in:
commit
a66b1bfb14
11 changed files with 700 additions and 53 deletions
|
|
@ -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<Pro
|
|||
subtitle = stringReference("CEX"),
|
||||
infoText = stringReference("1 800,00 POL"),
|
||||
iconUrl = "",
|
||||
badgeList = persistentListOf(
|
||||
BadgeUM(
|
||||
text = stringReference("4.9"),
|
||||
iconRes = R.drawable.ic_star_outline_24,
|
||||
),
|
||||
BadgeUM(
|
||||
text = stringReference("5 mins"),
|
||||
iconRes = R.drawable.ic_speed_24,
|
||||
),
|
||||
BadgeUM(
|
||||
text = stringReference("$3.45"),
|
||||
iconRes = R.drawable.ic_gas_24,
|
||||
extraUM = ProviderChooseUM.ExtraUM.Badges(
|
||||
persistentListOf(
|
||||
BadgeUM(
|
||||
text = stringReference("4.9"),
|
||||
iconRes = R.drawable.ic_star_outline_24,
|
||||
),
|
||||
BadgeUM(
|
||||
text = stringReference("5 mins"),
|
||||
iconRes = R.drawable.ic_speed_24,
|
||||
),
|
||||
BadgeUM(
|
||||
text = stringReference("$3.45"),
|
||||
iconRes = R.drawable.ic_gas_24,
|
||||
),
|
||||
),
|
||||
),
|
||||
isSelected = true,
|
||||
|
|
@ -246,19 +268,8 @@ private class ProviderChooseCryptoPreviewProvider : PreviewParameterProvider<Pro
|
|||
subtitle = stringReference("CEX"),
|
||||
infoText = stringReference("1 799,12 POL"),
|
||||
iconUrl = "",
|
||||
badgeList = persistentListOf(
|
||||
BadgeUM(
|
||||
text = stringReference("4.9"),
|
||||
iconRes = R.drawable.ic_star_outline_24,
|
||||
),
|
||||
BadgeUM(
|
||||
text = stringReference("5 mins"),
|
||||
iconRes = R.drawable.ic_speed_24,
|
||||
),
|
||||
BadgeUM(
|
||||
text = stringReference("$3.45"),
|
||||
iconRes = R.drawable.ic_gas_24,
|
||||
),
|
||||
extraUM = ProviderChooseUM.ExtraUM.Action(
|
||||
text = resourceReference(R.string.express_provider_permission_needed),
|
||||
),
|
||||
isSelected = false,
|
||||
labelUM = ProviderChooseUM.LabelUM.Text(
|
||||
|
|
@ -270,7 +281,9 @@ private class ProviderChooseCryptoPreviewProvider : PreviewParameterProvider<Pro
|
|||
subtitle = stringReference("CEX"),
|
||||
infoText = stringReference("1 799,12 POL"),
|
||||
iconUrl = "",
|
||||
badgeList = persistentListOf(),
|
||||
extraUM = ProviderChooseUM.ExtraUM.Error(
|
||||
text = resourceReference(R.string.express_provider_min_amount, wrappedList("1 POL")),
|
||||
),
|
||||
isSelected = false,
|
||||
labelUM = ProviderChooseUM.LabelUM.Text(
|
||||
text = stringReference("–0.2%"),
|
||||
|
|
@ -281,7 +294,7 @@ private class ProviderChooseCryptoPreviewProvider : PreviewParameterProvider<Pro
|
|||
subtitle = stringReference("CEX"),
|
||||
infoText = stringReference("1 POL"),
|
||||
iconUrl = "",
|
||||
badgeList = persistentListOf(),
|
||||
extraUM = ProviderChooseUM.ExtraUM.Empty,
|
||||
isSelected = false,
|
||||
labelUM = ProviderChooseUM.LabelUM.Text(
|
||||
text = stringReference("–0.2%"),
|
||||
|
|
@ -292,7 +305,7 @@ private class ProviderChooseCryptoPreviewProvider : PreviewParameterProvider<Pro
|
|||
subtitle = stringReference("CEX"),
|
||||
infoText = stringReference("1 POL"),
|
||||
iconUrl = "",
|
||||
badgeList = persistentListOf(),
|
||||
extraUM = ProviderChooseUM.ExtraUM.Empty,
|
||||
isSelected = false,
|
||||
labelUM = null,
|
||||
),
|
||||
|
|
@ -301,18 +314,20 @@ private class ProviderChooseCryptoPreviewProvider : PreviewParameterProvider<Pro
|
|||
subtitle = stringReference("CEX"),
|
||||
infoText = stringReference("1 POL"),
|
||||
iconUrl = "",
|
||||
badgeList = persistentListOf(
|
||||
BadgeUM(
|
||||
text = stringReference("4.9"),
|
||||
iconRes = R.drawable.ic_star_outline_24,
|
||||
),
|
||||
BadgeUM(
|
||||
text = stringReference("5 mins"),
|
||||
iconRes = R.drawable.ic_speed_24,
|
||||
),
|
||||
BadgeUM(
|
||||
text = stringReference("$3.45"),
|
||||
iconRes = R.drawable.ic_gas_24,
|
||||
extraUM = ProviderChooseUM.ExtraUM.Badges(
|
||||
persistentListOf(
|
||||
BadgeUM(
|
||||
text = stringReference("4.9"),
|
||||
iconRes = R.drawable.ic_star_outline_24,
|
||||
),
|
||||
BadgeUM(
|
||||
text = stringReference("5 mins"),
|
||||
iconRes = R.drawable.ic_speed_24,
|
||||
),
|
||||
BadgeUM(
|
||||
text = stringReference("$3.45"),
|
||||
iconRes = R.drawable.ic_gas_24,
|
||||
),
|
||||
),
|
||||
),
|
||||
isSelected = false,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
* @property infoText info text
|
||||
* @property iconUrl icon url
|
||||
* @property isSelected indicated whether component is selected (border outline)
|
||||
* @property badgeList list of badge components
|
||||
* @property extraUM list of extra components
|
||||
* @property labelUM label component (whether AuditLabel or text)
|
||||
*/
|
||||
data class ProviderChooseUM(
|
||||
|
|
@ -23,7 +23,7 @@ data class ProviderChooseUM(
|
|||
val infoText: TextReference,
|
||||
val iconUrl: String,
|
||||
val isSelected: Boolean,
|
||||
val badgeList: ImmutableList<BadgeUM>,
|
||||
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<BadgeUM>,
|
||||
) : ExtraUM()
|
||||
|
||||
data class Action(
|
||||
val text: TextReference,
|
||||
) : ExtraUM()
|
||||
|
||||
data class Error(
|
||||
val text: TextReference,
|
||||
) : ExtraUM()
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SwapQuoteUM>,
|
||||
val callback: ModelCallback,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
|
||||
interface ModelCallback {
|
||||
fun onProviderResult(quoteUM: SwapQuoteUM)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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<SwapProviderListItem>,
|
||||
val selectedProvider: ExpressProvider,
|
||||
)
|
||||
|
||||
internal data class SwapProviderListItem(
|
||||
val providerUM: ProviderChooseUM,
|
||||
val quote: SwapQuoteUM,
|
||||
)
|
||||
|
|
@ -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<SwapChooseProviderBottomSheetContent>
|
||||
field: MutableStateFlow<SwapChooseProviderBottomSheetContent> = 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SwapQuoteUM, SwapProviderListItem?> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<TangemBottomSheetConfigContent>(
|
||||
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<SwapProviderListItem>,
|
||||
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<SwapChooseProviderBottomSheetContent> {
|
||||
override val values: Sequence<SwapChooseProviderBottomSheetContent>
|
||||
get() = sequenceOf(
|
||||
SwapChooseProviderContentPreview.state,
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue