TranslationsStateReducer.kt
/* 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 mozilla.components.browser.state.reducer
import mozilla.components.browser.state.action.TranslationsAction
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.state.TranslationsState
import mozilla.components.concept.engine.translate.LanguageModel
import mozilla.components.concept.engine.translate.ModelOperation
import mozilla.components.concept.engine.translate.ModelState
import mozilla.components.concept.engine.translate.TranslationError
import mozilla.components.concept.engine.translate.TranslationOperation
import mozilla.components.concept.engine.translate.TranslationPageSettingOperation
import mozilla.components.concept.engine.translate.TranslationPageSettings
internal object TranslationsStateReducer {
/**
* Reducer for [BrowserState.translationEngine] and [SessionState.translationsState]
*/
@Suppress("LongMethod")
fun reduce(state: BrowserState, action: TranslationsAction): BrowserState = when (action) {
TranslationsAction.InitTranslationsBrowserState -> {
// No state change on this operation
state
}
is TranslationsAction.TranslateExpectedAction -> {
state.copyWithTranslationsState(action.tabId) {
it.copy(
isExpectedTranslate = true,
)
}
}
is TranslationsAction.TranslateOfferAction -> {
state.copyWithTranslationsState(action.tabId) {
it.copy(
isOfferTranslate = action.isOfferTranslate,
)
}
}
is TranslationsAction.TranslateStateChangeAction -> {
var isExpectedTranslate = state.findTab(action.tabId)?.translationsState?.isExpectedTranslate ?: true
var isOfferTranslate = state.findTab(action.tabId)?.translationsState?.isOfferTranslate ?: true
// Checking if a translation can be anticipated or not based on
// the new translation engine state detected metadata.
if (action.translationEngineState.detectedLanguages == null ||
action.translationEngineState.detectedLanguages?.supportedDocumentLang == false ||
action.translationEngineState.detectedLanguages?.userPreferredLangTag == null
) {
// Value can also update through [TranslateExpectedAction]
// via the translations engine.
isExpectedTranslate = false
// Value can also update through [TranslateOfferAction]
// via the translations engine.
isOfferTranslate = false
}
// Checking for if the translations engine is in the fully translated state or not based
// on if a visual change has occurred on the browser.
if (action.translationEngineState.hasVisibleChange != true) {
// In an untranslated state
var translationsError: TranslationError? = null
if (action.translationEngineState.detectedLanguages?.supportedDocumentLang == false) {
translationsError = TranslationError.LanguageNotSupportedError(cause = null)
}
state.copyWithTranslationsState(action.tabId) {
it.copy(
isOfferTranslate = isOfferTranslate,
isExpectedTranslate = isExpectedTranslate,
isTranslated = false,
translationEngineState = action.translationEngineState,
translationError = translationsError,
)
}
} else {
// In a translated state
state.copyWithTranslationsState(action.tabId) {
it.copy(
isOfferTranslate = isOfferTranslate,
isExpectedTranslate = isExpectedTranslate,
isTranslated = true,
isTranslateProcessing = false,
translationError = null,
translationEngineState = action.translationEngineState,
)
}
}
}
is TranslationsAction.TranslateAction ->
state.copyWithTranslationsState(action.tabId) {
it.copy(
isOfferTranslate = false,
isTranslateProcessing = true,
)
}
is TranslationsAction.TranslateRestoreAction ->
state.copyWithTranslationsState(action.tabId) {
it.copy(isRestoreProcessing = true)
}
is TranslationsAction.TranslateSuccessAction -> {
when (action.operation) {
TranslationOperation.TRANSLATE -> {
// The isTranslated state will be identified on a translation state change.
state.copyWithTranslationsState(action.tabId) {
it.copy(
translationError = null,
)
}
}
TranslationOperation.RESTORE -> {
state.copyWithTranslationsState(action.tabId) {
it.copy(
isTranslated = false,
isRestoreProcessing = false,
translationError = null,
)
}
}
TranslationOperation.FETCH_SUPPORTED_LANGUAGES -> {
// Reset the error state, and then generally expect
// [TranslationsAction.SetSupportedLanguagesAction] to update state in the
// success case.
state.copyWithTranslationsState(action.tabId) {
it.copy(
translationError = null,
)
}
}
TranslationOperation.FETCH_LANGUAGE_MODELS -> {
// Reset the error state, and then generally expect
// [TranslationsAction.SetLanguageModelsAction] to update state in the
// success case.
state.copyWithTranslationsState(action.tabId) {
it.copy(
translationError = null,
)
}
}
TranslationOperation.FETCH_PAGE_SETTINGS -> {
// Reset the error state, and then generally expect
// [TranslationsAction.SetPageSettingsAction] to update state in the
// success case.
state.copyWithTranslationsState(action.tabId) {
it.copy(
settingsError = null,
)
}
}
TranslationOperation.FETCH_OFFER_SETTING -> {
// Reset the error state, and then generally expect
// [TranslationsAction.SetGlobalOfferTranslateSettingAction] to update state in the
// success case.
state.copyWithTranslationsState(action.tabId) {
it.copy(
settingsError = null,
)
}
}
TranslationOperation.FETCH_AUTOMATIC_LANGUAGE_SETTINGS -> {
state.copy(
translationEngine = state.translationEngine.copy(
engineError = null,
),
)
}
TranslationOperation.FETCH_NEVER_TRANSLATE_SITES -> {
// Reset the error state, and then generally expect
// [TranslationsAction.SetNeverTranslateSitesAction] to update
// state in the success case.
state.copy(
translationEngine = state.translationEngine.copy(
neverTranslateSites = null,
),
)
}
}
}
is TranslationsAction.TranslateExceptionAction -> {
when (action.operation) {
TranslationOperation.TRANSLATE -> {
state.copyWithTranslationsState(action.tabId) {
it.copy(
isTranslateProcessing = false,
translationError = action.translationError,
)
}
}
TranslationOperation.RESTORE -> {
state.copyWithTranslationsState(action.tabId) {
it.copy(
isRestoreProcessing = false,
translationError = action.translationError,
)
}
}
TranslationOperation.FETCH_SUPPORTED_LANGUAGES -> {
state.copyWithTranslationsState(action.tabId) {
it.copy(
translationError = action.translationError,
)
}
}
TranslationOperation.FETCH_LANGUAGE_MODELS -> {
state.copyWithTranslationsState(action.tabId) {
it.copy(
translationError = action.translationError,
)
}
}
TranslationOperation.FETCH_PAGE_SETTINGS -> {
state.copyWithTranslationsState(action.tabId) {
it.copy(
pageSettings = null,
settingsError = action.translationError,
)
}
}
TranslationOperation.FETCH_OFFER_SETTING -> {
state.copyWithTranslationsState(action.tabId) {
it.copy(
translationError = action.translationError,
)
}
}
TranslationOperation.FETCH_AUTOMATIC_LANGUAGE_SETTINGS -> {
state.copyWithTranslationsState(action.tabId) {
it.copy(
translationError = action.translationError,
)
}
}
TranslationOperation.FETCH_NEVER_TRANSLATE_SITES -> {
state.copyWithTranslationsState(action.tabId) {
it.copy(
settingsError = action.translationError,
)
}
}
}
}
is TranslationsAction.EngineExceptionAction -> {
state.copy(translationEngine = state.translationEngine.copy(engineError = action.error))
}
is TranslationsAction.SetSupportedLanguagesAction ->
state.copy(
translationEngine = state.translationEngine.copy(
supportedLanguages = action.supportedLanguages,
engineError = null,
),
)
is TranslationsAction.SetLanguageModelsAction ->
state.copy(
translationEngine = state.translationEngine.copy(
languageModels = action.languageModels,
engineError = null,
),
)
is TranslationsAction.SetPageSettingsAction ->
state.copyWithTranslationsState(action.tabId) {
it.copy(
pageSettings = action.pageSettings,
settingsError = null,
)
}
is TranslationsAction.SetTranslateProcessingAction ->
state.copyWithTranslationsState(action.tabId) {
it.copy(
isTranslateProcessing = action.isProcessing,
)
}
is TranslationsAction.SetNeverTranslateSitesAction ->
state.copy(
translationEngine = state.translationEngine.copy(
neverTranslateSites = action.neverTranslateSites,
),
)
is TranslationsAction.RemoveNeverTranslateSiteAction -> {
val neverTranslateSites = state.translationEngine.neverTranslateSites
val updatedNeverTranslateSites = neverTranslateSites?.filter { it != action.origin }?.toList()
state.copy(
translationEngine = state.translationEngine.copy(
neverTranslateSites = updatedNeverTranslateSites,
),
)
}
is TranslationsAction.OperationRequestedAction ->
when (action.operation) {
TranslationOperation.FETCH_SUPPORTED_LANGUAGES -> {
state.copy(
translationEngine = state.translationEngine.copy(
supportedLanguages = null,
),
)
}
TranslationOperation.FETCH_LANGUAGE_MODELS -> {
state.copy(
translationEngine = state.translationEngine.copy(
languageModels = null,
),
)
}
TranslationOperation.FETCH_AUTOMATIC_LANGUAGE_SETTINGS -> {
state.copy(
translationEngine = state.translationEngine.copy(
languageSettings = null,
),
)
}
TranslationOperation.FETCH_PAGE_SETTINGS -> {
val tabId = action.tabId ?: state.selectedTab?.id
if (tabId != null) {
state.copyWithTranslationsState(tabId) {
it.copy(
pageSettings = null,
)
}
} else {
state
}
}
TranslationOperation.FETCH_OFFER_SETTING -> {
state.copy(
translationEngine = state.translationEngine.copy(
offerTranslation = null,
),
)
}
TranslationOperation.FETCH_NEVER_TRANSLATE_SITES -> {
state.copy(
translationEngine = state.translationEngine.copy(
neverTranslateSites = null,
),
)
}
TranslationOperation.TRANSLATE, TranslationOperation.RESTORE -> {
// No state change for these operations
state
}
}
is TranslationsAction.UpdatePageSettingAction -> {
val currentPageSettings =
state.findTab(action.tabId)?.translationsState?.pageSettings ?: TranslationPageSettings()
when (action.operation) {
TranslationPageSettingOperation.UPDATE_ALWAYS_OFFER_POPUP -> {
state.copyWithTranslationsState(action.tabId) {
it.copy(
pageSettings = currentPageSettings.copy(alwaysOfferPopup = action.setting),
)
}
}
TranslationPageSettingOperation.UPDATE_ALWAYS_TRANSLATE_LANGUAGE -> {
val alwaysTranslateLang = action.setting
var neverTranslateLang = currentPageSettings.neverTranslateLanguage
if (alwaysTranslateLang) {
// Always and never translate sites are always opposites when the other is true.
neverTranslateLang = false
}
state.copyWithTranslationsState(action.tabId) {
it.copy(
pageSettings = currentPageSettings.copy(
alwaysTranslateLanguage = alwaysTranslateLang,
neverTranslateLanguage = neverTranslateLang,
),
)
}
}
TranslationPageSettingOperation.UPDATE_NEVER_TRANSLATE_LANGUAGE -> {
var alwaysTranslateLang = currentPageSettings.alwaysTranslateLanguage
val neverTranslateLang = action.setting
if (neverTranslateLang) {
// Always and never translate sites are always opposites when the other is true.
alwaysTranslateLang = false
}
state.copyWithTranslationsState(action.tabId) {
it.copy(
pageSettings = currentPageSettings.copy(
alwaysTranslateLanguage = alwaysTranslateLang,
neverTranslateLanguage = neverTranslateLang,
),
)
}
}
TranslationPageSettingOperation.UPDATE_NEVER_TRANSLATE_SITE -> {
state.copyWithTranslationsState(action.tabId) {
it.copy(
pageSettings = currentPageSettings.copy(neverTranslateSite = action.setting),
)
}
}
}
}
is TranslationsAction.UpdateLanguageSettingsAction -> {
val languageSettings = state.translationEngine.languageSettings?.toMutableMap()
// Only set when keys are present.
if (languageSettings?.get(action.languageCode) != null) {
languageSettings[action.languageCode] = action.setting
}
state.copy(
translationEngine = state.translationEngine.copy(
languageSettings = languageSettings,
),
)
}
is TranslationsAction.SetGlobalOfferTranslateSettingAction -> {
state.copy(
translationEngine = state.translationEngine.copy(
offerTranslation = action.offerTranslation,
),
)
}
is TranslationsAction.UpdateGlobalOfferTranslateSettingAction -> {
state.copy(
translationEngine = state.translationEngine.copy(
offerTranslation = action.offerTranslation,
),
)
}
is TranslationsAction.SetEngineSupportedAction -> {
state.copy(
translationEngine = state.translationEngine.copy(
isEngineSupported = action.isEngineSupported,
engineError = null,
),
)
}
is TranslationsAction.FetchTranslationDownloadSizeAction -> {
state.copyWithTranslationsState(action.tabId) {
it.copy(
translationDownloadSize = null,
)
}
}
is TranslationsAction.SetTranslationDownloadSizeAction -> {
state.copyWithTranslationsState(action.tabId) {
it.copy(
translationDownloadSize = action.translationSize,
)
}
}
is TranslationsAction.SetLanguageSettingsAction -> {
state.copy(
translationEngine = state.translationEngine.copy(
languageSettings = action.languageSettings,
engineError = null,
),
)
}
is TranslationsAction.ManageLanguageModelsAction -> {
val processState = if (action.options.operation == ModelOperation.DOWNLOAD) {
ModelState.DOWNLOAD_IN_PROGRESS
} else {
ModelState.DELETION_IN_PROGRESS
}
val newModelState = LanguageModel.determineNewLanguageModelState(
appLanguage = state.locale?.language.toString(),
currentLanguageModels = state.translationEngine.languageModels,
options = action.options,
newStatus = processState,
)
state.copy(
translationEngine = state.translationEngine.copy(
languageModels = newModelState,
),
)
}
}
private inline fun BrowserState.copyWithTranslationsState(
tabId: String,
crossinline update: (TranslationsState) -> TranslationsState,
): BrowserState {
return updateTabOrCustomTabState(tabId) { current ->
current.createCopy(translationsState = update(current.translationsState))
}
}
}