import 'dart:async'; import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../domain/models/reminder_payload.dart'; typedef ReminderNotificationResponseHandler = Future Function(NotificationResponse response); class ReminderNotificationCallbacks { static const String _pendingKey = 'calendar_reminder_pending_notification_responses_v1'; static ReminderNotificationResponseHandler? _responseHandler; static Future _pendingStorageLock = Future.value(); static void Function(ReminderPayload)? onNotificationPayloadReceived; @visibleForTesting static Future resetForTest() async { _responseHandler = null; _pendingStorageLock = Future.value(); final prefs = await SharedPreferences.getInstance(); await prefs.remove(_pendingKey); } static Future bindResponseHandler( ReminderNotificationResponseHandler handler, ) async { _responseHandler = handler; await _drainPendingResponses(); } static Future onForegroundResponse( NotificationResponse response, ) async { final handler = _responseHandler; if (handler == null) { await _enqueuePendingResponse(response); return; } try { await handler(response); } catch (_) { await _enqueuePendingResponse(response); } } static Future onBackgroundResponse( NotificationResponse response, ) async { final handler = _responseHandler; if (handler == null) { await _enqueuePendingResponse(response); return; } try { await handler(response); } catch (_) { await _enqueuePendingResponse(response); } } static Future _withPendingStorageLock(Future Function() operation) { final completer = Completer(); final waitForTurn = _pendingStorageLock; _pendingStorageLock = waitForTurn.then((_) => completer.future); return waitForTurn.then((_) => operation()).whenComplete(() { if (!completer.isCompleted) { completer.complete(); } }); } static Future _enqueuePendingResponse( NotificationResponse response, ) async { await _withPendingStorageLock(() async { final prefs = await SharedPreferences.getInstance(); final current = prefs.getStringList(_pendingKey) ?? const []; final encoded = jsonEncode({ 'id': response.id, 'actionId': response.actionId, 'payload': response.payload, 'type': response.notificationResponseType.index, 'input': response.input, }); await prefs.setStringList(_pendingKey, [...current, encoded]); }); } static Future _drainPendingResponses() async { final handler = _responseHandler; if (handler == null) { return; } await _withPendingStorageLock(() async { final prefs = await SharedPreferences.getInstance(); final pending = prefs.getStringList(_pendingKey) ?? const []; if (pending.isEmpty) { return; } final remaining = []; for (final raw in pending) { Map parsed; try { parsed = Map.from(jsonDecode(raw) as Map); } catch (_) { continue; } final id = parsed['id'] as int?; final actionId = parsed['actionId'] as String?; final payload = parsed['payload'] as String?; final typeIndex = (parsed['type'] as int?) ?? 0; final input = parsed['input'] as String?; final type = NotificationResponseType.values[typeIndex.clamp(0, 1)]; try { await handler( NotificationResponse( id: id, actionId: actionId, payload: payload, input: input, notificationResponseType: type, ), ); } catch (_) { remaining.add(raw); } } if (remaining.isEmpty) { await prefs.remove(_pendingKey); return; } await prefs.setStringList(_pendingKey, remaining); }); } } @pragma('vm:entry-point') Future reminderNotificationTapBackground( NotificationResponse response, ) async { await ReminderNotificationCallbacks.onBackgroundResponse(response); }