Bug 1840339 - Add review highlights card for shopping experience

fenix/118.0
rahulsainani 10 months ago committed by mergify[bot]
parent 01fa755817
commit a92a39c79c

@ -5,6 +5,9 @@
package org.mozilla.fenix.shopping.store
import mozilla.components.lib.state.State
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.HighlightType
private const val NUMBER_OF_HIGHLIGHTS_FOR_COMPACT_MODE = 2
/**
* UI state of the review quality check feature.
@ -137,6 +140,15 @@ sealed interface ReviewQualityCheckState : State {
}
}
/**
* Highlights to display in compact mode that contains first 2 highlights of the first
* highlight type.
*/
fun Map<HighlightType, List<String>>.forCompactMode(): Map<HighlightType, List<String>> =
entries.first().let { entry ->
mapOf(entry.key to entry.value.take(NUMBER_OF_HIGHLIGHTS_FOR_COMPACT_MODE))
}
/**
* Fake analysis for showing the UI. To be deleted once the API is integrated.
*/
@ -146,5 +158,31 @@ private val fakeAnalysis = ReviewQualityCheckState.OptedIn.ProductReviewState.An
needsAnalysis = false,
adjustedRating = 3.6f,
productUrl = "123",
highlights = null,
highlights = mapOf(
HighlightType.QUALITY to listOf(
"High quality",
"Excellent craftsmanship",
"Superior materials",
),
HighlightType.PRICE to listOf(
"Affordable prices",
"Great value for money",
"Discounted offers",
),
HighlightType.SHIPPING to listOf(
"Fast and reliable shipping",
"Free shipping options",
"Express delivery",
),
HighlightType.PACKAGING_AND_APPEARANCE to listOf(
"Elegant packaging",
"Attractive appearance",
"Beautiful design",
),
HighlightType.COMPETITIVENESS to listOf(
"Competitive pricing",
"Strong market presence",
"Unbeatable deals",
),
),
)

