From 8b3aa2a7ba88ae09446e190aac2ac17c63409f44 Mon Sep 17 00:00:00 2001 From: doomsdayrs Date: Sun, 2 Jul 2023 12:59:21 -0400 Subject: [PATCH 1/4] Inline EnginePref into LazyColumn in Settings Previously, two scrolls would occur creating a complicated user experience. This commit solves the issue by inlining EnginePref into the LazyColumn. A problem occurs in which BlockingRadioButton causes a crash due to infinite height mechanics of LazyVerticalGrid. To solve this, a max height of 200.dp is applied to LazyVerticalGrid. --- .../ui/components/BlockRadioButton.kt | 6 +- .../translate/ui/screens/SettingsScreen.kt | 278 +++++++++--------- 2 files changed, 150 insertions(+), 134 deletions(-) diff --git a/app/src/main/java/com/bnyro/translate/ui/components/BlockRadioButton.kt b/app/src/main/java/com/bnyro/translate/ui/components/BlockRadioButton.kt index 4bfd28ff9..adf14cff5 100644 --- a/app/src/main/java/com/bnyro/translate/ui/components/BlockRadioButton.kt +++ b/app/src/main/java/com/bnyro/translate/ui/components/BlockRadioButton.kt @@ -18,6 +18,8 @@ package com.bnyro.translate.ui.components import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid @@ -35,13 +37,15 @@ fun BlockRadioButton( ) { Column { LazyVerticalGrid( - columns = GridCells.Fixed(3) + columns = GridCells.Fixed(3), + modifier = Modifier.heightIn(max = 200.dp) ) { items(items) { val index = items.indexOf(it) BlockButton( modifier = Modifier .weight(1f) + .fillMaxWidth() .padding(4.dp, 4.dp), text = it, selected = selected == index diff --git a/app/src/main/java/com/bnyro/translate/ui/screens/SettingsScreen.kt b/app/src/main/java/com/bnyro/translate/ui/screens/SettingsScreen.kt index c5c115a53..43c7b3524 100644 --- a/app/src/main/java/com/bnyro/translate/ui/screens/SettingsScreen.kt +++ b/app/src/main/java/com/bnyro/translate/ui/screens/SettingsScreen.kt @@ -20,7 +20,7 @@ package com.bnyro.translate.ui.screens import android.os.Handler import android.os.Looper import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -67,7 +67,7 @@ import com.bnyro.translate.util.LocaleHelper import com.bnyro.translate.util.Preferences @Suppress("KotlinConstantConditions") -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable fun SettingsScreen( navController: NavController @@ -134,163 +134,175 @@ fun SettingsScreen( ) } ) { pV -> - Column( + LazyColumn( + horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .padding(pV) .fillMaxSize() .padding(15.dp, 0.dp) ) { - EnginePref() - - LazyColumn( - horizontalAlignment = Alignment.CenterHorizontally - ) { - item { - SettingsCategory( - title = stringResource(R.string.general) - ) - - val appLanguages = LocaleHelper.getLanguages(context) + item { + EnginePref() + } - ListPreference( - title = stringResource(R.string.app_language), - preferenceKey = Preferences.appLanguageKey, - defaultValue = "", - entries = appLanguages.map { it.name }, - values = appLanguages.map { it.code } - ) { - Handler(Looper.getMainLooper()).postDelayed({ - (context as MainActivity).recreate() - }, 100) - } + item { + SettingsCategory( + title = stringResource(R.string.general) + ) + + val appLanguages = LocaleHelper.getLanguages(context) + + ListPreference( + title = stringResource(R.string.app_language), + preferenceKey = Preferences.appLanguageKey, + defaultValue = "", + entries = appLanguages.map { it.name }, + values = appLanguages.map { it.code } + ) { + Handler(Looper.getMainLooper()).postDelayed({ + (context as MainActivity).recreate() + }, 100) } + } - item { - PreferenceItem( - modifier = Modifier.padding(top = 10.dp), - title = stringResource(R.string.image_translation), - summary = stringResource(R.string.image_translation_summary) - ) { - showTessSettings = true - } + item { + PreferenceItem( + modifier = Modifier.padding(top = 10.dp), + title = stringResource(R.string.image_translation), + summary = stringResource(R.string.image_translation_summary) + ) { + showTessSettings = true } + } - item { - SettingsCategory( - title = stringResource(R.string.history) - ) + item { + SettingsCategory( + title = stringResource(R.string.history) + ) + + SwitchPreference( + preferenceKey = Preferences.historyEnabledKey, + defaultValue = true, + preferenceTitle = stringResource(R.string.history_enabled), + preferenceSummary = stringResource(R.string.history_summary) + ) + + SwitchPreference( + preferenceKey = Preferences.compactHistory, + defaultValue = true, + preferenceTitle = stringResource(R.string.compact_history), + preferenceSummary = stringResource(R.string.compact_history_summary) + ) + + SwitchPreference( + preferenceKey = Preferences.skipSimilarHistoryKey, + defaultValue = true, + preferenceTitle = stringResource(R.string.skip_similar_entries), + preferenceSummary = stringResource(R.string.skip_similar_entries_desc) + ) + } - SwitchPreference( - preferenceKey = Preferences.historyEnabledKey, - defaultValue = true, - preferenceTitle = stringResource(R.string.history_enabled), - preferenceSummary = stringResource(R.string.history_summary) - ) + item { + SettingsCategory( + title = stringResource(R.string.translation) + ) - SwitchPreference( - preferenceKey = Preferences.compactHistory, - defaultValue = true, - preferenceTitle = stringResource(R.string.compact_history), - preferenceSummary = stringResource(R.string.compact_history_summary) - ) + var translateAutomatically by remember { + mutableStateOf(Preferences.get(Preferences.translateAutomatically, true)) + } - SwitchPreference( - preferenceKey = Preferences.skipSimilarHistoryKey, - defaultValue = true, - preferenceTitle = stringResource(R.string.skip_similar_entries), - preferenceSummary = stringResource(R.string.skip_similar_entries_desc) - ) + SwitchPreference( + preferenceKey = Preferences.translateAutomatically, + defaultValue = true, + preferenceTitle = stringResource(R.string.translate_automatically), + preferenceSummary = stringResource(R.string.translate_automatically_summary) + ) { + translateAutomatically = it } - item { - SettingsCategory( - title = stringResource(R.string.translation) + AnimatedVisibility(visible = translateAutomatically) { + SliderPreference( + preferenceKey = Preferences.fetchDelay, + preferenceTitle = stringResource(R.string.fetch_delay), + preferenceSummary = stringResource(R.string.fetch_delay_summary), + defaultValue = 500f, + minValue = 100f, + maxValue = 1000f, + stepSize = 100f ) + } - var translateAutomatically by remember { - mutableStateOf(Preferences.get(Preferences.translateAutomatically, true)) - } - - SwitchPreference( - preferenceKey = Preferences.translateAutomatically, - defaultValue = true, - preferenceTitle = stringResource(R.string.translate_automatically), - preferenceSummary = stringResource(R.string.translate_automatically_summary) - ) { - translateAutomatically = it - } - - AnimatedVisibility(visible = translateAutomatically) { - SliderPreference( - preferenceKey = Preferences.fetchDelay, - preferenceTitle = stringResource(R.string.fetch_delay), - preferenceSummary = stringResource(R.string.fetch_delay_summary), - defaultValue = 500f, - minValue = 100f, - maxValue = 1000f, - stepSize = 100f - ) - } + SwitchPreference( + preferenceKey = Preferences.showAdditionalInfo, + defaultValue = true, + preferenceTitle = stringResource(R.string.additional_info), + preferenceSummary = stringResource(R.string.additional_info_summary) + ) + } - SwitchPreference( - preferenceKey = Preferences.showAdditionalInfo, - defaultValue = true, - preferenceTitle = stringResource(R.string.additional_info), - preferenceSummary = stringResource(R.string.additional_info_summary) + item { + SettingsCategory( + title = stringResource(R.string.simultaneous_translation) + ) + + SwitchPreference( + preferenceKey = Preferences.simultaneousTranslationKey, + defaultValue = false, + preferenceTitle = stringResource(R.string.simultaneous_translation), + preferenceSummary = stringResource( + R.string.simultaneous_translation_summary ) + ) { + enableSimultaneousTranslation = it } - item { - SettingsCategory( - title = stringResource(R.string.simultaneous_translation) - ) - - SwitchPreference( - preferenceKey = Preferences.simultaneousTranslationKey, - defaultValue = false, - preferenceTitle = stringResource(R.string.simultaneous_translation), - preferenceSummary = stringResource( - R.string.simultaneous_translation_summary - ) - ) { - enableSimultaneousTranslation = it - } - - AnimatedVisibility(visible = enableSimultaneousTranslation) { - Spacer( - modifier = Modifier - .height(10.dp) - ) - PreferenceItem( - title = stringResource(R.string.enabled_engines), - summary = stringResource(R.string.enabled_engines_summary), - modifier = Modifier.fillMaxWidth() - ) { - showEngineSelectDialog = true - } - } + AnimatedVisibility(visible = enableSimultaneousTranslation) { Spacer( modifier = Modifier .height(10.dp) ) - - val charCounterLimits = listOf(stringResource(R.string.none), "50", "100", "150", "200", "300", "400", "500", "1000", "2000", "3000", "5000") - ListPreference( - title = stringResource(R.string.character_warning_limit), - summary = stringResource(R.string.character_warning_limit_summary), - preferenceKey = Preferences.charCounterLimitKey, - defaultValue = charCounterLimits.first(), - entries = charCounterLimits, - values = charCounterLimits.map { - if (it.all { char -> char.isDigit() }) it else "" - } - ) - - Spacer( - modifier = Modifier - .height(15.dp) - ) + PreferenceItem( + title = stringResource(R.string.enabled_engines), + summary = stringResource(R.string.enabled_engines_summary), + modifier = Modifier.fillMaxWidth() + ) { + showEngineSelectDialog = true + } } + Spacer( + modifier = Modifier + .height(10.dp) + ) + + val charCounterLimits = listOf( + stringResource(R.string.none), + "50", + "100", + "150", + "200", + "300", + "400", + "500", + "1000", + "2000", + "3000", + "5000" + ) + ListPreference( + title = stringResource(R.string.character_warning_limit), + summary = stringResource(R.string.character_warning_limit_summary), + preferenceKey = Preferences.charCounterLimitKey, + defaultValue = charCounterLimits.first(), + entries = charCounterLimits, + values = charCounterLimits.map { + if (it.all { char -> char.isDigit() }) it else "" + } + ) + + Spacer( + modifier = Modifier + .height(15.dp) + ) } } } From adc3a9775b656ba0e1024e9a752087626baa896c Mon Sep 17 00:00:00 2001 From: doomsdayrs Date: Sun, 2 Jul 2023 13:04:23 -0400 Subject: [PATCH 2/4] Add title to ThemeModeDialog Helps make the dialog easier to understand. Solved by adding an optional parameter to ListPreferenceDialog for a title. --- .../com/bnyro/translate/ui/components/ThemeModeDialog.kt | 1 + .../translate/ui/components/prefs/ListPreferenceDialog.kt | 5 +++++ app/src/main/res/values/strings.xml | 1 + 3 files changed, 7 insertions(+) diff --git a/app/src/main/java/com/bnyro/translate/ui/components/ThemeModeDialog.kt b/app/src/main/java/com/bnyro/translate/ui/components/ThemeModeDialog.kt index 0cf0aed5b..d90976346 100644 --- a/app/src/main/java/com/bnyro/translate/ui/components/ThemeModeDialog.kt +++ b/app/src/main/java/com/bnyro/translate/ui/components/ThemeModeDialog.kt @@ -32,6 +32,7 @@ fun ThemeModeDialog( ) { val activity = LocalContext.current as MainActivity ListPreferenceDialog( + title= stringResource(R.string.select_theme), preferenceKey = Preferences.themeModeKey, onDismissRequest = { onDismiss.invoke() diff --git a/app/src/main/java/com/bnyro/translate/ui/components/prefs/ListPreferenceDialog.kt b/app/src/main/java/com/bnyro/translate/ui/components/prefs/ListPreferenceDialog.kt index 5b4650705..24738e0b1 100644 --- a/app/src/main/java/com/bnyro/translate/ui/components/prefs/ListPreferenceDialog.kt +++ b/app/src/main/java/com/bnyro/translate/ui/components/prefs/ListPreferenceDialog.kt @@ -34,10 +34,15 @@ fun ListPreferenceDialog( onDismissRequest: () -> Unit, options: List, currentValue: Int? = null, + title: String? = null, onOptionSelected: (ListPreferenceOption) -> Unit = {} ) { AlertDialog( onDismissRequest = onDismissRequest, + title = { + if (title != null) + Text(title) + }, text = { LazyColumn { items(options) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0f35676db..0481e705c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -83,4 +83,5 @@ Download and apply languages for text recognition from images. Please download language data in the settings first. Please select a language for the text recognition of images in the settings. + Select Theme \ No newline at end of file From 5873a74962197ccedda7f7306c763b5cb61327b5 Mon Sep 17 00:00:00 2001 From: doomsdayrs Date: Sun, 2 Jul 2023 13:10:29 -0400 Subject: [PATCH 3/4] Add highlight to selected theme Clarifies what is currently being used. Solved by adding an optional isSelected parameter to ListPreferenceOption & SelectableItem. --- .../com/bnyro/translate/obj/ListPreferenceOption.kt | 3 ++- .../bnyro/translate/ui/components/SelectableItem.kt | 5 ++++- .../bnyro/translate/ui/components/ThemeModeDialog.kt | 11 +++++++---- .../ui/components/prefs/ListPreferenceDialog.kt | 3 ++- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/bnyro/translate/obj/ListPreferenceOption.kt b/app/src/main/java/com/bnyro/translate/obj/ListPreferenceOption.kt index ca71582ce..980c8466e 100644 --- a/app/src/main/java/com/bnyro/translate/obj/ListPreferenceOption.kt +++ b/app/src/main/java/com/bnyro/translate/obj/ListPreferenceOption.kt @@ -19,5 +19,6 @@ package com.bnyro.translate.obj data class ListPreferenceOption( val name: String, - val value: Int + val value: Int, + val isSelected: Boolean = false ) diff --git a/app/src/main/java/com/bnyro/translate/ui/components/SelectableItem.kt b/app/src/main/java/com/bnyro/translate/ui/components/SelectableItem.kt index c27bd38a8..9be43faf3 100644 --- a/app/src/main/java/com/bnyro/translate/ui/components/SelectableItem.kt +++ b/app/src/main/java/com/bnyro/translate/ui/components/SelectableItem.kt @@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -33,6 +34,7 @@ import androidx.compose.ui.unit.dp @Composable fun SelectableItem( text: String, + isSelected: Boolean = false, onClick: () -> Unit = {} ) { Card( @@ -54,7 +56,8 @@ fun SelectableItem( text = text, modifier = Modifier .fillMaxWidth() - .padding(15.dp) + .padding(15.dp), + color = if (isSelected) MaterialTheme.colorScheme.primary else Color.Unspecified ) } } diff --git a/app/src/main/java/com/bnyro/translate/ui/components/ThemeModeDialog.kt b/app/src/main/java/com/bnyro/translate/ui/components/ThemeModeDialog.kt index d90976346..56eaa1bfe 100644 --- a/app/src/main/java/com/bnyro/translate/ui/components/ThemeModeDialog.kt +++ b/app/src/main/java/com/bnyro/translate/ui/components/ThemeModeDialog.kt @@ -32,7 +32,7 @@ fun ThemeModeDialog( ) { val activity = LocalContext.current as MainActivity ListPreferenceDialog( - title= stringResource(R.string.select_theme), + title = stringResource(R.string.select_theme), preferenceKey = Preferences.themeModeKey, onDismissRequest = { onDismiss.invoke() @@ -40,15 +40,18 @@ fun ThemeModeDialog( options = listOf( ListPreferenceOption( name = stringResource(R.string.theme_auto), - value = ThemeMode.AUTO + value = ThemeMode.AUTO, + isSelected = activity.themeMode == ThemeMode.AUTO ), ListPreferenceOption( name = stringResource(R.string.theme_light), - value = ThemeMode.LIGHT + value = ThemeMode.LIGHT, + isSelected = activity.themeMode == ThemeMode.LIGHT ), ListPreferenceOption( name = stringResource(R.string.theme_dark), - value = ThemeMode.DARK + value = ThemeMode.DARK, + isSelected = activity.themeMode == ThemeMode.DARK ) ), onOptionSelected = { diff --git a/app/src/main/java/com/bnyro/translate/ui/components/prefs/ListPreferenceDialog.kt b/app/src/main/java/com/bnyro/translate/ui/components/prefs/ListPreferenceDialog.kt index 24738e0b1..2d87c1363 100644 --- a/app/src/main/java/com/bnyro/translate/ui/components/prefs/ListPreferenceDialog.kt +++ b/app/src/main/java/com/bnyro/translate/ui/components/prefs/ListPreferenceDialog.kt @@ -55,7 +55,8 @@ fun ListPreferenceDialog( ) onOptionSelected.invoke(it) onDismissRequest.invoke() - } + }, + isSelected = it.isSelected ) } } From 3e13f0cdbdb729dc5eea89ae60d48c05f41ecb34 Mon Sep 17 00:00:00 2001 From: doomsdayrs Date: Sun, 2 Jul 2023 13:36:58 -0400 Subject: [PATCH 4/4] Fade in/out Accent Color picker instead of hiding. The issue was that 250.dp was just empty for no apparent reason. By instead having the picker disabled and at half alpha, the user can see what is the other option. This is resolved by using animateFloatAsState to control the alpha, and adding a pointer input override. --- .../ui/components/prefs/AccentColorPref.kt | 116 +++++++++++------- 1 file changed, 71 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/bnyro/translate/ui/components/prefs/AccentColorPref.kt b/app/src/main/java/com/bnyro/translate/ui/components/prefs/AccentColorPref.kt index 8626e1bf0..b6b580f03 100644 --- a/app/src/main/java/com/bnyro/translate/ui/components/prefs/AccentColorPref.kt +++ b/app/src/main/java/com/bnyro/translate/ui/components/prefs/AccentColorPref.kt @@ -18,8 +18,11 @@ package com.bnyro.translate.ui.components.prefs import android.os.Build -import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -39,6 +42,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerInputChange +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -105,54 +113,72 @@ fun AccentColorPrefDialog( ) } } - Row( - modifier = Modifier.height(250.dp) - ) { - AnimatedVisibility( - visible = color != null - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - listOf("R", "G", "B").forEachIndexed { index, c -> - val startIndex = index * 2 - color?.let { - ColorSlider( - label = c, - value = it.substring(startIndex, startIndex + 2).toInt(16), - onChange = { colorInt -> - var newHex = colorInt.toHexString() - if (newHex.length == 1) newHex = "0$newHex" - color = StringBuilder(it).apply { - setCharAt(startIndex, newHex[0]) - setCharAt(startIndex + 1, newHex[1]) - }.toString() - } - ) - } - } - Spacer(modifier = Modifier.height(20.dp)) - color?.let { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Box( - Modifier.size(50.dp).background( - MaterialTheme.colorScheme.primary, - CircleShape - ) - ) - Text(text = " => ", fontSize = 27.sp) - Box( - modifier = Modifier.size(50.dp).background( - it.hexToColor(), - CircleShape - ) - ) + + val isColorPickerEnabled = color != null + val imageAlpha: Float by animateFloatAsState( + targetValue = if (isColorPickerEnabled) 1f else .5f, + animationSpec = tween( + durationMillis = 250, + easing = LinearEasing, + ) + ) + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.height(250.dp).alpha(imageAlpha).let { + if (isColorPickerEnabled) { + it + } else { + // disable input + it.pointerInput(Unit){ + awaitPointerEventScope { + while (true) { + awaitPointerEvent(pass = PointerEventPass.Initial) + .changes + .forEach(PointerInputChange::consume) + } } } } } + ) { + listOf("R", "G", "B").forEachIndexed { index, c -> + val startIndex = index * 2 + ColorSlider( + label = c, + value = color?.substring(startIndex, startIndex + 2)?.toInt(16) ?: 0, + onChange = { colorInt -> + var newHex = colorInt.toHexString() + if (newHex.length == 1) newHex = "0$newHex" + color = StringBuilder(color).apply { + setCharAt(startIndex, newHex[0]) + setCharAt(startIndex + 1, newHex[1]) + }.toString() + } + ) + } + Spacer(modifier = Modifier.height(20.dp)) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Box( + Modifier + .size(50.dp) + .background( + MaterialTheme.colorScheme.primary, + CircleShape + ) + ) + Text(text = " => ", fontSize = 27.sp) + Box( + modifier = Modifier + .size(50.dp) + .background( + color?.hexToColor() ?: Color.Black, + CircleShape + ) + ) + } } } }