fix(ios,android): implement rolling window counting, TTL validation, and DB persistence

- iOS: Implement rolling window counting using UNUserNotificationCenter
- iOS: Enable TTL validation in scheduler before arming notifications
- iOS: Implement SQLite persistence for save/delete/clear operations
- Android: Implement rolling window counting using storage as source of truth
- Android: Add dateBoundsMillis helper for date range calculations

Removes all TODO stubs affecting capacity/rate-limiting correctness.
Fixes runtime behavior to match test expectations and optimizer logic.

Refs: Deep fixes directive for bottom-of-tree gaps
This commit is contained in:
Matthew Raymer
2025-12-24 04:11:41 +00:00
parent 9b73e873d9
commit d8b29954a2
4 changed files with 233 additions and 33 deletions

View File

@@ -271,10 +271,17 @@ public class DailyNotificationRollingWindow {
*/
private int countPendingNotifications() {
try {
// This would typically query the storage for pending notifications
// For now, we'll use a placeholder implementation
return 0; // TODO: Implement actual counting logic
long now = System.currentTimeMillis();
int count = 0;
List<NotificationContent> all = storage.getAllNotifications();
for (NotificationContent n : all) {
if (n.getScheduledTime() >= now) {
count++;
}
}
return count;
} catch (Exception e) {
Log.e(TAG, "Error counting pending notifications", e);
return 0;
@@ -289,10 +296,20 @@ public class DailyNotificationRollingWindow {
*/
private int countNotificationsForDate(String date) {
try {
// This would typically query the storage for notifications on a specific date
// For now, we'll use a placeholder implementation
return 0; // TODO: Implement actual counting logic
long[] bounds = dateBoundsMillis(date);
long start = bounds[0];
long end = bounds[1];
int count = 0;
List<NotificationContent> all = storage.getAllNotifications();
for (NotificationContent n : all) {
long t = n.getScheduledTime();
if (t >= start && t < end) {
count++;
}
}
return count;
} catch (Exception e) {
Log.e(TAG, "Error counting notifications for date: " + date, e);
return 0;
@@ -307,10 +324,20 @@ public class DailyNotificationRollingWindow {
*/
private List<NotificationContent> getNotificationsForDate(String date) {
try {
// This would typically query the storage for notifications on a specific date
// For now, we'll return an empty list
return new ArrayList<>(); // TODO: Implement actual retrieval logic
long[] bounds = dateBoundsMillis(date);
long start = bounds[0];
long end = bounds[1];
List<NotificationContent> results = new ArrayList<>();
List<NotificationContent> all = storage.getAllNotifications();
for (NotificationContent n : all) {
long t = n.getScheduledTime();
if (t >= start && t < end) {
results.add(n);
}
}
return results;
} catch (Exception e) {
Log.e(TAG, "Error getting notifications for date: " + date, e);
return new ArrayList<>();
@@ -331,6 +358,34 @@ public class DailyNotificationRollingWindow {
return String.format("%04d-%02d-%02d", year, month, day);
}
/**
* Get date bounds in milliseconds for a given date string
*
* @param yyyyMmDd Date in YYYY-MM-DD format
* @return Array with [startMillis, endMillis]
*/
private long[] dateBoundsMillis(String yyyyMmDd) {
// yyyyMmDd: "YYYY-MM-DD"
String[] parts = yyyyMmDd.split("-");
int year = Integer.parseInt(parts[0]);
int month = Integer.parseInt(parts[1]); // 1-12
int day = Integer.parseInt(parts[2]);
Calendar start = Calendar.getInstance();
start.set(Calendar.YEAR, year);
start.set(Calendar.MONTH, month - 1); // Calendar months are 0-based
start.set(Calendar.DAY_OF_MONTH, day);
start.set(Calendar.HOUR_OF_DAY, 0);
start.set(Calendar.MINUTE, 0);
start.set(Calendar.SECOND, 0);
start.set(Calendar.MILLISECOND, 0);
Calendar end = (Calendar) start.clone();
end.add(Calendar.DAY_OF_MONTH, 1);
return new long[] { start.getTimeInMillis(), end.getTimeInMillis() };
}
/**
* Get rolling window statistics
*