Merge pull request #3606 from vitorpamplona/claude/modernize-ui-preferences-56auhn

Redesign settings screen with segmented controls and sections
This commit is contained in:
Vitor Pamplona
2026-07-16 23:54:05 -04:00
committed by GitHub
3 changed files with 407 additions and 300 deletions
@@ -21,29 +21,40 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings
import android.content.Context
import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.border
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.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.clip
@@ -51,45 +62,44 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.os.LocaleListCompat
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.model.AccentColorType
import com.vitorpamplona.amethyst.model.BooleanType
import com.vitorpamplona.amethyst.model.ConnectivityType
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.FontFamilyType
import com.vitorpamplona.amethyst.model.FontSizeType
import com.vitorpamplona.amethyst.model.ThemeType
import com.vitorpamplona.amethyst.model.UiSettingsFlow
import com.vitorpamplona.amethyst.model.parseBooleanType
import com.vitorpamplona.amethyst.model.parseConnectivityType
import com.vitorpamplona.amethyst.model.parseFeatureSetType
import com.vitorpamplona.amethyst.model.parseFontFamilyType
import com.vitorpamplona.amethyst.model.parseFontSizeType
import com.vitorpamplona.amethyst.model.parseThemeType
import com.vitorpamplona.amethyst.ui.components.SpinnerSelectionDialog
import com.vitorpamplona.amethyst.ui.components.TextSpinner
import com.vitorpamplona.amethyst.ui.components.TitleExplainer
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size20dp
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
import com.vitorpamplona.amethyst.ui.theme.contentColorOnAccent
import com.vitorpamplona.amethyst.ui.theme.isLight
import com.vitorpamplona.amethyst.ui.theme.previewColor
import com.vitorpamplona.amethyst.ui.theme.toFontFamily
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.flow.MutableStateFlow
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import java.io.IOException
@@ -104,42 +114,384 @@ fun SettingsScreen(
TopBarWithBackButton(stringRes(id = R.string.application_preferences), nav)
},
) {
Column(Modifier.padding(it)) {
SettingsScreen(accountViewModel.settings.uiSettingsFlow)
SettingsScreen(accountViewModel.settings.uiSettingsFlow, Modifier.padding(it))
}
}
// Full-screen preview: the real top bar + the redesigned content, shown side by side in dark and
// light so both grounds (and the card-hairline contrast) can be eyeballed at once.
@Preview(name = "UI Preferences", device = "spec:width=2160px,height=2340px,dpi=440")
@Composable
fun SettingsScreenPreview() {
ThemeComparisonRow {
Scaffold(
topBar = {
TopBarWithBackButton(stringRes(id = R.string.application_preferences), EmptyNav())
},
) { padding ->
SettingsScreen(UiSettingsFlow(), Modifier.padding(padding))
}
}
}
@Preview(device = "spec:width=2160px,height=2340px,dpi=440")
@Composable
fun SettingsScreenPreview() {
ThemeComparisonRow {
SettingsScreen(UiSettingsFlow())
fun SettingsScreen(
sharedPrefs: UiSettingsFlow,
modifier: Modifier = Modifier,
) {
Column(
modifier =
modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalArrangement = Arrangement.spacedBy(20.dp),
) {
SettingsSection(R.string.settings_section_appearance) {
ThemeTile(sharedPrefs)
SettingsDivider()
AccentColorTile(sharedPrefs)
SettingsDivider()
FontFamilyTile(sharedPrefs)
SettingsDivider()
FontSizeTile(sharedPrefs)
}
SettingsSection(R.string.settings_section_media) {
ImagePreviewTile(sharedPrefs)
SettingsDivider()
VideoPlaybackTile(sharedPrefs)
SettingsDivider()
AutoplayVideosTile(sharedPrefs)
SettingsDivider()
UrlPreviewTile(sharedPrefs)
SettingsDivider()
ProfilePictureTile(sharedPrefs)
}
SettingsSection(R.string.settings_section_general) {
LanguageTile(sharedPrefs)
SettingsDivider()
UiModeTile(sharedPrefs)
SettingsDivider()
ImmersiveScrollingTile(sharedPrefs)
}
}
}
/**
* A [SettingsBlockTile] whose control is a full-width [SingleChoiceSegmentedButtonRow].
* This is the in-screen replacement for the old dropdown ([TextSpinner]) rows: every
* option is visible and one tap away. Best for 2–4 mutually-exclusive options.
*
* [optionTextStyle] lets each option render its own label preview — e.g. the font tiles
* draw each label in the very typeface / size it selects, so the row demonstrates the
* choices instead of only naming them.
*/
@Composable
private fun <T> SegmentedChoiceTile(
icon: MaterialSymbol,
@StringRes title: Int,
@StringRes description: Int,
options: List<T>,
labelRes: (T) -> Int,
selected: T,
onSelect: (T) -> Unit,
optionTextStyle: (@Composable (T) -> TextStyle)? = null,
) {
SettingsBlockTile(
icon = icon,
title = stringRes(title),
description = stringRes(description),
) {
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
options.forEachIndexed { index, option ->
SegmentedButton(
selected = option == selected,
onClick = { onSelect(option) },
shape = SegmentedButtonDefaults.itemShape(index = index, count = options.size),
// Drop the default check icon: the fill already signals selection, and the icon
// steals ~24dp that the label needs in a 3–4-up row.
icon = {},
) {
Text(
text = stringRes(labelRes(option)),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = optionTextStyle?.invoke(option) ?: LocalTextStyle.current,
)
}
}
}
}
}
/** A [SettingsControlRow] with a [Switch] backed by a [BooleanType] flow (ALWAYS ⇔ on). */
@Composable
private fun BooleanSwitchTile(
flow: MutableStateFlow<BooleanType>,
icon: MaterialSymbol,
@StringRes title: Int,
@StringRes description: Int,
) {
val value by flow.collectAsState()
val checked = value == BooleanType.ALWAYS
// Explicit Unit return: tryEmit returns Boolean, so the lambda would otherwise infer as
// (Boolean) -> Boolean and not fit Switch.onCheckedChange / SettingsControlRow.onClick.
val toggle: (Boolean) -> Unit = { isOn -> flow.tryEmit(if (isOn) BooleanType.ALWAYS else BooleanType.NEVER) }
SettingsControlRow(
icon = icon,
title = stringRes(title),
description = stringRes(description),
onClick = { toggle(!checked) },
) {
Switch(checked = checked, onCheckedChange = toggle)
}
}
@Composable
fun SettingsScreen(sharedPrefs: UiSettingsFlow) {
Column(
Modifier
.fillMaxSize()
.padding(top = Size10dp, start = Size20dp, end = Size20dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = RowColSpacing,
private fun ThemeTile(sharedPrefs: UiSettingsFlow) {
val theme by sharedPrefs.theme.collectAsState()
SegmentedChoiceTile(
icon = MaterialSymbols.BrightnessMedium,
title = R.string.theme,
description = R.string.theme_description,
options = ThemeType.entries,
labelRes = { it.resourceId },
selected = theme,
onSelect = { sharedPrefs.theme.tryEmit(it) },
)
}
// Compact labels for the connectivity segmented rows — "Unmetered WiFi" does not fit a 3-up
// segment, so it collapses to "Wi-Fi"; the others are already short.
@StringRes
private fun ConnectivityType.shortLabelRes(): Int =
when (this) {
ConnectivityType.ALWAYS -> R.string.connectivity_type_always
ConnectivityType.WIFI_ONLY -> R.string.connectivity_type_unmetered_wifi_only_short
ConnectivityType.NEVER -> R.string.connectivity_type_never
}
@StringRes
private fun FontFamilyType.shortLabelRes(): Int =
when (this) {
FontFamilyType.SYSTEM -> R.string.font_family_system_short
FontFamilyType.SANS_SERIF -> R.string.font_family_sans_serif_short
FontFamilyType.SERIF -> R.string.font_family_serif_short
FontFamilyType.MONOSPACE -> R.string.font_family_monospace_short
}
@StringRes
private fun FeatureSetType.shortLabelRes(): Int =
when (this) {
FeatureSetType.COMPLETE -> R.string.ui_feature_set_type_complete_short
FeatureSetType.SIMPLIFIED -> R.string.ui_feature_set_type_simplified_short
FeatureSetType.PERFORMANCE -> R.string.ui_feature_set_type_performance_short
}
@Composable
private fun FontFamilyTile(sharedPrefs: UiSettingsFlow) {
val fontFamily by sharedPrefs.fontFamily.collectAsState()
SegmentedChoiceTile(
icon = MaterialSymbols.Description,
title = R.string.font_family,
description = R.string.font_family_description,
options = FontFamilyType.entries,
labelRes = { it.shortLabelRes() },
selected = fontFamily,
onSelect = { sharedPrefs.fontFamily.tryEmit(it) },
// Draw each option's name in the very typeface it selects.
optionTextStyle = { LocalTextStyle.current.copy(fontFamily = it.toFontFamily() ?: FontFamily.Default) },
)
}
@Composable
private fun FontSizeTile(sharedPrefs: UiSettingsFlow) {
val fontSize by sharedPrefs.fontSize.collectAsState()
SegmentedChoiceTile(
icon = MaterialSymbols.ZoomOutMap,
title = R.string.font_size,
description = R.string.font_size_description,
options = FontSizeType.entries,
labelRes = { it.resourceId },
selected = fontSize,
onSelect = { sharedPrefs.fontSize.tryEmit(it) },
// Scale each label to preview its size — Small looks small, Huge looks huge. A small 13sp
// base keeps the spread gentle (~11–17sp) so the row stays tidy instead of lurching taller.
optionTextStyle = { LocalTextStyle.current.copy(fontSize = 13.sp * it.scale) },
)
}
@Composable
private fun UiModeTile(sharedPrefs: UiSettingsFlow) {
val featureSet by sharedPrefs.featureSet.collectAsState()
SegmentedChoiceTile(
icon = MaterialSymbols.Tune,
title = R.string.ui_style,
description = R.string.ui_style_description,
options = FeatureSetType.entries,
labelRes = { it.shortLabelRes() },
selected = featureSet,
onSelect = { sharedPrefs.featureSet.tryEmit(it) },
)
}
@Composable
private fun ImagePreviewTile(sharedPrefs: UiSettingsFlow) {
val value by sharedPrefs.automaticallyShowImages.collectAsState()
SegmentedChoiceTile(
icon = MaterialSymbols.Image,
title = R.string.automatically_load_images_gifs,
description = R.string.automatically_load_images_gifs_description,
options = ConnectivityType.entries,
labelRes = { it.shortLabelRes() },
selected = value,
onSelect = { sharedPrefs.automaticallyShowImages.tryEmit(it) },
)
}
@Composable
private fun VideoPlaybackTile(sharedPrefs: UiSettingsFlow) {
val value by sharedPrefs.automaticallyStartPlayback.collectAsState()
SegmentedChoiceTile(
icon = MaterialSymbols.Videocam,
title = R.string.automatically_play_videos,
description = R.string.automatically_play_videos_description,
options = ConnectivityType.entries,
labelRes = { it.shortLabelRes() },
selected = value,
onSelect = { sharedPrefs.automaticallyStartPlayback.tryEmit(it) },
)
}
@Composable
private fun UrlPreviewTile(sharedPrefs: UiSettingsFlow) {
val value by sharedPrefs.automaticallyShowUrlPreview.collectAsState()
SegmentedChoiceTile(
icon = MaterialSymbols.Link,
title = R.string.automatically_show_url_preview,
description = R.string.automatically_show_url_preview_description,
options = ConnectivityType.entries,
labelRes = { it.shortLabelRes() },
selected = value,
onSelect = { sharedPrefs.automaticallyShowUrlPreview.tryEmit(it) },
)
}
@Composable
private fun ProfilePictureTile(sharedPrefs: UiSettingsFlow) {
val value by sharedPrefs.automaticallyShowProfilePictures.collectAsState()
SegmentedChoiceTile(
icon = MaterialSymbols.AccountCircle,
title = R.string.automatically_show_profile_picture,
description = R.string.automatically_show_profile_picture_description,
options = ConnectivityType.entries,
labelRes = { it.shortLabelRes() },
selected = value,
onSelect = { sharedPrefs.automaticallyShowProfilePictures.tryEmit(it) },
)
}
@Composable
private fun AutoplayVideosTile(sharedPrefs: UiSettingsFlow) {
BooleanSwitchTile(
flow = sharedPrefs.automaticallyPlayVideos,
icon = MaterialSymbols.PlayCircle,
title = R.string.autoplay_videos,
description = R.string.autoplay_videos_description,
)
}
@Composable
private fun ImmersiveScrollingTile(sharedPrefs: UiSettingsFlow) {
BooleanSwitchTile(
flow = sharedPrefs.automaticallyHideNavigationBars,
icon = MaterialSymbols.Fullscreen,
title = R.string.automatically_hide_nav_bars,
description = R.string.automatically_hide_nav_bars_description,
)
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun AccentColorTile(sharedPrefs: UiSettingsFlow) {
val accent by sharedPrefs.accentColor.collectAsState()
val dark = !MaterialTheme.colorScheme.isLight
SettingsBlockTile(
icon = MaterialSymbols.Circle,
title = stringRes(R.string.accent_color),
description = stringRes(R.string.accent_color_description),
) {
ShowLanguageChoice(sharedPrefs)
ShowThemeChoice(sharedPrefs)
ShowAccentColorChoice(sharedPrefs)
ShowFontFamilyChoice(sharedPrefs)
ShowFontSizeChoice(sharedPrefs)
ShowImagePreviewChoice(sharedPrefs)
ShowVideoPlaybackChoice(sharedPrefs)
AutoplayVideosChoice(sharedPrefs)
ShowUrlPreviewChoice(sharedPrefs)
ShowProfilePictureChoice(sharedPrefs)
ImmersiveScrollingChoice(sharedPrefs)
FeatureSetChoice(sharedPrefs)
// FlowRow so every swatch stays visible (wraps to a second line on narrow screens) instead
// of scrolling the last ones off-edge with no affordance.
FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
AccentColorType.entries.forEach { option ->
AccentColorSwatch(
color = option.previewColor(dark),
label = stringRes(option.resourceId),
selected = option == accent,
onClick = { sharedPrefs.accentColor.tryEmit(option) },
)
}
}
}
}
// Language can't be a segmented row (50+ locales), so it reads as a disclosure row — icon + title +
// the current language on the right + a chevron — and tapping anywhere opens the picker dialog. That
// matches the app's other "opens a picker" rows instead of leaving a lone text-field dropdown here.
@Composable
private fun LanguageTile(sharedPrefs: UiSettingsFlow) {
val context = LocalContext.current
val languageEntries = remember { context.getLangPreferenceDropdownEntries() }
val languageList = remember { languageEntries.keys.map { TitleExplainer(it) }.toImmutableList() }
val language by sharedPrefs.preferredLanguage.collectAsState()
val languageIndex = getLanguageIndex(languageEntries, language)
val currentLabel = languageList.getOrNull(languageIndex)?.title ?: ""
var showPicker by remember { mutableStateOf(false) }
SettingsControlRow(
icon = MaterialSymbols.Language,
title = stringRes(R.string.language),
description = stringRes(R.string.language_description),
onClick = { showPicker = true },
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = currentLabel,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.widthIn(max = 140.dp),
)
Icon(
symbol = MaterialSymbols.ChevronRight,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 4.dp).size(20.dp),
)
}
}
if (showPicker) {
SpinnerSelectionDialog(
options = languageList,
onDismiss = { showPicker = false },
) { index ->
showPicker = false
sharedPrefs.preferredLanguage.tryEmit(languageEntries[languageList[index].title])
}
}
}
@@ -195,80 +547,6 @@ fun getLanguageIndex(
return languageIndex
}
@Composable
fun ShowLanguageChoice(sharedPrefs: UiSettingsFlow) {
val context = LocalContext.current
val languageEntries = remember { context.getLangPreferenceDropdownEntries() }
val languageList = remember { languageEntries.keys.map { TitleExplainer(it) }.toImmutableList() }
val language by sharedPrefs.preferredLanguage.collectAsState()
val languageIndex = getLanguageIndex(languageEntries, language)
SettingsRow(
R.string.language,
R.string.language_description,
languageList,
languageIndex,
) {
sharedPrefs.preferredLanguage.tryEmit(languageEntries[languageList[it].title])
}
}
@Composable
fun ShowThemeChoice(sharedPrefs: UiSettingsFlow) {
val themeOptions =
persistentListOf(
TitleExplainer(stringRes(ThemeType.SYSTEM.resourceId)),
TitleExplainer(stringRes(ThemeType.LIGHT.resourceId)),
TitleExplainer(stringRes(ThemeType.DARK.resourceId)),
)
val themeIndex by sharedPrefs.theme.collectAsState()
SettingsRow(
R.string.theme,
R.string.theme_description,
themeOptions,
themeIndex.screenCode,
) {
sharedPrefs.theme.tryEmit(parseThemeType(it))
}
}
@Composable
fun ShowAccentColorChoice(sharedPrefs: UiSettingsFlow) {
val accent by sharedPrefs.accentColor.collectAsState()
val dark = !MaterialTheme.colorScheme.isLight
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
text = stringRes(R.string.accent_color),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
text = stringRes(R.string.accent_color_description),
style = MaterialTheme.typography.bodySmall,
color = Color.Gray,
)
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
AccentColorType.entries.forEach { option ->
AccentColorSwatch(
color = option.previewColor(dark),
label = stringRes(option.resourceId),
selected = option == accent,
onClick = { sharedPrefs.accentColor.tryEmit(option) },
)
}
}
}
}
@Composable
private fun AccentColorSwatch(
color: Color,
@@ -303,195 +581,6 @@ private fun AccentColorSwatch(
}
}
@Composable
fun ShowFontFamilyChoice(sharedPrefs: UiSettingsFlow) {
val fontOptions =
persistentListOf(
TitleExplainer(stringRes(FontFamilyType.SYSTEM.resourceId)),
TitleExplainer(stringRes(FontFamilyType.SANS_SERIF.resourceId)),
TitleExplainer(stringRes(FontFamilyType.SERIF.resourceId)),
TitleExplainer(stringRes(FontFamilyType.MONOSPACE.resourceId)),
)
val fontIndex by sharedPrefs.fontFamily.collectAsState()
SettingsRow(
R.string.font_family,
R.string.font_family_description,
fontOptions,
fontIndex.screenCode,
) {
sharedPrefs.fontFamily.tryEmit(parseFontFamilyType(it))
}
}
@Composable
fun ShowFontSizeChoice(sharedPrefs: UiSettingsFlow) {
val fontSizeOptions =
persistentListOf(
TitleExplainer(stringRes(FontSizeType.SMALL.resourceId)),
TitleExplainer(stringRes(FontSizeType.NORMAL.resourceId)),
TitleExplainer(stringRes(FontSizeType.LARGE.resourceId)),
TitleExplainer(stringRes(FontSizeType.HUGE.resourceId)),
)
val fontSizeIndex by sharedPrefs.fontSize.collectAsState()
SettingsRow(
R.string.font_size,
R.string.font_size_description,
fontSizeOptions,
fontSizeIndex.screenCode,
) {
sharedPrefs.fontSize.tryEmit(parseFontSizeType(it))
}
}
@Composable
fun ShowImagePreviewChoice(sharedPrefs: UiSettingsFlow) {
val connectivityBasedOptions =
persistentListOf(
TitleExplainer(stringRes(ConnectivityType.ALWAYS.resourceId)),
TitleExplainer(stringRes(ConnectivityType.WIFI_ONLY.resourceId)),
TitleExplainer(stringRes(ConnectivityType.NEVER.resourceId)),
)
val showImagesIndex by sharedPrefs.automaticallyShowImages.collectAsState()
SettingsRow(
R.string.automatically_load_images_gifs,
R.string.automatically_load_images_gifs_description,
connectivityBasedOptions,
showImagesIndex.screenCode,
) {
sharedPrefs.automaticallyShowImages.tryEmit(parseConnectivityType(it))
}
}
@Composable
fun ShowVideoPlaybackChoice(sharedPrefs: UiSettingsFlow) {
val connectivityBasedOptions =
persistentListOf(
TitleExplainer(stringRes(ConnectivityType.ALWAYS.resourceId)),
TitleExplainer(stringRes(ConnectivityType.WIFI_ONLY.resourceId)),
TitleExplainer(stringRes(ConnectivityType.NEVER.resourceId)),
)
val videoIndex by sharedPrefs.automaticallyStartPlayback.collectAsState()
SettingsRow(
R.string.automatically_play_videos,
R.string.automatically_play_videos_description,
connectivityBasedOptions,
videoIndex.screenCode,
) {
sharedPrefs.automaticallyStartPlayback.tryEmit(parseConnectivityType(it))
}
}
@Composable
fun AutoplayVideosChoice(sharedPrefs: UiSettingsFlow) {
val autoplayIndex by sharedPrefs.automaticallyPlayVideos.collectAsState()
val booleanItems =
persistentListOf(
TitleExplainer(stringRes(ConnectivityType.ALWAYS.resourceId)),
TitleExplainer(stringRes(ConnectivityType.NEVER.resourceId)),
)
SettingsRow(
R.string.autoplay_videos,
R.string.autoplay_videos_description,
booleanItems,
autoplayIndex.screenCode,
) {
sharedPrefs.automaticallyPlayVideos.tryEmit(parseBooleanType(it))
}
}
@Composable
fun ShowUrlPreviewChoice(sharedPrefs: UiSettingsFlow) {
val connectivityBasedOptions =
persistentListOf(
TitleExplainer(stringRes(ConnectivityType.ALWAYS.resourceId)),
TitleExplainer(stringRes(ConnectivityType.WIFI_ONLY.resourceId)),
TitleExplainer(stringRes(ConnectivityType.NEVER.resourceId)),
)
val linkIndex by sharedPrefs.automaticallyShowUrlPreview.collectAsState()
SettingsRow(
R.string.automatically_show_url_preview,
R.string.automatically_show_url_preview_description,
connectivityBasedOptions,
linkIndex.screenCode,
) {
sharedPrefs.automaticallyShowUrlPreview.tryEmit(parseConnectivityType(it))
}
}
@Composable
fun ShowProfilePictureChoice(sharedPrefs: UiSettingsFlow) {
val profilePictureIndex by sharedPrefs.automaticallyShowProfilePictures.collectAsState()
val connectivityBasedOptions =
persistentListOf(
TitleExplainer(stringRes(ConnectivityType.ALWAYS.resourceId)),
TitleExplainer(stringRes(ConnectivityType.WIFI_ONLY.resourceId)),
TitleExplainer(stringRes(ConnectivityType.NEVER.resourceId)),
)
SettingsRow(
R.string.automatically_show_profile_picture,
R.string.automatically_show_profile_picture_description,
connectivityBasedOptions,
profilePictureIndex.screenCode,
) {
sharedPrefs.automaticallyShowProfilePictures.tryEmit(parseConnectivityType(it))
}
}
@Composable
fun ImmersiveScrollingChoice(sharedPrefs: UiSettingsFlow) {
val hideNavBarsIndex by sharedPrefs.automaticallyHideNavigationBars.collectAsState()
val booleanItems =
persistentListOf(
TitleExplainer(stringRes(ConnectivityType.ALWAYS.resourceId)),
TitleExplainer(stringRes(ConnectivityType.NEVER.resourceId)),
)
SettingsRow(
R.string.automatically_hide_nav_bars,
R.string.automatically_hide_nav_bars_description,
booleanItems,
hideNavBarsIndex.screenCode,
) {
sharedPrefs.automaticallyHideNavigationBars.tryEmit(parseBooleanType(it))
}
}
@Composable
fun FeatureSetChoice(sharedPrefs: UiSettingsFlow) {
val featureItems =
persistentListOf(
TitleExplainer(stringRes(FeatureSetType.COMPLETE.resourceId)),
TitleExplainer(stringRes(FeatureSetType.SIMPLIFIED.resourceId)),
TitleExplainer(stringRes(FeatureSetType.PERFORMANCE.resourceId)),
)
val featureSetIndex by sharedPrefs.featureSet.collectAsState()
SettingsRow(
R.string.ui_style,
R.string.ui_style_description,
featureItems,
featureSetIndex.screenCode,
) {
sharedPrefs.featureSet.tryEmit(parseFeatureSetType(it))
}
}
@Composable
fun SettingsRow(
name: Int,
@@ -80,7 +80,11 @@ internal fun SettingsSection(
shape = RoundedCornerShape(20.dp),
colors =
CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerLow,
// surfaceContainer (one step up from …Low) so the card separates from the page by
// tone rather than a hard outline. The neutral surface ramp puts the page at
// background #FDFDFD, where …Low #F7F7F7 was nearly invisible; #F2F2F2 reads as a
// soft card in light and #252525 stands clear of black in dark.
containerColor = MaterialTheme.colorScheme.surfaceContainer,
),
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp),
) {
+14
View File
@@ -1943,10 +1943,16 @@
<string name="connectivity_type_always">Always</string>
<string name="connectivity_type_unmetered_wifi_only">Unmetered WiFi</string>
<string name="connectivity_type_never">Never</string>
<!-- Compact label for the segmented control, where "Unmetered WiFi" does not fit. -->
<string name="connectivity_type_unmetered_wifi_only_short">Wi-Fi</string>
<string name="ui_feature_set_type_complete">Complete</string>
<string name="ui_feature_set_type_simplified">Simplified</string>
<string name="ui_feature_set_type_performance">Performance</string>
<!-- Compact labels for the segmented control. -->
<string name="ui_feature_set_type_complete_short">Full</string>
<string name="ui_feature_set_type_simplified_short">Simple</string>
<string name="ui_feature_set_type_performance_short">Fast</string>
<string name="gallery_type_classic">Classic</string>
<string name="gallery_type_modern">Modern</string>
@@ -1955,6 +1961,9 @@
<string name="light">Light</string>
<string name="dark">Dark</string>
<string name="application_preferences">Application Preferences</string>
<string name="settings_section_appearance">Appearance</string>
<string name="settings_section_media">Media &amp; Data</string>
<string name="settings_section_general">General</string>
<string name="wallet_connect">Wallet Connect</string>
<string name="language">Language</string>
<string name="theme">Theme</string>
@@ -1972,6 +1981,11 @@
<string name="font_family_sans_serif">Sans Serif</string>
<string name="font_family_serif">Serif</string>
<string name="font_family_monospace">Monospace</string>
<!-- Compact labels for the segmented control, rendered in each option's own typeface. -->
<string name="font_family_system_short">System</string>
<string name="font_family_sans_serif_short">Sans</string>
<string name="font_family_serif_short">Serif</string>
<string name="font_family_monospace_short">Mono</string>
<string name="font_size">Font Size</string>
<string name="font_size_description">Scale the text size across the app</string>
<string name="font_size_small">Small</string>