Bug 1840103 - Part 2: Update contextual onboarding card for Review quality check feature

fenix/120.0
Alexandru2909 9 months ago committed by mergify[bot]
parent ab2d1ee15b
commit bc4eb86069

@ -21,8 +21,23 @@ sealed interface ReviewQualityCheckState : State {
/**
* The state when the user has not opted in for the feature.
*
* @property retailers List of retailer names to be displayed in order in the onboarding UI.
*/
object NotOptedIn : ReviewQualityCheckState
data class NotOptedIn(
val retailers: List<ProductVendor> = listOf(
ProductVendor.AMAZON,
ProductVendor.BEST_BUY,
ProductVendor.WALMART,
),
) : ReviewQualityCheckState
/**
* Supported product retailers.
*/
enum class ProductVendor {
AMAZON, BEST_BUY, WALMART,
}
/**
* The state when the user has opted in for the feature.

@ -51,12 +51,12 @@ private fun mapStateForUpdateAction(
)
}
} else {
ReviewQualityCheckState.NotOptedIn
ReviewQualityCheckState.NotOptedIn()
}
}
ReviewQualityCheckAction.OptOut -> {
ReviewQualityCheckState.NotOptedIn
ReviewQualityCheckState.NotOptedIn()
}
ReviewQualityCheckAction.ToggleProductRecommendation -> {

@ -25,6 +25,7 @@ import org.mozilla.fenix.shopping.store.ReviewQualityCheckStore
* @param onRequestDismiss Invoked when a user action requests dismissal of the bottom sheet.
* @param modifier The modifier to be applied to the Composable.
*/
@Suppress("LongMethod")
@Composable
fun ReviewQualityCheckBottomSheet(
store: ReviewQualityCheckStore,
@ -42,9 +43,31 @@ fun ReviewQualityCheckBottomSheet(
when (val state = reviewQualityCheckState) {
is ReviewQualityCheckState.NotOptedIn -> {
ReviewQualityCheckContextualOnboarding(
retailers = state.retailers,
onPrimaryButtonClick = {
store.dispatch(ReviewQualityCheckAction.OptIn)
},
onLearnMoreClick = { url ->
store.dispatch(
ReviewQualityCheckAction.OpenLink(
ReviewQualityCheckState.LinkType.ExternalLink(url),
),
)
},
onPrivacyPolicyClick = { url ->
store.dispatch(
ReviewQualityCheckAction.OpenLink(
ReviewQualityCheckState.LinkType.ExternalLink(url),
),
)
},
onTermsOfUseClick = { url ->
store.dispatch(
ReviewQualityCheckAction.OpenLink(
ReviewQualityCheckState.LinkType.ExternalLink(url),
),
)
},
onSecondaryButtonClick = onRequestDismiss,
)
}

@ -4,71 +4,136 @@
package org.mozilla.fenix.shopping.ui
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.Image
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.wrapContentHeight
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import org.mozilla.fenix.R
import org.mozilla.fenix.compose.LinkText
import org.mozilla.fenix.compose.LinkTextState
import org.mozilla.fenix.compose.button.PrimaryButton
import org.mozilla.fenix.compose.button.TextButton
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.ProductVendor
import org.mozilla.fenix.theme.FirefoxTheme
const val PLACEHOLDER_URL = "www.fakespot.com"
/**
* A placeholder UI for review quality check contextual onboarding. The actual UI will be
* implemented as part of Bug 1840103 with the illustration.
*
* @param retailers List of retailers to be displayed in order.
* @param onLearnMoreClick Invoked when a user clicks on the learn more link.
* @param onPrivacyPolicyClick Invoked when a user clicks on the privacy policy link.
* @param onTermsOfUseClick Invoked when a user clicks on the terms of use link.
* @param onPrimaryButtonClick Invoked when a user clicks on the primary button.
* @param onSecondaryButtonClick Invoked when a user clicks on the secondary button.
*/
@Suppress("LongParameterList", "LongMethod")
@Composable
fun ColumnScope.ReviewQualityCheckContextualOnboarding(
fun ReviewQualityCheckContextualOnboarding(
retailers: List<ProductVendor>,
onLearnMoreClick: (String) -> Unit,
onPrivacyPolicyClick: (String) -> Unit,
onTermsOfUseClick: (String) -> Unit,
onPrimaryButtonClick: () -> Unit,
onSecondaryButtonClick: () -> Unit,
) {
val learnMoreText =
stringResource(id = R.string.review_quality_check_contextual_onboarding_learn_more_link)
val privacyPolicyText =
stringResource(id = R.string.review_quality_check_contextual_onboarding_privacy_policy)
val termsOfUseText =
stringResource(id = R.string.review_quality_check_contextual_onboarding_terms_use)
ReviewQualityCheckCard(modifier = Modifier.fillMaxWidth()) {
Text(
text = stringResource(R.string.review_quality_check_contextual_onboarding_title),
color = FirefoxTheme.colors.textPrimary,
style = FirefoxTheme.typography.headline5,
textAlign = TextAlign.Center,
modifier = Modifier.align(Alignment.CenterHorizontally),
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = createDescriptionString(),
color = FirefoxTheme.colors.textPrimary,
text = createDescriptionString(retailers),
color = FirefoxTheme.colors.textSecondary,
style = FirefoxTheme.typography.body2,
textAlign = TextAlign.Center,
modifier = Modifier.align(Alignment.CenterHorizontally),
)
Spacer(modifier = Modifier.height(12.dp))
Text(
LinkText(
text = stringResource(
id = R.string.review_quality_check_contextual_onboarding_learn_more,
stringResource(id = R.string.shopping_product_name),
stringResource(id = R.string.review_quality_check_contextual_onboarding_learn_more_link),
learnMoreText,
),
color = FirefoxTheme.colors.textPrimary,
style = FirefoxTheme.typography.body2,
textAlign = TextAlign.Center,
modifier = Modifier.align(Alignment.CenterHorizontally),
linkTextStates = listOf(
LinkTextState(
text = learnMoreText,
url = PLACEHOLDER_URL,
onClick = onLearnMoreClick,
),
),
style = FirefoxTheme.typography.body2.copy(
color = FirefoxTheme.colors.textSecondary,
),
linkTextDecoration = TextDecoration.Underline,
)
Spacer(modifier = Modifier.height(16.dp))
Spacer(modifier = Modifier.height(12.dp))
LinkText(
text = stringResource(
id = R.string.review_quality_check_contextual_onboarding_caption,
stringResource(id = R.string.shopping_product_name),
privacyPolicyText,
termsOfUseText,
),
linkTextStates = listOf(
LinkTextState(
text = privacyPolicyText,
url = PLACEHOLDER_URL,
onClick = onPrivacyPolicyClick,
),
LinkTextState(
text = termsOfUseText,
url = PLACEHOLDER_URL,
onClick = onTermsOfUseClick,
),
),
style = FirefoxTheme.typography.caption
.copy(
color = FirefoxTheme.colors.textSecondary,
),
linkTextDecoration = TextDecoration.Underline,
)
Spacer(modifier = Modifier.height(12.dp))
Image(
painter = painterResource(id = R.drawable.shopping_onboarding),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(all = 16.dp),
)
Spacer(modifier = Modifier.height(12.dp))
PrimaryButton(
text = stringResource(R.string.review_quality_check_contextual_onboarding_primary_button_text),
@ -78,46 +143,41 @@ fun ColumnScope.ReviewQualityCheckContextualOnboarding(
Spacer(modifier = Modifier.height(8.dp))
TextButton(
text = stringResource(R.string.review_quality_check_contextual_onboarding_secondary_button_text),
onClick = onSecondaryButtonClick,
modifier = Modifier.fillMaxWidth(),
)
) {
Text(
text = stringResource(R.string.review_quality_check_contextual_onboarding_secondary_button_text),
color = FirefoxTheme.colors.textAccent,
style = FirefoxTheme.typography.button,
maxLines = 1,
)
}
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(
R.string.review_quality_check_contextual_onboarding_caption,
stringResource(R.string.shopping_product_name),
stringResource(id = R.string.review_quality_check_contextual_onboarding_privacy_policy),
stringResource(id = R.string.review_quality_check_contextual_onboarding_terms_use),
),
color = FirefoxTheme.colors.textPrimary,
style = FirefoxTheme.typography.caption,
textAlign = TextAlign.Center,
modifier = Modifier.align(Alignment.CenterHorizontally),
)
}
@Composable
private fun createDescriptionString(
retailers: List<Int> = listOf(
R.string.review_quality_check_retailer_name_amazon,
R.string.review_quality_check_retailer_name_bestbuy,
R.string.review_quality_check_retailer_name_walmart,
),
retailers: List<ProductVendor>,
) = buildAnnotatedString {
val retailerNames = retailers.map {
when (it) {
ProductVendor.AMAZON -> R.string.review_quality_check_retailer_name_amazon
ProductVendor.BEST_BUY -> R.string.review_quality_check_retailer_name_bestbuy
ProductVendor.WALMART -> R.string.review_quality_check_retailer_name_walmart
}
}
val description = stringResource(
id = R.string.review_quality_check_contextual_onboarding_description,
stringResource(retailers[0]),
stringResource(retailerNames[0]),
stringResource(R.string.app_name),
stringResource(retailers[1]),
stringResource(retailers[2]),
stringResource(retailerNames[1]),
stringResource(retailerNames[2]),
)
append(description)
retailers.forEach {
retailerNames.forEach {
val retailer = stringResource(id = it)
val start = description.indexOf(retailer)

@ -110,7 +110,7 @@ fun ReviewQualityCheckInfoCard(
LinkText(
text = it.first,
linkTextState = listOf(it.second),
linkTextStates = listOf(it.second),
style = FirefoxTheme.typography.body2.copy(
color = FirefoxTheme.colors.textPrimary,
),

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

@ -45,7 +45,7 @@ class ReviewQualityCheckStoreTest {
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
val expected = ReviewQualityCheckState.NotOptedIn
val expected = ReviewQualityCheckState.NotOptedIn()
assertEquals(expected, tested.state)
}
@ -90,7 +90,7 @@ class ReviewQualityCheckStoreTest {
tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
val expected = ReviewQualityCheckState.NotOptedIn
val expected = ReviewQualityCheckState.NotOptedIn()
assertEquals(expected, tested.state)
}

Loading…
Cancel
Save