Bug 1862293 - Translations UI Integration Integrate Download Languages - Global Settings r=android-reviewers,ohall

Differential Revision: https://phabricator.services.mozilla.com/D211357
This commit is contained in:
iorgamgabriel 2024-06-04 10:09:39 +00:00
parent 9eaeb8ac81
commit 0067c27e35
10 changed files with 607 additions and 230 deletions

View file

@ -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),
}

View file

@ -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

View file

@ -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) {

View file

@ -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,

View file

@ -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,
}

View file

@ -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<DownloadLanguageItemPreference>,
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<DownloadLanguageItemPreference>,
notDownloadedItems: List<DownloadLanguageItemPreference>,
): 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<DownloadLanguageItemPreference> {
return mutableListOf<DownloadLanguageItemPreference>().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 = {},
)
}

View file

@ -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<DownloadLanguagesFeature>()
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<DownloadLanguageItemPreference>,
) {
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<DownloadLanguageItemPreference> {
val languageModels = browserStore.observeAsComposableState { state ->
state.translationEngine.languageModels
}.value?.toMutableList()
val languageItemPreferenceList = mutableListOf<DownloadLanguageItemPreference>()
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<DownloadLanguageItemPreference>,
) {
if (allLanguageSizeNotDownloaded != 0L) {
languageItemPreferenceList.add(
DownloadLanguageItemPreference(
languageModel = LanguageModel(
status = ModelState.NOT_DOWNLOADED,
size = allLanguageSizeNotDownloaded,
),
type = DownloadLanguageItemTypePreference.AllLanguages,
),
)
}
}
private fun addAllLanguagesDownloaded(
allLanguagesSizeDownloaded: Long,
languageItemPreferenceList: MutableList<DownloadLanguageItemPreference>,
) {
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
)
}

View file

@ -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<LanguageDialogPreferenceFragmentArgs>()
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,
),
)
}
}

View file

@ -1519,16 +1519,22 @@
android:id="@+id/downloadLanguagesDialogPreferenceFragment"
android:name="org.mozilla.fenix.translations.preferences.downloadlanguages.LanguageDialogPreferenceFragment">
<argument
android:name="downloadLanguageItemStatePreference"
app:argType="org.mozilla.fenix.translations.preferences.downloadlanguages.DownloadLanguageItemStatePreference" />
android:name="modelState"
app:argType="mozilla.components.concept.engine.translate.ModelState" />
<argument
android:name="languageNamePreference"
app:argType="string"
app:nullable="false" />
android:name="itemType"
app:argType="org.mozilla.fenix.translations.preferences.downloadlanguages.DownloadLanguageItemTypePreference" />
<argument
android:name="itemFileSizePreference"
app:argType="long"
app:nullable="false" />
android:name="languageCode"
app:nullable="true"
app:argType="string" />
<argument
android:name="languageDisplayName"
app:nullable="true"
app:argType="string" />
<argument
android:name="modelSize"
app:argType="long" />
</dialog>
</navigation>

View file

@ -2515,8 +2515,10 @@
<string name="download_languages_language_item_preference">%1$s (%2$s)</string>
<!-- The subhead of the download language preference screen will appear above the items that were not downloaded. -->
<string name="download_language_header_preference">Download Languages</string>
<!-- All languages list item. When the user presses this item, they can download or delete all languages. -->
<!-- All languages list item. When the user presses this item, they can download all languages. -->
<string name="download_language_all_languages_item_preference">All languages</string>
<!-- All languages list item. When the user presses this item, they can delete all languages that were downloaded. -->
<string name="download_language_all_languages_item_preference_to_delete">Delete all languages</string>
<!-- Content description (not visible, for screen readers etc.): For a language list item that was downloaded, the user can now delete it. -->
<string name="download_languages_item_content_description_downloaded_state">Delete</string>
<!-- Content description (not visible, for screen readers etc.): For a language list item, downloading is in progress. -->
@ -2524,7 +2526,7 @@
<!-- Content description (not visible, for screen readers etc.): For a language list item that was not downloaded. -->
<string name="download_languages_item_content_description_not_downloaded_state">Download</string>
<!-- Content description (not visible, for screen readers etc.): For a language list item that is selected. -->
<string name="download_languages_item_content_description_selected_state">Selected</string>
<string name="download_languages_item_content_description_selected_state" moz:removedIn="127" tools:ignore="UnusedResources">Selected</string>
<!-- Title for the dialog used by the translations feature to confirm deleting a language.
The dialog will be presented when the user requests deletion of a language.