Bug 1843029 - Add preferences for shopping experience

fenix/117.0
rahulsainani 11 months ago committed by mergify[bot]
parent 21068e8bb3
commit e1a84285da

@ -9,4 +9,48 @@ import mozilla.components.lib.state.Action
/**
* Actions for review quality check feature.
*/
sealed interface ReviewQualityCheckAction : Action
sealed interface ReviewQualityCheckAction : Action {
/**
* Actions that are observed by middlewares.
*/
sealed interface MiddlewareAction : ReviewQualityCheckAction
/**
* Actions that cause updates to state.
*/
sealed interface UpdateAction : ReviewQualityCheckAction
/**
* Actions related to preferences.
*/
sealed interface PreferencesMiddlewareAction : MiddlewareAction
/**
* Triggered when the store is initialized.
*/
object Init : PreferencesMiddlewareAction
/**
* Triggered when the user has opted in to the review quality check feature.
*/
object OptIn : PreferencesMiddlewareAction
/**
* Triggered when the user has opted out of the review quality check feature.
*/
object OptOut : PreferencesMiddlewareAction, UpdateAction
/**
* Triggered when the user has enabled or disabled product recommendations.
*/
object ToggleProductRecommendation : PreferencesMiddlewareAction, UpdateAction
/**
* Triggered as a result of a [PreferencesMiddlewareAction] to update the state.
*/
data class UpdateUserPreferences(
val hasUserOptedIn: Boolean,
val isProductRecommendationsEnabled: Boolean,
) : UpdateAction
}

@ -0,0 +1,61 @@
/* 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.state
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.mozilla.fenix.utils.Settings
/**
* Interface to get and set preferences for the review quality check feature.
*/
interface ReviewQualityCheckPreferences {
/**
* Returns true if the user has opted in to the review quality check feature.
*/
suspend fun enabled(): Boolean
/**
* Returns true if the user has enabled product recommendations.
*/
suspend fun productRecommendationsEnabled(): Boolean
/**
* Sets whether the user has opted in to the review quality check feature.
*/
suspend fun setEnabled(isEnabled: Boolean)
/**
* Sets whether the user has enabled product recommendations.
*/
suspend fun setProductRecommendationsEnabled(isEnabled: Boolean)
}
/**
* Implementation of [ReviewQualityCheckPreferences] that uses [Settings] to store/fetch
* preferences.
*
* @param settings The [Settings] instance to use.
*/
class ReviewQualityCheckPreferencesImpl(
private val settings: Settings,
) : ReviewQualityCheckPreferences {
override suspend fun enabled(): Boolean = withContext(Dispatchers.IO) {
settings.isReviewQualityCheckEnabled
}
override suspend fun productRecommendationsEnabled(): Boolean = withContext(Dispatchers.IO) {
settings.isReviewQualityCheckProductRecommendationsEnabled
}
override suspend fun setEnabled(isEnabled: Boolean) {
settings.isReviewQualityCheckEnabled = isEnabled
}
override suspend fun setProductRecommendationsEnabled(isEnabled: Boolean) {
settings.isReviewQualityCheckProductRecommendationsEnabled = isEnabled
}
}

