diff --git a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt
index 7f2734e..806233e 100644
--- a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt
@@ -1,3 +1,20 @@
+/*
+ * Copyright (c) 2025 Nishant Mishra
+ *
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
+ */
+
package org.nsh07.pomodoro
import android.os.Bundle
@@ -11,8 +28,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.nsh07.pomodoro.ui.AppScreen
-import org.nsh07.pomodoro.ui.NavItem
-import org.nsh07.pomodoro.ui.Screen
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsViewModel
import org.nsh07.pomodoro.ui.theme.TomatoTheme
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerViewModel
@@ -71,27 +86,4 @@ class MainActivity : ComponentActivity() {
// Increase the timer loop frequency again when visible to make the progress smoother
appContainer.appTimerRepository.timerFrequency = 10f
}
-
- companion object {
- val screens = listOf(
- NavItem(
- Screen.Timer,
- R.drawable.timer_outlined,
- R.drawable.timer_filled,
- R.string.timer
- ),
- NavItem(
- Screen.Stats,
- R.drawable.monitoring,
- R.drawable.monitoring_filled,
- R.string.stats
- ),
- NavItem(
- Screen.Settings,
- R.drawable.settings,
- R.drawable.settings_filled,
- R.string.settings
- )
- )
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt
index b26212e..6d1123e 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt
@@ -1,8 +1,18 @@
/*
* Copyright (c) 2025 Nishant Mishra
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
*/
package org.nsh07.pomodoro.ui
@@ -45,7 +55,6 @@ import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.ui.NavDisplay
import androidx.window.core.layout.WindowSizeClass
-import org.nsh07.pomodoro.MainActivity.Companion.screens
import org.nsh07.pomodoro.service.TimerService
import org.nsh07.pomodoro.ui.settingsScreen.SettingsScreenRoot
import org.nsh07.pomodoro.ui.statsScreen.StatsScreenRoot
@@ -100,7 +109,7 @@ fun AppScreen(
if (wide) ShortNavigationBarArrangement.Centered
else ShortNavigationBarArrangement.EqualWeight
) {
- screens.forEach {
+ mainScreens.forEach {
val selected = backStack.last() == it.route
ShortNavigationBarItem(
selected = selected,
@@ -131,7 +140,7 @@ fun AppScreen(
SharedTransitionLayout {
NavDisplay(
backStack = backStack,
- onBack = { backStack.removeLastOrNull() },
+ onBack = backStack::removeLastOrNull,
transitionSpec = {
ContentTransform(
fadeIn(motionScheme.defaultEffectsSpec()),
@@ -213,7 +222,7 @@ fun AppScreen(
)
}
- entry {
+ entry {
SettingsScreenRoot(
modifier = modifier.padding(
start = contentPadding.calculateStartPadding(layoutDirection),
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/Navigation.kt b/app/src/main/java/org/nsh07/pomodoro/ui/Navigation.kt
new file mode 100644
index 0000000..2641e7a
--- /dev/null
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/Navigation.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2025 Nishant Mishra
+ *
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
+ */
+
+package org.nsh07.pomodoro.ui
+
+import org.nsh07.pomodoro.R
+
+val mainScreens = listOf(
+ NavItem(
+ Screen.Timer,
+ R.drawable.timer_outlined,
+ R.drawable.timer_filled,
+ R.string.timer
+ ),
+ NavItem(
+ Screen.Stats,
+ R.drawable.monitoring,
+ R.drawable.monitoring_filled,
+ R.string.stats
+ ),
+ NavItem(
+ Screen.Settings.Main,
+ R.drawable.settings,
+ R.drawable.settings_filled,
+ R.string.settings
+ )
+)
+
+val settingsScreens = listOf(
+ SettingsNavItem(
+ Screen.Settings.Timer,
+ R.drawable.timer_filled,
+ R.string.timer,
+ listOf(R.string.durations, R.string.session_length, R.string.always_on_display)
+ ),
+ SettingsNavItem(
+ Screen.Settings.Alarm,
+ R.drawable.alarm,
+ R.string.alarm,
+ listOf(R.string.alarm_sound, R.string.alarm, R.string.vibrate)
+ ),
+ SettingsNavItem(
+ Screen.Settings.Appearance,
+ R.drawable.palette,
+ R.string.appearance,
+ listOf(R.string.color_scheme, R.string.theme, R.string.black_theme)
+ )
+)
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt
index 2a43655..a37fe21 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt
@@ -1,3 +1,20 @@
+/*
+ * Copyright (c) 2025 Nishant Mishra
+ *
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
+ */
+
package org.nsh07.pomodoro.ui
import androidx.annotation.DrawableRes
@@ -13,7 +30,19 @@ sealed class Screen : NavKey {
object AOD : Screen()
@Serializable
- object Settings : Screen()
+ sealed class Settings : Screen() {
+ @Serializable
+ object Main : Settings()
+
+ @Serializable
+ object Alarm : Settings()
+
+ @Serializable
+ object Appearance : Settings()
+
+ @Serializable
+ object Timer : Settings()
+ }
@Serializable
object Stats : Screen()
@@ -24,4 +53,11 @@ data class NavItem(
@param:DrawableRes val unselectedIcon: Int,
@param:DrawableRes val selectedIcon: Int,
@param:StringRes val label: Int
-)
\ No newline at end of file
+)
+
+data class SettingsNavItem(
+ val route: Screen.Settings,
+ @param:DrawableRes val icon: Int,
+ @param:StringRes val label: Int,
+ val innerSettings: List
+)
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/MinutesTransformation.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/MinutesTransformation.kt
deleted file mode 100644
index e68c680..0000000
--- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/MinutesTransformation.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.nsh07.pomodoro.ui.settingsScreen
-
-import androidx.compose.foundation.text.input.InputTransformation
-import androidx.compose.foundation.text.input.OutputTransformation
-import androidx.compose.foundation.text.input.TextFieldBuffer
-import androidx.compose.foundation.text.input.insert
-import androidx.core.text.isDigitsOnly
-
-object MinutesInputTransformation : InputTransformation {
- override fun TextFieldBuffer.transformInput() {
- if (!this.asCharSequence().isDigitsOnly() || this.length > 2) {
- revertAllChanges()
- }
- }
-}
-
-object MinutesOutputTransformation : OutputTransformation {
- override fun TextFieldBuffer.transformOutput() {
- if (this.length == 0) {
- insert(0, "00")
- } else if (this.toString().toInt() < 10) {
- insert(0, "0")
- }
- }
-}
\ No newline at end of file
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 b2bca48..47195a3 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
@@ -1,97 +1,80 @@
/*
* Copyright (c) 2025 Nishant Mishra
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
*/
package org.nsh07.pomodoro.ui.settingsScreen
-import android.app.Activity
import android.content.Intent
-import android.media.RingtoneManager
import android.net.Uri
-import android.os.Build
-import androidx.activity.compose.rememberLauncherForActivityResult
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.annotation.DrawableRes
-import androidx.annotation.StringRes
-import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideOutHorizontally
+import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
-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.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.input.TextFieldState
-import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.FilledTonalIconToggleButton
import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButtonDefaults
-import androidx.compose.material3.ListItem
import androidx.compose.material3.LocalTextStyle
-import androidx.compose.material3.MaterialTheme.colorScheme
-import androidx.compose.material3.MaterialTheme.typography
-import androidx.compose.material3.Slider
import androidx.compose.material3.SliderState
-import androidx.compose.material3.Switch
-import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
-import androidx.compose.material3.rememberSliderState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.SnapshotStateList
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
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.tooling.preview.Devices
-import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.core.net.toUri
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
+import androidx.navigation3.runtime.entryProvider
+import androidx.navigation3.ui.NavDisplay
import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.service.TimerService
+import org.nsh07.pomodoro.ui.ClickableListItem
+import org.nsh07.pomodoro.ui.Screen
+import org.nsh07.pomodoro.ui.settingsScreen.components.AboutCard
+import org.nsh07.pomodoro.ui.settingsScreen.screens.AlarmSettings
+import org.nsh07.pomodoro.ui.settingsScreen.screens.AppearanceSettings
+import org.nsh07.pomodoro.ui.settingsScreen.screens.TimerSettings
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsViewModel
+import org.nsh07.pomodoro.ui.settingsScreens
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)
@@ -102,6 +85,8 @@ fun SettingsScreenRoot(
) {
val context = LocalContext.current
+ val backStack = viewModel.backStack
+
DisposableEffect(Unit) {
viewModel.runTextFieldFlowCollection()
onDispose { viewModel.cancelTextFieldFlowCollection() }
@@ -134,6 +119,7 @@ fun SettingsScreenRoot(
SettingsScreen(
preferencesState = preferencesState,
+ backStack = backStack,
focusTimeInputFieldState = focusTimeInputFieldState,
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
longBreakTimeInputFieldState = longBreakTimeInputFieldState,
@@ -162,6 +148,7 @@ fun SettingsScreenRoot(
@Composable
private fun SettingsScreen(
preferencesState: PreferencesState,
+ backStack: SnapshotStateList,
focusTimeInputFieldState: TextFieldState,
shortBreakTimeInputFieldState: TextFieldState,
longBreakTimeInputFieldState: TextFieldState,
@@ -179,402 +166,118 @@ private fun SettingsScreen(
modifier: Modifier = Modifier
) {
val context = LocalContext.current
- val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
- val switchColors = SwitchDefaults.colors(
- checkedIconColor = colorScheme.primary,
- )
+ val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
- val themeMap: Map> = remember {
- mapOf(
- "auto" to Pair(
- R.drawable.brightness_auto,
- R.string.system_default
- ),
- "light" to Pair(R.drawable.light_mode, R.string.light),
- "dark" to Pair(R.drawable.dark_mode, R.string.dark)
- )
- }
- val reverseThemeMap: Map = remember {
- mapOf(
- context.getString(R.string.system_default) to "auto",
- context.getString(R.string.light) to "light",
- context.getString(R.string.dark) to "dark"
- )
- }
-
- var alarmName by remember { mutableStateOf("...") }
-
- LaunchedEffect(alarmSound) {
- withContext(Dispatchers.IO) {
- alarmName =
- RingtoneManager.getRingtone(context, alarmSound.toUri())?.getTitle(context) ?: ""
- }
- }
-
- val ringtonePickerLauncher = rememberLauncherForActivityResult(
- contract = ActivityResultContracts.StartActivityForResult()
- ) { result ->
- if (result.resultCode == Activity.RESULT_OK) {
- val uri =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- result.data?.getParcelableExtra(
- RingtoneManager.EXTRA_RINGTONE_PICKED_URI,
- Uri::class.java
- )
- } else {
- @Suppress("DEPRECATION")
- result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
- }
- onAlarmSoundChanged(uri)
- }
- }
-
- val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
- putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM)
- putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, stringResource(R.string.alarm_sound))
- putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, alarmSound.toUri())
- }
-
- val switchItems = remember(
- preferencesState.blackTheme,
- preferencesState.aodEnabled,
- alarmEnabled,
- vibrateEnabled
- ) {
- listOf(
- SettingsSwitchItem(
- checked = preferencesState.blackTheme,
- icon = R.drawable.contrast,
- label = R.string.black_theme,
- description = R.string.black_theme_desc,
- onClick = onBlackThemeChange
- ),
- SettingsSwitchItem(
- checked = preferencesState.aodEnabled,
- icon = R.drawable.aod,
- label = R.string.always_on_display,
- description = R.string.always_on_display_desc,
- onClick = onAodEnabledChange
- ),
- SettingsSwitchItem(
- checked = alarmEnabled,
- icon = R.drawable.alarm_on,
- label = R.string.alarm,
- description = R.string.alarm_desc,
- onClick = onAlarmEnabledChange
- ),
- SettingsSwitchItem(
- checked = vibrateEnabled,
- icon = R.drawable.mobile_vibrate,
- label = R.string.vibrate,
- description = R.string.vibrate_desc,
- onClick = onVibrateEnabledChange
- )
- )
- }
-
- Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) {
- TopAppBar(
- title = {
- Text(
- stringResource(R.string.settings),
- style = LocalTextStyle.current.copy(
- fontFamily = robotoFlexTopBar,
- fontSize = 32.sp,
- lineHeight = 32.sp
- )
- )
- },
- subtitle = {},
- colors = topBarColors,
- titleHorizontalAlignment = Alignment.CenterHorizontally,
- scrollBehavior = scrollBehavior
- )
-
- LazyColumn(
- verticalArrangement = Arrangement.spacedBy(2.dp),
- modifier = Modifier
- .background(topBarColors.containerColor)
- .fillMaxSize()
- .padding(horizontal = 16.dp)
- ) {
- item {
- Spacer(Modifier.height(12.dp))
- }
- item {
- Row(
- horizontalArrangement = Arrangement.Center,
- modifier = Modifier
- .fillMaxWidth()
- .horizontalScroll(rememberScrollState())
- ) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.spacedBy(2.dp)
- ) {
- Text(
- stringResource(R.string.focus),
- style = typography.titleSmallEmphasized
- )
- MinuteInputField(
- state = focusTimeInputFieldState,
- shape = RoundedCornerShape(
- topStart = topListItemShape.topStart,
- bottomStart = topListItemShape.topStart,
- topEnd = topListItemShape.bottomStart,
- bottomEnd = topListItemShape.bottomStart
- ),
- imeAction = ImeAction.Next
- )
- }
- Spacer(Modifier.width(2.dp))
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.spacedBy(2.dp)
- ) {
- Text(
- stringResource(R.string.short_break),
- style = typography.titleSmallEmphasized
- )
- MinuteInputField(
- state = shortBreakTimeInputFieldState,
- shape = RoundedCornerShape(middleListItemShape.topStart),
- imeAction = ImeAction.Next
- )
- }
- Spacer(Modifier.width(2.dp))
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.spacedBy(2.dp)
- ) {
- Text(
- stringResource(R.string.long_break),
- style = typography.titleSmallEmphasized
- )
- MinuteInputField(
- state = longBreakTimeInputFieldState,
- shape = RoundedCornerShape(
- topStart = bottomListItemShape.topStart,
- bottomStart = bottomListItemShape.topStart,
- topEnd = bottomListItemShape.bottomStart,
- bottomEnd = bottomListItemShape.bottomStart
- ),
- imeAction = ImeAction.Done
- )
- }
- }
- }
- item {
- Spacer(Modifier.height(12.dp))
- }
- item {
- ListItem(
- leadingContent = {
- Icon(
- painterResource(R.drawable.clocks),
- null
- )
- },
- headlineContent = {
- Text(stringResource(R.string.session_length))
- },
- supportingContent = {
- Column {
+ NavDisplay(
+ backStack = backStack,
+ onBack = backStack::removeLastOrNull,
+ transitionSpec = {
+ (slideInHorizontally(initialOffsetX = { it }))
+ .togetherWith(slideOutHorizontally(targetOffsetX = { -it / 4 }) + fadeOut())
+ },
+ popTransitionSpec = {
+ (slideInHorizontally(initialOffsetX = { -it / 4 }) + fadeIn())
+ .togetherWith(slideOutHorizontally(targetOffsetX = { it }))
+ },
+ predictivePopTransitionSpec = {
+ (slideInHorizontally(initialOffsetX = { -it / 4 }) + fadeIn())
+ .togetherWith(slideOutHorizontally(targetOffsetX = { it }))
+ },
+ entryProvider = entryProvider {
+ entry {
+ Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) {
+ TopAppBar(
+ title = {
Text(
- stringResource(
- R.string.session_length_desc,
- sessionsSliderState.value.toInt()
+ stringResource(R.string.settings),
+ style = LocalTextStyle.current.copy(
+ fontFamily = robotoFlexTopBar,
+ fontSize = 32.sp,
+ lineHeight = 32.sp
)
)
- Slider(
- state = sessionsSliderState,
- modifier = Modifier.padding(vertical = 4.dp)
- )
- }
- },
- colors = listItemColors,
- modifier = Modifier.clip(cardShape)
- )
- }
+ },
+ subtitle = {},
+ colors = topBarColors,
+ titleHorizontalAlignment = Alignment.CenterHorizontally,
+ scrollBehavior = scrollBehavior
+ )
- item { Spacer(Modifier.height(12.dp)) }
-
- item {
- ColorSchemePickerListItem(
- color = preferencesState.colorScheme.toColor(),
- items = 3,
- index = 0,
- onColorChange = onColorSchemeChange
- )
- }
- item {
- ThemePickerListItem(
- theme = preferencesState.theme,
- themeMap = themeMap,
- reverseThemeMap = reverseThemeMap,
- onThemeChange = onThemeChange,
- items = 3,
- index = 1,
- modifier = Modifier
- .clip(middleListItemShape)
- )
- }
- itemsIndexed(switchItems.take(2)) { index, item ->
- ListItem(
- leadingContent = {
- Icon(painterResource(item.icon), contentDescription = null)
- },
- headlineContent = { Text(stringResource(item.label)) },
- supportingContent = { Text(stringResource(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
- .padding(top = if (index != 0) 16.dp else 0.dp)
- .clip(if (index == 0) bottomListItemShape else cardShape)
- )
- }
-
- item { Spacer(Modifier.height(12.dp)) }
-
- item {
- ListItem(
- leadingContent = {
- Icon(painterResource(R.drawable.alarm), null)
- },
- headlineContent = { Text(stringResource(R.string.alarm_sound)) },
- supportingContent = { Text(alarmName) },
- colors = listItemColors,
- modifier = Modifier
- .clip(topListItemShape)
- .clickable(onClick = { ringtonePickerLauncher.launch(intent) })
- )
- }
- itemsIndexed(switchItems.drop(2)) { index, item ->
- ListItem(
- leadingContent = {
- Icon(painterResource(item.icon), contentDescription = null)
- },
- headlineContent = { Text(stringResource(item.label)) },
- supportingContent = { Text(stringResource(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(
- when (index) {
- switchItems.lastIndex - 2 -> bottomListItemShape
- else -> middleListItemShape
- }
- )
- )
- }
- item {
- var expanded by remember { mutableStateOf(false) }
- Column(
- horizontalAlignment = Alignment.End,
- modifier = Modifier
- .padding(vertical = 6.dp)
- .fillMaxWidth()
- ) {
- FilledTonalIconToggleButton(
- checked = expanded,
- onCheckedChange = { expanded = it },
- shapes = IconButtonDefaults.toggleableShapes(),
- modifier = Modifier.width(52.dp)
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp),
+ modifier = Modifier
+ .background(topBarColors.containerColor)
+ .fillMaxSize()
+ .padding(horizontal = 16.dp)
) {
- Icon(
- painterResource(R.drawable.info),
- null
- )
- }
- AnimatedVisibility(expanded) {
- Text(
- stringResource(R.string.pomodoro_info),
- style = typography.bodyMedium,
- color = colorScheme.onSurfaceVariant,
- modifier = Modifier.padding(8.dp)
- )
+ item { Spacer(Modifier.height(12.dp)) }
+
+ item { AboutCard() }
+
+ item { Spacer(Modifier.height(12.dp)) }
+
+ itemsIndexed(settingsScreens) { index, item ->
+ ClickableListItem(
+ leadingContent = {
+ Icon(painterResource(item.icon), null)
+ },
+ headlineContent = { Text(stringResource(item.label)) },
+ supportingContent = {
+ Text(
+ remember {
+ item.innerSettings.joinToString(", ") {
+ context.getString(it)
+ }
+ },
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ },
+ trailingContent = {
+ Icon(painterResource(R.drawable.arrow_forward_big), null)
+ },
+ items = settingsScreens.size,
+ index = index
+ ) { backStack.add(item.route) }
+ }
+
+ item { Spacer(Modifier.height(12.dp)) }
}
}
}
+
+ entry {
+ AlarmSettings(
+ preferencesState = preferencesState,
+ alarmEnabled = alarmEnabled,
+ vibrateEnabled = vibrateEnabled,
+ alarmSound = alarmSound,
+ onAlarmEnabledChange = onAlarmEnabledChange,
+ onVibrateEnabledChange = onVibrateEnabledChange,
+ onAlarmSoundChanged = onAlarmSoundChanged,
+ onBack = backStack::removeLastOrNull
+ )
+ }
+ entry {
+ AppearanceSettings(
+ preferencesState = preferencesState,
+ onBlackThemeChange = onBlackThemeChange,
+ onThemeChange = onThemeChange,
+ onColorSchemeChange = onColorSchemeChange,
+ onBack = backStack::removeLastOrNull
+ )
+ }
+ entry {
+ TimerSettings(
+ aodEnabled = preferencesState.aodEnabled,
+ focusTimeInputFieldState = focusTimeInputFieldState,
+ shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
+ longBreakTimeInputFieldState = longBreakTimeInputFieldState,
+ sessionsSliderState = sessionsSliderState,
+ onAodEnabledChange = onAodEnabledChange,
+ onBack = backStack::removeLastOrNull
+ )
+ }
}
- }
+ )
}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Preview(
- showSystemUi = true,
- device = Devices.PIXEL_9_PRO
-)
-@Composable
-fun SettingsScreenPreview() {
- TomatoTheme {
- SettingsScreen(
- preferencesState = PreferencesState(),
- 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,
- alarmSound = "null",
- onAlarmEnabledChange = {},
- onVibrateEnabledChange = {},
- onBlackThemeChange = {},
- onAodEnabledChange = {},
- onAlarmSoundChanged = {},
- onThemeChange = {},
- onColorSchemeChange = {},
- modifier = Modifier.fillMaxSize()
- )
- }
-}
-
-data class SettingsSwitchItem(
- val checked: Boolean,
- @param:DrawableRes val icon: Int,
- @param:StringRes val label: Int,
- @param:StringRes val description: Int,
- val onClick: (Boolean) -> Unit
-)
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsSwitchItem.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsSwitchItem.kt
new file mode 100644
index 0000000..75638e3
--- /dev/null
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsSwitchItem.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2025 Nishant Mishra
+ *
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
+ */
+
+package org.nsh07.pomodoro.ui.settingsScreen
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+
+data class SettingsSwitchItem(
+ val checked: Boolean,
+ @param:DrawableRes val icon: Int,
+ @param:StringRes val label: Int,
+ @param:StringRes val description: Int,
+ val onClick: (Boolean) -> Unit
+)
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/AboutCard.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutCard.kt
similarity index 86%
rename from app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/AboutCard.kt
rename to app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutCard.kt
index 96da61c..fe0e1ce 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/AboutCard.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutCard.kt
@@ -1,11 +1,21 @@
/*
* Copyright (c) 2025 Nishant Mishra
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
*/
-package org.nsh07.pomodoro.ui.settingsScreen
+package org.nsh07.pomodoro.ui.settingsScreen.components
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
@@ -36,7 +46,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import org.nsh07.pomodoro.BuildConfig
import org.nsh07.pomodoro.R
-import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexHeadline
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
// Taken from https://github.com/shub39/Grit/blob/master/app/src/main/java/com/shub39/grit/core/presentation/settings/ui/component/AboutApp.kt
@@ -71,10 +80,7 @@ fun AboutCard(modifier: Modifier = Modifier) {
style = MaterialTheme.typography.titleLarge,
fontFamily = robotoFlexTopBar
)
- Text(
- text = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})",
- fontFamily = robotoFlexHeadline
- )
+ Text(text = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
}
Spacer(modifier = Modifier.weight(1f))
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ColorSchemePickerDialog.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ColorSchemePickerDialog.kt
similarity index 87%
rename from app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ColorSchemePickerDialog.kt
rename to app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ColorSchemePickerDialog.kt
index 519f8c7..052f051 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ColorSchemePickerDialog.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ColorSchemePickerDialog.kt
@@ -1,4 +1,21 @@
-package org.nsh07.pomodoro.ui.settingsScreen
+/*
+ * Copyright (c) 2025 Nishant Mishra
+ *
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
+ */
+
+package org.nsh07.pomodoro.ui.settingsScreen.components
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Column
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ColorSchemePickerListItem.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ColorSchemePickerListItem.kt
similarity index 73%
rename from app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ColorSchemePickerListItem.kt
rename to app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ColorSchemePickerListItem.kt
index 088d5be..549ac64 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ColorSchemePickerListItem.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ColorSchemePickerListItem.kt
@@ -1,11 +1,21 @@
/*
* Copyright (c) 2025 Nishant Mishra
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
*/
-package org.nsh07.pomodoro.ui.settingsScreen
+package org.nsh07.pomodoro.ui.settingsScreen.components
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Icon
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/MinuteInputField.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/MinuteInputField.kt
similarity index 77%
rename from app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/MinuteInputField.kt
rename to app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/MinuteInputField.kt
index 6a55afc..1b1f449 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/MinuteInputField.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/MinuteInputField.kt
@@ -1,4 +1,21 @@
-package org.nsh07.pomodoro.ui.settingsScreen
+/*
+ * Copyright (c) 2025 Nishant Mishra
+ *
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
+ */
+
+package org.nsh07.pomodoro.ui.settingsScreen.components
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/MinutesTransformation.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/MinutesTransformation.kt
new file mode 100644
index 0000000..7598168
--- /dev/null
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/MinutesTransformation.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2025 Nishant Mishra
+ *
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
+ */
+
+package org.nsh07.pomodoro.ui.settingsScreen.components
+
+import androidx.compose.foundation.text.input.InputTransformation
+import androidx.compose.foundation.text.input.OutputTransformation
+import androidx.compose.foundation.text.input.TextFieldBuffer
+import androidx.compose.foundation.text.input.insert
+import androidx.core.text.isDigitsOnly
+
+object MinutesInputTransformation : InputTransformation {
+ override fun TextFieldBuffer.transformInput() {
+ if (!this.asCharSequence().isDigitsOnly() || this.length > 2) {
+ revertAllChanges()
+ }
+ }
+}
+
+object MinutesOutputTransformation : OutputTransformation {
+ override fun TextFieldBuffer.transformOutput() {
+ if (this.length == 0) {
+ insert(0, "00")
+ } else if (this.toString().toInt() < 10) {
+ insert(0, "0")
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemeDialog.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ThemeDialog.kt
similarity index 87%
rename from app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemeDialog.kt
rename to app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ThemeDialog.kt
index 1de30cd..244612b 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemeDialog.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ThemeDialog.kt
@@ -1,11 +1,21 @@
/*
* Copyright (c) 2025 Nishant Mishra
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
*/
-package org.nsh07.pomodoro.ui.settingsScreen
+package org.nsh07.pomodoro.ui.settingsScreen.components
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Arrangement
@@ -85,7 +95,7 @@ fun ThemeDialog(
) {
themeMap.entries.forEachIndexed { index: Int, pair: Map.Entry> ->
val text = pair.value.second
- val selected = text == selectedOption.value
+ val selected = text == selectedOption.intValue
ListItem(
leadingContent = {
@@ -113,9 +123,9 @@ fun ThemeDialog(
}
)
.selectable(
- selected = (text == selectedOption.value),
+ selected = (text == selectedOption.intValue),
onClick = {
- selectedOption.value = text
+ selectedOption.intValue = text
onThemeChange(
reverseThemeMap[context.getString(
selectedOption.intValue
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemePickerListItem.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ThemePickerListItem.kt
similarity index 72%
rename from app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemePickerListItem.kt
rename to app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ThemePickerListItem.kt
index 0c66f00..4701d19 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/ThemePickerListItem.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/ThemePickerListItem.kt
@@ -1,11 +1,21 @@
/*
* Copyright (c) 2025 Nishant Mishra
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
*/
-package org.nsh07.pomodoro.ui.settingsScreen
+package org.nsh07.pomodoro.ui.settingsScreen.components
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Icon
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt
new file mode 100644
index 0000000..3b6b48f
--- /dev/null
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2025 Nishant Mishra
+ *
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
+ */
+
+package org.nsh07.pomodoro.ui.settingsScreen.screens
+
+import android.app.Activity
+import android.content.Intent
+import android.media.RingtoneManager
+import android.net.Uri
+import android.os.Build
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.LargeFlexibleTopAppBar
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.Switch
+import androidx.compose.material3.SwitchDefaults
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.core.net.toUri
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import org.nsh07.pomodoro.R
+import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem
+import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState
+import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
+import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
+import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors
+import org.nsh07.pomodoro.ui.theme.CustomColors.topBarColors
+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 AlarmSettings(
+ preferencesState: PreferencesState,
+ alarmEnabled: Boolean,
+ vibrateEnabled: Boolean,
+ alarmSound: String,
+ onAlarmEnabledChange: (Boolean) -> Unit,
+ onVibrateEnabledChange: (Boolean) -> Unit,
+ onAlarmSoundChanged: (Uri?) -> Unit,
+ onBack: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
+ val context = LocalContext.current
+
+ var alarmName by remember { mutableStateOf("...") }
+
+ LaunchedEffect(alarmSound) {
+ withContext(Dispatchers.IO) {
+ alarmName =
+ RingtoneManager.getRingtone(context, alarmSound.toUri())?.getTitle(context) ?: ""
+ }
+ }
+
+ val ringtonePickerLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.StartActivityForResult()
+ ) { result ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ val uri =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ result.data?.getParcelableExtra(
+ RingtoneManager.EXTRA_RINGTONE_PICKED_URI,
+ Uri::class.java
+ )
+ } else {
+ @Suppress("DEPRECATION")
+ result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
+ }
+ onAlarmSoundChanged(uri)
+ }
+ }
+
+ val ringtonePickerIntent = remember(alarmSound) {
+ Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
+ putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM)
+ putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, context.getString(R.string.alarm_sound))
+ putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, alarmSound.toUri())
+ }
+ }
+
+ val switchItems = remember(
+ preferencesState.blackTheme,
+ preferencesState.aodEnabled,
+ alarmEnabled,
+ vibrateEnabled
+ ) {
+ listOf(
+ SettingsSwitchItem(
+ checked = alarmEnabled,
+ icon = R.drawable.alarm_on,
+ label = R.string.alarm,
+ description = R.string.alarm_desc,
+ onClick = onAlarmEnabledChange
+ ),
+ SettingsSwitchItem(
+ checked = vibrateEnabled,
+ icon = R.drawable.mobile_vibrate,
+ label = R.string.vibrate,
+ description = R.string.vibrate_desc,
+ onClick = onVibrateEnabledChange
+ )
+ )
+ }
+
+ Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) {
+ LargeFlexibleTopAppBar(
+ title = {
+ Text(stringResource(R.string.alarm), fontFamily = robotoFlexTopBar)
+ },
+ subtitle = {
+ Text(stringResource(R.string.settings))
+ },
+ navigationIcon = {
+ IconButton(onBack) {
+ Icon(
+ painterResource(R.drawable.arrow_back),
+ null
+ )
+ }
+ },
+ colors = topBarColors,
+ scrollBehavior = scrollBehavior
+ )
+
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp),
+ modifier = Modifier
+ .background(topBarColors.containerColor)
+ .fillMaxSize()
+ .padding(horizontal = 16.dp)
+ ) {
+ item {
+ Spacer(Modifier.height(14.dp))
+ }
+
+ item {
+ ListItem(
+ leadingContent = {
+ Icon(painterResource(R.drawable.alarm), null)
+ },
+ headlineContent = { Text(stringResource(R.string.alarm_sound)) },
+ supportingContent = { Text(alarmName) },
+ colors = listItemColors,
+ modifier = Modifier
+ .clip(topListItemShape)
+ .clickable(onClick = { ringtonePickerLauncher.launch(ringtonePickerIntent) })
+ )
+ }
+ itemsIndexed(switchItems) { index, item ->
+ ListItem(
+ leadingContent = {
+ Icon(painterResource(item.icon), contentDescription = null)
+ },
+ headlineContent = { Text(stringResource(item.label)) },
+ supportingContent = { Text(stringResource(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(
+ when (index) {
+ switchItems.lastIndex -> bottomListItemShape
+ else -> middleListItemShape
+ }
+ )
+ )
+ }
+
+ item { Spacer(Modifier.height(12.dp)) }
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
+@Preview
+@Composable
+fun AlarmSettingsPreview() {
+ val preferencesState = PreferencesState()
+ AlarmSettings(
+ preferencesState = preferencesState,
+ alarmEnabled = true,
+ vibrateEnabled = false,
+ alarmSound = "",
+ onAlarmEnabledChange = {},
+ onVibrateEnabledChange = {},
+ onAlarmSoundChanged = {},
+ onBack = {})
+}
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AppearanceSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AppearanceSettings.kt
new file mode 100644
index 0000000..81175af
--- /dev/null
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AppearanceSettings.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2025 Nishant Mishra
+ *
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
+ */
+
+package org.nsh07.pomodoro.ui.settingsScreen.screens
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.LargeFlexibleTopAppBar
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.Switch
+import androidx.compose.material3.SwitchDefaults
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+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.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import org.nsh07.pomodoro.R
+import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem
+import org.nsh07.pomodoro.ui.settingsScreen.components.ColorSchemePickerListItem
+import org.nsh07.pomodoro.ui.settingsScreen.components.ThemePickerListItem
+import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState
+import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
+import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
+import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors
+import org.nsh07.pomodoro.ui.theme.CustomColors.topBarColors
+import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.bottomListItemShape
+import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.middleListItemShape
+import org.nsh07.pomodoro.utils.toColor
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun AppearanceSettings(
+ preferencesState: PreferencesState,
+ onBlackThemeChange: (Boolean) -> Unit,
+ onThemeChange: (String) -> Unit,
+ onColorSchemeChange: (Color) -> Unit,
+ onBack: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ val themeMap: Map> = remember {
+ mapOf(
+ "auto" to Pair(
+ R.drawable.brightness_auto,
+ R.string.system_default
+ ),
+ "light" to Pair(R.drawable.light_mode, R.string.light),
+ "dark" to Pair(R.drawable.dark_mode, R.string.dark)
+ )
+ }
+ val reverseThemeMap: Map = mapOf(
+ stringResource(R.string.system_default) to "auto",
+ stringResource(R.string.light) to "light",
+ stringResource(R.string.dark) to "dark"
+ )
+
+ val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
+
+ Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) {
+ LargeFlexibleTopAppBar(
+ title = {
+ Text(stringResource(R.string.appearance), fontFamily = robotoFlexTopBar)
+ },
+ subtitle = {
+ Text(stringResource(R.string.settings))
+ },
+ navigationIcon = {
+ IconButton(onBack) {
+ Icon(
+ painterResource(R.drawable.arrow_back),
+ null
+ )
+ }
+ },
+ colors = topBarColors,
+ scrollBehavior = scrollBehavior
+ )
+
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp),
+ modifier = Modifier
+ .background(topBarColors.containerColor)
+ .fillMaxSize()
+ .padding(horizontal = 16.dp)
+ ) {
+ item {
+ Spacer(Modifier.height(14.dp))
+ }
+ item {
+ ColorSchemePickerListItem(
+ color = preferencesState.colorScheme.toColor(),
+ items = 3,
+ index = 0,
+ onColorChange = onColorSchemeChange
+ )
+ }
+ item {
+ ThemePickerListItem(
+ theme = preferencesState.theme,
+ themeMap = themeMap,
+ reverseThemeMap = reverseThemeMap,
+ onThemeChange = onThemeChange,
+ items = 3,
+ index = 1,
+ modifier = Modifier
+ .clip(middleListItemShape)
+ )
+ }
+ item {
+ val item = SettingsSwitchItem(
+ checked = preferencesState.blackTheme,
+ icon = R.drawable.contrast,
+ label = R.string.black_theme,
+ description = R.string.black_theme_desc,
+ onClick = onBlackThemeChange
+ )
+ ListItem(
+ leadingContent = {
+ Icon(painterResource(item.icon), contentDescription = null)
+ },
+ headlineContent = { Text(stringResource(item.label)) },
+ supportingContent = { Text(stringResource(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)) }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun AppearanceSettingsPreview() {
+ val preferencesState = PreferencesState()
+ AppearanceSettings(
+ preferencesState = preferencesState,
+ onBlackThemeChange = {},
+ onThemeChange = {},
+ onColorSchemeChange = {},
+ onBack = {}
+ )
+}
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt
new file mode 100644
index 0000000..01dddba
--- /dev/null
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2025 Nishant Mishra
+ *
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
+ */
+
+package org.nsh07.pomodoro.ui.settingsScreen.screens
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.background
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+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.lazy.LazyColumn
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.FilledTonalIconToggleButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.LargeFlexibleTopAppBar
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.MaterialTheme.colorScheme
+import androidx.compose.material3.MaterialTheme.typography
+import androidx.compose.material3.Slider
+import androidx.compose.material3.SliderState
+import androidx.compose.material3.Switch
+import androidx.compose.material3.SwitchDefaults
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+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
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import org.nsh07.pomodoro.R
+import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem
+import org.nsh07.pomodoro.ui.settingsScreen.components.MinuteInputField
+import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
+import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
+import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors
+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
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun TimerSettings(
+ aodEnabled: Boolean,
+ focusTimeInputFieldState: TextFieldState,
+ shortBreakTimeInputFieldState: TextFieldState,
+ longBreakTimeInputFieldState: TextFieldState,
+ sessionsSliderState: SliderState,
+ onAodEnabledChange: (Boolean) -> Unit,
+ onBack: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
+
+ Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) {
+ LargeFlexibleTopAppBar(
+ title = {
+ Text(stringResource(R.string.timer), fontFamily = robotoFlexTopBar)
+ },
+ subtitle = {
+ Text(stringResource(R.string.settings))
+ },
+ navigationIcon = {
+ IconButton(onBack) {
+ Icon(
+ painterResource(R.drawable.arrow_back),
+ null
+ )
+ }
+ },
+ colors = topBarColors,
+ scrollBehavior = scrollBehavior
+ )
+
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp),
+ modifier = Modifier
+ .background(topBarColors.containerColor)
+ .fillMaxSize()
+ .padding(horizontal = 16.dp)
+ ) {
+ item {
+ Spacer(Modifier.height(14.dp))
+ }
+ item {
+ Row(
+ horizontalArrangement = Arrangement.Center,
+ modifier = Modifier
+ .fillMaxWidth()
+ .horizontalScroll(rememberScrollState())
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ Text(
+ stringResource(R.string.focus),
+ style = typography.titleSmallEmphasized
+ )
+ MinuteInputField(
+ state = focusTimeInputFieldState,
+ shape = RoundedCornerShape(
+ topStart = topListItemShape.topStart,
+ bottomStart = topListItemShape.topStart,
+ topEnd = topListItemShape.bottomStart,
+ bottomEnd = topListItemShape.bottomStart
+ ),
+ imeAction = ImeAction.Next
+ )
+ }
+ Spacer(Modifier.width(2.dp))
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ Text(
+ stringResource(R.string.short_break),
+ style = typography.titleSmallEmphasized
+ )
+ MinuteInputField(
+ state = shortBreakTimeInputFieldState,
+ shape = RoundedCornerShape(middleListItemShape.topStart),
+ imeAction = ImeAction.Next
+ )
+ }
+ Spacer(Modifier.width(2.dp))
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ Text(
+ stringResource(R.string.long_break),
+ style = typography.titleSmallEmphasized
+ )
+ MinuteInputField(
+ state = longBreakTimeInputFieldState,
+ shape = RoundedCornerShape(
+ topStart = bottomListItemShape.topStart,
+ bottomStart = bottomListItemShape.topStart,
+ topEnd = bottomListItemShape.bottomStart,
+ bottomEnd = bottomListItemShape.bottomStart
+ ),
+ imeAction = ImeAction.Done
+ )
+ }
+ }
+ }
+ item {
+ Spacer(Modifier.height(12.dp))
+ }
+ item {
+ ListItem(
+ leadingContent = {
+ Icon(painterResource(R.drawable.clocks), null)
+ },
+ headlineContent = {
+ Text(stringResource(R.string.session_length))
+ },
+ supportingContent = {
+ Column {
+ Text(
+ stringResource(
+ R.string.session_length_desc,
+ sessionsSliderState.value.toInt()
+ )
+ )
+ Slider(
+ state = sessionsSliderState,
+ modifier = Modifier.padding(vertical = 4.dp)
+ )
+ }
+ },
+ colors = listItemColors,
+ modifier = Modifier.clip(cardShape)
+ )
+ }
+ item { Spacer(Modifier.height(12.dp)) }
+ item {
+ val item = SettingsSwitchItem(
+ checked = aodEnabled,
+ icon = R.drawable.aod,
+ label = R.string.always_on_display,
+ description = R.string.always_on_display_desc,
+ onClick = onAodEnabledChange
+ )
+ ListItem(
+ leadingContent = {
+ Icon(
+ painterResource(item.icon),
+ contentDescription = null,
+ modifier = Modifier.padding(top = 4.dp)
+ )
+ },
+ headlineContent = { Text(stringResource(item.label)) },
+ supportingContent = { Text(stringResource(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(cardShape)
+ )
+ }
+
+ item {
+ var expanded by remember { mutableStateOf(false) }
+ Column(
+ horizontalAlignment = Alignment.End,
+ modifier = Modifier
+ .padding(vertical = 6.dp)
+ .fillMaxWidth()
+ ) {
+ FilledTonalIconToggleButton(
+ checked = expanded,
+ onCheckedChange = { expanded = it },
+ shapes = IconButtonDefaults.toggleableShapes(),
+ modifier = Modifier.width(52.dp)
+ ) {
+ Icon(
+ painterResource(R.drawable.info),
+ null
+ )
+ }
+ AnimatedVisibility(expanded) {
+ Text(
+ stringResource(R.string.pomodoro_info),
+ style = typography.bodyMedium,
+ color = colorScheme.onSurfaceVariant,
+ modifier = Modifier.padding(8.dp)
+ )
+ }
+ }
+ }
+
+ item { Spacer(Modifier.height(12.dp)) }
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
+@Composable
+private fun TimerSettingsPreview() {
+ val focusTimeInputFieldState = TextFieldState("25")
+ val shortBreakTimeInputFieldState = TextFieldState("5")
+ val longBreakTimeInputFieldState = TextFieldState("15")
+ val sessionsSliderState = SliderState(
+ value = 4f,
+ valueRange = 1f..8f,
+ steps = 6
+ )
+ TimerSettings(
+ focusTimeInputFieldState = focusTimeInputFieldState,
+ shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
+ longBreakTimeInputFieldState = longBreakTimeInputFieldState,
+ sessionsSliderState = sessionsSliderState,
+ aodEnabled = true,
+ onBack = {},
+ onAodEnabledChange = {}
+ )
+}
\ No newline at end of file
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 454bf82..76ebb8c 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
@@ -1,8 +1,18 @@
/*
* Copyright (c) 2025 Nishant Mishra
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
*/
package org.nsh07.pomodoro.ui.settingsScreen.viewModel
@@ -11,6 +21,7 @@ import android.net.Uri
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SliderState
+import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.graphics.Color
import androidx.lifecycle.ViewModel
@@ -31,12 +42,15 @@ import kotlinx.coroutines.launch
import org.nsh07.pomodoro.TomatoApplication
import org.nsh07.pomodoro.data.AppPreferenceRepository
import org.nsh07.pomodoro.data.TimerRepository
+import org.nsh07.pomodoro.ui.Screen
@OptIn(FlowPreview::class, ExperimentalMaterial3Api::class)
class SettingsViewModel(
private val preferenceRepository: AppPreferenceRepository,
private val timerRepository: TimerRepository,
) : ViewModel() {
+ val backStack = mutableStateListOf(Screen.Settings.Main)
+
private val _preferencesState = MutableStateFlow(PreferencesState())
val preferencesState = _preferencesState.asStateFlow()
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 738f989..064d7d8 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
@@ -1,9 +1,28 @@
+/*
+ * Copyright (c) 2025 Nishant Mishra
+ *
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
+ */
+
package org.nsh07.pomodoro.ui.theme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ListItemColors
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme.colorScheme
+import androidx.compose.material3.SwitchColors
+import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.TopAppBarColors
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
@@ -41,4 +60,9 @@ object CustomColors {
supportingColor = colorScheme.onSecondaryFixedVariant,
trailingIconColor = colorScheme.onSecondaryFixedVariant
)
+
+ val switchColors: SwitchColors
+ @Composable get() = SwitchDefaults.colors(
+ checkedIconColor = colorScheme.primary,
+ )
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/arrow_back.xml b/app/src/main/res/drawable/arrow_back.xml
new file mode 100644
index 0000000..d867d93
--- /dev/null
+++ b/app/src/main/res/drawable/arrow_back.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/arrow_forward_big.xml b/app/src/main/res/drawable/arrow_forward_big.xml
new file mode 100644
index 0000000..eac1368
--- /dev/null
+++ b/app/src/main/res/drawable/arrow_forward_big.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c185a08..2697a07 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,20 @@
+
+
Alarm
Ring alarm when a timer completes
@@ -59,4 +76,6 @@
Vibrate
Vibrate when a timer completes
Weekly productivity analysis
+ Appearance
+ Durations
\ No newline at end of file