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