From ddba5fe3cf58c957a619c2f359d8e3c13706761e Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 Jul 2026 15:25:28 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../impl/CashbackBottomSheetComponent.kt | 21 +++ .../impl/DefaultTangemPayCashbackComponent.kt | 49 ++++++ .../cashback/impl/model/CashbackRates.kt | 13 ++ .../cashback/impl/model/CashbackTier.kt | 13 ++ .../TangemPayCashbackAccrualsConverter.kt | 50 ++++++ .../TangemPayCashbackDetailsConverter.kt | 34 ++++ .../TangemPayCashbackInfoTilesConverter.kt | 42 +++++ .../impl/model/TangemPayCashbackModel.kt | 108 +++++++++--- .../impl/model/TangemPayCashbackNavigation.kt | 13 ++ .../model/TangemPayCashbackTiersConverter.kt | 35 ++++ .../cashback/impl/ui/CashbackBottomSheet.kt | 33 ++++ .../TangemPayCashbackAccrualsBottomSheet.kt | 148 +++++++++++++++++ .../ui/TangemPayCashbackDetailsBottomSheet.kt | 88 ++++++++++ .../impl/ui/TangemPayCashbackInfoTiles.kt | 156 ++++++++++++++++++ .../impl/ui/TangemPayCashbackScreen.kt | 119 +++++++------ .../ui/state/TangemPayCashbackAccrualsUM.kt | 25 +++ .../ui/state/TangemPayCashbackDetailsUM.kt | 11 ++ .../ui/state/TangemPayCashbackInfoTilesUM.kt | 20 +++ .../ui/state/TangemPayCashbackScreenUM.kt | 9 + 19 files changed, 919 insertions(+), 68 deletions(-) create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/CashbackBottomSheetComponent.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/CashbackRates.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/CashbackTier.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackAccrualsConverter.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackDetailsConverter.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackInfoTilesConverter.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackNavigation.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackTiersConverter.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/CashbackBottomSheet.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackAccrualsBottomSheet.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackDetailsBottomSheet.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackInfoTiles.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackAccrualsUM.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackDetailsUM.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackInfoTilesUM.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackScreenUM.kt diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/CashbackBottomSheetComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/CashbackBottomSheetComponent.kt new file mode 100644 index 0000000000..927e086d5e --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/CashbackBottomSheetComponent.kt @@ -0,0 +1,21 @@ +package com.tangem.features.tangempay.cashback.impl + +import androidx.compose.runtime.Composable +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent + +internal class CashbackBottomSheetComponent( + appComponentContext: AppComponentContext, + private val onDismiss: () -> Unit, + private val content: @Composable () -> Unit, +) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext { + + override fun dismiss() { + onDismiss() + } + + @Composable + override fun BottomSheet() { + content() + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/DefaultTangemPayCashbackComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/DefaultTangemPayCashbackComponent.kt index 526e6b0a7a..16441aec9c 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/DefaultTangemPayCashbackComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/DefaultTangemPayCashbackComponent.kt @@ -4,10 +4,19 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.arkivanov.decompose.router.slot.childSlot +import com.arkivanov.decompose.router.slot.dismiss import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.features.tangempay.cashback.api.TangemPayCashbackComponent import com.tangem.features.tangempay.cashback.impl.model.TangemPayCashbackModel +import com.tangem.features.tangempay.cashback.impl.model.TangemPayCashbackNavigation +import com.tangem.features.tangempay.cashback.impl.ui.TangemPayCashbackAccrualsBottomSheet +import com.tangem.features.tangempay.cashback.impl.ui.TangemPayCashbackDetailsBottomSheet import com.tangem.features.tangempay.cashback.impl.ui.TangemPayCashbackScreen import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -20,10 +29,50 @@ internal class DefaultTangemPayCashbackComponent @AssistedInject constructor( private val model: TangemPayCashbackModel = getOrCreateModel(params) + private val bottomSheetSlot = childSlot( + source = model.bottomSheetNavigation, + serializer = TangemPayCashbackNavigation.serializer(), + handleBackButton = false, + childFactory = ::bottomSheetChild, + ) + @Composable override fun Content(modifier: Modifier) { val state by model.uiState.collectAsStateWithLifecycle() + val bottomSheet by bottomSheetSlot.subscribeAsState() TangemPayCashbackScreen(state = state, modifier = modifier) + bottomSheet.child?.instance?.BottomSheet() + } + + private fun bottomSheetChild( + navigation: TangemPayCashbackNavigation, + componentContext: ComponentContext, + ): ComposableBottomSheetComponent { + val context = childByContext(componentContext) + return when (navigation) { + TangemPayCashbackNavigation.Details -> CashbackBottomSheetComponent( + appComponentContext = context, + onDismiss = model.bottomSheetNavigation::dismiss, + content = { + val state by model.detailsSheet.collectAsStateWithLifecycle() + TangemPayCashbackDetailsBottomSheet( + state = state, + onDismiss = model.bottomSheetNavigation::dismiss, + ) + }, + ) + TangemPayCashbackNavigation.Accruals -> CashbackBottomSheetComponent( + appComponentContext = context, + onDismiss = model.bottomSheetNavigation::dismiss, + content = { + val state by model.accrualsSheet.collectAsStateWithLifecycle() + TangemPayCashbackAccrualsBottomSheet( + state = state, + onDismiss = model.bottomSheetNavigation::dismiss, + ) + }, + ) + } } @AssistedFactory diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/CashbackRates.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/CashbackRates.kt new file mode 100644 index 0000000000..35c723ad2a --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/CashbackRates.kt @@ -0,0 +1,13 @@ +package com.tangem.features.tangempay.cashback.impl.model + +import java.util.Locale + +internal object CashbackRates { + + private val byTier = mapOf( + "basic" to 1, + "plus" to 2, + ) + + fun forTier(tier: String): Int? = byTier[tier.lowercase(Locale.ROOT)] +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/CashbackTier.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/CashbackTier.kt new file mode 100644 index 0000000000..6fd2f41086 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/CashbackTier.kt @@ -0,0 +1,13 @@ +package com.tangem.features.tangempay.cashback.impl.model + +import com.tangem.domain.models.account.TangemPayTariffPlan + +/** Cashback program tier, mapped once from the domain and shared by the rate tile and the details sheet. */ +internal data class CashbackTier( + val planType: TangemPayTariffPlan.Type, + val rate: Int?, + val label: String, + val scope: String, + val minPurchase: String?, + val monthlyCap: String?, +) \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackAccrualsConverter.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackAccrualsConverter.kt new file mode 100644 index 0000000000..fa6c5a0f7d --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackAccrualsConverter.kt @@ -0,0 +1,50 @@ +package com.tangem.features.tangempay.cashback.impl.model + +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.pay.model.CashbackDocument +import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackAccrualsUM +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + +internal class TangemPayCashbackAccrualsConverter( + private val onDocClick: (url: String) -> Unit, +) : Converter, TangemPayCashbackAccrualsUM> { + + // TODO([REDACTED_TASK_KEY]): move hardcoded strings to string resources + override fun convert(value: List): TangemPayCashbackAccrualsUM { + return TangemPayCashbackAccrualsUM( + title = stringReference("Accruals"), + infoRows = INFO_ROWS, + docRows = value.map { doc -> + TangemPayCashbackAccrualsUM.DocRow( + title = stringReference(doc.title), + onClick = { onDocClick(doc.url) }, + ) + }.toImmutableList(), + ) + } + + private companion object { + val INFO_ROWS = persistentListOf( + TangemPayCashbackAccrualsUM.InfoRow( + title = stringReference("How we calculate cashback?"), + description = stringReference( + "We process purchases within 5 days after the operation and count only completed transactions", + ), + ), + TangemPayCashbackAccrualsUM.InfoRow( + title = stringReference("How we pay cashback?"), + description = stringReference("From the 2nd and the 5th of the next month"), + ), + TangemPayCashbackAccrualsUM.InfoRow( + title = stringReference("Exceptions"), + description = stringReference( + "No cashback will be awarded for in-person/in-store purchases at EU merchants; also for " + + "withdrawals, transfers, quasi-cash, mobile phone bills, government services and certain " + + "other categories", + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackDetailsConverter.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackDetailsConverter.kt new file mode 100644 index 0000000000..5abef3b05c --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackDetailsConverter.kt @@ -0,0 +1,34 @@ +package com.tangem.features.tangempay.cashback.impl.model + +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackDetailsUM +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.toImmutableList + +internal class TangemPayCashbackDetailsConverter : Converter, TangemPayCashbackDetailsUM> { + + // TODO([REDACTED_TASK_KEY]): move hardcoded strings to string resources + override fun convert(value: List): TangemPayCashbackDetailsUM { + return TangemPayCashbackDetailsUM( + title = title(value), + rows = value.map(::tierRow).toImmutableList(), + ) + } + + private fun title(tiers: List): TextReference { + val rates = tiers.mapNotNull { it.rate } + return when { + rates.isEmpty() -> stringReference("Cashback") + rates.size == 1 -> stringReference("Cashback ${rates.single()}%") + else -> stringReference("Cashback up to ${rates.max()}%") + } + } + + private fun tierRow(tier: CashbackTier): TextReference { + val rate = tier.rate?.let { "$it% for " }.orEmpty() + val min = tier.minPurchase?.let { ", min purchase $it" }.orEmpty() + val cap = tier.monthlyCap?.let { ", up to $it per month" }.orEmpty() + return stringReference("$rate${tier.scope} with your ${tier.label}$min$cap") + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackInfoTilesConverter.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackInfoTilesConverter.kt new file mode 100644 index 0000000000..1351e92fa9 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackInfoTilesConverter.kt @@ -0,0 +1,42 @@ +package com.tangem.features.tangempay.cashback.impl.model + +import com.tangem.core.ui.R +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.models.account.TangemPayTariffPlan +import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackInfoTilesUM + +internal class TangemPayCashbackInfoTilesConverter( + private val onRateClick: () -> Unit, + private val onAccrualsClick: () -> Unit, +) { + + // TODO([REDACTED_TASK_KEY]): move hardcoded strings to string resources + fun convert( + tiers: List, + currentPlanType: TangemPayTariffPlan.Type, + currentPlanName: String?, + ): TangemPayCashbackInfoTilesUM { + val rate = tiers.selectTier(currentPlanType)?.rate + return TangemPayCashbackInfoTilesUM( + rate = TangemPayCashbackInfoTilesUM.Tile( + iconRes = R.drawable.ic_percent_24, + title = stringReference(if (rate != null) "Cashback $rate%" else "Cashback"), + subtitle = currentPlanName?.let { stringReference("With your $it plan") } ?: TextReference.EMPTY, + onClick = onRateClick, + ), + accruals = TangemPayCashbackInfoTilesUM.Tile( + iconRes = R.drawable.ic_information_24, + title = stringReference("Accruals"), + subtitle = stringReference("Limits and exceptions"), + onClick = onAccrualsClick, + ), + ) + } + + private fun List.selectTier(planType: TangemPayTariffPlan.Type): CashbackTier? { + return firstOrNull { + planType != TangemPayTariffPlan.Type.UNKNOWN && it.planType == planType + } ?: firstOrNull() + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackModel.kt index 8a8bb76b1a..d84f59fc81 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackModel.kt @@ -1,44 +1,114 @@ package com.tangem.features.tangempay.cashback.impl.model import androidx.compose.runtime.Stable +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.activate import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router -import com.tangem.domain.pay.model.TangemPayCashback -import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackUM +import com.tangem.core.navigation.url.UrlOpener +import com.tangem.domain.models.account.TangemPayTariffPlan +import com.tangem.domain.pay.model.CashbackDocument +import com.tangem.domain.pay.model.CashbackPromotions +import com.tangem.domain.pay.model.CashbackSummary +import com.tangem.domain.pay.repository.CashbackRepository +import com.tangem.domain.pay.repository.OnboardingRepository +import com.tangem.features.tangempay.cashback.api.TangemPayCashbackComponent +import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackAccrualsUM +import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackDetailsUM +import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackScreenUM import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runSuspendCatching +import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import org.joda.time.DateTime -import java.math.BigDecimal +import kotlinx.coroutines.launch import javax.inject.Inject @Stable @ModelScoped internal class TangemPayCashbackModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, + paramsContainer: ParamsContainer, private val router: Router, + private val urlOpener: UrlOpener, + private val cashbackRepository: CashbackRepository, + private val onboardingRepository: OnboardingRepository, ) : Model() { + private val params: TangemPayCashbackComponent.Params = paramsContainer.require() + private val userWalletId get() = params.userWalletId + + val bottomSheetNavigation: SlotNavigation = SlotNavigation() + private val cashbackConverter = TangemPayCashbackUmConverter(onCloseClick = router::pop) + private val tiersConverter = TangemPayCashbackTiersConverter() + private val infoTilesConverter = TangemPayCashbackInfoTilesConverter( + onRateClick = { bottomSheetNavigation.activate(TangemPayCashbackNavigation.Details) }, + onAccrualsClick = { bottomSheetNavigation.activate(TangemPayCashbackNavigation.Accruals) }, + ) + private val detailsConverter = TangemPayCashbackDetailsConverter() + private val accrualsConverter = TangemPayCashbackAccrualsConverter(onDocClick = urlOpener::openUrl) - val uiState: StateFlow - field = MutableStateFlow(cashbackConverter.convert(STUB_CASHBACK)) + val detailsSheet: StateFlow + field = MutableStateFlow(detailsConverter.convert(emptyList())) - private companion object { - // TODO([REDACTED_TASK_KEY]): replace stub with repository load - val STUB_CASHBACK = TangemPayCashback( - confirmedAmount = BigDecimal("22.54"), - pendingAmount = BigDecimal("13.65"), - currency = "USD", - payoutCurrency = "USDC", - payoutNetwork = "Polygon", - period = TangemPayCashback.Period( - year = 2026, - month = 6, - payoutStart = DateTime.parse("2026-07-01"), - payoutEnd = DateTime.parse("2026-07-05"), + val accrualsSheet: StateFlow + field = MutableStateFlow(accrualsConverter.convert(emptyList())) + + val uiState: StateFlow + field = MutableStateFlow( + TangemPayCashbackScreenUM( + cashback = cashbackConverter.convert(value = null), + infoTiles = null, ), ) + + init { + loadCashback() + } + + private fun loadCashback() { + modelScope.launch { + val summaryDeferred = async { loadSummary() } + val promotionsDeferred = async { loadPromotions() } + val docsDeferred = async { loadDocs() } + val planDeferred = async { loadPlan() } + + val summary = summaryDeferred.await() + val promotions = promotionsDeferred.await() + val plan = planDeferred.await() + val tiers = promotions?.let(tiersConverter::convert).orEmpty() + + uiState.value = TangemPayCashbackScreenUM( + cashback = cashbackConverter.convert((summary as? CashbackSummary.Enabled)?.cashback), + infoTiles = promotions?.let { + infoTilesConverter.convert( + tiers = tiers, + currentPlanType = plan?.type ?: TangemPayTariffPlan.Type.UNKNOWN, + currentPlanName = plan?.name, + ) + }, + ) + detailsSheet.value = detailsConverter.convert(tiers) + accrualsSheet.value = accrualsConverter.convert(docsDeferred.await()) + } + } + + private suspend fun loadSummary(): CashbackSummary? = + runSuspendCatching { cashbackRepository.getCashbackSummary(userWalletId).getOrNull() }.getOrNull() + + private suspend fun loadPromotions(): CashbackPromotions? = + runSuspendCatching { cashbackRepository.getCashbackPromotions(userWalletId).getOrNull() }.getOrNull() + + private suspend fun loadDocs(): List = + runSuspendCatching { cashbackRepository.getCashbackAccrualDocs(userWalletId).getOrNull() } + .getOrNull().orEmpty() + + private suspend fun loadPlan(): TangemPayTariffPlan? { + return runSuspendCatching { + onboardingRepository.getCustomerInfo(userWalletId).getOrNull() + }.getOrNull()?.tariffPlan?.plan } } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackNavigation.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackNavigation.kt new file mode 100644 index 0000000000..70dc908d28 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackNavigation.kt @@ -0,0 +1,13 @@ +package com.tangem.features.tangempay.cashback.impl.model + +import kotlinx.serialization.Serializable + +@Serializable +internal sealed class TangemPayCashbackNavigation { + + @Serializable + data object Details : TangemPayCashbackNavigation() + + @Serializable + data object Accruals : TangemPayCashbackNavigation() +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackTiersConverter.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackTiersConverter.kt new file mode 100644 index 0000000000..d8e5bd52c5 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackTiersConverter.kt @@ -0,0 +1,35 @@ +package com.tangem.features.tangempay.cashback.impl.model + +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.getJavaCurrencyByCode +import com.tangem.core.ui.format.bigdecimal.optionalDecimals +import com.tangem.domain.models.account.TangemPayTariffPlan +import com.tangem.domain.pay.model.CashbackPromotions +import com.tangem.utils.converter.Converter +import java.math.BigDecimal + +internal class TangemPayCashbackTiersConverter : Converter> { + + override fun convert(value: CashbackPromotions): List { + return value.cardTiers.map { tier -> + CashbackTier( + planType = TangemPayTariffPlan.Type.fromString(tier.tier), + rate = CashbackRates.forTier(tier.tier), + label = tier.label, + scope = tier.scope, + minPurchase = tier.minTransactionAmount?.formatUsd(), + monthlyCap = tier.monthlyCapAmount?.formatUsd(), + ) + } + } + + private fun BigDecimal.formatUsd(): String { + val currency = getJavaCurrencyByCode(AMOUNT_CURRENCY_CODE) + return format { fiat(currency.currencyCode, currency.symbol).optionalDecimals() } + } + + private companion object { + const val AMOUNT_CURRENCY_CODE = "USD" + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/CashbackBottomSheet.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/CashbackBottomSheet.kt new file mode 100644 index 0000000000..e2889fbdbc --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/CashbackBottomSheet.kt @@ -0,0 +1,33 @@ +package com.tangem.features.tangempay.cashback.impl.ui + +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.runtime.Composable +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType +import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.res.TangemTheme + +@Composable +internal fun CashbackBottomSheet(title: TextReference, onDismiss: () -> Unit, content: @Composable () -> Unit) { + TangemBottomSheet( + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = onDismiss, + content = TangemBottomSheetConfigContent.Empty, + ), + type = TangemBottomSheetType.Modal, + containerColor = TangemTheme.colors3.bg.secondary, + title = { + TangemTopNavigation( + title = title, + contentAlign = TangemTopNavigation.ContentAlign.Center, + windowInsets = WindowInsets(0), + onClose = onDismiss, + ) + }, + content = { content() }, + ) +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackAccrualsBottomSheet.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackAccrualsBottomSheet.kt new file mode 100644 index 0000000000..26b8e25057 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackAccrualsBottomSheet.kt @@ -0,0 +1,148 @@ +package com.tangem.features.tangempay.cashback.impl.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.R +import com.tangem.core.ui.ds2.row.TangemRow +import com.tangem.core.ui.ds2.row.TangemRowText +import com.tangem.core.ui.ds2.row.TangemRowTextRole +import com.tangem.core.ui.ds2.row.TangemRowVerticalAlignment +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign +import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackAccrualsUM +import kotlinx.collections.immutable.persistentListOf + +@Composable +internal fun TangemPayCashbackAccrualsBottomSheet(state: TangemPayCashbackAccrualsUM, onDismiss: () -> Unit) { + CashbackBottomSheet(title = state.title, onDismiss = onDismiss) { + AccrualsContent(state) + } +} + +@Composable +private fun AccrualsContent(state: TangemPayCashbackAccrualsUM) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + ) { + state.infoRows.forEach { row -> + TangemRow( + verticalAlignment = TangemRowVerticalAlignment.Top, + startSlot = { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(id = R.drawable.ic_information_24), + contentDescription = null, + tint = TangemTheme.colors3.icon.primary, + ) + }, + titleSlot = { TangemRowText(text = row.title, role = TangemRowTextRole.Title) }, + subtitleSlot = { + TangemRowText(text = row.description, role = TangemRowTextRole.Subtitle, maxLines = Int.MAX_VALUE) + }, + divider = true, + ) + } + state.docRows.forEachIndexed { index, row -> + TangemRow( + verticalAlignment = TangemRowVerticalAlignment.Center, + startSlot = { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(id = R.drawable.ic_doc_24), + contentDescription = null, + tint = TangemTheme.colors3.icon.primary, + ) + }, + titleSlot = { TangemRowText(text = row.title, role = TangemRowTextRole.Title) }, + endSlot = { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(id = R.drawable.ic_chevron_right_24), + contentDescription = null, + tint = TangemTheme.colors3.icon.secondary, + ) + }, + onClick = row.onClick, + divider = index < state.docRows.lastIndex, + ) + } + } +} + +@Preview(showBackground = true, widthDp = 402) +@Preview(showBackground = true, widthDp = 402, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun TangemPayCashbackAccrualsBottomSheetPreview( + @PreviewParameter(TangemPayCashbackAccrualsPreviewProvider::class) state: TangemPayCashbackAccrualsUM, +) { + TangemThemePreviewRedesign { + Column( + modifier = Modifier + .fillMaxWidth() + .background(TangemTheme.colors3.bg.secondary), + ) { + AccrualsContent(state = state) + } + } +} + +private class TangemPayCashbackAccrualsPreviewProvider : + CollectionPreviewParameterProvider( + listOf( + TangemPayCashbackAccrualsUM( + title = stringReference("Accruals"), + infoRows = previewInfoRows(), + docRows = persistentListOf( + TangemPayCashbackAccrualsUM.DocRow( + title = stringReference("All categories without cashback"), + onClick = {}, + ), + TangemPayCashbackAccrualsUM.DocRow( + title = stringReference("Full terms of cashback program"), + onClick = {}, + ), + ), + ), + TangemPayCashbackAccrualsUM( + title = stringReference("Accruals"), + infoRows = previewInfoRows(), + docRows = persistentListOf(), + ), + ), + ) + +private fun previewInfoRows() = persistentListOf( + TangemPayCashbackAccrualsUM.InfoRow( + title = stringReference("How we calculate cashback?"), + description = stringReference( + "We process purchases within 5 days after the operation and count only completed transactions", + ), + ), + TangemPayCashbackAccrualsUM.InfoRow( + title = stringReference("How we pay cashback?"), + description = stringReference("From the 2nd and the 5th of the next month"), + ), + TangemPayCashbackAccrualsUM.InfoRow( + title = stringReference("Exceptions"), + description = stringReference( + "No cashback will be awarded for in-person/in-store purchases at EU merchants; also for " + + "withdrawals, transfers, quasi-cash, mobile phone bills, government services and certain " + + "other categories", + ), + ), +) \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackDetailsBottomSheet.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackDetailsBottomSheet.kt new file mode 100644 index 0000000000..155006736a --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackDetailsBottomSheet.kt @@ -0,0 +1,88 @@ +package com.tangem.features.tangempay.cashback.impl.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.R +import com.tangem.core.ui.ds2.row.TangemRow +import com.tangem.core.ui.ds2.row.TangemRowText +import com.tangem.core.ui.ds2.row.TangemRowTextRole +import com.tangem.core.ui.ds2.row.TangemRowVerticalAlignment +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.TangemThemePreviewRedesign +import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackDetailsUM +import kotlinx.collections.immutable.persistentListOf + +@Composable +internal fun TangemPayCashbackDetailsBottomSheet(state: TangemPayCashbackDetailsUM, onDismiss: () -> Unit) { + CashbackBottomSheet(title = state.title, onDismiss = onDismiss) { + DetailsContent(state) + } +} + +@Composable +private fun DetailsContent(state: TangemPayCashbackDetailsUM) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + ) { + state.rows.forEachIndexed { index, row -> + TangemRow( + verticalAlignment = TangemRowVerticalAlignment.Top, + startSlot = { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(id = R.drawable.ic_information_24), + contentDescription = null, + tint = TangemTheme.colors3.icon.primary, + ) + }, + titleSlot = { TangemRowText(text = row, role = TangemRowTextRole.Title, maxLines = Int.MAX_VALUE) }, + divider = index < state.rows.lastIndex, + ) + } + } +} + +@Preview(showBackground = true, widthDp = 402) +@Preview(showBackground = true, widthDp = 402, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun TangemPayCashbackDetailsBottomSheetPreview( + @PreviewParameter(TangemPayCashbackDetailsPreviewProvider::class) state: TangemPayCashbackDetailsUM, +) { + TangemThemePreviewRedesign { + Column( + modifier = Modifier + .fillMaxWidth() + .background(TangemTheme.colors3.bg.secondary), + ) { + DetailsContent(state = state) + } + } +} + +private class TangemPayCashbackDetailsPreviewProvider : CollectionPreviewParameterProvider( + listOf( + TangemPayCashbackDetailsUM( + title = stringReference("Cashback up to 2%"), + rows = persistentListOf( + stringReference("1% for All purchases with your Basic cards, min purchase $30, up to $100 per month"), + stringReference("2% for All purchases with your Plus cards, min purchase $30, up to $300 per month"), + ), + ), + ), +) \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackInfoTiles.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackInfoTiles.kt new file mode 100644 index 0000000000..9f460ccc87 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackInfoTiles.kt @@ -0,0 +1,156 @@ +package com.tangem.features.tangempay.cashback.impl.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +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.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.R +import com.tangem.core.ui.ds2.surface.TangemSurface +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign +import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackInfoTilesUM + +@Composable +internal fun TangemPayCashbackInfoTiles(state: TangemPayCashbackInfoTilesUM, modifier: Modifier = Modifier) { + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .height(IntrinsicSize.Min), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Tile( + tile = state.rate, + modifier = Modifier + .weight(1f) + .fillMaxHeight(), + ) + Tile( + tile = state.accruals, + modifier = Modifier + .weight(1f) + .fillMaxHeight(), + ) + } +} + +@Composable +private fun Tile(tile: TangemPayCashbackInfoTilesUM.Tile, modifier: Modifier = Modifier) { + TangemSurface( + modifier = modifier, + color = TangemTheme.colors3.bg.secondary, + shape = RoundedCornerShape(16.dp), + onClick = tile.onClick, + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Box( + modifier = Modifier + .size(40.dp) + .clip(CircleShape) + .background(TangemTheme.colors3.bg.tertiary), + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = Modifier.size(20.dp), + painter = painterResource(id = tile.iconRes), + contentDescription = null, + tint = TangemTheme.colors3.icon.primary, + ) + } + Spacer(modifier = Modifier.weight(1f)) + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = tile.title.resolveReference(), + style = TangemTheme.typography3.body.medium, + color = TangemTheme.colors3.text.primary, + ) + Text( + text = tile.subtitle.resolveReference(), + style = TangemTheme.typography3.caption.medium, + color = TangemTheme.colors3.text.secondary, + ) + } + } + } +} + +@Preview(showBackground = true, widthDp = 402) +@Preview(showBackground = true, widthDp = 402, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun TangemPayCashbackInfoTilesPreview( + @PreviewParameter(TangemPayCashbackInfoTilesPreviewProvider::class) state: TangemPayCashbackInfoTilesUM, +) { + TangemThemePreviewRedesign { + Column( + modifier = Modifier + .background(TangemTheme.colors3.bg.primary) + .padding(vertical = 16.dp), + ) { + TangemPayCashbackInfoTiles(state = state) + } + } +} + +private class TangemPayCashbackInfoTilesPreviewProvider : + CollectionPreviewParameterProvider( + listOf( + TangemPayCashbackInfoTilesUM( + rate = TangemPayCashbackInfoTilesUM.Tile( + iconRes = R.drawable.ic_percent_24, + title = stringReference("Cashback 1%"), + subtitle = stringReference("With your Basic plan"), + onClick = {}, + ), + accruals = TangemPayCashbackInfoTilesUM.Tile( + iconRes = R.drawable.ic_information_24, + title = stringReference("Accruals"), + subtitle = stringReference("Limits and exceptions"), + onClick = {}, + ), + ), + TangemPayCashbackInfoTilesUM( + rate = TangemPayCashbackInfoTilesUM.Tile( + iconRes = R.drawable.ic_percent_24, + title = stringReference("Cashback up to 2%"), + subtitle = stringReference("With your Plus plan"), + onClick = {}, + ), + accruals = TangemPayCashbackInfoTilesUM.Tile( + iconRes = R.drawable.ic_information_24, + title = stringReference("Accruals"), + subtitle = stringReference("Limits and exceptions"), + onClick = {}, + ), + ), + ), + ) \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackScreen.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackScreen.kt index 65635bc62a..f58e8f6783 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackScreen.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/TangemPayCashbackScreen.kt @@ -10,15 +10,12 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.shape.RoundedCornerShape 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.draw.BlurredEdgeTreatment -import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset @@ -31,16 +28,17 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp -import com.tangem.core.ui.ds.image.TangemIconUM -import com.tangem.core.ui.ds.topbar.TangemTopBar -import com.tangem.core.ui.ds2.button.TangemButton +import com.tangem.core.ui.components.haze.hazeForegroundEffectTangem +import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.LocalIsInDarkTheme import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreviewRedesign +import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackInfoTilesUM +import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackScreenUM import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackUM -import com.tangem.features.tangempay.details.impl.R +import dev.chrisbanes.haze.HazeStyle import com.tangem.core.ui.R as CoreUiR private const val GLOW_RADIUS_FACTOR = 0.585f @@ -48,31 +46,24 @@ private const val GLOW_BLUE_ALPHA = 0.20f private const val GLOW_WARM_ALPHA = 0.15f @Composable -internal fun TangemPayCashbackScreen(state: TangemPayCashbackUM, modifier: Modifier = Modifier) { +internal fun TangemPayCashbackScreen(state: TangemPayCashbackScreenUM, modifier: Modifier = Modifier) { Box( modifier = modifier .fillMaxSize() .background(TangemTheme.colors3.bg.primary), ) { - if (state.isEmpty) { + if (state.cashback.isEmpty) { EmptyStateGlow(modifier = Modifier.fillMaxSize()) } Column(modifier = Modifier.fillMaxSize()) { - TangemTopBar( - modifier = Modifier.statusBarsPadding(), + TangemTopNavigation( // TODO([REDACTED_TASK_KEY]): move to string resources title = stringReference("Cashback"), - endContent = { - TangemButton( - iconStart = TangemIconUM.Icon(iconRes = R.drawable.ic_close_24), - onClick = state.onCloseClick, - size = TangemButton.Size.X11, - variant = TangemButton.Variant.Material, - ) - }, + contentAlign = TangemTopNavigation.ContentAlign.Center, + onClose = state.cashback.onCloseClick, ) - HeroBlock(state = state) - state.banner?.let { banner -> + HeroBlock(state = state.cashback) + state.cashback.banner?.let { banner -> CashbackBanner( banner = banner, modifier = Modifier @@ -80,6 +71,12 @@ internal fun TangemPayCashbackScreen(state: TangemPayCashbackUM, modifier: Modif .padding(horizontal = 16.dp), ) } + state.infoTiles?.let { infoTiles -> + TangemPayCashbackInfoTiles( + state = infoTiles, + modifier = Modifier.padding(top = 24.dp), + ) + } } } } @@ -92,7 +89,7 @@ private fun EmptyStateGlow(modifier: Modifier = Modifier) { val blue = if (isDark) Color(0xFF0090F9) else Color(0xFF0092FC) Box( modifier = modifier - .blur(56.dp, edgeTreatment = BlurredEdgeTreatment.Unbounded) + .hazeForegroundEffectTangem(style = HazeStyle(blurRadius = 56.dp, tint = null)) .drawBehind { val radius = size.width * GLOW_RADIUS_FACTOR val center = Offset(x = size.width / 2f, y = 0f) @@ -175,43 +172,67 @@ private fun CashbackBanner(banner: TangemPayCashbackUM.Banner, modifier: Modifie @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, device = Devices.PIXEL_7_PRO) @Composable private fun TangemPayCashbackScreenPreview( - @PreviewParameter(TangemPayCashbackUMProvider::class) state: TangemPayCashbackUM, + @PreviewParameter(TangemPayCashbackScreenUMProvider::class) state: TangemPayCashbackScreenUM, ) { TangemThemePreviewRedesign { TangemPayCashbackScreen(state = state) } } -private class TangemPayCashbackUMProvider : CollectionPreviewParameterProvider( +private class TangemPayCashbackScreenUMProvider : CollectionPreviewParameterProvider( collection = listOf( - TangemPayCashbackUM( - title = stringReference("$22.54 earned in June"), - subtitle = stringReference("Will be deposited on July 1–5"), - isEmpty = false, - banner = TangemPayCashbackUM.Banner( - text = stringReference("Cashback $22.54 for June will be deposited till July 5"), - type = TangemPayCashbackUM.Banner.Type.Info, - ), - onCloseClick = {}, - ), - TangemPayCashbackUM( - title = stringReference("$22.54 earned in June"), - subtitle = stringReference("Will be deposited on July 1–5"), - isEmpty = false, - banner = TangemPayCashbackUM.Banner( - text = stringReference( - "We received a refund for a purchase for which cashback had previously been awarded", + TangemPayCashbackScreenUM( + cashback = TangemPayCashbackUM( + title = stringReference("$22.54 earned in June"), + subtitle = stringReference("Will be deposited on July 1–5"), + isEmpty = false, + banner = TangemPayCashbackUM.Banner( + text = stringReference("Cashback $22.54 for June will be deposited till July 5"), + type = TangemPayCashbackUM.Banner.Type.Info, ), - type = TangemPayCashbackUM.Banner.Type.Error, + onCloseClick = {}, ), - onCloseClick = {}, + infoTiles = previewInfoTiles(), ), - TangemPayCashbackUM( - title = stringReference("Start spending and earn cashback"), - subtitle = stringReference("Collected amount will be shown here"), - isEmpty = true, - banner = null, - onCloseClick = {}, + TangemPayCashbackScreenUM( + cashback = TangemPayCashbackUM( + title = stringReference("$22.54 earned in June"), + subtitle = stringReference("Will be deposited on July 1–5"), + isEmpty = false, + banner = TangemPayCashbackUM.Banner( + text = stringReference( + "We received a refund for a purchase for which cashback had previously been awarded", + ), + type = TangemPayCashbackUM.Banner.Type.Error, + ), + onCloseClick = {}, + ), + infoTiles = null, + ), + TangemPayCashbackScreenUM( + cashback = TangemPayCashbackUM( + title = stringReference("Start spending and earn cashback"), + subtitle = stringReference("Collected amount will be shown here"), + isEmpty = true, + banner = null, + onCloseClick = {}, + ), + infoTiles = null, ), ), +) + +private fun previewInfoTiles() = TangemPayCashbackInfoTilesUM( + rate = TangemPayCashbackInfoTilesUM.Tile( + iconRes = CoreUiR.drawable.ic_percent_24, + title = stringReference("Cashback 1%"), + subtitle = stringReference("With your Basic plan"), + onClick = {}, + ), + accruals = TangemPayCashbackInfoTilesUM.Tile( + iconRes = CoreUiR.drawable.ic_information_24, + title = stringReference("Accruals"), + subtitle = stringReference("Limits and exceptions"), + onClick = {}, + ), ) \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackAccrualsUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackAccrualsUM.kt new file mode 100644 index 0000000000..9f97f0a438 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackAccrualsUM.kt @@ -0,0 +1,25 @@ +package com.tangem.features.tangempay.cashback.impl.ui.state + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.extensions.TextReference +import kotlinx.collections.immutable.ImmutableList + +@Immutable +data class TangemPayCashbackAccrualsUM( + val title: TextReference, + val infoRows: ImmutableList, + val docRows: ImmutableList, +) { + + @Immutable + data class InfoRow( + val title: TextReference, + val description: TextReference, + ) + + @Immutable + data class DocRow( + val title: TextReference, + val onClick: () -> Unit, + ) +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackDetailsUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackDetailsUM.kt new file mode 100644 index 0000000000..a322ebef79 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackDetailsUM.kt @@ -0,0 +1,11 @@ +package com.tangem.features.tangempay.cashback.impl.ui.state + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.extensions.TextReference +import kotlinx.collections.immutable.ImmutableList + +@Immutable +internal data class TangemPayCashbackDetailsUM( + val title: TextReference, + val rows: ImmutableList, +) \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackInfoTilesUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackInfoTilesUM.kt new file mode 100644 index 0000000000..eb5e254e09 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackInfoTilesUM.kt @@ -0,0 +1,20 @@ +package com.tangem.features.tangempay.cashback.impl.ui.state + +import androidx.annotation.DrawableRes +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.extensions.TextReference + +@Immutable +data class TangemPayCashbackInfoTilesUM( + val rate: Tile, + val accruals: Tile, +) { + + @Immutable + data class Tile( + @DrawableRes val iconRes: Int, + val title: TextReference, + val subtitle: TextReference, + val onClick: () -> Unit, + ) +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackScreenUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackScreenUM.kt new file mode 100644 index 0000000000..f04de5e5d6 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/ui/state/TangemPayCashbackScreenUM.kt @@ -0,0 +1,9 @@ +package com.tangem.features.tangempay.cashback.impl.ui.state + +import androidx.compose.runtime.Immutable + +@Immutable +internal data class TangemPayCashbackScreenUM( + val cashback: TangemPayCashbackUM, + val infoTiles: TangemPayCashbackInfoTilesUM?, +) \ No newline at end of file