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

@@ -74,6 +74,14 @@
<button class="button" onclick="requestPermissions()">Request Permissions</button>
<button class="button" onclick="testNotification()">Test Notification</button>
<button class="button" onclick="checkComprehensiveStatus()">Full System Status</button>
<button class="button" onclick="loadAlarmList()">📋 List Alarms</button>
<div id="alarmListContainer" class="status" style="margin-top: 20px; display: none;">
<strong>📋 Scheduled Alarms</strong>
<div id="alarmList" style="margin-top: 10px; text-align: left;">
Loading...
</div>
</div>
<div id="status" class="status">
Ready to test...
@@ -385,11 +393,89 @@
}
}
function loadAlarmList() {
const status = document.getElementById('status');
const alarmListContainer = document.getElementById('alarmListContainer');
const alarmList = document.getElementById('alarmList');
status.innerHTML = 'Loading alarm list...';
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
alarmListContainer.style.display = 'block';
alarmList.innerHTML = 'Loading...';
try {
if (!window.DailyNotification) {
status.innerHTML = 'DailyNotification plugin not available';
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
alarmList.innerHTML = '❌ Plugin unavailable';
return;
}
window.DailyNotification.getSchedulesWithStatus({
kind: 'notify',
enabled: true
})
.then(result => {
const schedules = result.schedules || [];
if (schedules.length === 0) {
alarmList.innerHTML = '<em>No alarms scheduled</em>';
status.innerHTML = '✅ No alarms found';
status.style.background = 'rgba(255, 255, 255, 0.1)';
return;
}
let html = '<div style="display: flex; flex-direction: column; gap: 10px;">';
schedules.forEach(schedule => {
const nextRun = schedule.nextRunAt ? new Date(schedule.nextRunAt) : null;
const nextRunStr = nextRun ? nextRun.toLocaleString() : 'Not scheduled';
const statusIcon = schedule.isActuallyScheduled ? '✅' : '⚠️';
const statusText = schedule.isActuallyScheduled ? 'Scheduled in AlarmManager' : 'Not in AlarmManager';
const statusColor = schedule.isActuallyScheduled ? 'rgba(0, 255, 0, 0.2)' : 'rgba(255, 165, 0, 0.2)';
html += `
<div style="padding: 12px; background: ${statusColor}; border-radius: 8px; border-left: 4px solid ${schedule.isActuallyScheduled ? '#0f0' : '#ffa500'};">
<div style="font-weight: bold; margin-bottom: 6px;">
${statusIcon} ${schedule.id}
</div>
<div style="font-size: 0.9em; margin-bottom: 4px;">
📅 Next Run: ${nextRunStr}
</div>
<div style="font-size: 0.85em; color: rgba(255, 255, 255, 0.8);">
${schedule.cron ? `Cron: ${schedule.cron}` : schedule.clockTime ? `Time: ${schedule.clockTime}` : 'No schedule pattern'}
</div>
<div style="font-size: 0.85em; margin-top: 4px; color: rgba(255, 255, 255, 0.9);">
Status: ${statusText}
</div>
</div>
`;
});
html += '</div>';
alarmList.innerHTML = html;
status.innerHTML = `✅ Found ${schedules.length} alarm(s)`;
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
})
.catch(error => {
alarmList.innerHTML = `❌ Error: ${error.message}`;
status.innerHTML = `Failed to load alarms: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
});
} catch (error) {
alarmList.innerHTML = `❌ Error: ${error.message}`;
status.innerHTML = `Failed to load alarms: ${error.message}`;
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
}
}
// Attach to window object
window.configurePlugin = configurePlugin;
window.testNotification = testNotification;
window.requestPermissions = requestPermissions;
window.checkComprehensiveStatus = checkComprehensiveStatus;
window.loadAlarmList = loadAlarmList;
function loadPermissionStatus() {
const notificationPermStatus = document.getElementById('notificationPermStatus');