diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt index 9b20c6596c42..a32c9838924e 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt @@ -40,5 +40,6 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) { FromReviewQualityCheck(R.id.reviewQualityCheckFragment), FromAddonsManagementFragment(R.id.addonsManagementFragment), FromTranslationsDialogFragment(R.id.translationsDialogFragment), + FromDownloadLanguagesPreferenceFragment(R.id.downloadLanguagesPreferenceFragment), FromMenuDialogFragment(R.id.menuDialogFragment), } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Activity.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Activity.kt index 93cc516d8736..6335969327b9 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Activity.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/Activity.kt @@ -55,6 +55,7 @@ import org.mozilla.fenix.shopping.ReviewQualityCheckFragmentDirections import org.mozilla.fenix.tabstray.TabsTrayFragmentDirections import org.mozilla.fenix.trackingprotection.TrackingProtectionPanelDialogFragmentDirections import org.mozilla.fenix.translations.TranslationsDialogFragmentDirections +import org.mozilla.fenix.translations.preferences.downloadlanguages.DownloadLanguagesPreferenceFragmentDirections import java.security.InvalidParameterException /** @@ -326,6 +327,9 @@ private fun getHomeNavDirections( BrowserDirection.FromTranslationsDialogFragment -> TranslationsDialogFragmentDirections.actionGlobalBrowser() BrowserDirection.FromMenuDialogFragment -> MenuDialogFragmentDirections.actionGlobalBrowser() + + BrowserDirection.FromDownloadLanguagesPreferenceFragment -> + DownloadLanguagesPreferenceFragmentDirections.actionGlobalBrowser() } const val REQUEST_CODE_BROWSER_ROLE = 1 diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DeleteLanguageFileDialog.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DeleteLanguageFileDialog.kt index d414e0df5a38..855451f75c74 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DeleteLanguageFileDialog.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DeleteLanguageFileDialog.kt @@ -30,9 +30,9 @@ import java.util.Locale */ @Composable fun DeleteLanguageFileDialog( - language: String, + language: String? = null, isAllLanguagesItemType: Boolean, - fileSize: Long, + fileSize: Long? = null, onConfirmDelete: () -> Unit, onCancel: () -> Unit, ) { @@ -43,24 +43,28 @@ fun DeleteLanguageFileDialog( shape = RoundedCornerShape(8.dp), ), title = { - val title: String = if (isAllLanguagesItemType) { + val title: String? = if (isAllLanguagesItemType) { stringResource( id = R.string.delete_language_all_languages_file_dialog_title, - fileSize.toMegabyteOrKilobyteString(), + fileSize?.toMegabyteOrKilobyteString() ?: 0L, ) } else { - stringResource( - id = R.string.delete_language_file_dialog_title, - language, - fileSize.toMegabyteOrKilobyteString(), - ) + language?.let { + stringResource( + id = R.string.delete_language_file_dialog_title, + it, + fileSize?.toMegabyteOrKilobyteString() ?: 0L, + ) + } } - Text( - text = title, - color = FirefoxTheme.colors.textPrimary, - style = FirefoxTheme.typography.headline7, - ) + title?.let { + Text( + text = it, + color = FirefoxTheme.colors.textPrimary, + style = FirefoxTheme.typography.headline7, + ) + } }, text = { val message: String = if (isAllLanguagesItemType) { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguageFileDialog.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguageFileDialog.kt index 94bf2d9c9973..e7f93296fa96 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguageFileDialog.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguageFileDialog.kt @@ -50,7 +50,7 @@ import org.mozilla.fenix.theme.FirefoxTheme @Suppress("LongMethod") fun DownloadLanguageFileDialog( downloadLanguageDialogType: DownloadLanguageFileDialogType, - fileSize: Long, + fileSize: Long? = null, isCheckBoxEnabled: Boolean, isCacheMessage: Boolean = false, onSavingModeStateChange: (Boolean) -> Unit, @@ -70,12 +70,12 @@ fun DownloadLanguageFileDialog( if (downloadLanguageDialogType is DownloadLanguageFileDialogType.TranslationRequest) { stringResource( R.string.translations_download_language_file_dialog_title, - fileSize.toMegabyteOrKilobyteString(), + fileSize?.toMegabyteOrKilobyteString() ?: 0L, ) } else { stringResource( R.string.download_language_file_dialog_title, - fileSize.toMegabyteOrKilobyteString(), + fileSize?.toMegabyteOrKilobyteString() ?: 0L, ) } Text( @@ -90,11 +90,15 @@ fun DownloadLanguageFileDialog( downloadLanguageDialogType is DownloadLanguageFileDialogType.TranslationRequest ) { Text( - text = if (isCacheMessage) { stringResource( - R.string.download_language_file_dialog_message_all_languages, - ) } else { stringResource( - R.string.download_language_file_dialog_message_all_languages_no_cache, - ) }, + text = if (isCacheMessage) { + stringResource( + R.string.download_language_file_dialog_message_all_languages, + ) + } else { + stringResource( + R.string.download_language_file_dialog_message_all_languages_no_cache, + ) + }, modifier = Modifier.padding(top = 16.dp, bottom = 16.dp), style = FirefoxTheme.typography.body2, color = FirefoxTheme.colors.textPrimary, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguageItemPreference.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguageItemPreference.kt index 5965010bb3ed..be33367deb5f 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguageItemPreference.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguageItemPreference.kt @@ -4,32 +4,21 @@ package org.mozilla.fenix.translations.preferences.downloadlanguages -import android.os.Parcelable -import kotlinx.parcelize.Parcelize -import org.mozilla.geckoview.TranslationsController +import mozilla.components.concept.engine.translate.LanguageModel /** * DownloadLanguageItemPreference that will appear on [DownloadLanguagesPreferenceFragment] screen. * * @property languageModel The object comes from the translation engine. - * @property state State of the [languageModel]. + * @property type The type of the language item. + * @property enabled Whether the item is enabled or not. + * */ data class DownloadLanguageItemPreference( - val languageModel: TranslationsController.RuntimeTranslation.LanguageModel, - val state: DownloadLanguageItemStatePreference, -) - -/** - * DownloadLanguageItemStatePreference the state of the [DownloadLanguageItemPreference] - * - * @property type The type of the language item. - * @property status State of the language file. - */ -@Parcelize -data class DownloadLanguageItemStatePreference( + val languageModel: LanguageModel, val type: DownloadLanguageItemTypePreference, - val status: DownloadLanguageItemStatusPreference, -) : Parcelable + val enabled: Boolean = true, +) /** * DownloadLanguageItemTypePreference the type of the [DownloadLanguageItemPreference] @@ -39,13 +28,3 @@ enum class DownloadLanguageItemTypePreference { AllLanguages, GeneralLanguage, } - -/** - * DownloadLanguageItemStatusPreference the status of the [DownloadLanguageItemPreference] - */ -enum class DownloadLanguageItemStatusPreference { - Downloaded, - NotDownloaded, - DownloadInProgress, - Selected, -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguagesPreference.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguagesPreference.kt index aaf9976e736c..d8511383123a 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguagesPreference.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguagesPreference.kt @@ -12,10 +12,12 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.Icon @@ -38,6 +40,9 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp +import mozilla.components.concept.engine.translate.Language +import mozilla.components.concept.engine.translate.LanguageModel +import mozilla.components.concept.engine.translate.ModelState import mozilla.components.feature.downloads.toMegabyteOrKilobyteString import org.mozilla.fenix.R import org.mozilla.fenix.compose.Divider @@ -46,42 +51,73 @@ import org.mozilla.fenix.compose.LinkTextState import org.mozilla.fenix.compose.annotation.LightDarkPreview import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.translations.DownloadIconIndicator -import org.mozilla.geckoview.TranslationsController import java.util.Locale /** * Firefox Download Languages preference screen. * * @param downloadLanguageItemPreferences List of [DownloadLanguageItemPreference]s that needs to be displayed. + * @param learnMoreUrl The learn more link for translations website. + * @param onLearnMoreClicked Invoked when the user clicks on the "Learn More" button. * @param onItemClick Invoked when the user clicks on the language item. */ -@Suppress("LongMethod") +@Suppress("LongMethod", "CyclomaticComplexMethod") @Composable fun DownloadLanguagesPreference( downloadLanguageItemPreferences: List, + learnMoreUrl: String, + onLearnMoreClicked: () -> Unit, onItemClick: (DownloadLanguageItemPreference) -> Unit, ) { val downloadedItems = downloadLanguageItemPreferences.filter { - it.state.status == DownloadLanguageItemStatusPreference.Downloaded && - it.state.type == DownloadLanguageItemTypePreference.GeneralLanguage + it.languageModel.status == ModelState.DOWNLOADED && + it.type == DownloadLanguageItemTypePreference.GeneralLanguage } val notDownloadedItems = downloadLanguageItemPreferences.filter { - it.state.status == DownloadLanguageItemStatusPreference.NotDownloaded && - it.state.type == DownloadLanguageItemTypePreference.GeneralLanguage + it.languageModel.status == ModelState.NOT_DOWNLOADED && + it.type == DownloadLanguageItemTypePreference.GeneralLanguage } - val inProgressItems = downloadLanguageItemPreferences.filter { - it.state.status == DownloadLanguageItemStatusPreference.DownloadInProgress && - it.state.type == DownloadLanguageItemTypePreference.GeneralLanguage + val downloadInProgressItems = downloadLanguageItemPreferences.filter { + it.languageModel.status == ModelState.DOWNLOAD_IN_PROGRESS && + it.type == DownloadLanguageItemTypePreference.GeneralLanguage } - val allLanguagesItem = downloadLanguageItemPreferences.last { - it.state.type == DownloadLanguageItemTypePreference.AllLanguages + val deleteInProgressItems = downloadLanguageItemPreferences.filter { + it.languageModel.status == ModelState.DELETION_IN_PROGRESS && + it.type == DownloadLanguageItemTypePreference.GeneralLanguage } - val defaultLanguageItem = downloadLanguageItemPreferences.last { - it.state.type == DownloadLanguageItemTypePreference.PivotLanguage + var allLanguagesItemDownloaded: DownloadLanguageItemPreference? = null + if (downloadLanguageItemPreferences.any { + it.type == DownloadLanguageItemTypePreference.AllLanguages && + it.languageModel.status == ModelState.DOWNLOADED + } + ) { + allLanguagesItemDownloaded = downloadLanguageItemPreferences.last { + it.type == DownloadLanguageItemTypePreference.AllLanguages && + it.languageModel.status == ModelState.DOWNLOADED + } + } + + var allLanguagesItemNotDownloaded: DownloadLanguageItemPreference? = null + if (downloadLanguageItemPreferences.any { + it.type == DownloadLanguageItemTypePreference.AllLanguages && + it.languageModel.status == ModelState.NOT_DOWNLOADED + } + ) { + allLanguagesItemNotDownloaded = downloadLanguageItemPreferences.last { + it.type == DownloadLanguageItemTypePreference.AllLanguages && + it.languageModel.status == ModelState.NOT_DOWNLOADED + } + } + + var pivotLanguage: DownloadLanguageItemPreference? = null + if (downloadLanguageItemPreferences.any { it.type == DownloadLanguageItemTypePreference.PivotLanguage }) { + pivotLanguage = downloadLanguageItemPreferences.last { + it.type == DownloadLanguageItemTypePreference.PivotLanguage + } } Column( @@ -89,22 +125,33 @@ fun DownloadLanguagesPreference( color = FirefoxTheme.colors.layer1, ), ) { - DownloadLanguagesHeaderPreference() + DownloadLanguagesHeaderPreference( + learnMoreUrl = learnMoreUrl, + onLearnMoreClicked = onLearnMoreClicked, + ) LazyColumn { - item { - DownloadLanguagesHeader( - stringResource( - id = R.string.download_languages_available_languages_preference, - ), - ) + if ( + allLanguagesItemDownloaded != null || + pivotLanguage?.languageModel?.status == ModelState.DOWNLOADED || + downloadInProgressItems.isNotEmpty() + ) { + item { + DownloadLanguagesHeader( + stringResource( + id = R.string.download_languages_available_languages_preference, + ), + ) + } } - item { - LanguageItemPreference( - item = defaultLanguageItem, - onItemClick = onItemClick, - ) + allLanguagesItemDownloaded?.let { + item { + LanguageItemPreference( + item = allLanguagesItemDownloaded, + onItemClick = onItemClick, + ) + } } items(downloadedItems) { item: DownloadLanguageItemPreference -> @@ -114,31 +161,75 @@ fun DownloadLanguagesPreference( ) } - items(inProgressItems) { item: DownloadLanguageItemPreference -> + items(downloadInProgressItems) { item: DownloadLanguageItemPreference -> LanguageItemPreference( item = item, onItemClick = onItemClick, ) } - item { - Divider(Modifier.padding(top = 8.dp, bottom = 8.dp)) + if ( + pivotLanguage != null && + pivotLanguage.languageModel.status == ModelState.DOWNLOADED + ) { + item { + LanguageItemPreference( + item = pivotLanguage, + onItemClick = onItemClick, + ) + } } - item { - DownloadLanguagesHeader( - stringResource( - id = R.string.download_language_header_preference, - ), + if (pivotLanguage?.languageModel?.status == ModelState.NOT_DOWNLOADED || + shouldShowDownloadLanguagesHeader( + allLanguagesItemNotDownloaded = allLanguagesItemNotDownloaded, + deleteInProgressItems = deleteInProgressItems, + notDownloadedItems = notDownloadedItems, ) + ) { + if (pivotLanguage?.languageModel?.status == ModelState.DOWNLOADED || + downloadInProgressItems.isNotEmpty() || + allLanguagesItemDownloaded != null + ) { + item { + Divider(Modifier.padding(top = 8.dp, bottom = 8.dp)) + } + } + + item { + DownloadLanguagesHeader( + stringResource( + id = R.string.download_language_header_preference, + ), + ) + } } - item { + allLanguagesItemNotDownloaded?.let { + item { + LanguageItemPreference( + item = allLanguagesItemNotDownloaded, + onItemClick = onItemClick, + ) + } + } + + if (pivotLanguage != null && + pivotLanguage.languageModel.status == ModelState.NOT_DOWNLOADED + ) { + item { + LanguageItemPreference( + item = pivotLanguage, + onItemClick = onItemClick, + ) + } + } + + items(deleteInProgressItems) { item: DownloadLanguageItemPreference -> LanguageItemPreference( - item = allLanguagesItem, + item = item, onItemClick = onItemClick, ) - Divider(Modifier.padding(top = 8.dp, bottom = 8.dp)) } items(notDownloadedItems) { item: DownloadLanguageItemPreference -> @@ -153,14 +244,26 @@ fun DownloadLanguagesPreference( } } +private fun shouldShowDownloadLanguagesHeader( + allLanguagesItemNotDownloaded: DownloadLanguageItemPreference?, + deleteInProgressItems: List, + notDownloadedItems: List, +): Boolean { + return allLanguagesItemNotDownloaded != null || + deleteInProgressItems.isNotEmpty() || + notDownloadedItems.isNotEmpty() +} + @Composable private fun DownloadLanguagesHeader(title: String) { Text( text = title, modifier = Modifier .fillMaxWidth() - .padding(start = 72.dp, end = 16.dp, top = 8.dp) - .semantics { heading() }, + .padding(start = 72.dp, end = 16.dp) + .semantics { heading() } + .defaultMinSize(minHeight = 36.dp) + .wrapContentHeight(), color = FirefoxTheme.colors.textAccent, style = FirefoxTheme.typography.headline8, ) @@ -172,16 +275,36 @@ private fun LanguageItemPreference( onItemClick: (DownloadLanguageItemPreference) -> Unit, ) { val description: String = - if (item.state.type == DownloadLanguageItemTypePreference.PivotLanguage) { + if ( + item.type == DownloadLanguageItemTypePreference.PivotLanguage && + item.languageModel.status == ModelState.DOWNLOADED && + !item.enabled + ) { stringResource(id = R.string.download_languages_default_system_language_require_preference) } else { - item.languageModel.size.toMegabyteOrKilobyteString() + var size = 0L + item.languageModel.size?.let { size = it } + size.toMegabyteOrKilobyteString() } - val contentDescription = - downloadLanguageItemContentDescriptionPreference(item = item, itemDescription = description) + val label = if (item.type == DownloadLanguageItemTypePreference.AllLanguages) { + if (item.languageModel.status == ModelState.NOT_DOWNLOADED) { + stringResource(id = R.string.download_language_all_languages_item_preference) + } else { + stringResource(id = R.string.download_language_all_languages_item_preference_to_delete) + } + } else { + item.languageModel.language?.localizedDisplayName + } - item.languageModel.language?.localizedDisplayName?.let { + val contentDescription = + downloadLanguageItemContentDescriptionPreference( + item = item, + label = label, + itemDescription = description, + ) + + label?.let { TextListItemInlineDescription( label = it, modifier = Modifier @@ -190,25 +313,33 @@ private fun LanguageItemPreference( ) .clearAndSetSemantics { role = Role.Button - this.contentDescription = - contentDescription - }, + this.contentDescription = contentDescription + } + .defaultMinSize(minHeight = 56.dp) + .wrapContentHeight(), description = description, - enabled = item.state.status != DownloadLanguageItemStatusPreference.DownloadInProgress, + enabled = item.enabled, onClick = { onItemClick(item) }, - icon = { IconDownloadLanguageItemPreference(item = item) }, + icon = { + IconDownloadLanguageItemPreference( + item = item, + ) + }, ) } } @Composable -private fun DownloadLanguagesHeaderPreference() { +private fun DownloadLanguagesHeaderPreference( + learnMoreUrl: String, + onLearnMoreClicked: () -> Unit, +) { val learnMoreText = stringResource(id = R.string.download_languages_header_learn_more_preference) val learnMoreState = LinkTextState( text = learnMoreText, - url = "www.mozilla.com", - onClick = {}, + url = learnMoreUrl, + onClick = { onLearnMoreClicked() }, ) Box( modifier = Modifier @@ -232,52 +363,53 @@ private fun DownloadLanguagesHeaderPreference() { linkTextDecoration = TextDecoration.Underline, ) } + + Spacer(modifier = Modifier.size(8.dp)) } @Composable private fun downloadLanguageItemContentDescriptionPreference( item: DownloadLanguageItemPreference, + label: String? = null, itemDescription: String, ): String { val contentDescription: String - when (item.state.status) { - DownloadLanguageItemStatusPreference.Downloaded -> { + when (item.languageModel.status) { + ModelState.DOWNLOADED -> { contentDescription = - item.languageModel.language?.localizedDisplayName + " " + itemDescription + stringResource( + "$label $itemDescription" + stringResource( id = R.string.download_languages_item_content_description_downloaded_state, ) } - DownloadLanguageItemStatusPreference.NotDownloaded -> { + ModelState.NOT_DOWNLOADED -> { contentDescription = - item.languageModel.language?.localizedDisplayName + " " + itemDescription + " " + stringResource( + "$label $itemDescription " + stringResource( id = R.string.download_languages_item_content_description_not_downloaded_state, ) } - DownloadLanguageItemStatusPreference.DownloadInProgress -> { + ModelState.DOWNLOAD_IN_PROGRESS, ModelState.DELETION_IN_PROGRESS -> { contentDescription = - item.languageModel.language?.localizedDisplayName + " " + itemDescription + " " + stringResource( + "$label $itemDescription " + stringResource( id = R.string.download_languages_item_content_description_in_progress_state, ) } - - DownloadLanguageItemStatusPreference.Selected -> { - contentDescription = - item.languageModel.language?.localizedDisplayName + " " + itemDescription + stringResource( - id = R.string.download_languages_item_content_description_selected_state, - ) - } } return contentDescription } @Composable -private fun IconDownloadLanguageItemPreference(item: DownloadLanguageItemPreference) { - when (item.state.status) { - DownloadLanguageItemStatusPreference.Downloaded -> { - if (item.state.type != DownloadLanguageItemTypePreference.PivotLanguage) { +private fun IconDownloadLanguageItemPreference( + item: DownloadLanguageItemPreference, +) { + when (item.languageModel.status) { + ModelState.DOWNLOADED -> { + if ( + item.type != DownloadLanguageItemTypePreference.PivotLanguage || + item.enabled + ) { Icon( painter = painterResource( id = R.drawable.ic_delete, @@ -288,27 +420,21 @@ private fun IconDownloadLanguageItemPreference(item: DownloadLanguageItemPrefere } } - DownloadLanguageItemStatusPreference.NotDownloaded -> { - if (item.state.type != DownloadLanguageItemTypePreference.PivotLanguage) { - Icon( - painter = painterResource( - id = R.drawable.ic_download, - ), - contentDescription = null, - tint = FirefoxTheme.colors.iconPrimary, - ) - } + ModelState.NOT_DOWNLOADED -> { + Icon( + painter = painterResource( + id = R.drawable.ic_download, + ), + contentDescription = null, + tint = FirefoxTheme.colors.iconPrimary, + ) } - DownloadLanguageItemStatusPreference.DownloadInProgress -> { - if (item.state.type != DownloadLanguageItemTypePreference.PivotLanguage) { - DownloadIconIndicator( - icon = painterResource(id = R.drawable.mozac_ic_sync_24), - ) - } + ModelState.DOWNLOAD_IN_PROGRESS, ModelState.DELETION_IN_PROGRESS -> { + DownloadIconIndicator( + icon = painterResource(id = R.drawable.mozac_ic_sync_24), + ) } - - DownloadLanguageItemStatusPreference.Selected -> {} } } @@ -368,6 +494,7 @@ private fun TextListItemInlineDescription( } IconButton( onClick = { onClick.invoke() }, + enabled = enabled, modifier = Modifier .padding(end = 16.dp) .size(24.dp) @@ -384,82 +511,51 @@ internal fun getLanguageListPreference(): List { return mutableListOf().apply { add( DownloadLanguageItemPreference( - languageModel = TranslationsController.RuntimeTranslation.LanguageModel( - TranslationsController.Language( - Locale.FRENCH.toLanguageTag(), - Locale.FRENCH.displayLanguage, - ), - false, - 30000000, - ), - state = DownloadLanguageItemStatePreference( - type = DownloadLanguageItemTypePreference.GeneralLanguage, - status = DownloadLanguageItemStatusPreference.Downloaded, + languageModel = LanguageModel( + language = Language(Locale.FRENCH.toLanguageTag(), Locale.FRENCH.displayName), + status = ModelState.DOWNLOADED, + size = 100L, ), + type = DownloadLanguageItemTypePreference.GeneralLanguage, ), ) add( DownloadLanguageItemPreference( - languageModel = TranslationsController.RuntimeTranslation.LanguageModel( - TranslationsController.Language( - Locale.GERMAN.toLanguageTag(), - Locale.GERMAN.displayLanguage, - ), - false, - 30000000, - ), - state = DownloadLanguageItemStatePreference( - type = DownloadLanguageItemTypePreference.GeneralLanguage, - status = DownloadLanguageItemStatusPreference.NotDownloaded, + languageModel = LanguageModel( + language = Language(Locale.GERMAN.toLanguageTag(), Locale.GERMAN.displayName), + status = ModelState.NOT_DOWNLOADED, + size = 100L, ), + type = DownloadLanguageItemTypePreference.GeneralLanguage, ), ) add( DownloadLanguageItemPreference( - languageModel = TranslationsController.RuntimeTranslation.LanguageModel( - TranslationsController.Language( - Locale.ITALIAN.toLanguageTag(), - Locale.ITALIAN.displayLanguage, - ), - false, - 30000000, - ), - state = DownloadLanguageItemStatePreference( - type = DownloadLanguageItemTypePreference.GeneralLanguage, - status = DownloadLanguageItemStatusPreference.DownloadInProgress, + languageModel = LanguageModel( + language = Language(Locale.ITALIAN.toLanguageTag(), Locale.ITALIAN.displayName), + status = ModelState.DOWNLOAD_IN_PROGRESS, + size = 100L, ), + type = DownloadLanguageItemTypePreference.GeneralLanguage, ), ) add( DownloadLanguageItemPreference( - languageModel = TranslationsController.RuntimeTranslation.LanguageModel( - TranslationsController.Language( - Locale.ENGLISH.toLanguageTag(), - Locale.ENGLISH.displayLanguage, - ), - true, - 30000000, - ), - state = DownloadLanguageItemStatePreference( - type = DownloadLanguageItemTypePreference.PivotLanguage, - status = DownloadLanguageItemStatusPreference.Selected, + languageModel = LanguageModel( + language = Language(Locale.ENGLISH.toLanguageTag(), Locale.ENGLISH.displayName), + status = ModelState.DELETION_IN_PROGRESS, + size = 100L, ), + type = DownloadLanguageItemTypePreference.GeneralLanguage, ), ) add( DownloadLanguageItemPreference( - languageModel = TranslationsController.RuntimeTranslation.LanguageModel( - TranslationsController.Language( - stringResource(id = R.string.download_language_all_languages_item_preference), - stringResource(id = R.string.download_language_all_languages_item_preference), - ), - true, - 90000000, - ), - state = DownloadLanguageItemStatePreference( - type = DownloadLanguageItemTypePreference.AllLanguages, - status = DownloadLanguageItemStatusPreference.NotDownloaded, + languageModel = LanguageModel( + status = ModelState.NOT_DOWNLOADED, + size = 100L, ), + type = DownloadLanguageItemTypePreference.AllLanguages, ), ) } @@ -471,6 +567,8 @@ private fun DownloadLanguagesPreferencePreview() { FirefoxTheme { DownloadLanguagesPreference( downloadLanguageItemPreferences = getLanguageListPreference(), + learnMoreUrl = "https://www.mozilla.org", + onLearnMoreClicked = {}, onItemClick = {}, ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguagesPreferenceFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguagesPreferenceFragment.kt index c24533210b45..52d7578a007e 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguagesPreferenceFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/DownloadLanguagesPreferenceFragment.kt @@ -8,15 +8,30 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.runtime.Composable import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment import androidx.navigation.findNavController +import mozilla.components.browser.state.action.TranslationsAction +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.translate.LanguageModel +import mozilla.components.concept.engine.translate.ModelManagementOptions +import mozilla.components.concept.engine.translate.ModelOperation +import mozilla.components.concept.engine.translate.ModelState +import mozilla.components.concept.engine.translate.OperationLevel +import mozilla.components.lib.state.ext.observeAsComposableState import mozilla.components.support.base.feature.ViewBoundFeatureWrapper +import mozilla.components.support.locale.LocaleManager +import org.mozilla.fenix.BrowserDirection +import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar +import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.theme.FirefoxTheme +import java.util.Locale /** * A fragment displaying Download Languages screen. @@ -25,6 +40,7 @@ class DownloadLanguagesPreferenceFragment : Fragment() { private val downloadLanguagesFeature = ViewBoundFeatureWrapper() private var isDataSaverEnabledAndWifiDisabled = false + private val browserStore: BrowserStore by lazy { requireComponents.core.store } override fun onResume() { super.onResume() @@ -38,24 +54,48 @@ class DownloadLanguagesPreferenceFragment : Fragment() { ): View = ComposeView(requireContext()).apply { setContent { FirefoxTheme { + val learnMoreUrl = SupportUtils.getSumoURLForTopic( + requireContext(), + SupportUtils.SumoTopic.TRANSLATIONS, + ) + val downloadLanguageItemsPreference = getDownloadLanguageItemsPreference() + DownloadLanguagesPreference( - downloadLanguageItemPreferences = getLanguageListPreference(), + downloadLanguageItemPreferences = downloadLanguageItemsPreference, + learnMoreUrl = learnMoreUrl, + onLearnMoreClicked = { openBrowserAndLoad(learnMoreUrl) }, onItemClick = { downloadLanguageItemPreference -> - if (downloadLanguageItemPreference.state.status == - DownloadLanguageItemStatusPreference.Downloaded || + if (downloadLanguageItemPreference.languageModel.status == + ModelState.DOWNLOADED || shouldShowPrefDownloadLanguageFileDialog( downloadLanguageItemPreference, ) ) { - downloadLanguageItemPreference.languageModel.language?.localizedDisplayName?.let { - findNavController().navigate( - DownloadLanguagesPreferenceFragmentDirections - .actionDownloadLanguagesPreferenceToDownloadLanguagesDialogPreference( - downloadLanguageItemPreference.state, - it, - downloadLanguageItemPreference.languageModel.size, - ), + var size = 0L + downloadLanguageItemPreference.languageModel.size?.let { size = it } + + findNavController().navigate( + DownloadLanguagesPreferenceFragmentDirections + .actionDownloadLanguagesPreferenceToDownloadLanguagesDialogPreference( + modelState = downloadLanguageItemPreference.languageModel.status, + itemType = downloadLanguageItemPreference.type, + languageCode = downloadLanguageItemPreference.languageModel.language?.code, + languageDisplayName = + downloadLanguageItemPreference.languageModel.language?.localizedDisplayName, + modelSize = size, + ), + ) + } else { + if ( + downloadLanguageItemPreference.type == + DownloadLanguageItemTypePreference.AllLanguages + ) { + deleteOrDownloadAllLanguagesModel( + allLanguagesItemPreference = downloadLanguageItemPreference, + allLanguages = downloadLanguageItemsPreference, ) + } else { + deleteOrDownloadModel(downloadLanguageItemPreference) } } }, @@ -64,15 +104,6 @@ class DownloadLanguagesPreferenceFragment : Fragment() { } } - private fun shouldShowPrefDownloadLanguageFileDialog( - downloadLanguageItemPreference: DownloadLanguageItemPreference, - ) = - ( - downloadLanguageItemPreference.state.status == DownloadLanguageItemStatusPreference.NotDownloaded && - isDataSaverEnabledAndWifiDisabled && - !requireContext().settings().ignoreTranslationsDataSaverWarning - ) - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) downloadLanguagesFeature.set( @@ -87,4 +118,175 @@ class DownloadLanguagesPreferenceFragment : Fragment() { view = view, ) } + + private fun deleteOrDownloadModel(downloadLanguageItemPreference: DownloadLanguageItemPreference) { + val options = ModelManagementOptions( + languageToManage = downloadLanguageItemPreference.languageModel.language?.code, + operation = if ( + downloadLanguageItemPreference.languageModel.status == + ModelState.NOT_DOWNLOADED + ) { + ModelOperation.DOWNLOAD + } else { + ModelOperation.DELETE + }, + operationLevel = OperationLevel.LANGUAGE, + ) + browserStore.dispatch( + TranslationsAction.ManageLanguageModelsAction( + options = options, + ), + ) + } + + private fun deleteOrDownloadAllLanguagesModel( + allLanguagesItemPreference: DownloadLanguageItemPreference, + allLanguages: List, + ) { + if (allLanguagesItemPreference.languageModel.status == ModelState.DOWNLOADED) { + val downloadedItems = allLanguages.filter { + it.languageModel.status == ModelState.DOWNLOADED && + it.type == DownloadLanguageItemTypePreference.GeneralLanguage + } + + for (downloadedItem in downloadedItems) { + if (!downloadedItem.languageModel.language?.code.equals(Locale.ENGLISH.language)) { + deleteOrDownloadModel(downloadedItem) + } + } + } else { + if (allLanguagesItemPreference.languageModel.status == ModelState.NOT_DOWNLOADED) { + val notDownloadedItems = allLanguages.filter { + it.languageModel.status == ModelState.NOT_DOWNLOADED && + it.type == DownloadLanguageItemTypePreference.GeneralLanguage + } + for (notDownloadedItem in notDownloadedItems) { + deleteOrDownloadModel(notDownloadedItem) + } + } + } + } + + private fun openBrowserAndLoad(learnMoreUrl: String) { + (requireActivity() as HomeActivity).openToBrowserAndLoad( + searchTermOrURL = learnMoreUrl, + newTab = true, + from = BrowserDirection.FromDownloadLanguagesPreferenceFragment, + ) + } + + @Composable + private fun getDownloadLanguageItemsPreference(): List { + val languageModels = browserStore.observeAsComposableState { state -> + state.translationEngine.languageModels + }.value?.toMutableList() + val languageItemPreferenceList = mutableListOf() + val appLocale = browserStore.state.locale ?: LocaleManager.getSystemDefault() + + languageModels?.let { + var allLanguagesSizeNotDownloaded = 0L + var allLanguagesSizeDownloaded = 0L + + for (languageModel in languageModels) { + var size = 0L + languageModel.size?.let { size = it } + + if (languageModel.status == ModelState.NOT_DOWNLOADED) { + allLanguagesSizeNotDownloaded += size + } + + if ( + languageModel.status == ModelState.DOWNLOADED && + !languageModel.language?.code.equals( + Locale.ENGLISH.language, + ) + ) { + allLanguagesSizeDownloaded += size + } + } + + addAllLanguagesNotDownloaded( + allLanguagesSizeNotDownloaded, + languageItemPreferenceList, + ) + + addAllLanguagesDownloaded( + allLanguagesSizeDownloaded, + languageItemPreferenceList, + ) + + val iterator = languageModels.iterator() + while (iterator.hasNext()) { + val languageModel = iterator.next() + if (!appLocale.language.equals(Locale.ENGLISH.language) && languageModel.language?.code.equals( + Locale.ENGLISH.language, + ) + ) { + languageItemPreferenceList.add( + DownloadLanguageItemPreference( + languageModel = languageModel, + type = DownloadLanguageItemTypePreference.PivotLanguage, + enabled = allLanguagesSizeDownloaded == 0L, + ), + ) + iterator.remove() + } + + if (!languageModel.language?.code.equals(Locale.ENGLISH.language)) { + languageItemPreferenceList.add( + DownloadLanguageItemPreference( + languageModel = languageModel, + type = DownloadLanguageItemTypePreference.GeneralLanguage, + enabled = languageModel.status == ModelState.DOWNLOADED || + languageModel.status == ModelState.NOT_DOWNLOADED, + ), + ) + } + } + } + return languageItemPreferenceList + } + + private fun addAllLanguagesNotDownloaded( + allLanguageSizeNotDownloaded: Long, + languageItemPreferenceList: MutableList, + ) { + if (allLanguageSizeNotDownloaded != 0L) { + languageItemPreferenceList.add( + DownloadLanguageItemPreference( + languageModel = LanguageModel( + status = ModelState.NOT_DOWNLOADED, + size = allLanguageSizeNotDownloaded, + ), + type = DownloadLanguageItemTypePreference.AllLanguages, + ), + ) + } + } + + private fun addAllLanguagesDownloaded( + allLanguagesSizeDownloaded: Long, + languageItemPreferenceList: MutableList, + ) { + if (allLanguagesSizeDownloaded != 0L) { + languageItemPreferenceList.add( + DownloadLanguageItemPreference( + languageModel = LanguageModel( + status = ModelState.DOWNLOADED, + size = allLanguagesSizeDownloaded, + ), + type = DownloadLanguageItemTypePreference.AllLanguages, + ), + ) + } + } + + private fun shouldShowPrefDownloadLanguageFileDialog( + downloadLanguageItemPreference: DownloadLanguageItemPreference, + ) = + ( + downloadLanguageItemPreference.languageModel.status == ModelState.NOT_DOWNLOADED && + isDataSaverEnabledAndWifiDisabled && + !requireContext().settings().ignoreTranslationsDataSaverWarning + ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/LanguageDialogPreferenceFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/LanguageDialogPreferenceFragment.kt index cf3fde6a2cf9..36401ce8378a 100644 --- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/LanguageDialogPreferenceFragment.kt +++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/translations/preferences/downloadlanguages/LanguageDialogPreferenceFragment.kt @@ -17,14 +17,24 @@ import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.DialogFragment import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs +import mozilla.components.browser.state.action.TranslationsAction +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.concept.engine.translate.ModelManagementOptions +import mozilla.components.concept.engine.translate.ModelOperation +import mozilla.components.concept.engine.translate.ModelState +import mozilla.components.concept.engine.translate.OperationLevel +import mozilla.components.lib.state.ext.observeAsComposableState +import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.theme.FirefoxTheme +import java.util.Locale /** * A fragment dialog displays a delete or download language. */ class LanguageDialogPreferenceFragment : DialogFragment() { private val args by navArgs() + private val browserStore: BrowserStore by lazy { requireComponents.core.store } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = super.onCreateDialog(savedInstanceState).apply { @@ -39,10 +49,10 @@ class LanguageDialogPreferenceFragment : DialogFragment() { savedInstanceState: Bundle?, ): View { val view = ComposeView(requireContext()) - if (args.downloadLanguageItemStatePreference.status == DownloadLanguageItemStatusPreference.Downloaded) { + if (args.modelState == ModelState.DOWNLOADED) { setPrefDeleteLanguageFileDialog(view) } else { - if (args.downloadLanguageItemStatePreference.status == DownloadLanguageItemStatusPreference.NotDownloaded) { + if (args.modelState == ModelState.NOT_DOWNLOADED) { setDownloadLanguageFileDialog(view) } } @@ -54,13 +64,43 @@ class LanguageDialogPreferenceFragment : DialogFragment() { composeView.apply { setContent { FirefoxTheme { + val languageModels = browserStore.observeAsComposableState { state -> + state.translationEngine.languageModels + }.value?.toMutableList() + DeleteLanguageFileDialog( - language = args.languageNamePreference, + language = args.languageDisplayName, isAllLanguagesItemType = - args.downloadLanguageItemStatePreference.type == + args.itemType == DownloadLanguageItemTypePreference.AllLanguages, - fileSize = args.itemFileSizePreference, - onConfirmDelete = { findNavController().popBackStack() }, + fileSize = args.modelSize, + onConfirmDelete = { + if (args.itemType == DownloadLanguageItemTypePreference.AllLanguages) { + languageModels?.let { + val downloadedItems = it.filter { languageModel -> + languageModel.status == ModelState.DOWNLOADED + } + + for (downloadedItem in downloadedItems) { + if (!downloadedItem.language?.code.equals( + Locale.ENGLISH.language, + ) + ) { + deleteOrDownloadModel( + modelOperation = ModelOperation.DELETE, + languageToManage = downloadedItem.language?.code, + ) + } + } + } + } else { + deleteOrDownloadModel( + modelOperation = ModelOperation.DELETE, + languageToManage = args.languageCode, + ) + } + findNavController().popBackStack() + }, onCancel = { findNavController().popBackStack() }, ) } @@ -73,20 +113,44 @@ class LanguageDialogPreferenceFragment : DialogFragment() { setContent { FirefoxTheme { var checkBoxEnabled by remember { mutableStateOf(false) } + val languageModels = browserStore.observeAsComposableState { state -> + state.translationEngine.languageModels + }.value?.toMutableList() + DownloadLanguageFileDialog( - downloadLanguageDialogType = if (args.downloadLanguageItemStatePreference.type == + downloadLanguageDialogType = if (args.itemType == DownloadLanguageItemTypePreference.AllLanguages ) { DownloadLanguageFileDialogType.AllLanguages } else { DownloadLanguageFileDialogType.Default }, - fileSize = args.itemFileSizePreference, + fileSize = args.modelSize, isCheckBoxEnabled = checkBoxEnabled, onSavingModeStateChange = { checkBoxEnabled = it }, onConfirmDownload = { requireContext().settings().ignoreTranslationsDataSaverWarning = checkBoxEnabled + + if (args.itemType == DownloadLanguageItemTypePreference.AllLanguages) { + languageModels?.let { + val downloadedItems = it.filter { languageModel -> + languageModel.status == ModelState.NOT_DOWNLOADED + } + for (downloadedItem in downloadedItems) { + deleteOrDownloadModel( + modelOperation = ModelOperation.DOWNLOAD, + languageToManage = downloadedItem.language?.code, + ) + } + } + } else { + deleteOrDownloadModel( + modelOperation = ModelOperation.DOWNLOAD, + languageToManage = args.languageCode, + ) + } + findNavController().popBackStack() }, onCancel = { findNavController().popBackStack() }, @@ -95,4 +159,17 @@ class LanguageDialogPreferenceFragment : DialogFragment() { } } } + + private fun deleteOrDownloadModel(modelOperation: ModelOperation, languageToManage: String?) { + val options = ModelManagementOptions( + languageToManage = languageToManage, + operation = modelOperation, + operationLevel = OperationLevel.LANGUAGE, + ) + browserStore.dispatch( + TranslationsAction.ManageLanguageModelsAction( + options = options, + ), + ) + } } diff --git a/mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml b/mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml index 7d14877e5c0f..9b62d8d2e729 100644 --- a/mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml +++ b/mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml @@ -1519,16 +1519,22 @@ android:id="@+id/downloadLanguagesDialogPreferenceFragment" android:name="org.mozilla.fenix.translations.preferences.downloadlanguages.LanguageDialogPreferenceFragment"> + android:name="modelState" + app:argType="mozilla.components.concept.engine.translate.ModelState" /> + android:name="itemType" + app:argType="org.mozilla.fenix.translations.preferences.downloadlanguages.DownloadLanguageItemTypePreference" /> + android:name="languageCode" + app:nullable="true" + app:argType="string" /> + + diff --git a/mobile/android/fenix/app/src/main/res/values/strings.xml b/mobile/android/fenix/app/src/main/res/values/strings.xml index 3b3e7708a963..b8d4a9c44fb4 100644 --- a/mobile/android/fenix/app/src/main/res/values/strings.xml +++ b/mobile/android/fenix/app/src/main/res/values/strings.xml @@ -2515,8 +2515,10 @@ %1$s (%2$s) Download Languages - + All languages + + Delete all languages Delete @@ -2524,7 +2526,7 @@ Download - Selected + Selected