feat(apps): 重构 UI 架构为 presentation 层并新增 l10n 国际化支持
This commit is contained in:
@@ -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;
|
||||
|
||||
+40
-28
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
-28
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+27
-15
@@ -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,
|
||||
Reference in New Issue
Block a user