diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt index ca48eb2..580fa90 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt @@ -130,7 +130,7 @@ fun SettingsScreenRoot( alarmSound = alarmSound, onAlarmEnabledChange = viewModel::saveAlarmEnabled, onVibrateEnabledChange = viewModel::saveVibrateEnabled, - onBlackThemeChange = {}, + onBlackThemeChange = viewModel::saveBlackTheme, onAlarmSoundChanged = { viewModel.saveAlarmSound(it) Intent(context, TimerService::class.java).apply { @@ -138,6 +138,7 @@ fun SettingsScreenRoot( context.startService(this) } }, + onThemeChange = viewModel::saveTheme, modifier = modifier ) } @@ -157,6 +158,7 @@ private fun SettingsScreen( onVibrateEnabledChange: (Boolean) -> Unit, onBlackThemeChange: (Boolean) -> Unit, onAlarmSoundChanged: (Uri?) -> Unit, + onThemeChange: (String) -> Unit, modifier: Modifier = Modifier ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -164,6 +166,24 @@ private fun SettingsScreen( checkedIconColor = colorScheme.primary, ) + val themeMap: Map> = remember { + mapOf( + "auto" to Pair( + R.drawable.brightness_auto, + "System default" + ), + "light" to Pair(R.drawable.light_mode, "Light"), + "dark" to Pair(R.drawable.dark_mode, "Dark") + ) + } + val reverseThemeMap: Map = remember { + mapOf( + "System default" to "auto", + "Light" to "light", + "Dark" to "dark" + ) + } + val context = LocalContext.current val ringtonePickerLauncher = rememberLauncherForActivityResult( @@ -190,7 +210,7 @@ private fun SettingsScreen( putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, alarmSound.toUri()) } - val switchItems = remember(alarmEnabled, vibrateEnabled) { + val switchItems = remember(preferencesState.blackTheme, alarmEnabled, vibrateEnabled) { listOf( SettingsSwitchItem( checked = preferencesState.blackTheme, @@ -360,33 +380,13 @@ private fun SettingsScreen( ) } 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, + ThemePickerListItem( + theme = preferencesState.theme, + themeMap = themeMap, + reverseThemeMap = reverseThemeMap, + onThemeChange = onThemeChange, modifier = Modifier .clip(middleListItemShape) - .clickable(onClick = {}) ) } item { @@ -436,7 +436,7 @@ private fun SettingsScreen( Text( remember(alarmSound) { RingtoneManager.getRingtone(context, alarmSound.toUri()) - .getTitle(context) + ?.getTitle(context) ?: "" } ) }, @@ -530,9 +530,9 @@ fun SettingsScreenPreview() { TomatoTheme { SettingsScreen( preferencesState = PreferencesState(), - focusTimeInputFieldState = rememberTextFieldState((25 * 60 * 1000).toString()), - shortBreakTimeInputFieldState = rememberTextFieldState((5 * 60 * 1000).toString()), - longBreakTimeInputFieldState = rememberTextFieldState((15 * 60 * 1000).toString()), + focusTimeInputFieldState = rememberTextFieldState((25).toString()), + shortBreakTimeInputFieldState = rememberTextFieldState((5).toString()), + longBreakTimeInputFieldState = rememberTextFieldState((15).toString()), sessionsSliderState = rememberSliderState(value = 3f, steps = 3, valueRange = 1f..5f), alarmEnabled = true, vibrateEnabled = true, @@ -541,6 +541,7 @@ fun SettingsScreenPreview() { onVibrateEnabledChange = {}, onBlackThemeChange = {}, onAlarmSoundChanged = {}, + onThemeChange = {}, modifier = Modifier.fillMaxSize() ) } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemeDialog.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemeDialog.kt new file mode 100644 index 0000000..a66ccf6 --- /dev/null +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemeDialog.kt @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.nsh07.pomodoro.ui.settingsScreen + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.selection.selectableGroup +import androidx.compose.material3.AlertDialogDefaults +import androidx.compose.material3.BasicAlertDialog +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.MaterialTheme.shapes +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.unit.dp +import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors +import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.bottomListItemShape +import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.middleListItemShape +import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ThemeDialog( + themeMap: Map>, + reverseThemeMap: Map, + theme: String, + setShowThemeDialog: (Boolean) -> Unit, + onThemeChange: (String) -> Unit +) { + val selectedOption = + remember { mutableStateOf(themeMap[theme]!!.second) } + + BasicAlertDialog( + onDismissRequest = { setShowThemeDialog(false) } + ) { + Surface( + modifier = Modifier + .wrapContentWidth() + .wrapContentHeight(), + shape = shapes.extraLarge, + color = colorScheme.surfaceContainer, + tonalElevation = AlertDialogDefaults.TonalElevation + ) { + Column(modifier = Modifier.padding(24.dp)) { + Text( + text = "Choose theme", + style = MaterialTheme.typography.headlineSmall + ) + Spacer(modifier = Modifier.height(24.dp)) + Column( + verticalArrangement = Arrangement.spacedBy(2.dp), + modifier = Modifier.selectableGroup() + ) { + themeMap.entries.forEachIndexed { index: Int, pair: Map.Entry> -> + val text = pair.value.second + ListItem( + leadingContent = { + RadioButton( + selected = (text == selectedOption.value), + onClick = null // null recommended for accessibility with screenreaders + ) + }, + headlineContent = { + Text( + text = text, + style = MaterialTheme.typography.bodyLarge, + ) + }, + colors = listItemColors, + modifier = Modifier + .clip( + when (index) { + 0 -> topListItemShape + themeMap.size - 1 -> bottomListItemShape + else -> middleListItemShape + } + ) + .selectable( + selected = (text == selectedOption.value), + onClick = { + selectedOption.value = text + onThemeChange(reverseThemeMap[selectedOption.value]!!) + }, + role = Role.RadioButton + ) + ) + } + } + Spacer(modifier = Modifier.height(24.dp)) + TextButton( + shapes = ButtonDefaults.shapes(), + onClick = { setShowThemeDialog(false) }, + modifier = Modifier.align(Alignment.End) + ) { + Text("Ok") + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemePickerListItem.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemePickerListItem.kt new file mode 100644 index 0000000..4c43549 --- /dev/null +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemePickerListItem.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.nsh07.pomodoro.ui.settingsScreen + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors + +@Composable +fun ThemePickerListItem( + theme: String, + themeMap: Map>, + reverseThemeMap: Map, + onThemeChange: (String) -> Unit, + modifier: Modifier = Modifier +) { + var showDialog by rememberSaveable { mutableStateOf(false) } + + if (showDialog) { + ThemeDialog( + themeMap = themeMap, + reverseThemeMap = reverseThemeMap, + theme = theme, + setShowThemeDialog = { showDialog = it }, + onThemeChange = onThemeChange + ) + } + + ListItem( + leadingContent = { + Icon( + painter = painterResource(themeMap[theme]!!.first), + contentDescription = null + ) + }, + headlineContent = { Text("Theme") }, + supportingContent = { + Text(themeMap[theme]!!.second) + }, + colors = listItemColors, + modifier = modifier + .fillMaxWidth() + .clickable { showDialog = true } + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/PreferencesState.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/PreferencesState.kt index 25a3374..0ae4849 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/PreferencesState.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/PreferencesState.kt @@ -12,7 +12,7 @@ import androidx.compose.ui.graphics.Color @Immutable data class PreferencesState( - val theme: String = "system", + val theme: String = "auto", val colorScheme: String = Color.White.toString(), val blackTheme: Boolean = false ) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt index 5cab4c2..a341d5f 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt @@ -65,7 +65,7 @@ class SettingsViewModel( init { viewModelScope.launch { val theme = preferenceRepository.getStringPreference("theme") - ?: preferenceRepository.saveStringPreference("theme", "system") + ?: preferenceRepository.saveStringPreference("theme", "auto") val colorScheme = preferenceRepository.getStringPreference("color_scheme") ?: preferenceRepository.saveStringPreference("color_scheme", Color.White.toString()) val blackTheme = preferenceRepository.getBooleanPreference("black_theme") @@ -128,50 +128,50 @@ class SettingsViewModel( fun saveAlarmEnabled(enabled: Boolean) { viewModelScope.launch { - preferenceRepository.saveBooleanPreference("alarm_enabled", enabled) timerRepository.alarmEnabled = enabled + preferenceRepository.saveBooleanPreference("alarm_enabled", enabled) } } fun saveVibrateEnabled(enabled: Boolean) { viewModelScope.launch { - preferenceRepository.saveBooleanPreference("vibrate_enabled", enabled) timerRepository.vibrateEnabled = enabled + preferenceRepository.saveBooleanPreference("vibrate_enabled", enabled) } } fun saveAlarmSound(uri: Uri?) { viewModelScope.launch { + timerRepository.alarmSoundUri = uri preferenceRepository.saveStringPreference("alarm_sound", uri.toString()) } - timerRepository.alarmSoundUri = uri } fun saveColorScheme(colorScheme: Color) { viewModelScope.launch { + _preferencesState.update { currentState -> + currentState.copy(colorScheme = colorScheme.toString()) + } preferenceRepository.saveStringPreference("color_scheme", colorScheme.toString()) } - _preferencesState.update { currentState -> - currentState.copy(colorScheme = colorScheme.toString()) - } } fun saveTheme(theme: String) { viewModelScope.launch { + _preferencesState.update { currentState -> + currentState.copy(theme = theme) + } preferenceRepository.saveStringPreference("theme", theme) } - _preferencesState.update { currentState -> - currentState.copy(theme = theme) - } } fun saveBlackTheme(blackTheme: Boolean) { viewModelScope.launch { + _preferencesState.update { currentState -> + currentState.copy(blackTheme = blackTheme) + } preferenceRepository.saveBooleanPreference("black_theme", blackTheme) } - _preferencesState.update { currentState -> - currentState.copy(blackTheme = blackTheme) - } } companion object { diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/theme/Color.kt b/app/src/main/java/org/nsh07/pomodoro/ui/theme/Color.kt index bf8ee3a..2c72b3e 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/theme/Color.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/theme/Color.kt @@ -18,15 +18,17 @@ val PurpleGrey40 = Color(0xFF625b71) val Pink40 = Color(0xFF7D5260) object CustomColors { + var black = false + @OptIn(ExperimentalMaterial3Api::class) val topBarColors: TopAppBarColors - @Composable get() { - return TopAppBarDefaults.topAppBarColors( - containerColor = colorScheme.surfaceContainer, - scrolledContainerColor = colorScheme.surfaceContainer + @Composable get() = + TopAppBarDefaults.topAppBarColors( + containerColor = if (!black) colorScheme.surfaceContainer else colorScheme.surface, + scrolledContainerColor = if (!black) colorScheme.surfaceContainer else colorScheme.surface ) - } val listItemColors: ListItemColors - @Composable get() = ListItemDefaults.colors(containerColor = colorScheme.surfaceBright) + @Composable get() = + ListItemDefaults.colors(containerColor = if (!black) colorScheme.surfaceBright else colorScheme.surfaceContainer) } \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/theme/Theme.kt b/app/src/main/java/org/nsh07/pomodoro/ui/theme/Theme.kt index 97ca9b2..da61be6 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/theme/Theme.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/theme/Theme.kt @@ -1,5 +1,6 @@ package org.nsh07.pomodoro.ui.theme +import android.app.Activity import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi @@ -10,8 +11,11 @@ import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat import com.materialkolor.dynamiccolor.ColorSpec import com.materialkolor.rememberDynamicColorScheme @@ -56,6 +60,15 @@ fun TomatoTheme( else -> LightColorScheme } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme + } + } + CustomColors.black = blackTheme && darkTheme + val dynamicColorScheme = rememberDynamicColorScheme( seedColor = when (seedColor) { Color.White -> colorScheme.primary