From 04d03862d2fc0f58063f9bf85859263f8767b2bf Mon Sep 17 00:00:00 2001 From: iorgamgabriel Date: Wed, 21 Feb 2024 13:02:39 +0200 Subject: [PATCH] Bug 1856986 - Translations UI Integration Preference Logic - Page Settings --- .../translations/TranslationOptionsDialog.kt | 102 ++++-------- .../fenix/translations/TranslationSettings.kt | 21 ++- .../translations/TranslationSwitchItem.kt | 84 +++++++++- .../translations/TranslationsBottomSheet.kt | 94 ++++++++++- .../TranslationsDialogFragment.kt | 84 +++++++--- .../TranslationsDialogMiddleware.kt | 47 ++++++ .../translations/TranslationsDialogStore.kt | 50 ++++-- .../TranslationsDialogMiddlewareTest.kt | 153 +++++++++++++++++- 8 files changed, 508 insertions(+), 127 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/translations/TranslationOptionsDialog.kt b/app/src/main/java/org/mozilla/fenix/translations/TranslationOptionsDialog.kt index 584f42004..bf9291ba5 100644 --- a/app/src/main/java/org/mozilla/fenix/translations/TranslationOptionsDialog.kt +++ b/app/src/main/java/org/mozilla/fenix/translations/TranslationOptionsDialog.kt @@ -17,9 +17,6 @@ import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshots.SnapshotStateList -import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -52,37 +49,15 @@ fun TranslationOptionsDialog( ) { TranslationOptionsDialogHeader(onBackClicked) - val translationOptionsListState = remember { - translationOptionsList.toMutableStateList() - } - - EnabledTranslationOptionsItems( - translationOptionsListState, - ) - LazyColumn { - items(translationOptionsListState) { item: TranslationSwitchItem -> - + items(translationOptionsList) { item: TranslationSwitchItem -> val translationSwitchItem = TranslationSwitchItem( + type = item.type, textLabel = item.textLabel, - description = if (item.isChecked) item.description else null, - importance = item.importance, isChecked = item.isChecked, isEnabled = item.isEnabled, - hasDivider = item.hasDivider, - onStateChange = { checked -> - // If the item has the same importance, only one switch should be enabled. - val iterator = translationOptionsListState.iterator() - iterator.forEach { - if (it != item && it.importance == item.importance && it.isChecked) { - it.isChecked = false - } - } - - val index = translationOptionsListState.indexOf(item) - translationOptionsListState[index] = translationOptionsListState[index].copy( - isChecked = checked, - ) + onStateChange = { translationPageSettingsOption, checked -> + item.onStateChange.invoke(translationPageSettingsOption, checked) }, ) TranslationOptions( @@ -115,46 +90,33 @@ fun TranslationOptionsDialog( } } -/** - * If the item with the highest importance is checked, all other items should be disabled. - * If all items are unchecked, all of them are enabled. - * If the item with the highest importance is unchecked and a item with importance 1 is checked , - * the item with importance 0 is disabled. - * If the item with importance 0 is checked all the items are enabled. - */ -@Composable -private fun EnabledTranslationOptionsItems( - translationOptionsListState: SnapshotStateList, -) { - val itemCheckedWithHighestImportance = - translationOptionsListState.sortedByDescending { listItem -> listItem.importance } - .firstOrNull { it.isChecked } - - if (itemCheckedWithHighestImportance == null || itemCheckedWithHighestImportance.importance == 0) { - translationOptionsListState.forEach { - it.isEnabled = true - } - } else { - translationOptionsListState.forEach { - it.isEnabled = it.importance >= itemCheckedWithHighestImportance.importance - } - } -} - @Composable private fun TranslationOptions( translationSwitchItem: TranslationSwitchItem, ) { SwitchWithLabel( label = translationSwitchItem.textLabel, - description = translationSwitchItem.description, + description = if (translationSwitchItem.isChecked) { + translationSwitchItem.type.descriptionId?.let { + stringResource( + id = it, + ) + } + } else { + null + }, enabled = translationSwitchItem.isEnabled, checked = translationSwitchItem.isChecked, - onCheckedChange = translationSwitchItem.onStateChange, + onCheckedChange = { checked -> + translationSwitchItem.onStateChange.invoke( + translationSwitchItem.type, + checked, + ) + }, modifier = Modifier.padding(start = 72.dp, end = 16.dp), ) - if (translationSwitchItem.hasDivider) { + if (translationSwitchItem.type.hasDivider) { Divider(Modifier.padding(top = 4.dp, bottom = 4.dp)) } } @@ -201,52 +163,44 @@ fun getTranslationOptionsList(): List { return mutableListOf().apply { add( TranslationSwitchItem( + type = TranslationPageSettingsOption.AlwaysOfferPopup(), textLabel = stringResource(R.string.translation_option_bottom_sheet_always_translate), isChecked = false, - hasDivider = true, isEnabled = true, - onStateChange = { }, + onStateChange = { _, _ -> }, ), ) add( TranslationSwitchItem( + type = TranslationPageSettingsOption.AlwaysTranslateLanguage(), textLabel = stringResource( id = R.string.translation_option_bottom_sheet_always_translate_in_language, formatArgs = arrayOf(Locale("es").displayName), ), - description = stringResource(id = R.string.translation_option_bottom_sheet_switch_description), - importance = 1, isChecked = false, isEnabled = true, - hasDivider = false, - onStateChange = {}, + onStateChange = { _, _ -> }, ), ) add( TranslationSwitchItem( + type = TranslationPageSettingsOption.NeverTranslateLanguage(), textLabel = stringResource( id = R.string.translation_option_bottom_sheet_never_translate_in_language, formatArgs = arrayOf(Locale("es").displayName), ), - description = stringResource(id = R.string.translation_option_bottom_sheet_switch_description), - importance = 1, isChecked = true, isEnabled = true, - hasDivider = true, - onStateChange = {}, + onStateChange = { _, _ -> }, ), ) add( TranslationSwitchItem( + type = TranslationPageSettingsOption.NeverTranslateSite(), textLabel = stringResource(R.string.translation_option_bottom_sheet_never_translate_site), - description = stringResource( - id = R.string.translation_option_bottom_sheet_switch_never_translate_site_description, - ), - importance = 2, isChecked = true, isEnabled = true, - hasDivider = true, - onStateChange = {}, + onStateChange = { _, _ -> }, ), ) } diff --git a/app/src/main/java/org/mozilla/fenix/translations/TranslationSettings.kt b/app/src/main/java/org/mozilla/fenix/translations/TranslationSettings.kt index 691532e0e..69d010c58 100644 --- a/app/src/main/java/org/mozilla/fenix/translations/TranslationSettings.kt +++ b/app/src/main/java/org/mozilla/fenix/translations/TranslationSettings.kt @@ -49,13 +49,18 @@ fun TranslationSettings( items(translationSwitchList) { item: TranslationSwitchItem -> SwitchWithLabel( checked = item.isChecked, - onCheckedChange = item.onStateChange, + onCheckedChange = { checked -> + item.onStateChange.invoke( + item.type, + checked, + ) + }, label = item.textLabel, modifier = Modifier .padding(start = 72.dp, end = 16.dp), ) - if (item.hasDivider) { + if (item.type.hasDivider) { Divider(Modifier.padding(top = 8.dp, bottom = 8.dp)) } } @@ -119,20 +124,24 @@ internal fun getTranslationSettingsSwitchList(): List { return mutableListOf().apply { add( TranslationSwitchItem( + type = TranslationSettingsScreenOption.OfferToTranslate( + hasDivider = false, + ), textLabel = stringResource(R.string.translation_settings_offer_to_translate), isChecked = true, - hasDivider = false, isEnabled = true, - onStateChange = {}, + onStateChange = { _, _ -> }, ), ) add( TranslationSwitchItem( + type = TranslationSettingsScreenOption.AlwaysDownloadInSavingMode( + hasDivider = true, + ), textLabel = stringResource(R.string.translation_settings_always_download), isChecked = false, - hasDivider = true, isEnabled = true, - onStateChange = {}, + onStateChange = { _, _ -> }, ), ) } diff --git a/app/src/main/java/org/mozilla/fenix/translations/TranslationSwitchItem.kt b/app/src/main/java/org/mozilla/fenix/translations/TranslationSwitchItem.kt index ec6b60f1c..272067b80 100644 --- a/app/src/main/java/org/mozilla/fenix/translations/TranslationSwitchItem.kt +++ b/app/src/main/java/org/mozilla/fenix/translations/TranslationSwitchItem.kt @@ -4,24 +4,92 @@ package org.mozilla.fenix.translations +import org.mozilla.fenix.R + /** * TranslationSwitchItem that will appear on Translation screens. - * + * @property type [TranslationSettingsOption] type depending on the screen. + * In [TranslationOptionsDialog] is TranslationPageSettingsOption and in [TranslationSettings] is + * [TranslationSettingsScreenOption]. * @property textLabel The text that will appear on the switch item. - * @property description An optional description text below the label. - * @property importance Based on this, the translation switch item is enabled or disabled. * @property isChecked Whether the switch is checked or not. * @property isEnabled Whether the switch is enabled or not. - * @property hasDivider Whether a divider should appear under the switch item. * @property onStateChange Invoked when the switch item is clicked, * the new checked state is passed into the callback. */ data class TranslationSwitchItem( + val type: TranslationSettingsOption, val textLabel: String, - var description: String? = null, - val importance: Int = 0, var isChecked: Boolean, var isEnabled: Boolean = true, - val hasDivider: Boolean, - val onStateChange: (Boolean) -> Unit, + val onStateChange: (TranslationSettingsOption, Boolean) -> Unit, +) + +/** + * Translation settings that is related to the web-page. It will appear in [TranslationsOptionsDialog]. + */ +sealed class TranslationPageSettingsOption( + override val descriptionId: Int? = null, + override val hasDivider: Boolean, +) : TranslationSettingsOption(hasDivider = hasDivider) { + + /** + * The system should offer a translation on a page. + */ + data class AlwaysOfferPopup( + override val hasDivider: Boolean = true, + ) : TranslationPageSettingsOption(hasDivider = hasDivider) + + /** + * The page's always translate language setting. + */ + data class AlwaysTranslateLanguage( + override val hasDivider: Boolean = false, + override val descriptionId: Int = R.string.translation_option_bottom_sheet_switch_description, + ) : TranslationPageSettingsOption(hasDivider = hasDivider) + + /** + * The page's never translate language setting. + */ + data class NeverTranslateLanguage( + override val hasDivider: Boolean = true, + override val descriptionId: Int = R.string.translation_option_bottom_sheet_switch_description, + ) : TranslationPageSettingsOption(hasDivider = hasDivider) + + /** + * The page's never translate site setting. + */ + data class NeverTranslateSite( + override val hasDivider: Boolean = true, + override val descriptionId: + Int = R.string.translation_option_bottom_sheet_switch_never_translate_site_description, + ) : TranslationPageSettingsOption(hasDivider = hasDivider) +} + +/** + * @property hasDivider Organizes translation settings toggle layouts. + * Whether a divider should appear under the switch item. + * @property descriptionId An optional description text id below the label. + * + */ +sealed class TranslationSettingsOption( + open val hasDivider: Boolean, + open val descriptionId: Int? = null, ) + +/** + * Translation settings that is related to the web-page.It will appear in [TranslationSettings]. + */ +sealed class TranslationSettingsScreenOption { + /** + * The page's offer to translate when possible. + */ + data class OfferToTranslate(override val hasDivider: Boolean = true) : + TranslationSettingsOption(hasDivider = hasDivider) + + /** + * The page's always download languages in data saving mode. + */ + data class AlwaysDownloadInSavingMode(override val hasDivider: Boolean = true) : + TranslationSettingsOption(hasDivider = hasDivider) +} diff --git a/app/src/main/java/org/mozilla/fenix/translations/TranslationsBottomSheet.kt b/app/src/main/java/org/mozilla/fenix/translations/TranslationsBottomSheet.kt index 98c341ac3..cc823a635 100644 --- a/app/src/main/java/org/mozilla/fenix/translations/TranslationsBottomSheet.kt +++ b/app/src/main/java/org/mozilla/fenix/translations/TranslationsBottomSheet.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.translations +import android.content.Context import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.core.FastOutSlowInEasing @@ -22,11 +23,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.rememberNestedScrollInteropConnection +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import mozilla.components.concept.engine.translate.Language +import mozilla.components.concept.engine.translate.TranslationPageSettings +import org.mozilla.fenix.R import org.mozilla.fenix.theme.FirefoxTheme private const val BOTTOM_SHEET_HANDLE_WIDTH_PERCENT = 0.1f @@ -154,13 +158,99 @@ internal fun TranslationsDialog( @Composable internal fun TranslationsOptionsDialog( + context: Context, + translationPageSettings: TranslationPageSettings? = null, + initialFrom: Language? = null, + onStateChange: (TranslationSettingsOption, Boolean) -> Unit, onBackClicked: () -> Unit, onTranslationSettingsClicked: () -> Unit, + aboutTranslationClicked: () -> Unit, ) { TranslationOptionsDialog( - translationOptionsList = getTranslationOptionsList(), + translationOptionsList = getTranslationSwitchItemList( + translationPageSettings = translationPageSettings, + initialFrom = initialFrom, + context = context, + onStateChange = onStateChange, + ), onBackClicked = onBackClicked, onTranslationSettingsClicked = onTranslationSettingsClicked, - aboutTranslationClicked = {}, + aboutTranslationClicked = aboutTranslationClicked, ) } + +@Composable +private fun getTranslationSwitchItemList( + translationPageSettings: TranslationPageSettings? = null, + initialFrom: Language? = null, + context: Context, + onStateChange: (TranslationSettingsOption, Boolean) -> Unit, +): List { + val translationSwitchItemList = mutableListOf() + + translationPageSettings?.let { + val alwaysOfferPopup = translationPageSettings.alwaysOfferPopup + val alwaysTranslateLanguage = translationPageSettings.alwaysTranslateLanguage + val neverTranslateLanguage = translationPageSettings.neverTranslateLanguage + val neverTranslateSite = translationPageSettings.neverTranslateSite + + alwaysOfferPopup?.let { + translationSwitchItemList.add( + TranslationSwitchItem( + type = TranslationPageSettingsOption.AlwaysOfferPopup(), + textLabel = context.getString(R.string.translation_option_bottom_sheet_always_translate), + isChecked = it, + isEnabled = !( + alwaysTranslateLanguage == true || + neverTranslateLanguage == true || + neverTranslateSite == true + ), + onStateChange = onStateChange, + ), + ) + } + + alwaysTranslateLanguage?.let { + translationSwitchItemList.add( + TranslationSwitchItem( + type = TranslationPageSettingsOption.AlwaysTranslateLanguage(), + textLabel = context.getString( + R.string.translation_option_bottom_sheet_always_translate_in_language, + initialFrom?.localizedDisplayName, + ), + isChecked = it, + isEnabled = neverTranslateSite != true, + onStateChange = onStateChange, + ), + ) + } + + neverTranslateLanguage?.let { + translationSwitchItemList.add( + TranslationSwitchItem( + type = TranslationPageSettingsOption.NeverTranslateLanguage(), + textLabel = context.getString( + R.string.translation_option_bottom_sheet_never_translate_in_language, + initialFrom?.localizedDisplayName, + ), + isChecked = it, + isEnabled = neverTranslateSite != true, + onStateChange = onStateChange, + ), + ) + } + + translationPageSettings.neverTranslateSite?.let { + translationSwitchItemList.add( + TranslationSwitchItem( + type = TranslationPageSettingsOption.NeverTranslateSite(), + textLabel = stringResource(R.string.translation_option_bottom_sheet_never_translate_site), + isChecked = it, + isEnabled = true, + onStateChange = onStateChange, + ), + ) + } + } + return translationSwitchItemList +} diff --git a/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt index 1fdb42af7..062e4b584 100644 --- a/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogFragment.kt @@ -28,7 +28,9 @@ import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.translate.Language import mozilla.components.concept.engine.translate.TranslationError import mozilla.components.lib.state.ext.observeAsComposableState import mozilla.components.support.base.feature.ViewBoundFeatureWrapper @@ -114,6 +116,14 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { val density = LocalDensity.current + val translationsDialogState = + translationsDialogStore.observeAsComposableState { it }.value + + val learnMoreUrl = SupportUtils.getSumoURLForTopic( + requireContext(), + SupportUtils.SumoTopic.TRANSLATIONS, + ) + TranslationDialogBottomSheet { TranslationsAnimation( translationsVisibility = translationsVisibility, @@ -131,13 +141,9 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { } }, ) { - val learnMoreUrl = SupportUtils.getSumoURLForTopic( - requireContext(), - SupportUtils.SumoTopic.TRANSLATIONS, - ) - TranslationsDialogContent( learnMoreUrl = learnMoreUrl, + translationsDialogState = translationsDialogState, ) { translationsVisibility = false } @@ -159,19 +165,12 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { } }, ) { - TranslationsOptionsDialog( - onBackClicked = { - translationsVisibility = true - }, - onTranslationSettingsClicked = { - findNavController().navigate( - TranslationsDialogFragmentDirections - .actionTranslationsDialogFragmentToTranslationSettingsFragment( - sessionId = args.sessionId, - ), - ) - }, - ) + TranslationsOptionsDialogContent( + learnMoreUrl = learnMoreUrl, + initialFrom = translationsDialogState?.initialFrom, + ) { + translationsVisibility = true + } } } } @@ -213,12 +212,13 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { ) } - @Composable @Suppress("LongMethod") - private fun TranslationsDialogContent(learnMoreUrl: String, onSettingClicked: () -> Unit) { - val translationsDialogState = - translationsDialogStore.observeAsComposableState { it }.value - + @Composable + private fun TranslationsDialogContent( + learnMoreUrl: String, + translationsDialogState: TranslationsDialogState? = null, + onSettingClicked: () -> Unit, + ) { translationsDialogState?.let { state -> isTranslationInProgress = state.isTranslationInProgress @@ -312,6 +312,44 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() { } } + @Composable + private fun TranslationsOptionsDialogContent( + learnMoreUrl: String, + initialFrom: Language? = null, + onBackClicked: () -> Unit, + ) { + val pageSettingsState = + browserStore.observeAsComposableState { state -> + state.findTab(args.sessionId)?.translationsState?.pageSettings + }.value + + TranslationsOptionsDialog( + context = requireContext(), + translationPageSettings = pageSettingsState, + initialFrom = initialFrom, + onStateChange = { type, checked -> + translationsDialogStore.dispatch( + TranslationsDialogAction.UpdatePageSettingsValue( + type as TranslationPageSettingsOption, + checked, + ), + ) + }, + onBackClicked = onBackClicked, + onTranslationSettingsClicked = { + findNavController().navigate( + TranslationsDialogFragmentDirections + .actionTranslationsDialogFragmentToTranslationSettingsFragment( + sessionId = args.sessionId, + ), + ) + }, + aboutTranslationClicked = { + openBrowserAndLoad(learnMoreUrl) + }, + ) + } + override fun onDismiss(dialog: DialogInterface) { super.onDismiss(dialog) if (isTranslationInProgress == true) { diff --git a/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogMiddleware.kt b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogMiddleware.kt index 6671843b5..6a1e6f359 100644 --- a/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogMiddleware.kt +++ b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogMiddleware.kt @@ -7,6 +7,7 @@ package org.mozilla.fenix.translations import mozilla.components.browser.state.action.TranslationsAction import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.translate.TranslationOperation +import mozilla.components.concept.engine.translate.TranslationPageSettingOperation import mozilla.components.lib.state.Middleware import mozilla.components.lib.state.MiddlewareContext @@ -18,6 +19,7 @@ class TranslationsDialogMiddleware( private val sessionId: String, ) : Middleware { + @Suppress("LongMethod") override fun invoke( context: MiddlewareContext, next: (TranslationsDialogAction) -> Unit, @@ -43,6 +45,15 @@ class TranslationsDialogMiddleware( ) } + is TranslationsDialogAction.FetchPageSettings -> { + browserStore.dispatch( + TranslationsAction.OperationRequestedAction( + tabId = sessionId, + operation = TranslationOperation.FETCH_PAGE_SETTINGS, + ), + ) + } + is TranslationsDialogAction.TranslateAction -> { context.state.initialFrom?.code?.let { fromLanguage -> context.state.initialTo?.code?.let { toLanguage -> @@ -64,6 +75,42 @@ class TranslationsDialogMiddleware( browserStore.dispatch(TranslationsAction.TranslateRestoreAction(sessionId)) } + is TranslationsDialogAction.UpdatePageSettingsValue -> { + when (action.type) { + is TranslationPageSettingsOption.AlwaysOfferPopup -> browserStore.dispatch( + TranslationsAction.UpdatePageSettingAction( + tabId = sessionId, + operation = TranslationPageSettingOperation.UPDATE_ALWAYS_OFFER_POPUP, + setting = action.checkValue, + ), + ) + + is TranslationPageSettingsOption.AlwaysTranslateLanguage -> browserStore.dispatch( + TranslationsAction.UpdatePageSettingAction( + tabId = sessionId, + operation = TranslationPageSettingOperation.UPDATE_ALWAYS_TRANSLATE_LANGUAGE, + setting = action.checkValue, + ), + ) + + is TranslationPageSettingsOption.NeverTranslateLanguage -> browserStore.dispatch( + TranslationsAction.UpdatePageSettingAction( + tabId = sessionId, + operation = TranslationPageSettingOperation.UPDATE_NEVER_TRANSLATE_LANGUAGE, + setting = action.checkValue, + ), + ) + + is TranslationPageSettingsOption.NeverTranslateSite -> browserStore.dispatch( + TranslationsAction.UpdatePageSettingAction( + tabId = sessionId, + operation = TranslationPageSettingOperation.UPDATE_NEVER_TRANSLATE_SITE, + setting = action.checkValue, + ), + ) + } + } + else -> { next(action) } diff --git a/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogStore.kt b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogStore.kt index a02fb9027..ac190e31b 100644 --- a/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogStore.kt +++ b/app/src/main/java/org/mozilla/fenix/translations/TranslationsDialogStore.kt @@ -3,11 +3,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.fenix.translations -import mozilla.components.browser.state.action.TranslationsAction import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.translate.Language import mozilla.components.concept.engine.translate.TranslationDownloadSize import mozilla.components.concept.engine.translate.TranslationError +import mozilla.components.concept.engine.translate.TranslationOperation import mozilla.components.lib.state.Action import mozilla.components.lib.state.Middleware import mozilla.components.lib.state.State @@ -26,7 +26,11 @@ class TranslationsDialogStore( initialState, TranslationsDialogReducer::reduce, middlewares, -) +) { + init { + dispatch(TranslationsDialogAction.FetchPageSettings) + } +} /** * The current state of the Translations bottom sheet dialog. @@ -65,23 +69,32 @@ sealed class TranslationsDialogAction : Action { /** * Invoked when the [TranslationsDialogStore] is added to the fragment. */ - object InitTranslationsDialog : TranslationsDialogAction() + data object InitTranslationsDialog : TranslationsDialogAction() + + /** + * When FetchSupportedLanguages is dispatched, an [TranslationOperation.FETCH_SUPPORTED_LANGUAGES] + * will be dispatched to the [BrowserStore]. + * This action should be used when an [UpdateTranslationError] appears and the user presses the "Try Again" button + * from the [TranslationsDialogBottomSheet]. + */ + data object FetchSupportedLanguages : TranslationsDialogAction() /** - * When FetchSupportedLanguages is dispatched, an [TranslationsAction.OperationRequestedAction] - * will be dispatched to the [BrowserStore] + * When FetchPageSettings is dispatched, an [TranslationOperation.FETCH_PAGE_SETTINGS] + * will be dispatched to the [BrowserStore]. + * This action should be used when [TranslationsDialogStore] gets initialised. */ - object FetchSupportedLanguages : TranslationsDialogAction() + data object FetchPageSettings : TranslationsDialogAction() /** * Invoked when the user wants to translate a website. */ - object TranslateAction : TranslationsDialogAction() + data object TranslateAction : TranslationsDialogAction() /** * Invoked when the user wants to restore the website to its original pre-translated content. */ - object RestoreTranslation : TranslationsDialogAction() + data object RestoreTranslation : TranslationsDialogAction() /** * Invoked when a translation error occurs during the translation process. @@ -144,6 +157,14 @@ sealed class TranslationsDialogAction : Action { */ data class UpdateTranslatedPageTitle(val title: String) : TranslationsDialogAction() + /** + * Invoked when the user wants to update a PageSettings value. + */ + data class UpdatePageSettingsValue( + val type: TranslationPageSettingsOption, + val checkValue: Boolean, + ) : TranslationsDialogAction() + /** * Updates the translation download file size. */ @@ -164,17 +185,17 @@ sealed class PositiveButtonType { /** * The translating indicator will appear. */ - object InProgress : PositiveButtonType() + data object InProgress : PositiveButtonType() /** * The button is in a disabled state. */ - object Disabled : PositiveButtonType() + data object Disabled : PositiveButtonType() /** * The button is in a enabled state. */ - object Enabled : PositiveButtonType() + data object Enabled : PositiveButtonType() } /** @@ -184,12 +205,12 @@ sealed class DismissDialogState { /** * The dialog should be dismissed. */ - object Dismiss : DismissDialogState() + data object Dismiss : DismissDialogState() /** * This is the step when translation is in progress and the dialog is waiting to be dismissed. */ - object WaitingToBeDismissed : DismissDialogState() + data object WaitingToBeDismissed : DismissDialogState() } internal object TranslationsDialogReducer { @@ -298,6 +319,9 @@ internal object TranslationsDialogReducer { } is TranslationsDialogAction.TranslateAction, + is TranslationsDialogAction.UpdatePageSettingsValue, + TranslationsDialogAction.TranslateAction, + TranslationsDialogAction.FetchPageSettings, TranslationsDialogAction.FetchSupportedLanguages, TranslationsDialogAction.RestoreTranslation, is TranslationsDialogAction.FetchDownloadFileSizeAction, diff --git a/app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogMiddlewareTest.kt b/app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogMiddlewareTest.kt index 4f540b252..44891f370 100644 --- a/app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogMiddlewareTest.kt +++ b/app/src/test/java/org/mozilla/fenix/translations/TranslationsDialogMiddlewareTest.kt @@ -11,6 +11,7 @@ import mozilla.components.browser.state.action.TranslationsAction import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.translate.Language import mozilla.components.concept.engine.translate.TranslationOperation +import mozilla.components.concept.engine.translate.TranslationPageSettingOperation import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.libstate.ext.waitUntilIdle import org.junit.Test @@ -31,7 +32,8 @@ class TranslationsDialogMiddlewareTest { initialState = TranslationsDialogState(), middlewares = listOf(translationsDialogMiddleware), ) - translationStore.dispatch(TranslationsDialogAction.FetchSupportedLanguages).joinBlocking() + translationStore.dispatch(TranslationsDialogAction.FetchSupportedLanguages) + .joinBlocking() translationStore.waitUntilIdle() @@ -129,4 +131,153 @@ class TranslationsDialogMiddlewareTest { ) } } + + @Test + fun `GIVEN translationState WHEN FetchPageSettings from TranslationDialogStore is called THEN call FETCH_PAGE_SETTINGS from BrowserStore`() = + runTest { + val browserStore = mockk(relaxed = true) + val translationsDialogMiddleware = + TranslationsDialogMiddleware(browserStore = browserStore, sessionId = "tab1") + + val translationStore = TranslationsDialogStore( + initialState = TranslationsDialogState(), + middlewares = listOf(translationsDialogMiddleware), + ) + translationStore.dispatch(TranslationsDialogAction.FetchPageSettings).joinBlocking() + + translationStore.waitUntilIdle() + + verify { + browserStore.dispatch( + TranslationsAction.OperationRequestedAction( + tabId = "tab1", + operation = TranslationOperation.FETCH_PAGE_SETTINGS, + ), + ) + } + } + + @Test + fun `GIVEN translationState WHEN UpdatePageSettingsValue with action type AlwaysOfferPopup from TranslationDialogStore is called THEN call UpdatePageSettingAction from BrowserStore`() = + runTest { + val browserStore = mockk(relaxed = true) + val translationsDialogMiddleware = + TranslationsDialogMiddleware(browserStore = browserStore, sessionId = "tab1") + + val translationStore = TranslationsDialogStore( + initialState = TranslationsDialogState(), + middlewares = listOf(translationsDialogMiddleware), + ) + translationStore.dispatch( + TranslationsDialogAction.UpdatePageSettingsValue( + type = TranslationPageSettingsOption.AlwaysOfferPopup(), + checkValue = true, + ), + ).joinBlocking() + + translationStore.waitUntilIdle() + + verify { + browserStore.dispatch( + TranslationsAction.UpdatePageSettingAction( + tabId = "tab1", + operation = TranslationPageSettingOperation.UPDATE_ALWAYS_OFFER_POPUP, + setting = true, + ), + ) + } + } + + @Test + fun `GIVEN translationState WHEN UpdatePageSettingsValue with action type AlwaysTranslateLanguage from TranslationDialogStore is called THEN call UpdatePageSettingAction from BrowserStore`() = + runTest { + val browserStore = mockk(relaxed = true) + val translationsDialogMiddleware = + TranslationsDialogMiddleware(browserStore = browserStore, sessionId = "tab1") + + val translationStore = TranslationsDialogStore( + initialState = TranslationsDialogState(), + middlewares = listOf(translationsDialogMiddleware), + ) + translationStore.dispatch( + TranslationsDialogAction.UpdatePageSettingsValue( + type = TranslationPageSettingsOption.AlwaysTranslateLanguage(), + checkValue = false, + ), + ).joinBlocking() + + translationStore.waitUntilIdle() + + verify { + browserStore.dispatch( + TranslationsAction.UpdatePageSettingAction( + tabId = "tab1", + operation = TranslationPageSettingOperation.UPDATE_ALWAYS_TRANSLATE_LANGUAGE, + setting = false, + ), + ) + } + } + + @Test + fun `GIVEN translationState WHEN UpdatePageSettingsValue with action type NeverTranslateLanguage from TranslationDialogStore is called THEN call UpdatePageSettingAction from BrowserStore`() = + runTest { + val browserStore = mockk(relaxed = true) + val translationsDialogMiddleware = + TranslationsDialogMiddleware(browserStore = browserStore, sessionId = "tab1") + + val translationStore = TranslationsDialogStore( + initialState = TranslationsDialogState(), + middlewares = listOf(translationsDialogMiddleware), + ) + translationStore.dispatch( + TranslationsDialogAction.UpdatePageSettingsValue( + type = TranslationPageSettingsOption.NeverTranslateLanguage(), + checkValue = true, + ), + ).joinBlocking() + + translationStore.waitUntilIdle() + + verify { + browserStore.dispatch( + TranslationsAction.UpdatePageSettingAction( + tabId = "tab1", + operation = TranslationPageSettingOperation.UPDATE_NEVER_TRANSLATE_LANGUAGE, + setting = true, + ), + ) + } + } + + @Test + fun `GIVEN translationState WHEN UpdatePageSettingsValue with action type NeverTranslateSite from TranslationDialogStore is called THEN call UpdatePageSettingAction from BrowserStore`() = + runTest { + val browserStore = mockk(relaxed = true) + val translationsDialogMiddleware = + TranslationsDialogMiddleware(browserStore = browserStore, sessionId = "tab1") + + val translationStore = TranslationsDialogStore( + initialState = TranslationsDialogState(), + middlewares = listOf(translationsDialogMiddleware), + ) + translationStore.dispatch( + TranslationsDialogAction.UpdatePageSettingsValue( + type = TranslationPageSettingsOption.NeverTranslateSite(), + checkValue = false, + ), + ).joinBlocking() + + translationStore.waitUntilIdle() + + verify { + browserStore.dispatch( + TranslationsAction.UpdatePageSettingAction( + tabId = "tab1", + operation = TranslationPageSettingOperation.UPDATE_NEVER_TRANSLATE_SITE, + setting = false, + ), + ) + } + } }