From 8d5d9f4af8d690f78a5d5c0528f99fde19a32f3d Mon Sep 17 00:00:00 2001 From: feby-saji Date: Fri, 20 Feb 2026 17:52:38 +0530 Subject: [PATCH 1/2] Improve settings UI --- .../feature/settings/impl/SettingsDialog.kt | 236 +++++++++++++----- .../impl/src/main/res/values/strings.xml | 2 +- gradle/libs.versions.toml | 4 +- 3 files changed, 175 insertions(+), 67 deletions(-) diff --git a/feature/settings/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/impl/SettingsDialog.kt b/feature/settings/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/impl/SettingsDialog.kt index b2758e286..2cc697f43 100644 --- a/feature/settings/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/impl/SettingsDialog.kt +++ b/feature/settings/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/impl/SettingsDialog.kt @@ -28,22 +28,35 @@ import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectableGroup +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Close import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.RadioButton +import androidx.compose.material3.SegmentedButton +import androidx.compose.material3.SegmentedButtonDefaults +import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler @@ -55,13 +68,11 @@ import androidx.compose.ui.window.DialogProperties import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.android.gms.oss.licenses.OssLicensesMenuActivity +import com.google.protobuf.option import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTextButton import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.designsystem.theme.supportsDynamicTheming import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig -import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig.DARK -import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig.FOLLOW_SYSTEM -import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig.LIGHT import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand.ANDROID import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand.DEFAULT @@ -70,6 +81,7 @@ import com.google.samples.apps.nowinandroid.feature.settings.impl.R.string import com.google.samples.apps.nowinandroid.feature.settings.impl.SettingsUiState.Loading import com.google.samples.apps.nowinandroid.feature.settings.impl.SettingsUiState.Success + @Composable fun SettingsDialog( onDismiss: () -> Unit, @@ -164,13 +176,14 @@ private fun ColumnScope.SettingsPanel( onChangeDarkThemeConfig: (darkThemeConfig: DarkThemeConfig) -> Unit, ) { SettingsDialogSectionTitle(text = stringResource(string.feature_settings_impl_theme)) - Column(Modifier.selectableGroup()) { - SettingsDialogThemeChooserRow( + Row(Modifier.selectableGroup()) { + SettingsDialogThemeRadioBtn( text = stringResource(string.feature_settings_impl_brand_default), selected = settings.brand == DEFAULT, onClick = { onChangeThemeBrand(DEFAULT) }, ) - SettingsDialogThemeChooserRow( + Spacer(Modifier.width(20.dp)) + SettingsDialogThemeRadioBtn( text = stringResource(string.feature_settings_impl_brand_android), selected = settings.brand == ANDROID, onClick = { onChangeThemeBrand(ANDROID) }, @@ -180,36 +193,65 @@ private fun ColumnScope.SettingsPanel( Column { SettingsDialogSectionTitle(text = stringResource(string.feature_settings_impl_dynamic_color_preference)) Column(Modifier.selectableGroup()) { - SettingsDialogThemeChooserRow( - text = stringResource(string.feature_settings_impl_dynamic_color_yes), - selected = settings.useDynamicColor, - onClick = { onChangeDynamicColorPreference(true) }, - ) - SettingsDialogThemeChooserRow( - text = stringResource(string.feature_settings_impl_dynamic_color_no), - selected = !settings.useDynamicColor, - onClick = { onChangeDynamicColorPreference(false) }, + + SettingsDialogThemeDynamicColorRow( + options = listOf( stringResource(string.feature_settings_impl_dynamic_color_yes), stringResource(string.feature_settings_impl_dynamic_color_no)), + selectedIndex = if (settings.useDynamicColor) 0 else 1, + onOptionSelected = { index -> + onChangeDynamicColorPreference(index == 0) + } ) +// SettingsDialogThemeChooserRow( +// text = stringResource(string.feature_settings_impl_dynamic_color_yes), +// selected = settings.useDynamicColor, +// onClick = { onChangeDynamicColorPreference(true) }, +// ) +// SettingsDialogThemeChooserRow( +// text = stringResource(string.feature_settings_impl_dynamic_color_no), +// selected = !settings.useDynamicColor, +// onClick = { onChangeDynamicColorPreference(false) }, +// ) } } } SettingsDialogSectionTitle(text = stringResource(string.feature_settings_impl_dark_mode_preference)) Column(Modifier.selectableGroup()) { - SettingsDialogThemeChooserRow( - text = stringResource(string.feature_settings_impl_dark_mode_config_system_default), - selected = settings.darkThemeConfig == FOLLOW_SYSTEM, - onClick = { onChangeDarkThemeConfig(FOLLOW_SYSTEM) }, - ) - SettingsDialogThemeChooserRow( - text = stringResource(string.feature_settings_impl_dark_mode_config_light), - selected = settings.darkThemeConfig == LIGHT, - onClick = { onChangeDarkThemeConfig(LIGHT) }, - ) - SettingsDialogThemeChooserRow( - text = stringResource(string.feature_settings_impl_dark_mode_config_dark), - selected = settings.darkThemeConfig == DARK, - onClick = { onChangeDarkThemeConfig(DARK) }, + val darkModeOptions = listOf( + stringResource(string.feature_settings_impl_dark_mode_config_system_default), + stringResource(string.feature_settings_impl_dark_mode_config_light), + stringResource(string.feature_settings_impl_dark_mode_config_dark) ) + + SettingsDialogThemeDarkModeRow( + options = darkModeOptions, + selectedIndex = when(settings.darkThemeConfig) { + DarkThemeConfig.FOLLOW_SYSTEM -> 0 + DarkThemeConfig.LIGHT -> 1 + DarkThemeConfig.DARK -> 2 + }, + onOptionSelected = { index ->val newConfig = when (index) { + 0 -> DarkThemeConfig.FOLLOW_SYSTEM + 1 -> DarkThemeConfig.LIGHT + else -> DarkThemeConfig.DARK + } + onChangeDarkThemeConfig(newConfig)} + ) + +// SettingsDialogThemeChooserRow( +// text = stringResource(string.feature_settings_impl_dark_mode_config_system_default), +// selected = settings.darkThemeConfig == FOLLOW_SYSTEM, +// onClick = { onChangeDarkThemeConfig(FOLLOW_SYSTEM) }, +// ) +// SettingsDialogThemeChooserRow( +// text = stringResource(string.feature_settings_impl_dark_mode_config_light), +// selected = settings.darkThemeConfig == LIGHT, +// onClick = { onChangeDarkThemeConfig(LIGHT) }, +// ) +// SettingsDialogThemeChooserRow( +// text = stringResource(string.feature_settings_impl_dark_mode_config_dark), +// selected = settings.darkThemeConfig == DARK, +// onClick = { onChangeDarkThemeConfig(DARK) }, +// ) } } @@ -223,20 +265,19 @@ private fun SettingsDialogSectionTitle(text: String) { } @Composable -fun SettingsDialogThemeChooserRow( +fun SettingsDialogThemeRadioBtn( text: String, selected: Boolean, onClick: () -> Unit, ) { Row( Modifier - .fillMaxWidth() .selectable( selected = selected, role = Role.RadioButton, onClick = onClick, ) - .padding(12.dp), + .padding(5.dp), verticalAlignment = Alignment.CenterVertically, ) { RadioButton( @@ -248,6 +289,73 @@ fun SettingsDialogThemeChooserRow( } } +@Composable +fun SettingsDialogThemeDynamicColorRow( options :List, + selectedIndex :Int, + onOptionSelected : (Int) -> Unit + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + options.forEachIndexed { index, stringId -> + val isSelected = index == selectedIndex + + if (isSelected) { + Button( + onClick = { onOptionSelected(index) }, + modifier = Modifier.weight(1f), + shape = CircleShape + ) { + Spacer(Modifier.width(3.dp)) + Text(text = stringId) + } + } else { + OutlinedButton( + onClick = { onOptionSelected(index) }, + modifier = Modifier.weight(1f), + shape = CircleShape + ) { + Spacer(Modifier.width(3.dp)) + Text(text = stringId) } + } + } + } + } + +@Composable +fun SettingsDialogThemeDarkModeRow( + options: List, + selectedIndex: Int, + onOptionSelected: (Int) -> Unit +) { + SingleChoiceSegmentedButtonRow( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 1.dp) + ) { + options.forEachIndexed { index, stringId -> + val isSelected = index == selectedIndex + SegmentedButton( + shape = SegmentedButtonDefaults.itemShape(index = index, count = options.size), + onClick = { onOptionSelected(index) }, + selected = isSelected, + label = { + Text(text = stringId, maxLines = 1) + }, + colors = SegmentedButtonDefaults.colors( + activeContainerColor = MaterialTheme.colorScheme.primary , + activeContentColor = MaterialTheme.colorScheme.onPrimary, + inactiveContainerColor = Color.Transparent, + inactiveContentColor = MaterialTheme.colorScheme.onSurface + ), + icon = {} + ) } + } +} + @OptIn(ExperimentalLayoutApi::class) @Composable private fun LinksPanel() { @@ -285,39 +393,39 @@ private fun LinksPanel() { } } -@Preview -@Composable -private fun PreviewSettingsDialog() { - NiaTheme { - SettingsDialog( - onDismiss = {}, - settingsUiState = Success( - UserEditableSettings( - brand = DEFAULT, - darkThemeConfig = FOLLOW_SYSTEM, - useDynamicColor = false, - ), - ), - onChangeThemeBrand = {}, - onChangeDynamicColorPreference = {}, - onChangeDarkThemeConfig = {}, - ) - } -} +//@Preview +//@Composable +//private fun PreviewSettingsDialog() { +// NiaTheme { +// SettingsDialog( +// onDismiss = {}, +// settingsUiState = Success( +// UserEditableSettings( +// brand = DEFAULT, +// darkThemeConfig = DarkThemeConfig.FOLLOW_SYSTEM, +// useDynamicColor = false, +// ), +// ), +// onChangeThemeBrand = {}, +// onChangeDynamicColorPreference = {}, +// onChangeDarkThemeConfig = {}, +// ) +// } +//} -@Preview -@Composable -private fun PreviewSettingsDialogLoading() { - NiaTheme { - SettingsDialog( - onDismiss = {}, - settingsUiState = Loading, - onChangeThemeBrand = {}, - onChangeDynamicColorPreference = {}, - onChangeDarkThemeConfig = {}, - ) - } -} +//@Preview +//@Composable +//private fun PreviewSettingsDialogLoading() { +// NiaTheme { +// SettingsDialog( +// onDismiss = {}, +// settingsUiState = Loading, +// onChangeThemeBrand = {}, +// onChangeDynamicColorPreference = {}, +// onChangeDarkThemeConfig = {}, +// ) +// } +//} private const val PRIVACY_POLICY_URL = "https://policies.google.com/privacy" private const val BRAND_GUIDELINES_URL = "https://developer.android.com/distribute/marketing-tools/brand-guidelines" diff --git a/feature/settings/impl/src/main/res/values/strings.xml b/feature/settings/impl/src/main/res/values/strings.xml index 18e0dcf18..91228ebdc 100644 --- a/feature/settings/impl/src/main/res/values/strings.xml +++ b/feature/settings/impl/src/main/res/values/strings.xml @@ -27,7 +27,7 @@ Default Android Dark mode preference - System default + System Light Dark Use Dynamic Color diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fbe070c1a..e8786d8b1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,8 +2,8 @@ accompanist = "0.37.0" androidDesugarJdkLibs = "2.1.4" # AGP and tools should be updated together -androidGradlePlugin = "9.0.0" -androidTools = "32.0.0" +androidGradlePlugin = "9.0.1" +androidTools = "32.0.1" androidxActivity = "1.9.3" androidxAppCompat = "1.7.0" androidxBrowser = "1.8.0" From 639c6df8015fcfc6c3052dcb25e2046c4ee9e753 Mon Sep 17 00:00:00 2001 From: feby-saji Date: Fri, 20 Feb 2026 18:02:14 +0530 Subject: [PATCH 2/2] Improve the layout and widgets in settings --- .../feature/settings/impl/SettingsDialog.kt | 102 ++++++------------ 1 file changed, 35 insertions(+), 67 deletions(-) diff --git a/feature/settings/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/impl/SettingsDialog.kt b/feature/settings/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/impl/SettingsDialog.kt index 2cc697f43..3b6a328e5 100644 --- a/feature/settings/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/impl/SettingsDialog.kt +++ b/feature/settings/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/impl/SettingsDialog.kt @@ -28,9 +28,7 @@ import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState @@ -38,13 +36,9 @@ import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectableGroup import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.Close import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.RadioButton @@ -60,6 +54,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.Preview @@ -68,7 +63,6 @@ import androidx.compose.ui.window.DialogProperties import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.android.gms.oss.licenses.OssLicensesMenuActivity -import com.google.protobuf.option import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTextButton import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.designsystem.theme.supportsDynamicTheming @@ -117,7 +111,7 @@ fun SettingsDialog( */ AlertDialog( properties = DialogProperties(usePlatformDefaultWidth = false), - modifier = Modifier.widthIn(max = configuration.screenWidthDp.dp - 80.dp), + modifier = Modifier.widthIn(max = LocalWindowInfo.current.containerSize.width.dp - 80.dp), onDismissRequest = { onDismiss() }, title = { Text( @@ -201,16 +195,6 @@ private fun ColumnScope.SettingsPanel( onChangeDynamicColorPreference(index == 0) } ) -// SettingsDialogThemeChooserRow( -// text = stringResource(string.feature_settings_impl_dynamic_color_yes), -// selected = settings.useDynamicColor, -// onClick = { onChangeDynamicColorPreference(true) }, -// ) -// SettingsDialogThemeChooserRow( -// text = stringResource(string.feature_settings_impl_dynamic_color_no), -// selected = !settings.useDynamicColor, -// onClick = { onChangeDynamicColorPreference(false) }, -// ) } } } @@ -235,23 +219,7 @@ private fun ColumnScope.SettingsPanel( else -> DarkThemeConfig.DARK } onChangeDarkThemeConfig(newConfig)} - ) - -// SettingsDialogThemeChooserRow( -// text = stringResource(string.feature_settings_impl_dark_mode_config_system_default), -// selected = settings.darkThemeConfig == FOLLOW_SYSTEM, -// onClick = { onChangeDarkThemeConfig(FOLLOW_SYSTEM) }, -// ) -// SettingsDialogThemeChooserRow( -// text = stringResource(string.feature_settings_impl_dark_mode_config_light), -// selected = settings.darkThemeConfig == LIGHT, -// onClick = { onChangeDarkThemeConfig(LIGHT) }, -// ) -// SettingsDialogThemeChooserRow( -// text = stringResource(string.feature_settings_impl_dark_mode_config_dark), -// selected = settings.darkThemeConfig == DARK, -// onClick = { onChangeDarkThemeConfig(DARK) }, -// ) + ) } } @@ -393,39 +361,39 @@ private fun LinksPanel() { } } -//@Preview -//@Composable -//private fun PreviewSettingsDialog() { -// NiaTheme { -// SettingsDialog( -// onDismiss = {}, -// settingsUiState = Success( -// UserEditableSettings( -// brand = DEFAULT, -// darkThemeConfig = DarkThemeConfig.FOLLOW_SYSTEM, -// useDynamicColor = false, -// ), -// ), -// onChangeThemeBrand = {}, -// onChangeDynamicColorPreference = {}, -// onChangeDarkThemeConfig = {}, -// ) -// } -//} +@Preview +@Composable +private fun PreviewSettingsDialog() { + NiaTheme { + SettingsDialog( + onDismiss = {}, + settingsUiState = Success( + UserEditableSettings( + brand = DEFAULT, + darkThemeConfig = DarkThemeConfig.FOLLOW_SYSTEM, + useDynamicColor = false, + ), + ), + onChangeThemeBrand = {}, + onChangeDynamicColorPreference = {}, + onChangeDarkThemeConfig = {}, + ) + } +} -//@Preview -//@Composable -//private fun PreviewSettingsDialogLoading() { -// NiaTheme { -// SettingsDialog( -// onDismiss = {}, -// settingsUiState = Loading, -// onChangeThemeBrand = {}, -// onChangeDynamicColorPreference = {}, -// onChangeDarkThemeConfig = {}, -// ) -// } -//} +@Preview +@Composable +private fun PreviewSettingsDialogLoading() { + NiaTheme { + SettingsDialog( + onDismiss = {}, + settingsUiState = Loading, + onChangeThemeBrand = {}, + onChangeDynamicColorPreference = {}, + onChangeDarkThemeConfig = {}, + ) + } +} private const val PRIVACY_POLICY_URL = "https://policies.google.com/privacy" private const val BRAND_GUIDELINES_URL = "https://developer.android.com/distribute/marketing-tools/brand-guidelines"