feat(apps): 重构 UI 架构为 presentation 层并新增 l10n 国际化支持

This commit is contained in:
qzl
2026-03-27 14:05:03 +08:00
parent b1f0eb8921
commit c592cc7854
178 changed files with 10748 additions and 5764 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
import 'package:social_app/core/api/i_api_client.dart';
import 'package:social_app/core/network/i_api_client.dart';
class TodoApi {
final IApiClient _client;
@@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../../../core/di/injection.dart';
import '../../../../core/router/app_routes.dart';
import '../../../../app/di/injection.dart';
import '../../../../app/router/app_routes.dart';
import '../../../../core/l10n/l10n.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../shared/widgets/back_title_page_header.dart';
import '../../../../shared/widgets/detail_header_action_menu.dart';
@@ -70,15 +72,15 @@ class _TodoDetailScreenState extends State<TodoDetailScreen> {
String _getPriorityLabel(int priority) {
switch (priority) {
case 1:
return '重要紧急';
return context.l10n.todoQuadrantImportantUrgent;
case 2:
return '重要不紧急';
return context.l10n.todoQuadrantImportantNotUrgent;
case 3:
return '紧急不重要';
return context.l10n.todoQuadrantUrgentNotImportant;
case 4:
return '不紧急不重要';
return context.l10n.todoQuadrantNotUrgentNotImportant;
default:
return '未知';
return context.l10n.commonUnknown;
}
}
@@ -122,7 +124,7 @@ class _TodoDetailScreenState extends State<TodoDetailScreen> {
Widget _buildHeader() {
return BackTitlePageHeader(
title: '待办详情',
title: context.l10n.todoDetailTitle,
onBack: () => context.pop(_didMutate),
trailing: _buildHeaderMenu(),
);
@@ -133,15 +135,15 @@ class _TodoDetailScreenState extends State<TodoDetailScreen> {
return null;
}
return DetailHeaderActionMenu<_TodoHeaderAction>(
items: const [
items: [
DetailHeaderActionItem<_TodoHeaderAction>(
value: _TodoHeaderAction.edit,
label: '编辑',
label: context.l10n.commonEdit,
icon: LucideIcons.pencil,
),
DetailHeaderActionItem<_TodoHeaderAction>(
value: _TodoHeaderAction.delete,
label: '删除',
label: context.l10n.commonDelete,
icon: LucideIcons.trash2,
isDestructive: true,
),
@@ -167,11 +169,14 @@ class _TodoDetailScreenState extends State<TodoDetailScreen> {
}
if (_error != null) {
return ErrorRetrySurface(message: '加载失败: $_error', onRetry: _loadTodo);
return ErrorRetrySurface(
message: context.l10n.commonLoadFailed(_error!),
onRetry: _loadTodo,
);
}
if (_todo == null) {
return const Center(child: Text('待办不存在'));
return Center(child: Text(context.l10n.todoNotFound));
}
return Padding(
@@ -182,7 +187,7 @@ class _TodoDetailScreenState extends State<TodoDetailScreen> {
const SizedBox(height: 12),
if (_todo!.scheduleItems.isNotEmpty) ...[
Text(
'日历事件卡片',
context.l10n.todoCalendarEventCards,
style: TextStyle(
fontFamily: 'Inter',
fontSize: 12,
@@ -208,8 +213,9 @@ class _TodoDetailScreenState extends State<TodoDetailScreen> {
}
String _formatEventTime(DateTime start, DateTime? end) {
final startStr =
'${start.year}${start.month}${start.day}${start.hour.toString().padLeft(2, '0')}:${start.minute.toString().padLeft(2, '0')}';
final startStr = DateFormat.yMd(
context.l10n.localeName,
).add_Hm().format(start);
if (end != null) {
final endStr =
'${end.hour.toString().padLeft(2, '0')}:${end.minute.toString().padLeft(2, '0')}';
@@ -264,20 +270,22 @@ class _TodoDetailScreenState extends State<TodoDetailScreen> {
Container(height: 1, color: AppColors.border),
const SizedBox(height: 8),
_buildInfoRow(
label: '所属象限',
label: context.l10n.todoPriorityQuadrant,
value: _getPriorityLabel(_todo!.priority),
valueColor: _getPriorityColor(_todo!.priority),
),
const SizedBox(height: 8),
_buildInfoRow(
label: '关联日历事件',
value: '${_todo!.scheduleItems.length}',
label: context.l10n.todoLinkedCalendarEvents,
value: context.l10n.todoItemCount(_todo!.scheduleItems.length),
valueColor: AppColors.g3Text,
),
const SizedBox(height: 8),
_buildInfoRow(
label: '状态',
value: _todo!.status == 'done' ? '已完成' : '进行中',
label: context.l10n.todoStatus,
value: _todo!.status == 'done'
? context.l10n.todoStatusDone
: context.l10n.todoStatusInProgress,
valueColor: _todo!.status == 'done'
? AppColors.success
: AppColors.blue600,
@@ -289,11 +297,11 @@ class _TodoDetailScreenState extends State<TodoDetailScreen> {
String _buildSubtitle() {
final parts = <String>[];
parts.add('象限内顺序 #${_todo!.order + 1}');
parts.add(context.l10n.todoQuadrantOrder(_todo!.order + 1));
if (_todo!.scheduleItems.isNotEmpty) {
parts.add('已拆分为${_todo!.scheduleItems.length}个日历事件');
parts.add(context.l10n.todoSplitToEvents(_todo!.scheduleItems.length));
} else {
parts.add('未关联日历事件');
parts.add(context.l10n.todoNoLinkedEvents);
}
return parts.join(' · ');
}
@@ -391,9 +399,9 @@ class _TodoDetailScreenState extends State<TodoDetailScreen> {
void _deleteTodo() async {
final confirm = await showDestructiveActionSheet(
context,
title: '删除待办',
message: '确定要删除这个待办吗?',
confirmText: '确认删除',
title: context.l10n.todoDeleteTitle,
message: context.l10n.todoDeleteMessage,
confirmText: context.l10n.todoDeleteConfirm,
);
if (confirm == true) {
@@ -404,7 +412,11 @@ class _TodoDetailScreenState extends State<TodoDetailScreen> {
}
} catch (e) {
if (mounted) {
Toast.show(context, '删除失败: $e', type: ToastType.error);
Toast.show(
context,
context.l10n.todoDeleteFailed(e.toString()),
type: ToastType.error,
);
}
}
}
@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import '../../../../core/di/injection.dart';
import '../../../../app/di/injection.dart';
import '../../../../core/l10n/l10n.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../shared/widgets/app_button.dart';
import '../../../../shared/widgets/app_pressable.dart';
@@ -134,7 +136,9 @@ class _TodoEditScreenState extends State<TodoEditScreen> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
BackTitlePageHeader(
title: widget.isCreateMode ? '新建待办' : '编辑待办',
title: widget.isCreateMode
? context.l10n.todoCreateTitle
: context.l10n.todoEditTitle,
),
Expanded(child: _buildBody()),
_buildBottomAction(),
@@ -152,11 +156,14 @@ class _TodoEditScreenState extends State<TodoEditScreen> {
}
if (_error != null) {
return ErrorRetrySurface(message: '加载失败: $_error', onRetry: _loadPage);
return ErrorRetrySurface(
message: context.l10n.commonLoadFailed(_error!),
onRetry: _loadPage,
);
}
if (!widget.isCreateMode && _todo == null) {
return const Center(child: Text('待办不存在'));
return Center(child: Text(context.l10n.todoNotFound));
}
return ListView(
@@ -194,8 +201,8 @@ class _TodoEditScreenState extends State<TodoEditScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'待办信息',
Text(
context.l10n.todoInfoTitle,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
@@ -205,10 +212,10 @@ class _TodoEditScreenState extends State<TodoEditScreen> {
const SizedBox(height: AppSpacing.xs),
Text(
widget.isCreateMode
? '创建后可在四象限中查看并继续调整优先级与关联事件。'
? context.l10n.todoInfoDescCreate
: _todo?.status == 'done'
? '该待办已完成,你仍可调整内容并重新组织关联事件。'
: '调整标题、优先级和关联事件,保持任务结构清晰。',
? context.l10n.todoInfoDescDone
: context.l10n.todoInfoDescDefault,
style: const TextStyle(fontSize: 13, color: AppColors.slate500),
),
],
@@ -229,19 +236,19 @@ class _TodoEditScreenState extends State<TodoEditScreen> {
children: [
AppSheetInputField(
controller: _titleController,
label: '标题',
hint: '输入待办标题',
label: context.l10n.todoFieldTitle,
hint: context.l10n.todoFieldTitleHint,
),
const SizedBox(height: AppSpacing.lg),
AppSheetInputField(
controller: _descriptionController,
label: '描述(可选)',
hint: '补充细节或备注',
label: context.l10n.todoFieldDescriptionOptional,
hint: context.l10n.todoFieldDescriptionHint,
maxLines: 2,
),
const SizedBox(height: AppSpacing.lg),
const Text(
'优先级',
Text(
context.l10n.todoPriority,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
@@ -254,21 +261,21 @@ class _TodoEditScreenState extends State<TodoEditScreen> {
runSpacing: AppSpacing.sm,
children: [
_PriorityPill(
label: '重要紧急',
label: context.l10n.todoQuadrantImportantUrgent,
selected: _priority == 1,
borderColor: AppColors.g1Border,
activeColor: AppColors.g1Text,
onTap: () => setState(() => _priority = 1),
),
_PriorityPill(
label: '紧急不重要',
label: context.l10n.todoQuadrantUrgentNotImportant,
selected: _priority == 3,
borderColor: AppColors.g2Border,
activeColor: AppColors.g2Text,
onTap: () => setState(() => _priority = 3),
),
_PriorityPill(
label: '重要不紧急',
label: context.l10n.todoQuadrantImportantNotUrgent,
selected: _priority == 2,
borderColor: AppColors.g3Border,
activeColor: AppColors.g3Text,
@@ -295,8 +302,8 @@ class _TodoEditScreenState extends State<TodoEditScreen> {
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text(
'关联日历事件',
Text(
context.l10n.todoLinkedCalendarEvents,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
@@ -305,7 +312,7 @@ class _TodoEditScreenState extends State<TodoEditScreen> {
),
const Spacer(),
Text(
'${_selectedScheduleItemIds.length}',
context.l10n.todoItemCount(_selectedScheduleItemIds.length),
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
@@ -316,12 +323,12 @@ class _TodoEditScreenState extends State<TodoEditScreen> {
),
const SizedBox(height: AppSpacing.sm),
if (_scheduleItems.isEmpty)
const Padding(
Padding(
padding: EdgeInsets.symmetric(vertical: AppSpacing.xl),
child: Center(
child: Text(
'暂无可关联的日历事件',
style: TextStyle(color: AppColors.slate500),
context.l10n.todoNoSelectableCalendarEvents,
style: const TextStyle(color: AppColors.slate500),
),
),
)
@@ -372,7 +379,11 @@ class _TodoEditScreenState extends State<TodoEditScreen> {
border: const Border(top: BorderSide(color: AppColors.borderSecondary)),
),
child: AppButton(
text: _saving ? '保存中...' : (widget.isCreateMode ? '创建待办' : '保存修改'),
text: _saving
? context.l10n.todoSaveInProgress
: (widget.isCreateMode
? context.l10n.todoCreateButton
: context.l10n.todoSaveChanges),
onPressed: canSave ? _save : null,
),
);
@@ -384,7 +395,7 @@ class _TodoEditScreenState extends State<TodoEditScreen> {
}
final title = _titleController.text.trim();
if (title.isEmpty) {
Toast.show(context, '请输入标题', type: ToastType.warning);
Toast.show(context, context.l10n.todoEnterTitle, type: ToastType.warning);
return;
}
@@ -420,7 +431,11 @@ class _TodoEditScreenState extends State<TodoEditScreen> {
if (!mounted) {
return;
}
Toast.show(context, '保存失败: $error', type: ToastType.error);
Toast.show(
context,
context.l10n.todoSaveFailed(error.toString()),
type: ToastType.error,
);
} finally {
if (mounted) {
setState(() {
@@ -431,7 +446,7 @@ class _TodoEditScreenState extends State<TodoEditScreen> {
}
String _formatDate(DateTime dt) {
return '${dt.year}${dt.month}${dt.day}${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}';
return DateFormat.yMd(context.l10n.localeName).add_Hm().format(dt);
}
}
@@ -2,10 +2,11 @@ import 'package:flutter/material.dart';
import 'package:drag_and_drop_lists/drag_and_drop_lists.dart';
import 'package:go_router/go_router.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../../../core/di/injection.dart';
import '../../../../core/router/app_routes.dart';
import '../../../../app/di/injection.dart';
import '../../../../app/router/app_routes.dart';
import '../../../../core/l10n/l10n.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../home/ui/navigation/home_return_policy.dart';
import '../../../home/presentation/navigation/home_return_policy.dart';
import '../../../../shared/widgets/app_pull_refresh_feedback.dart';
import '../../../../shared/widgets/app_pressable.dart';
import '../../../../shared/widgets/back_title_page_header.dart';
@@ -13,8 +14,8 @@ import '../../../../shared/widgets/error_retry_surface.dart';
import '../../../../shared/widgets/full_screen_loading.dart';
import '../../../../shared/widgets/toast/toast.dart';
import '../../../../shared/widgets/toast/toast_type.dart';
import '../../../calendar/ui/calendar_state_manager.dart';
import '../../../calendar/ui/widgets/bottom_dock.dart';
import '../../../calendar/presentation/calendar_state_manager.dart';
import '../../../calendar/presentation/widgets/bottom_dock.dart';
import '../../data/todo_api.dart';
import '../../data/todo_repository.dart';
@@ -88,7 +89,7 @@ class _TodoQuadrantsScreenState extends State<TodoQuadrantsScreen> {
setState(() {
_todos = previousTodos;
});
Toast.show(context, '移动失败', type: ToastType.error);
Toast.show(context, context.l10n.todoMoveFailed, type: ToastType.error);
} finally {
if (mounted) {
setState(() {
@@ -236,7 +237,11 @@ class _TodoQuadrantsScreenState extends State<TodoQuadrantsScreen> {
});
} else {
setState(() => _isPullRefreshing = false);
Toast.show(context, '刷新失败,请稍后重试', type: ToastType.error);
Toast.show(
context,
context.l10n.todoRefreshFailed,
type: ToastType.error,
);
}
} finally {
_loadingTodosRequest = false;
@@ -275,7 +280,11 @@ class _TodoQuadrantsScreenState extends State<TodoQuadrantsScreen> {
}
} catch (e) {
if (mounted) {
Toast.show(context, '完成失败: $e', type: ToastType.error);
Toast.show(
context,
context.l10n.todoCompleteFailed(e.toString()),
type: ToastType.error,
);
}
}
}
@@ -320,7 +329,7 @@ class _TodoQuadrantsScreenState extends State<TodoQuadrantsScreen> {
Widget _buildHeader() {
return BackTitlePageHeader(
title: '待办事项',
title: context.l10n.todoScreenTitle,
showBackButton: false,
trailing: Row(
mainAxisSize: MainAxisSize.min,
@@ -361,7 +370,10 @@ class _TodoQuadrantsScreenState extends State<TodoQuadrantsScreen> {
}
if (_error != null) {
return ErrorRetrySurface(message: '加载失败: $_error', onRetry: _loadTodos);
return ErrorRetrySurface(
message: context.l10n.commonLoadFailed(_error!),
onRetry: _loadTodos,
);
}
final content = _buildDragBoard();
@@ -385,7 +397,7 @@ class _TodoQuadrantsScreenState extends State<TodoQuadrantsScreen> {
final quadrants = [
_QuadrantMeta(
value: 1,
title: '重要紧急',
title: context.l10n.todoQuadrantImportantUrgent,
textColor: AppColors.g1Text,
dividerColor: AppColors.g1Divider,
borderColor: AppColors.g1Border,
@@ -393,7 +405,7 @@ class _TodoQuadrantsScreenState extends State<TodoQuadrantsScreen> {
),
_QuadrantMeta(
value: 3,
title: '紧急不重要',
title: context.l10n.todoQuadrantUrgentNotImportant,
textColor: AppColors.g2Text,
dividerColor: AppColors.g2Divider,
borderColor: AppColors.g2Border,
@@ -401,7 +413,7 @@ class _TodoQuadrantsScreenState extends State<TodoQuadrantsScreen> {
),
_QuadrantMeta(
value: 2,
title: '重要不紧急',
title: context.l10n.todoQuadrantImportantNotUrgent,
textColor: AppColors.g3Text,
dividerColor: AppColors.g3Divider,
borderColor: AppColors.g3Border,
@@ -500,7 +512,7 @@ class _TodoQuadrantsScreenState extends State<TodoQuadrantsScreen> {
),
),
Text(
'${meta.items.length}',
context.l10n.todoItemCount(meta.items.length),
style: TextStyle(
fontFamily: 'Inter',
fontSize: 12,
@@ -522,7 +534,7 @@ class _TodoQuadrantsScreenState extends State<TodoQuadrantsScreen> {
height: 60,
child: Center(
child: Text(
'暂无待办',
context.l10n.todoNoItems,
style: TextStyle(
fontFamily: 'Inter',
fontSize: 13,