Files
social-app/apps/lib/features/notification/data/services/reminder_notification_callbacks.dart
T

153 lines
4.4 KiB
Dart
Raw Normal View History

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<void> Function(NotificationResponse response);
class ReminderNotificationCallbacks {
static const String _pendingKey =
'calendar_reminder_pending_notification_responses_v1';
static ReminderNotificationResponseHandler? _responseHandler;
static Future<void> _pendingStorageLock = Future<void>.value();
static void Function(ReminderPayload)? onNotificationPayloadReceived;
@visibleForTesting
static Future<void> resetForTest() async {
_responseHandler = null;
_pendingStorageLock = Future<void>.value();
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_pendingKey);
}
static Future<void> bindResponseHandler(
ReminderNotificationResponseHandler handler,
) async {
_responseHandler = handler;
await _drainPendingResponses();
}
static Future<void> onForegroundResponse(
NotificationResponse response,
) async {
final handler = _responseHandler;
if (handler == null) {
await _enqueuePendingResponse(response);
return;
}
try {
await handler(response);
} catch (_) {
await _enqueuePendingResponse(response);
}
}
static Future<void> onBackgroundResponse(
NotificationResponse response,
) async {
final handler = _responseHandler;
if (handler == null) {
await _enqueuePendingResponse(response);
return;
}
try {
await handler(response);
} catch (_) {
await _enqueuePendingResponse(response);
}
}
static Future<T> _withPendingStorageLock<T>(Future<T> Function() operation) {
final completer = Completer<void>();
final waitForTurn = _pendingStorageLock;
_pendingStorageLock = waitForTurn.then((_) => completer.future);
return waitForTurn.then((_) => operation()).whenComplete(() {
if (!completer.isCompleted) {
completer.complete();
}
});
}
static Future<void> _enqueuePendingResponse(
NotificationResponse response,
) async {
await _withPendingStorageLock(() async {
final prefs = await SharedPreferences.getInstance();
final current = prefs.getStringList(_pendingKey) ?? const <String>[];
final encoded = jsonEncode({
'id': response.id,
'actionId': response.actionId,
'payload': response.payload,
'type': response.notificationResponseType.index,
'input': response.input,
});
await prefs.setStringList(_pendingKey, <String>[...current, encoded]);
});
}
static Future<void> _drainPendingResponses() async {
final handler = _responseHandler;
if (handler == null) {
return;
}
await _withPendingStorageLock(() async {
final prefs = await SharedPreferences.getInstance();
final pending = prefs.getStringList(_pendingKey) ?? const <String>[];
if (pending.isEmpty) {
return;
}
final remaining = <String>[];
for (final raw in pending) {
Map<String, dynamic> parsed;
try {
parsed = Map<String, dynamic>.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<void> reminderNotificationTapBackground(
NotificationResponse response,
) async {
await ReminderNotificationCallbacks.onBackgroundResponse(response);
}