/* 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.translations 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 import mozilla.components.lib.state.Store /** * The [TranslationsDialogStore] holds the [TranslationsDialogState] (state tree). * * The only way to change the [TranslationsDialogState] inside * [TranslationsDialogStore] is to dispatch an [Action] on it. */ class TranslationsDialogStore( initialState: TranslationsDialogState, middlewares: List> = emptyList(), ) : Store( initialState, TranslationsDialogReducer::reduce, middlewares, ) { init { dispatch(TranslationsDialogAction.FetchPageSettings) } } /** * The current state of the Translations bottom sheet dialog. * * @property isTranslated The page is currently translated. * @property isTranslationInProgress The page is currently attempting a translation. * @property positiveButtonType Can be enabled,disabled or in progress. * @property translationDownloadSize A data class to contain information * related to the download size required for a given translation to/from pair. * @property error An error that can occur during the translation process. * @property documentLangDisplayName Language name to display in * case [TranslationError.LanguageNotSupportedError] appears. * @property dismissDialogState Whether the dialog bottom sheet should be dismissed. * @property initialFrom Initial "from" language, based on the translation state and page state. * @property initialTo Initial "to" language, based on the translation state and page state. * @property fromLanguages Translation menu items to be shown in the translate from dropdown. * @property toLanguages Translation menu items to be shown in the translate to dropdown. * @property translatedPageTitle Title of the bottom sheet dialogue if the page was translated. */ data class TranslationsDialogState( var isTranslated: Boolean = false, val isTranslationInProgress: Boolean = false, val positiveButtonType: PositiveButtonType? = null, val translationDownloadSize: TranslationDownloadSize? = null, val error: TranslationError? = null, val documentLangDisplayName: String? = null, val dismissDialogState: DismissDialogState? = null, val initialFrom: Language? = null, val initialTo: Language? = null, val fromLanguages: List? = null, val toLanguages: List? = null, val translatedPageTitle: String? = null, ) : State /** * Action to dispatch through the `TranslationsStore` to modify `TranslationsDialogState` through the reducer. */ sealed class TranslationsDialogAction : Action { /** * Invoked when the [TranslationsDialogStore] is added to the fragment. */ 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 FetchPageSettings is dispatched, an [TranslationOperation.FETCH_PAGE_SETTINGS] * will be dispatched to the [BrowserStore]. * This action should be used when [TranslationsDialogStore] gets initialised. */ data object FetchPageSettings : TranslationsDialogAction() /** * Invoked when the user wants to translate a website. */ data object TranslateAction : TranslationsDialogAction() /** * Invoked when the user wants to restore the website to its original pre-translated content. */ data object RestoreTranslation : TranslationsDialogAction() /** * Invoked when a translation error occurs during the translation process. */ data class UpdateTranslationError( val translationError: TranslationError? = null, val documentLangDisplayName: String? = null, ) : TranslationsDialogAction() /** * Updates translate from languages list. */ data class UpdateTranslateFromLanguages( val translateFromLanguages: List, ) : TranslationsDialogAction() /** * Updates translate to languages list. */ data class UpdateTranslateToLanguages( val translateToLanguages: List, ) : TranslationsDialogAction() /** * Updates to the current selected language from the "translateFromLanguages" list. */ data class UpdateFromSelectedLanguage( val language: Language, ) : TranslationsDialogAction() /** * Updates to the current selected language from the "translateToLanguages" list. */ data class UpdateToSelectedLanguage( val language: Language, ) : TranslationsDialogAction() /** * Dismiss the translation dialog fragment. */ data class DismissDialog( val dismissDialogState: DismissDialogState, ) : TranslationsDialogAction() /** * Updates the dialog content to translation in progress status. */ data class UpdateTranslationInProgress( val inProgress: Boolean, ) : TranslationsDialogAction() /** * Updates the dialog content to translated status. */ data class UpdateTranslated( val isTranslated: Boolean, ) : TranslationsDialogAction() /** * Updates the dialog title if the page was translated. */ 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. */ data class UpdateDownloadTranslationDownloadSize(val translationDownloadSize: TranslationDownloadSize? = null) : TranslationsDialogAction() /** * Fetch the translation download file size. */ data class FetchDownloadFileSizeAction(val toLanguage: Language, val fromLanguage: Language) : TranslationsDialogAction() } /** * Positive button type from the translation bottom sheet. */ sealed class PositiveButtonType { /** * The translating indicator will appear. */ data object InProgress : PositiveButtonType() /** * The button is in a disabled state. */ data object Disabled : PositiveButtonType() /** * The button is in a enabled state. */ data object Enabled : PositiveButtonType() } /** * Dismiss translation bottom sheet type. */ sealed class DismissDialogState { /** * The dialog should be dismissed. */ data object Dismiss : DismissDialogState() /** * This is the step when translation is in progress and the dialog is waiting to be dismissed. */ data object WaitingToBeDismissed : DismissDialogState() } internal object TranslationsDialogReducer { /** * Reduces the translations dialog state from the current state and an action performed on it. * * @param state The current translations dialog state. * @param action The action to perform. * @return The new [TranslationsDialogState]. */ @Suppress("LongMethod") fun reduce( state: TranslationsDialogState, action: TranslationsDialogAction, ): TranslationsDialogState { return when (action) { is TranslationsDialogAction.UpdateFromSelectedLanguage -> { state.copy( initialFrom = action.language, positiveButtonType = if (state.initialTo != null && action.language != state.initialTo) { PositiveButtonType.Enabled } else { PositiveButtonType.Disabled }, ) } is TranslationsDialogAction.UpdateToSelectedLanguage -> { state.copy( initialTo = action.language, positiveButtonType = if (state.initialFrom != null && action.language != state.initialFrom) { PositiveButtonType.Enabled } else { PositiveButtonType.Disabled }, ) } is TranslationsDialogAction.UpdateTranslateToLanguages -> { state.copy(toLanguages = action.translateToLanguages) } is TranslationsDialogAction.UpdateTranslateFromLanguages -> { state.copy(fromLanguages = action.translateFromLanguages) } is TranslationsDialogAction.DismissDialog -> { state.copy(dismissDialogState = action.dismissDialogState) } is TranslationsDialogAction.UpdateTranslationInProgress -> { state.copy( isTranslationInProgress = action.inProgress, positiveButtonType = if (action.inProgress) { PositiveButtonType.InProgress } else { state.positiveButtonType }, ) } is TranslationsDialogAction.InitTranslationsDialog -> { state.copy( positiveButtonType = if (state.initialTo == null || state.initialFrom == null) { PositiveButtonType.Disabled } else { state.positiveButtonType }, ) } is TranslationsDialogAction.UpdateTranslationError -> { state.copy( error = action.translationError, documentLangDisplayName = action.documentLangDisplayName, positiveButtonType = if (action.translationError is TranslationError.LanguageNotSupportedError) { PositiveButtonType.Disabled } else { PositiveButtonType.Enabled }, ) } is TranslationsDialogAction.UpdateTranslated -> { state.copy( isTranslated = action.isTranslated, positiveButtonType = PositiveButtonType.Disabled, ) } is TranslationsDialogAction.UpdateTranslatedPageTitle -> { state.copy(translatedPageTitle = action.title) } is TranslationsDialogAction.UpdateDownloadTranslationDownloadSize -> { state.copy( translationDownloadSize = if ( action.translationDownloadSize?.fromLanguage == state.initialFrom && action.translationDownloadSize?.toLanguage == state.initialTo && isTranslationDownloadSizeValid(action.translationDownloadSize) ) { action.translationDownloadSize } else { null }, ) } is TranslationsDialogAction.TranslateAction, is TranslationsDialogAction.UpdatePageSettingsValue, TranslationsDialogAction.TranslateAction, TranslationsDialogAction.FetchPageSettings, TranslationsDialogAction.FetchSupportedLanguages, TranslationsDialogAction.RestoreTranslation, is TranslationsDialogAction.FetchDownloadFileSizeAction, -> { // handled by [TranslationsDialogMiddleware] state } } } private fun isTranslationDownloadSizeValid(translationDownloadSize: TranslationDownloadSize?) = translationDownloadSize?.size != 0L && translationDownloadSize?.error == null }