[MBL-2994] Show Featured badge (#2475)

* Wire Featured badge in KSRewardCard and AddOnViewHolder (same placement as Secret). Add Featured reward previews on KSRewardsCard.

* Fix lint
This commit is contained in:
JL
2026-02-19 15:44:47 -08:00
committed by GitHub
parent fc89dfaac1
commit 64e27c6489
3 changed files with 184 additions and 12 deletions

View File

@@ -377,7 +377,8 @@ fun RewardCarouselScreen(
} else null,
addonsPillVisible = reward.hasAddons(),
isCTAButtonVisible = project.isAllowedToPledge(),
isSecret = reward.isSecretReward() == true
isSecret = reward.isSecretReward() == true,
isFeatured = reward.isFeatured() == true
)
}
}

View File

@@ -20,16 +20,19 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
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.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import com.kickstarter.R
import com.kickstarter.models.Photo
import com.kickstarter.ui.compose.designsystem.KSFeaturedRewardBadge
import com.kickstarter.ui.compose.designsystem.KSGreenBadge
import com.kickstarter.ui.compose.designsystem.KSPrimaryGreenButton
import com.kickstarter.ui.compose.designsystem.KSSecretRewardBadge
@@ -94,6 +97,35 @@ fun KSRewardCardPreviewImageSelectedSecret() {
}
}
@Composable
@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
fun KSRewardCardPreviewImageSelectedFeatured() {
KSTheme {
KSRewardCard(
amount = "$20",
conversion = "about $400",
title = "Featured reward",
backerCountBadgeText = "23 backers",
image = Photo.builder().altText("").full("").build(),
description = "this is a description",
isCTAButtonEnabled = true,
isSecret = false,
isFeatured = true,
estimatedDelivery = "June 10th, 2026",
includes = listOf("1 Comic Book", "2 pins", "3 happy meals"),
yourSelectionIsVisible = true,
ctaButtonText = "Select",
expirationDateText = "4 Days",
shippingSummaryText = "Anywhere",
addonsPillVisible = true,
remainingText = "5 left",
estimatedShippingCost = "About $10-$15",
onRewardSelectClicked = { }
)
}
}
@Composable
@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
@@ -122,6 +154,35 @@ fun KSRewardCardPreviewImageNoSelectedSecret() {
}
}
@Composable
@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
fun KSRewardCardPreviewImageNoSelectedFeatured() {
KSTheme {
KSRewardCard(
amount = "$20",
conversion = "about $400",
title = "Featured reward",
backerCountBadgeText = "23 backers",
image = Photo.builder().altText("").full("").build(),
description = "this is a description",
isCTAButtonEnabled = true,
isSecret = false,
isFeatured = true,
estimatedDelivery = "June 10th, 2026",
includes = listOf("1 Comic Book", "2 pins", "3 happy meals"),
yourSelectionIsVisible = false,
ctaButtonText = "Select",
expirationDateText = "4 Days",
shippingSummaryText = "Anywhere",
addonsPillVisible = true,
remainingText = "5 left",
estimatedShippingCost = "About $10-$15",
onRewardSelectClicked = { }
)
}
}
@Composable
@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
@@ -177,6 +238,34 @@ fun KSRewardCardPreviewNoImageSelectedSecret() {
}
}
@Composable
@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
fun KSRewardCardPreviewNoImageSelectedFeatured() {
KSTheme {
KSRewardCard(
amount = "$20",
conversion = "about $400",
title = "Featured reward",
backerCountBadgeText = "23 backers",
description = "this is a description",
isCTAButtonEnabled = true,
isSecret = false,
isFeatured = true,
estimatedDelivery = "June 10th, 2026",
includes = listOf("1 Comic Book", "2 pins", "3 happy meals"),
yourSelectionIsVisible = true,
ctaButtonText = "Select",
expirationDateText = "4 Days",
shippingSummaryText = "Anywhere",
addonsPillVisible = true,
remainingText = "5 left",
estimatedShippingCost = "About $10-$15",
onRewardSelectClicked = { }
)
}
}
@Composable
@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
@@ -237,6 +326,40 @@ fun KSRewardCardPreviewNoImageNoSelectedSecret() {
}
}
@Composable
@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
fun KSRewardCardPreviewNoImageNoSelectedFeatured() {
KSTheme {
Box(
modifier = Modifier
.width(KSTheme.dimensions.cardWidth)
.padding(KSTheme.dimensions.paddingMediumLarge)
) {
KSRewardCard(
amount = "$20",
conversion = "about $400",
title = "Featured reward",
backerCountBadgeText = "23 backers",
description = "this is a description",
isCTAButtonEnabled = true,
isSecret = false,
isFeatured = true,
estimatedDelivery = "June 10th, 2026",
includes = listOf("1 Comic Book", "2 pins", "3 happy meals"),
yourSelectionIsVisible = false,
ctaButtonText = "Select",
expirationDateText = "4 Days",
shippingSummaryText = "Anywhere",
addonsPillVisible = true,
remainingText = "5 left",
estimatedShippingCost = "About $10-$15",
onRewardSelectClicked = { }
)
}
}
}
@Composable
@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
@@ -288,27 +411,41 @@ fun KSRewardCard(
estimatedShippingCost: String? = null,
onRewardSelectClicked: () -> Unit,
isSecret: Boolean = false,
isFeatured: Boolean = false,
) {
Box(modifier = modifier.width(KSTheme.dimensions.cardWidth)) {
if (isSecret && image == null && !yourSelectionIsVisible) {
if ((isSecret || isFeatured) && image == null && !yourSelectionIsVisible) {
Box(
modifier = Modifier
.offset(x = dimensions.paddingMedium, y = -(dimensions.secretRewardBadgeOffsetY))
.zIndex(1f)
) {
KSSecretRewardBadge()
Row(
horizontalArrangement = Arrangement.spacedBy(dimensions.paddingSmall),
verticalAlignment = Alignment.CenterVertically
) {
if (isSecret) KSSecretRewardBadge()
if (isFeatured) KSFeaturedRewardBadge()
}
}
}
val cardShape = RoundedCornerShape(KSTheme.dimensions.radiusMediumSmall)
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(KSTheme.dimensions.radiusMediumSmall),
shape = cardShape,
colors = CardDefaults.cardColors(containerColor = KSTheme.colors.kds_white),
border = null,
) {
Column(modifier = Modifier.background(KSTheme.colors.kds_white)) {
Column(
modifier = Modifier
.clip(cardShape)
.background(KSTheme.colors.kds_white)
) {
Box {
if (image != null) {
KSRewardAsyncImage(image = image)
if (isSecret) {
if (isSecret || isFeatured) {
Box(
modifier = Modifier
.align(Alignment.BottomStart)
@@ -317,14 +454,20 @@ fun KSRewardCard(
y = dimensions.secretRewardBadgeOffsetY
)
) {
KSSecretRewardBadge()
Row(
horizontalArrangement = Arrangement.spacedBy(dimensions.paddingSmall),
verticalAlignment = Alignment.CenterVertically
) {
if (isSecret) KSSecretRewardBadge()
if (isFeatured) KSFeaturedRewardBadge()
}
}
}
}
if (yourSelectionIsVisible) {
YourSelectionTag()
if (isSecret && image == null) {
if ((isSecret || isFeatured) && image == null) {
Box(
modifier = Modifier
.align(Alignment.BottomStart)
@@ -333,7 +476,13 @@ fun KSRewardCard(
top = dimensions.paddingXXXLarge
)
) {
KSSecretRewardBadge()
Row(
horizontalArrangement = Arrangement.spacedBy(dimensions.paddingSmall),
verticalAlignment = Alignment.CenterVertically
) {
if (isSecret) KSSecretRewardBadge()
if (isFeatured) KSFeaturedRewardBadge()
}
}
}
}

View File

@@ -2,6 +2,12 @@ package com.kickstarter.ui.viewholders
import android.util.Pair
import android.view.View
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.view.isGone
import androidx.recyclerview.widget.LinearLayoutManager
import coil.load
@@ -14,6 +20,7 @@ import com.kickstarter.libs.utils.ViewUtils
import com.kickstarter.libs.utils.extensions.addToDisposable
import com.kickstarter.models.Reward
import com.kickstarter.ui.adapters.RewardItemsAdapter
import com.kickstarter.ui.compose.designsystem.KSFeaturedRewardBadge
import com.kickstarter.ui.compose.designsystem.KSSecretRewardBadge
import com.kickstarter.ui.compose.designsystem.KSTheme
import com.kickstarter.ui.data.ProjectData
@@ -154,14 +161,22 @@ class AddOnViewHolder(private val binding: ItemAddOnBinding) : KSViewHolder(bind
val badgeOverImage = binding.secretBadgeComposeOverImage
val badgeAboveCard = binding.secretBadgeComposeAboveCard
if (reward.isSecretReward() == true) {
val showBadge = reward.isSecretReward() == true || reward.isFeatured() == true
if (showBadge) {
if (hasImage) {
badgeOverImage.visibility = View.VISIBLE
badgeAboveCard.visibility = View.GONE
badgeOverImage.setContent {
KSTheme {
KSSecretRewardBadge()
Row(
modifier = Modifier.wrapContentSize(),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (reward.isSecretReward() == true) KSSecretRewardBadge()
if (reward.isFeatured() == true) KSFeaturedRewardBadge()
}
}
}
} else {
@@ -170,7 +185,14 @@ class AddOnViewHolder(private val binding: ItemAddOnBinding) : KSViewHolder(bind
badgeAboveCard.setContent {
KSTheme {
KSSecretRewardBadge()
Row(
modifier = Modifier.wrapContentSize(),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (reward.isSecretReward() == true) KSSecretRewardBadge()
if (reward.isFeatured() == true) KSFeaturedRewardBadge()
}
}
}
}