@ -4,19 +4,32 @@
package org.mozilla.fenix.shopping.ui
import androidx.compose.animation.Crossfade
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.spring
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.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
@ -25,7 +38,9 @@ import org.mozilla.fenix.compose.SwitchWithLabel
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.compose.button.SecondaryButton
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.HighlightType
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.OptedIn.ProductReviewState.AnalysisPresent
import org.mozilla.fenix.shopping.store.forCompactMode
import org.mozilla.fenix.theme.FirefoxTheme
/**
@ -60,6 +75,20 @@ fun ProductAnalysis(
modifier = Modifier.fillMaxWidth(),
)
if (productAnalysis.highlights != null) {
HighlightsCard(
highlights = productAnalysis.highlights,
modifier = Modifier.fillMaxWidth(),
)
Text(
text = stringResource(R.string.review_quality_check_highlights_caption),
color = FirefoxTheme.colors.textPrimary,
style = FirefoxTheme.typography.caption,
modifier = Modifier.padding(horizontal = 16.dp),
)
}
SettingsCard(
modifier = Modifier.fillMaxWidth(),
productRecommendationsEnabled = productRecommendationsEnabled,
@ -121,6 +150,129 @@ private fun AdjustedProductRatingCard(
}
}
@Composable
private fun HighlightsCard(
highlights: Map<HighlightType, List<String>>,
modifier: Modifier = Modifier,
) {
ReviewQualityCheckCard(modifier = modifier) {
var isExpanded by remember { mutableStateOf(false) }
val highlightsForCompactMode = remember(highlights) { highlights.forCompactMode() }
val highlightsToDisplay = remember(isExpanded, highlights) {
if (isExpanded) {
highlights
} else {
highlightsForCompactMode
}
}
Text(
text = stringResource(R.string.review_quality_check_highlights_title),
color = FirefoxTheme.colors.textPrimary,
style = FirefoxTheme.typography.headline8,
)
Spacer(modifier = Modifier.height(16.dp))
Box(
contentAlignment = Alignment.BottomCenter,
modifier = Modifier.fillMaxWidth(),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.animateContentSize(animationSpec = spring()),
) {
highlightsToDisplay.forEach { highlight ->
HighlightTitle(highlight.key)
Spacer(modifier = Modifier.height(8.dp))
highlight.value.forEach {
HighlightText(it)
Spacer(modifier = Modifier.height(4.dp))
}
if (highlightsToDisplay.entries.last().key != highlight.key) {
Spacer(modifier = Modifier.height(16.dp))
}
}
}
Crossfade(
targetState = isExpanded,
label = "HighlightsCard-Crossfade",
) { expanded ->
if (expanded.not()) {
Spacer(
modifier = Modifier
.height(32.dp)
.fillMaxWidth()
.background(
brush = Brush.verticalGradient(
colors = listOf(
FirefoxTheme.colors.layer2.copy(alpha = 0f),
FirefoxTheme.colors.layer2,
),
),
),
)
}
}
}
Spacer(modifier = Modifier.height(8.dp))
SecondaryButton(
text = if (isExpanded) {
stringResource(R.string.review_quality_check_highlights_show_less)
} else {
stringResource(R.string.review_quality_check_highlights_show_more)
},
onClick = { isExpanded = isExpanded.not() },
)
}
}
@Composable
private fun HighlightText(text: String) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Spacer(modifier = Modifier.width(32.dp))
Text(
text = text,
color = FirefoxTheme.colors.textPrimary,
style = FirefoxTheme.typography.body2,
)
}
}
@Composable
private fun HighlightTitle(highlightType: HighlightType) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
val highlight = remember(highlightType) { highlightType.toHighlight() }
Icon(
painter = painterResource(id = highlight.iconResourceId),
tint = FirefoxTheme.colors.iconPrimary,
contentDescription = null,
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(id = highlight.titleResourceId),
color = FirefoxTheme.colors.textPrimary,
style = FirefoxTheme.typography.headline8,
)
}
}
@Composable
private fun SettingsCard(
productRecommendationsEnabled: Boolean,
@ -151,6 +303,42 @@ private fun SettingsCard(
}
}
private fun HighlightType.toHighlight() =
when (this) {
HighlightType.QUALITY -> Highlight.QUALITY
HighlightType.PRICE -> Highlight.PRICE
HighlightType.SHIPPING -> Highlight.SHIPPING
HighlightType.PACKAGING_AND_APPEARANCE -> Highlight.PACKAGING_AND_APPEARANCE
HighlightType.COMPETITIVENESS -> Highlight.COMPETITIVENESS
}
// As part of Bug 1841600, update iconResourceId for each highlight type.
private enum class Highlight(
val titleResourceId: Int,
val iconResourceId: Int,
) {
QUALITY(
titleResourceId = R.string.review_quality_check_highlights_type_quality,
iconResourceId = R.drawable.ic_shopping_cart,
),
PRICE(
titleResourceId = R.string.review_quality_check_highlights_type_price,
iconResourceId = R.drawable.ic_shopping_cart,
),
SHIPPING(
titleResourceId = R.string.review_quality_check_highlights_type_shipping,
iconResourceId = R.drawable.ic_shopping_cart,
),
PACKAGING_AND_APPEARANCE(
titleResourceId = R.string.review_quality_check_highlights_type_packaging_appearance,
iconResourceId = R.drawable.ic_shopping_cart,
),
COMPETITIVENESS(
titleResourceId = R.string.review_quality_check_highlights_type_competitiveness,
iconResourceId = R.drawable.ic_shopping_cart,
),
}
@Composable
@LightDarkPreview
private fun ProductAnalysisPreview() {
@ -169,27 +357,27 @@ private fun ProductAnalysisPreview() {
adjustedRating = 3.6f,
productUrl = "123",
highlights = mapOf(
ReviewQualityCheckState.HighlightType.QUALITY to listOf(
HighlightType.QUALITY to listOf(
"High quality",
"Excellent craftsmanship",
"Superior materials",
),
ReviewQualityCheckState.HighlightType.PRICE to listOf(
HighlightType.PRICE to listOf(
"Affordable prices",
"Great value for money",
"Discounted offers",
),
ReviewQualityCheckState.HighlightType.SHIPPING to listOf(
HighlightType.SHIPPING to listOf(
"Fast and reliable shipping",
"Free shipping options",
"Express delivery",
),
ReviewQualityCheckState.HighlightType.PACKAGING_AND_APPEARANCE to listOf(
HighlightType.PACKAGING_AND_APPEARANCE to listOf(
"Elegant packaging",
"Attractive appearance",
"Beautiful design",
),
ReviewQualityCheckState.HighlightType.COMPETITIVENESS to listOf(
HighlightType.COMPETITIVENESS to listOf(
"Competitive pricing",
"Strong market presence",
"Unbeatable deals",

@ -130,4 +130,13 @@
<string name="review_quality_check_settings_turn_off" translatable="false">Turn off review quality check</string>
<string name="review_quality_check_adjusted_rating_title" translatable="false">Adjusted rating</string>
<string name="review_quality_check_adjusted_rating_description" translatable="false">Unreliable reviews removed</string>
<string name="review_quality_check_highlights_caption" translatable="false">Summarized using information provided by Fakespot.com. View full analysis</string>
<string name="review_quality_check_highlights_title" translatable="false">Highlights from recent reviews</string>
<string name="review_quality_check_highlights_show_less" translatable="false">Show less</string>
<string name="review_quality_check_highlights_show_more" translatable="false">Show more</string>
<string name="review_quality_check_highlights_type_quality" translatable="false">Quality</string>
<string name="review_quality_check_highlights_type_price" translatable="false">Price</string>
<string name="review_quality_check_highlights_type_shipping" translatable="false">Shipping</string>
<string name="review_quality_check_highlights_type_packaging_appearance" translatable="false">Packaging and appearance</string>
<string name="review_quality_check_highlights_type_competitiveness" translatable="false">Competitiveness</string>
</resources>

@ -0,0 +1,68 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.shopping.store
import org.junit.Assert.assertEquals
import org.junit.Test
class ReviewQualityCheckStateTest {
@Test
fun `WHEN highlights are present THEN highlights to display in compact mode should contain first 2 highlights of the first highlight type`() {
val highlights = mapOf(
ReviewQualityCheckState.HighlightType.QUALITY to listOf(
"High quality",
"Excellent craftsmanship",
"Superior materials",
),
ReviewQualityCheckState.HighlightType.PRICE to listOf(
"Affordable prices",
"Great value for money",
"Discounted offers",
),
ReviewQualityCheckState.HighlightType.SHIPPING to listOf(
"Fast and reliable shipping",
"Free shipping options",
"Express delivery",
),
ReviewQualityCheckState.HighlightType.PACKAGING_AND_APPEARANCE to listOf(
"Elegant packaging",
"Attractive appearance",
"Beautiful design",
),
ReviewQualityCheckState.HighlightType.COMPETITIVENESS to listOf(
"Competitive pricing",
"Strong market presence",
"Unbeatable deals",
),
)
val expected = mapOf(
ReviewQualityCheckState.HighlightType.QUALITY to listOf(
"High quality",
"Excellent craftsmanship",
),
)
assertEquals(expected, highlights.forCompactMode())
}
@Test
fun `WHEN only 1 highlight is present THEN highlights to display in compact mode should contain that one`() {
val highlights = mapOf(
ReviewQualityCheckState.HighlightType.PRICE to listOf(
"Affordable prices",
),
)
val expected = mapOf(
ReviewQualityCheckState.HighlightType.PRICE to listOf(
"Affordable prices",
),
)
assertEquals(expected, highlights.forCompactMode())
}
}
Loading…
Cancel
Save