153 lines
4.4 KiB
Dart
153 lines
4.4 KiB
Dart
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);
|
|
}
|