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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user