feat(apps): 重构 UI 架构为 presentation 层并新增 l10n 国际化支持
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user