|
|
|
@ -5,6 +5,7 @@
|
|
|
|
|
package org.mozilla.fenix.translations
|
|
|
|
|
|
|
|
|
|
import android.content.res.Configuration
|
|
|
|
|
import androidx.compose.foundation.clickable
|
|
|
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
|
|
|
import androidx.compose.foundation.layout.Box
|
|
|
|
|
import androidx.compose.foundation.layout.Column
|
|
|
|
@ -13,6 +14,7 @@ import androidx.compose.foundation.layout.Spacer
|
|
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
|
|
import androidx.compose.foundation.layout.height
|
|
|
|
|
import androidx.compose.foundation.layout.padding
|
|
|
|
|
import androidx.compose.foundation.layout.requiredSizeIn
|
|
|
|
|
import androidx.compose.foundation.layout.size
|
|
|
|
|
import androidx.compose.foundation.layout.width
|
|
|
|
|
import androidx.compose.foundation.layout.wrapContentSize
|
|
|
|
@ -30,19 +32,27 @@ import androidx.compose.runtime.setValue
|
|
|
|
|
import androidx.compose.runtime.snapshotFlow
|
|
|
|
|
import androidx.compose.ui.Alignment
|
|
|
|
|
import androidx.compose.ui.Modifier
|
|
|
|
|
import androidx.compose.ui.layout.onGloballyPositioned
|
|
|
|
|
import androidx.compose.ui.platform.LocalConfiguration
|
|
|
|
|
import androidx.compose.ui.platform.LocalDensity
|
|
|
|
|
import androidx.compose.ui.res.painterResource
|
|
|
|
|
import androidx.compose.ui.res.stringResource
|
|
|
|
|
import androidx.compose.ui.semantics.Role
|
|
|
|
|
import androidx.compose.ui.semantics.clearAndSetSemantics
|
|
|
|
|
import androidx.compose.ui.semantics.heading
|
|
|
|
|
import androidx.compose.ui.semantics.role
|
|
|
|
|
import androidx.compose.ui.semantics.semantics
|
|
|
|
|
import androidx.compose.ui.text.style.TextDecoration
|
|
|
|
|
import androidx.compose.ui.unit.DpOffset
|
|
|
|
|
import androidx.compose.ui.unit.dp
|
|
|
|
|
import mozilla.components.concept.engine.translate.Language
|
|
|
|
|
import mozilla.components.concept.engine.translate.TranslationError
|
|
|
|
|
import org.mozilla.fenix.R
|
|
|
|
|
import org.mozilla.fenix.compose.BetaLabel
|
|
|
|
|
import org.mozilla.fenix.compose.ContextualMenu
|
|
|
|
|
import org.mozilla.fenix.compose.LinkText
|
|
|
|
|
import org.mozilla.fenix.compose.LinkTextState
|
|
|
|
|
import org.mozilla.fenix.compose.MenuItem
|
|
|
|
|
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
|
|
|
|
import org.mozilla.fenix.compose.button.PrimaryButton
|
|
|
|
|
import org.mozilla.fenix.compose.button.TertiaryButton
|
|
|
|
@ -50,18 +60,35 @@ import org.mozilla.fenix.compose.button.TextButton
|
|
|
|
|
import org.mozilla.fenix.shopping.ui.ReviewQualityCheckInfoCard
|
|
|
|
|
import org.mozilla.fenix.shopping.ui.ReviewQualityCheckInfoType
|
|
|
|
|
import org.mozilla.fenix.theme.FirefoxTheme
|
|
|
|
|
import java.util.Locale
|
|
|
|
|
|
|
|
|
|
private val ICON_SIZE = 24.dp
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Firefox Translations bottom sheet dialog.
|
|
|
|
|
*
|
|
|
|
|
* @param learnMoreUrl The learn more link for translations website.
|
|
|
|
|
* @param showFirstTimeTranslation Whether translations first flow should be shown.
|
|
|
|
|
* @param translationError The type of translation errors that can occur.
|
|
|
|
|
* @param translateFromLanguages Translation menu items to be shown in the translate from dropdown.
|
|
|
|
|
* @param translateToLanguages Translation menu items are to be shown in the translate to dropdown.
|
|
|
|
|
* @param onSettingClicked Invoked when the user clicks on the settings button.
|
|
|
|
|
* @param onLearnMoreClicked Invoked when the user clicks on the "Learn More" button.
|
|
|
|
|
* @param onTranslateButtonClicked Invoked when the user clicks on the "Translate" button.
|
|
|
|
|
* @param onNotNowButtonClicked Invoked when the user clicks on the "Not Now" button.
|
|
|
|
|
*/
|
|
|
|
|
@Composable
|
|
|
|
|
@Suppress("LongParameterList")
|
|
|
|
|
fun TranslationsDialogBottomSheet(
|
|
|
|
|
learnMoreUrl: String,
|
|
|
|
|
showFirstTimeTranslation: Boolean,
|
|
|
|
|
translationError: TranslationError? = null,
|
|
|
|
|
translateFromLanguages: List<Language>,
|
|
|
|
|
translateToLanguages: List<Language>,
|
|
|
|
|
onSettingClicked: () -> Unit,
|
|
|
|
|
onLearnMoreClicked: () -> Unit,
|
|
|
|
|
onTranslateButtonClick: () -> Unit,
|
|
|
|
|
onTranslateButtonClicked: () -> Unit,
|
|
|
|
|
onNotNowButtonClicked: () -> Unit,
|
|
|
|
|
) {
|
|
|
|
|
var orientation by remember { mutableIntStateOf(Configuration.ORIENTATION_PORTRAIT) }
|
|
|
|
|
|
|
|
|
@ -80,12 +107,18 @@ fun TranslationsDialogBottomSheet(
|
|
|
|
|
.clearAndSetSemantics {},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
TranslationsDialogHeader(onSettingClicked, showFirstTimeTranslation)
|
|
|
|
|
TranslationsDialogHeader(
|
|
|
|
|
showFirstTimeTranslation = showFirstTimeTranslation,
|
|
|
|
|
onSettingClicked = onSettingClicked,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(8.dp))
|
|
|
|
|
|
|
|
|
|
if (showFirstTimeTranslation) {
|
|
|
|
|
TranslationsDialogInfoMessage(onLearnMoreClicked, learnMoreUrl)
|
|
|
|
|
TranslationsDialogInfoMessage(
|
|
|
|
|
learnMoreUrl = learnMoreUrl,
|
|
|
|
|
onLearnMoreClicked = onLearnMoreClicked,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
translationError?.let {
|
|
|
|
@ -97,11 +130,17 @@ fun TranslationsDialogBottomSheet(
|
|
|
|
|
if (translationError !is TranslationError.CouldNotLoadLanguagesError) {
|
|
|
|
|
when (orientation) {
|
|
|
|
|
Configuration.ORIENTATION_LANDSCAPE -> {
|
|
|
|
|
TranslationsDropdownsInLandscapeMode()
|
|
|
|
|
TranslationsDialogContentInLandscapeMode(
|
|
|
|
|
translateFromLanguages = translateFromLanguages,
|
|
|
|
|
translateToLanguages = translateToLanguages,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
TranslationsDropdownsInPortraitMode()
|
|
|
|
|
TranslationsDialogContentInPortraitMode(
|
|
|
|
|
translateFromLanguages = translateFromLanguages,
|
|
|
|
|
translateToLanguages = translateToLanguages,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -109,40 +148,56 @@ fun TranslationsDialogBottomSheet(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TranslationsDialogActionButtons(
|
|
|
|
|
onTranslateButtonClick = onTranslateButtonClick,
|
|
|
|
|
translationError = translationError,
|
|
|
|
|
onTranslateButtonClicked = onTranslateButtonClicked,
|
|
|
|
|
onNotNowButtonClicked = onNotNowButtonClicked,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
private fun TranslationsDropdownsInPortraitMode() {
|
|
|
|
|
private fun TranslationsDialogContentInPortraitMode(
|
|
|
|
|
translateFromLanguages: List<Language>,
|
|
|
|
|
translateToLanguages: List<Language>,
|
|
|
|
|
) {
|
|
|
|
|
Column {
|
|
|
|
|
TranslationsDropdown(
|
|
|
|
|
header = stringResource(id = R.string.translations_bottom_sheet_translate_from),
|
|
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
translateLanguages = translateFromLanguages,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(16.dp))
|
|
|
|
|
|
|
|
|
|
TranslationsDropdown(
|
|
|
|
|
header = stringResource(id = R.string.translations_bottom_sheet_translate_to),
|
|
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
translateLanguages = translateToLanguages,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
private fun TranslationsDropdownsInLandscapeMode() {
|
|
|
|
|
private fun TranslationsDialogContentInLandscapeMode(
|
|
|
|
|
translateFromLanguages: List<Language>,
|
|
|
|
|
translateToLanguages: List<Language>,
|
|
|
|
|
) {
|
|
|
|
|
Column {
|
|
|
|
|
Row {
|
|
|
|
|
TranslationsDropdown(
|
|
|
|
|
modifier = Modifier.weight(1f),
|
|
|
|
|
header = stringResource(id = R.string.translations_bottom_sheet_translate_from),
|
|
|
|
|
modifier = Modifier.weight(1f),
|
|
|
|
|
isInLandscapeMode = true,
|
|
|
|
|
translateLanguages = translateFromLanguages,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.width(16.dp))
|
|
|
|
|
|
|
|
|
|
TranslationsDropdown(
|
|
|
|
|
modifier = Modifier.weight(1f),
|
|
|
|
|
header = stringResource(id = R.string.translations_bottom_sheet_translate_to),
|
|
|
|
|
modifier = Modifier.weight(1f),
|
|
|
|
|
isInLandscapeMode = true,
|
|
|
|
|
translateLanguages = translateToLanguages,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -150,8 +205,8 @@ private fun TranslationsDropdownsInLandscapeMode() {
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
private fun TranslationsDialogHeader(
|
|
|
|
|
onSettingClicked: () -> Unit,
|
|
|
|
|
showFirstTimeTranslation: Boolean,
|
|
|
|
|
onSettingClicked: () -> Unit,
|
|
|
|
|
) {
|
|
|
|
|
val title: String = if (showFirstTimeTranslation) {
|
|
|
|
|
stringResource(
|
|
|
|
@ -236,11 +291,12 @@ private fun TranslationErrorWarning(translationError: TranslationError) {
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
private fun TranslationsDialogInfoMessage(
|
|
|
|
|
onLearnMoreClicked: () -> Unit,
|
|
|
|
|
learnMoreUrl: String,
|
|
|
|
|
onLearnMoreClicked: () -> Unit,
|
|
|
|
|
) {
|
|
|
|
|
val learnMoreText =
|
|
|
|
|
stringResource(id = R.string.translations_bottom_sheet_info_message_learn_more)
|
|
|
|
|
|
|
|
|
|
val learnMoreState = LinkTextState(
|
|
|
|
|
text = learnMoreText,
|
|
|
|
|
url = learnMoreUrl,
|
|
|
|
@ -265,40 +321,119 @@ private fun TranslationsDialogInfoMessage(
|
|
|
|
|
@Composable
|
|
|
|
|
private fun TranslationsDropdown(
|
|
|
|
|
header: String,
|
|
|
|
|
translateLanguages: List<Language>,
|
|
|
|
|
modifier: Modifier = Modifier,
|
|
|
|
|
isInLandscapeMode: Boolean = false,
|
|
|
|
|
) {
|
|
|
|
|
val density = LocalDensity.current
|
|
|
|
|
|
|
|
|
|
var expanded by remember { mutableStateOf(false) }
|
|
|
|
|
|
|
|
|
|
var selectedLanguage by remember {
|
|
|
|
|
mutableStateOf(
|
|
|
|
|
translateLanguages.last().localizedDisplayName,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var contextMenuWidthDp by remember {
|
|
|
|
|
mutableStateOf(0.dp)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Column(
|
|
|
|
|
modifier = modifier
|
|
|
|
|
.clickable {
|
|
|
|
|
expanded = true
|
|
|
|
|
}
|
|
|
|
|
.semantics { role = Role.DropdownList },
|
|
|
|
|
) {
|
|
|
|
|
Column(modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
|
|
|
|
Text(
|
|
|
|
|
text = header,
|
|
|
|
|
modifier = Modifier.wrapContentSize(),
|
|
|
|
|
color = FirefoxTheme.colors.textPrimary,
|
|
|
|
|
style = FirefoxTheme.typography.caption,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(4.dp))
|
|
|
|
|
|
|
|
|
|
Row {
|
|
|
|
|
selectedLanguage?.let {
|
|
|
|
|
Text(
|
|
|
|
|
text = "English",
|
|
|
|
|
text = it,
|
|
|
|
|
modifier = Modifier.weight(1f),
|
|
|
|
|
color = FirefoxTheme.colors.textPrimary,
|
|
|
|
|
style = FirefoxTheme.typography.subtitle1,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.width(10.dp))
|
|
|
|
|
|
|
|
|
|
Box {
|
|
|
|
|
Icon(
|
|
|
|
|
painter = painterResource(id = R.drawable.mozac_ic_dropdown_arrow),
|
|
|
|
|
contentDescription = null,
|
|
|
|
|
tint = FirefoxTheme.colors.iconPrimary,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
ContextualMenu(
|
|
|
|
|
showMenu = expanded,
|
|
|
|
|
onDismissRequest = {
|
|
|
|
|
expanded = false
|
|
|
|
|
},
|
|
|
|
|
menuItems = getContextMenuItems(translateLanguages = translateLanguages) {
|
|
|
|
|
expanded = false
|
|
|
|
|
selectedLanguage = it.localizedDisplayName
|
|
|
|
|
},
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
.onGloballyPositioned { coordinates ->
|
|
|
|
|
contextMenuWidthDp = with(density) {
|
|
|
|
|
coordinates.size.width.toDp()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.requiredSizeIn(maxHeight = 200.dp),
|
|
|
|
|
offset = if (isInLandscapeMode) {
|
|
|
|
|
DpOffset(
|
|
|
|
|
-contextMenuWidthDp + ICON_SIZE,
|
|
|
|
|
-ICON_SIZE,
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
DpOffset(
|
|
|
|
|
0.dp,
|
|
|
|
|
-ICON_SIZE,
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Divider(color = FirefoxTheme.colors.formDefault)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun getContextMenuItems(
|
|
|
|
|
translateLanguages: List<Language>,
|
|
|
|
|
onClickItem: (Language) -> Unit,
|
|
|
|
|
): List<MenuItem> {
|
|
|
|
|
val menuItems = mutableListOf<MenuItem>()
|
|
|
|
|
translateLanguages.map { item ->
|
|
|
|
|
item.localizedDisplayName?.let {
|
|
|
|
|
menuItems.add(
|
|
|
|
|
MenuItem(
|
|
|
|
|
title = it,
|
|
|
|
|
onClick = {
|
|
|
|
|
onClickItem(item)
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return menuItems
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
private fun TranslationsDialogActionButtons(
|
|
|
|
|
onTranslateButtonClick: () -> Unit,
|
|
|
|
|
translationError: TranslationError? = null,
|
|
|
|
|
onTranslateButtonClicked: () -> Unit,
|
|
|
|
|
onNotNowButtonClicked: () -> Unit,
|
|
|
|
|
) {
|
|
|
|
|
val isTranslationInProgress = remember { mutableStateOf(false) }
|
|
|
|
|
|
|
|
|
@ -317,8 +452,9 @@ private fun TranslationsDialogActionButtons(
|
|
|
|
|
TextButton(
|
|
|
|
|
text = negativeButtonTitle,
|
|
|
|
|
modifier = Modifier,
|
|
|
|
|
onClick = {},
|
|
|
|
|
onClick = onNotNowButtonClicked,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.width(10.dp))
|
|
|
|
|
|
|
|
|
|
if (isTranslationInProgress.value) {
|
|
|
|
@ -344,7 +480,7 @@ private fun TranslationsDialogActionButtons(
|
|
|
|
|
modifier = Modifier.wrapContentSize(),
|
|
|
|
|
) {
|
|
|
|
|
isTranslationInProgress.value = true
|
|
|
|
|
onTranslateButtonClick()
|
|
|
|
|
onTranslateButtonClicked()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
PrimaryButton(
|
|
|
|
@ -352,7 +488,7 @@ private fun TranslationsDialogActionButtons(
|
|
|
|
|
modifier = Modifier.wrapContentSize(),
|
|
|
|
|
) {
|
|
|
|
|
isTranslationInProgress.value = true
|
|
|
|
|
onTranslateButtonClick()
|
|
|
|
|
onTranslateButtonClicked()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -367,9 +503,78 @@ private fun TranslationsDialogBottomSheetPreview() {
|
|
|
|
|
learnMoreUrl = "",
|
|
|
|
|
showFirstTimeTranslation = true,
|
|
|
|
|
translationError = TranslationError.LanguageNotSupportedError(null),
|
|
|
|
|
translateFromLanguages = getTranslateFromLanguageList(),
|
|
|
|
|
translateToLanguages = getTranslateToLanguageList(),
|
|
|
|
|
onSettingClicked = {},
|
|
|
|
|
onLearnMoreClicked = {},
|
|
|
|
|
onTranslateButtonClick = {},
|
|
|
|
|
onTranslateButtonClicked = {},
|
|
|
|
|
onNotNowButtonClicked = {},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
internal fun getTranslateFromLanguageList(): List<Language> {
|
|
|
|
|
return mutableListOf<Language>().apply {
|
|
|
|
|
add(
|
|
|
|
|
Language(
|
|
|
|
|
code = Locale.CHINA.toLanguageTag(),
|
|
|
|
|
localizedDisplayName = Locale.CHINA.displayLanguage,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
add(
|
|
|
|
|
Language(
|
|
|
|
|
code = Locale.ENGLISH.toLanguageTag(),
|
|
|
|
|
localizedDisplayName = Locale.ENGLISH.displayLanguage,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
add(
|
|
|
|
|
Language(
|
|
|
|
|
code = Locale.GERMAN.toLanguageTag(),
|
|
|
|
|
localizedDisplayName = Locale.GERMAN.displayLanguage,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
add(
|
|
|
|
|
Language(
|
|
|
|
|
code = Locale.JAPANESE.toLanguageTag(),
|
|
|
|
|
localizedDisplayName = Locale.JAPANESE.displayLanguage,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
internal fun getTranslateToLanguageList(): List<Language> {
|
|
|
|
|
return mutableListOf<Language>().apply {
|
|
|
|
|
add(
|
|
|
|
|
Language(
|
|
|
|
|
code = Locale.KOREAN.toLanguageTag(),
|
|
|
|
|
localizedDisplayName = Locale.KOREAN.displayLanguage,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
add(
|
|
|
|
|
Language(
|
|
|
|
|
code = Locale.CANADA.toLanguageTag(),
|
|
|
|
|
localizedDisplayName = Locale.CANADA.displayLanguage,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
add(
|
|
|
|
|
Language(
|
|
|
|
|
code = Locale.FRENCH.toLanguageTag(),
|
|
|
|
|
localizedDisplayName = Locale.FRENCH.displayLanguage,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
add(
|
|
|
|
|
Language(
|
|
|
|
|
code = Locale.ITALY.toLanguageTag(),
|
|
|
|
|
localizedDisplayName = Locale.ITALY.displayLanguage,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
add(
|
|
|
|
|
Language(
|
|
|
|
|
code = Locale.GERMAN.toLanguageTag(),
|
|
|
|
|
localizedDisplayName = Locale.GERMAN.displayLanguage,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|