feat: 实现日历提醒 in-app fallback 机制及通知服务重构
This commit is contained in:
@@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user