feat: Add UI in settings to change theme settings

UI doesn't work right now. This will be implemented in further commits. #30
This commit is contained in:
Nishant Mishra
2025-09-21 15:27:47 +05:30
parent c35d25c97c
commit c19f4f936c
7 changed files with 187 additions and 6 deletions

View File

@@ -61,6 +61,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
@@ -74,14 +75,17 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.service.TimerService
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsViewModel
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
import org.nsh07.pomodoro.ui.theme.CustomColors.topBarColors
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.bottomListItemShape
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.cardShape
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.middleListItemShape
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
import org.nsh07.pomodoro.ui.theme.TomatoTheme
import org.nsh07.pomodoro.utils.toColor
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -104,6 +108,8 @@ fun SettingsScreenRoot(
val vibrateEnabled by viewModel.vibrateEnabled.collectAsStateWithLifecycle(true)
val alarmSound by viewModel.alarmSound.collectAsStateWithLifecycle(viewModel.currentAlarmSound)
val preferencesState by viewModel.preferencesState.collectAsStateWithLifecycle()
val sessionsSliderState = rememberSaveable(
saver = SliderState.Saver(
viewModel.sessionsSliderState.onValueChangeFinished,
@@ -114,6 +120,7 @@ fun SettingsScreenRoot(
}
SettingsScreen(
preferencesState = preferencesState,
focusTimeInputFieldState = focusTimeInputFieldState,
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
longBreakTimeInputFieldState = longBreakTimeInputFieldState,
@@ -123,6 +130,7 @@ fun SettingsScreenRoot(
alarmSound = alarmSound,
onAlarmEnabledChange = viewModel::saveAlarmEnabled,
onVibrateEnabledChange = viewModel::saveVibrateEnabled,
onBlackThemeChange = {},
onAlarmSoundChanged = {
viewModel.saveAlarmSound(it)
Intent(context, TimerService::class.java).apply {
@@ -137,6 +145,7 @@ fun SettingsScreenRoot(
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun SettingsScreen(
preferencesState: PreferencesState,
focusTimeInputFieldState: TextFieldState,
shortBreakTimeInputFieldState: TextFieldState,
longBreakTimeInputFieldState: TextFieldState,
@@ -146,6 +155,7 @@ private fun SettingsScreen(
alarmSound: String,
onAlarmEnabledChange: (Boolean) -> Unit,
onVibrateEnabledChange: (Boolean) -> Unit,
onBlackThemeChange: (Boolean) -> Unit,
onAlarmSoundChanged: (Uri?) -> Unit,
modifier: Modifier = Modifier
) {
@@ -182,6 +192,13 @@ private fun SettingsScreen(
val switchItems = remember(alarmEnabled, vibrateEnabled) {
listOf(
SettingsSwitchItem(
checked = preferencesState.blackTheme,
icon = R.drawable.contrast,
label = "Black theme",
description = "Use a pure black dark theme",
onClick = onBlackThemeChange
),
SettingsSwitchItem(
checked = alarmEnabled,
icon = R.drawable.alarm_on,
@@ -314,9 +331,101 @@ private fun SettingsScreen(
}
},
colors = listItemColors,
modifier = Modifier.clip(topListItemShape)
modifier = Modifier.clip(cardShape)
)
}
item { Spacer(Modifier.height(12.dp)) }
item {
ListItem(
leadingContent = {
Icon(
painter = painterResource(R.drawable.palette),
contentDescription = null,
tint = colorScheme.primary
)
},
headlineContent = { Text("Color scheme") },
supportingContent = {
Text(
if (preferencesState.colorScheme.toColor() == Color.White) "Dynamic"
else "Color"
)
},
colors = listItemColors,
modifier = Modifier
.clip(topListItemShape)
.clickable(onClick = {})
)
}
item {
ListItem(
leadingContent = {
Icon(
painter = painterResource(
when (preferencesState.theme) {
"dark" -> R.drawable.dark_mode
"light" -> R.drawable.light_mode
else -> R.drawable.brightness_auto
}
),
contentDescription = null
)
},
headlineContent = { Text("Theme") },
supportingContent = {
Text(
when (preferencesState.theme) {
"dark" -> "Dark"
"light" -> "Light"
else -> "System default"
}
)
},
colors = listItemColors,
modifier = Modifier
.clip(middleListItemShape)
.clickable(onClick = {})
)
}
item {
val item = switchItems[0]
ListItem(
leadingContent = {
Icon(painterResource(item.icon), contentDescription = null)
},
headlineContent = { Text(item.label) },
supportingContent = { Text(item.description) },
trailingContent = {
Switch(
checked = item.checked,
onCheckedChange = { item.onClick(it) },
thumbContent = {
if (item.checked) {
Icon(
painter = painterResource(R.drawable.check),
contentDescription = null,
modifier = Modifier.size(SwitchDefaults.IconSize),
)
} else {
Icon(
painter = painterResource(R.drawable.clear),
contentDescription = null,
modifier = Modifier.size(SwitchDefaults.IconSize),
)
}
},
colors = switchColors
)
},
colors = listItemColors,
modifier = Modifier.clip(bottomListItemShape)
)
}
item { Spacer(Modifier.height(12.dp)) }
item {
ListItem(
leadingContent = {
@@ -333,12 +442,11 @@ private fun SettingsScreen(
},
colors = listItemColors,
modifier = Modifier
.clip(bottomListItemShape)
.clip(topListItemShape)
.clickable(onClick = { ringtonePickerLauncher.launch(intent) })
)
}
item { Spacer(Modifier.height(12.dp)) }
itemsIndexed(switchItems) { index, item ->
itemsIndexed(switchItems.drop(1)) { index, item ->
ListItem(
leadingContent = {
Icon(painterResource(item.icon), contentDescription = null)
@@ -371,8 +479,7 @@ private fun SettingsScreen(
modifier = Modifier
.clip(
when (index) {
0 -> topListItemShape
switchItems.lastIndex -> bottomListItemShape
switchItems.lastIndex - 1 -> bottomListItemShape
else -> middleListItemShape
}
)
@@ -422,6 +529,7 @@ private fun SettingsScreen(
fun SettingsScreenPreview() {
TomatoTheme {
SettingsScreen(
preferencesState = PreferencesState(),
focusTimeInputFieldState = rememberTextFieldState((25 * 60 * 1000).toString()),
shortBreakTimeInputFieldState = rememberTextFieldState((5 * 60 * 1000).toString()),
longBreakTimeInputFieldState = rememberTextFieldState((15 * 60 * 1000).toString()),
@@ -431,6 +539,7 @@ fun SettingsScreenPreview() {
alarmSound = "null",
onAlarmEnabledChange = {},
onVibrateEnabledChange = {},
onBlackThemeChange = {},
onAlarmSoundChanged = {},
modifier = Modifier.fillMaxSize()
)

View File

@@ -147,6 +147,33 @@ class SettingsViewModel(
timerRepository.alarmSoundUri = uri
}
fun saveColorScheme(colorScheme: Color) {
viewModelScope.launch {
preferenceRepository.saveStringPreference("color_scheme", colorScheme.toString())
}
_preferencesState.update { currentState ->
currentState.copy(colorScheme = colorScheme.toString())
}
}
fun saveTheme(theme: String) {
viewModelScope.launch {
preferenceRepository.saveStringPreference("theme", theme)
}
_preferencesState.update { currentState ->
currentState.copy(theme = theme)
}
}
fun saveBlackTheme(blackTheme: Boolean) {
viewModelScope.launch {
preferenceRepository.saveBooleanPreference("black_theme", blackTheme)
}
_preferencesState.update { currentState ->
currentState.copy(blackTheme = blackTheme)
}
}
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#e3e3e3"
android:pathData="M312,640h64l16,-46q8,-21 31.5,-33.5T505,548q22,0 39.5,12.5T570,594l9,27q3,8 10.5,13.5T606,640q15,0 23.5,-12.5T633,601L519,299q-3,-9 -13.5,-14t-36.5,-5q-9,0 -17,5t-11,14L312,640ZM426,496 L478,346h4l52,150L426,496ZM346,800L240,800q-33,0 -56.5,-23.5T160,720v-106l-77,-78q-11,-12 -17,-26.5T60,480q0,-15 6,-29.5T83,424l77,-78v-106q0,-33 23.5,-56.5T240,160h106l78,-77q12,-11 26.5,-17t29.5,-6q15,0 29.5,6t26.5,17l78,77h106q33,0 56.5,23.5T800,240v106l77,78q11,12 17,26.5t6,29.5q0,15 -6,29.5T877,536l-77,78v106q0,33 -23.5,56.5T720,800L614,800l-78,77q-12,11 -26.5,17T480,900q-15,0 -29.5,-6T424,877l-78,-77Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#e3e3e3"
android:pathData="M480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM520,797q119,-15 199.5,-104.5T800,480q0,-123 -80.5,-212.5T520,163v634Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#e3e3e3"
android:pathData="M480,840q-151,0 -255.5,-104.5T120,480q0,-138 90,-239.5T440,122q13,-2 23,3.5t16,14.5q6,9 6.5,21t-7.5,23q-17,26 -25.5,55t-8.5,61q0,90 63,153t153,63q31,0 61.5,-9t54.5,-25q11,-7 22.5,-6.5T819,481q10,5 15.5,15t3.5,24q-14,138 -117.5,229T480,840Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#e3e3e3"
android:pathData="M480,680q-83,0 -141.5,-58.5T280,480q0,-83 58.5,-141.5T480,280q83,0 141.5,58.5T680,480q0,83 -58.5,141.5T480,680ZM80,520q-17,0 -28.5,-11.5T40,480q0,-17 11.5,-28.5T80,440h80q17,0 28.5,11.5T200,480q0,17 -11.5,28.5T160,520L80,520ZM800,520q-17,0 -28.5,-11.5T760,480q0,-17 11.5,-28.5T800,440h80q17,0 28.5,11.5T920,480q0,17 -11.5,28.5T880,520h-80ZM480,200q-17,0 -28.5,-11.5T440,160v-80q0,-17 11.5,-28.5T480,40q17,0 28.5,11.5T520,80v80q0,17 -11.5,28.5T480,200ZM480,920q-17,0 -28.5,-11.5T440,880v-80q0,-17 11.5,-28.5T480,760q17,0 28.5,11.5T520,800v80q0,17 -11.5,28.5T480,920ZM226,282l-43,-42q-12,-11 -11.5,-28t11.5,-29q12,-12 29,-12t28,12l42,43q11,12 11,28t-11,28q-11,12 -27.5,11.5T226,282ZM720,777 L678,734q-11,-12 -11,-28.5t11,-27.5q11,-12 27.5,-11.5T734,678l43,42q12,11 11.5,28T777,777q-12,12 -29,12t-28,-12ZM678,282q-12,-11 -11.5,-27.5T678,226l42,-43q11,-12 28,-11.5t29,11.5q12,12 12,29t-12,28l-43,42q-12,11 -28,11t-28,-11ZM183,777q-12,-12 -12,-29t12,-28l43,-42q12,-11 28.5,-11t27.5,11q12,11 11.5,27.5T282,734l-42,43q-11,12 -28,11.5T183,777Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#e3e3e3"
android:pathData="M480,880q-82,0 -155,-31.5t-127.5,-86Q143,708 111.5,635T80,480q0,-83 32.5,-156t88,-127Q256,143 330,111.5T488,80q80,0 151,27.5t124.5,76q53.5,48.5 85,115T880,442q0,115 -70,176.5T640,680h-74q-9,0 -12.5,5t-3.5,11q0,12 15,34.5t15,51.5q0,50 -27.5,74T480,880ZM260,520q26,0 43,-17t17,-43q0,-26 -17,-43t-43,-17q-26,0 -43,17t-17,43q0,26 17,43t43,17ZM380,360q26,0 43,-17t17,-43q0,-26 -17,-43t-43,-17q-26,0 -43,17t-17,43q0,26 17,43t43,17ZM580,360q26,0 43,-17t17,-43q0,-26 -17,-43t-43,-17q-26,0 -43,17t-17,43q0,26 17,43t43,17ZM700,520q26,0 43,-17t17,-43q0,-26 -17,-43t-43,-17q-26,0 -43,17t-17,43q0,26 17,43t43,17Z" />
</vector>