mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-11 13:47:38 +01:00
- FoodEntry.slot -> slotMinute: read via entrySlotMinute, written via slotFields (whole-hour stays byte-identical, else slot_min), kept by copyWithDeleted and the edit screen, so the phone never republishes a stripped copy of a PC entry. Out-of-range peer values echo, never throw. - Satisfaction is nearest-slot snapping (satisfiedSlots) everywhere: loggedSlotsToday, due check, kuchnia occupancy, future-day logged set (that day's schedule), slot selector. Schedule edits no longer un-log. - Future pre-log builds DateTime from slot ~/ 60, slot % 60. - Schedule history + sync go through scheduleToWire/scheduleFromWire. - Notifications: ids are slot minutes; syncToSlots cancels whatever the platform reports active (getActiveNotifications, reminder channel) that is not due, covering legacy hour ids; full 0..1439 sweep only if the lookup throws. - Settings: 15-minute dropdowns + "Custom..." (24h showTimePicker), off-grid values always selectable, counts capped by grid capacity; editor moved to widgets/meal_schedule_editor.dart + meal_time_dropdown.dart. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015npsYVB2PdtWkG7MPAi4tn
85 lines
3.2 KiB
Dart
85 lines
3.2 KiB
Dart
/// `flutter_local_notifications`-backed [NotificationBackend] (Android).
|
|
library;
|
|
|
|
import 'package:diet_guard_app/services/notification_backend.dart';
|
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
|
|
/// Wraps [FlutterLocalNotificationsPlugin] so the due-slot logic in
|
|
/// `notification_service.dart` is unit-testable against a fake platform
|
|
/// channel, independent of the real plugin's native implementation.
|
|
class LocalNotificationsBackend implements NotificationBackend {
|
|
/// Creates a backend over [plugin].
|
|
LocalNotificationsBackend(this.plugin);
|
|
|
|
/// The wrapped plugin. Injectable so a test can point it at a fake channel.
|
|
final FlutterLocalNotificationsPlugin plugin;
|
|
|
|
static const _channelId = 'diet_guard_due_slot';
|
|
static const _channelName = 'Meal reminders';
|
|
|
|
@override
|
|
Future<void> initialize() async {
|
|
const settings = InitializationSettings(
|
|
android: AndroidInitializationSettings('@mipmap/ic_launcher'),
|
|
);
|
|
await plugin.initialize(settings: settings);
|
|
}
|
|
|
|
/// Requests Android 13+'s runtime `POST_NOTIFICATIONS` permission.
|
|
///
|
|
/// Returns null on platforms where this Android-specific call doesn't
|
|
/// apply -- the caller treats null and false the same way (don't block on
|
|
/// it; notifications degrade silently if denied, matching the rest of this
|
|
/// service's silent-on-failure stance). Since the desktop target became a
|
|
/// web build, `android/` is this backend's only real platform, so in
|
|
/// production the non-null path always runs; the fallback is not reachable
|
|
/// from `flutter test` without polluting the process-global plugin
|
|
/// registration other tests in this file rely on, so it's excluded from
|
|
/// coverage rather than chased with a fragile test-ordering trick.
|
|
@override
|
|
Future<bool?> requestPermission() =>
|
|
plugin
|
|
.resolvePlatformSpecificImplementation<
|
|
AndroidFlutterLocalNotificationsPlugin
|
|
>()
|
|
?.requestNotificationsPermission() ??
|
|
// coverage:ignore-line
|
|
Future.value();
|
|
|
|
@override
|
|
Future<void> show(int slot, String title, String body) async {
|
|
const details = NotificationDetails(
|
|
android: AndroidNotificationDetails(
|
|
_channelId,
|
|
_channelName,
|
|
importance: Importance.high,
|
|
priority: Priority.high,
|
|
),
|
|
);
|
|
await plugin.show(
|
|
id: slot,
|
|
title: title,
|
|
body: body,
|
|
notificationDetails: details,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> cancel(int slot) => plugin.cancel(id: slot);
|
|
|
|
/// Asks Android which notifications this app has in the shade (API 23+;
|
|
/// minSdk is 26), restricted to the reminder channel so nothing else the
|
|
/// app might ever post is swept up. Older builds posted on this same
|
|
/// channel id, so their hour-keyed reminders are included -- which is what
|
|
/// makes the upgrade orphan-free.
|
|
@override
|
|
Future<Set<int>> activeIds() async => {
|
|
for (final active in await plugin.getActiveNotifications())
|
|
if (active.channelId == _channelId && active.id != null) active.id!,
|
|
};
|
|
}
|
|
|
|
/// Opens the platform notification backend.
|
|
NotificationBackend openNotificationBackend() =>
|
|
LocalNotificationsBackend(FlutterLocalNotificationsPlugin());
|