Bug 1847740 - Add navigation middleware for review quality check

fenix/119.0
Alexandru2909 10 months ago committed by mergify[bot]
parent e4da26fb34
commit 790be45d7b

@ -41,4 +41,5 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) {
FromLoginDetailFragment(R.id.loginDetailFragment),
FromTabsTray(R.id.tabsTrayFragment),
FromRecentlyClosed(R.id.recentlyClosedFragment),
FromReviewQualityCheck(R.id.reviewQualityCheckFragment),
}

@ -152,6 +152,7 @@ import org.mozilla.fenix.settings.search.SaveSearchEngineFragmentDirections
import org.mozilla.fenix.settings.studies.StudiesFragmentDirections
import org.mozilla.fenix.settings.wallpaper.WallpaperSettingsFragmentDirections
import org.mozilla.fenix.share.AddNewDeviceFragmentDirections
import org.mozilla.fenix.shopping.ReviewQualityCheckFragmentDirections
import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor.Companion.ACTION_OPEN_PRIVATE_TAB
import org.mozilla.fenix.tabhistory.TabHistoryDialogFragment
import org.mozilla.fenix.tabstray.TabsTrayFragment
@ -1024,6 +1025,9 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
BrowserDirection.FromStudiesFragment -> StudiesFragmentDirections.actionGlobalBrowser(
customTabSessionId,
)
BrowserDirection.FromReviewQualityCheck -> ReviewQualityCheckFragmentDirections.actionGlobalBrowser(
customTabSessionId,
)
}
/**

@ -16,6 +16,8 @@ import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.lifecycle.lifecycleScope
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.shopping.di.ReviewQualityCheckMiddlewareProvider
import org.mozilla.fenix.shopping.store.ReviewQualityCheckStore
@ -33,6 +35,13 @@ class ReviewQualityCheckFragment : BottomSheetDialogFragment() {
middleware = ReviewQualityCheckMiddlewareProvider.provideMiddleware(
settings = requireComponents.settings,
browserStore = requireComponents.core.store,
openLink = { link, shouldOpenInNewTab ->
(requireActivity() as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = link,
newTab = shouldOpenInNewTab,
from = BrowserDirection.FromReviewQualityCheck,
)
},
scope = lifecycleScope,
),
)

@ -6,6 +6,7 @@ package org.mozilla.fenix.shopping.di
import kotlinx.coroutines.CoroutineScope
import mozilla.components.browser.state.store.BrowserStore
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckNavigationMiddleware
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckNetworkMiddleware
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckPreferencesImpl
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckPreferencesMiddleware
@ -23,16 +24,20 @@ object ReviewQualityCheckMiddlewareProvider {
*
* @param settings The [Settings] instance to use.
* @param browserStore The [BrowserStore] instance to access state.
* @param openLink Opens a link. The callback is invoked with the URL [String] parameter and
* whether or not it should open in a new or the currently selected tab [Boolean] parameter.
* @param scope The [CoroutineScope] to use for launching coroutines.
*/
fun provideMiddleware(
settings: Settings,
browserStore: BrowserStore,
openLink: (String, Boolean) -> Unit,
scope: CoroutineScope,
): List<ReviewQualityCheckMiddleware> =
listOf(
providePreferencesMiddleware(settings, scope),
provideNetworkMiddleware(browserStore, scope),
provideNavigationMiddleware(openLink, scope),
)
private fun providePreferencesMiddleware(
@ -50,4 +55,12 @@ object ReviewQualityCheckMiddlewareProvider {
reviewQualityCheckService = ReviewQualityCheckServiceImpl(browserStore),
scope = scope,
)
private fun provideNavigationMiddleware(
openLink: (String, Boolean) -> Unit,
scope: CoroutineScope,
) = ReviewQualityCheckNavigationMiddleware(
openLink = openLink,
scope = scope,
)
}

@ -0,0 +1,51 @@
/* 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.middleware
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.MiddlewareContext
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState
/**
* Middleware that handles navigation events for the review quality check feature.
*
* @property openLink Callback used to open an url.
* @property scope [CoroutineScope] used to launch coroutines.
*/
class ReviewQualityCheckNavigationMiddleware(
private val openLink: (String, Boolean) -> Unit,
private val scope: CoroutineScope,
) : Middleware<ReviewQualityCheckState, ReviewQualityCheckAction> {
override fun invoke(
context: MiddlewareContext<ReviewQualityCheckState, ReviewQualityCheckAction>,
next: (ReviewQualityCheckAction) -> Unit,
action: ReviewQualityCheckAction,
) {
next(action)
when (action) {
is ReviewQualityCheckAction.NavigationMiddlewareAction -> processAction(action)
else -> {
// no-op
}
}
}
private fun processAction(
action: ReviewQualityCheckAction.NavigationMiddlewareAction,
) = scope.launch {
when (action) {
is ReviewQualityCheckAction.OpenLink -> {
when (action.link) {
is ReviewQualityCheckState.LinkType.ExternalLink -> openLink(action.link.url, true)
is ReviewQualityCheckState.LinkType.AnalyzeLink -> openLink(action.link.url, false)
}
}
}
}
}