@ -0,0 +1,93 @@
/* 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.state
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.MiddlewareContext
import mozilla.components.lib.state.Store
/**
* Middleware for getting and setting review quality check user preferences.
*
* @param reviewQualityCheckPreferences The [ReviewQualityCheckPreferences] instance to use.
* @param scope The [CoroutineScope] to use for launching coroutines.
*/
class ReviewQualityCheckPreferencesMiddleware(
private val reviewQualityCheckPreferences: ReviewQualityCheckPreferences,
private val scope: CoroutineScope,
) : Middleware<ReviewQualityCheckState, ReviewQualityCheckAction> {
override fun invoke(
context: MiddlewareContext<ReviewQualityCheckState, ReviewQualityCheckAction>,
next: (ReviewQualityCheckAction) -> Unit,
action: ReviewQualityCheckAction,
) {
when (action) {
is ReviewQualityCheckAction.PreferencesMiddlewareAction -> {
processAction(context.store, action)
}
else -> {
// no-op
}
}
// Forward the actions
next(action)
}
private fun processAction(
store: Store<ReviewQualityCheckState, ReviewQualityCheckAction>,
action: ReviewQualityCheckAction.PreferencesMiddlewareAction,
) {
when (action) {
is ReviewQualityCheckAction.Init -> {
scope.launch {
val hasUserOptedIn = reviewQualityCheckPreferences.enabled()
val isProductRecommendationsEnabled =
reviewQualityCheckPreferences.productRecommendationsEnabled()
store.dispatch(
ReviewQualityCheckAction.UpdateUserPreferences(
hasUserOptedIn = hasUserOptedIn,
isProductRecommendationsEnabled = isProductRecommendationsEnabled,
),
)
}
}
ReviewQualityCheckAction.OptIn -> {
scope.launch {
val isProductRecommendationsEnabled =
reviewQualityCheckPreferences.productRecommendationsEnabled()
store.dispatch(
ReviewQualityCheckAction.UpdateUserPreferences(
hasUserOptedIn = true,
isProductRecommendationsEnabled = isProductRecommendationsEnabled,
),
)
// Update the preference
reviewQualityCheckPreferences.setEnabled(true)
}
}
ReviewQualityCheckAction.OptOut -> {
scope.launch {
// Update the preference
reviewQualityCheckPreferences.setEnabled(false)
}
}
ReviewQualityCheckAction.ToggleProductRecommendation -> {
scope.launch {
reviewQualityCheckPreferences.setProductRecommendationsEnabled(
!reviewQualityCheckPreferences.productRecommendationsEnabled(),
)
}
}
}
}
}

@ -4,12 +4,70 @@
package org.mozilla.fenix.shopping.state
import kotlinx.coroutines.CoroutineScope
import mozilla.components.lib.state.Store
/**
* Store for review quality check feature.
*
* @param reviewQualityCheckPreferences The [ReviewQualityCheckPreferences] instance to use.
* @param scope The [CoroutineScope] to use for launching coroutines.
*/
class ReviewQualityCheckStore : Store<ReviewQualityCheckState, ReviewQualityCheckAction>(
class ReviewQualityCheckStore(
reviewQualityCheckPreferences: ReviewQualityCheckPreferences,
scope: CoroutineScope,
) : Store<ReviewQualityCheckState, ReviewQualityCheckAction>(
initialState = ReviewQualityCheckState.Initial,
reducer = { _, _ -> ReviewQualityCheckState.Initial },
)
middleware = listOf(
ReviewQualityCheckPreferencesMiddleware(reviewQualityCheckPreferences, scope),
),
reducer = ::reducer,
) {
init {
dispatch(ReviewQualityCheckAction.Init)
}
}
private fun reducer(
state: ReviewQualityCheckState,
action: ReviewQualityCheckAction,
): ReviewQualityCheckState {
if (action is ReviewQualityCheckAction.UpdateAction) {
return mapStateForUpdateAction(state, action)
}
return state
}
private fun mapStateForUpdateAction(
state: ReviewQualityCheckState,
action: ReviewQualityCheckAction.UpdateAction,
): ReviewQualityCheckState {
when (action) {
is ReviewQualityCheckAction.UpdateUserPreferences -> {
return if (action.hasUserOptedIn) {
if (state is ReviewQualityCheckState.OptedIn) {
state.copy(productRecommendationsPreference = action.isProductRecommendationsEnabled)
} else {
ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = action.isProductRecommendationsEnabled,
)
}
} else {
ReviewQualityCheckState.NotOptedIn
}
}
ReviewQualityCheckAction.OptOut -> {
return ReviewQualityCheckState.NotOptedIn
}
ReviewQualityCheckAction.ToggleProductRecommendation -> {
return if (state is ReviewQualityCheckState.OptedIn) {
state.copy(productRecommendationsPreference = !state.productRecommendationsPreference)
} else {
state
}
}
}
}

