@@ -29,6 +29,7 @@ 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
|
||||||
@@ -41,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
|
||||||
@@ -87,6 +89,10 @@ class DefaultAppContainer(context: Context) : AppContainer {
|
|||||||
.setVisibility(VISIBILITY_PUBLIC)
|
.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 {
|
||||||
MutableStateFlow(
|
MutableStateFlow(
|
||||||
TimerState(
|
TimerState(
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,5 +62,5 @@ class AppTimerRepository : TimerRepository {
|
|||||||
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()
|
||||||
|
|||||||
@@ -63,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)
|
||||||
@@ -163,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),
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ fun SettingsScreenRoot(
|
|||||||
val longBreakTimeInputFieldState = viewModel.longBreakTimeTextFieldState
|
val longBreakTimeInputFieldState = viewModel.longBreakTimeTextFieldState
|
||||||
|
|
||||||
val isPlus by viewModel.isPlus.collectAsStateWithLifecycle()
|
val isPlus by viewModel.isPlus.collectAsStateWithLifecycle()
|
||||||
|
val serviceRunning by viewModel.serviceRunning.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val settingsState by viewModel.settingsState.collectAsStateWithLifecycle()
|
val settingsState by viewModel.settingsState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
@@ -115,6 +116,7 @@ fun SettingsScreenRoot(
|
|||||||
|
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
isPlus = isPlus,
|
isPlus = isPlus,
|
||||||
|
serviceRunning = serviceRunning,
|
||||||
settingsState = settingsState,
|
settingsState = settingsState,
|
||||||
backStack = backStack,
|
backStack = backStack,
|
||||||
focusTimeInputFieldState = focusTimeInputFieldState,
|
focusTimeInputFieldState = focusTimeInputFieldState,
|
||||||
@@ -132,6 +134,7 @@ fun SettingsScreenRoot(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun SettingsScreen(
|
private fun SettingsScreen(
|
||||||
isPlus: Boolean,
|
isPlus: Boolean,
|
||||||
|
serviceRunning: Boolean,
|
||||||
settingsState: SettingsState,
|
settingsState: SettingsState,
|
||||||
backStack: SnapshotStateList<Screen.Settings>,
|
backStack: SnapshotStateList<Screen.Settings>,
|
||||||
focusTimeInputFieldState: TextFieldState,
|
focusTimeInputFieldState: TextFieldState,
|
||||||
@@ -292,6 +295,7 @@ private fun SettingsScreen(
|
|||||||
entry<Screen.Settings.Timer> {
|
entry<Screen.Settings.Timer> {
|
||||||
TimerSettings(
|
TimerSettings(
|
||||||
isPlus = isPlus,
|
isPlus = isPlus,
|
||||||
|
serviceRunning = serviceRunning,
|
||||||
settingsState = settingsState,
|
settingsState = settingsState,
|
||||||
focusTimeInputFieldState = focusTimeInputFieldState,
|
focusTimeInputFieldState = focusTimeInputFieldState,
|
||||||
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
|
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -50,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
|
||||||
@@ -60,7 +61,7 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.rememberSliderState
|
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
|
||||||
@@ -95,6 +96,7 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
|
|||||||
@Composable
|
@Composable
|
||||||
fun TimerSettings(
|
fun TimerSettings(
|
||||||
isPlus: Boolean,
|
isPlus: Boolean,
|
||||||
|
serviceRunning: Boolean,
|
||||||
settingsState: SettingsState,
|
settingsState: SettingsState,
|
||||||
focusTimeInputFieldState: TextFieldState,
|
focusTimeInputFieldState: TextFieldState,
|
||||||
shortBreakTimeInputFieldState: TextFieldState,
|
shortBreakTimeInputFieldState: TextFieldState,
|
||||||
@@ -111,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())
|
|
||||||
onAction(SettingsAction.SaveDndEnabled(false))
|
|
||||||
}
|
|
||||||
|
|
||||||
val switchItems = listOf(
|
val switchItems = listOf(
|
||||||
SettingsSwitchItem(
|
SettingsSwitchItem(
|
||||||
checked = settingsState.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,
|
||||||
@@ -171,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 {
|
||||||
@@ -190,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,
|
||||||
@@ -210,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
|
||||||
)
|
)
|
||||||
@@ -225,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,
|
||||||
@@ -257,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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -281,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) {
|
||||||
@@ -405,6 +422,7 @@ private fun TimerSettingsPreview() {
|
|||||||
)
|
)
|
||||||
TimerSettings(
|
TimerSettings(
|
||||||
isPlus = false,
|
isPlus = false,
|
||||||
|
serviceRunning = true,
|
||||||
settingsState = remember { SettingsState() },
|
settingsState = remember { SettingsState() },
|
||||||
focusTimeInputFieldState = focusTimeInputFieldState,
|
focusTimeInputFieldState = focusTimeInputFieldState,
|
||||||
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
|
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
|
||||||
|
|||||||
@@ -43,17 +43,26 @@ 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 serviceRunning = timerRepository.serviceRunning.asStateFlow()
|
||||||
|
|
||||||
private val _settingsState = MutableStateFlow(SettingsState())
|
private val _settingsState = MutableStateFlow(SettingsState())
|
||||||
val settingsState = _settingsState.asStateFlow()
|
val settingsState = _settingsState.asStateFlow()
|
||||||
@@ -106,6 +115,7 @@ class SettingsViewModel(
|
|||||||
"session_length",
|
"session_length",
|
||||||
sessionsSliderState.value.toInt()
|
sessionsSliderState.value.toInt()
|
||||||
)
|
)
|
||||||
|
refreshTimer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,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()
|
||||||
@@ -129,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()
|
||||||
@@ -142,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()
|
||||||
@@ -152,6 +165,7 @@ 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()
|
||||||
@@ -269,18 +283,42 @@ class SettingsViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@@ -42,19 +41,20 @@ 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()
|
||||||
@@ -70,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()
|
||||||
@@ -155,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 {
|
||||||
@@ -162,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,
|
||||||
|
|||||||
@@ -93,4 +93,5 @@
|
|||||||
<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="help_with_translation">Help with translation</string>
|
||||||
|
<string name="timer_settings_reset_info">Reset the timer to change settings</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user