mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-07-04 13:23:11 +02:00
M3 – GitHub OAuth device flow: replace PAT-paste with a guided "Connect
GitHub" button that runs the device-code flow; tapping with no client id
now opens a setup dialog (instructions + inline paste field) rather than
a buried inline hint. Bakes in the app's own OAuth App client id so fresh
installs work with zero manual config. Auto-syncs immediately after
connect. Verified end-to-end on the real phone: OAuth flow → token saved
→ PC's 48-entry log merged in (confirmed via food-bank vs manual source
labels in History).
M4 – Background meal-slot notifications: WorkManager periodic task (15 min
floor) checks for overdue slots and posts/cancels notifications via
flutter_local_notifications. New permissions: POST_NOTIFICATIONS,
WAKE_LOCK, RECEIVE_BOOT_COMPLETED, REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
INTERNET (was missing — latent sync bug). "Disable battery optimization"
button in Settings. Verified on real phone: WorkManager registered, forced
run posted a real notification ("Meal not logged / You haven't logged your
16:00 meal yet."), isolated to background path (only caller is the
WorkManager dispatcher, not any foreground lifecycle hook).
AGP9 release crash fix: AGP 9 defaults isMinifyEnabled/isShrinkResources
to true for release even with no proguard config; R8 stripped
WorkDatabase_Impl's reflection-only constructor, crashing every launch
with NoSuchMethodException. Explicitly disabled both flags in
build.gradle.kts. Verified via dexdump (constructor present) and on-device
launch (no crash). Proper R8 keep rules are the long-term fix; tracked.
177 tests, flutter analyze clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SWPUBzE24Ls9i9GMRwXnnn
53 lines
2.3 KiB
Dart
53 lines
2.3 KiB
Dart
/// WorkManager-driven periodic check: re-runs the same due/missing-slot
|
|
/// logic diet_guard's `_gate.py` uses to decide whether to lock the PC, and
|
|
/// syncs notifications to match. Registered as a 15-minute periodic task
|
|
/// (WorkManager's periodic floor) rather than four fixed exact alarms --
|
|
/// more robust against OEM background-kill behavior, at the cost of ±15 min
|
|
/// precision (accepted; see the project plan). Deliberately **not**
|
|
/// requesting `SCHEDULE_EXACT_ALARM` for this reason -- don't reach for it
|
|
/// to "fix" perceived lateness.
|
|
library;
|
|
|
|
import 'package:diet_guard_app/models/slot.dart';
|
|
import 'package:diet_guard_app/services/log_storage_service.dart';
|
|
import 'package:diet_guard_app/services/notification_service.dart';
|
|
import 'package:workmanager/workmanager.dart';
|
|
|
|
/// Unique WorkManager task name for the periodic due-slot check.
|
|
const String backgroundCheckTaskName = 'diet_guard.background_check';
|
|
|
|
/// Reads the local log, computes today's due-but-unlogged slots as of
|
|
/// [now] (defaults to the real clock), and syncs notifications to match.
|
|
///
|
|
/// Extracted from [backgroundCheckCallbackDispatcher] so this logic is
|
|
/// unit-testable without the real WorkManager plugin, which only runs as a
|
|
/// true background isolate on-device. [now] is injectable for the same
|
|
/// reason `slot.dart`'s functions are clock-free: a test should not depend
|
|
/// on the wall-clock hour it happens to run at.
|
|
Future<void> checkAndNotify({DateTime? now}) async {
|
|
await LogStorageService.init();
|
|
await NotificationService.init();
|
|
final logged = await LogStorageService.instance.loggedSlotsToday();
|
|
final due = missingSlots(now ?? DateTime.now(), logged);
|
|
await NotificationService.instance.syncToSlots(due);
|
|
}
|
|
|
|
/// WorkManager entry point invoked by the OS on each periodic tick.
|
|
///
|
|
/// Deliberately thin: all logic lives in [checkAndNotify] so it stays unit
|
|
/// testable. This dispatcher itself is integration-only -- manually
|
|
/// smoke-tested on-device (see the project plan's verification section),
|
|
/// not chased for unit coverage.
|
|
// coverage:ignore-start
|
|
@pragma('vm:entry-point')
|
|
void backgroundCheckCallbackDispatcher() {
|
|
Workmanager().executeTask((taskName, inputData) async {
|
|
if (taskName == backgroundCheckTaskName) {
|
|
await checkAndNotify();
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
// coverage:ignore-end
|