feat(ui): implement waking screen on alarm without wakelock
Closes: #46
This commit is contained in:
@@ -1,4 +1,20 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
@@ -7,7 +23,6 @@
|
|||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.POST_PROMOTED_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_PROMOTED_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".TomatoApplication"
|
android:name=".TomatoApplication"
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import org.nsh07.pomodoro.utils.toColor
|
|||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
private val timerViewModel: TimerViewModel by viewModels(factoryProducer = { TimerViewModel.Factory })
|
private val timerViewModel: TimerViewModel by viewModels(factoryProducer = { TimerViewModel.Factory })
|
||||||
private val settingsViewModel: SettingsViewModel by viewModels(factoryProducer = { SettingsViewModel.Factory })
|
private val settingsViewModel: SettingsViewModel by viewModels(factoryProducer = { SettingsViewModel.Factory })
|
||||||
|
|
||||||
private val appContainer by lazy {
|
private val appContainer by lazy {
|
||||||
@@ -45,6 +45,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
appContainer.activityTurnScreenOn = {
|
||||||
|
setShowWhenLocked(it)
|
||||||
|
setTurnScreenOn(it)
|
||||||
|
}
|
||||||
setContent {
|
setContent {
|
||||||
val preferencesState by settingsViewModel.preferencesState.collectAsStateWithLifecycle()
|
val preferencesState by settingsViewModel.preferencesState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2025 Nishant Mishra
|
* Copyright (c) 2025 Nishant Mishra
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
*
|
||||||
|
* 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.data
|
package org.nsh07.pomodoro.data
|
||||||
@@ -27,6 +37,7 @@ interface AppContainer {
|
|||||||
val notificationBuilder: NotificationCompat.Builder
|
val notificationBuilder: NotificationCompat.Builder
|
||||||
val timerState: MutableStateFlow<TimerState>
|
val timerState: MutableStateFlow<TimerState>
|
||||||
val time: MutableStateFlow<Long>
|
val time: MutableStateFlow<Long>
|
||||||
|
var activityTurnScreenOn: (Boolean) -> Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultAppContainer(context: Context) : AppContainer {
|
class DefaultAppContainer(context: Context) : AppContainer {
|
||||||
@@ -78,4 +89,6 @@ class DefaultAppContainer(context: Context) : AppContainer {
|
|||||||
MutableStateFlow(appTimerRepository.focusTime)
|
MutableStateFlow(appTimerRepository.focusTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var activityTurnScreenOn: (Boolean) -> Unit = {}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Nishant Mishra
|
||||||
|
*
|
||||||
|
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||||
|
*
|
||||||
|
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||||
|
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||||
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.nsh07.pomodoro.service
|
package org.nsh07.pomodoro.service
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
@@ -7,7 +24,6 @@ import android.media.AudioAttributes
|
|||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.PowerManager
|
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.os.VibrationEffect
|
import android.os.VibrationEffect
|
||||||
import android.os.Vibrator
|
import android.os.Vibrator
|
||||||
@@ -23,7 +39,6 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.nsh07.pomodoro.MainActivity
|
|
||||||
import org.nsh07.pomodoro.R
|
import org.nsh07.pomodoro.R
|
||||||
import org.nsh07.pomodoro.TomatoApplication
|
import org.nsh07.pomodoro.TomatoApplication
|
||||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerMode
|
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerMode
|
||||||
@@ -60,10 +75,6 @@ class TimerService : Service() {
|
|||||||
private val skipScope = CoroutineScope(Dispatchers.IO + job)
|
private val skipScope = CoroutineScope(Dispatchers.IO + job)
|
||||||
|
|
||||||
private var alarm: MediaPlayer? = null
|
private var alarm: MediaPlayer? = null
|
||||||
|
|
||||||
|
|
||||||
private var wakeLock: PowerManager.WakeLock? = null
|
|
||||||
|
|
||||||
private val vibrator by lazy {
|
private val vibrator by lazy {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
val vibratorManager = getSystemService(VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
val vibratorManager = getSystemService(VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
||||||
@@ -94,7 +105,6 @@ class TimerService : Service() {
|
|||||||
saveTimeToDb()
|
saveTimeToDb()
|
||||||
notificationManager.cancel(1)
|
notificationManager.cancel(1)
|
||||||
alarm?.release()
|
alarm?.release()
|
||||||
wakeLock?.release()
|
|
||||||
}
|
}
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
@@ -123,7 +133,6 @@ class TimerService : Service() {
|
|||||||
return super.onStartCommand(intent, flags, startId)
|
return super.onStartCommand(intent, flags, startId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun toggleTimer() {
|
private fun toggleTimer() {
|
||||||
updateProgressSegments()
|
updateProgressSegments()
|
||||||
|
|
||||||
@@ -164,20 +173,6 @@ class TimerService : Service() {
|
|||||||
if (iterations == 0) showTimerNotification(time.toInt())
|
if (iterations == 0) showTimerNotification(time.toInt())
|
||||||
|
|
||||||
if (time < 0) {
|
if (time < 0) {
|
||||||
val powerManager = this@TimerService.getSystemService(POWER_SERVICE) as PowerManager
|
|
||||||
wakeLock = powerManager.newWakeLock(
|
|
||||||
PowerManager.SCREEN_BRIGHT_WAKE_LOCK or
|
|
||||||
PowerManager.ACQUIRE_CAUSES_WAKEUP or
|
|
||||||
PowerManager.ON_AFTER_RELEASE,
|
|
||||||
"PomodoroApp:AlarmWakeLock"
|
|
||||||
)
|
|
||||||
wakeLock?.acquire(2 * 60 * 1000L)
|
|
||||||
val intent = Intent(this@TimerService, MainActivity::class.java).apply {
|
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
||||||
}
|
|
||||||
startActivity(intent)
|
|
||||||
|
|
||||||
|
|
||||||
skipTimer()
|
skipTimer()
|
||||||
_timerState.update { currentState ->
|
_timerState.update { currentState ->
|
||||||
currentState.copy(timerRunning = false)
|
currentState.copy(timerRunning = false)
|
||||||
@@ -197,7 +192,10 @@ class TimerService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission", "StringFormatInvalid") // We check for the permission when pressing the Play button in the UI
|
@SuppressLint(
|
||||||
|
"MissingPermission",
|
||||||
|
"StringFormatInvalid"
|
||||||
|
) // We check for the permission when pressing the Play button in the UI
|
||||||
fun showTimerNotification(
|
fun showTimerNotification(
|
||||||
remainingTime: Int, paused: Boolean = false, complete: Boolean = false
|
remainingTime: Int, paused: Boolean = false, complete: Boolean = false
|
||||||
) {
|
) {
|
||||||
@@ -372,6 +370,8 @@ class TimerService : Service() {
|
|||||||
fun startAlarm() {
|
fun startAlarm() {
|
||||||
if (timerRepository.alarmEnabled) alarm?.start()
|
if (timerRepository.alarmEnabled) alarm?.start()
|
||||||
|
|
||||||
|
appContainer.activityTurnScreenOn(true)
|
||||||
|
|
||||||
if (timerRepository.vibrateEnabled) {
|
if (timerRepository.vibrateEnabled) {
|
||||||
if (!vibrator.hasVibrator()) {
|
if (!vibrator.hasVibrator()) {
|
||||||
return
|
return
|
||||||
@@ -393,8 +393,7 @@ class TimerService : Service() {
|
|||||||
vibrator.cancel()
|
vibrator.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
wakeLock?.release()
|
appContainer.activityTurnScreenOn(false)
|
||||||
wakeLock = null
|
|
||||||
|
|
||||||
_timerState.update { currentState ->
|
_timerState.update { currentState ->
|
||||||
currentState.copy(alarmRinging = false)
|
currentState.copy(alarmRinging = false)
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2025 Nishant Mishra
|
* Copyright (c) 2025 Nishant Mishra
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
*
|
||||||
|
* 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.timerScreen
|
package org.nsh07.pomodoro.ui.timerScreen
|
||||||
|
|
||||||
import androidx.activity.compose.LocalActivity
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -25,7 +34,6 @@ import androidx.compose.material3.Surface
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@@ -39,21 +47,6 @@ fun AlarmDialog(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
stopAlarm: () -> Unit
|
stopAlarm: () -> Unit
|
||||||
) {
|
) {
|
||||||
val activity = LocalActivity.current
|
|
||||||
|
|
||||||
// Set lockscreen flags when dialog appears, remove when it disappears
|
|
||||||
DisposableEffect(Unit) {
|
|
||||||
// Show over lockscreen
|
|
||||||
activity?.setShowWhenLocked(true)
|
|
||||||
activity?.setTurnScreenOn(true)
|
|
||||||
|
|
||||||
onDispose {
|
|
||||||
// Remove lockscreen flags when dialog is dismissed
|
|
||||||
activity?.setShowWhenLocked(false)
|
|
||||||
activity?.setTurnScreenOn(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BasicAlertDialog(
|
BasicAlertDialog(
|
||||||
onDismissRequest = stopAlarm,
|
onDismissRequest = stopAlarm,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@@ -93,4 +86,3 @@ fun AlarmDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user