Merge branch 'dev'
This commit is contained in:
@@ -43,8 +43,8 @@ android {
|
|||||||
applicationId = "org.nsh07.pomodoro"
|
applicationId = "org.nsh07.pomodoro"
|
||||||
minSdk = 27
|
minSdk = 27
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 19
|
versionCode = 20
|
||||||
versionName = "1.6.4"
|
versionName = "1.6.5"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
@@ -57,9 +57,9 @@ android {
|
|||||||
release {
|
release {
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
isShrinkResources = true
|
isShrinkResources = true
|
||||||
proguardFiles(
|
}
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
debug {
|
||||||
)
|
applicationIdSuffix = ".debug"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,10 +68,16 @@ android {
|
|||||||
create("foss") {
|
create("foss") {
|
||||||
dimension = "version"
|
dimension = "version"
|
||||||
isDefault = true
|
isDefault = true
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules-foss.pro"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
create("play") {
|
create("play") {
|
||||||
dimension = "version"
|
dimension = "version"
|
||||||
versionNameSuffix = "-play"
|
versionNameSuffix = "-play"
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules-play.pro"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
21
app/proguard-rules-play.pro
Normal file
21
app/proguard-rules-play.pro
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
@@ -25,7 +25,6 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
*/
|
*/
|
||||||
class FossBillingManager : BillingManager {
|
class FossBillingManager : BillingManager {
|
||||||
override val isPlus = MutableStateFlow(true).asStateFlow()
|
override val isPlus = MutableStateFlow(true).asStateFlow()
|
||||||
override val isLoaded = MutableStateFlow(true).asStateFlow()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object BillingManagerProvider {
|
object BillingManagerProvider {
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* 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.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonColors
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.nsh07.pomodoro.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TopButton(
|
||||||
|
buttonColors: ButtonColors,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
Button(
|
||||||
|
colors = buttonColors,
|
||||||
|
onClick = { uriHandler.openUri("https://coff.ee/nsh07") },
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.bmc),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.height(24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = stringResource(R.string.bmc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BottomButton(
|
||||||
|
buttonColors: ButtonColors,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
Button(
|
||||||
|
colors = buttonColors,
|
||||||
|
onClick = { uriHandler.openUri("https://hosted.weblate.org/engage/tomato/") },
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.weblate),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = stringResource(R.string.help_with_translation))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,34 +50,22 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val preferencesState by settingsViewModel.preferencesState.collectAsStateWithLifecycle()
|
val settingsState by settingsViewModel.settingsState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val darkTheme = when (preferencesState.theme) {
|
val darkTheme = when (settingsState.theme) {
|
||||||
"dark" -> true
|
"dark" -> true
|
||||||
"light" -> false
|
"light" -> false
|
||||||
else -> isSystemInDarkTheme()
|
else -> isSystemInDarkTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
val seed = preferencesState.colorScheme.toColor()
|
val seed = settingsState.colorScheme.toColor()
|
||||||
|
|
||||||
val isPlus by settingsViewModel.isPlus.collectAsStateWithLifecycle()
|
val isPlus by settingsViewModel.isPlus.collectAsStateWithLifecycle()
|
||||||
val isPurchaseStateLoaded by settingsViewModel.isPurchaseStateLoaded.collectAsStateWithLifecycle()
|
|
||||||
val isSettingsLoaded by settingsViewModel.isSettingsLoaded.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
LaunchedEffect(isPurchaseStateLoaded, isPlus, isSettingsLoaded) {
|
|
||||||
if (isPurchaseStateLoaded && isSettingsLoaded) {
|
|
||||||
if (!isPlus) {
|
|
||||||
settingsViewModel.resetPaywalledSettings()
|
|
||||||
} else {
|
|
||||||
settingsViewModel.reloadSettings()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TomatoTheme(
|
TomatoTheme(
|
||||||
darkTheme = darkTheme,
|
darkTheme = darkTheme,
|
||||||
seedColor = seed,
|
seedColor = seed,
|
||||||
blackTheme = preferencesState.blackTheme
|
blackTheme = settingsState.blackTheme
|
||||||
) {
|
) {
|
||||||
val colorScheme = colorScheme
|
val colorScheme = colorScheme
|
||||||
LaunchedEffect(colorScheme) {
|
LaunchedEffect(colorScheme) {
|
||||||
@@ -86,7 +74,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
AppScreen(
|
AppScreen(
|
||||||
isPlus = isPlus,
|
isPlus = isPlus,
|
||||||
isAODEnabled = preferencesState.aodEnabled,
|
isAODEnabled = settingsState.aodEnabled,
|
||||||
setTimerFrequency = {
|
setTimerFrequency = {
|
||||||
appContainer.appTimerRepository.timerFrequency = it
|
appContainer.appTimerRepository.timerFrequency = it
|
||||||
}
|
}
|
||||||
@@ -105,6 +93,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
// Increase the timer loop frequency again when visible to make the progress smoother
|
// Increase the timer loop frequency again when visible to make the progress smoother
|
||||||
appContainer.appTimerRepository.timerFrequency = 10f
|
appContainer.appTimerRepository.timerFrequency = 60f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,5 +21,4 @@ import kotlinx.coroutines.flow.StateFlow
|
|||||||
|
|
||||||
interface BillingManager {
|
interface BillingManager {
|
||||||
val isPlus: StateFlow<Boolean>
|
val isPlus: StateFlow<Boolean>
|
||||||
val isLoaded: StateFlow<Boolean>
|
|
||||||
}
|
}
|
||||||
@@ -23,11 +23,13 @@ import android.content.Context
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationCompat.VISIBILITY_PUBLIC
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import org.nsh07.pomodoro.R
|
import org.nsh07.pomodoro.R
|
||||||
import org.nsh07.pomodoro.billing.BillingManager
|
import org.nsh07.pomodoro.billing.BillingManager
|
||||||
import org.nsh07.pomodoro.billing.BillingManagerProvider
|
import org.nsh07.pomodoro.billing.BillingManagerProvider
|
||||||
|
import org.nsh07.pomodoro.service.ServiceHelper
|
||||||
import org.nsh07.pomodoro.service.addTimerActions
|
import org.nsh07.pomodoro.service.addTimerActions
|
||||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
|
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
|
||||||
import org.nsh07.pomodoro.utils.millisecondsToStr
|
import org.nsh07.pomodoro.utils.millisecondsToStr
|
||||||
@@ -40,6 +42,7 @@ interface AppContainer {
|
|||||||
val notificationManager: NotificationManagerCompat
|
val notificationManager: NotificationManagerCompat
|
||||||
val notificationManagerService: NotificationManager
|
val notificationManagerService: NotificationManager
|
||||||
val notificationBuilder: NotificationCompat.Builder
|
val notificationBuilder: NotificationCompat.Builder
|
||||||
|
val serviceHelper: ServiceHelper
|
||||||
val timerState: MutableStateFlow<TimerState>
|
val timerState: MutableStateFlow<TimerState>
|
||||||
val time: MutableStateFlow<Long>
|
val time: MutableStateFlow<Long>
|
||||||
var activityTurnScreenOn: (Boolean) -> Unit
|
var activityTurnScreenOn: (Boolean) -> Unit
|
||||||
@@ -83,6 +86,11 @@ class DefaultAppContainer(context: Context) : AppContainer {
|
|||||||
.setSilent(true)
|
.setSilent(true)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setRequestPromotedOngoing(true)
|
.setRequestPromotedOngoing(true)
|
||||||
|
.setVisibility(VISIBILITY_PUBLIC)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val serviceHelper: ServiceHelper by lazy {
|
||||||
|
ServiceHelper(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val timerState: MutableStateFlow<TimerState> by lazy {
|
override val timerState: MutableStateFlow<TimerState> by lazy {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
|
|
||||||
fun getDatabase(context: Context): AppDatabase {
|
fun getDatabase(context: Context): AppDatabase {
|
||||||
return Instance ?: synchronized(this) {
|
return Instance ?: synchronized(this) {
|
||||||
Room.databaseBuilder(context, AppDatabase::class.java, "app_database")
|
Instance ?: Room.databaseBuilder(context, AppDatabase::class.java, "app_database")
|
||||||
.build()
|
.build()
|
||||||
.also { Instance = it }
|
.also { Instance = it }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import android.net.Uri
|
|||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import androidx.compose.material3.ColorScheme
|
import androidx.compose.material3.ColorScheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface that holds the timer durations for each timer type. This repository maintains a single
|
* Interface that holds the timer durations for each timer type. This repository maintains a single
|
||||||
@@ -43,7 +44,7 @@ interface TimerRepository {
|
|||||||
|
|
||||||
var alarmSoundUri: Uri?
|
var alarmSoundUri: Uri?
|
||||||
|
|
||||||
var serviceRunning: Boolean
|
var serviceRunning: MutableStateFlow<Boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,12 +55,12 @@ class AppTimerRepository : TimerRepository {
|
|||||||
override var shortBreakTime = 5 * 60 * 1000L
|
override var shortBreakTime = 5 * 60 * 1000L
|
||||||
override var longBreakTime = 15 * 60 * 1000L
|
override var longBreakTime = 15 * 60 * 1000L
|
||||||
override var sessionLength = 4
|
override var sessionLength = 4
|
||||||
override var timerFrequency: Float = 10f
|
override var timerFrequency: Float = 60f
|
||||||
override var alarmEnabled = true
|
override var alarmEnabled = true
|
||||||
override var vibrateEnabled = true
|
override var vibrateEnabled = true
|
||||||
override var dndEnabled: Boolean = false
|
override var dndEnabled: Boolean = false
|
||||||
override var colorScheme = lightColorScheme()
|
override var colorScheme = lightColorScheme()
|
||||||
override var alarmSoundUri: Uri? =
|
override var alarmSoundUri: Uri? =
|
||||||
Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI
|
Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI
|
||||||
override var serviceRunning = false
|
override var serviceRunning = MutableStateFlow(false)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerAction
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class that holds a reference to [Context] and helps call [Context.startService] in
|
||||||
|
* [androidx.lifecycle.ViewModel]s. This class must be managed by an [android.app.Application] class
|
||||||
|
* to scope it to the Activity's lifecycle and prevent leaks.
|
||||||
|
*/
|
||||||
|
class ServiceHelper(private val context: Context) {
|
||||||
|
fun startService(action: TimerAction) {
|
||||||
|
when (action) {
|
||||||
|
TimerAction.ResetTimer ->
|
||||||
|
Intent(context, TimerService::class.java).also {
|
||||||
|
it.action = TimerService.Actions.RESET.toString()
|
||||||
|
context.startService(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
is TimerAction.SkipTimer ->
|
||||||
|
Intent(context, TimerService::class.java).also {
|
||||||
|
it.action = TimerService.Actions.SKIP.toString()
|
||||||
|
context.startService(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
TimerAction.StopAlarm ->
|
||||||
|
Intent(context, TimerService::class.java).also {
|
||||||
|
it.action =
|
||||||
|
TimerService.Actions.STOP_ALARM.toString()
|
||||||
|
context.startService(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
TimerAction.ToggleTimer ->
|
||||||
|
Intent(context, TimerService::class.java).also {
|
||||||
|
it.action = TimerService.Actions.TOGGLE.toString()
|
||||||
|
context.startService(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -98,12 +98,12 @@ class TimerService : Service() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
timerRepository.serviceRunning = true
|
timerRepository.serviceRunning.update { true }
|
||||||
alarm = initializeMediaPlayer()
|
alarm = initializeMediaPlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
timerRepository.serviceRunning = false
|
timerRepository.serviceRunning.update { false }
|
||||||
runBlocking {
|
runBlocking {
|
||||||
job.cancel()
|
job.cancel()
|
||||||
saveTimeToDb()
|
saveTimeToDb()
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ fun SharedTransitionScope.AlwaysOnDisplay(
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDispose {
|
onDispose {
|
||||||
setTimerFrequency(10f)
|
setTimerFrequency(60f)
|
||||||
window.clearFlags(
|
window.clearFlags(
|
||||||
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
|
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
|
||||||
WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
|
WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -64,7 +63,6 @@ import org.nsh07.pomodoro.ui.settingsScreen.SettingsScreenRoot
|
|||||||
import org.nsh07.pomodoro.ui.statsScreen.StatsScreenRoot
|
import org.nsh07.pomodoro.ui.statsScreen.StatsScreenRoot
|
||||||
import org.nsh07.pomodoro.ui.timerScreen.AlarmDialog
|
import org.nsh07.pomodoro.ui.timerScreen.AlarmDialog
|
||||||
import org.nsh07.pomodoro.ui.timerScreen.TimerScreen
|
import org.nsh07.pomodoro.ui.timerScreen.TimerScreen
|
||||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerAction
|
|
||||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerViewModel
|
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@@ -79,9 +77,7 @@ fun AppScreen(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val uiState by timerViewModel.timerState.collectAsStateWithLifecycle()
|
val uiState by timerViewModel.timerState.collectAsStateWithLifecycle()
|
||||||
val remainingTime by timerViewModel.time.collectAsStateWithLifecycle()
|
val progress by timerViewModel.progress.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val progress by rememberUpdatedState((uiState.totalTime.toFloat() - remainingTime) / uiState.totalTime)
|
|
||||||
|
|
||||||
val layoutDirection = LocalLayoutDirection.current
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
val motionScheme = motionScheme
|
val motionScheme = motionScheme
|
||||||
@@ -166,34 +162,7 @@ fun AppScreen(
|
|||||||
timerState = uiState,
|
timerState = uiState,
|
||||||
isPlus = isPlus,
|
isPlus = isPlus,
|
||||||
progress = { progress },
|
progress = { progress },
|
||||||
onAction = { action ->
|
onAction = timerViewModel::onAction,
|
||||||
when (action) {
|
|
||||||
TimerAction.ResetTimer ->
|
|
||||||
Intent(context, TimerService::class.java).also {
|
|
||||||
it.action = TimerService.Actions.RESET.toString()
|
|
||||||
context.startService(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
is TimerAction.SkipTimer ->
|
|
||||||
Intent(context, TimerService::class.java).also {
|
|
||||||
it.action = TimerService.Actions.SKIP.toString()
|
|
||||||
context.startService(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
TimerAction.StopAlarm ->
|
|
||||||
Intent(context, TimerService::class.java).also {
|
|
||||||
it.action =
|
|
||||||
TimerService.Actions.STOP_ALARM.toString()
|
|
||||||
context.startService(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
TimerAction.ToggleTimer ->
|
|
||||||
Intent(context, TimerService::class.java).also {
|
|
||||||
it.action = TimerService.Actions.TOGGLE.toString()
|
|
||||||
context.startService(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(
|
.padding(
|
||||||
start = contentPadding.calculateStartPadding(layoutDirection),
|
start = contentPadding.calculateStartPadding(layoutDirection),
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ package org.nsh07.pomodoro.ui.settingsScreen
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.LocaleManager
|
import android.app.LocaleManager
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
@@ -55,7 +53,6 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@@ -68,7 +65,6 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import androidx.navigation3.runtime.entryProvider
|
import androidx.navigation3.runtime.entryProvider
|
||||||
import androidx.navigation3.ui.NavDisplay
|
import androidx.navigation3.ui.NavDisplay
|
||||||
import org.nsh07.pomodoro.R
|
import org.nsh07.pomodoro.R
|
||||||
import org.nsh07.pomodoro.service.TimerService
|
|
||||||
import org.nsh07.pomodoro.ui.Screen
|
import org.nsh07.pomodoro.ui.Screen
|
||||||
import org.nsh07.pomodoro.ui.settingsScreen.components.AboutCard
|
import org.nsh07.pomodoro.ui.settingsScreen.components.AboutCard
|
||||||
import org.nsh07.pomodoro.ui.settingsScreen.components.ClickableListItem
|
import org.nsh07.pomodoro.ui.settingsScreen.components.ClickableListItem
|
||||||
@@ -77,7 +73,8 @@ import org.nsh07.pomodoro.ui.settingsScreen.components.PlusPromo
|
|||||||
import org.nsh07.pomodoro.ui.settingsScreen.screens.AlarmSettings
|
import org.nsh07.pomodoro.ui.settingsScreen.screens.AlarmSettings
|
||||||
import org.nsh07.pomodoro.ui.settingsScreen.screens.AppearanceSettings
|
import org.nsh07.pomodoro.ui.settingsScreen.screens.AppearanceSettings
|
||||||
import org.nsh07.pomodoro.ui.settingsScreen.screens.TimerSettings
|
import org.nsh07.pomodoro.ui.settingsScreen.screens.TimerSettings
|
||||||
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState
|
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsAction
|
||||||
|
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsState
|
||||||
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsViewModel
|
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsViewModel
|
||||||
import org.nsh07.pomodoro.ui.settingsScreens
|
import org.nsh07.pomodoro.ui.settingsScreens
|
||||||
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
||||||
@@ -92,8 +89,6 @@ fun SettingsScreenRoot(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.Factory)
|
viewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.Factory)
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val backStack = viewModel.backStack
|
val backStack = viewModel.backStack
|
||||||
|
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
@@ -106,12 +101,9 @@ fun SettingsScreenRoot(
|
|||||||
val longBreakTimeInputFieldState = viewModel.longBreakTimeTextFieldState
|
val longBreakTimeInputFieldState = viewModel.longBreakTimeTextFieldState
|
||||||
|
|
||||||
val isPlus by viewModel.isPlus.collectAsStateWithLifecycle()
|
val isPlus by viewModel.isPlus.collectAsStateWithLifecycle()
|
||||||
val alarmEnabled by viewModel.alarmEnabled.collectAsStateWithLifecycle(true)
|
val serviceRunning by viewModel.serviceRunning.collectAsStateWithLifecycle()
|
||||||
val vibrateEnabled by viewModel.vibrateEnabled.collectAsStateWithLifecycle(true)
|
|
||||||
val dndEnabled by viewModel.dndEnabled.collectAsStateWithLifecycle(false)
|
|
||||||
val alarmSound by viewModel.alarmSound.collectAsStateWithLifecycle(viewModel.currentAlarmSound)
|
|
||||||
|
|
||||||
val preferencesState by viewModel.preferencesState.collectAsStateWithLifecycle()
|
val settingsState by viewModel.settingsState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val sessionsSliderState = rememberSaveable(
|
val sessionsSliderState = rememberSaveable(
|
||||||
saver = SliderState.Saver(
|
saver = SliderState.Saver(
|
||||||
@@ -124,30 +116,14 @@ fun SettingsScreenRoot(
|
|||||||
|
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
isPlus = isPlus,
|
isPlus = isPlus,
|
||||||
preferencesState = preferencesState,
|
serviceRunning = serviceRunning,
|
||||||
|
settingsState = settingsState,
|
||||||
backStack = backStack,
|
backStack = backStack,
|
||||||
focusTimeInputFieldState = focusTimeInputFieldState,
|
focusTimeInputFieldState = focusTimeInputFieldState,
|
||||||
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
|
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
|
||||||
longBreakTimeInputFieldState = longBreakTimeInputFieldState,
|
longBreakTimeInputFieldState = longBreakTimeInputFieldState,
|
||||||
sessionsSliderState = sessionsSliderState,
|
sessionsSliderState = sessionsSliderState,
|
||||||
alarmEnabled = alarmEnabled,
|
onAction = viewModel::onAction,
|
||||||
vibrateEnabled = vibrateEnabled,
|
|
||||||
dndEnabled = dndEnabled,
|
|
||||||
alarmSound = alarmSound,
|
|
||||||
onAlarmEnabledChange = viewModel::saveAlarmEnabled,
|
|
||||||
onVibrateEnabledChange = viewModel::saveVibrateEnabled,
|
|
||||||
onBlackThemeChange = viewModel::saveBlackTheme,
|
|
||||||
onAodEnabledChange = viewModel::saveAodEnabled,
|
|
||||||
onDndEnabledChange = viewModel::saveDndEnabled,
|
|
||||||
onAlarmSoundChanged = {
|
|
||||||
viewModel.saveAlarmSound(it)
|
|
||||||
Intent(context, TimerService::class.java).apply {
|
|
||||||
action = TimerService.Actions.RESET.toString()
|
|
||||||
context.startService(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onThemeChange = viewModel::saveTheme,
|
|
||||||
onColorSchemeChange = viewModel::saveColorScheme,
|
|
||||||
setShowPaywall = setShowPaywall,
|
setShowPaywall = setShowPaywall,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
@@ -158,24 +134,14 @@ fun SettingsScreenRoot(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun SettingsScreen(
|
private fun SettingsScreen(
|
||||||
isPlus: Boolean,
|
isPlus: Boolean,
|
||||||
preferencesState: PreferencesState,
|
serviceRunning: Boolean,
|
||||||
|
settingsState: SettingsState,
|
||||||
backStack: SnapshotStateList<Screen.Settings>,
|
backStack: SnapshotStateList<Screen.Settings>,
|
||||||
focusTimeInputFieldState: TextFieldState,
|
focusTimeInputFieldState: TextFieldState,
|
||||||
shortBreakTimeInputFieldState: TextFieldState,
|
shortBreakTimeInputFieldState: TextFieldState,
|
||||||
longBreakTimeInputFieldState: TextFieldState,
|
longBreakTimeInputFieldState: TextFieldState,
|
||||||
sessionsSliderState: SliderState,
|
sessionsSliderState: SliderState,
|
||||||
alarmEnabled: Boolean,
|
onAction: (SettingsAction) -> Unit,
|
||||||
vibrateEnabled: Boolean,
|
|
||||||
dndEnabled: Boolean,
|
|
||||||
alarmSound: String,
|
|
||||||
onAlarmEnabledChange: (Boolean) -> Unit,
|
|
||||||
onVibrateEnabledChange: (Boolean) -> Unit,
|
|
||||||
onBlackThemeChange: (Boolean) -> Unit,
|
|
||||||
onAodEnabledChange: (Boolean) -> Unit,
|
|
||||||
onDndEnabledChange: (Boolean) -> Unit,
|
|
||||||
onAlarmSoundChanged: (Uri?) -> Unit,
|
|
||||||
onThemeChange: (String) -> Unit,
|
|
||||||
onColorSchemeChange: (Color) -> Unit,
|
|
||||||
setShowPaywall: (Boolean) -> Unit,
|
setShowPaywall: (Boolean) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
@@ -312,23 +278,16 @@ private fun SettingsScreen(
|
|||||||
|
|
||||||
entry<Screen.Settings.Alarm> {
|
entry<Screen.Settings.Alarm> {
|
||||||
AlarmSettings(
|
AlarmSettings(
|
||||||
preferencesState = preferencesState,
|
settingsState = settingsState,
|
||||||
alarmEnabled = alarmEnabled,
|
onAction = onAction,
|
||||||
vibrateEnabled = vibrateEnabled,
|
|
||||||
alarmSound = alarmSound,
|
|
||||||
onAlarmEnabledChange = onAlarmEnabledChange,
|
|
||||||
onVibrateEnabledChange = onVibrateEnabledChange,
|
|
||||||
onAlarmSoundChanged = onAlarmSoundChanged,
|
|
||||||
onBack = backStack::removeLastOrNull
|
onBack = backStack::removeLastOrNull
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
entry<Screen.Settings.Appearance> {
|
entry<Screen.Settings.Appearance> {
|
||||||
AppearanceSettings(
|
AppearanceSettings(
|
||||||
preferencesState = preferencesState,
|
settingsState = settingsState,
|
||||||
isPlus = isPlus,
|
isPlus = isPlus,
|
||||||
onBlackThemeChange = onBlackThemeChange,
|
onAction = onAction,
|
||||||
onThemeChange = onThemeChange,
|
|
||||||
onColorSchemeChange = onColorSchemeChange,
|
|
||||||
setShowPaywall = setShowPaywall,
|
setShowPaywall = setShowPaywall,
|
||||||
onBack = backStack::removeLastOrNull
|
onBack = backStack::removeLastOrNull
|
||||||
)
|
)
|
||||||
@@ -336,14 +295,13 @@ private fun SettingsScreen(
|
|||||||
entry<Screen.Settings.Timer> {
|
entry<Screen.Settings.Timer> {
|
||||||
TimerSettings(
|
TimerSettings(
|
||||||
isPlus = isPlus,
|
isPlus = isPlus,
|
||||||
aodEnabled = preferencesState.aodEnabled,
|
serviceRunning = serviceRunning,
|
||||||
dndEnabled = dndEnabled,
|
settingsState = settingsState,
|
||||||
focusTimeInputFieldState = focusTimeInputFieldState,
|
focusTimeInputFieldState = focusTimeInputFieldState,
|
||||||
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
|
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
|
||||||
longBreakTimeInputFieldState = longBreakTimeInputFieldState,
|
longBreakTimeInputFieldState = longBreakTimeInputFieldState,
|
||||||
sessionsSliderState = sessionsSliderState,
|
sessionsSliderState = sessionsSliderState,
|
||||||
onAodEnabledChange = onAodEnabledChange,
|
onAction = onAction,
|
||||||
onDndEnabledChange = onDndEnabledChange,
|
|
||||||
setShowPaywall = setShowPaywall,
|
setShowPaywall = setShowPaywall,
|
||||||
onBack = backStack::removeLastOrNull,
|
onBack = backStack::removeLastOrNull,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import androidx.annotation.StringRes
|
|||||||
|
|
||||||
data class SettingsSwitchItem(
|
data class SettingsSwitchItem(
|
||||||
val checked: Boolean,
|
val checked: Boolean,
|
||||||
|
val enabled: Boolean = true,
|
||||||
@param:DrawableRes val icon: Int,
|
@param:DrawableRes val icon: Int,
|
||||||
@param:StringRes val label: Int,
|
@param:StringRes val label: Int,
|
||||||
@param:StringRes val description: Int,
|
@param:StringRes val description: Int,
|
||||||
|
|||||||
@@ -24,10 +24,8 @@ import androidx.compose.foundation.layout.FlowRow
|
|||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
@@ -119,41 +117,8 @@ fun AboutCard(
|
|||||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
Button(
|
TopButton(buttonColors)
|
||||||
colors = buttonColors,
|
BottomButton(buttonColors)
|
||||||
onClick = { uriHandler.openUri("https://coff.ee/nsh07") }
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painterResource(R.drawable.bmc),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.height(24.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(text = stringResource(R.string.bmc))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(
|
|
||||||
colors = buttonColors,
|
|
||||||
onClick = { uriHandler.openUri("https://play.google.com/store/apps/details?id=org.nsh07.pomodoro") }
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painterResource(R.drawable.play_store),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(20.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(text = stringResource(R.string.rate_on_google_play))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,12 +46,14 @@ import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
|
|||||||
@Composable
|
@Composable
|
||||||
fun MinuteInputField(
|
fun MinuteInputField(
|
||||||
state: TextFieldState,
|
state: TextFieldState,
|
||||||
|
enabled: Boolean,
|
||||||
shape: Shape,
|
shape: Shape,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
imeAction: ImeAction = ImeAction.Next
|
imeAction: ImeAction = ImeAction.Next
|
||||||
) {
|
) {
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
state = state,
|
state = state,
|
||||||
|
enabled = enabled,
|
||||||
lineLimits = TextFieldLineLimits.SingleLine,
|
lineLimits = TextFieldLineLimits.SingleLine,
|
||||||
inputTransformation = MinutesInputTransformation,
|
inputTransformation = MinutesInputTransformation,
|
||||||
// outputTransformation = MinutesOutputTransformation,
|
// outputTransformation = MinutesOutputTransformation,
|
||||||
@@ -63,7 +65,7 @@ fun MinuteInputField(
|
|||||||
fontFamily = interClock,
|
fontFamily = interClock,
|
||||||
fontSize = 57.sp,
|
fontSize = 57.sp,
|
||||||
letterSpacing = (-2).sp,
|
letterSpacing = (-2).sp,
|
||||||
color = colorScheme.onSurfaceVariant,
|
color = if (enabled) colorScheme.onSurfaceVariant else colorScheme.outlineVariant,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
),
|
),
|
||||||
cursorBrush = SolidColor(colorScheme.onSurface),
|
cursorBrush = SolidColor(colorScheme.onSurface),
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package org.nsh07.pomodoro.ui.settingsScreen.screens
|
package org.nsh07.pomodoro.ui.settingsScreen.screens
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.media.RingtoneManager
|
import android.media.RingtoneManager
|
||||||
@@ -64,7 +65,8 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.nsh07.pomodoro.R
|
import org.nsh07.pomodoro.R
|
||||||
import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem
|
import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem
|
||||||
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState
|
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsAction
|
||||||
|
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsState
|
||||||
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
||||||
import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
|
import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
|
||||||
import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors
|
import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors
|
||||||
@@ -76,13 +78,8 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
|
|||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AlarmSettings(
|
fun AlarmSettings(
|
||||||
preferencesState: PreferencesState,
|
settingsState: SettingsState,
|
||||||
alarmEnabled: Boolean,
|
onAction: (SettingsAction) -> Unit,
|
||||||
vibrateEnabled: Boolean,
|
|
||||||
alarmSound: String,
|
|
||||||
onAlarmEnabledChange: (Boolean) -> Unit,
|
|
||||||
onVibrateEnabledChange: (Boolean) -> Unit,
|
|
||||||
onAlarmSoundChanged: (Uri?) -> Unit,
|
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
@@ -91,10 +88,11 @@ fun AlarmSettings(
|
|||||||
|
|
||||||
var alarmName by remember { mutableStateOf("...") }
|
var alarmName by remember { mutableStateOf("...") }
|
||||||
|
|
||||||
LaunchedEffect(alarmSound) {
|
LaunchedEffect(settingsState.alarmSound) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
alarmName =
|
alarmName =
|
||||||
RingtoneManager.getRingtone(context, alarmSound.toUri())?.getTitle(context) ?: ""
|
RingtoneManager.getRingtone(context, settingsState.alarmSound.toUri())
|
||||||
|
?.getTitle(context) ?: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,38 +110,39 @@ fun AlarmSettings(
|
|||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
|
result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
|
||||||
}
|
}
|
||||||
onAlarmSoundChanged(uri)
|
onAction(SettingsAction.SaveAlarmSound(uri))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val ringtonePickerIntent = remember(alarmSound) {
|
@SuppressLint("LocalContextGetResourceValueCall")
|
||||||
|
val ringtonePickerIntent = remember(settingsState.alarmSound) {
|
||||||
Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
|
Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
|
||||||
putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM)
|
putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM)
|
||||||
putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, context.getString(R.string.alarm_sound))
|
putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, context.getString(R.string.alarm_sound))
|
||||||
putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, alarmSound.toUri())
|
putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, settingsState.alarmSound.toUri())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val switchItems = remember(
|
val switchItems = remember(
|
||||||
preferencesState.blackTheme,
|
settingsState.blackTheme,
|
||||||
preferencesState.aodEnabled,
|
settingsState.aodEnabled,
|
||||||
alarmEnabled,
|
settingsState.alarmEnabled,
|
||||||
vibrateEnabled
|
settingsState.vibrateEnabled
|
||||||
) {
|
) {
|
||||||
listOf(
|
listOf(
|
||||||
SettingsSwitchItem(
|
SettingsSwitchItem(
|
||||||
checked = alarmEnabled,
|
checked = settingsState.alarmEnabled,
|
||||||
icon = R.drawable.alarm_on,
|
icon = R.drawable.alarm_on,
|
||||||
label = R.string.sound,
|
label = R.string.sound,
|
||||||
description = R.string.alarm_desc,
|
description = R.string.alarm_desc,
|
||||||
onClick = onAlarmEnabledChange
|
onClick = { onAction(SettingsAction.SaveAlarmEnabled(it)) }
|
||||||
),
|
),
|
||||||
SettingsSwitchItem(
|
SettingsSwitchItem(
|
||||||
checked = vibrateEnabled,
|
checked = settingsState.vibrateEnabled,
|
||||||
icon = R.drawable.mobile_vibrate,
|
icon = R.drawable.mobile_vibrate,
|
||||||
label = R.string.vibrate,
|
label = R.string.vibrate,
|
||||||
description = R.string.vibrate_desc,
|
description = R.string.vibrate_desc,
|
||||||
onClick = onVibrateEnabledChange
|
onClick = { onAction(SettingsAction.SaveVibrateEnabled(it)) }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -241,14 +240,10 @@ fun AlarmSettings(
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun AlarmSettingsPreview() {
|
fun AlarmSettingsPreview() {
|
||||||
val preferencesState = PreferencesState()
|
val settingsState = SettingsState()
|
||||||
AlarmSettings(
|
AlarmSettings(
|
||||||
preferencesState = preferencesState,
|
settingsState = settingsState,
|
||||||
alarmEnabled = true,
|
onAction = {},
|
||||||
vibrateEnabled = false,
|
onBack = {}
|
||||||
alarmSound = "",
|
)
|
||||||
onAlarmEnabledChange = {},
|
|
||||||
onVibrateEnabledChange = {},
|
|
||||||
onAlarmSoundChanged = {},
|
|
||||||
onBack = {})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import androidx.compose.material3.TopAppBarDefaults
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -50,7 +49,8 @@ import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem
|
|||||||
import org.nsh07.pomodoro.ui.settingsScreen.components.ColorSchemePickerListItem
|
import org.nsh07.pomodoro.ui.settingsScreen.components.ColorSchemePickerListItem
|
||||||
import org.nsh07.pomodoro.ui.settingsScreen.components.PlusDivider
|
import org.nsh07.pomodoro.ui.settingsScreen.components.PlusDivider
|
||||||
import org.nsh07.pomodoro.ui.settingsScreen.components.ThemePickerListItem
|
import org.nsh07.pomodoro.ui.settingsScreen.components.ThemePickerListItem
|
||||||
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState
|
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsAction
|
||||||
|
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsState
|
||||||
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
||||||
import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
|
import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
|
||||||
import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors
|
import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors
|
||||||
@@ -62,11 +62,9 @@ import org.nsh07.pomodoro.utils.toColor
|
|||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AppearanceSettings(
|
fun AppearanceSettings(
|
||||||
preferencesState: PreferencesState,
|
settingsState: SettingsState,
|
||||||
isPlus: Boolean,
|
isPlus: Boolean,
|
||||||
onBlackThemeChange: (Boolean) -> Unit,
|
onAction: (SettingsAction) -> Unit,
|
||||||
onThemeChange: (String) -> Unit,
|
|
||||||
onColorSchemeChange: (Color) -> Unit,
|
|
||||||
setShowPaywall: (Boolean) -> Unit,
|
setShowPaywall: (Boolean) -> Unit,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
@@ -105,8 +103,8 @@ fun AppearanceSettings(
|
|||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ThemePickerListItem(
|
ThemePickerListItem(
|
||||||
theme = preferencesState.theme,
|
theme = settingsState.theme,
|
||||||
onThemeChange = onThemeChange,
|
onThemeChange = { onAction(SettingsAction.SaveTheme(it)) },
|
||||||
items = if (isPlus) 3 else 1,
|
items = if (isPlus) 3 else 1,
|
||||||
index = 0
|
index = 0
|
||||||
)
|
)
|
||||||
@@ -118,20 +116,20 @@ fun AppearanceSettings(
|
|||||||
|
|
||||||
item {
|
item {
|
||||||
ColorSchemePickerListItem(
|
ColorSchemePickerListItem(
|
||||||
color = preferencesState.colorScheme.toColor(),
|
color = settingsState.colorScheme.toColor(),
|
||||||
items = 3,
|
items = 3,
|
||||||
index = if (isPlus) 1 else 0,
|
index = if (isPlus) 1 else 0,
|
||||||
isPlus = isPlus,
|
isPlus = isPlus,
|
||||||
onColorChange = onColorSchemeChange,
|
onColorChange = { onAction(SettingsAction.SaveColorScheme(it)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
val item = SettingsSwitchItem(
|
val item = SettingsSwitchItem(
|
||||||
checked = preferencesState.blackTheme,
|
checked = settingsState.blackTheme,
|
||||||
icon = R.drawable.contrast,
|
icon = R.drawable.contrast,
|
||||||
label = R.string.black_theme,
|
label = R.string.black_theme,
|
||||||
description = R.string.black_theme_desc,
|
description = R.string.black_theme_desc,
|
||||||
onClick = onBlackThemeChange
|
onClick = { onAction(SettingsAction.SaveBlackTheme(it)) }
|
||||||
)
|
)
|
||||||
ListItem(
|
ListItem(
|
||||||
leadingContent = {
|
leadingContent = {
|
||||||
@@ -175,14 +173,12 @@ fun AppearanceSettings(
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun AppearanceSettingsPreview() {
|
fun AppearanceSettingsPreview() {
|
||||||
val preferencesState = PreferencesState()
|
val settingsState = SettingsState()
|
||||||
TomatoTheme(dynamicColor = false) {
|
TomatoTheme(dynamicColor = false) {
|
||||||
AppearanceSettings(
|
AppearanceSettings(
|
||||||
preferencesState = preferencesState,
|
settingsState = settingsState,
|
||||||
isPlus = false,
|
isPlus = false,
|
||||||
onBlackThemeChange = {},
|
onAction = {},
|
||||||
onThemeChange = {},
|
|
||||||
onColorSchemeChange = {},
|
|
||||||
setShowPaywall = {},
|
setShowPaywall = {},
|
||||||
onBack = {}
|
onBack = {}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -36,10 +36,12 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.input.TextFieldState
|
import androidx.compose.foundation.text.input.TextFieldState
|
||||||
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
import androidx.compose.material3.FilledTonalIconToggleButton
|
import androidx.compose.material3.FilledTonalIconToggleButton
|
||||||
@@ -48,6 +50,7 @@ import androidx.compose.material3.IconButton
|
|||||||
import androidx.compose.material3.IconButtonDefaults
|
import androidx.compose.material3.IconButtonDefaults
|
||||||
import androidx.compose.material3.LargeFlexibleTopAppBar
|
import androidx.compose.material3.LargeFlexibleTopAppBar
|
||||||
import androidx.compose.material3.ListItem
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||||
import androidx.compose.material3.MaterialTheme.typography
|
import androidx.compose.material3.MaterialTheme.typography
|
||||||
import androidx.compose.material3.Slider
|
import androidx.compose.material3.Slider
|
||||||
@@ -56,8 +59,9 @@ import androidx.compose.material3.Switch
|
|||||||
import androidx.compose.material3.SwitchDefaults
|
import androidx.compose.material3.SwitchDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.rememberSliderState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -76,6 +80,8 @@ import org.nsh07.pomodoro.R
|
|||||||
import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem
|
import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem
|
||||||
import org.nsh07.pomodoro.ui.settingsScreen.components.MinuteInputField
|
import org.nsh07.pomodoro.ui.settingsScreen.components.MinuteInputField
|
||||||
import org.nsh07.pomodoro.ui.settingsScreen.components.PlusDivider
|
import org.nsh07.pomodoro.ui.settingsScreen.components.PlusDivider
|
||||||
|
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsAction
|
||||||
|
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsState
|
||||||
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
||||||
import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
|
import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
|
||||||
import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors
|
import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors
|
||||||
@@ -90,17 +96,16 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
|
|||||||
@Composable
|
@Composable
|
||||||
fun TimerSettings(
|
fun TimerSettings(
|
||||||
isPlus: Boolean,
|
isPlus: Boolean,
|
||||||
aodEnabled: Boolean,
|
serviceRunning: Boolean,
|
||||||
dndEnabled: Boolean,
|
settingsState: SettingsState,
|
||||||
focusTimeInputFieldState: TextFieldState,
|
focusTimeInputFieldState: TextFieldState,
|
||||||
shortBreakTimeInputFieldState: TextFieldState,
|
shortBreakTimeInputFieldState: TextFieldState,
|
||||||
longBreakTimeInputFieldState: TextFieldState,
|
longBreakTimeInputFieldState: TextFieldState,
|
||||||
sessionsSliderState: SliderState,
|
sessionsSliderState: SliderState,
|
||||||
onAodEnabledChange: (Boolean) -> Unit,
|
onAction: (SettingsAction) -> Unit,
|
||||||
onDndEnabledChange: (Boolean) -> Unit,
|
setShowPaywall: (Boolean) -> Unit,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier
|
||||||
setShowPaywall: (Boolean) -> Unit
|
|
||||||
) {
|
) {
|
||||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -108,14 +113,10 @@ fun TimerSettings(
|
|||||||
val notificationManagerService =
|
val notificationManagerService =
|
||||||
remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
|
remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
if (!notificationManagerService.isNotificationPolicyAccessGranted())
|
|
||||||
onDndEnabledChange(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
val switchItems = listOf(
|
val switchItems = listOf(
|
||||||
SettingsSwitchItem(
|
SettingsSwitchItem(
|
||||||
checked = dndEnabled,
|
checked = settingsState.dndEnabled,
|
||||||
|
enabled = !serviceRunning,
|
||||||
icon = R.drawable.dnd,
|
icon = R.drawable.dnd,
|
||||||
label = R.string.dnd,
|
label = R.string.dnd,
|
||||||
description = R.string.dnd_desc,
|
description = R.string.dnd_desc,
|
||||||
@@ -128,15 +129,15 @@ fun TimerSettings(
|
|||||||
} else if (!it && notificationManagerService.isNotificationPolicyAccessGranted()) {
|
} else if (!it && notificationManagerService.isNotificationPolicyAccessGranted()) {
|
||||||
notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL)
|
notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL)
|
||||||
}
|
}
|
||||||
onDndEnabledChange(it)
|
onAction(SettingsAction.SaveDndEnabled(it))
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
SettingsSwitchItem(
|
SettingsSwitchItem(
|
||||||
checked = aodEnabled,
|
checked = settingsState.aodEnabled,
|
||||||
icon = R.drawable.aod,
|
icon = R.drawable.aod,
|
||||||
label = R.string.always_on_display,
|
label = R.string.always_on_display,
|
||||||
description = R.string.always_on_display_desc,
|
description = R.string.always_on_display_desc,
|
||||||
onClick = onAodEnabledChange
|
onClick = { onAction(SettingsAction.SaveAodEnabled(it)) }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -168,6 +169,20 @@ fun TimerSettings(
|
|||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
|
CompositionLocalProvider(LocalContentColor provides colorScheme.error) {
|
||||||
|
AnimatedVisibility(serviceRunning) {
|
||||||
|
Column {
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Icon(painterResource(R.drawable.info), null)
|
||||||
|
Text(stringResource(R.string.timer_settings_reset_info))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Spacer(Modifier.height(14.dp))
|
Spacer(Modifier.height(14.dp))
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
@@ -187,6 +202,7 @@ fun TimerSettings(
|
|||||||
)
|
)
|
||||||
MinuteInputField(
|
MinuteInputField(
|
||||||
state = focusTimeInputFieldState,
|
state = focusTimeInputFieldState,
|
||||||
|
enabled = !serviceRunning,
|
||||||
shape = RoundedCornerShape(
|
shape = RoundedCornerShape(
|
||||||
topStart = topListItemShape.topStart,
|
topStart = topListItemShape.topStart,
|
||||||
bottomStart = topListItemShape.topStart,
|
bottomStart = topListItemShape.topStart,
|
||||||
@@ -207,6 +223,7 @@ fun TimerSettings(
|
|||||||
)
|
)
|
||||||
MinuteInputField(
|
MinuteInputField(
|
||||||
state = shortBreakTimeInputFieldState,
|
state = shortBreakTimeInputFieldState,
|
||||||
|
enabled = !serviceRunning,
|
||||||
shape = RoundedCornerShape(middleListItemShape.topStart),
|
shape = RoundedCornerShape(middleListItemShape.topStart),
|
||||||
imeAction = ImeAction.Next
|
imeAction = ImeAction.Next
|
||||||
)
|
)
|
||||||
@@ -222,6 +239,7 @@ fun TimerSettings(
|
|||||||
)
|
)
|
||||||
MinuteInputField(
|
MinuteInputField(
|
||||||
state = longBreakTimeInputFieldState,
|
state = longBreakTimeInputFieldState,
|
||||||
|
enabled = !serviceRunning,
|
||||||
shape = RoundedCornerShape(
|
shape = RoundedCornerShape(
|
||||||
topStart = bottomListItemShape.topStart,
|
topStart = bottomListItemShape.topStart,
|
||||||
bottomStart = bottomListItemShape.topStart,
|
bottomStart = bottomListItemShape.topStart,
|
||||||
@@ -254,6 +272,7 @@ fun TimerSettings(
|
|||||||
)
|
)
|
||||||
Slider(
|
Slider(
|
||||||
state = sessionsSliderState,
|
state = sessionsSliderState,
|
||||||
|
enabled = !serviceRunning,
|
||||||
modifier = Modifier.padding(vertical = 4.dp)
|
modifier = Modifier.padding(vertical = 4.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -278,6 +297,7 @@ fun TimerSettings(
|
|||||||
trailingContent = {
|
trailingContent = {
|
||||||
Switch(
|
Switch(
|
||||||
checked = item.checked,
|
checked = item.checked,
|
||||||
|
enabled = item.enabled,
|
||||||
onCheckedChange = { item.onClick(it) },
|
onCheckedChange = { item.onClick(it) },
|
||||||
thumbContent = {
|
thumbContent = {
|
||||||
if (item.checked) {
|
if (item.checked) {
|
||||||
@@ -313,7 +333,7 @@ fun TimerSettings(
|
|||||||
item {
|
item {
|
||||||
PlusDivider(setShowPaywall)
|
PlusDivider(setShowPaywall)
|
||||||
}
|
}
|
||||||
itemsIndexed(switchItems.drop(1)) { index, item ->
|
items(switchItems.drop(1)) { item ->
|
||||||
ListItem(
|
ListItem(
|
||||||
leadingContent = {
|
leadingContent = {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -392,24 +412,23 @@ fun TimerSettings(
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun TimerSettingsPreview() {
|
private fun TimerSettingsPreview() {
|
||||||
val focusTimeInputFieldState = TextFieldState("25")
|
val focusTimeInputFieldState = rememberTextFieldState("25")
|
||||||
val shortBreakTimeInputFieldState = TextFieldState("5")
|
val shortBreakTimeInputFieldState = rememberTextFieldState("5")
|
||||||
val longBreakTimeInputFieldState = TextFieldState("15")
|
val longBreakTimeInputFieldState = rememberTextFieldState("15")
|
||||||
val sessionsSliderState = SliderState(
|
val sessionsSliderState = rememberSliderState(
|
||||||
value = 4f,
|
value = 4f,
|
||||||
valueRange = 1f..8f,
|
valueRange = 1f..8f,
|
||||||
steps = 6
|
steps = 6
|
||||||
)
|
)
|
||||||
TimerSettings(
|
TimerSettings(
|
||||||
isPlus = false,
|
isPlus = false,
|
||||||
aodEnabled = true,
|
serviceRunning = true,
|
||||||
dndEnabled = false,
|
settingsState = remember { SettingsState() },
|
||||||
focusTimeInputFieldState = focusTimeInputFieldState,
|
focusTimeInputFieldState = focusTimeInputFieldState,
|
||||||
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
|
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
|
||||||
longBreakTimeInputFieldState = longBreakTimeInputFieldState,
|
longBreakTimeInputFieldState = longBreakTimeInputFieldState,
|
||||||
sessionsSliderState = sessionsSliderState,
|
sessionsSliderState = sessionsSliderState,
|
||||||
onAodEnabledChange = {},
|
onAction = {},
|
||||||
onDndEnabledChange = {},
|
|
||||||
setShowPaywall = {},
|
setShowPaywall = {},
|
||||||
onBack = {}
|
onBack = {}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.nsh07.pomodoro.ui.settingsScreen.viewModel
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
data class PreferencesState(
|
|
||||||
val theme: String = "auto",
|
|
||||||
val colorScheme: String = Color.White.toString(),
|
|
||||||
val blackTheme: Boolean = false,
|
|
||||||
val aodEnabled: Boolean = false
|
|
||||||
)
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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.viewModel
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
sealed interface SettingsAction {
|
||||||
|
data class SaveAlarmEnabled(val enabled: Boolean) : SettingsAction
|
||||||
|
data class SaveVibrateEnabled(val enabled: Boolean) : SettingsAction
|
||||||
|
data class SaveBlackTheme(val enabled: Boolean) : SettingsAction
|
||||||
|
data class SaveAodEnabled(val enabled: Boolean) : SettingsAction
|
||||||
|
data class SaveDndEnabled(val enabled: Boolean) : SettingsAction
|
||||||
|
data class SaveAlarmSound(val uri: Uri?) : SettingsAction
|
||||||
|
data class SaveTheme(val theme: String) : SettingsAction
|
||||||
|
data class SaveColorScheme(val color: Color) : SettingsAction
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* 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.viewModel
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class SettingsState(
|
||||||
|
val theme: String = "auto",
|
||||||
|
val alarmSound: String = "",
|
||||||
|
val colorScheme: String = Color.White.toString(),
|
||||||
|
val blackTheme: Boolean = false,
|
||||||
|
val aodEnabled: Boolean = false,
|
||||||
|
val alarmEnabled: Boolean = true,
|
||||||
|
val vibrateEnabled: Boolean = true,
|
||||||
|
val dndEnabled: Boolean = false
|
||||||
|
)
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
package org.nsh07.pomodoro.ui.settingsScreen.viewModel
|
package org.nsh07.pomodoro.ui.settingsScreen.viewModel
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.provider.Settings
|
||||||
import androidx.compose.foundation.text.input.TextFieldState
|
import androidx.compose.foundation.text.input.TextFieldState
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.SliderState
|
import androidx.compose.material3.SliderState
|
||||||
@@ -36,31 +37,35 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.nsh07.pomodoro.TomatoApplication
|
import org.nsh07.pomodoro.TomatoApplication
|
||||||
import org.nsh07.pomodoro.billing.BillingManager
|
import org.nsh07.pomodoro.billing.BillingManager
|
||||||
import org.nsh07.pomodoro.data.AppPreferenceRepository
|
import org.nsh07.pomodoro.data.AppPreferenceRepository
|
||||||
import org.nsh07.pomodoro.data.TimerRepository
|
import org.nsh07.pomodoro.data.TimerRepository
|
||||||
|
import org.nsh07.pomodoro.service.ServiceHelper
|
||||||
import org.nsh07.pomodoro.ui.Screen
|
import org.nsh07.pomodoro.ui.Screen
|
||||||
|
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerAction
|
||||||
|
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerMode
|
||||||
|
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
|
||||||
|
import org.nsh07.pomodoro.utils.millisecondsToStr
|
||||||
|
|
||||||
@OptIn(FlowPreview::class, ExperimentalMaterial3Api::class)
|
@OptIn(FlowPreview::class, ExperimentalMaterial3Api::class)
|
||||||
class SettingsViewModel(
|
class SettingsViewModel(
|
||||||
private val billingManager: BillingManager,
|
private val billingManager: BillingManager,
|
||||||
private val preferenceRepository: AppPreferenceRepository,
|
private val preferenceRepository: AppPreferenceRepository,
|
||||||
|
private val serviceHelper: ServiceHelper,
|
||||||
|
private val time: MutableStateFlow<Long>,
|
||||||
private val timerRepository: TimerRepository,
|
private val timerRepository: TimerRepository,
|
||||||
|
private val timerState: MutableStateFlow<TimerState>
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val backStack = mutableStateListOf<Screen.Settings>(Screen.Settings.Main)
|
val backStack = mutableStateListOf<Screen.Settings>(Screen.Settings.Main)
|
||||||
|
|
||||||
val isPlus = billingManager.isPlus
|
val isPlus = billingManager.isPlus
|
||||||
val isPurchaseStateLoaded = billingManager.isLoaded
|
val serviceRunning = timerRepository.serviceRunning.asStateFlow()
|
||||||
|
|
||||||
private val _isSettingsLoaded = MutableStateFlow(false)
|
private val _settingsState = MutableStateFlow(SettingsState())
|
||||||
val isSettingsLoaded = _isSettingsLoaded.asStateFlow()
|
val settingsState = _settingsState.asStateFlow()
|
||||||
|
|
||||||
private val _preferencesState = MutableStateFlow(PreferencesState())
|
|
||||||
val preferencesState = _preferencesState.asStateFlow()
|
|
||||||
|
|
||||||
val focusTimeTextFieldState by lazy {
|
val focusTimeTextFieldState by lazy {
|
||||||
TextFieldState((timerRepository.focusTime / 60000).toString())
|
TextFieldState((timerRepository.focusTime / 60000).toString())
|
||||||
@@ -81,25 +86,26 @@ class SettingsViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentAlarmSound = timerRepository.alarmSoundUri.toString()
|
|
||||||
|
|
||||||
private var focusFlowCollectionJob: Job? = null
|
private var focusFlowCollectionJob: Job? = null
|
||||||
private var shortBreakFlowCollectionJob: Job? = null
|
private var shortBreakFlowCollectionJob: Job? = null
|
||||||
private var longBreakFlowCollectionJob: Job? = null
|
private var longBreakFlowCollectionJob: Job? = null
|
||||||
|
|
||||||
val alarmSound =
|
|
||||||
preferenceRepository.getStringPreferenceFlow("alarm_sound").distinctUntilChanged()
|
|
||||||
val alarmEnabled =
|
|
||||||
preferenceRepository.getBooleanPreferenceFlow("alarm_enabled").distinctUntilChanged()
|
|
||||||
val vibrateEnabled =
|
|
||||||
preferenceRepository.getBooleanPreferenceFlow("vibrate_enabled").distinctUntilChanged()
|
|
||||||
val dndEnabled =
|
|
||||||
preferenceRepository.getBooleanPreferenceFlow("dnd_enabled").distinctUntilChanged()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
reloadSettings()
|
reloadSettings()
|
||||||
_isSettingsLoaded.value = true
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onAction(action: SettingsAction) {
|
||||||
|
when (action) {
|
||||||
|
is SettingsAction.SaveAlarmSound -> saveAlarmSound(action.uri)
|
||||||
|
is SettingsAction.SaveAlarmEnabled -> saveAlarmEnabled(action.enabled)
|
||||||
|
is SettingsAction.SaveVibrateEnabled -> saveVibrateEnabled(action.enabled)
|
||||||
|
is SettingsAction.SaveDndEnabled -> saveDndEnabled(action.enabled)
|
||||||
|
is SettingsAction.SaveColorScheme -> saveColorScheme(action.color)
|
||||||
|
is SettingsAction.SaveTheme -> saveTheme(action.theme)
|
||||||
|
is SettingsAction.SaveBlackTheme -> saveBlackTheme(action.enabled)
|
||||||
|
is SettingsAction.SaveAodEnabled -> saveAodEnabled(action.enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +115,7 @@ class SettingsViewModel(
|
|||||||
"session_length",
|
"session_length",
|
||||||
sessionsSliderState.value.toInt()
|
sessionsSliderState.value.toInt()
|
||||||
)
|
)
|
||||||
|
refreshTimer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +126,7 @@ class SettingsViewModel(
|
|||||||
.collect {
|
.collect {
|
||||||
if (it.isNotEmpty()) {
|
if (it.isNotEmpty()) {
|
||||||
timerRepository.focusTime = it.toString().toLong() * 60 * 1000
|
timerRepository.focusTime = it.toString().toLong() * 60 * 1000
|
||||||
|
refreshTimer()
|
||||||
preferenceRepository.saveIntPreference(
|
preferenceRepository.saveIntPreference(
|
||||||
"focus_time",
|
"focus_time",
|
||||||
timerRepository.focusTime.toInt()
|
timerRepository.focusTime.toInt()
|
||||||
@@ -132,6 +140,7 @@ class SettingsViewModel(
|
|||||||
.collect {
|
.collect {
|
||||||
if (it.isNotEmpty()) {
|
if (it.isNotEmpty()) {
|
||||||
timerRepository.shortBreakTime = it.toString().toLong() * 60 * 1000
|
timerRepository.shortBreakTime = it.toString().toLong() * 60 * 1000
|
||||||
|
refreshTimer()
|
||||||
preferenceRepository.saveIntPreference(
|
preferenceRepository.saveIntPreference(
|
||||||
"short_break_time",
|
"short_break_time",
|
||||||
timerRepository.shortBreakTime.toInt()
|
timerRepository.shortBreakTime.toInt()
|
||||||
@@ -145,6 +154,7 @@ class SettingsViewModel(
|
|||||||
.collect {
|
.collect {
|
||||||
if (it.isNotEmpty()) {
|
if (it.isNotEmpty()) {
|
||||||
timerRepository.longBreakTime = it.toString().toLong() * 60 * 1000
|
timerRepository.longBreakTime = it.toString().toLong() * 60 * 1000
|
||||||
|
refreshTimer()
|
||||||
preferenceRepository.saveIntPreference(
|
preferenceRepository.saveIntPreference(
|
||||||
"long_break_time",
|
"long_break_time",
|
||||||
timerRepository.longBreakTime.toInt()
|
timerRepository.longBreakTime.toInt()
|
||||||
@@ -155,85 +165,88 @@ class SettingsViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun cancelTextFieldFlowCollection() {
|
fun cancelTextFieldFlowCollection() {
|
||||||
|
if (!serviceRunning.value) serviceHelper.startService(TimerAction.ResetTimer)
|
||||||
focusFlowCollectionJob?.cancel()
|
focusFlowCollectionJob?.cancel()
|
||||||
shortBreakFlowCollectionJob?.cancel()
|
shortBreakFlowCollectionJob?.cancel()
|
||||||
longBreakFlowCollectionJob?.cancel()
|
longBreakFlowCollectionJob?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveAlarmEnabled(enabled: Boolean) {
|
private fun saveAlarmEnabled(enabled: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
timerRepository.alarmEnabled = enabled
|
timerRepository.alarmEnabled = enabled
|
||||||
|
_settingsState.update { currentState ->
|
||||||
|
currentState.copy(alarmEnabled = enabled)
|
||||||
|
}
|
||||||
preferenceRepository.saveBooleanPreference("alarm_enabled", enabled)
|
preferenceRepository.saveBooleanPreference("alarm_enabled", enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveVibrateEnabled(enabled: Boolean) {
|
private fun saveVibrateEnabled(enabled: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
timerRepository.vibrateEnabled = enabled
|
timerRepository.vibrateEnabled = enabled
|
||||||
|
_settingsState.update { currentState ->
|
||||||
|
currentState.copy(vibrateEnabled = enabled)
|
||||||
|
}
|
||||||
preferenceRepository.saveBooleanPreference("vibrate_enabled", enabled)
|
preferenceRepository.saveBooleanPreference("vibrate_enabled", enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveDndEnabled(enabled: Boolean) {
|
private fun saveDndEnabled(enabled: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
timerRepository.dndEnabled = enabled
|
timerRepository.dndEnabled = enabled
|
||||||
|
_settingsState.update { currentState ->
|
||||||
|
currentState.copy(dndEnabled = enabled)
|
||||||
|
}
|
||||||
preferenceRepository.saveBooleanPreference("dnd_enabled", enabled)
|
preferenceRepository.saveBooleanPreference("dnd_enabled", enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveAlarmSound(uri: Uri?) {
|
private fun saveAlarmSound(uri: Uri?) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
timerRepository.alarmSoundUri = uri
|
timerRepository.alarmSoundUri = uri
|
||||||
|
_settingsState.update { currentState ->
|
||||||
|
currentState.copy(alarmSound = uri.toString())
|
||||||
|
}
|
||||||
preferenceRepository.saveStringPreference("alarm_sound", uri.toString())
|
preferenceRepository.saveStringPreference("alarm_sound", uri.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveColorScheme(colorScheme: Color) {
|
private fun saveColorScheme(colorScheme: Color) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_preferencesState.update { currentState ->
|
_settingsState.update { currentState ->
|
||||||
currentState.copy(colorScheme = colorScheme.toString())
|
currentState.copy(colorScheme = colorScheme.toString())
|
||||||
}
|
}
|
||||||
preferenceRepository.saveStringPreference("color_scheme", colorScheme.toString())
|
preferenceRepository.saveStringPreference("color_scheme", colorScheme.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveTheme(theme: String) {
|
private fun saveTheme(theme: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_preferencesState.update { currentState ->
|
_settingsState.update { currentState ->
|
||||||
currentState.copy(theme = theme)
|
currentState.copy(theme = theme)
|
||||||
}
|
}
|
||||||
preferenceRepository.saveStringPreference("theme", theme)
|
preferenceRepository.saveStringPreference("theme", theme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveBlackTheme(blackTheme: Boolean) {
|
private fun saveBlackTheme(blackTheme: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_preferencesState.update { currentState ->
|
_settingsState.update { currentState ->
|
||||||
currentState.copy(blackTheme = blackTheme)
|
currentState.copy(blackTheme = blackTheme)
|
||||||
}
|
}
|
||||||
preferenceRepository.saveBooleanPreference("black_theme", blackTheme)
|
preferenceRepository.saveBooleanPreference("black_theme", blackTheme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveAodEnabled(aodEnabled: Boolean) {
|
private fun saveAodEnabled(aodEnabled: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_preferencesState.update { currentState ->
|
_settingsState.update { currentState ->
|
||||||
currentState.copy(aodEnabled = aodEnabled)
|
currentState.copy(aodEnabled = aodEnabled)
|
||||||
}
|
}
|
||||||
preferenceRepository.saveBooleanPreference("aod_enabled", aodEnabled)
|
preferenceRepository.saveBooleanPreference("aod_enabled", aodEnabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetPaywalledSettings() {
|
|
||||||
_preferencesState.update { currentState ->
|
|
||||||
currentState.copy(
|
|
||||||
aodEnabled = false,
|
|
||||||
blackTheme = false,
|
|
||||||
colorScheme = Color.White.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun reloadSettings() {
|
suspend fun reloadSettings() {
|
||||||
val theme = preferenceRepository.getStringPreference("theme")
|
val theme = preferenceRepository.getStringPreference("theme")
|
||||||
?: preferenceRepository.saveStringPreference("theme", "auto")
|
?: preferenceRepository.saveStringPreference("theme", "auto")
|
||||||
@@ -243,29 +256,69 @@ class SettingsViewModel(
|
|||||||
?: preferenceRepository.saveBooleanPreference("black_theme", false)
|
?: preferenceRepository.saveBooleanPreference("black_theme", false)
|
||||||
val aodEnabled = preferenceRepository.getBooleanPreference("aod_enabled")
|
val aodEnabled = preferenceRepository.getBooleanPreference("aod_enabled")
|
||||||
?: preferenceRepository.saveBooleanPreference("aod_enabled", false)
|
?: preferenceRepository.saveBooleanPreference("aod_enabled", false)
|
||||||
|
val alarmSound = preferenceRepository.getStringPreference("alarm_sound")
|
||||||
|
?: preferenceRepository.saveStringPreference(
|
||||||
|
"alarm_sound",
|
||||||
|
(Settings.System.DEFAULT_ALARM_ALERT_URI
|
||||||
|
?: Settings.System.DEFAULT_RINGTONE_URI).toString()
|
||||||
|
)
|
||||||
|
val alarmEnabled = preferenceRepository.getBooleanPreference("alarm_enabled")
|
||||||
|
?: preferenceRepository.saveBooleanPreference("alarm_enabled", true)
|
||||||
|
val vibrateEnabled = preferenceRepository.getBooleanPreference("vibrate_enabled")
|
||||||
|
?: preferenceRepository.saveBooleanPreference("vibrate_enabled", true)
|
||||||
|
val dndEnabled = preferenceRepository.getBooleanPreference("dnd_enabled")
|
||||||
|
?: preferenceRepository.saveBooleanPreference("dnd_enabled", false)
|
||||||
|
|
||||||
_preferencesState.update { currentState ->
|
_settingsState.update { currentState ->
|
||||||
currentState.copy(
|
currentState.copy(
|
||||||
theme = theme,
|
theme = theme,
|
||||||
colorScheme = colorScheme,
|
colorScheme = colorScheme,
|
||||||
|
alarmSound = alarmSound,
|
||||||
blackTheme = blackTheme,
|
blackTheme = blackTheme,
|
||||||
aodEnabled = aodEnabled
|
aodEnabled = aodEnabled,
|
||||||
|
alarmEnabled = alarmEnabled,
|
||||||
|
vibrateEnabled = vibrateEnabled,
|
||||||
|
dndEnabled = dndEnabled
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun refreshTimer() {
|
||||||
|
if (!serviceRunning.value) {
|
||||||
|
time.update { timerRepository.focusTime }
|
||||||
|
|
||||||
|
timerState.update { currentState ->
|
||||||
|
currentState.copy(
|
||||||
|
timerMode = TimerMode.FOCUS,
|
||||||
|
timeStr = millisecondsToStr(time.value),
|
||||||
|
totalTime = time.value,
|
||||||
|
nextTimerMode = if (timerRepository.sessionLength > 1) TimerMode.SHORT_BREAK else TimerMode.LONG_BREAK,
|
||||||
|
nextTimeStr = millisecondsToStr(if (timerRepository.sessionLength > 1) timerRepository.shortBreakTime else timerRepository.longBreakTime),
|
||||||
|
currentFocusCount = 1,
|
||||||
|
totalFocusCount = timerRepository.sessionLength
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||||
initializer {
|
initializer {
|
||||||
val application = (this[APPLICATION_KEY] as TomatoApplication)
|
val application = (this[APPLICATION_KEY] as TomatoApplication)
|
||||||
|
val appBillingManager = application.container.billingManager
|
||||||
val appPreferenceRepository = application.container.appPreferenceRepository
|
val appPreferenceRepository = application.container.appPreferenceRepository
|
||||||
val appTimerRepository = application.container.appTimerRepository
|
val appTimerRepository = application.container.appTimerRepository
|
||||||
val appBillingManager = application.container.billingManager
|
val serviceHelper = application.container.serviceHelper
|
||||||
|
val time = application.container.time
|
||||||
|
val timerState = application.container.timerState
|
||||||
|
|
||||||
SettingsViewModel(
|
SettingsViewModel(
|
||||||
billingManager = appBillingManager,
|
billingManager = appBillingManager,
|
||||||
preferenceRepository = appPreferenceRepository,
|
preferenceRepository = appPreferenceRepository,
|
||||||
|
serviceHelper = serviceHelper,
|
||||||
|
time = time,
|
||||||
timerRepository = appTimerRepository,
|
timerRepository = appTimerRepository,
|
||||||
|
timerState = timerState
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,9 @@
|
|||||||
|
|
||||||
package org.nsh07.pomodoro.ui.timerScreen.viewModel
|
package org.nsh07.pomodoro.ui.timerScreen.viewModel
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
|
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
@@ -30,8 +29,11 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.nsh07.pomodoro.TomatoApplication
|
import org.nsh07.pomodoro.TomatoApplication
|
||||||
@@ -39,22 +41,28 @@ import org.nsh07.pomodoro.data.PreferenceRepository
|
|||||||
import org.nsh07.pomodoro.data.Stat
|
import org.nsh07.pomodoro.data.Stat
|
||||||
import org.nsh07.pomodoro.data.StatRepository
|
import org.nsh07.pomodoro.data.StatRepository
|
||||||
import org.nsh07.pomodoro.data.TimerRepository
|
import org.nsh07.pomodoro.data.TimerRepository
|
||||||
|
import org.nsh07.pomodoro.service.ServiceHelper
|
||||||
import org.nsh07.pomodoro.utils.millisecondsToStr
|
import org.nsh07.pomodoro.utils.millisecondsToStr
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
class TimerViewModel(
|
class TimerViewModel(
|
||||||
application: Application,
|
|
||||||
private val preferenceRepository: PreferenceRepository,
|
private val preferenceRepository: PreferenceRepository,
|
||||||
|
private val serviceHelper: ServiceHelper,
|
||||||
private val statRepository: StatRepository,
|
private val statRepository: StatRepository,
|
||||||
private val timerRepository: TimerRepository,
|
private val timerRepository: TimerRepository,
|
||||||
private val _timerState: MutableStateFlow<TimerState>,
|
private val _timerState: MutableStateFlow<TimerState>,
|
||||||
private val _time: MutableStateFlow<Long>
|
private val _time: MutableStateFlow<Long>
|
||||||
) : AndroidViewModel(application) {
|
) : ViewModel() {
|
||||||
val timerState: StateFlow<TimerState> = _timerState.asStateFlow()
|
val timerState: StateFlow<TimerState> = _timerState.asStateFlow()
|
||||||
|
|
||||||
val time: StateFlow<Long> = _time.asStateFlow()
|
val time: StateFlow<Long> = _time.asStateFlow()
|
||||||
|
|
||||||
|
val progress = _time.combine(_timerState) { remainingTime, uiState ->
|
||||||
|
(uiState.totalTime.toFloat() - remainingTime) / uiState.totalTime
|
||||||
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 0f)
|
||||||
|
|
||||||
private var cycles = 0
|
private var cycles = 0
|
||||||
|
|
||||||
private var startTime = 0L
|
private var startTime = 0L
|
||||||
@@ -62,7 +70,7 @@ class TimerViewModel(
|
|||||||
private var pauseDuration = 0L
|
private var pauseDuration = 0L
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (!timerRepository.serviceRunning)
|
if (!timerRepository.serviceRunning.value)
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
timerRepository.focusTime =
|
timerRepository.focusTime =
|
||||||
preferenceRepository.getIntPreference("focus_time")?.toLong()
|
preferenceRepository.getIntPreference("focus_time")?.toLong()
|
||||||
@@ -108,9 +116,6 @@ class TimerViewModel(
|
|||||||
)
|
)
|
||||||
).toUri()
|
).toUri()
|
||||||
|
|
||||||
preferenceRepository.getBooleanPreference("aod_enabled")
|
|
||||||
?: preferenceRepository.saveBooleanPreference("aod_enabled", false)
|
|
||||||
|
|
||||||
_time.update { timerRepository.focusTime }
|
_time.update { timerRepository.focusTime }
|
||||||
cycles = 0
|
cycles = 0
|
||||||
startTime = 0L
|
startTime = 0L
|
||||||
@@ -150,6 +155,10 @@ class TimerViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onAction(action: TimerAction) {
|
||||||
|
serviceHelper.startService(action)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||||
initializer {
|
initializer {
|
||||||
@@ -157,12 +166,13 @@ class TimerViewModel(
|
|||||||
val appPreferenceRepository = application.container.appPreferenceRepository
|
val appPreferenceRepository = application.container.appPreferenceRepository
|
||||||
val appStatRepository = application.container.appStatRepository
|
val appStatRepository = application.container.appStatRepository
|
||||||
val appTimerRepository = application.container.appTimerRepository
|
val appTimerRepository = application.container.appTimerRepository
|
||||||
|
val serviceHelper = application.container.serviceHelper
|
||||||
val timerState = application.container.timerState
|
val timerState = application.container.timerState
|
||||||
val time = application.container.time
|
val time = application.container.time
|
||||||
|
|
||||||
TimerViewModel(
|
TimerViewModel(
|
||||||
application = application,
|
|
||||||
preferenceRepository = appPreferenceRepository,
|
preferenceRepository = appPreferenceRepository,
|
||||||
|
serviceHelper = serviceHelper,
|
||||||
statRepository = appStatRepository,
|
statRepository = appStatRepository,
|
||||||
timerRepository = appTimerRepository,
|
timerRepository = appTimerRepository,
|
||||||
_timerState = timerState,
|
_timerState = timerState,
|
||||||
|
|||||||
26
app/src/main/res/drawable/weblate.xml
Normal file
26
app/src/main/res/drawable/weblate.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="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M9.662,3.809c-1.875,1.19 -2.81,3.515 -2.83,5.795 -0.014,2.628 0.666,5.258 1.988,7.305 0.936,1.46 2.238,2.715 3.836,3.412a6.942,6.942 0,0 0,5.647 -0.07c1.997,-0.927 3.523,-2.73 4.463,-4.785 1.606,-3.518 1.643,-7.724 0.12,-11.295 -1.146,0.458 -2.166,-0.271 -2.166,-0.271s0.003,1.122 -1.083,1.685c1.115,2.612 1.088,5.717 -0.03,8.263 -0.538,1.225 -1.358,2.365 -2.498,3.01 -0.917,0.52 -2.04,0.625 -3.052,0.184 -1.342,-0.585 -2.293,-1.864 -2.89,-3.254 -0.466,-1.067 -0.782,-2.447 -0.802,-3.878 -0.037,-1.724 0.728,-3.193 1.635,-3.218 0.622,-0.024 1.427,0.918 1.598,2.435 0.158,1.543 -0.177,3.72 -1.174,5.49 0.677,1.085 1.77,1.98 2.951,1.974 1.386,-2.338 1.827,-4.911 1.793,-6.987 -0.02,-2.28 -0.955,-4.603 -2.83,-5.795 -1.437,-0.907 -3.173,-0.948 -4.676,0zM3.278,3.9s-1.018,0.73 -2.163,0.27c-1.524,3.573 -1.488,7.778 0.12,11.296 0.94,2.056 2.465,3.858 4.462,4.785a6.95,6.95 0,0 0,5.523 0.124,9.12 9.12,0 0,1 -1.75,-1.455 11.18,11.18 0,0 1,-1.267 -1.628c-0.768,-0.08 -1.498,-0.482 -2.003,-0.913 -1.447,-1.213 -2.453,-3.478 -2.632,-5.9 -0.12,-1.635 0.14,-3.354 0.795,-4.894C3.276,5.022 3.278,3.9 3.278,3.9z" />
|
||||||
|
</vector>
|
||||||
@@ -62,4 +62,16 @@
|
|||||||
<string name="weekly_productivity_analysis">साप्ताहिक उत्पादकता विश्लेषण</string>
|
<string name="weekly_productivity_analysis">साप्ताहिक उत्पादकता विश्लेषण</string>
|
||||||
<string name="appearance">दिखावट</string>
|
<string name="appearance">दिखावट</string>
|
||||||
<string name="durations">अवधियां</string>
|
<string name="durations">अवधियां</string>
|
||||||
|
<string name="dnd">परेशान न करें</string>
|
||||||
|
<string name="dnd_desc">फ़ोकस टाइमर चलाते समय \'परेशान न करें\' मोड चालू करें</string>
|
||||||
|
<string name="get_plus">Tomato+ प्राप्त करें</string>
|
||||||
|
<string name="dynamic_color">डायनामिक रंग</string>
|
||||||
|
<string name="dynamic_color_desc">अपने वॉलपेपर से थीम रंग अनुकूलित करें</string>
|
||||||
|
<string name="tomato_foss_desc">इस संस्करण में सभी सुविधाएँ अनलॉक हैं। अगर मेरे ऐप ने आपके जीवन में कोई बदलाव लाया है, तो कृपया %1$s पर दान करके मेरी मदद करें।</string>
|
||||||
|
<string name="language">भाषा</string>
|
||||||
|
<string name="choose_language">भाषा चुनें</string>
|
||||||
|
<string name="rate_on_google_play">Google Play पर रेटिंग दें</string>
|
||||||
|
<string name="selected">चयनित</string>
|
||||||
|
<string name="help_with_translation">अनुवाद में सहायता करें</string>
|
||||||
|
<string name="timer_settings_reset_info">सेटिंग्स बदलने के लिए टाइमर रीसेट करें</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
76
app/src/main/res/values-in/strings.xml
Normal file
76
app/src/main/res/values-in/strings.xml
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="dynamic">Dinamis</string>
|
||||||
|
<string name="alarm">Alarm</string>
|
||||||
|
<string name="alarm_desc">Bunyikan alarm ketika timer selesai</string>
|
||||||
|
<string name="alarm_sound">Suara alarm</string>
|
||||||
|
<string name="black_theme">Tema hitam</string>
|
||||||
|
<string name="black_theme_desc">Gunakan tema hitam</string>
|
||||||
|
<string name="break_">Rehat</string>
|
||||||
|
<string name="choose_color_scheme">Pilih skema warna</string>
|
||||||
|
<string name="choose_theme">Pilih tema</string>
|
||||||
|
<string name="color">Warna</string>
|
||||||
|
<string name="color_scheme">Skema warna</string>
|
||||||
|
<string name="completed">Selesai</string>
|
||||||
|
<string name="dark">Gelap</string>
|
||||||
|
<string name="exit">Keluar</string>
|
||||||
|
<string name="focus">Fokus</string>
|
||||||
|
<string name="focus_per_day_avg">fokus per hari (rerata)</string>
|
||||||
|
<string name="last_month">Bulan lalu</string>
|
||||||
|
<string name="last_week">Minggu lalu</string>
|
||||||
|
<string name="last_year">Tahun lalu</string>
|
||||||
|
<string name="light">Terang</string>
|
||||||
|
<string name="long_break">Rehat panjang</string>
|
||||||
|
<string name="min_remaining_notification">%1$s menit tersisa</string>
|
||||||
|
<string name="monthly_productivity_analysis">Analisis produktivitas bulanan</string>
|
||||||
|
<string name="more">Lebih banyak</string>
|
||||||
|
<string name="more_info">Info lebih</string>
|
||||||
|
<string name="ok">Oke</string>
|
||||||
|
<string name="pause">Jeda</string>
|
||||||
|
<string name="paused">Dijeda</string>
|
||||||
|
<string name="play">Mulai</string>
|
||||||
|
<string name="pomodoro_info">\"Sesi\" adalah rangkaian interval pomodoro yang berisi interval fokus, interval rehat pendek, dan interval rehat panjang. Rehat terakhir dalam suatu sesi selalu merupakan rehat panjang.</string>
|
||||||
|
<string name="productivity_analysis">Analisis produktivitas</string>
|
||||||
|
<string name="productivity_analysis_desc">Durasi fokus berdasarkan segmen waktu dalam sehari</string>
|
||||||
|
<string name="restart">Mulai ulang</string>
|
||||||
|
<string name="session_length">Lama sesi</string>
|
||||||
|
<string name="session_length_desc">Interval fokus dalam satu sesi: %1$d</string>
|
||||||
|
<string name="settings">Pengaturan</string>
|
||||||
|
<string name="short_break">Rehat pendek</string>
|
||||||
|
<string name="skip">Lewati</string>
|
||||||
|
<string name="skip_to_next">Lewati ke berikutnya</string>
|
||||||
|
<string name="start">Mulai</string>
|
||||||
|
<string name="start_next">Mulai selanjutnya</string>
|
||||||
|
<string name="stats">Statistik</string>
|
||||||
|
<string name="stop">Hentikan</string>
|
||||||
|
<string name="stop_alarm">Hentikan alarm</string>
|
||||||
|
<string name="stop_alarm_dialog_text">Sesi ini sudah selesai. Ketuk di mana saja untuk hentikan alarm.</string>
|
||||||
|
<string name="stop_alarm_question">Hentikan Alarm?</string>
|
||||||
|
<string name="system_default">Sistem</string>
|
||||||
|
<string name="theme">Tema</string>
|
||||||
|
<string name="timer">Timer</string>
|
||||||
|
<string name="timer_progress">Progres timer</string>
|
||||||
|
<string name="timer_session_count">%1$d dari %2$d</string>
|
||||||
|
<string name="today">Hari ini</string>
|
||||||
|
<string name="up_next">Berikutnya</string>
|
||||||
|
<string name="up_next_notification">Berikutnya: %1$s (%2$s)</string>
|
||||||
|
<string name="vibrate">Getar</string>
|
||||||
|
<string name="vibrate_desc">Getar ketika timer selesai</string>
|
||||||
|
<string name="weekly_productivity_analysis">Analisis produktivitas mingguan</string>
|
||||||
|
<string name="appearance">Tampilan</string>
|
||||||
|
<string name="durations">Durasi</string>
|
||||||
|
<string name="sound">Suara</string>
|
||||||
|
<string name="dnd">Jangan Ganggu</string>
|
||||||
|
<string name="dnd_desc">Aktifkan mode Jangan Ganggu ketika timer Fokus berjalan</string>
|
||||||
|
<string name="get_plus">Dapatkan Tomato+</string>
|
||||||
|
<string name="dynamic_color">Warna dinamis</string>
|
||||||
|
<string name="dynamic_color_desc">Sesuaikan warna tema dari wallpaper perangkat</string>
|
||||||
|
<string name="tomato_foss">Tomato FOSS</string>
|
||||||
|
<string name="tomato_foss_desc">Semua fitur tidak terkunci di versi sumber terbuka ini. Jika aplikasi ini bermanfaat , mohon pertimbangkan untuk dukung saya dengan berdonasi di %1$s.</string>
|
||||||
|
<string name="language">Bahasa</string>
|
||||||
|
<string name="choose_language">Pilih bahasa</string>
|
||||||
|
<string name="rate_on_google_play">Beri nilai di Google Play</string>
|
||||||
|
<string name="selected">Dipilih</string>
|
||||||
|
<string name="always_on_display_desc">Ketuk di mana saja saat melihat pengatur waktu untuk beralih ke mode AOD</string>
|
||||||
|
<string name="always_on_display">Always On Display (AOD)</string>
|
||||||
|
</resources>
|
||||||
@@ -65,4 +65,12 @@
|
|||||||
<string name="dynamic_color_desc">Adatta i colori del tema dal tuo sfondo</string>
|
<string name="dynamic_color_desc">Adatta i colori del tema dal tuo sfondo</string>
|
||||||
<string name="language">Lingua</string>
|
<string name="language">Lingua</string>
|
||||||
<string name="choose_language">Scegli la lingua</string>
|
<string name="choose_language">Scegli la lingua</string>
|
||||||
|
<string name="durations">Durate</string>
|
||||||
|
<string name="dnd_desc">Attiva la modalità non disturbare quando avvii un timer di concentrazione.</string>
|
||||||
|
<string name="tomato_foss">Tomato FOSS</string>
|
||||||
|
<string name="rate_on_google_play">Valuta su Google Play</string>
|
||||||
|
<string name="selected">Selezionato</string>
|
||||||
|
<string name="dynamic_color">Colore dinamico</string>
|
||||||
|
<string name="tomato_foss_desc">Tutte le funzionalità sono sbloccate in questa versione. Se la mia app ha fatto la differenza nella tua vita, considera di supportarmi con una donazione su %1$s.</string>
|
||||||
|
<string name="always_on_display">Schermo sempre attivo</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,3 +1,65 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
</resources>
|
<string name="alarm">Alarm</string>
|
||||||
|
<string name="alarm_desc">Laat alarm afgaan wanneer de timer afloopt</string>
|
||||||
|
<string name="alarm_sound">Alarm geluid</string>
|
||||||
|
<string name="always_on_display">Always On Display</string>
|
||||||
|
<string name="always_on_display_desc">Tik ergens terwijl u de timer bekijkt om over te schakelen naar de AOD-modus</string>
|
||||||
|
<string name="black_theme">Donker thema</string>
|
||||||
|
<string name="black_theme_desc">Gebruik een puur-zwart donker thema</string>
|
||||||
|
<string name="break_">Pauze</string>
|
||||||
|
<string name="choose_color_scheme">Kies een kleurthema</string>
|
||||||
|
<string name="choose_theme">Kies een thema</string>
|
||||||
|
<string name="color">Kleur</string>
|
||||||
|
<string name="color_scheme">Kleurthema</string>
|
||||||
|
<string name="completed">Voltooid</string>
|
||||||
|
<string name="dark">Donker</string>
|
||||||
|
<string name="dynamic">Dynamisch</string>
|
||||||
|
<string name="exit">Verlaat</string>
|
||||||
|
<string name="focus">Focus</string>
|
||||||
|
<string name="focus_per_day_avg">focus per dag (gem.)</string>
|
||||||
|
<string name="last_month">Voorbije maand</string>
|
||||||
|
<string name="last_week">Voorbije week</string>
|
||||||
|
<string name="last_year">Voorbije jaar</string>
|
||||||
|
<string name="light">Licht</string>
|
||||||
|
<string name="long_break">Lange pauze</string>
|
||||||
|
<string name="min_remaining_notification">%1$s min resterend</string>
|
||||||
|
<string name="monthly_productivity_analysis">Maandelijkse productiviteitsanalyse</string>
|
||||||
|
<string name="more">Meer</string>
|
||||||
|
<string name="more_info">Meer info</string>
|
||||||
|
<string name="ok">OK</string>
|
||||||
|
<string name="pause">Pauzeer</string>
|
||||||
|
<string name="paused">Gepauzeerd</string>
|
||||||
|
<string name="play">Start</string>
|
||||||
|
<string name="pomodoro_info">Een \"sessie\" is een reeks pomodoro-intervallen die bestaan uit focusintervallen, korte pauzes en een lange pauze. De laatste pauze van een sessie is altijd een lange pauze.</string>
|
||||||
|
<string name="productivity_analysis">Productiviteitsanalyse</string>
|
||||||
|
<string name="productivity_analysis_desc">Focustijden op verschillende tijdstippen van de dag</string>
|
||||||
|
<string name="restart">Herstart</string>
|
||||||
|
<string name="session_length">Sessielengte</string>
|
||||||
|
<string name="session_length_desc">Focusintervallen in één sessie: %1$d</string>
|
||||||
|
<string name="settings">Instellingen</string>
|
||||||
|
<string name="short_break">Korte pauze</string>
|
||||||
|
<string name="skip">Skip</string>
|
||||||
|
<string name="skip_to_next">Skip naar de volgende</string>
|
||||||
|
<string name="start">Start</string>
|
||||||
|
<string name="start_next">Start de volgende</string>
|
||||||
|
<string name="stats">Statistieken</string>
|
||||||
|
<string name="stop">Stop</string>
|
||||||
|
<string name="stop_alarm">Stop alarm</string>
|
||||||
|
<string name="stop_alarm_dialog_text">De huidige sessie is voltooid. Tik ergens om het alarm te stoppen.</string>
|
||||||
|
<string name="stop_alarm_question">Alarm stoppen?</string>
|
||||||
|
<string name="system_default">Systeem</string>
|
||||||
|
<string name="theme">Thema</string>
|
||||||
|
<string name="timer">Timer</string>
|
||||||
|
<string name="timer_progress">Timervoortgang</string>
|
||||||
|
<string name="timer_session_count">%1$d van %2$d</string>
|
||||||
|
<string name="today">Vandaag</string>
|
||||||
|
<string name="up_next">Volgende</string>
|
||||||
|
<string name="up_next_notification">Volgende: %1$s (%2$s)</string>
|
||||||
|
<string name="vibrate">Trilling</string>
|
||||||
|
<string name="vibrate_desc">Tril wanneer een timer afgaat</string>
|
||||||
|
<string name="weekly_productivity_analysis">Wekelijkse productiviteitsanalyse</string>
|
||||||
|
<string name="appearance">Uiterlijk</string>
|
||||||
|
<string name="durations">Duur</string>
|
||||||
|
<string name="sound">Geluid</string>
|
||||||
|
</resources>
|
||||||
|
|||||||
@@ -75,4 +75,6 @@
|
|||||||
<string name="choose_language">選擇語言</string>
|
<string name="choose_language">選擇語言</string>
|
||||||
<string name="rate_on_google_play">在 Google Play 上評分</string>
|
<string name="rate_on_google_play">在 Google Play 上評分</string>
|
||||||
<string name="selected">已選擇</string>
|
<string name="selected">已選擇</string>
|
||||||
|
<string name="help_with_translation">協助翻譯</string>
|
||||||
|
<string name="timer_settings_reset_info">重置計時器以變更設定</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -92,4 +92,6 @@
|
|||||||
<string name="rate_on_google_play">Rate on Google Play</string>
|
<string name="rate_on_google_play">Rate on Google Play</string>
|
||||||
<string name="bmc">BuyMeACoffee</string>
|
<string name="bmc">BuyMeACoffee</string>
|
||||||
<string name="selected">Selected</string>
|
<string name="selected">Selected</string>
|
||||||
|
<string name="help_with_translation">Help with translation</string>
|
||||||
|
<string name="timer_settings_reset_info">Reset the timer to change settings</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -33,9 +33,6 @@ class PlayBillingManager : BillingManager {
|
|||||||
private val _isPlus = MutableStateFlow(false)
|
private val _isPlus = MutableStateFlow(false)
|
||||||
override val isPlus = _isPlus.asStateFlow()
|
override val isPlus = _isPlus.asStateFlow()
|
||||||
|
|
||||||
private val _isLoaded = MutableStateFlow(false)
|
|
||||||
override val isLoaded = _isLoaded.asStateFlow()
|
|
||||||
|
|
||||||
private val purchases by lazy { Purchases.sharedInstance }
|
private val purchases by lazy { Purchases.sharedInstance }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -48,11 +45,9 @@ class PlayBillingManager : BillingManager {
|
|||||||
purchases.getCustomerInfoWith(
|
purchases.getCustomerInfoWith(
|
||||||
onSuccess = { customerInfo ->
|
onSuccess = { customerInfo ->
|
||||||
_isPlus.value = customerInfo.entitlements[ENTITLEMENT_ID]?.isActive == true
|
_isPlus.value = customerInfo.entitlements[ENTITLEMENT_ID]?.isActive == true
|
||||||
_isLoaded.value = true
|
|
||||||
},
|
},
|
||||||
onError = { error ->
|
onError = { error ->
|
||||||
Log.e("GooglePlayPaywallManager", "Error fetching customer info: $error")
|
Log.e("GooglePlayPaywallManager", "Error fetching customer info: $error")
|
||||||
_isLoaded.value = true
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonColors
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.nsh07.pomodoro.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TopButton(
|
||||||
|
buttonColors: ButtonColors,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
Button(
|
||||||
|
colors = buttonColors,
|
||||||
|
onClick = { uriHandler.openUri("https://hosted.weblate.org/engage/tomato/") },
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.weblate),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = stringResource(R.string.help_with_translation))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BottomButton(
|
||||||
|
buttonColors: ButtonColors,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
Button(
|
||||||
|
colors = buttonColors,
|
||||||
|
onClick = { uriHandler.openUri("https://play.google.com/store/apps/details?id=org.nsh07.pomodoro") },
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.play_store),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = stringResource(R.string.rate_on_google_play))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
fastlane/metadata/android/en-US/changelogs/20.txt
Normal file
10
fastlane/metadata/android/en-US/changelogs/20.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
New features:
|
||||||
|
- Timer durations now refresh automatically when changed in Settings
|
||||||
|
- As a consequence of the above feature and to improve stability, it is no longer possible to change the timer durations while the timer is running
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- Significantly improved timer screen performance (by ~90%)
|
||||||
|
- Progress circle in timer screen is now much smoother
|
||||||
|
- Notification is now visible on lockscreen by default
|
||||||
|
|
||||||
|
- Updated translations
|
||||||
12
fastlane/metadata/android/id/full_description.txt
Normal file
12
fastlane/metadata/android/id/full_description.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<i> Tomat</i> adalah timer Pomodoro minimalis untuk Android berdasarkan Material 3 Expressive.
|
||||||
|
|
||||||
|
Tomat sepenuhnya gratis dan bersumber terbuka selamanya. Anda dapat menemukan kode sumber, melaporkan bug, atau menyarankan fitur di https://github.com/nsh07/Tomato
|
||||||
|
|
||||||
|
<b>Fitur: </b>
|
||||||
|
- UI minimalis sederhana berdasarka Material 3 Expressive terbaru
|
||||||
|
- Statistik terperinci dari waktu kerja/belajar yang mudah dipahami
|
||||||
|
- Statistik untuk harian terlihat sekilas
|
||||||
|
- Statistik untuk minggu lalu dan bulan lalu ditampilkan dalam grafik yang bersih dan mudah dibaca
|
||||||
|
- Statistik tambahan untuk minggu lalu dan bulan yang menampilkan hari apa yang paling produktif
|
||||||
|
- Parameter timer yang dapat disesuaikan
|
||||||
|
- Dukungan untuk Live Updates Android 16
|
||||||
1
fastlane/metadata/android/id/short_description.txt
Normal file
1
fastlane/metadata/android/id/short_description.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Timer Podomoro minimalis
|
||||||
12
fastlane/metadata/android/nl-NL/full_description.txt
Normal file
12
fastlane/metadata/android/nl-NL/full_description.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<i>Tomato</i> is een minimalistische Pomodoro timer voor Android gebaseerd op Material 3 Expressive.
|
||||||
|
|
||||||
|
Tomato is volledig gratis and open-source, voor altijd. Je kan de broncode vinden of fouten rapporteren op https://github.com/nsh07/Tomato
|
||||||
|
|
||||||
|
<b>Mogelijkheden van deze app:</b>
|
||||||
|
- Simpele, minimalistische UI gebaseerd op de laatste Material 3 expressive richtlijnen
|
||||||
|
- Gedetailleerde statistieken van werk/studietijd in een gemakkelijk begrijpbare manier weergegeven
|
||||||
|
- Statistieken van de huidige dag in één oogopslag zichtbaar
|
||||||
|
- Statistieken van de voorbije week en maand weergegeven in een makkelijk leesbare grafiek
|
||||||
|
- Extra statistieken van de voorbije week en maand die tonen op welk moment van de dag je het meest productief bent
|
||||||
|
- Aanpasbare timer parameters
|
||||||
|
- Ondersteuning voor Android 16 Live-updates
|
||||||
1
fastlane/metadata/android/nl-NL/short_description.txt
Normal file
1
fastlane/metadata/android/nl-NL/short_description.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Minimalistische Pomodoro timer
|
||||||
@@ -12,9 +12,9 @@ ksp = "2.3.1"
|
|||||||
lifecycleRuntimeKtx = "2.9.4"
|
lifecycleRuntimeKtx = "2.9.4"
|
||||||
materialKolor = "4.0.3"
|
materialKolor = "4.0.3"
|
||||||
navigation3 = "1.0.0-rc01"
|
navigation3 = "1.0.0-rc01"
|
||||||
revenuecat = "9.12.1"
|
revenuecat = "9.13.0"
|
||||||
room = "2.8.3"
|
room = "2.8.3"
|
||||||
vico = "2.2.1"
|
vico = "2.3.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||||
|
|||||||
Reference in New Issue
Block a user