@ -1676,6 +1676,22 @@ class Settings(private val appContext: Context) : PreferencesHolder {
}
}
/**
* Indicates if the review quality check feature is enabled by the user.
*/
var isReviewQualityCheckEnabled by booleanPreference(
key = appContext.getPreferenceKey(R.string.pref_key_is_review_quality_check_enabled),
default = false,
)
/**
* Indicates if the review quality check product recommendations option is enabled by the user.
*/
var isReviewQualityCheckProductRecommendationsEnabled by booleanPreference(
key = appContext.getPreferenceKey(R.string.pref_key_is_review_quality_check_product_recommendations_enabled),
default = false,
)
/**
* Get the current mode for how https-only is enabled.
*/

@ -364,4 +364,8 @@
<!-- Notification Pre Permission Prompt -->
<string name="pref_key_notification_pre_permission_prompt_enabled">pref_key_notification_pre_permission_prompt_enabled</string>
<string name="pref_key_is_notification_pre_permission_prompt_shown">pref_key_is_notification_pre_permission_prompt_shown</string>
<!--Shopping -->
<string name="pref_key_is_review_quality_check_enabled">pref_key_is_review_quality_check_enabled</string>
<string name="pref_key_is_review_quality_check_product_recommendations_enabled">pref_key_is_review_quality_check_product_recommendations_enabled</string>
</resources>

@ -0,0 +1,132 @@
/* 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.state
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.test.runTest
import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.libstate.ext.waitUntilIdle
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.Rule
import org.junit.Test
class ReviewQualityCheckStoreTest {
@get:Rule
val coroutinesTestRule = MainCoroutineRule()
private val dispatcher = coroutinesTestRule.testDispatcher
private val scope = coroutinesTestRule.scope
@Test
fun `GIVEN the user has not opted in the feature WHEN store is created THEN state should display not opted in UI`() =
runTest {
val tested = ReviewQualityCheckStore(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(
isEnabled = false,
),
scope = scope,
)
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
val expected = ReviewQualityCheckState.NotOptedIn
assertEquals(expected, tested.state)
}
@Test
fun `GIVEN the user has not opted in the feature WHEN the user opts in THEN state should display opted in UI`() =
runTest {
val tested = ReviewQualityCheckStore(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(
isEnabled = false,
isProductRecommendationsEnabled = false,
),
scope = scope,
)
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.OptIn).joinBlocking()
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
val expected = ReviewQualityCheckState.OptedIn(productRecommendationsPreference = false)
assertEquals(expected, tested.state)
}
@Test
fun `GIVEN the user has opted in the feature WHEN the user opts out THEN state should display not opted in UI`() =
runTest {
val tested = ReviewQualityCheckStore(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(
isEnabled = true,
isProductRecommendationsEnabled = true,
),
scope = scope,
)
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.OptOut).joinBlocking()
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
val expected = ReviewQualityCheckState.NotOptedIn
assertEquals(expected, tested.state)
}
@Test
fun `GIVEN the user has opted in the feature and product recommendations are off WHEN the user turns on product recommendations THEN state should reflect that`() =
runTest {
val tested = ReviewQualityCheckStore(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(
isEnabled = true,
isProductRecommendationsEnabled = false,
),
scope = scope,
)
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.ToggleProductRecommendation).joinBlocking()
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
val expected = ReviewQualityCheckState.OptedIn(productRecommendationsPreference = true)
assertEquals(expected, tested.state)
}
@Test
fun `GIVEN the user has opted in the feature and product recommendations are on WHEN the user turns off product recommendations THEN state should reflect that`() =
runTest {
val tested = ReviewQualityCheckStore(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(
isEnabled = true,
isProductRecommendationsEnabled = true,
),
scope = scope,
)
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.ToggleProductRecommendation).joinBlocking()
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
val expected = ReviewQualityCheckState.OptedIn(productRecommendationsPreference = false)
assertEquals(expected, tested.state)
}
}
private class FakeReviewQualityCheckPreferences(
private val isEnabled: Boolean = false,
private val isProductRecommendationsEnabled: Boolean = false,
) : ReviewQualityCheckPreferences {
override suspend fun enabled(): Boolean = isEnabled
override suspend fun productRecommendationsEnabled(): Boolean = isProductRecommendationsEnabled
override suspend fun setEnabled(isEnabled: Boolean) {
}
override suspend fun setProductRecommendationsEnabled(isEnabled: Boolean) {
}
}
Loading…
Cancel
Save