feat: 实现 Auth 全局状态机与 401 统一处理机制

- 新增 AuthSessionInvalidated 事件处理 token 失效场景
- ApiInterceptor 新增 authFailureCallback 单飞机制
- AuthBloc 区分 manual logout 与 auto expiry 语义
- 新增 startup recovery fallback 防止启动卡死

feat: 重构 Calendar DayWeek 视图事件布局引擎

- 新增 DayEventLayoutEngine 解耦事件计算与渲染
- 新增 DayTimelineMetrics 统一时间轴常量
- 新增 DayViewScale 支持捏合缩放

feat: 新增 Settings 页面共享 UI 组件

- 新增 BackTitlePageHeader 统一页面 header
- 新增 DetailHeaderActionMenu 统一操作菜单
- 新增 DestructiveActionSheet 统一删除确认
- 新增 AppToggleSwitch 统一开关组件

feat: Chat UI Schema 支持导航操作

- 支持 navigation 类型 action 触发内部路由跳转
- 新增路径验证与参数处理

chore: 更新相关测试覆盖 auth 失效路径
This commit is contained in:
qzl
2026-03-18 13:35:25 +08:00
parent 19981964fb
commit b34697660d
56 changed files with 2602 additions and 784 deletions
@@ -57,6 +57,17 @@ class CreateEventSheet extends StatefulWidget {
class _CreateEventSheetState extends State<CreateEventSheet>
with SingleTickerProviderStateMixin {
static const List<int?> _defaultReminderOptions = [
null,
0,
5,
10,
15,
30,
60,
120,
];
late TabController _tabController;
final _titleController = TextEditingController();
final _descriptionController = TextEditingController();
@@ -89,7 +100,9 @@ class _CreateEventSheetState extends State<CreateEventSheet>
_endDate = event.endAt;
_endTime = event.endAt;
_selectedColor = event.metadata?.color ?? '#3B82F6';
_reminderMinutes = event.metadata?.reminderMinutes ?? 15;
_reminderMinutes = _sanitizeReminderMinutes(
event.metadata?.reminderMinutes,
);
} else {
final now =
widget.initialDate ?? _roundToNearestMinute(DateTime.now(), 5);
@@ -512,7 +525,7 @@ class _CreateEventSheetState extends State<CreateEventSheet>
}
Widget _buildReminderPicker() {
const options = <int?>[null, 0, 5, 10, 15, 30, 60, 120];
final options = _buildReminderOptions();
String labelOf(int? value) {
if (value == null) {
return '无提醒';
@@ -573,6 +586,24 @@ class _CreateEventSheetState extends State<CreateEventSheet>
);
}
int? _sanitizeReminderMinutes(int? minutes) {
if (minutes == null || minutes < 0) {
return null;
}
return minutes;
}
List<int?> _buildReminderOptions() {
final current = _sanitizeReminderMinutes(_reminderMinutes);
final nonNull = _defaultReminderOptions.whereType<int>().toSet();
if (current != null) {
nonNull.add(current);
}
final sorted = nonNull.toList()..sort();
return [null, ...sorted];
}
Future<void> _saveEvent() async {
if (_titleController.text.trim().isEmpty || _saving) return;
setState(() {