Merge pull request #1824 from vitorpamplona/claude/improve-translation-settings-dDwmL
Redesign language settings UI with searchable language pickers
This commit is contained in:
+438
-168
@@ -20,28 +20,43 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.SwapHoriz
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MenuAnchorType
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.InputChip
|
||||
import androidx.compose.material3.InputChipDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -49,10 +64,9 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
@@ -64,7 +78,6 @@ import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.SpacedBy10dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.SpacedBy5dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
|
||||
import java.util.Locale as JavaLocale
|
||||
|
||||
@@ -97,141 +110,219 @@ fun UserSettingsScreen(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = SpacedBy10dp,
|
||||
) {
|
||||
DontTranslateFromSetting(accountViewModel)
|
||||
TranslateToSetting(accountViewModel)
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
DontTranslateFromSetting(accountViewModel)
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
LanguagePreferencesSetting(accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
private fun getAllLanguagesSorted(): List<JavaLocale> {
|
||||
val seen = mutableSetOf<String>()
|
||||
return JavaLocale.getAvailableLocales()
|
||||
.filter { it.language.isNotBlank() && it.country.isBlank() && seen.add(it.language) }
|
||||
.sortedBy { it.displayName.lowercase() }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DontTranslateFromSetting(accountViewModel: AccountViewModel) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val selectedLanguages = accountViewModel.dontTranslateFromFilteredBySpokenLanguages().toMutableSet()
|
||||
private fun SearchableLanguageList(
|
||||
languages: List<JavaLocale>,
|
||||
onSelect: (JavaLocale) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var searchQuery by remember { mutableStateOf("") }
|
||||
val filtered =
|
||||
remember(searchQuery, languages) {
|
||||
if (searchQuery.isBlank()) {
|
||||
languages
|
||||
} else {
|
||||
languages.filter {
|
||||
it.displayName.contains(searchQuery, ignoreCase = true) ||
|
||||
it.language.contains(searchQuery, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
SettingsRow(
|
||||
name = R.string.dont_translate_from,
|
||||
description = R.string.dont_translate_from_description,
|
||||
) {
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = !expanded },
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = stringRes(R.string.quick_action_select),
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||
modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryEditable),
|
||||
Column(modifier) {
|
||||
OutlinedTextField(
|
||||
value = searchQuery,
|
||||
onValueChange = { searchQuery = it },
|
||||
placeholder = { Text(stringRes(R.string.search_languages)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Search,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
ExposedDropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
LazyColumn(modifier = Modifier.heightIn(max = 200.dp)) {
|
||||
items(filtered, key = { it.language }) { locale ->
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onSelect(locale) }
|
||||
.padding(horizontal = 8.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
selectedLanguages.forEach { languageCode ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = JavaLocale.forLanguageTag(languageCode).displayName) },
|
||||
onClick = {
|
||||
accountViewModel.toggleDontTranslateFrom(languageCode)
|
||||
selectedLanguages.remove(languageCode)
|
||||
expanded = false
|
||||
},
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = stringRes(R.string.remove_language, languageCode),
|
||||
tint = Color.Red,
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = locale.displayName,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Text(
|
||||
text = locale.language,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TranslateToSetting(accountViewModel: AccountViewModel) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val currentTranslateTo = accountViewModel.translateTo()
|
||||
val languageList = ConfigurationCompat.getLocales(Resources.getSystem().configuration)
|
||||
val allLanguages = remember { getAllLanguagesSorted() }
|
||||
var showPicker by remember { mutableStateOf(false) }
|
||||
|
||||
Column {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
SettingsRow(
|
||||
name = R.string.translate_to,
|
||||
description = R.string.translate_to_description,
|
||||
) {
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = !expanded },
|
||||
OutlinedCard(
|
||||
modifier = Modifier.clickable { showPicker = !showPicker },
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = JavaLocale(currentTranslateTo).displayName,
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||
modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryEditable),
|
||||
)
|
||||
|
||||
ExposedDropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
for (i in 0 until languageList.size()) {
|
||||
languageList.get(i)?.let { lang ->
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (accountViewModel.account.settings.translateToContains(lang)) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
} else {
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
}
|
||||
Spacer(modifier = Modifier.size(10.dp))
|
||||
Text(text = lang.displayName)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
accountViewModel.updateTranslateTo(lang)
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = JavaLocale(currentTranslateTo).displayName,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showPicker) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
SearchableLanguageList(
|
||||
languages = allLanguages,
|
||||
onSelect = { locale ->
|
||||
accountViewModel.updateTranslateTo(locale)
|
||||
showPicker = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun DontTranslateFromSetting(accountViewModel: AccountViewModel) {
|
||||
val selectedLanguages = accountViewModel.dontTranslateFrom().toSet()
|
||||
var showAddPicker by remember { mutableStateOf(false) }
|
||||
val allLanguages = remember { getAllLanguagesSorted() }
|
||||
|
||||
val availableToAdd =
|
||||
remember(selectedLanguages, allLanguages) {
|
||||
allLanguages.filter { it.language !in selectedLanguages }
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
SettingsRow(
|
||||
name = R.string.dont_translate_from,
|
||||
description = R.string.dont_translate_from_description,
|
||||
) {}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
selectedLanguages.forEach { languageCode ->
|
||||
InputChip(
|
||||
selected = true,
|
||||
onClick = { accountViewModel.toggleDontTranslateFrom(languageCode) },
|
||||
label = { Text(JavaLocale(languageCode).displayName) },
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = stringRes(R.string.remove_language, languageCode),
|
||||
modifier = Modifier.size(InputChipDefaults.IconSize),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
InputChip(
|
||||
selected = false,
|
||||
onClick = { showAddPicker = !showAddPicker },
|
||||
label = { Text(stringRes(R.string.add_language)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(InputChipDefaults.IconSize),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (showAddPicker) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
SearchableLanguageList(
|
||||
languages = availableToAdd,
|
||||
onSelect = { locale ->
|
||||
accountViewModel.toggleDontTranslateFrom(locale.language)
|
||||
showAddPicker = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LanguagePreferencesSetting(accountViewModel: AccountViewModel) {
|
||||
val languagePreferences = accountViewModel.account.settings.syncedSettings.languages.languagePreferences
|
||||
var showAddPair by remember { mutableStateOf(false) }
|
||||
|
||||
if (languagePreferences.isEmpty()) return
|
||||
|
||||
Column(verticalArrangement = SpacedBy5dp) {
|
||||
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
SettingsRow(
|
||||
name = R.string.language_preferences,
|
||||
description = R.string.language_preferences_description,
|
||||
) {}
|
||||
|
||||
if (languagePreferences.isEmpty() && !showAddPair) {
|
||||
Text(
|
||||
text = stringRes(R.string.no_language_preferences),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
)
|
||||
}
|
||||
|
||||
languagePreferences.forEach { (key, preference) ->
|
||||
val parts = key.split(",")
|
||||
if (parts.size == 2) {
|
||||
LanguagePreferenceItem(
|
||||
LanguagePreferenceCard(
|
||||
source = parts[0],
|
||||
target = parts[1],
|
||||
preference = preference,
|
||||
@@ -239,12 +330,31 @@ fun LanguagePreferencesSetting(accountViewModel: AccountViewModel) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
TextButton(
|
||||
onClick = { showAddPair = !showAddPair },
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(stringRes(R.string.add_language_pair))
|
||||
}
|
||||
|
||||
if (showAddPair) {
|
||||
AddLanguagePairCard(
|
||||
accountViewModel = accountViewModel,
|
||||
onDismiss = { showAddPair = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun LanguagePreferenceItem(
|
||||
private fun LanguagePreferenceCard(
|
||||
source: String,
|
||||
target: String,
|
||||
preference: String,
|
||||
@@ -252,77 +362,237 @@ private fun LanguagePreferenceItem(
|
||||
) {
|
||||
val sourceName = JavaLocale(source).displayName
|
||||
val targetName = JavaLocale(target).displayName
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = !expanded },
|
||||
modifier = Modifier.padding(vertical = 4.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = stringRes(R.string.language_preference_pair, sourceName, targetName),
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
label = {
|
||||
Text(
|
||||
if (preference == source) {
|
||||
stringRes(R.string.translations_show_in_lang_first, sourceName)
|
||||
} else {
|
||||
stringRes(R.string.translations_show_in_lang_first, targetName)
|
||||
},
|
||||
OutlinedCard(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.SwapHoriz,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
},
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||
modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryEditable),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringRes(R.string.language_preference_pair, sourceName, targetName),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
IconButton(
|
||||
onClick = {
|
||||
accountViewModel.prefer(source, target, preference)
|
||||
},
|
||||
modifier = Modifier.size(32.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = stringRes(R.string.delete_preference),
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.size(18.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ExposedDropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (preference == source) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
} else {
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
}
|
||||
Spacer(modifier = Modifier.size(10.dp))
|
||||
Text(stringRes(R.string.translations_show_in_lang_first, sourceName))
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
accountViewModel.prefer(source, target, source)
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (preference == target) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
} else {
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
}
|
||||
Spacer(modifier = Modifier.size(10.dp))
|
||||
Text(stringRes(R.string.translations_show_in_lang_first, targetName))
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
accountViewModel.prefer(source, target, target)
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { accountViewModel.prefer(source, target, source) }
|
||||
.padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
RadioButton(
|
||||
selected = preference == source,
|
||||
onClick = { accountViewModel.prefer(source, target, source) },
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = stringRes(R.string.show_first, sourceName),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { accountViewModel.prefer(source, target, target) }
|
||||
.padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
RadioButton(
|
||||
selected = preference == target,
|
||||
onClick = { accountViewModel.prefer(source, target, target) },
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = stringRes(R.string.show_first, targetName),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddLanguagePairCard(
|
||||
accountViewModel: AccountViewModel,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val allLanguages = remember { getAllLanguagesSorted() }
|
||||
var selectedSource by remember { mutableStateOf<JavaLocale?>(null) }
|
||||
var selectedTarget by remember { mutableStateOf<JavaLocale?>(null) }
|
||||
var pickingSource by remember { mutableStateOf(true) }
|
||||
var pickingTarget by remember { mutableStateOf(false) }
|
||||
|
||||
OutlinedCard(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
Text(
|
||||
text = stringRes(R.string.add_language_pair),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
OutlinedCard(
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.clickable {
|
||||
pickingSource = true
|
||||
pickingTarget = false
|
||||
},
|
||||
) {
|
||||
Column(modifier = Modifier.padding(8.dp)) {
|
||||
Text(
|
||||
text = stringRes(R.string.source_language),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Text(
|
||||
text = selectedSource?.displayName ?: stringRes(R.string.quick_action_select),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = if (selectedSource != null) FontWeight.Medium else FontWeight.Normal,
|
||||
color =
|
||||
if (selectedSource != null) {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Default.SwapHoriz,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
|
||||
OutlinedCard(
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.clickable {
|
||||
pickingTarget = true
|
||||
pickingSource = false
|
||||
},
|
||||
) {
|
||||
Column(modifier = Modifier.padding(8.dp)) {
|
||||
Text(
|
||||
text = stringRes(R.string.target_language),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Text(
|
||||
text = selectedTarget?.displayName ?: stringRes(R.string.quick_action_select),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = if (selectedTarget != null) FontWeight.Medium else FontWeight.Normal,
|
||||
color =
|
||||
if (selectedTarget != null) {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pickingSource) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
SearchableLanguageList(
|
||||
languages = allLanguages,
|
||||
onSelect = { locale ->
|
||||
selectedSource = locale
|
||||
pickingSource = false
|
||||
if (selectedTarget == null) {
|
||||
pickingTarget = true
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (pickingTarget) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
SearchableLanguageList(
|
||||
languages = allLanguages.filter { it.language != selectedSource?.language },
|
||||
onSelect = { locale ->
|
||||
selectedTarget = locale
|
||||
pickingTarget = false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (selectedSource != null && selectedTarget != null) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringRes(R.string.go_back))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
TextButton(
|
||||
onClick = {
|
||||
val src = selectedSource!!.language
|
||||
val tgt = selectedTarget!!.language
|
||||
accountViewModel.prefer(src, tgt, tgt)
|
||||
onDismiss()
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
stringRes(
|
||||
R.string.show_first,
|
||||
selectedTarget!!.displayName,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1508,6 +1508,14 @@
|
||||
<string name="language_preferences">Language Display Preferences</string>
|
||||
<string name="language_preferences_description">For each translated language pair, choose which language to show first.</string>
|
||||
<string name="language_preference_pair">%1$s → %2$s</string>
|
||||
<string name="search_languages">Search languages</string>
|
||||
<string name="add_language">Add language</string>
|
||||
<string name="add_language_pair">Add language pair</string>
|
||||
<string name="source_language">Source language</string>
|
||||
<string name="target_language">Target language</string>
|
||||
<string name="show_first">Show %1$s first</string>
|
||||
<string name="no_language_preferences">No language display preferences yet. These are created automatically when translations occur, or you can add them manually.</string>
|
||||
<string name="delete_preference">Delete preference</string>
|
||||
<string name="pause">Pause</string>
|
||||
<string name="play">Play</string>
|
||||
<string name="open_dropdown_menu">Open dropdown menu</string>
|
||||
|
||||
Reference in New Issue
Block a user