- Daily reminder scheduling via flutter_local_notifications (inexact, repeating at chosen time) - Biometric lock gate on app launch with auto-prompt and manual unlock button - Theme mode selector (System/Light/Dark) persisted to settings DB - PDF export of full mood history shared via share_plus - Android: POST_NOTIFICATIONS, USE_BIOMETRIC permissions; switched to FlutterFragmentActivity
67 lines
2.3 KiB
Dart
67 lines
2.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
import 'package:timezone/timezone.dart' as tz;
|
|
|
|
class NotificationService {
|
|
static final _plugin = FlutterLocalNotificationsPlugin();
|
|
static bool _initialised = false;
|
|
|
|
static const _channelId = 'daily_reminder';
|
|
static const _notificationId = 0;
|
|
|
|
static Future<void> init() async {
|
|
if (_initialised) return;
|
|
const android = AndroidInitializationSettings('@mipmap/ic_launcher');
|
|
await _plugin.initialize(
|
|
settings: const InitializationSettings(android: android),
|
|
);
|
|
_initialised = true;
|
|
}
|
|
|
|
static Future<bool> requestPermission() async {
|
|
final android = _plugin.resolvePlatformSpecificImplementation<
|
|
AndroidFlutterLocalNotificationsPlugin>();
|
|
return (await android?.requestNotificationsPermission()) ?? false;
|
|
}
|
|
|
|
static Future<void> scheduleDaily(TimeOfDay time) async {
|
|
await _plugin.cancelAll();
|
|
const details = NotificationDetails(
|
|
android: AndroidNotificationDetails(
|
|
_channelId,
|
|
'Daily reminder',
|
|
channelDescription: 'Reminds you to log your mood',
|
|
importance: Importance.defaultImportance,
|
|
priority: Priority.defaultPriority,
|
|
),
|
|
);
|
|
await _plugin.zonedSchedule(
|
|
id: _notificationId,
|
|
title: 'DailyYou',
|
|
body: 'Time to log your mood!',
|
|
scheduledDate: _nextInstanceOfTime(time.hour, time.minute),
|
|
notificationDetails: details,
|
|
androidScheduleMode: AndroidScheduleMode.inexactAllowWhileIdle,
|
|
matchDateTimeComponents: DateTimeComponents.time,
|
|
);
|
|
}
|
|
|
|
static Future<void> cancelAll() => _plugin.cancelAll();
|
|
|
|
static tz.TZDateTime _nextInstanceOfTime(int hour, int minute) {
|
|
final now = tz.TZDateTime.now(tz.UTC);
|
|
final offset = DateTime.now().timeZoneOffset;
|
|
var targetMinutes = hour * 60 + minute - offset.inMinutes;
|
|
// Normalise into [0, 24*60)
|
|
targetMinutes = ((targetMinutes % (24 * 60)) + 24 * 60) % (24 * 60);
|
|
final utcHour = targetMinutes ~/ 60;
|
|
final utcMin = targetMinutes % 60;
|
|
var scheduled =
|
|
tz.TZDateTime(tz.UTC, now.year, now.month, now.day, utcHour, utcMin);
|
|
if (scheduled.isBefore(now)) {
|
|
scheduled = scheduled.add(const Duration(days: 1));
|
|
}
|
|
return scheduled;
|
|
}
|
|
}
|