You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
817 lines
29 KiB
Kotlin
817 lines
29 KiB
Kotlin
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package org.mozilla.fenix.translations
|
|
|
|
import 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
|
|
import androidx.compose.foundation.layout.Row
|
|
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
|
|
import androidx.compose.material.Divider
|
|
import androidx.compose.material.Icon
|
|
import androidx.compose.material.IconButton
|
|
import androidx.compose.material.Text
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.mutableIntStateOf
|
|
import androidx.compose.runtime.mutableStateOf
|
|
import androidx.compose.runtime.remember
|
|
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
|
|
import org.mozilla.fenix.compose.button.TextButton
|
|
import org.mozilla.fenix.nimbus.FxNimbus
|
|
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 translationsDialogState The current state of the Translations bottom sheet dialog.
|
|
* @param learnMoreUrl The learn more link for translations website.
|
|
* @param showFirstTimeFlow Whether translations first flow should be shown.
|
|
* @param onSettingClicked Invoked when the user clicks on the settings button.
|
|
* @param onLearnMoreClicked Invoked when the user clicks on the "Learn More" button.
|
|
* @param onPositiveButtonClicked Invoked when the user clicks on the positive button.
|
|
* @param onNegativeButtonClicked Invoked when the user clicks on the negative button.
|
|
* @param onFromDropdownSelected Invoked when the user selects an item on the from dropdown.
|
|
* @param onToDropdownSelected Invoked when the user selects an item on the to dropdown.
|
|
*/
|
|
@Suppress("LongParameterList")
|
|
@Composable
|
|
fun TranslationsDialogBottomSheet(
|
|
translationsDialogState: TranslationsDialogState,
|
|
learnMoreUrl: String,
|
|
showFirstTimeFlow: Boolean = false,
|
|
onSettingClicked: () -> Unit,
|
|
onLearnMoreClicked: () -> Unit,
|
|
onPositiveButtonClicked: () -> Unit,
|
|
onNegativeButtonClicked: () -> Unit,
|
|
onFromDropdownSelected: (Language) -> Unit,
|
|
onToDropdownSelected: (Language) -> Unit,
|
|
) {
|
|
Column(
|
|
modifier = Modifier.padding(16.dp),
|
|
) {
|
|
BetaLabel(
|
|
modifier = Modifier
|
|
.padding(bottom = 8.dp)
|
|
.clearAndSetSemantics {},
|
|
)
|
|
|
|
TranslationsDialogHeader(
|
|
title = if (
|
|
translationsDialogState.isTranslated && translationsDialogState.translatedPageTitle != null
|
|
) {
|
|
translationsDialogState.translatedPageTitle
|
|
} else {
|
|
getTranslationsDialogTitle(
|
|
showFirstTime = showFirstTimeFlow,
|
|
)
|
|
},
|
|
onSettingClicked = onSettingClicked,
|
|
)
|
|
|
|
Spacer(modifier = Modifier.height(8.dp))
|
|
|
|
if (showFirstTimeFlow) {
|
|
TranslationsDialogInfoMessage(
|
|
learnMoreUrl = learnMoreUrl,
|
|
onLearnMoreClicked = onLearnMoreClicked,
|
|
)
|
|
}
|
|
|
|
DialogContentBaseOnTranslationState(
|
|
translationsDialogState = translationsDialogState,
|
|
learnMoreUrl = learnMoreUrl,
|
|
onLearnMoreClicked = onLearnMoreClicked,
|
|
onPositiveButtonClicked = onPositiveButtonClicked,
|
|
onNegativeButtonClicked = onNegativeButtonClicked,
|
|
onFromDropdownSelected = onFromDropdownSelected,
|
|
onToDropdownSelected = onToDropdownSelected,
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dialog content will adapt based on the [TranslationsDialogState].
|
|
*
|
|
* @param translationsDialogState The current state of the Translations bottom sheet dialog.
|
|
* @param learnMoreUrl The learn more link for translations website.
|
|
* @param onLearnMoreClicked Invoked when the user clicks on the learn more button.
|
|
* @param onPositiveButtonClicked Invoked when the user clicks on the positive button.
|
|
* @param onNegativeButtonClicked Invoked when the user clicks on the negative button.
|
|
* @param onFromDropdownSelected Invoked when the user selects an item on the from dropdown.
|
|
* @param onToDropdownSelected Invoked when the user selects an item on the to dropdown.
|
|
*/
|
|
@Composable
|
|
private fun DialogContentBaseOnTranslationState(
|
|
translationsDialogState: TranslationsDialogState,
|
|
learnMoreUrl: String,
|
|
onLearnMoreClicked: () -> Unit,
|
|
onPositiveButtonClicked: () -> Unit,
|
|
onNegativeButtonClicked: () -> Unit,
|
|
onFromDropdownSelected: (Language) -> Unit,
|
|
onToDropdownSelected: (Language) -> Unit,
|
|
) {
|
|
if (translationsDialogState.error != null) {
|
|
DialogContentAnErrorOccurred(
|
|
translationsDialogState = translationsDialogState,
|
|
learnMoreUrl = learnMoreUrl,
|
|
onLearnMoreClicked = onLearnMoreClicked,
|
|
onFromDropdownSelected = onFromDropdownSelected,
|
|
onToDropdownSelected = onToDropdownSelected,
|
|
onPositiveButtonClicked = onPositiveButtonClicked,
|
|
onNegativeButtonClicked = onNegativeButtonClicked,
|
|
)
|
|
} else if (translationsDialogState.isTranslated) {
|
|
DialogContentTranslated(
|
|
translateToLanguages = translationsDialogState.toLanguages,
|
|
onFromDropdownSelected = onFromDropdownSelected,
|
|
onToDropdownSelected = onToDropdownSelected,
|
|
onPositiveButtonClicked = onPositiveButtonClicked,
|
|
onNegativeButtonClicked = onNegativeButtonClicked,
|
|
positiveButtonType = translationsDialogState.positiveButtonType,
|
|
initialTo = translationsDialogState.initialTo,
|
|
)
|
|
} else {
|
|
Spacer(modifier = Modifier.height(14.dp))
|
|
|
|
TranslationsDialogContent(
|
|
translateFromLanguages = translationsDialogState.fromLanguages,
|
|
translateToLanguages = translationsDialogState.toLanguages,
|
|
initialFrom = translationsDialogState.initialFrom,
|
|
initialTo = translationsDialogState.initialTo,
|
|
onFromDropdownSelected = onFromDropdownSelected,
|
|
onToDropdownSelected = onToDropdownSelected,
|
|
)
|
|
|
|
Spacer(modifier = Modifier.height(16.dp))
|
|
|
|
TranslationsDialogActionButtons(
|
|
positiveButtonText = stringResource(id = R.string.translations_bottom_sheet_positive_button),
|
|
negativeButtonText = stringResource(id = R.string.translations_bottom_sheet_negative_button),
|
|
positiveButtonType = translationsDialogState.positiveButtonType,
|
|
onNegativeButtonClicked = onNegativeButtonClicked,
|
|
onPositiveButtonClicked = onPositiveButtonClicked,
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dialog content if the web page was translated.
|
|
*
|
|
* @param translateToLanguages Translation menu items to be shown in the translate to dropdown.
|
|
* @param onFromDropdownSelected Invoked when the user selects an item on the from dropdown.
|
|
* @param onToDropdownSelected Invoked when the user selects an item on the to dropdown.
|
|
* @param onPositiveButtonClicked Invoked when the user clicks on the positive button.
|
|
* @param onNegativeButtonClicked Invoked when the user clicks on the negative button.
|
|
* @param positiveButtonType Can be enabled,disabled or in progress. If it is null, the button will be disabled.
|
|
* @param initialTo Initial "to" language, based on the translation state and page state.
|
|
*/
|
|
@Composable
|
|
private fun DialogContentTranslated(
|
|
translateToLanguages: List<Language>?,
|
|
onFromDropdownSelected: (Language) -> Unit,
|
|
onToDropdownSelected: (Language) -> Unit,
|
|
onPositiveButtonClicked: () -> Unit,
|
|
onNegativeButtonClicked: () -> Unit,
|
|
positiveButtonType: PositiveButtonType? = null,
|
|
initialTo: Language? = null,
|
|
) {
|
|
Spacer(modifier = Modifier.height(14.dp))
|
|
|
|
TranslationsDialogContent(
|
|
translateToLanguages = translateToLanguages,
|
|
initialTo = initialTo,
|
|
onFromDropdownSelected = onFromDropdownSelected,
|
|
onToDropdownSelected = onToDropdownSelected,
|
|
)
|
|
|
|
Spacer(modifier = Modifier.height(16.dp))
|
|
|
|
TranslationsDialogActionButtons(
|
|
positiveButtonText = stringResource(id = R.string.translations_bottom_sheet_positive_button),
|
|
negativeButtonText = stringResource(id = R.string.translations_bottom_sheet_negative_button_restore),
|
|
positiveButtonType = positiveButtonType,
|
|
onPositiveButtonClicked = onPositiveButtonClicked,
|
|
onNegativeButtonClicked = onNegativeButtonClicked,
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Dialog content if an [TranslationError] appears during the translation process.
|
|
*
|
|
* @param translationsDialogState The current state of the Translations bottom sheet dialog.
|
|
* @param learnMoreUrl The learn more link for translations website.
|
|
* @param onLearnMoreClicked Invoked when the user clicks on the learn more button.
|
|
* @param onFromDropdownSelected Invoked when the user selects an item on the from dropdown.
|
|
* @param onToDropdownSelected Invoked when the user selects an item on the to dropdown.
|
|
* @param onPositiveButtonClicked Invoked when the user clicks on the positive button.
|
|
* @param onNegativeButtonClicked Invoked when the user clicks on the negative button.
|
|
*/
|
|
@Suppress("LongParameterList")
|
|
@Composable
|
|
private fun DialogContentAnErrorOccurred(
|
|
translationsDialogState: TranslationsDialogState,
|
|
learnMoreUrl: String,
|
|
onLearnMoreClicked: () -> Unit,
|
|
onFromDropdownSelected: (Language) -> Unit,
|
|
onToDropdownSelected: (Language) -> Unit,
|
|
onPositiveButtonClicked: () -> Unit,
|
|
onNegativeButtonClicked: () -> Unit,
|
|
) {
|
|
translationsDialogState.error?.let { translationError ->
|
|
TranslationErrorWarning(
|
|
translationError = translationError,
|
|
documentLangDisplayName = translationsDialogState.documentLangDisplayName,
|
|
learnMoreUrl = learnMoreUrl,
|
|
onLearnMoreClicked = onLearnMoreClicked,
|
|
)
|
|
|
|
Spacer(modifier = Modifier.height(14.dp))
|
|
|
|
if (translationError !is TranslationError.CouldNotLoadLanguagesError) {
|
|
TranslationsDialogContent(
|
|
translateFromLanguages = translationsDialogState.fromLanguages,
|
|
translateToLanguages = translationsDialogState.toLanguages,
|
|
initialFrom = translationsDialogState.initialFrom,
|
|
initialTo = translationsDialogState.initialTo,
|
|
onFromDropdownSelected = onFromDropdownSelected,
|
|
onToDropdownSelected = onToDropdownSelected,
|
|
)
|
|
}
|
|
|
|
val negativeButtonTitle =
|
|
if (translationError is TranslationError.LanguageNotSupportedError) {
|
|
stringResource(id = R.string.translations_bottom_sheet_negative_button_error)
|
|
} else {
|
|
stringResource(id = R.string.translations_bottom_sheet_negative_button)
|
|
}
|
|
|
|
val positiveButtonTitle =
|
|
if (translationError is TranslationError.CouldNotLoadLanguagesError) {
|
|
stringResource(id = R.string.translations_bottom_sheet_positive_button_error)
|
|
} else {
|
|
stringResource(id = R.string.translations_bottom_sheet_positive_button)
|
|
}
|
|
|
|
val positiveButtonType =
|
|
if (translationError is TranslationError.LanguageNotSupportedError) {
|
|
PositiveButtonType.Disabled
|
|
} else {
|
|
PositiveButtonType.Enabled
|
|
}
|
|
|
|
TranslationsDialogActionButtons(
|
|
positiveButtonText = positiveButtonTitle,
|
|
negativeButtonText = negativeButtonTitle,
|
|
positiveButtonType = positiveButtonType,
|
|
onNegativeButtonClicked = onNegativeButtonClicked,
|
|
onPositiveButtonClicked = onPositiveButtonClicked,
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun TranslationsDialogContent(
|
|
translateFromLanguages: List<Language>? = null,
|
|
translateToLanguages: List<Language>? = null,
|
|
initialFrom: Language? = null,
|
|
initialTo: Language? = null,
|
|
onFromDropdownSelected: (Language) -> Unit,
|
|
onToDropdownSelected: (Language) -> Unit,
|
|
) {
|
|
var orientation by remember { mutableIntStateOf(Configuration.ORIENTATION_PORTRAIT) }
|
|
|
|
val configuration = LocalConfiguration.current
|
|
|
|
LaunchedEffect(configuration) {
|
|
snapshotFlow { configuration.orientation }.collect { orientation = it }
|
|
}
|
|
when (orientation) {
|
|
Configuration.ORIENTATION_LANDSCAPE -> {
|
|
TranslationsDialogContentInLandscapeMode(
|
|
translateFromLanguages = translateFromLanguages,
|
|
translateToLanguages = translateToLanguages,
|
|
initialFrom = initialFrom,
|
|
initialTo = initialTo,
|
|
onFromDropdownSelected = onFromDropdownSelected,
|
|
onToDropdownSelected = onToDropdownSelected,
|
|
)
|
|
}
|
|
|
|
else -> {
|
|
TranslationsDialogContentInPortraitMode(
|
|
translateFromLanguages = translateFromLanguages,
|
|
translateToLanguages = translateToLanguages,
|
|
initialFrom = initialFrom,
|
|
initialTo = initialTo,
|
|
onFromDropdownSelected = onFromDropdownSelected,
|
|
onToDropdownSelected = onToDropdownSelected,
|
|
)
|
|
}
|
|
}
|
|
|
|
Spacer(modifier = Modifier.height(16.dp))
|
|
}
|
|
|
|
@Composable
|
|
private fun TranslationsDialogContentInPortraitMode(
|
|
translateFromLanguages: List<Language>? = null,
|
|
translateToLanguages: List<Language>? = null,
|
|
initialFrom: Language? = null,
|
|
initialTo: Language? = null,
|
|
onFromDropdownSelected: (Language) -> Unit,
|
|
onToDropdownSelected: (Language) -> Unit,
|
|
) {
|
|
Column {
|
|
translateFromLanguages?.let {
|
|
TranslationsDropdown(
|
|
header = stringResource(id = R.string.translations_bottom_sheet_translate_from),
|
|
modifier = Modifier.fillMaxWidth(),
|
|
isInLandscapeMode = false,
|
|
translateLanguages = translateFromLanguages,
|
|
initiallySelected = initialFrom,
|
|
onLanguageSelection = onFromDropdownSelected,
|
|
)
|
|
|
|
Spacer(modifier = Modifier.height(16.dp))
|
|
}
|
|
|
|
translateToLanguages?.let {
|
|
TranslationsDropdown(
|
|
header = stringResource(id = R.string.translations_bottom_sheet_translate_to),
|
|
modifier = Modifier.fillMaxWidth(),
|
|
isInLandscapeMode = false,
|
|
translateLanguages = it,
|
|
initiallySelected = initialTo,
|
|
onLanguageSelection = onToDropdownSelected,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun TranslationsDialogContentInLandscapeMode(
|
|
translateFromLanguages: List<Language>? = null,
|
|
translateToLanguages: List<Language>? = null,
|
|
initialFrom: Language? = null,
|
|
initialTo: Language? = null,
|
|
onFromDropdownSelected: (Language) -> Unit,
|
|
onToDropdownSelected: (Language) -> Unit,
|
|
) {
|
|
Column {
|
|
Row {
|
|
translateFromLanguages?.let {
|
|
TranslationsDropdown(
|
|
header = stringResource(id = R.string.translations_bottom_sheet_translate_from),
|
|
modifier = Modifier.weight(1f),
|
|
isInLandscapeMode = true,
|
|
translateLanguages = translateFromLanguages,
|
|
initiallySelected = initialFrom,
|
|
onLanguageSelection = onFromDropdownSelected,
|
|
)
|
|
|
|
Spacer(modifier = Modifier.width(16.dp))
|
|
}
|
|
|
|
translateToLanguages?.let {
|
|
TranslationsDropdown(
|
|
header = stringResource(id = R.string.translations_bottom_sheet_translate_to),
|
|
modifier = Modifier.weight(1f),
|
|
isInLandscapeMode = true,
|
|
translateLanguages = it,
|
|
initiallySelected = initialTo,
|
|
onLanguageSelection = onToDropdownSelected,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun TranslationsDialogHeader(
|
|
title: String,
|
|
onSettingClicked: () -> Unit,
|
|
) {
|
|
Row(
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
) {
|
|
Text(
|
|
text = title,
|
|
modifier = Modifier
|
|
.weight(1f)
|
|
.semantics { heading() },
|
|
color = FirefoxTheme.colors.textPrimary,
|
|
style = FirefoxTheme.typography.headline7,
|
|
)
|
|
|
|
Spacer(modifier = Modifier.width(4.dp))
|
|
|
|
if (FxNimbus.features.translations.value().pageSettingsEnabled) {
|
|
IconButton(
|
|
onClick = { onSettingClicked() },
|
|
modifier = Modifier.size(24.dp),
|
|
) {
|
|
Icon(
|
|
painter = painterResource(id = R.drawable.mozac_ic_settings_24),
|
|
contentDescription = stringResource(id = R.string.translation_option_bottom_sheet_title),
|
|
tint = FirefoxTheme.colors.iconPrimary,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun TranslationErrorWarning(
|
|
translationError: TranslationError,
|
|
documentLangDisplayName: String? = null,
|
|
learnMoreUrl: String,
|
|
onLearnMoreClicked: () -> Unit,
|
|
) {
|
|
val modifier = Modifier
|
|
.padding(top = 8.dp)
|
|
.fillMaxWidth()
|
|
|
|
when (translationError) {
|
|
is TranslationError.CouldNotTranslateError -> {
|
|
ReviewQualityCheckInfoCard(
|
|
title = stringResource(id = R.string.translation_error_could_not_translate_warning_text),
|
|
type = ReviewQualityCheckInfoType.Error,
|
|
modifier = modifier,
|
|
)
|
|
}
|
|
|
|
is TranslationError.CouldNotLoadLanguagesError -> {
|
|
ReviewQualityCheckInfoCard(
|
|
title = stringResource(id = R.string.translation_error_could_not_load_languages_warning_text),
|
|
type = ReviewQualityCheckInfoType.Error,
|
|
modifier = modifier,
|
|
)
|
|
}
|
|
|
|
is TranslationError.LanguageNotSupportedError -> {
|
|
val learnMoreText =
|
|
stringResource(id = R.string.translation_error_language_not_supported_learn_more)
|
|
|
|
documentLangDisplayName?.let {
|
|
ReviewQualityCheckInfoCard(
|
|
title = stringResource(
|
|
id = R.string.translation_error_language_not_supported_warning_text,
|
|
it,
|
|
),
|
|
type = ReviewQualityCheckInfoType.Info,
|
|
modifier = modifier,
|
|
footer = stringResource(
|
|
id = R.string.translation_error_language_not_supported_learn_more,
|
|
) to LinkTextState(
|
|
text = learnMoreText,
|
|
url = learnMoreUrl,
|
|
onClick = { onLearnMoreClicked() },
|
|
),
|
|
)
|
|
}
|
|
}
|
|
|
|
else -> {}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun TranslationsDialogInfoMessage(
|
|
learnMoreUrl: String,
|
|
onLearnMoreClicked: () -> Unit,
|
|
) {
|
|
val learnMoreText =
|
|
stringResource(id = R.string.translations_bottom_sheet_info_message_learn_more)
|
|
|
|
val learnMoreState = LinkTextState(
|
|
text = learnMoreText,
|
|
url = learnMoreUrl,
|
|
onClick = { onLearnMoreClicked() },
|
|
)
|
|
|
|
Box {
|
|
LinkText(
|
|
text = stringResource(
|
|
R.string.translations_bottom_sheet_info_message,
|
|
learnMoreText,
|
|
),
|
|
linkTextStates = listOf(learnMoreState),
|
|
style = FirefoxTheme.typography.subtitle1.copy(
|
|
color = FirefoxTheme.colors.textPrimary,
|
|
),
|
|
linkTextDecoration = TextDecoration.Underline,
|
|
)
|
|
}
|
|
}
|
|
|
|
@Suppress("LongMethod")
|
|
@Composable
|
|
private fun TranslationsDropdown(
|
|
header: String,
|
|
translateLanguages: List<Language>,
|
|
modifier: Modifier = Modifier,
|
|
isInLandscapeMode: Boolean,
|
|
initiallySelected: Language? = null,
|
|
onLanguageSelection: (Language) -> Unit,
|
|
) {
|
|
val density = LocalDensity.current
|
|
|
|
var expanded by remember { mutableStateOf(false) }
|
|
|
|
var contextMenuWidthDp by remember {
|
|
mutableStateOf(0.dp)
|
|
}
|
|
|
|
Column(
|
|
modifier = modifier
|
|
.clickable {
|
|
expanded = true
|
|
}
|
|
.semantics { role = Role.DropdownList },
|
|
) {
|
|
Text(
|
|
text = header,
|
|
modifier = Modifier.wrapContentSize(),
|
|
color = FirefoxTheme.colors.textPrimary,
|
|
style = FirefoxTheme.typography.caption,
|
|
)
|
|
|
|
Spacer(modifier = Modifier.height(4.dp))
|
|
|
|
var initialValue =
|
|
stringResource(R.string.translations_bottom_sheet_default_dropdown_selection)
|
|
initiallySelected?.localizedDisplayName?.let {
|
|
initialValue = it
|
|
}
|
|
|
|
Row {
|
|
Text(
|
|
text = initialValue,
|
|
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,
|
|
selectedLanguage = initiallySelected,
|
|
onClickItem = {
|
|
onLanguageSelection(it)
|
|
},
|
|
),
|
|
modifier = Modifier
|
|
.onGloballyPositioned { coordinates ->
|
|
contextMenuWidthDp = with(density) {
|
|
coordinates.size.width.toDp()
|
|
}
|
|
}
|
|
.requiredSizeIn(maxHeight = 200.dp)
|
|
.padding(horizontal = if (initiallySelected == null) 36.dp else 4.dp),
|
|
offset = if (isInLandscapeMode) {
|
|
DpOffset(
|
|
-contextMenuWidthDp + ICON_SIZE,
|
|
-ICON_SIZE,
|
|
)
|
|
} else {
|
|
DpOffset(
|
|
0.dp,
|
|
-ICON_SIZE,
|
|
)
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
Divider(color = FirefoxTheme.colors.formDefault)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun getTranslationsDialogTitle(
|
|
showFirstTime: Boolean = false,
|
|
) = if (showFirstTime) {
|
|
stringResource(
|
|
id = R.string.translations_bottom_sheet_title_first_time,
|
|
stringResource(id = R.string.firefox),
|
|
)
|
|
} else {
|
|
stringResource(id = R.string.translations_bottom_sheet_title)
|
|
}
|
|
|
|
private fun getContextMenuItems(
|
|
translateLanguages: List<Language>,
|
|
selectedLanguage: Language? = null,
|
|
onClickItem: (Language) -> Unit,
|
|
): List<MenuItem> {
|
|
val menuItems = mutableListOf<MenuItem>()
|
|
translateLanguages.map { item ->
|
|
item.localizedDisplayName?.let {
|
|
menuItems.add(
|
|
MenuItem(
|
|
title = it,
|
|
isChecked = item == selectedLanguage,
|
|
onClick = {
|
|
onClickItem(item)
|
|
},
|
|
),
|
|
)
|
|
}
|
|
}
|
|
return menuItems
|
|
}
|
|
|
|
@Composable
|
|
private fun TranslationsDialogActionButtons(
|
|
positiveButtonText: String,
|
|
negativeButtonText: String,
|
|
positiveButtonType: PositiveButtonType? = null,
|
|
onPositiveButtonClicked: () -> Unit,
|
|
onNegativeButtonClicked: () -> Unit,
|
|
) {
|
|
Row(
|
|
modifier = Modifier.fillMaxWidth(),
|
|
horizontalArrangement = Arrangement.End,
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
) {
|
|
TextButton(
|
|
text = negativeButtonText,
|
|
modifier = Modifier,
|
|
onClick = onNegativeButtonClicked,
|
|
)
|
|
|
|
Spacer(modifier = Modifier.width(10.dp))
|
|
|
|
when (positiveButtonType) {
|
|
PositiveButtonType.InProgress -> {
|
|
DownloadIndicator(
|
|
text = positiveButtonText,
|
|
contentDescription = stringResource(
|
|
id = R.string.translations_bottom_sheet_translating_in_progress_content_description,
|
|
),
|
|
icon = painterResource(id = R.drawable.mozac_ic_sync_24),
|
|
)
|
|
}
|
|
|
|
PositiveButtonType.Enabled -> {
|
|
PrimaryButton(
|
|
text = positiveButtonText,
|
|
modifier = Modifier.wrapContentSize(),
|
|
) {
|
|
onPositiveButtonClicked()
|
|
}
|
|
}
|
|
|
|
else -> {
|
|
TertiaryButton(
|
|
text = positiveButtonText,
|
|
enabled = false,
|
|
modifier = Modifier.wrapContentSize(),
|
|
) {
|
|
onPositiveButtonClicked()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
@LightDarkPreview
|
|
private fun TranslationsDialogBottomSheetPreview() {
|
|
FirefoxTheme {
|
|
TranslationsDialogBottomSheet(
|
|
translationsDialogState = TranslationsDialogState(
|
|
positiveButtonType = PositiveButtonType.Enabled,
|
|
toLanguages = getTranslateToLanguageList(),
|
|
fromLanguages = getTranslateFromLanguageList(),
|
|
),
|
|
learnMoreUrl = "",
|
|
showFirstTimeFlow = true,
|
|
onSettingClicked = {},
|
|
onLearnMoreClicked = {},
|
|
onPositiveButtonClicked = {},
|
|
onNegativeButtonClicked = {},
|
|
onFromDropdownSelected = {},
|
|
onToDropdownSelected = {},
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
internal fun getTranslateFromLanguageList(): List<Language> {
|
|
return mutableListOf<Language>().apply {
|
|
add(
|
|
Language(
|
|
code = Locale.ENGLISH.toLanguageTag(),
|
|
localizedDisplayName = Locale.ENGLISH.displayLanguage,
|
|
),
|
|
)
|
|
add(
|
|
Language(
|
|
code = Locale.FRENCH.toLanguageTag(),
|
|
localizedDisplayName = Locale.FRENCH.displayLanguage,
|
|
),
|
|
)
|
|
add(
|
|
Language(
|
|
code = Locale.GERMAN.toLanguageTag(),
|
|
localizedDisplayName = Locale.GERMAN.displayLanguage,
|
|
),
|
|
)
|
|
add(
|
|
Language(
|
|
code = Locale.ITALIAN.toLanguageTag(),
|
|
localizedDisplayName = Locale.ITALIAN.displayLanguage,
|
|
),
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
internal fun getTranslateToLanguageList(): List<Language> {
|
|
return mutableListOf<Language>().apply {
|
|
add(
|
|
Language(
|
|
code = Locale.ENGLISH.toLanguageTag(),
|
|
localizedDisplayName = Locale.ENGLISH.displayLanguage,
|
|
),
|
|
)
|
|
add(
|
|
Language(
|
|
code = Locale.FRENCH.toLanguageTag(),
|
|
localizedDisplayName = Locale.FRENCH.displayLanguage,
|
|
),
|
|
)
|
|
add(
|
|
Language(
|
|
code = Locale.GERMAN.toLanguageTag(),
|
|
localizedDisplayName = Locale.GERMAN.displayLanguage,
|
|
),
|
|
)
|
|
add(
|
|
Language(
|
|
code = Locale.ITALIAN.toLanguageTag(),
|
|
localizedDisplayName = Locale.ITALIAN.displayLanguage,
|
|
),
|
|
)
|
|
}
|
|
}
|