fix(apps): 修复通知点击不显示ReminderOverlay和日历编辑后不刷新问题
- AppDelegate: 只存储payload字段而非整个userInfo字典 - LocalNotificationService: 移除旧的取消/稍后提醒action按钮配置 - ReminderNotificationCallbacks: 添加onNotificationPayloadReceived静态回调 - IOSNotificationPayloadBridge: 添加setPendingPayload方法 - main.dart: 设置onNotificationPayloadReceived触发ReminderOverlay显示,添加WidgetsBindingObserver处理后台恢复 - CalendarEventDetailScreen: 编辑保存后正确传递刷新信号给日视图
This commit is contained in:
@@ -25,9 +25,8 @@ import UserNotifications
|
|||||||
withCompletionHandler completionHandler: @escaping () -> Void
|
withCompletionHandler completionHandler: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
let userInfo = response.notification.request.content.userInfo
|
let userInfo = response.notification.request.content.userInfo
|
||||||
if let jsonData = try? JSONSerialization.data(withJSONObject: userInfo, options: []),
|
if let payloadString = userInfo["payload"] as? String {
|
||||||
let jsonString = String(data: jsonData, encoding: .utf8) {
|
UserDefaults.standard.set(payloadString, forKey: "pending_notification_payload")
|
||||||
UserDefaults.standard.set(jsonString, forKey: "pending_notification_payload")
|
|
||||||
}
|
}
|
||||||
completionHandler()
|
completionHandler()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ class IOSNotificationPayloadBridge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> setPendingPayload(ReminderPayload payload) async {
|
||||||
|
await _prefs.setString(_key, jsonEncode(payload.toJson()));
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> clearPendingPayload() async {
|
Future<void> clearPendingPayload() async {
|
||||||
await _prefs.remove(_key);
|
await _prefs.remove(_key);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,31 +7,15 @@ import 'package:timezone/timezone.dart' as tz;
|
|||||||
|
|
||||||
import 'reminder_notification_callbacks.dart';
|
import 'reminder_notification_callbacks.dart';
|
||||||
import '../../features/calendar/data/models/schedule_item_model.dart';
|
import '../../features/calendar/data/models/schedule_item_model.dart';
|
||||||
import '../../features/calendar/reminders/models/reminder_action.dart';
|
|
||||||
import '../../features/calendar/reminders/models/reminder_payload.dart';
|
import '../../features/calendar/reminders/models/reminder_payload.dart';
|
||||||
|
|
||||||
typedef ReminderNotificationActionHandler =
|
|
||||||
Future<void> Function({
|
|
||||||
required ReminderAction action,
|
|
||||||
required ReminderPayload payload,
|
|
||||||
});
|
|
||||||
|
|
||||||
class LocalNotificationService {
|
class LocalNotificationService {
|
||||||
static const String _iosCategoryId = 'calendar_reminder_v2';
|
|
||||||
static const String _actionCancel = 'cancel';
|
|
||||||
static const String _actionSnooze = 'snooze10m';
|
|
||||||
|
|
||||||
final FlutterLocalNotificationsPlugin _plugin;
|
final FlutterLocalNotificationsPlugin _plugin;
|
||||||
bool _initialized = false;
|
bool _initialized = false;
|
||||||
ReminderNotificationActionHandler? _actionHandler;
|
|
||||||
|
|
||||||
LocalNotificationService({FlutterLocalNotificationsPlugin? plugin})
|
LocalNotificationService({FlutterLocalNotificationsPlugin? plugin})
|
||||||
: _plugin = plugin ?? FlutterLocalNotificationsPlugin();
|
: _plugin = plugin ?? FlutterLocalNotificationsPlugin();
|
||||||
|
|
||||||
void bindActionHandler(ReminderNotificationActionHandler handler) {
|
|
||||||
_actionHandler = handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
if (_initialized) {
|
if (_initialized) {
|
||||||
return;
|
return;
|
||||||
@@ -39,25 +23,10 @@ class LocalNotificationService {
|
|||||||
tz_data.initializeTimeZones();
|
tz_data.initializeTimeZones();
|
||||||
|
|
||||||
const android = AndroidInitializationSettings('@mipmap/ic_launcher');
|
const android = AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||||
final ios = DarwinInitializationSettings(
|
const ios = DarwinInitializationSettings(
|
||||||
requestAlertPermission: false,
|
requestAlertPermission: false,
|
||||||
requestBadgePermission: false,
|
requestBadgePermission: false,
|
||||||
requestSoundPermission: false,
|
requestSoundPermission: false,
|
||||||
notificationCategories: [
|
|
||||||
DarwinNotificationCategory(
|
|
||||||
_iosCategoryId,
|
|
||||||
actions: <DarwinNotificationAction>[
|
|
||||||
DarwinNotificationAction.plain(_actionCancel, '取消'),
|
|
||||||
DarwinNotificationAction.plain(
|
|
||||||
_actionSnooze,
|
|
||||||
'稍后提醒',
|
|
||||||
options: <DarwinNotificationActionOption>{
|
|
||||||
DarwinNotificationActionOption.foreground,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
final settings = InitializationSettings(android: android, iOS: ios);
|
final settings = InitializationSettings(android: android, iOS: ios);
|
||||||
|
|
||||||
@@ -190,20 +159,11 @@ class LocalNotificationService {
|
|||||||
timeoutAfter: 30000,
|
timeoutAfter: 30000,
|
||||||
autoCancel: true,
|
autoCancel: true,
|
||||||
groupKey: _getGroupKey(fireAt),
|
groupKey: _getGroupKey(fireAt),
|
||||||
actions: <AndroidNotificationAction>[
|
|
||||||
AndroidNotificationAction(_actionCancel, '取消'),
|
|
||||||
AndroidNotificationAction(
|
|
||||||
_actionSnooze,
|
|
||||||
'稍后提醒',
|
|
||||||
showsUserInterface: true,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
iOS: DarwinNotificationDetails(
|
iOS: DarwinNotificationDetails(
|
||||||
presentAlert: true,
|
presentAlert: true,
|
||||||
presentSound: true,
|
presentSound: true,
|
||||||
presentBadge: true,
|
presentBadge: true,
|
||||||
categoryIdentifier: _iosCategoryId,
|
|
||||||
threadIdentifier: _getThreadIdentifier(fireAt),
|
threadIdentifier: _getThreadIdentifier(fireAt),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -341,23 +301,6 @@ class LocalNotificationService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final actionId = response.actionId;
|
ReminderNotificationCallbacks.onNotificationPayloadReceived?.call(payload);
|
||||||
ReminderAction? action;
|
|
||||||
if (actionId == _actionCancel) {
|
|
||||||
action = ReminderAction.archive;
|
|
||||||
} else if (actionId == _actionSnooze) {
|
|
||||||
action = ReminderAction.snooze10m;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final handler = _actionHandler;
|
|
||||||
if (handler == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await handler(action: action, payload: payload);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../../features/calendar/reminders/models/reminder_payload.dart';
|
||||||
|
|
||||||
typedef ReminderNotificationResponseHandler =
|
typedef ReminderNotificationResponseHandler =
|
||||||
Future<void> Function(NotificationResponse response);
|
Future<void> Function(NotificationResponse response);
|
||||||
|
|
||||||
@@ -13,6 +15,7 @@ class ReminderNotificationCallbacks {
|
|||||||
'calendar_reminder_pending_notification_responses_v1';
|
'calendar_reminder_pending_notification_responses_v1';
|
||||||
static ReminderNotificationResponseHandler? _responseHandler;
|
static ReminderNotificationResponseHandler? _responseHandler;
|
||||||
static Future<void> _pendingStorageLock = Future<void>.value();
|
static Future<void> _pendingStorageLock = Future<void>.value();
|
||||||
|
static void Function(ReminderPayload)? onNotificationPayloadReceived;
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
static Future<void> resetForTest() async {
|
static Future<void> resetForTest() async {
|
||||||
|
|||||||
@@ -218,15 +218,19 @@ class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
|
|||||||
return event.startAt.isBefore(now);
|
return event.startAt.isBefore(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleHeaderAction(
|
Future<void> _handleHeaderAction(
|
||||||
_CalendarHeaderAction action,
|
_CalendarHeaderAction action,
|
||||||
ScheduleItemModel event,
|
ScheduleItemModel event,
|
||||||
) {
|
) async {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case _CalendarHeaderAction.edit:
|
case _CalendarHeaderAction.edit:
|
||||||
context.push(AppRoutes.calendarEventEdit(event.id)).then((_) {
|
final changed = await context.push<bool>(
|
||||||
|
AppRoutes.calendarEventEdit(event.id),
|
||||||
|
);
|
||||||
|
if (changed == true) {
|
||||||
_loadEvent();
|
_loadEvent();
|
||||||
});
|
if (mounted) context.pop(true);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
case _CalendarHeaderAction.delete:
|
case _CalendarHeaderAction.delete:
|
||||||
_showDeleteConfirmation();
|
_showDeleteConfirmation();
|
||||||
|
|||||||
+43
-12
@@ -18,7 +18,6 @@ import 'features/auth/presentation/bloc/auth_event.dart';
|
|||||||
import 'features/auth/presentation/bloc/auth_state.dart';
|
import 'features/auth/presentation/bloc/auth_state.dart';
|
||||||
import 'features/calendar/data/services/calendar_service.dart';
|
import 'features/calendar/data/services/calendar_service.dart';
|
||||||
import 'features/calendar/data/services/calendar_repository.dart';
|
import 'features/calendar/data/services/calendar_repository.dart';
|
||||||
import 'features/calendar/reminders/reminder_action_executor.dart';
|
|
||||||
import 'features/calendar/reminders/reminder_queue_manager.dart';
|
import 'features/calendar/reminders/reminder_queue_manager.dart';
|
||||||
import 'features/calendar/reminders/ui/reminder_overlay.dart';
|
import 'features/calendar/reminders/ui/reminder_overlay.dart';
|
||||||
import 'features/calendar/ui/calendar_state_manager.dart';
|
import 'features/calendar/ui/calendar_state_manager.dart';
|
||||||
@@ -32,15 +31,6 @@ void main() async {
|
|||||||
await AppConstants.init();
|
await AppConstants.init();
|
||||||
|
|
||||||
final rootNavigatorKey = GlobalKey<NavigatorState>();
|
final rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
sl<LocalNotificationService>().bindActionHandler(({
|
|
||||||
required action,
|
|
||||||
required payload,
|
|
||||||
}) {
|
|
||||||
return sl<ReminderActionExecutor>().handleAction(
|
|
||||||
action: action,
|
|
||||||
payload: payload,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await sl<LocalNotificationService>().initialize();
|
await sl<LocalNotificationService>().initialize();
|
||||||
|
|
||||||
final authBloc = sl<AuthBloc>();
|
final authBloc = sl<AuthBloc>();
|
||||||
@@ -74,8 +64,23 @@ void main() async {
|
|||||||
await payloadBridge.clearPendingPayload();
|
await payloadBridge.clearPendingPayload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final linksyAppKey = GlobalKey();
|
||||||
|
|
||||||
|
ReminderNotificationCallbacks.onNotificationPayloadReceived =
|
||||||
|
(payload) async {
|
||||||
|
await payloadBridge.setPendingPayload(payload);
|
||||||
|
queueManager.enqueueFromClick(payload);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
final state = linksyAppKey.currentState;
|
||||||
|
if (state != null && state is _LinksyAppState) {
|
||||||
|
state.showReminderOverlayFromNotification();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
LinksyApp(
|
LinksyApp(
|
||||||
|
key: linksyAppKey,
|
||||||
authBloc: authBloc,
|
authBloc: authBloc,
|
||||||
rootNavigatorKey: rootNavigatorKey,
|
rootNavigatorKey: rootNavigatorKey,
|
||||||
sessionBootstrapper: AuthSessionBootstrapper(
|
sessionBootstrapper: AuthSessionBootstrapper(
|
||||||
@@ -116,18 +121,39 @@ class LinksyApp extends StatefulWidget {
|
|||||||
State<LinksyApp> createState() => _LinksyAppState();
|
State<LinksyApp> createState() => _LinksyAppState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LinksyAppState extends State<LinksyApp> {
|
class _LinksyAppState extends State<LinksyApp> with WidgetsBindingObserver {
|
||||||
OverlayEntry? _reminderOverlay;
|
OverlayEntry? _reminderOverlay;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
_checkAndShowReminderOverlay();
|
_checkAndShowReminderOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (state == AppLifecycleState.resumed) {
|
||||||
|
_checkAndShowReminderOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _checkAndShowReminderOverlay() async {
|
Future<void> _checkAndShowReminderOverlay() async {
|
||||||
if (widget.queueManager.currentPayload != null) {
|
if (widget.queueManager.currentPayload != null) {
|
||||||
_showReminderOverlay();
|
_showReminderOverlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final pendingPayload = await widget.payloadBridge.getPendingPayload();
|
||||||
|
if (pendingPayload != null) {
|
||||||
|
widget.queueManager.enqueueFromClick(pendingPayload);
|
||||||
|
await widget.payloadBridge.clearPendingPayload();
|
||||||
|
_showReminderOverlay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,10 +173,15 @@ class _LinksyAppState extends State<LinksyApp> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Overlay.of(context).insert(_reminderOverlay!);
|
Overlay.of(context).insert(_reminderOverlay!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showReminderOverlayFromNotification() {
|
||||||
|
if (widget.queueManager.currentPayload != null) {
|
||||||
|
_showReminderOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onReminderComplete() {
|
void _onReminderComplete() {
|
||||||
_reminderOverlay?.remove();
|
_reminderOverlay?.remove();
|
||||||
_reminderOverlay = null;
|
_reminderOverlay = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user