@ -22,6 +22,11 @@ sealed interface ReviewQualityCheckAction : Action {
*/
sealed interface PreferencesMiddlewareAction : ReviewQualityCheckAction
/**
* Actions related to navigation events.
*/
sealed interface NavigationMiddlewareAction : ReviewQualityCheckAction
/**
* Actions related to network requests.
*/
@ -69,4 +74,9 @@ sealed interface ReviewQualityCheckAction : Action {
* Triggered when the user retries to fetch product analysis after a failure.
*/
object RetryProductAnalysis : NetworkAction
/**
* Triggered when opening a link from the review quality check feature.
*/
data class OpenLink(val link: ReviewQualityCheckState.LinkType) : NavigationMiddlewareAction
}

@ -100,6 +100,21 @@ sealed interface ReviewQualityCheckState : State {
QUALITY, PRICE, SHIPPING, PACKAGING_AND_APPEARANCE, COMPETITIVENESS
}
/**
* Types of links that can be opened from the review quality check feature.
*/
sealed class LinkType {
/**
* Opens a link to analyze a product.
*/
data class AnalyzeLink(val url: String) : LinkType()
/**
* Opens an external "Learn more" link.
*/
data class ExternalLink(val url: String) : LinkType()
}
/**
* The state of the recommended product.
*/

@ -29,6 +29,7 @@ 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.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
@ -38,6 +39,7 @@ import org.mozilla.fenix.compose.ClickableSubstringLink
import org.mozilla.fenix.compose.SwitchWithLabel
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.compose.button.SecondaryButton
import org.mozilla.fenix.settings.SupportUtils
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
@ -61,7 +63,7 @@ fun ProductAnalysis(
productAnalysis: AnalysisPresent,
onOptOutClick: () -> Unit,
onProductRecommendationsEnabledStateChange: (Boolean) -> Unit,
onReviewGradeLearnMoreClick: () -> Unit,
onReviewGradeLearnMoreClick: (String) -> Unit,
modifier: Modifier = Modifier,
) {
Column(
@ -324,7 +326,7 @@ private fun SettingsCard(
@Composable
private fun ReviewQualityInfo(
modifier: Modifier = Modifier,
onLearnMoreClick: () -> Unit,
onLearnMoreClick: (String) -> Unit,
) {
Column(
modifier = modifier,
@ -342,13 +344,22 @@ private fun ReviewQualityInfo(
val link = stringResource(R.string.review_quality_check_info_learn_more_link)
val text = stringResource(R.string.review_quality_check_info_learn_more, link)
val linkStartIndex = text.indexOf(link)
val context = LocalContext.current
val linkEndIndex = linkStartIndex + link.length
ClickableSubstringLink(
text = text,
textStyle = FirefoxTheme.typography.body2,
clickableStartIndex = linkStartIndex,
clickableEndIndex = linkEndIndex,
onClick = onLearnMoreClick,
onClick = {
onLearnMoreClick(
// Placeholder Sumo page
SupportUtils.getSumoURLForTopic(
context,
SupportUtils.SumoTopic.HELP,
),
)
},
)
Text(

@ -58,8 +58,12 @@ fun ReviewQualityCheckBottomSheet(
onProductRecommendationsEnabledStateChange = {
store.dispatch(ReviewQualityCheckAction.ToggleProductRecommendation)
},
onReviewGradeLearnMoreClick = {
// Bug 1847740
onReviewGradeLearnMoreClick = { url ->
store.dispatch(
ReviewQualityCheckAction.OpenLink(
ReviewQualityCheckState.LinkType.ExternalLink(url),
),
)
},
)
}
@ -80,7 +84,7 @@ private fun ProductReview(
state: ReviewQualityCheckState.OptedIn,
onOptOutClick: () -> Unit,
onProductRecommendationsEnabledStateChange: (Boolean) -> Unit,
onReviewGradeLearnMoreClick: () -> Unit,
onReviewGradeLearnMoreClick: (String) -> Unit,
) {
Crossfade(
targetState = state.productReviewState,

@ -0,0 +1,75 @@
package org.mozilla.fenix.shopping.middleware
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.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState
import org.mozilla.fenix.shopping.store.ReviewQualityCheckStore
class ReviewQualityCheckNavigationMiddlewareTest {
@get:Rule
val coroutinesTestRule = MainCoroutineRule()
private val dispatcher = coroutinesTestRule.testDispatcher
private val scope = coroutinesTestRule.scope
@Test
fun `WHEN opening an external link THEN the link should be opened in a new tab`() {
var isOpenedInSelectedTab = false
var isOpenedInNewTab = false
val store = ReviewQualityCheckStore(
middleware = listOf(
ReviewQualityCheckNavigationMiddleware(
openLink = { _, openInNewTab ->
if (openInNewTab) {
isOpenedInNewTab = true
} else {
isOpenedInSelectedTab = true
}
},
scope = scope,
),
),
)
store.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
store.dispatch(ReviewQualityCheckAction.OpenLink(ReviewQualityCheckState.LinkType.ExternalLink("www.mozilla.com"))).joinBlocking()
store.waitUntilIdle()
assertEquals(true, isOpenedInNewTab)
assertEquals(false, isOpenedInSelectedTab)
}
@Test
fun `WHEN re-analzying a product THEN the link should be opened in the currently selected tab`() {
var isOpenedInSelectedTab = false
var isOpenedInNewTab = false
val store = ReviewQualityCheckStore(
middleware = listOf(
ReviewQualityCheckNavigationMiddleware(
openLink = { _, openInNewTab ->
if (openInNewTab) {
isOpenedInNewTab = true
} else {
isOpenedInSelectedTab = true
}
},
scope = scope,
),
),
)
store.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
store.dispatch(ReviewQualityCheckAction.OpenLink(ReviewQualityCheckState.LinkType.AnalyzeLink("www.mozilla.com"))).joinBlocking()
store.waitUntilIdle()
assertEquals(true, isOpenedInSelectedTab)
assertEquals(false, isOpenedInNewTab)
}
}
Loading…
Cancel
Save