feat: 实现日历提醒 in-app fallback 机制及通知服务重构

This commit is contained in:
zl-q
2026-03-20 01:30:34 +08:00
parent 7fd536e976
commit d574128815
55 changed files with 4565 additions and 647 deletions
@@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import '../../../../core/theme/design_tokens.dart';
import '../models/reminder_action.dart';
import '../models/reminder_payload.dart';
import '../reminder_action_executor.dart';
import 'reminder_presentation_coordinator.dart';
import 'widgets/reminder_action_sheet.dart';
class ReminderForegroundPresenter {
final GlobalKey<NavigatorState> _navigatorKey;
final ReminderActionExecutor _executor;
final ReminderPresentationCoordinator _coordinator;
bool _isPresenting = false;
ReminderForegroundPresenter({
required GlobalKey<NavigatorState> navigatorKey,
required ReminderActionExecutor executor,
ReminderPresentationCoordinator? coordinator,
}) : _navigatorKey = navigatorKey,
_executor = executor,
_coordinator = coordinator ?? ReminderPresentationCoordinator();
Future<void> present(ReminderPayload payload) async {
final context = _navigatorKey.currentContext;
if (context == null) {
return;
}
final lifecycleState = WidgetsBinding.instance.lifecycleState;
final isAppActive = lifecycleState == AppLifecycleState.resumed;
final shouldPresent = _coordinator.shouldPresent(
eventId: payload.eventId,
isAppActive: isAppActive,
);
if (!shouldPresent || _isPresenting) {
return;
}
_isPresenting = true;
try {
final action = await showModalBottomSheet<ReminderAction>(
context: context,
useRootNavigator: true,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (sheetContext) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.all(AppSpacing.md),
child: ReminderActionSheet(
onSnooze: () {
Navigator.of(sheetContext).pop(ReminderAction.snooze10m);
},
onArchive: () {
Navigator.of(sheetContext).pop(ReminderAction.archive);
},
),
),
);
},
);
if (action == null) {
return;
}
await _executor.handleAction(action: action, payload: payload);
} finally {
_isPresenting = false;
}
}
}
@@ -0,0 +1,29 @@
typedef ReminderPresentationNow = DateTime Function();
class ReminderPresentationCoordinator {
final Duration _dedupeWindow;
final ReminderPresentationNow _now;
final Map<String, DateTime> _lastPresentedAtByEventId = <String, DateTime>{};
ReminderPresentationCoordinator({
Duration dedupeWindow = const Duration(seconds: 30),
ReminderPresentationNow? now,
}) : _dedupeWindow = dedupeWindow,
_now = now ?? DateTime.now;
bool shouldPresent({required String eventId, required bool isAppActive}) {
if (!isAppActive) {
return false;
}
final currentTime = _now();
final lastPresentedAt = _lastPresentedAtByEventId[eventId];
if (lastPresentedAt != null &&
currentTime.difference(lastPresentedAt) < _dedupeWindow) {
return false;
}
_lastPresentedAtByEventId[eventId] = currentTime;
return true;
}
}
@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import '../../../../../core/theme/design_tokens.dart';
import '../../../../../shared/widgets/app_button.dart';
class ReminderActionSheet extends StatelessWidget {
const ReminderActionSheet({
super.key,
required this.onSnooze,
required this.onArchive,
});
final VoidCallback onSnooze;
final VoidCallback onArchive;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: AppColors.borderSecondary),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'提醒操作',
textAlign: TextAlign.center,
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(color: AppColors.slate900),
),
const SizedBox(height: AppSpacing.lg),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: AppButton(
text: '稍后提醒',
isOutlined: true,
onPressed: onSnooze,
),
),
const SizedBox(width: AppSpacing.md),
Expanded(
child: AppButton(text: '归档', onPressed: onArchive),
),
],
),
],
),
);
}
}