diff --git a/app/src/main/java/org/mozilla/fenix/shopping/middleware/ReviewQualityCheckNavigationMiddleware.kt b/app/src/main/java/org/mozilla/fenix/shopping/middleware/ReviewQualityCheckNavigationMiddleware.kt index 76fe36574..105a91105 100644 --- a/app/src/main/java/org/mozilla/fenix/shopping/middleware/ReviewQualityCheckNavigationMiddleware.kt +++ b/app/src/main/java/org/mozilla/fenix/shopping/middleware/ReviewQualityCheckNavigationMiddleware.kt @@ -64,5 +64,7 @@ class ReviewQualityCheckNavigationMiddleware( is ReviewQualityCheckAction.OpenOnboardingPrivacyPolicyLink -> PRIVACY_POLICY_URL is ReviewQualityCheckAction.OpenPoweredByLink -> POWERED_BY_URL + + is ReviewQualityCheckAction.OpenRecommendedProduct -> action.productUrl } } diff --git a/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckAction.kt b/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckAction.kt index 4efaceaca..6bd98c14b 100644 --- a/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckAction.kt +++ b/app/src/main/java/org/mozilla/fenix/shopping/store/ReviewQualityCheckAction.kt @@ -166,4 +166,11 @@ sealed interface ReviewQualityCheckAction : Action { * Triggered when the user reports a product is back in stock. */ object ReportProductBackInStock : TelemetryAction + + /** + * Triggered when the user clicks on the recommended product. + * + * @property productUrl The product's link to open. + */ + data class OpenRecommendedProduct(val productUrl: String) : NavigationMiddlewareAction } diff --git a/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysis.kt b/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysis.kt index c99280e2f..a620d4742 100644 --- a/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysis.kt +++ b/app/src/main/java/org/mozilla/fenix/shopping/ui/ProductAnalysis.kt @@ -8,6 +8,7 @@ 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.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -18,6 +19,7 @@ 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.size import androidx.compose.foundation.layout.width import androidx.compose.material.Icon import androidx.compose.material.Text @@ -33,22 +35,26 @@ import androidx.compose.ui.layout.layout import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import org.mozilla.fenix.R import org.mozilla.fenix.compose.Divider +import org.mozilla.fenix.compose.Image 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.ReviewQualityCheckState.OptedIn.ProductReviewState.AnalysisPresent.AnalysisStatus +import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.RecommendedProductState import org.mozilla.fenix.shopping.store.forCompactMode import org.mozilla.fenix.theme.FirefoxTheme import java.util.SortedMap private val combinedParentHorizontalPadding = 32.dp +private val productRecommendationImageSize = 60.dp /** * UI for review quality check content displaying product analysis. @@ -64,6 +70,7 @@ private val combinedParentHorizontalPadding = 32.dp * @param onFooterLinkClick Invoked when the user clicks on the footer link. * @param onShowMoreRecentReviewsClicked Invoked when the user clicks to show more recent reviews. * @param onExpandSettings Invoked when the user expands the settings card. + * @param onRecommendedProductClick Invoked when the user clicks on the product recommendation. * @param modifier The modifier to be applied to the Composable. */ @Composable @@ -79,6 +86,7 @@ fun ProductAnalysis( onFooterLinkClick: () -> Unit, onShowMoreRecentReviewsClicked: () -> Unit, onExpandSettings: () -> Unit, + onRecommendedProductClick: (String) -> Unit, modifier: Modifier = Modifier, ) { Column( @@ -129,6 +137,15 @@ fun ProductAnalysis( onLearnMoreClick = onReviewGradeLearnMoreClick, ) + if (productAnalysis.recommendedProductState is RecommendedProductState.Product) { + ProductRecommendation( + product = productAnalysis.recommendedProductState, + onClick = { + onRecommendedProductClick(productAnalysis.recommendedProductState.productUrl) + }, + ) + } + ReviewQualityCheckSettingsCard( productRecommendationsEnabled = productRecommendationsEnabled, onProductRecommendationsEnabledStateChange = onProductRecommendationsEnabledStateChange, @@ -404,6 +421,68 @@ private enum class Highlight( ), } +@Composable +private fun ProductRecommendation( + product: RecommendedProductState.Product, + onClick: () -> Unit, +) { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + ReviewQualityCheckCard( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick), + ) { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = stringResource(R.string.review_quality_check_ad_title), + color = FirefoxTheme.colors.textPrimary, + style = FirefoxTheme.typography.headline8, + ) + + Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + Image( + url = product.imageUrl, + modifier = Modifier.size(productRecommendationImageSize), + targetSize = productRecommendationImageSize, + ) + + Text( + text = product.name, + modifier = Modifier.weight(1.0f), + color = FirefoxTheme.colors.textAccent, + textDecoration = TextDecoration.Underline, + style = FirefoxTheme.typography.body2, + ) + + ReviewGradeCompact(grade = product.reviewGrade) + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = product.formattedPrice, + color = FirefoxTheme.colors.textPrimary, + style = FirefoxTheme.typography.headline8, + ) + + StarRating(value = product.adjustedRating) + } + } + } + + Text( + text = stringResource( + id = R.string.review_quality_check_ad_caption, + stringResource(id = R.string.shopping_product_name), + ), + color = FirefoxTheme.colors.textSecondary, + style = FirefoxTheme.typography.body2, + ) + } +} + private class ProductAnalysisPreviewModel( val productRecommendationsEnabled: Boolean?, val productAnalysis: AnalysisPresent, @@ -443,8 +522,7 @@ private class ProductAnalysisPreviewModel( "Unbeatable deals", ), ), - recommendedProductState: ReviewQualityCheckState.RecommendedProductState = - ReviewQualityCheckState.RecommendedProductState.Initial, + recommendedProductState: RecommendedProductState = RecommendedProductState.Initial, productVendor: ReviewQualityCheckState.ProductVendor = ReviewQualityCheckState.ProductVendor.AMAZON, ) : this( productRecommendationsEnabled = productRecommendationsEnabled, @@ -483,6 +561,20 @@ private class ProductAnalysisPreviewModelParameterProvider : ), ), ), + ProductAnalysisPreviewModel( + productRecommendationsEnabled = true, + recommendedProductState = RecommendedProductState.Product( + name = "The best desk ever with a really really really long product name that " + + "forces the preview to wrap its text to at least 4 lines.", + productUrl = "www.mozilla.com", + imageUrl = "https://i.fakespot.io/b6vx27xf3rgwr1a597q6qd3rutp6", + formattedPrice = "$123.45", + reviewGrade = ReviewQualityCheckState.Grade.B, + adjustedRating = 4.23f, + isSponsored = true, + analysisUrl = "", + ), + ), ) } @@ -510,6 +602,7 @@ private fun ProductAnalysisPreview( onFooterLinkClick = {}, onShowMoreRecentReviewsClicked = {}, onExpandSettings = {}, + onRecommendedProductClick = {}, ) } } diff --git a/app/src/main/java/org/mozilla/fenix/shopping/ui/ReviewQualityCheckBottomSheet.kt b/app/src/main/java/org/mozilla/fenix/shopping/ui/ReviewQualityCheckBottomSheet.kt index 117fcd8ab..5f005fb03 100644 --- a/app/src/main/java/org/mozilla/fenix/shopping/ui/ReviewQualityCheckBottomSheet.kt +++ b/app/src/main/java/org/mozilla/fenix/shopping/ui/ReviewQualityCheckBottomSheet.kt @@ -102,6 +102,10 @@ fun ReviewQualityCheckBottomSheet( onShowMoreRecentReviewsClicked = { store.dispatch(ReviewQualityCheckAction.ShowMoreRecentReviewsClicked) }, + onRecommendedProductClick = { + onRequestDismiss(BottomSheetDismissSource.LINK_OPENED) + store.dispatch(ReviewQualityCheckAction.OpenRecommendedProduct(it)) + }, ) } @@ -129,6 +133,7 @@ private fun ProductReview( onExpandSettings: () -> Unit, onReviewGradeLearnMoreClick: () -> Unit, onFooterLinkClick: () -> Unit, + onRecommendedProductClick: (String) -> Unit, ) { Crossfade( targetState = state.productReviewState, @@ -147,6 +152,7 @@ private fun ProductReview( onExpandSettings = onExpandSettings, onReviewGradeLearnMoreClick = onReviewGradeLearnMoreClick, onFooterLinkClick = onFooterLinkClick, + onRecommendedProductClick = onRecommendedProductClick, ) }