feat(offline-first): persist hijri+unsplash cache and scale secondary times

This commit is contained in:
dwindown
2026-04-06 07:53:14 +07:00
parent 4062db77e4
commit 185c55a143
8 changed files with 371 additions and 41 deletions

View File

@@ -1,7 +1,10 @@
import 'dart:async';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:intl/intl.dart';
import '../local/models.dart';
import 'hijri_service.dart';
import 'myquran_service.dart';
class ScheduleCacheStatus {
@@ -196,6 +199,71 @@ class SyncService {
}
}
Future<void> _pruneHijriCache(DateTime referenceDate) async {
if (!Hive.isBoxOpen(HiveBoxes.hijriCache)) return;
final hijriBox = Hive.box<String>(HiveBoxes.hijriCache);
final staleKeys = staleScheduleKeys(
hijriBox.keys.cast<String>(),
referenceDate,
);
if (staleKeys.isNotEmpty) {
await hijriBox.deleteAll(staleKeys);
await hijriBox.compact();
}
}
Set<String> _priorityHijriWarmupKeys(
Set<String> allDateKeys,
DateTime referenceDate,
) {
final prioritized = <String>{};
for (var offset = 0; offset <= 7; offset++) {
final key = _canonicalDateKey(referenceDate.add(Duration(days: offset)));
if (allDateKeys.contains(key)) prioritized.add(key);
}
return prioritized;
}
Set<String> _collectRollingWindowScheduleDateKeys(
Box<DailyPrayerSchedule> scheduleBox,
DateTime referenceDate,
) {
final allowedMonths = rollingWindowMonths(referenceDate);
final keys = <String>{};
for (final rawKey in scheduleBox.keys) {
if (rawKey is! String) continue;
final parsed = _parseScheduleDate(rawKey);
if (parsed == null) continue;
final monthKey = DateFormat('yyyy-MM').format(parsed);
if (!allowedMonths.contains(monthKey)) continue;
keys.add(_canonicalDateKey(parsed));
}
return keys;
}
Future<void> _warmHijriCacheForScheduleRange(
Box<DailyPrayerSchedule> scheduleBox,
DateTime referenceDate,
) async {
final scheduleDateKeys = _collectRollingWindowScheduleDateKeys(
scheduleBox,
referenceDate,
);
if (scheduleDateKeys.isEmpty) return;
final priorityKeys = _priorityHijriWarmupKeys(scheduleDateKeys, referenceDate);
if (priorityKeys.isNotEmpty) {
await HijriCalendarService.instance.warmCacheForDateKeys(priorityKeys);
}
final remainingKeys = scheduleDateKeys.difference(priorityKeys);
if (remainingKeys.isNotEmpty) {
unawaited(HijriCalendarService.instance.warmCacheForDateKeys(remainingKeys));
}
}
bool _shouldAttemptAutoRefresh({
required ScheduleCacheStatus status,
required bool hasTodayData,
@@ -288,9 +356,11 @@ class SyncService {
if (success) {
if (hasCurrentMonth && hasNextMonth) {
await _pruneScheduleCache(scheduleBox, now);
await _pruneHijriCache(now);
}
settings.lastSyncDate = DateFormat('yyyy-MM-dd HH:mm').format(now);
await settings.save();
await _warmHijriCacheForScheduleRange(scheduleBox, now);
}
return success;
@@ -306,16 +376,20 @@ class SyncService {
}
final now = referenceDate ?? DateTime.now();
final scheduleBox =
Hive.box<DailyPrayerSchedule>(HiveBoxes.prayerSchedule);
final status = getCacheStatus(now);
final hasTodayData = getTodaySchedule(now) != null;
if (!_shouldAttemptAutoRefresh(status: status, hasTodayData: hasTodayData)) {
unawaited(_warmHijriCacheForScheduleRange(scheduleBox, now));
return const AutoRefreshResult.skipped('cache-fresh');
}
final lastAttempt = _parseAttemptTimestamp(settings.lastAutoSyncAttemptDate);
if (lastAttempt != null &&
now.difference(lastAttempt) < _autoRefreshCooldown) {
unawaited(_warmHijriCacheForScheduleRange(scheduleBox, now));
return const AutoRefreshResult.skipped('cooldown');
}
@@ -323,6 +397,9 @@ class SyncService {
await settings.save();
final synced = await syncMonthlyData(referenceDate: now);
if (!synced) {
unawaited(_warmHijriCacheForScheduleRange(scheduleBox, now));
}
return synced
? const AutoRefreshResult.synced()
: const AutoRefreshResult.failed('sync-failed');