feat: implement settings screen with reminders, biometrics, theme, and PDF export
- 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
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user