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
@@ -4,13 +4,17 @@ import 'package:lucide_icons/lucide_icons.dart';
import '../../../../core/di/injection.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../shared/widgets/app_loading_indicator.dart';
import '../../../../shared/widgets/page_header.dart' as widgets;
import '../../../../shared/widgets/back_title_page_header.dart';
import '../../../../shared/widgets/detail_header_action_menu.dart';
import '../../../../shared/widgets/destructive_action_sheet.dart';
import '../../../../shared/widgets/app_button.dart';
import '../../../../shared/widgets/toast/toast.dart';
import '../../../../shared/widgets/toast/toast_type.dart';
import '../../../calendar/data/calendar_api.dart';
import '../../data/todo_api.dart';
enum _TodoHeaderAction { edit, delete }
class TodoDetailScreen extends StatefulWidget {
final String todoId;
@@ -87,8 +91,9 @@ class _TodoDetailScreenState extends State<TodoDetailScreen> {
backgroundColor: AppColors.todoBg,
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(context),
_buildHeader(),
Expanded(child: _buildContent()),
],
),
@@ -96,39 +101,47 @@ class _TodoDetailScreenState extends State<TodoDetailScreen> {
);
}
Widget _buildHeader(BuildContext context) {
return SizedBox(
height: 64,
child: Padding(
padding: const EdgeInsets.only(left: 16, right: 16, top: 12, bottom: 8),
child: Row(
children: [
widgets.BackButton(onPressed: () => Navigator.of(context).pop()),
const Spacer(),
if (_todo != null) ...[
IconButton(
onPressed: _editTodo,
icon: const Icon(
LucideIcons.pencil,
size: 20,
color: AppColors.slate600,
),
),
IconButton(
onPressed: _deleteTodo,
icon: const Icon(
LucideIcons.trash2,
size: 20,
color: Colors.red,
),
),
],
],
),
),
Widget _buildHeader() {
return BackTitlePageHeader(
title: '待办详情',
onBack: () => context.pop(),
trailing: _buildHeaderMenu(),
);
}
Widget? _buildHeaderMenu() {
if (_todo == null) {
return null;
}
return DetailHeaderActionMenu<_TodoHeaderAction>(
items: const [
DetailHeaderActionItem<_TodoHeaderAction>(
value: _TodoHeaderAction.edit,
label: '编辑',
icon: LucideIcons.pencil,
),
DetailHeaderActionItem<_TodoHeaderAction>(
value: _TodoHeaderAction.delete,
label: '删除',
icon: LucideIcons.trash2,
isDestructive: true,
),
],
onSelected: _handleHeaderAction,
);
}
void _handleHeaderAction(_TodoHeaderAction action) {
switch (action) {
case _TodoHeaderAction.edit:
_editTodo();
return;
case _TodoHeaderAction.delete:
_deleteTodo();
return;
}
}
Widget _buildContent() {
if (_isLoading) {
return const Center(child: AppLoadingIndicator(size: 22));
@@ -382,22 +395,11 @@ class _TodoDetailScreenState extends State<TodoDetailScreen> {
}
void _deleteTodo() async {
final confirm = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认删除'),
content: const Text('确定要删除这个待办吗?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('删除', style: TextStyle(color: Colors.red)),
),
],
),
final confirm = await showDestructiveActionSheet(
context,
title: '删除待办',
message: '确定要删除这个待办吗?',
confirmText: '确认删除',
);
if (confirm == true) {