feat: 重构 Reminder Notification 系统并更新应用包名

This commit is contained in:
qzl
2026-03-30 18:36:57 +08:00
parent 9fb2a6857b
commit 91bf3c3f96
90 changed files with 5133 additions and 3017 deletions
@@ -453,7 +453,7 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
mainAxisSize: MainAxisSize.min,
children: [
Text(
_weekdayLabel(date),
_weekdayLabel(context, date),
style: TextStyle(
fontSize: 11,
color: isWeekend
@@ -492,8 +492,16 @@ class _CalendarDayWeekScreenState extends State<CalendarDayWeekScreen>
);
}
String _weekdayLabel(DateTime date) {
const labels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
String _weekdayLabel(BuildContext context, DateTime date) {
final labels = [
context.l10n.calendarMonthWeekdaySunShort,
context.l10n.calendarMonthWeekdayMonShort,
context.l10n.calendarMonthWeekdayTueShort,
context.l10n.calendarMonthWeekdayWedShort,
context.l10n.calendarMonthWeekdayThuShort,
context.l10n.calendarMonthWeekdayFriShort,
context.l10n.calendarMonthWeekdaySatShort,
];
return labels[date.weekday % 7];
}
@@ -0,0 +1,237 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import '../../../../app/di/injection.dart';
import '../../../../app/router/app_routes.dart';
import '../../../../core/l10n/l10n.dart';
import '../../../../core/notification/models/reminder_alarm.dart';
import '../../../../core/notification/services/reminder_reconcile_service.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../shared/widgets/app_button.dart';
import '../../../../shared/widgets/app_loading_indicator.dart';
import '../../../../shared/widgets/toast/toast.dart';
import '../../../../shared/widgets/toast/toast_type.dart';
import '../../data/models/schedule_item_model.dart';
import '../../data/services/calendar_service.dart';
class CalendarReminderAlarmScreen extends StatefulWidget {
const CalendarReminderAlarmScreen({super.key, required this.eventId});
final String eventId;
@override
State<CalendarReminderAlarmScreen> createState() =>
_CalendarReminderAlarmScreenState();
}
class _CalendarReminderAlarmScreenState
extends State<CalendarReminderAlarmScreen> {
late final Future<ScheduleItemModel> _eventFuture;
bool _isSubmitting = false;
@override
void initState() {
super.initState();
_eventFuture = sl<CalendarService>().getEventById(widget.eventId);
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: colorScheme.surface,
appBar: AppBar(
backgroundColor: colorScheme.surface,
title: Text(context.l10n.notificationChannelName),
),
body: FutureBuilder<ScheduleItemModel>(
future: _eventFuture,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const Center(child: AppLoadingIndicator());
}
if (snapshot.hasError || snapshot.data == null) {
return Center(
child: Padding(
padding: const EdgeInsets.all(AppSpacing.lg),
child: Text(
context.l10n.errorRequestFailed,
style: TextStyle(color: colorScheme.error),
),
),
);
}
final event = snapshot.data!;
return SafeArea(
child: Padding(
padding: const EdgeInsets.all(AppSpacing.lg),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_EventCard(event: event),
const Spacer(),
Row(
children: [
Expanded(
child: AppButton(
text: context.l10n.notificationSnoozeLater,
isOutlined: true,
isLoading: _isSubmitting,
onPressed: _isSubmitting
? null
: () => _snoozeEvent(event),
),
),
const SizedBox(width: AppSpacing.md),
Expanded(
child: AppButton(
text: context.l10n.calendarDetailArchiveConfirm,
isLoading: _isSubmitting,
onPressed: _isSubmitting
? null
: () => _archiveEvent(event),
),
),
],
),
],
),
),
);
},
),
);
}
Future<void> _archiveEvent(ScheduleItemModel event) async {
setState(() {
_isSubmitting = true;
});
try {
await sl<CalendarService>().archiveEvent(event.id);
if (!mounted) {
return;
}
context.go(AppRoutes.calendarEventDetail(event.id));
} catch (_) {
if (mounted) {
Toast.show(
context,
context.l10n.calendarDetailArchiveFailed,
type: ToastType.error,
);
}
} finally {
if (mounted) {
setState(() {
_isSubmitting = false;
});
}
}
}
Future<void> _snoozeEvent(ScheduleItemModel event) async {
setState(() {
_isSubmitting = true;
});
try {
await sl<ReminderReconcileService>().snooze10m(_snapshotFromEvent(event));
if (!mounted) {
return;
}
Toast.show(context, context.l10n.notificationSnoozeMinutes(10));
context.go(AppRoutes.calendarEventDetail(event.id));
} catch (_) {
if (mounted) {
Toast.show(
context,
context.l10n.todoSaveFailed('snooze failed'),
type: ToastType.error,
);
}
} finally {
if (mounted) {
setState(() {
_isSubmitting = false;
});
}
}
}
ReminderEventSnapshot _snapshotFromEvent(ScheduleItemModel event) {
return ReminderEventSnapshot(
eventId: event.id,
title: event.title,
startAt: event.startAt,
endAt: event.endAt,
timezone: event.timezone,
reminderMinutes: event.metadata?.reminderMinutes,
location: event.metadata?.location,
notes: event.metadata?.notes,
isArchived: event.status == ScheduleStatus.archived,
);
}
}
class _EventCard extends StatelessWidget {
const _EventCard({required this.event});
final ScheduleItemModel event;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
final formatter = DateFormat('MM-dd HH:mm');
final endAt = event.endAt;
return Container(
decoration: BoxDecoration(
color: colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: colorScheme.outlineVariant),
),
padding: const EdgeInsets.all(AppSpacing.lg),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
event.title,
style: textTheme.titleLarge?.copyWith(
color: colorScheme.onSurface,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: AppSpacing.md),
Text(
endAt == null
? formatter.format(event.startAt)
: '${formatter.format(event.startAt)} - ${formatter.format(endAt)}',
style: textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
if ((event.metadata?.location?.isNotEmpty ?? false)) ...[
const SizedBox(height: AppSpacing.sm),
Text(
context.l10n.notificationLocation(event.metadata!.location!),
style: textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
if ((event.metadata?.notes?.isNotEmpty ?? false)) ...[
const SizedBox(height: AppSpacing.sm),
Text(
context.l10n.notificationNotes(event.metadata!.notes!),
style: textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
],
),
);
}
}