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
@@ -4,6 +4,7 @@ import '../../../../core/di/injection.dart';
import '../../../../core/notifications/local_notification_service.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../shared/widgets/app_loading_indicator.dart';
import '../../../../shared/widgets/app_selection_sheet.dart';
import '../../../../shared/widgets/app_sheet_input_field.dart';
import '../../../../shared/widgets/back_title_page_header.dart';
import '../../../../shared/widgets/toast/toast.dart';
@@ -107,12 +108,21 @@ class _CreateEventSheetState extends State<CreateEventSheet>
event.metadata?.reminderMinutes,
);
} else {
final now =
widget.initialDate ?? _roundToNearestMinute(DateTime.now(), 5);
_startDate = now;
_startTime = now;
_endDate = now;
_endTime = now.add(const Duration(hours: 1));
final now = DateTime.now();
final initial = widget.initialDate;
final rounded = _roundToNearestMinute(now, 5);
_startDate = initial != null
? DateTime(
initial.year,
initial.month,
initial.day,
rounded.hour,
rounded.minute,
)
: rounded;
_startTime = _startDate;
_endDate = _startDate;
_endTime = _startDate.add(const Duration(hours: 1));
}
}
@@ -139,15 +149,19 @@ class _CreateEventSheetState extends State<CreateEventSheet>
@override
Widget build(BuildContext context) {
if (widget.pageMode) {
return Container(
color: AppColors.background,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildPageHeader(),
_buildTabBar(),
Expanded(child: _buildTabContent()),
],
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => FocusScope.of(context).unfocus(),
child: Container(
color: AppColors.background,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildPageHeader(),
_buildTabBar(),
Expanded(child: _buildTabContent()),
],
),
),
);
}
@@ -331,12 +345,7 @@ class _CreateEventSheetState extends State<CreateEventSheet>
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTextField(
'标题',
_titleController,
'请输入日程标题',
autofocus: !_isEditing,
),
_buildTextField('标题', _titleController, '请输入日程标题'),
const SizedBox(height: 20),
_buildDateTimePicker('开始', _startDate, _startTime, (date, time) {
setState(() {
@@ -580,7 +589,6 @@ class _CreateEventSheetState extends State<CreateEventSheet>
}
Widget _buildReminderPicker() {
final options = _buildReminderOptions();
String labelOf(int? value) {
if (value == null) {
return '无提醒';
@@ -603,37 +611,51 @@ class _CreateEventSheetState extends State<CreateEventSheet>
),
),
const SizedBox(height: 8),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: AppColors.border),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<int?>(
value: _reminderMinutes,
isExpanded: true,
InkWell(
onTap: () async {
final options = _buildReminderOptions();
final selected = await showAppSelectionSheet<int?>(
context,
title: '选择提醒时间',
items: options
.map(
(value) => DropdownMenuItem<int?>(
value: value,
child: Text(
labelOf(value),
style: const TextStyle(
fontSize: 14,
color: AppColors.slate700,
),
),
),
)
.map((v) => AppSelectionItem(value: v, label: labelOf(v)))
.toList(),
onChanged: (value) {
setState(() {
_reminderMinutes = value;
});
},
selectedValue: _reminderMinutes,
);
if (selected != null) {
setState(() {
_reminderMinutes = selected;
});
}
},
borderRadius: BorderRadius.circular(AppRadius.md),
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.slate50,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: AppColors.borderSecondary),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Text(
labelOf(_reminderMinutes),
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color: AppColors.slate900,
),
),
),
const Icon(
LucideIcons.chevronRight,
size: 16,
color: AppColors.slate400,
),
],
),
),
),
@@ -54,7 +54,7 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
if (_selectedYear == minDate.year &&
_selectedMonth == minDate.month &&
_selectedDay == minDate.day) {
return _allHours.where((h) => h > minDate.hour).toList();
return _allHours.where((h) => h >= minDate.hour).toList();
}
return _allHours;
}
@@ -73,7 +73,7 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
_selectedMonth == minDate.month &&
_selectedDay == minDate.day &&
_selectedHour == minDate.hour) {
return _allMinutes.where((m) => m > minDate.minute).toList();
return _allMinutes.where((m) => m >= minDate.minute).toList();
}
return _allMinutes;
}
@@ -100,6 +100,12 @@ class _DateTimePickerSheetState extends State<DateTimePickerSheet> {
_hourController = FixedExtentScrollController(
initialItem: _filteredHours.indexOf(_selectedHour),
);
if (_filteredMinutes.isEmpty) {
_selectedMinute = 0;
} else if (!_filteredMinutes.contains(_selectedMinute)) {
_selectedMinute = _filteredMinutes.first;
}
_minuteController = FixedExtentScrollController(
initialItem: _filteredMinutes.indexOf(_selectedMinute),
);