diff --git a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt index a98932a..9404106 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 @@ -70,27 +85,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/SettingsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt index 9bc992a..ebc7c3f 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 @@ -19,6 +19,11 @@ package org.nsh07.pomodoro.ui.settingsScreen import android.content.Intent import android.net.Uri +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.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -27,10 +32,12 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed 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.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.SliderState import androidx.compose.material3.Text @@ -40,24 +47,36 @@ import androidx.compose.material3.rememberSliderState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier 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.style.TextOverflow import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberNavBackStack +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.topBarColors import org.nsh07.pomodoro.ui.theme.TomatoTheme @@ -147,44 +166,122 @@ private fun SettingsScreen( onColorSchemeChange: (Color) -> Unit, modifier: Modifier = Modifier ) { + val context = LocalContext.current val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + val backStack = rememberNavBackStack(Screen.Settings.Main) - Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { - TopAppBar( - title = { - Text( - stringResource(R.string.settings), - style = LocalTextStyle.current.copy( - fontFamily = robotoFlexTopBar, - fontSize = 32.sp, - lineHeight = 32.sp + 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.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 { 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 ) - }, - 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 { AboutCard() } - - item { Spacer(Modifier.height(12.dp)) } - - item {} - - item { Spacer(Modifier.height(12.dp)) } + } + 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) 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 index f971356..3b6b48f 100644 --- 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 @@ -151,10 +151,10 @@ fun AlarmSettings( Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { LargeFlexibleTopAppBar( title = { - Text("Alarm", fontFamily = robotoFlexTopBar) + Text(stringResource(R.string.alarm), fontFamily = robotoFlexTopBar) }, subtitle = { - Text("Settings") + Text(stringResource(R.string.settings)) }, navigationIcon = { IconButton(onBack) { @@ -241,12 +241,7 @@ fun AlarmSettings( @Preview @Composable fun AlarmSettingsPreview() { - val preferencesState = PreferencesState( - theme = "auto", - colorScheme = "White", - blackTheme = false, - aodEnabled = false - ) + val preferencesState = PreferencesState() AlarmSettings( preferencesState = preferencesState, alarmEnabled = true, 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 index c9379f3..81175af 100644 --- 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 @@ -42,7 +42,6 @@ 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.tooling.preview.Preview @@ -70,8 +69,6 @@ fun AppearanceSettings( onBack: () -> Unit, modifier: Modifier = Modifier ) { - val context = LocalContext.current - val themeMap: Map> = remember { mapOf( "auto" to Pair( @@ -82,23 +79,21 @@ fun AppearanceSettings( "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" - ) - } + 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("Appearance", fontFamily = robotoFlexTopBar) + Text(stringResource(R.string.appearance), fontFamily = robotoFlexTopBar) }, subtitle = { - Text("Settings") + Text(stringResource(R.string.settings)) }, navigationIcon = { IconButton(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 index c10d0ab..01dddba 100644 --- 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 @@ -93,10 +93,10 @@ fun TimerSettings( Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { LargeFlexibleTopAppBar( title = { - Text("Timer", fontFamily = robotoFlexTopBar) + Text(stringResource(R.string.timer), fontFamily = robotoFlexTopBar) }, subtitle = { - Text("Settings") + Text(stringResource(R.string.settings)) }, navigationIcon = { IconButton(onBack) { 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