From 4bf7f982bac7b990c5f856cea5bc8119b2a3b4c9 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 13 Mar 2026 09:24:12 -0400 Subject: [PATCH] Makes the new translation settings work --- .../vitorpamplona/amethyst/model/Account.kt | 10 ++ .../amethyst/model/AccountSettings.kt | 14 +- .../amethyst/model/AccountSyncedSettings.kt | 71 +++++----- .../ui/screen/loggedIn/AccountViewModel.kt | 10 +- .../loggedIn/settings/AppSettingsScreen.kt | 30 +++++ .../loggedIn/settings/UserSettingsScreen.kt | 121 +++++++++++------- 6 files changed, 172 insertions(+), 84 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 055cb6ae2..84e3cc0f7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -497,6 +497,16 @@ class Account( sendNewAppSpecificData() } + suspend fun addDontTranslateFrom(languageCode: String) { + settings.addDontTranslateFrom(languageCode) + sendNewAppSpecificData() + } + + suspend fun removeDontTranslateFrom(languageCode: String) { + settings.removeDontTranslateFrom(languageCode) + sendNewAppSpecificData() + } + suspend fun updateTranslateTo(languageCode: Locale) { if (settings.updateTranslateTo(languageCode)) { sendNewAppSpecificData() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt index 57538f5a8..5e6ad853d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt @@ -322,7 +322,19 @@ class AccountSettings( saveAccountSettings() } - fun translateToContains(languageCode: Locale) = syncedSettings.languages.translateTo.contains(languageCode.language) + fun addDontTranslateFrom(languageCode: String) { + syncedSettings.languages.addDontTranslateFrom(languageCode) + saveAccountSettings() + } + + fun removeDontTranslateFrom(languageCode: String) { + syncedSettings.languages.removeDontTranslateFrom(languageCode) + saveAccountSettings() + } + + fun translateToContains(languageCode: Locale) = + syncedSettings.languages.translateTo.value + .contains(languageCode.language) fun updateTranslateTo(languageCode: Locale): Boolean { if (syncedSettings.languages.updateTranslateTo(languageCode)) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettings.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettings.kt index eb5006401..3789e1c98 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettings.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettings.kt @@ -45,9 +45,9 @@ class AccountSyncedSettings( ) val languages = AccountLanguagePreferences( - internalSettings.languages.dontTranslateFrom, - internalSettings.languages.languagePreferences, - internalSettings.languages.translateTo, + MutableStateFlow(internalSettings.languages.dontTranslateFrom), + MutableStateFlow(internalSettings.languages.languagePreferences), + MutableStateFlow(internalSettings.languages.translateTo), ) val security = AccountSecurityPreferences( @@ -66,9 +66,9 @@ class AccountSyncedSettings( ), languages = AccountLanguagePreferencesInternal( - languages.dontTranslateFrom, - languages.languagePreferences, - languages.translateTo, + languages.dontTranslateFrom.value, + languages.languagePreferences.value, + languages.translateTo.value, ), security = AccountSecurityPreferencesInternal( @@ -98,16 +98,16 @@ class AccountSyncedSettings( zaps.defaultZapType.tryEmit(syncedSettingsInternal.zaps.defaultZapType) } - if (languages.dontTranslateFrom != syncedSettingsInternal.languages.dontTranslateFrom) { - languages.dontTranslateFrom = syncedSettingsInternal.languages.dontTranslateFrom + if (languages.dontTranslateFrom.value != syncedSettingsInternal.languages.dontTranslateFrom) { + languages.dontTranslateFrom.value = syncedSettingsInternal.languages.dontTranslateFrom } - if (languages.languagePreferences != syncedSettingsInternal.languages.languagePreferences) { - languages.languagePreferences = syncedSettingsInternal.languages.languagePreferences + if (languages.languagePreferences.value != syncedSettingsInternal.languages.languagePreferences) { + languages.languagePreferences.value = syncedSettingsInternal.languages.languagePreferences } - if (languages.translateTo != syncedSettingsInternal.languages.translateTo) { - languages.translateTo = syncedSettingsInternal.languages.translateTo + if (languages.translateTo.value != syncedSettingsInternal.languages.translateTo) { + languages.translateTo.value = syncedSettingsInternal.languages.translateTo } if (security.showSensitiveContent.value != syncedSettingsInternal.security.showSensitiveContent) { @@ -123,7 +123,7 @@ class AccountSyncedSettings( } } - fun dontTranslateFromFilteredBySpokenLanguages(): Set = languages.dontTranslateFrom - getLanguagesSpokenByUser() + fun dontTranslateFromFilteredBySpokenLanguages(): Set = languages.dontTranslateFrom.value - getLanguagesSpokenByUser() } @Stable @@ -140,27 +140,36 @@ class AccountZapPreferences( @Stable class AccountLanguagePreferences( - var dontTranslateFrom: Set, - var languagePreferences: Map, - var translateTo: String, + var dontTranslateFrom: MutableStateFlow>, + var languagePreferences: MutableStateFlow>, + var translateTo: MutableStateFlow, ) { // --- // language services // --- fun toggleDontTranslateFrom(languageCode: String) { - dontTranslateFrom = - if (!dontTranslateFrom.contains(languageCode)) { - dontTranslateFrom.plus(languageCode) + dontTranslateFrom.update { + if (it.contains(languageCode)) { + it - languageCode } else { - dontTranslateFrom.minus(languageCode) + it + languageCode } + } } - fun translateToContains(languageCode: Locale) = translateTo.contains(languageCode.language) + fun addDontTranslateFrom(languageCode: String) { + dontTranslateFrom.update { it + languageCode } + } + + fun removeDontTranslateFrom(languageCode: String) { + dontTranslateFrom.update { it - languageCode } + } + + fun translateToContains(languageCode: Locale) = translateTo.value.contains(languageCode.language) fun updateTranslateTo(languageCode: Locale): Boolean { - if (translateTo != languageCode.language) { - translateTo = languageCode.language + if (translateTo.value != languageCode.language) { + translateTo.tryEmit(languageCode.language) return true } return false @@ -172,13 +181,15 @@ class AccountLanguagePreferences( preference: String, ) { val key = "$source,$target" - if (key !in languagePreferences) { - languagePreferences = languagePreferences + Pair(key, preference) - } else { - if (languagePreferences.get(key) == preference) { - languagePreferences = languagePreferences.minus(key) + languagePreferences.update { + if (key !in it) { + it + Pair(key, preference) } else { - languagePreferences = languagePreferences + Pair(key, preference) + if (it.get(key) == preference) { + it.minus(key) + } else { + it + Pair(key, preference) + } } } } @@ -186,7 +197,7 @@ class AccountLanguagePreferences( fun preferenceBetween( source: String, target: String, - ): String? = languagePreferences["$source,$target"] + ): String? = languagePreferences.value["$source,$target"] } @Stable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 3ae5a63e6..09a901b7d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -953,11 +953,9 @@ class AccountViewModel( fun markDonatedInThisVersion() = account.markDonatedInThisVersion() - fun dontTranslateFrom() = account.settings.syncedSettings.languages.dontTranslateFrom + fun dontTranslateFrom() = account.settings.syncedSettings.languages.dontTranslateFrom.value - fun dontTranslateFromFilteredBySpokenLanguages() = account.settings.syncedSettings.dontTranslateFromFilteredBySpokenLanguages() - - fun translateTo() = account.settings.syncedSettings.languages.translateTo + fun translateTo() = account.settings.syncedSettings.languages.translateTo.value fun defaultZapType() = account.settings.syncedSettings.zaps.defaultZapType.value @@ -1007,6 +1005,10 @@ class AccountViewModel( fun toggleDontTranslateFrom(languageCode: String) = launchSigner { account.toggleDontTranslateFrom(languageCode) } + fun addDontTranslateFrom(languageCode: String) = launchSigner { account.addDontTranslateFrom(languageCode) } + + fun removeDontTranslateFrom(languageCode: String) = launchSigner { account.removeDontTranslateFrom(languageCode) } + fun updateTranslateTo(languageCode: Locale) = launchSigner { account.updateTranslateTo(languageCode) } fun prefer( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AppSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AppSettingsScreen.kt index 2ab3d0539..39bb49260 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AppSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AppSettingsScreen.kt @@ -422,3 +422,33 @@ fun SettingsRow( } } } + +@Composable +fun SettingsRow( + name: Int, + description: Int, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(5.dp), + ) { + Column( + modifier = Modifier.weight(2.0f), + verticalArrangement = Arrangement.spacedBy(3.dp), + ) { + Text( + text = stringRes(name), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + text = stringRes(description), + style = MaterialTheme.typography.bodySmall, + color = Color.Gray, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + ) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/UserSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/UserSettingsScreen.kt index 755b98ccf..ef2f01e2c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/UserSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/UserSettingsScreen.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings +import android.R.attr.targetName import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -40,7 +41,6 @@ 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.material.icons.filled.Delete import androidx.compose.material.icons.filled.Search @@ -67,6 +67,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.ui.navigation.navs.INav @@ -79,6 +80,7 @@ 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.ThemeComparisonRow +import okio.`-DeprecatedOkio`.source import java.util.Locale as JavaLocale @Preview(device = "spec:width=2160px,height=2340px,dpi=440") @@ -193,7 +195,8 @@ private fun SearchableLanguageList( @Composable fun TranslateToSetting(accountViewModel: AccountViewModel) { - val currentTranslateTo = accountViewModel.translateTo() + val currentTranslateTo by accountViewModel.account.settings.syncedSettings.languages.translateTo + .collectAsStateWithLifecycle() val allLanguages = remember { getAllLanguagesSorted() } var showPicker by remember { mutableStateOf(false) } @@ -235,7 +238,8 @@ fun TranslateToSetting(accountViewModel: AccountViewModel) { @OptIn(ExperimentalLayoutApi::class) @Composable fun DontTranslateFromSetting(accountViewModel: AccountViewModel) { - val selectedLanguages = accountViewModel.dontTranslateFrom().toSet() + val selectedLanguages by accountViewModel.account.settings.syncedSettings.languages.dontTranslateFrom + .collectAsStateWithLifecycle() var showAddPicker by remember { mutableStateOf(false) } val allLanguages = remember { getAllLanguagesSorted() } @@ -248,7 +252,7 @@ fun DontTranslateFromSetting(accountViewModel: AccountViewModel) { SettingsRow( name = R.string.dont_translate_from, description = R.string.dont_translate_from_description, - ) {} + ) Spacer(modifier = Modifier.height(8.dp)) @@ -260,7 +264,7 @@ fun DontTranslateFromSetting(accountViewModel: AccountViewModel) { selectedLanguages.forEach { languageCode -> InputChip( selected = true, - onClick = { accountViewModel.toggleDontTranslateFrom(languageCode) }, + onClick = { accountViewModel.removeDontTranslateFrom(languageCode) }, label = { Text(JavaLocale(languageCode).displayName) }, trailingIcon = { Icon( @@ -292,7 +296,7 @@ fun DontTranslateFromSetting(accountViewModel: AccountViewModel) { SearchableLanguageList( languages = availableToAdd, onSelect = { locale -> - accountViewModel.toggleDontTranslateFrom(locale.language) + accountViewModel.addDontTranslateFrom(locale.language) showAddPicker = false }, ) @@ -302,14 +306,16 @@ fun DontTranslateFromSetting(accountViewModel: AccountViewModel) { @Composable fun LanguagePreferencesSetting(accountViewModel: AccountViewModel) { - val languagePreferences = accountViewModel.account.settings.syncedSettings.languages.languagePreferences + val languagePreferences by + accountViewModel.account.settings.syncedSettings.languages.languagePreferences + .collectAsStateWithLifecycle() var showAddPair by remember { mutableStateOf(false) } 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( @@ -332,20 +338,20 @@ 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) { + if (!showAddPair) { + 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)) + } + } else { AddLanguagePairCard( accountViewModel = accountViewModel, onDismiss = { showAddPair = false }, @@ -448,7 +454,8 @@ private fun AddLanguagePairCard( val allLanguages = remember { getAllLanguagesSorted() } var selectedSource by remember { mutableStateOf(null) } var selectedTarget by remember { mutableStateOf(null) } - var pickingSource by remember { mutableStateOf(true) } + var selectedPreference by remember { mutableStateOf(null) } + var pickingSource by remember { mutableStateOf(false) } var pickingTarget by remember { mutableStateOf(false) } OutlinedCard(modifier = Modifier.fillMaxWidth()) { @@ -557,41 +564,57 @@ private fun AddLanguagePairCard( ) } + val selectedSource = selectedSource + val selectedTarget = selectedTarget if (selectedSource != null && selectedTarget != null) { Spacer(modifier = Modifier.height(12.dp)) Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End, + modifier = + Modifier + .fillMaxWidth() + .clickable { + accountViewModel.prefer(selectedSource.language, selectedTarget.language, selectedSource.language) + onDismiss() + }.padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically, ) { - TextButton(onClick = onDismiss) { - Text(stringRes(R.string.go_back)) - } - - Spacer(modifier = Modifier.width(8.dp)) - - TextButton( + RadioButton( + selected = selectedPreference == selectedSource, onClick = { - val src = selectedSource!!.language - val tgt = selectedTarget!!.language - accountViewModel.prefer(src, tgt, tgt) + accountViewModel.prefer(selectedSource.language, selectedTarget.language, selectedSource.language) 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, - ), - ) - } + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = stringRes(R.string.show_first, selectedSource.displayName), + style = MaterialTheme.typography.bodyMedium, + ) + } + + Row( + modifier = + Modifier + .fillMaxWidth() + .clickable { + accountViewModel.prefer(selectedSource.language, selectedTarget.language, selectedTarget.language) + onDismiss() + }.padding(vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + RadioButton( + selected = selectedPreference == selectedTarget, + onClick = { + accountViewModel.prefer(selectedSource.language, selectedTarget.language, selectedTarget.language) + onDismiss() + }, + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = stringRes(R.string.show_first, selectedTarget.displayName), + style = MaterialTheme.typography.bodyMedium, + ) } } }