feat: 重构会话管理与提醒通知系统
This commit is contained in:
@@ -12,10 +12,12 @@ class ReminderSchedulerService {
|
||||
ReminderSchedulerService({FlutterLocalNotificationsPlugin? plugin})
|
||||
: _plugin = plugin ?? FlutterLocalNotificationsPlugin();
|
||||
|
||||
static const String _channelId = 'calendar_reminder_alarm_v2';
|
||||
static const String _channelId = 'calendar_reminder_alarm_v3';
|
||||
static const String _channelName = 'Schedule alarm';
|
||||
static const String _channelDescription =
|
||||
'Alarm-style notifications for scheduled events';
|
||||
static const String _androidSoundResource = 'reminder_1';
|
||||
static const String _iosSoundFile = 'reminder_1.wav';
|
||||
|
||||
final FlutterLocalNotificationsPlugin _plugin;
|
||||
final List<void Function(ReminderNotificationTap tap)> _tapCallbacks = [];
|
||||
@@ -45,6 +47,8 @@ class ReminderSchedulerService {
|
||||
_channelName,
|
||||
description: _channelDescription,
|
||||
importance: Importance.max,
|
||||
playSound: true,
|
||||
sound: RawResourceAndroidNotificationSound(_androidSoundResource),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -120,9 +124,18 @@ class ReminderSchedulerService {
|
||||
DateTime? now,
|
||||
}) async {
|
||||
await _ensureInitialized();
|
||||
await cancelEventReminders(event.eventId);
|
||||
final alarms = buildAlarmsForEvent(event, now: now);
|
||||
for (final alarm in alarms) {
|
||||
final pending = await _plugin.pendingNotificationRequests();
|
||||
final plan = buildUpsertPlan(
|
||||
eventId: event.eventId,
|
||||
pending: pending,
|
||||
desired: alarms,
|
||||
);
|
||||
|
||||
for (final id in plan.idsToCancel) {
|
||||
await _plugin.cancel(id);
|
||||
}
|
||||
for (final alarm in plan.alarmsToSchedule) {
|
||||
await _scheduleAlarm(alarm);
|
||||
}
|
||||
}
|
||||
@@ -252,6 +265,7 @@ class ReminderSchedulerService {
|
||||
category: AndroidNotificationCategory.alarm,
|
||||
timeoutAfter: 15000,
|
||||
playSound: true,
|
||||
sound: RawResourceAndroidNotificationSound(_androidSoundResource),
|
||||
enableVibration: true,
|
||||
fullScreenIntent: false,
|
||||
ticker: 'calendar-reminder',
|
||||
@@ -259,6 +273,7 @@ class ReminderSchedulerService {
|
||||
const iosDetails = DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentSound: true,
|
||||
sound: _iosSoundFile,
|
||||
interruptionLevel: InterruptionLevel.timeSensitive,
|
||||
);
|
||||
|
||||
@@ -344,6 +359,77 @@ class ReminderSchedulerService {
|
||||
return hash;
|
||||
}
|
||||
|
||||
static int notificationIdFor(String eventId, int fireTimeBucket) {
|
||||
return _notificationId(eventId, fireTimeBucket);
|
||||
}
|
||||
|
||||
static ReminderUpsertPlan buildUpsertPlan({
|
||||
required String eventId,
|
||||
required List<PendingNotificationRequest> pending,
|
||||
required List<ReminderAlarm> desired,
|
||||
}) {
|
||||
final desiredById = <int, ReminderAlarm>{
|
||||
for (final alarm in desired)
|
||||
_notificationId(alarm.eventId, alarm.fireTimeBucket): alarm,
|
||||
};
|
||||
|
||||
final existingPayloadById = <int, Map<String, dynamic>>{};
|
||||
for (final request in pending) {
|
||||
final raw = request.payload;
|
||||
if (raw == null || raw.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
final payload = _parsePayload(raw);
|
||||
if (payload['eventId'] != eventId) {
|
||||
continue;
|
||||
}
|
||||
existingPayloadById[request.id] = payload;
|
||||
}
|
||||
|
||||
final idsToCancel = <int>[];
|
||||
for (final entry in existingPayloadById.entries) {
|
||||
final desiredAlarm = desiredById[entry.key];
|
||||
if (desiredAlarm == null) {
|
||||
idsToCancel.add(entry.key);
|
||||
continue;
|
||||
}
|
||||
if (!_matchesAlarmPayload(entry.value, desiredAlarm)) {
|
||||
idsToCancel.add(entry.key);
|
||||
}
|
||||
}
|
||||
|
||||
final alarmsToSchedule = <ReminderAlarm>[];
|
||||
for (final entry in desiredById.entries) {
|
||||
final existingPayload = existingPayloadById[entry.key];
|
||||
if (existingPayload == null ||
|
||||
!_matchesAlarmPayload(existingPayload, entry.value)) {
|
||||
alarmsToSchedule.add(entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
return ReminderUpsertPlan(
|
||||
idsToCancel: idsToCancel,
|
||||
alarmsToSchedule: alarmsToSchedule,
|
||||
);
|
||||
}
|
||||
|
||||
static bool _matchesAlarmPayload(
|
||||
Map<String, dynamic> payload,
|
||||
ReminderAlarm alarm,
|
||||
) {
|
||||
return payload['eventId'] == alarm.eventId &&
|
||||
payload['title'] == alarm.title &&
|
||||
payload['startAt'] == alarm.startAt.toIso8601String() &&
|
||||
payload['endAt'] == alarm.endAt?.toIso8601String() &&
|
||||
payload['timezone'] == alarm.timezone &&
|
||||
payload['reminderMinutes'] == alarm.reminderMinutes &&
|
||||
payload['fireAt'] == alarm.fireAt.toIso8601String() &&
|
||||
payload['fireTimeBucket'] == alarm.fireTimeBucket &&
|
||||
payload['location'] == alarm.location &&
|
||||
payload['notes'] == alarm.notes &&
|
||||
payload['version'] == alarm.version;
|
||||
}
|
||||
|
||||
static tz.Location _safeLocation(String timezone) {
|
||||
try {
|
||||
return tz.getLocation(timezone);
|
||||
@@ -353,6 +439,16 @@ class ReminderSchedulerService {
|
||||
}
|
||||
}
|
||||
|
||||
class ReminderUpsertPlan {
|
||||
const ReminderUpsertPlan({
|
||||
required this.idsToCancel,
|
||||
required this.alarmsToSchedule,
|
||||
});
|
||||
|
||||
final List<int> idsToCancel;
|
||||
final List<ReminderAlarm> alarmsToSchedule;
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void _onBackgroundTap(NotificationResponse response) {
|
||||
debugPrint('Background reminder tap received: ${response.payload}');
|
||||
|
||||
Reference in New Issue
Block a user