@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* 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.timerScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
|
import androidx.compose.material3.BasicAlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.MaterialTheme.typography
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.nsh07.pomodoro.R
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AlarmDialog(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
stopAlarm: () -> Unit
|
||||||
|
) {
|
||||||
|
BasicAlertDialog(
|
||||||
|
onDismissRequest = stopAlarm,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.clickable(onClick = stopAlarm),
|
||||||
|
shape = MaterialTheme.shapes.extraLarge,
|
||||||
|
tonalElevation = AlertDialogDefaults.TonalElevation,
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(24.dp)) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.alarm),
|
||||||
|
contentDescription = "Alarm",
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
Text(
|
||||||
|
text = "Stop Alarm?",
|
||||||
|
style = typography.headlineSmall,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
Text(
|
||||||
|
text = "Current timer session is complete. Tap anywhere to stop the alarm."
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Button(
|
||||||
|
onClick = stopAlarm,
|
||||||
|
modifier = Modifier.align(Alignment.End),
|
||||||
|
) {
|
||||||
|
Text("Ok")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -111,6 +111,9 @@ fun TimerScreen(
|
|||||||
onResult = {}
|
onResult = {}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (timerState.alarmRinging)
|
||||||
|
AlarmDialog { onAction(TimerAction.StopAlarm) }
|
||||||
|
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
|
|||||||
@@ -10,5 +10,6 @@ package org.nsh07.pomodoro.ui.timerScreen.viewModel
|
|||||||
sealed interface TimerAction {
|
sealed interface TimerAction {
|
||||||
data object ResetTimer : TimerAction
|
data object ResetTimer : TimerAction
|
||||||
data object SkipTimer : TimerAction
|
data object SkipTimer : TimerAction
|
||||||
|
data object StopAlarm : TimerAction
|
||||||
data object ToggleTimer : TimerAction
|
data object ToggleTimer : TimerAction
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,8 @@ data class TimerState(
|
|||||||
val nextTimeStr: String = "5:00",
|
val nextTimeStr: String = "5:00",
|
||||||
val showBrandTitle: Boolean = true,
|
val showBrandTitle: Boolean = true,
|
||||||
val currentFocusCount: Int = 1,
|
val currentFocusCount: Int = 1,
|
||||||
val totalFocusCount: Int = 4
|
val totalFocusCount: Int = 4,
|
||||||
|
val alarmRinging: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class TimerMode {
|
enum class TimerMode {
|
||||||
|
|||||||
@@ -8,19 +8,23 @@
|
|||||||
package org.nsh07.pomodoro.ui.timerScreen.viewModel
|
package org.nsh07.pomodoro.ui.timerScreen.viewModel
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Application
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.media.MediaPlayer
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
|
import android.provider.Settings
|
||||||
import androidx.compose.material3.ColorScheme
|
import androidx.compose.material3.ColorScheme
|
||||||
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.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
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.application
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.initializer
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
@@ -46,12 +50,13 @@ import kotlin.text.Typography.middleDot
|
|||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
class TimerViewModel(
|
class TimerViewModel(
|
||||||
|
application: Application,
|
||||||
private val preferenceRepository: PreferenceRepository,
|
private val preferenceRepository: PreferenceRepository,
|
||||||
private val statRepository: StatRepository,
|
private val statRepository: StatRepository,
|
||||||
private val timerRepository: TimerRepository,
|
private val timerRepository: TimerRepository,
|
||||||
private val notificationBuilder: NotificationCompat.Builder,
|
private val notificationBuilder: NotificationCompat.Builder,
|
||||||
private val notificationManager: NotificationManagerCompat
|
private val notificationManager: NotificationManagerCompat
|
||||||
) : ViewModel() {
|
) : AndroidViewModel(application) {
|
||||||
private val _timerState = MutableStateFlow(
|
private val _timerState = MutableStateFlow(
|
||||||
TimerState(
|
TimerState(
|
||||||
totalTime = timerRepository.focusTime,
|
totalTime = timerRepository.focusTime,
|
||||||
@@ -73,6 +78,11 @@ class TimerViewModel(
|
|||||||
|
|
||||||
private lateinit var cs: ColorScheme
|
private lateinit var cs: ColorScheme
|
||||||
|
|
||||||
|
private val alarm = MediaPlayer.create(
|
||||||
|
this.application,
|
||||||
|
Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI
|
||||||
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
timerRepository.focusTime =
|
timerRepository.focusTime =
|
||||||
@@ -126,6 +136,7 @@ class TimerViewModel(
|
|||||||
when (action) {
|
when (action) {
|
||||||
TimerAction.ResetTimer -> resetTimer()
|
TimerAction.ResetTimer -> resetTimer()
|
||||||
TimerAction.SkipTimer -> skipTimer()
|
TimerAction.SkipTimer -> skipTimer()
|
||||||
|
TimerAction.StopAlarm -> stopAlarm()
|
||||||
TimerAction.ToggleTimer -> toggleTimer()
|
TimerAction.ToggleTimer -> toggleTimer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,9 +342,24 @@ class TimerViewModel(
|
|||||||
)
|
)
|
||||||
.setShowWhen(true)
|
.setShowWhen(true)
|
||||||
.setWhen(System.currentTimeMillis() + remainingTime) // Sets the Live Activity/Now Bar chip time
|
.setWhen(System.currentTimeMillis() + remainingTime) // Sets the Live Activity/Now Bar chip time
|
||||||
.setSilent(!complete)
|
.setSilent(true)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (complete) {
|
||||||
|
alarm.start()
|
||||||
|
_timerState.update { currentState ->
|
||||||
|
currentState.copy(alarmRinging = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopAlarm() {
|
||||||
|
alarm.pause()
|
||||||
|
alarm.seekTo(0)
|
||||||
|
_timerState.update { currentState ->
|
||||||
|
currentState.copy(alarmRinging = false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -374,6 +400,7 @@ class TimerViewModel(
|
|||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
|
|
||||||
TimerViewModel(
|
TimerViewModel(
|
||||||
|
application = application,
|
||||||
preferenceRepository = appPreferenceRepository,
|
preferenceRepository = appPreferenceRepository,
|
||||||
statRepository = appStatRepository,
|
statRepository = appStatRepository,
|
||||||
timerRepository = appTimerRepository,
|
timerRepository = appTimerRepository,
|
||||||
|
|||||||
16
app/src/main/res/drawable/alarm.xml
Normal file
16
app/src/main/res/drawable/alarm.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!--
|
||||||
|
~ 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="#e3e3e3"
|
||||||
|
android:pathData="M520,504v-144q0,-17 -11.5,-28.5T480,320q-17,0 -28.5,11.5T440,360v159q0,8 3,15.5t9,13.5l112,112q11,11 28,11t28,-11q11,-11 11,-28t-11,-28L520,504ZM480,880q-75,0 -140.5,-28.5t-114,-77q-48.5,-48.5 -77,-114T120,520q0,-75 28.5,-140.5t77,-114q48.5,-48.5 114,-77T480,160q75,0 140.5,28.5t114,77q48.5,48.5 77,114T840,520q0,75 -28.5,140.5t-77,114q-48.5,48.5 -114,77T480,880ZM82,292q-11,-11 -11,-28t11,-28l114,-114q11,-11 28,-11t28,11q11,11 11,28t-11,28L138,292q-11,11 -28,11t-28,-11ZM878,292q-11,11 -28,11t-28,-11L708,178q-11,-11 -11,-28t11,-28q11,-11 28,-11t28,11l114,114q11,11 11,28t-11,28Z" />
|
||||||
|
</vector>
|
||||||
Reference in New Issue
Block a user