diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9dc04d4..a5372a3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + @@ -26,6 +28,13 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt b/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt index b0e144f..e1902bc 100644 --- a/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt +++ b/app/src/main/java/org/nsh07/pomodoro/TomatoApplication.kt @@ -1,6 +1,8 @@ package org.nsh07.pomodoro import android.app.Application +import android.app.NotificationChannel +import android.app.NotificationManager import org.nsh07.pomodoro.data.AppContainer import org.nsh07.pomodoro.data.DefaultAppContainer @@ -9,5 +11,13 @@ class TomatoApplication : Application() { override fun onCreate() { super.onCreate() container = DefaultAppContainer(this) + + val notificationChannel = NotificationChannel( + "timer", + "Timer progress", + NotificationManager.IMPORTANCE_HIGH + ) + + container.notificationManager.createNotificationChannel(notificationChannel) } } \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt b/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt index 2d45910..51c3570 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt @@ -8,11 +8,23 @@ package org.nsh07.pomodoro.data import android.content.Context +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import kotlinx.coroutines.flow.MutableStateFlow +import org.nsh07.pomodoro.R +import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState +import org.nsh07.pomodoro.utils.millisecondsToStr interface AppContainer { val appPreferenceRepository: AppPreferenceRepository val appStatRepository: AppStatRepository val appTimerRepository: AppTimerRepository + val notificationManager: NotificationManagerCompat + val notificationBuilder: NotificationCompat.Builder + val timerState: MutableStateFlow + val time: MutableStateFlow } class DefaultAppContainer(context: Context) : AppContainer { @@ -27,4 +39,31 @@ class DefaultAppContainer(context: Context) : AppContainer { override val appTimerRepository: AppTimerRepository by lazy { AppTimerRepository() } + override val notificationManager: NotificationManagerCompat by lazy { + NotificationManagerCompat.from(context) + } + + override val notificationBuilder: NotificationCompat.Builder by lazy { + NotificationCompat.Builder(context, "timer") + .setSmallIcon(R.drawable.tomato_logo_notification) + .setOngoing(true) + .setColor(Color.Red.toArgb()) + .setRequestPromotedOngoing(true) + .setOngoing(true) + } + + override val timerState: MutableStateFlow by lazy { + MutableStateFlow( + TimerState( + totalTime = appTimerRepository.focusTime, + timeStr = millisecondsToStr(appTimerRepository.focusTime), + nextTimeStr = millisecondsToStr(appTimerRepository.shortBreakTime) + ) + ) + } + + override val time: MutableStateFlow by lazy { + MutableStateFlow(appTimerRepository.focusTime) + } + } \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt b/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt new file mode 100644 index 0000000..9e94929 --- /dev/null +++ b/app/src/main/java/org/nsh07/pomodoro/service/TimerService.kt @@ -0,0 +1,346 @@ +package org.nsh07.pomodoro.service + +import android.annotation.SuppressLint +import android.app.Service +import android.content.Intent +import android.media.MediaPlayer +import android.os.IBinder +import android.os.SystemClock +import android.provider.Settings +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.ui.graphics.toArgb +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.nsh07.pomodoro.TomatoApplication +import org.nsh07.pomodoro.data.AppContainer +import org.nsh07.pomodoro.data.StatRepository +import org.nsh07.pomodoro.data.TimerRepository +import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerMode +import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState +import org.nsh07.pomodoro.utils.millisecondsToStr +import kotlin.text.Typography.middleDot + +@ExperimentalAnimationApi +class TimerService : Service() { + private lateinit var appContainer: AppContainer + + private lateinit var timerRepository: TimerRepository + private lateinit var statRepository: StatRepository + private lateinit var notificationManager: NotificationManagerCompat + private lateinit var notificationBuilder: NotificationCompat.Builder + private lateinit var _timerState: MutableStateFlow + private lateinit var _time: MutableStateFlow + + val timeStateFlow by lazy { + _time.asStateFlow() + } + + var time: Long + get() = timeStateFlow.value + set(value) = _time.update { value } + + lateinit var timerState: StateFlow + + private var cycles = 0 + private var startTime = 0L + private var pauseTime = 0L + private var pauseDuration = 0L + + private val timerJob = SupervisorJob() + private val scope = CoroutineScope(Dispatchers.IO + timerJob) + private val skipScope = CoroutineScope(Dispatchers.IO) + + private lateinit var alarm: MediaPlayer + + private var cs: ColorScheme = lightColorScheme() + + override fun onBind(intent: Intent?): IBinder? { + return null + } + + override fun onCreate() { + appContainer = (application as TomatoApplication).container + timerRepository = appContainer.appTimerRepository + statRepository = appContainer.appStatRepository + notificationManager = NotificationManagerCompat.from(this) + notificationBuilder = appContainer.notificationBuilder + _timerState = appContainer.timerState + _time = appContainer.time + + timerState = _timerState.asStateFlow() + + alarm = MediaPlayer.create( + this, + Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI + ) + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + when (intent?.action) { + Actions.TOGGLE.toString() -> { + startForegroundService() + toggleTimer() + } + + Actions.RESET.toString() -> { + if (timerState.value.timerRunning) toggleTimer() + resetTimer() + stopForegroundService() + } + + Actions.SKIP.toString() -> skipTimer(true) + + Actions.STOP_ALARM.toString() -> stopAlarm() + } + return super.onStartCommand(intent, flags, startId) + } + + private fun toggleTimer() { + if (timerState.value.timerRunning) { + showTimerNotification(time.toInt(), paused = true) + _timerState.update { currentState -> + currentState.copy(timerRunning = false) + } + pauseTime = SystemClock.elapsedRealtime() + } else { + _timerState.update { it.copy(timerRunning = true) } + if (pauseTime != 0L) pauseDuration += SystemClock.elapsedRealtime() - pauseTime + + var iterations = -1 + + scope.launch { + while (true) { + if (!timerState.value.timerRunning) break + if (startTime == 0L) startTime = SystemClock.elapsedRealtime() + + time = when (timerState.value.timerMode) { + TimerMode.FOCUS -> timerRepository.focusTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration).toInt() + + TimerMode.SHORT_BREAK -> timerRepository.shortBreakTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration).toInt() + + else -> timerRepository.longBreakTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration).toInt() + } + + iterations = (iterations + 1) % 50 + + if (iterations == 0) showTimerNotification(time.toInt()) + + if (time < 0) { + skipTimer() + + _timerState.update { currentState -> + currentState.copy(timerRunning = false) + } + timerJob.cancel() + } else { + _timerState.update { currentState -> + currentState.copy( + timeStr = millisecondsToStr(time) + ) + } + } + + delay(100) + } + } + } + } + + @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.. + currentState.copy(alarmRinging = true) + } + } + } + + private fun resetTimer() { + skipScope.launch { + saveTimeToDb() + time = timerRepository.focusTime + cycles = 0 + startTime = 0L + pauseTime = 0L + pauseDuration = 0L + + _timerState.update { currentState -> + currentState.copy( + timerMode = TimerMode.FOCUS, + timeStr = millisecondsToStr(time), + totalTime = time, + 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 + ) + } + } + } + + private fun skipTimer(fromButton: Boolean = false) { + skipScope.launch { + saveTimeToDb() + showTimerNotification(0, paused = true, complete = !fromButton) + startTime = 0L + pauseTime = 0L + pauseDuration = 0L + + cycles = (cycles + 1) % (timerRepository.sessionLength * 2) + + if (cycles % 2 == 0) { + time = timerRepository.focusTime + _timerState.update { currentState -> + currentState.copy( + timerMode = TimerMode.FOCUS, + timeStr = millisecondsToStr(time), + totalTime = time, + nextTimerMode = if (cycles == (timerRepository.sessionLength - 1) * 2) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK, + nextTimeStr = if (cycles == (timerRepository.sessionLength - 1) * 2) millisecondsToStr( + timerRepository.longBreakTime + ) else millisecondsToStr( + timerRepository.shortBreakTime + ), + currentFocusCount = cycles / 2 + 1, + totalFocusCount = timerRepository.sessionLength + ) + } + } else { + val long = cycles == (timerRepository.sessionLength * 2) - 1 + time = if (long) timerRepository.longBreakTime else timerRepository.shortBreakTime + + _timerState.update { currentState -> + currentState.copy( + timerMode = if (long) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK, + timeStr = millisecondsToStr(time), + totalTime = time, + nextTimerMode = TimerMode.FOCUS, + nextTimeStr = millisecondsToStr(timerRepository.focusTime) + ) + } + } + } + } + + fun stopAlarm() { + alarm.pause() + alarm.seekTo(0) + _timerState.update { currentState -> + currentState.copy(alarmRinging = false) + } + } + + suspend fun saveTimeToDb() { + when (timerState.value.timerMode) { + TimerMode.FOCUS -> statRepository.addFocusTime( + (timerState.value.totalTime - time).coerceAtLeast( + 0L + ) + ) + + else -> statRepository.addBreakTime((timerState.value.totalTime - time).coerceAtLeast(0L)) + } + } + + private fun startForegroundService() { + startForeground(1, notificationBuilder.build()) + } + + private fun stopForegroundService() { + notificationManager.cancel(1) + stopForeground(STOP_FOREGROUND_REMOVE) + stopSelf() + } + + private fun setStopButton() { + // TODO + } + + private fun setResumeButton() { + // TODO + } + + override fun onDestroy() { + super.onDestroy() + timerJob.cancel() + } + + enum class Actions { + TOGGLE, SKIP, RESET, STOP_ALARM + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt index dadfe37..24914b1 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt @@ -7,8 +7,10 @@ package org.nsh07.pomodoro.ui +import android.content.Intent import androidx.compose.animation.ContentTransform import androidx.compose.animation.Crossfade +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleOut @@ -33,6 +35,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource @@ -44,19 +47,26 @@ import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.ui.NavDisplay import androidx.window.core.layout.WindowSizeClass import org.nsh07.pomodoro.MainActivity.Companion.screens +import org.nsh07.pomodoro.service.TimerService import org.nsh07.pomodoro.ui.settingsScreen.SettingsScreenRoot import org.nsh07.pomodoro.ui.statsScreen.StatsScreenRoot import org.nsh07.pomodoro.ui.statsScreen.viewModel.StatsViewModel import org.nsh07.pomodoro.ui.timerScreen.TimerScreen +import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerAction import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerViewModel -@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@OptIn( + ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class, + ExperimentalAnimationApi::class +) @Composable fun AppScreen( modifier: Modifier = Modifier, timerViewModel: TimerViewModel = viewModel(factory = TimerViewModel.Factory), statsViewModel: StatsViewModel = viewModel(factory = StatsViewModel.Factory) ) { + val context = LocalContext.current + val uiState by timerViewModel.timerState.collectAsStateWithLifecycle() val remainingTime by timerViewModel.time.collectAsStateWithLifecycle() @@ -139,7 +149,33 @@ fun AppScreen( TimerScreen( timerState = uiState, progress = { progress }, - onAction = timerViewModel::onAction, + onAction = { action -> + 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.padding( start = contentPadding.calculateStartPadding(layoutDirection), end = contentPadding.calculateEndPadding(layoutDirection), 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 ebfd45e..a173dd4 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,38 +7,22 @@ package org.nsh07.pomodoro.ui.timerScreen.viewModel -import android.annotation.SuppressLint import android.app.Application -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Intent -import android.media.MediaPlayer -import android.os.SystemClock -import android.provider.Settings 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.AndroidViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY -import androidx.lifecycle.application import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow 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 @@ -46,7 +30,6 @@ 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( @@ -54,20 +37,10 @@ class TimerViewModel( private val preferenceRepository: PreferenceRepository, private val statRepository: StatRepository, private val timerRepository: TimerRepository, - private val notificationBuilder: NotificationCompat.Builder, - private val notificationManager: NotificationManagerCompat + private val _timerState: MutableStateFlow, + private val _time: MutableStateFlow ) : AndroidViewModel(application) { - private val _timerState = MutableStateFlow( - TimerState( - totalTime = timerRepository.focusTime, - timeStr = millisecondsToStr(timerRepository.focusTime), - nextTimeStr = millisecondsToStr(timerRepository.shortBreakTime) - ) - ) - val timerState: StateFlow = _timerState.asStateFlow() - var timerJob: Job? = null - private val _time = MutableStateFlow(timerRepository.focusTime) val time: StateFlow = _time.asStateFlow() private var cycles = 0 @@ -78,11 +51,6 @@ class TimerViewModel( private lateinit var cs: ColorScheme - private val alarm = MediaPlayer.create( - this.application, - Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI - ) - init { viewModelScope.launch(Dispatchers.IO) { timerRepository.focusTime = @@ -132,16 +100,6 @@ class TimerViewModel( cs = colorScheme } - fun onAction(action: TimerAction) { - when (action) { - is TimerAction.SkipTimer -> skipTimer(action.fromButton) - - TimerAction.ResetTimer -> resetTimer() - TimerAction.StopAlarm -> stopAlarm() - TimerAction.ToggleTimer -> toggleTimer() - } - } - private fun resetTimer() { viewModelScope.launch { saveTimeToDb() @@ -165,107 +123,6 @@ class TimerViewModel( } } - private fun skipTimer(fromButton: Boolean = false) { - viewModelScope.launch { - saveTimeToDb() - showTimerNotification(0, paused = true, complete = !fromButton) - startTime = 0L - pauseTime = 0L - pauseDuration = 0L - - cycles = (cycles + 1) % (timerRepository.sessionLength * 2) - - if (cycles % 2 == 0) { - _time.update { timerRepository.focusTime } - _timerState.update { currentState -> - currentState.copy( - timerMode = TimerMode.FOCUS, - timeStr = millisecondsToStr(time.value), - totalTime = time.value, - nextTimerMode = if (cycles == (timerRepository.sessionLength - 1) * 2) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK, - nextTimeStr = if (cycles == (timerRepository.sessionLength - 1) * 2) millisecondsToStr( - timerRepository.longBreakTime - ) else millisecondsToStr( - timerRepository.shortBreakTime - ), - currentFocusCount = cycles / 2 + 1, - totalFocusCount = timerRepository.sessionLength - ) - } - } else { - val long = cycles == (timerRepository.sessionLength * 2) - 1 - _time.update { if (long) timerRepository.longBreakTime else timerRepository.shortBreakTime } - - _timerState.update { currentState -> - currentState.copy( - timerMode = if (long) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK, - timeStr = millisecondsToStr(time.value), - totalTime = time.value, - nextTimerMode = TimerMode.FOCUS, - nextTimeStr = millisecondsToStr(timerRepository.focusTime) - ) - } - } - } - } - - private fun toggleTimer() { - if (timerState.value.timerRunning) { - showTimerNotification(time.value.toInt(), paused = true) - _timerState.update { currentState -> - currentState.copy(timerRunning = false) - } - timerJob?.cancel() - pauseTime = SystemClock.elapsedRealtime() - } else { - _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 - if (startTime == 0L) startTime = SystemClock.elapsedRealtime() - - _time.update { - when (timerState.value.timerMode) { - TimerMode.FOCUS -> - timerRepository.focusTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration).toInt() - - TimerMode.SHORT_BREAK -> - timerRepository.shortBreakTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration).toInt() - - else -> - timerRepository.longBreakTime - (SystemClock.elapsedRealtime() - startTime - pauseDuration).toInt() - } - } - - iterations = (iterations + 1) % 50 - - if (iterations == 0) showTimerNotification(time.value.toInt()) - - if (time.value < 0) { - skipTimer() - - _timerState.update { currentState -> - currentState.copy(timerRunning = false) - } - timerJob?.cancel() - } else { - _timerState.update { currentState -> - currentState.copy( - timeStr = millisecondsToStr(time.value) - ) - } - } - - delay(100) - } - } - } - } - suspend fun saveTimeToDb() { when (timerState.value.timerMode) { TimerMode.FOCUS -> statRepository @@ -276,93 +133,6 @@ 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.. - currentState.copy(alarmRinging = true) - } - } - } - - fun stopAlarm() { - alarm.pause() - alarm.seekTo(0) - _timerState.update { currentState -> - currentState.copy(alarmRinging = false) - } - } - companion object { val Factory: ViewModelProvider.Factory = viewModelFactory { initializer { @@ -370,43 +140,16 @@ class TimerViewModel( val appPreferenceRepository = application.container.appPreferenceRepository val appStatRepository = application.container.appStatRepository val appTimerRepository = application.container.appTimerRepository - - val notificationManager = NotificationManagerCompat.from(application) - - val notificationChannel = NotificationChannel( - "timer", - "Timer progress", - NotificationManager.IMPORTANCE_HIGH - ) - - notificationManager.createNotificationChannel(notificationChannel) - - val openAppIntent = Intent(application, MainActivity::class.java) - openAppIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - openAppIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) - - val contentIntent = PendingIntent.getActivity( - application, - System.currentTimeMillis().toInt(), - openAppIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - - val notificationBuilder = NotificationCompat.Builder(application, "timer") - .setSmallIcon(R.drawable.tomato_logo_notification) - .setOngoing(true) - .setColor(Color.Red.toArgb()) - .setContentIntent(contentIntent) - .setRequestPromotedOngoing(true) - .setOngoing(true) + val timerState = application.container.timerState + val time = application.container.time TimerViewModel( application = application, preferenceRepository = appPreferenceRepository, statRepository = appStatRepository, timerRepository = appTimerRepository, - notificationBuilder = notificationBuilder, - notificationManager = notificationManager + _timerState = timerState, + _time = time ) } }