diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0e52b75..9dc04d4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,9 @@ + + + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && checked) { + permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } + onAction(TimerAction.ToggleTimer) + }, checked = timerState.timerRunning, colors = IconButtonDefaults.filledIconToggleButtonColors( checkedContainerColor = color, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt index b99458d..74cc8ac 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt @@ -7,7 +7,17 @@ package org.nsh07.pomodoro.ui.timerScreen.viewModel +import android.annotation.SuppressLint +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Intent import android.os.SystemClock +import androidx.compose.material3.ColorScheme +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY @@ -23,6 +33,8 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import org.nsh07.pomodoro.MainActivity +import org.nsh07.pomodoro.R import org.nsh07.pomodoro.TomatoApplication import org.nsh07.pomodoro.data.PreferenceRepository import org.nsh07.pomodoro.data.Stat @@ -30,14 +42,16 @@ import org.nsh07.pomodoro.data.StatRepository import org.nsh07.pomodoro.data.TimerRepository import org.nsh07.pomodoro.utils.millisecondsToStr import java.time.LocalDate +import kotlin.text.Typography.middleDot @OptIn(FlowPreview::class) class TimerViewModel( private val preferenceRepository: PreferenceRepository, private val statRepository: StatRepository, - private val timerRepository: TimerRepository + private val timerRepository: TimerRepository, + private val notificationBuilder: NotificationCompat.Builder, + private val notificationManager: NotificationManagerCompat ) : ViewModel() { - // TODO: Document code private val _timerState = MutableStateFlow( TimerState( totalTime = timerRepository.focusTime, @@ -57,6 +71,8 @@ class TimerViewModel( private var pauseTime = 0L private var pauseDuration = 0L + private lateinit var cs: ColorScheme + init { viewModelScope.launch(Dispatchers.IO) { timerRepository.focusTime = @@ -102,6 +118,10 @@ class TimerViewModel( } } + fun setCompositionLocals(colorScheme: ColorScheme) { + cs = colorScheme + } + fun onAction(action: TimerAction) { when (action) { TimerAction.ResetTimer -> resetTimer() @@ -134,6 +154,7 @@ class TimerViewModel( private fun skipTimer() { viewModelScope.launch { saveTimeToDb() + showTimerNotification(0, paused = true, complete = true) startTime = 0L pauseTime = 0L pauseDuration = 0L @@ -174,6 +195,7 @@ class TimerViewModel( private fun toggleTimer() { if (timerState.value.timerRunning) { + showTimerNotification(time.value.toInt(), paused = true) _timerState.update { currentState -> currentState.copy(timerRunning = false) } @@ -183,6 +205,8 @@ class TimerViewModel( _timerState.update { it.copy(timerRunning = true) } if (pauseTime != 0L) pauseDuration += SystemClock.elapsedRealtime() - pauseTime + var iterations = -1 + timerJob = viewModelScope.launch { while (true) { if (!timerState.value.timerRunning) break @@ -201,6 +225,10 @@ class TimerViewModel( } } + iterations = (iterations + 1) % 50 + + if (iterations == 0) showTimerNotification(time.value.toInt()) + if (time.value < 0) { skipTimer() @@ -232,6 +260,78 @@ class TimerViewModel( } } + @SuppressLint("MissingPermission") // We check for the permission when pressing the Play button in the UI + fun showTimerNotification( + remainingTime: Int, + paused: Boolean = false, + complete: Boolean = false + ) { + val totalTime = when (timerState.value.timerMode) { + TimerMode.FOCUS -> timerRepository.focusTime.toInt() + TimerMode.SHORT_BREAK -> timerRepository.shortBreakTime.toInt() + else -> timerRepository.longBreakTime.toInt() + } + + val currentTimer = when (timerState.value.timerMode) { + TimerMode.FOCUS -> "Focus" + TimerMode.SHORT_BREAK -> "Short break" + else -> "Long break" + } + + val nextTimer = when (timerState.value.nextTimerMode) { + TimerMode.FOCUS -> "Focus" + TimerMode.SHORT_BREAK -> "Short break" + else -> "Long break" + } + + val remainingTimeString = + if ((remainingTime.toFloat() / 60000f) < 1.0f) "< 1" + else (remainingTime.toFloat() / 60000f).toInt() + + notificationManager.notify( + 1, + notificationBuilder + .setContentTitle( + if (!complete) { + "$currentTimer $middleDot $remainingTimeString min remaining" + if (paused) " $middleDot Paused" else "" + } else "$currentTimer $middleDot Completed" + ) + .setContentText("Up next: $nextTimer (${timerState.value.nextTimeStr})") + .setStyle( + NotificationCompat.ProgressStyle() + .also { + // Add all the Focus, Short break and long break intervals in order + for (i in 0... + --> + + + + +