feat(android): add getSchedulesWithStatus() and alarm list UI

Adds ability to list alarms with AlarmManager status in web interface.

Changes:
- Add getSchedulesWithStatus() method to DailyNotificationPlugin
- Add ScheduleWithStatus TypeScript interface with isActuallyScheduled flag
- Add alarm list UI to android-test-app with status indicators

Backend:
- getSchedulesWithStatus() returns schedules with AlarmManager verification
- Checks isAlarmScheduled() for each notify schedule with nextRunAt
- Returns isActuallyScheduled boolean flag for each schedule

TypeScript:
- New ScheduleWithStatus interface extending Schedule
- Method signature with JSDoc and usage examples

UI:
- New "📋 List Alarms" button in test app
- Color-coded alarm cards (green=scheduled, orange=not scheduled)
- Shows schedule ID, next run time, pattern, and AlarmManager status
- Useful for debugging recovery scenarios and verifying alarm state

Use case:
- Verify which alarms are in database vs actually scheduled
- Debug Phase 1/2/3 recovery scenarios
- Visual confirmation of alarm state after app launch/boot

Related:
- Enhances: android-test-app for Phase 1-3 testing
- Supports: Recovery verification and debugging
This commit is contained in:
Matthew Raymer
2025-11-28 04:56:19 +00:00
parent 945956dc5a
commit 73301f7d1d
3 changed files with 169 additions and 0 deletions

View File

@@ -1864,6 +1864,57 @@ open class DailyNotificationPlugin : Plugin() {
}
}
/**
* Get all schedules with their AlarmManager status
* Returns schedules from database with isActuallyScheduled flag for each
*/
@PluginMethod
fun getSchedulesWithStatus(call: PluginCall) {
CoroutineScope(Dispatchers.IO).launch {
try {
val options = call.getObject("options")
val kind = options?.getString("kind")
val enabled = options?.getBoolean("enabled")
val context = context ?: return@launch call.reject("Context not available")
val schedules = when {
kind != null && enabled != null ->
getDatabase().scheduleDao().getByKindAndEnabled(kind, enabled)
kind != null ->
getDatabase().scheduleDao().getByKind(kind)
enabled != null ->
if (enabled) getDatabase().scheduleDao().getEnabled() else getDatabase().scheduleDao().getAll().filter { !it.enabled }
else ->
getDatabase().scheduleDao().getAll()
}
// For each schedule, check if it's actually scheduled in AlarmManager
val schedulesArray = org.json.JSONArray()
schedules.forEach { schedule ->
val scheduleJson = scheduleToJson(schedule)
// Only check AlarmManager status for "notify" schedules with nextRunAt
if (schedule.kind == "notify" && schedule.nextRunAt != null) {
val isScheduled = NotifyReceiver.isAlarmScheduled(context, schedule.nextRunAt!!)
scheduleJson.put("isActuallyScheduled", isScheduled)
} else {
scheduleJson.put("isActuallyScheduled", false)
}
schedulesArray.put(scheduleJson)
}
call.resolve(JSObject().apply {
put("schedules", schedulesArray)
})
} catch (e: Exception) {
Log.e(TAG, "Failed to get schedules with status", e)
call.reject("Failed to get schedules with status: ${e.message}")
}
}
}
@PluginMethod
fun createSchedule(call: PluginCall) {
CoroutineScope(Dispatchers.IO).launch {