Updated on 2026-08-14
This commit is contained in:
parent
ddfe8e2789
commit
3773f5f8c9
9 changed files with 150 additions and 37 deletions
|
|
@ -5,6 +5,7 @@ import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
import com.tangem.features.marketing.api.MarketingBannerComponent
|
||||
|
||||
internal interface AllOffersComponent : ComposableBottomSheetComponent {
|
||||
|
||||
|
|
@ -14,6 +15,12 @@ internal interface AllOffersComponent : ComposableBottomSheetComponent {
|
|||
val onDismiss: () -> Unit,
|
||||
val openRedirectPage: (quote: OnrampProviderWithQuote.Data) -> Unit,
|
||||
val amountCurrencyCode: String,
|
||||
// Marketing banner components are created and owned by the parent onramp-main component and passed
|
||||
// down so this sheet reuses their models (and their amount-gated request flows) instead of building
|
||||
// its own: [marketingBannerComponent] renders the standalone banner, [linkedMarketingBannerComponent]
|
||||
// renders the per-provider LINKED_TO_PROVIDER banner next to each offer.
|
||||
val marketingBannerComponent: MarketingBannerComponent,
|
||||
val linkedMarketingBannerComponent: MarketingBannerComponent,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, AllOffersComponent>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import dagger.assisted.AssistedInject
|
|||
|
||||
internal class DefaultAllOffersComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted params: AllOffersComponent.Params,
|
||||
@Assisted private val params: AllOffersComponent.Params,
|
||||
) : AllOffersComponent, AppComponentContext by context {
|
||||
|
||||
private val model: AllOffersModel = getOrCreateModel(params)
|
||||
|
|
@ -27,6 +27,8 @@ internal class DefaultAllOffersComponent @AssistedInject constructor(
|
|||
val state by model.state.collectAsState()
|
||||
AllOffersContentSheet(
|
||||
state = state,
|
||||
marketingBannerComponent = params.marketingBannerComponent,
|
||||
linkedMarketingBannerComponent = params.linkedMarketingBannerComponent,
|
||||
onCloseClick = { dismiss() },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,11 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemThemeRedesign
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import com.tangem.domain.onramp.model.PaymentMethodStatus
|
||||
import com.tangem.domain.onramp.model.PaymentMethodType
|
||||
import com.tangem.features.marketing.api.MarketingBannerComponent
|
||||
import com.tangem.features.onramp.alloffers.entity.AllOffersPaymentMethodUM
|
||||
import com.tangem.features.onramp.alloffers.entity.AllOffersStateUM
|
||||
import com.tangem.features.onramp.alloffers.entity.OnrampPaymentMethodConfig
|
||||
|
|
@ -35,13 +37,18 @@ import com.tangem.features.onramp.impl.R
|
|||
import com.tangem.features.onramp.main.entity.OnrampOfferAdvantagesUM
|
||||
import com.tangem.features.onramp.main.entity.OnrampOfferCategoryUM
|
||||
import com.tangem.features.onramp.main.entity.OnrampOfferUM
|
||||
import com.tangem.features.onramp.main.ui.Offer
|
||||
import com.tangem.features.onramp.main.ui.OfferWithLinkedBanner
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
@Composable
|
||||
internal fun AllOffersContentSheet(state: AllOffersStateUM, onCloseClick: () -> Unit) {
|
||||
internal fun AllOffersContentSheet(
|
||||
state: AllOffersStateUM,
|
||||
marketingBannerComponent: MarketingBannerComponent,
|
||||
linkedMarketingBannerComponent: MarketingBannerComponent,
|
||||
onCloseClick: () -> Unit,
|
||||
) {
|
||||
val onBack = remember(state) {
|
||||
{
|
||||
if (state is AllOffersStateUM.Content && state.currentMethod != null) {
|
||||
|
|
@ -71,37 +78,64 @@ internal fun AllOffersContentSheet(state: AllOffersStateUM, onCloseClick: () ->
|
|||
}
|
||||
},
|
||||
content = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(vertical = 8.dp)
|
||||
.animateContentSize(),
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = state is AllOffersStateUM.Content && state.currentMethod != null,
|
||||
transitionSpec = {
|
||||
fadeIn(tween(durationMillis = 220)) togetherWith
|
||||
fadeOut(tween(durationMillis = 220))
|
||||
},
|
||||
label = "Change offers and payment method state",
|
||||
) { shouldShowOffersScreen ->
|
||||
when (state) {
|
||||
AllOffersStateUM.Loading -> AllOffersContentLoading()
|
||||
is AllOffersStateUM.Error -> AllOffersError(state.errorNotification)
|
||||
is AllOffersStateUM.Content -> {
|
||||
if (shouldShowOffersScreen) {
|
||||
state.currentMethod?.let {
|
||||
OffersBasedOnPaymentMethodContent(offers = it.offers)
|
||||
}
|
||||
} else {
|
||||
PaymentMethodsContent(methods = state.methods)
|
||||
AllOffersSheetContent(
|
||||
state = state,
|
||||
marketingBannerComponent = marketingBannerComponent,
|
||||
linkedMarketingBannerComponent = linkedMarketingBannerComponent,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AllOffersSheetContent(
|
||||
state: AllOffersStateUM,
|
||||
marketingBannerComponent: MarketingBannerComponent,
|
||||
linkedMarketingBannerComponent: MarketingBannerComponent,
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// Standalone marketing banner at the top of the sheet (DS3 -> wrap in the redesign theme).
|
||||
// Renders nothing when no matching campaign, so it adds no space in the common case.
|
||||
TangemThemeRedesign {
|
||||
marketingBannerComponent.Content(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(vertical = 8.dp)
|
||||
.animateContentSize(),
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = state is AllOffersStateUM.Content && state.currentMethod != null,
|
||||
transitionSpec = {
|
||||
fadeIn(tween(durationMillis = 220)) togetherWith
|
||||
fadeOut(tween(durationMillis = 220))
|
||||
},
|
||||
label = "Change offers and payment method state",
|
||||
) { shouldShowOffersScreen ->
|
||||
when (state) {
|
||||
AllOffersStateUM.Loading -> AllOffersContentLoading()
|
||||
is AllOffersStateUM.Error -> AllOffersError(state.errorNotification)
|
||||
is AllOffersStateUM.Content -> {
|
||||
if (shouldShowOffersScreen) {
|
||||
state.currentMethod?.let { method ->
|
||||
OffersBasedOnPaymentMethodContent(
|
||||
offers = method.offers,
|
||||
linkedMarketingBannerComponent = linkedMarketingBannerComponent,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
PaymentMethodsContent(methods = state.methods)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -127,7 +161,10 @@ private fun PaymentMethodTitle(onCloseClick: () -> Unit) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun OffersBasedOnPaymentMethodContent(offers: ImmutableList<OnrampOfferUM>) {
|
||||
private fun OffersBasedOnPaymentMethodContent(
|
||||
offers: ImmutableList<OnrampOfferUM>,
|
||||
linkedMarketingBannerComponent: MarketingBannerComponent,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -136,7 +173,7 @@ private fun OffersBasedOnPaymentMethodContent(offers: ImmutableList<OnrampOfferU
|
|||
) {
|
||||
offers.fastForEach { offer ->
|
||||
key("${offer.paymentMethod.id} ${offer.providerName} ${offer.rate}") {
|
||||
Offer(offer)
|
||||
OfferWithLinkedBanner(offer, linkedMarketingBannerComponent)
|
||||
SpacerH(8.dp)
|
||||
}
|
||||
}
|
||||
|
|
@ -250,11 +287,18 @@ private fun AllOffersContentSheetPaymentPreview() {
|
|||
currentMethod = method,
|
||||
onBackClicked = {},
|
||||
),
|
||||
marketingBannerComponent = PreviewMarketingBannerComponent,
|
||||
linkedMarketingBannerComponent = PreviewMarketingBannerComponent,
|
||||
onCloseClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val PreviewMarketingBannerComponent = object : MarketingBannerComponent {
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) = Unit
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
|
|
@ -319,6 +363,8 @@ private fun AllOffersContentSheetOffersPreview() {
|
|||
currentMethod = null,
|
||||
onBackClicked = {},
|
||||
),
|
||||
marketingBannerComponent = PreviewMarketingBannerComponent,
|
||||
linkedMarketingBannerComponent = PreviewMarketingBannerComponent,
|
||||
onCloseClick = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,6 +106,8 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor(
|
|||
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||
openRedirectPage = params.openRedirectPage,
|
||||
amountCurrencyCode = config.amountCurrencyCode,
|
||||
marketingBannerComponent = marketingBannerComponent,
|
||||
linkedMarketingBannerComponent = linkedMarketingBannerComponent,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ internal fun OnrampOffersContent(state: OnrampOffersBlockUM, linkedMarketingBann
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun OfferWithLinkedBanner(offer: OnrampOfferUM, linkedMarketingBannerComponent: MarketingBannerComponent) {
|
||||
internal fun OfferWithLinkedBanner(offer: OnrampOfferUM, linkedMarketingBannerComponent: MarketingBannerComponent) {
|
||||
val hasBanner = linkedMarketingBannerComponent.hasLinkedBanner(offer.providerId)
|
||||
// Square the offer's bottom corners so the bottom-rounded banner glues to it as one card.
|
||||
Offer(offer, roundBottom = !hasBanner)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue