mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-11 13:27:39 +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
70 lines
2.6 KiB
Dart
70 lines
2.6 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
/// The channel id `LocalNotificationsBackend` posts reminders on -- and the
|
|
/// one older, hour-keyed builds used too.
|
|
const reminderChannelId = 'diet_guard_due_slot';
|
|
|
|
/// Mocks the raw `dexterous.com/flutter/local_notifications` MethodChannel
|
|
/// the way the package's own test suite does
|
|
/// (`android_flutter_local_notifications_test.dart`), so
|
|
/// [NotificationService] can be exercised end-to-end (init/show/cancel)
|
|
/// without a real Android plugin.
|
|
///
|
|
/// Stateful, like the real notification shade: `show` adds an id, `cancel`
|
|
/// removes it, and `getActiveNotifications` reports what is left. Without
|
|
/// that, the active-id lookup `syncToSlots` relies on would always see an
|
|
/// empty shade and every orphan test would pass vacuously. Seed [active]
|
|
/// (id -> channel id) to simulate reminders a previous run or an older build
|
|
/// left behind.
|
|
///
|
|
/// Returns the call log so a test can assert which slots were shown vs.
|
|
/// cancelled.
|
|
List<MethodCall> installFakeAndroidNotifications({
|
|
Map<int, String>? active,
|
|
}) {
|
|
AndroidFlutterLocalNotificationsPlugin.registerWith();
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
|
const channel = MethodChannel('dexterous.com/flutter/local_notifications');
|
|
final log = <MethodCall>[];
|
|
final shade = active ?? <int, String>{};
|
|
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
|
.setMockMethodCallHandler(channel, (call) async {
|
|
log.add(call);
|
|
final args = call.arguments;
|
|
switch (call.method) {
|
|
case 'initialize':
|
|
return true;
|
|
case 'requestNotificationsPermission':
|
|
return true;
|
|
case 'show':
|
|
final map = args as Map;
|
|
final details = map['platformSpecifics'] as Map?;
|
|
shade[map['id'] as int] =
|
|
details?['channelId'] as String? ?? reminderChannelId;
|
|
return null;
|
|
case 'cancel':
|
|
shade.remove((args as Map)['id']);
|
|
return null;
|
|
case 'getActiveNotifications':
|
|
return [
|
|
for (final entry in shade.entries)
|
|
{'id': entry.key, 'channelId': entry.value},
|
|
];
|
|
default:
|
|
return null;
|
|
}
|
|
});
|
|
|
|
addTearDown(() {
|
|
debugDefaultTargetPlatformOverride = null;
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
|
.setMockMethodCallHandler(channel, null);
|
|
});
|
|
|
|
return log;
|
|
}
|