Skip to content

Commit

Permalink
Merge pull request #15 from kirasok/handle_past_alarms
Browse files Browse the repository at this point in the history
feat(domain): handle past alarms
  • Loading branch information
kirasok authored Apr 3, 2024
2 parents 442e368 + 222cc7b commit 91e72c8
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import java.time.ZonedDateTime
data class Alarm(
@PrimaryKey(autoGenerate = true) val id: Int = 0, // id is generated on **db.insert**, not on creation of object
val timestamp: ZonedDateTime,
var enabled: Boolean = true,
)

class InvalidAlarmException(message: String) : Exception(message)
data class InvalidAlarmException(val invalidAlarmError: InvalidAlarmError) :
Exception(invalidAlarmError.toString())

enum class InvalidAlarmError {
PAST_TIMESTAMP, NULL_ALARM
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package io.github.kirasok.alarmix.domain.use_case

import io.github.kirasok.alarmix.domain.model.Alarm
import io.github.kirasok.alarmix.domain.model.InvalidAlarmError
import io.github.kirasok.alarmix.domain.model.InvalidAlarmException
import io.github.kirasok.alarmix.domain.repository.AlarmRepository
import io.github.kirasok.alarmix.domain.repository.AlarmScheduler
import java.time.ZonedDateTime

data class AlarmUseCases(
val getAlarms: GetAlarms,
Expand Down Expand Up @@ -32,8 +34,7 @@ class InsertAlarm(
validate(alarm)
val id =
repository.insertAlarm(alarm) // alarm.id is set during insertion in DB, repository return id so we can use it in scheduler
if (alarm.enabled)
scheduler.schedule(alarm.copy(id = id))
scheduler.schedule(alarm.copy(id = id))
}
}

Expand All @@ -46,11 +47,11 @@ class DeleteAlarm(private val repository: AlarmRepository) {
}

class ValidateAlarm(private val scheduler: AlarmScheduler) {
suspend operator fun invoke(
operator fun invoke(
alarm: Alarm,
): Boolean = when {
alarm.timestamp.toEpochSecond() * 1000 < System.currentTimeMillis() && alarm.enabled // accepts in milliseconds
-> throw InvalidAlarmException("timestamp can't be less than current time")
alarm.timestamp.isBefore(ZonedDateTime.now())
-> throw InvalidAlarmException(InvalidAlarmError.PAST_TIMESTAMP)

!scheduler.canSchedule() -> throw SecurityException(
"Can't schedule alarm without permission from user"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ import android.os.IBinder
import android.os.VibrationAttributes
import android.os.VibrationEffect
import android.os.VibratorManager
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import dagger.hilt.android.AndroidEntryPoint
import io.github.kirasok.alarmix.NotificationChannels
import io.github.kirasok.alarmix.R
import io.github.kirasok.alarmix.domain.model.Alarm
import io.github.kirasok.alarmix.domain.model.InvalidAlarmError
import io.github.kirasok.alarmix.domain.model.InvalidAlarmException
import io.github.kirasok.alarmix.domain.use_case.AlarmUseCases
import io.github.kirasok.alarmix.presentation.MainActivity
Expand All @@ -48,7 +48,11 @@ class AlarmService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
private val alarmActionReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.getStringExtra(ALARM_ACTION_KEY)?.let { AlarmAction.valueOf(it) }) {
AlarmAction.DISMISS -> onDestroy()
AlarmAction.DISMISS -> {
runBlocking { useCases.deleteAlarm(alarm) }
onDestroy()
}

AlarmAction.SNOOZE -> onDestroy() // TODO: implement snooze
null -> throw IllegalStateException("Alarm action can't be null")
}
Expand All @@ -75,7 +79,7 @@ class AlarmService : Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnEr
val id = intent?.getIntExtra(AlarmReceiver.INTENT_EXTRA_ALARM_ID, -1).takeIf { it != -1 }
?: throw IllegalStateException("Alarm id can't be null")
alarm = runBlocking { useCases.getAlarmById(id) }
?: throw InvalidAlarmException("Alarm can't be null")
?: throw InvalidAlarmException(InvalidAlarmError.NULL_ALARM)

mediaPlayer = MediaPlayer()
initMediaPlayer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import dagger.hilt.android.AndroidEntryPoint
import io.github.kirasok.alarmix.domain.model.InvalidAlarmError
import io.github.kirasok.alarmix.domain.model.InvalidAlarmException
import io.github.kirasok.alarmix.domain.use_case.AlarmUseCases
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import java.time.ZonedDateTime
import javax.inject.Inject

@AndroidEntryPoint
Expand All @@ -20,12 +21,17 @@ class BootReceiver : BroadcastReceiver() {
// Get alarms
val alarms = runBlocking(Dispatchers.IO) { alarmUseCases.getAlarms() }

// Enable enabled alarms which timestamp is after now
alarms.forEach {
if (it.enabled && it.timestamp.isAfter(ZonedDateTime.now())) {
runBlocking(Dispatchers.IO) { alarmUseCases.insertAlarm(it) } // On insert, alarm will be validated, replaced in DB and scheduled
} else if (it.enabled) { // Disable alarms which timestamp is in past
runBlocking(Dispatchers.IO) { alarmUseCases.insertAlarm(it.copy(enabled = false)) }
// Insert each alarm
alarms.forEach { alarm ->
runBlocking {
try {
alarmUseCases.insertAlarm(alarm) // insertAlarm validates alarm and throws InvalidAlarmException if alarm is not valid
} catch (e: InvalidAlarmException) {
when (e.invalidAlarmError) {
InvalidAlarmError.PAST_TIMESTAMP -> alarmUseCases.deleteAlarm(alarm) // If alarm has past timestamp then we delete it
else -> {}
}
}
}
}
}
Expand Down

0 comments on commit 91e72c8

Please sign in to comment.