@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Screen.Settings> {
|
||||
entry<Screen.Settings.Main> {
|
||||
SettingsScreenRoot(
|
||||
modifier = modifier.padding(
|
||||
start = contentPadding.calculateStartPadding(layoutDirection),
|
||||
|
||||
62
app/src/main/java/org/nsh07/pomodoro/ui/Navigation.kt
Normal file
62
app/src/main/java/org/nsh07/pomodoro/ui/Navigation.kt
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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)
|
||||
)
|
||||
)
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
data class SettingsNavItem(
|
||||
val route: Screen.Settings,
|
||||
@param:DrawableRes val icon: Int,
|
||||
@param:StringRes val label: Int,
|
||||
val innerSettings: List<Int>
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Screen.Settings>,
|
||||
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<String, Pair<Int, Int>> = 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<String, String> = 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<Screen.Settings.Main> {
|
||||
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<Screen.Settings.Alarm> {
|
||||
AlarmSettings(
|
||||
preferencesState = preferencesState,
|
||||
alarmEnabled = alarmEnabled,
|
||||
vibrateEnabled = vibrateEnabled,
|
||||
alarmSound = alarmSound,
|
||||
onAlarmEnabledChange = onAlarmEnabledChange,
|
||||
onVibrateEnabledChange = onVibrateEnabledChange,
|
||||
onAlarmSoundChanged = onAlarmSoundChanged,
|
||||
onBack = backStack::removeLastOrNull
|
||||
)
|
||||
}
|
||||
entry<Screen.Settings.Appearance> {
|
||||
AppearanceSettings(
|
||||
preferencesState = preferencesState,
|
||||
onBlackThemeChange = onBlackThemeChange,
|
||||
onThemeChange = onThemeChange,
|
||||
onColorSchemeChange = onColorSchemeChange,
|
||||
onBack = backStack::removeLastOrNull
|
||||
)
|
||||
}
|
||||
entry<Screen.Settings.Timer> {
|
||||
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
|
||||
)
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
)
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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))
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.ui.settingsScreen.components
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.ui.settingsScreen
|
||||
package org.nsh07.pomodoro.ui.settingsScreen.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.ui.settingsScreen.components
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.background
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, Pair<Int, Int>> ->
|
||||
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
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.ui.settingsScreen
|
||||
package org.nsh07.pomodoro.ui.settingsScreen.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 = {})
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, Pair<Int, Int>> = 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<String, String> = 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 = {}
|
||||
)
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 = {}
|
||||
)
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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>(Screen.Settings.Main)
|
||||
|
||||
private val _preferencesState = MutableStateFlow(PreferencesState())
|
||||
val preferencesState = _preferencesState.asStateFlow()
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
21
app/src/main/res/drawable/arrow_back.xml
Normal file
21
app/src/main/res/drawable/arrow_back.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<!--
|
||||
~ 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 Foobar. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#e3e3e3"
|
||||
android:pathData="m313,520 l196,196q12,12 11.5,28T508,772q-12,11 -28,11.5T452,772L188,508q-6,-6 -8.5,-13t-2.5,-15q0,-8 2.5,-15t8.5,-13l264,-264q11,-11 27.5,-11t28.5,11q12,12 12,28.5T508,245L313,440h447q17,0 28.5,11.5T800,480q0,17 -11.5,28.5T760,520L313,520Z" />
|
||||
</vector>
|
||||
26
app/src/main/res/drawable/arrow_forward_big.xml
Normal file
26
app/src/main/res/drawable/arrow_forward_big.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<!--
|
||||
~ 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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#e3e3e3"
|
||||
android:pathData="m321,880 l-71,-71 329,-329 -329,-329 71,-71 400,400L321,880Z" />
|
||||
</vector>
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<string name="alarm">Alarm</string>
|
||||
<string name="alarm_desc">Ring alarm when a timer completes</string>
|
||||
@@ -59,4 +76,6 @@
|
||||
<string name="vibrate">Vibrate</string>
|
||||
<string name="vibrate_desc">Vibrate when a timer completes</string>
|
||||
<string name="weekly_productivity_analysis">Weekly productivity analysis</string>
|
||||
<string name="appearance">Appearance</string>
|
||||
<string name="durations">Durations</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user