refactor: 梳理规则体系并统一记忆与部署流程

This commit is contained in:
qzl
2026-03-23 17:57:24 +08:00
parent 2a14ad1d8e
commit f4b7eb7e09
39 changed files with 2091 additions and 1454 deletions
+13 -1
View File
@@ -24,6 +24,8 @@ import '../../features/todo/ui/screens/todo_edit_screen.dart';
import '../../features/settings/ui/screens/settings_screen.dart';
import '../../features/settings/ui/screens/features_screen.dart';
import '../../features/settings/ui/screens/memory_screen.dart';
import '../../features/settings/ui/screens/user_memory_view_screen.dart';
import '../../features/settings/ui/screens/work_memory_view_screen.dart';
import '../../features/settings/ui/screens/user_memory_detail_screen.dart';
import '../../features/settings/ui/screens/work_memory_detail_screen.dart';
import '../../features/settings/ui/screens/edit_profile_screen.dart';
@@ -45,6 +47,8 @@ final _protectedRoutes = [
AppRoutes.settingsMemory,
AppRoutes.settingsMemoryUser,
AppRoutes.settingsMemoryWork,
AppRoutes.settingsMemoryUserEdit,
AppRoutes.settingsMemoryWorkEdit,
AppRoutes.settingsEditProfile,
AppRoutes.messageInviteList,
];
@@ -182,10 +186,18 @@ GoRouter createAppRouter(AuthBloc authBloc) {
),
GoRoute(
path: AppRoutes.settingsMemoryUser,
builder: (context, state) => const UserMemoryDetailScreen(),
builder: (context, state) => const UserMemoryViewScreen(),
),
GoRoute(
path: AppRoutes.settingsMemoryWork,
builder: (context, state) => const WorkMemoryViewScreen(),
),
GoRoute(
path: AppRoutes.settingsMemoryUserEdit,
builder: (context, state) => const UserMemoryDetailScreen(),
),
GoRoute(
path: AppRoutes.settingsMemoryWorkEdit,
builder: (context, state) => const WorkMemoryDetailScreen(),
),
GoRoute(
+2
View File
@@ -32,5 +32,7 @@ class AppRoutes {
static const settingsMemory = '/settings/memory';
static const settingsMemoryUser = '/settings/memory/user';
static const settingsMemoryWork = '/settings/memory/work';
static const settingsMemoryUserEdit = '/settings/memory/user/edit';
static const settingsMemoryWorkEdit = '/settings/memory/work/edit';
static const settingsEditProfile = '/edit-profile';
}
@@ -5,8 +5,6 @@ import 'package:social_app/core/theme/design_tokens.dart';
import 'package:social_app/core/router/app_routes.dart';
import 'package:social_app/shared/widgets/app_loading_indicator.dart';
import 'package:social_app/shared/widgets/app_pressable.dart';
import 'package:social_app/shared/widgets/toast/toast.dart';
import 'package:social_app/shared/widgets/toast/toast_type.dart';
import '../widgets/settings_page_scaffold.dart';
import '../../data/models/memory_models.dart';
import '../../data/services/memory_service.dart';
@@ -58,7 +56,6 @@ class _MemoryScreenState extends State<MemoryScreen> {
return SettingsPageScaffold(
title: '我的记忆',
onBack: () => context.pop(),
footer: _buildFooter(),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@@ -393,7 +390,7 @@ class _MemoryScreenState extends State<MemoryScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'工作Profile',
'工作画像',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
@@ -486,36 +483,4 @@ class _MemoryScreenState extends State<MemoryScreen> {
}),
);
}
Widget? _buildFooter() {
return AppPressable(
onTap: () {
Toast.show(context, '记忆会随着你的使用自动完善', type: ToastType.info);
},
borderRadius: BorderRadius.circular(AppRadius.lg),
child: Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.surfaceInfoLight,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: AppColors.borderQuaternary),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.info_outline, size: 16, color: AppColors.blue500),
const SizedBox(width: AppSpacing.sm),
const Text(
'点击卡片查看或编辑详情',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: AppColors.blue600,
),
),
],
),
),
);
}
}
@@ -89,7 +89,7 @@ class _UserMemoryDetailScreenState extends State<UserMemoryDetailScreen> {
@override
Widget build(BuildContext context) {
return SettingsPageScaffold(
title: '个人偏好',
title: '编辑个人偏好',
onBack: () => context.pop(),
footer: _hasChanges ? _buildSaveButton() : null,
body: Column(
@@ -182,7 +182,7 @@ class _UserMemoryDetailScreenState extends State<UserMemoryDetailScreen> {
borderRadius: BorderRadius.circular(AppRadius.lg),
boxShadow: [
BoxShadow(
color: const Color(0x4D60A5FA),
color: AppColors.blue500.withValues(alpha: 0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
@@ -190,16 +190,7 @@ class _UserMemoryDetailScreenState extends State<UserMemoryDetailScreen> {
),
child: Center(
child: _isSaving
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
AppColors.white,
),
),
)
? const AppLoadingIndicator(variant: AppLoadingVariant.button)
: const Text(
'保存更改',
style: TextStyle(
@@ -0,0 +1,554 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:social_app/core/di/injection.dart';
import 'package:social_app/core/router/app_routes.dart';
import 'package:social_app/core/theme/design_tokens.dart';
import 'package:social_app/shared/widgets/app_loading_indicator.dart';
import 'package:social_app/shared/widgets/app_pressable.dart';
import 'package:social_app/shared/widgets/detail_header_action_menu.dart';
import '../../data/models/memory_models.dart';
import '../../data/services/memory_service.dart';
import '../widgets/settings_page_scaffold.dart';
enum _UserMemoryHeaderAction { edit }
class UserMemoryViewScreen extends StatefulWidget {
const UserMemoryViewScreen({super.key});
@override
State<UserMemoryViewScreen> createState() => _UserMemoryViewScreenState();
}
class _UserMemoryViewScreenState extends State<UserMemoryViewScreen> {
final MemoryService _memoryService = sl<MemoryService>();
UserMemoryContent? _memory;
bool _isLoading = true;
String? _error;
@override
void initState() {
super.initState();
_loadMemory();
}
Future<void> _loadMemory() async {
if (!mounted) return;
setState(() {
_isLoading = true;
_error = null;
});
try {
final memory = await _memoryService.getUserMemory();
if (!mounted) return;
setState(() {
_memory = memory;
_isLoading = false;
});
} catch (_) {
if (!mounted) return;
setState(() {
_error = '加载失败';
_isLoading = false;
});
}
}
void _onHeaderAction(_UserMemoryHeaderAction action) {
switch (action) {
case _UserMemoryHeaderAction.edit:
context.push(AppRoutes.settingsMemoryUserEdit);
}
}
@override
Widget build(BuildContext context) {
return SettingsPageScaffold(
title: '个人偏好',
onBack: () => context.pop(),
trailing: DetailHeaderActionMenu<_UserMemoryHeaderAction>(
items: const [
DetailHeaderActionItem<_UserMemoryHeaderAction>(
value: _UserMemoryHeaderAction.edit,
label: '编辑',
icon: Icons.edit_outlined,
),
],
onSelected: _onHeaderAction,
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (_isLoading) ...[
const SizedBox(height: AppSpacing.xxl * 2),
const Center(child: AppLoadingIndicator(size: 32)),
] else if (_error != null) ...[
const SizedBox(height: AppSpacing.xxl * 2),
_buildErrorState(),
] else
_buildContent(_memory ?? UserMemoryContent()),
],
),
);
}
Widget _buildErrorState() {
return Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.error_outline, size: 48, color: AppColors.slate300),
const SizedBox(height: AppSpacing.md),
Text(_error ?? '加载失败', style: TextStyle(color: AppColors.slate500)),
const SizedBox(height: AppSpacing.lg),
AppPressable(
onTap: _loadMemory,
borderRadius: BorderRadius.circular(AppRadius.md),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.lg,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: AppColors.blue50,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: AppColors.blue100),
),
child: const Text(
'重新加载',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.blue600,
),
),
),
),
],
),
);
}
Widget _buildContent(UserMemoryContent memory) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildSectionCard(
title: '基本信息',
icon: Icons.person_outline,
children: [
_buildInfoRow(Icons.work_outline, '职业', _text(memory.occupation)),
_buildInfoRow(Icons.public_outlined, '时区', _text(memory.timezone)),
_buildInfoRow(
Icons.translate_outlined,
'主要语言',
_text(memory.primaryLanguage),
),
],
),
const SizedBox(height: AppSpacing.md),
_buildSectionCard(
title: '偏好设置',
icon: Icons.tune,
children: [
_buildInfoRow(
Icons.chat_bubble_outline,
'沟通风格',
_text(memory.preferences.communicationStyle),
),
_buildInfoRow(
Icons.place_outlined,
'位置偏好',
_text(memory.preferences.locationPreference),
),
_buildInfoRow(
Icons.balcony_outlined,
'工作生活方式',
_text(memory.preferences.workLifestyle),
),
_buildInfoRow(
Icons.language_outlined,
'语言偏好',
_listText(memory.preferences.languagePreference),
),
_buildInfoRow(
Icons.notifications_outlined,
'通知偏好',
_listText(memory.preferences.notificationPreference),
),
],
),
const SizedBox(height: AppSpacing.md),
_buildSectionCard(
title: '日程偏好',
icon: Icons.schedule_outlined,
children: [
_buildInfoRow(
Icons.timer_outlined,
'会议缓冲时间',
_minutesText(memory.schedulingPreferences.meetingBufferMinutes),
),
_buildInfoRow(
Icons.format_list_numbered,
'每日最多会议',
_numberText(memory.schedulingPreferences.maxMeetingsPerDay),
),
_buildInfoRow(
Icons.timelapse_outlined,
'偏好会议时长',
_intListText(
memory.schedulingPreferences.preferredMeetingDurationMinutes,
suffix: '分钟',
),
),
_buildInfoRow(
Icons.note_outlined,
'备注',
_text(memory.schedulingPreferences.notes),
multiline: true,
),
],
),
const SizedBox(height: AppSpacing.md),
_buildSectionCard(
title: '联系人',
icon: Icons.people_outline,
children: [_buildPeople(memory.people)],
),
const SizedBox(height: AppSpacing.md),
_buildSectionCard(
title: '地点',
icon: Icons.place_outlined,
children: [_buildPlaces(memory.places)],
),
const SizedBox(height: AppSpacing.md),
_buildSectionCard(
title: '兴趣',
icon: Icons.interests_outlined,
children: [_buildTags(memory.interests)],
),
const SizedBox(height: AppSpacing.md),
_buildSectionCard(
title: '回避话题',
icon: Icons.not_interested_outlined,
children: [_buildTags(memory.avoidTopics)],
),
const SizedBox(height: AppSpacing.md),
_buildSectionCard(
title: '自定义规则',
icon: Icons.rule_folder_outlined,
children: [_buildTags(memory.customRules)],
),
const SizedBox(height: AppSpacing.md),
_buildSectionCard(
title: '周期习惯',
icon: Icons.repeat,
children: [_buildRoutines(memory.recurringRoutines)],
),
const SizedBox(height: AppSpacing.xxl),
],
);
}
Widget _buildSectionCard({
required String title,
required IconData icon,
required List<Widget> children,
}) {
return Container(
padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: AppColors.borderSecondary),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 28,
height: 28,
decoration: BoxDecoration(
color: AppColors.blue50,
borderRadius: BorderRadius.circular(AppRadius.md),
),
child: Icon(icon, size: 16, color: AppColors.blue600),
),
const SizedBox(width: AppSpacing.sm),
Text(
title,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: AppColors.slate900,
),
),
],
),
const SizedBox(height: AppSpacing.md),
...children,
],
),
);
}
Widget _buildInfoRow(
IconData icon,
String label,
String value, {
bool multiline = false,
}) {
return Container(
margin: const EdgeInsets.only(bottom: AppSpacing.sm),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.surfaceSecondary,
borderRadius: BorderRadius.circular(AppRadius.md),
),
child: Row(
crossAxisAlignment: multiline
? CrossAxisAlignment.start
: CrossAxisAlignment.center,
children: [
Icon(icon, size: 16, color: AppColors.slate500),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: AppColors.slate500,
),
),
const SizedBox(height: 2),
Text(
value,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.slate800,
),
),
],
),
),
],
),
);
}
Widget _buildPeople(List<Person> people) {
if (people.isEmpty) {
return _buildEmptyTip('暂无联系人');
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: people.map((person) {
return Container(
margin: const EdgeInsets.only(bottom: AppSpacing.sm),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.surfaceSecondary,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: AppColors.borderTertiary),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow(Icons.badge_outlined, '姓名', _text(person.name)),
_buildInfoRow(
Icons.account_tree_outlined,
'关系',
_text(person.relationship),
),
_buildInfoRow(
Icons.person_pin_outlined,
'角色',
_text(person.role),
),
_buildInfoRow(
Icons.phone_outlined,
'联系方式',
_text(person.preferredContactChannel),
),
_buildInfoRow(
Icons.note_outlined,
'备注',
_text(person.notes),
multiline: true,
),
],
),
);
}).toList(),
);
}
Widget _buildPlaces(List<Place> places) {
if (places.isEmpty) {
return _buildEmptyTip('暂无地点');
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: places.map((place) {
return Container(
margin: const EdgeInsets.only(bottom: AppSpacing.sm),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.surfaceSecondary,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: AppColors.borderTertiary),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow(Icons.place_outlined, '名称', _text(place.name)),
_buildInfoRow(
Icons.category_outlined,
'类别',
_text(place.category),
),
_buildInfoRow(
Icons.favorite_border,
'偏好',
_text(place.preference),
),
_buildInfoRow(Icons.map_outlined, '地址', _text(place.address)),
],
),
);
}).toList(),
);
}
Widget _buildRoutines(List<RecurringRoutine> routines) {
if (routines.isEmpty) {
return _buildEmptyTip('暂无周期习惯');
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: routines.map((routine) {
return Container(
margin: const EdgeInsets.only(bottom: AppSpacing.sm),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.surfaceSecondary,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: AppColors.borderTertiary),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow(Icons.title_outlined, '名称', _text(routine.name)),
_buildInfoRow(
Icons.subject_outlined,
'描述',
_text(routine.description),
multiline: true,
),
_buildInfoRow(Icons.repeat_one, '周期', _text(routine.cadence)),
],
),
);
}).toList(),
);
}
Widget _buildTags(List<String> tags) {
if (tags.isEmpty) {
return _buildEmptyTip('暂无数据');
}
return Wrap(
spacing: AppSpacing.sm,
runSpacing: AppSpacing.sm,
children: tags.map((tag) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: AppColors.blue50,
borderRadius: BorderRadius.circular(AppRadius.full),
border: Border.all(color: AppColors.blue100),
),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.label_outline, size: 14, color: AppColors.blue500),
const SizedBox(width: AppSpacing.xs),
Text(
tag,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: AppColors.blue600,
),
),
],
),
);
}).toList(),
);
}
Widget _buildEmptyTip(String text) {
return Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.surfaceSecondary,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: AppColors.borderTertiary),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.inbox_outlined, size: 16, color: AppColors.slate400),
const SizedBox(width: AppSpacing.sm),
Text(
text,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: AppColors.slate500,
),
),
],
),
);
}
String _text(String? value) {
final raw = value?.trim() ?? '';
return raw.isEmpty ? '未设置' : raw;
}
String _listText(List<String> values) {
if (values.isEmpty) return '未设置';
return values.join('');
}
String _intListText(List<int> values, {required String suffix}) {
if (values.isEmpty) return '未设置';
return values.map((value) => '$value$suffix').join('');
}
String _minutesText(int? value) {
if (value == null) return '未设置';
return '$value 分钟';
}
String _numberText(int? value) {
if (value == null) return '未设置';
return '$value';
}
}
@@ -89,7 +89,7 @@ class _WorkMemoryDetailScreenState extends State<WorkMemoryDetailScreen> {
@override
Widget build(BuildContext context) {
return SettingsPageScaffold(
title: '工作Profile',
title: '编辑工作画像',
onBack: () => context.pop(),
footer: _hasChanges ? _buildSaveButton() : null,
body: Column(
@@ -182,7 +182,7 @@ class _WorkMemoryDetailScreenState extends State<WorkMemoryDetailScreen> {
borderRadius: BorderRadius.circular(AppRadius.lg),
boxShadow: [
BoxShadow(
color: const Color(0x4D60A5FA),
color: AppColors.blue500.withValues(alpha: 0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
@@ -190,16 +190,7 @@ class _WorkMemoryDetailScreenState extends State<WorkMemoryDetailScreen> {
),
child: Center(
child: _isSaving
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
AppColors.white,
),
),
)
? const AppLoadingIndicator(variant: AppLoadingVariant.button)
: const Text(
'保存更改',
style: TextStyle(
@@ -0,0 +1,522 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:social_app/core/di/injection.dart';
import 'package:social_app/core/router/app_routes.dart';
import 'package:social_app/core/theme/design_tokens.dart';
import 'package:social_app/shared/widgets/app_loading_indicator.dart';
import 'package:social_app/shared/widgets/app_pressable.dart';
import 'package:social_app/shared/widgets/detail_header_action_menu.dart';
import '../../data/models/memory_models.dart';
import '../../data/services/memory_service.dart';
import '../widgets/settings_page_scaffold.dart';
enum _WorkMemoryHeaderAction { edit }
class WorkMemoryViewScreen extends StatefulWidget {
const WorkMemoryViewScreen({super.key});
@override
State<WorkMemoryViewScreen> createState() => _WorkMemoryViewScreenState();
}
class _WorkMemoryViewScreenState extends State<WorkMemoryViewScreen> {
final MemoryService _memoryService = sl<MemoryService>();
WorkProfileContent? _memory;
bool _isLoading = true;
String? _error;
@override
void initState() {
super.initState();
_loadMemory();
}
Future<void> _loadMemory() async {
if (!mounted) return;
setState(() {
_isLoading = true;
_error = null;
});
try {
final memory = await _memoryService.getWorkMemory();
if (!mounted) return;
setState(() {
_memory = memory;
_isLoading = false;
});
} catch (_) {
if (!mounted) return;
setState(() {
_error = '加载失败';
_isLoading = false;
});
}
}
void _onHeaderAction(_WorkMemoryHeaderAction action) {
switch (action) {
case _WorkMemoryHeaderAction.edit:
context.push(AppRoutes.settingsMemoryWorkEdit);
}
}
@override
Widget build(BuildContext context) {
return SettingsPageScaffold(
title: '工作画像',
onBack: () => context.pop(),
trailing: DetailHeaderActionMenu<_WorkMemoryHeaderAction>(
items: const [
DetailHeaderActionItem<_WorkMemoryHeaderAction>(
value: _WorkMemoryHeaderAction.edit,
label: '编辑',
icon: Icons.edit_outlined,
),
],
onSelected: _onHeaderAction,
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (_isLoading) ...[
const SizedBox(height: AppSpacing.xxl * 2),
const Center(child: AppLoadingIndicator(size: 32)),
] else if (_error != null) ...[
const SizedBox(height: AppSpacing.xxl * 2),
_buildErrorState(),
] else
_buildContent(_memory ?? WorkProfileContent()),
],
),
);
}
Widget _buildErrorState() {
return Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.error_outline, size: 48, color: AppColors.slate300),
const SizedBox(height: AppSpacing.md),
Text(_error ?? '加载失败', style: TextStyle(color: AppColors.slate500)),
const SizedBox(height: AppSpacing.lg),
AppPressable(
onTap: _loadMemory,
borderRadius: BorderRadius.circular(AppRadius.md),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.lg,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: AppColors.blue50,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: AppColors.blue100),
),
child: const Text(
'重新加载',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.blue600,
),
),
),
),
],
),
);
}
Widget _buildContent(WorkProfileContent memory) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildSectionCard(
title: '基本信息',
icon: Icons.work_outline,
children: [
_buildInfoRow(Icons.badge_outlined, '职业', _text(memory.occupation)),
],
),
const SizedBox(height: AppSpacing.md),
_buildSectionCard(
title: '专长',
icon: Icons.psychology_outlined,
children: [_buildTags(memory.expertise)],
),
const SizedBox(height: AppSpacing.md),
_buildSectionCard(
title: '偏好工具',
icon: Icons.build_outlined,
children: [_buildTags(memory.preferredTools)],
),
const SizedBox(height: AppSpacing.md),
_buildSectionCard(
title: '当前项目',
icon: Icons.folder_outlined,
children: [_buildProjects(memory.currentProjects)],
),
const SizedBox(height: AppSpacing.md),
_buildSectionCard(
title: '团队成员',
icon: Icons.groups_outlined,
children: [_buildTeamMembers(memory.teamMembers)],
),
const SizedBox(height: AppSpacing.md),
_buildSectionCard(
title: '工作习惯',
icon: Icons.schedule_outlined,
children: [
_buildInfoRow(
Icons.timelapse_outlined,
'可用时段',
_timeWindowSummary(memory.workHabits.availableHours),
),
_buildInfoRow(
Icons.flash_on_outlined,
'深度工作时段',
_timeWindowSummary(memory.workHabits.deepWorkBlocks),
),
_buildInfoRow(
Icons.meeting_room_outlined,
'偏好会议时段',
_timeWindowSummary(memory.workHabits.preferredMeetingWindows),
),
_buildInfoRow(
Icons.do_not_disturb_alt_outlined,
'免打扰时段',
_timeWindowSummary(memory.workHabits.noMeetingWindows),
),
_buildInfoRow(
Icons.timer_outlined,
'偏好会议时长',
_intListText(
memory.workHabits.preferredMeetingDurationMinutes,
suffix: '分钟',
),
),
_buildInfoRow(
Icons.notifications_outlined,
'通知渠道',
_text(memory.workHabits.notificationChannel),
),
_buildInfoRow(
Icons.note_outlined,
'备注',
_text(memory.workHabits.notes),
multiline: true,
),
],
),
const SizedBox(height: AppSpacing.md),
_buildSectionCard(
title: '团队背景',
icon: Icons.business_outlined,
children: [
_buildInfoRow(
Icons.apartment_outlined,
'团队背景描述',
_text(memory.teamContext),
multiline: true,
),
],
),
const SizedBox(height: AppSpacing.md),
_buildSectionCard(
title: '工作规则',
icon: Icons.rule_outlined,
children: [_buildTags(memory.workRules)],
),
const SizedBox(height: AppSpacing.xxl),
],
);
}
Widget _buildSectionCard({
required String title,
required IconData icon,
required List<Widget> children,
}) {
return Container(
padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: AppColors.borderSecondary),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 28,
height: 28,
decoration: BoxDecoration(
color: AppColors.violet500.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(AppRadius.md),
),
child: Icon(icon, size: 16, color: AppColors.violet600),
),
const SizedBox(width: AppSpacing.sm),
Text(
title,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: AppColors.slate900,
),
),
],
),
const SizedBox(height: AppSpacing.md),
...children,
],
),
);
}
Widget _buildInfoRow(
IconData icon,
String label,
String value, {
bool multiline = false,
}) {
return Container(
margin: const EdgeInsets.only(bottom: AppSpacing.sm),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.surfaceSecondary,
borderRadius: BorderRadius.circular(AppRadius.md),
),
child: Row(
crossAxisAlignment: multiline
? CrossAxisAlignment.start
: CrossAxisAlignment.center,
children: [
Icon(icon, size: 16, color: AppColors.slate500),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: AppColors.slate500,
),
),
const SizedBox(height: 2),
Text(
value,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.slate800,
),
),
],
),
),
],
),
);
}
Widget _buildProjects(List<CurrentProject> projects) {
if (projects.isEmpty) {
return _buildEmptyTip('暂无项目');
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: projects.map((project) {
return Container(
margin: const EdgeInsets.only(bottom: AppSpacing.sm),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.surfaceSecondary,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: AppColors.borderTertiary),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow(Icons.title_outlined, '项目名称', _text(project.name)),
_buildInfoRow(
Icons.subject_outlined,
'描述',
_text(project.description),
multiline: true,
),
_buildInfoRow(Icons.flag_outlined, '状态', _text(project.status)),
_buildInfoRow(
Icons.priority_high_outlined,
'优先级',
_text(project.priority),
),
_buildInfoRow(
Icons.event_outlined,
'截止日期',
project.deadline == null
? '未设置'
: project.deadline!.toIso8601String().split('T').first,
),
_buildInfoRow(
Icons.group_add_outlined,
'协作人',
_listText(project.collaborators),
),
_buildInfoRow(
Icons.emoji_events_outlined,
'关键里程碑',
project.keyMilestones.isEmpty
? '未设置'
: '${project.keyMilestones.length}',
),
_buildInfoRow(
Icons.note_alt_outlined,
'备注',
_text(project.notes),
),
],
),
);
}).toList(),
);
}
Widget _buildTeamMembers(List<TeamMember> members) {
if (members.isEmpty) {
return _buildEmptyTip('暂无团队成员');
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: members.map((member) {
return Container(
margin: const EdgeInsets.only(bottom: AppSpacing.sm),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.surfaceSecondary,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: AppColors.borderTertiary),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow(Icons.badge_outlined, '姓名', _text(member.name)),
_buildInfoRow(
Icons.person_pin_outlined,
'角色',
_text(member.role),
),
_buildInfoRow(
Icons.account_tree_outlined,
'关系',
_text(member.relationship),
),
_buildInfoRow(
Icons.phone_outlined,
'联系方式',
_text(member.preferredContactChannel),
),
_buildInfoRow(Icons.note_outlined, '备注', _text(member.notes)),
],
),
);
}).toList(),
);
}
Widget _buildTags(List<String> tags) {
if (tags.isEmpty) {
return _buildEmptyTip('暂无数据');
}
return Wrap(
spacing: AppSpacing.sm,
runSpacing: AppSpacing.sm,
children: tags.map((tag) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: AppColors.violet500.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(AppRadius.full),
border: Border.all(
color: AppColors.violet500.withValues(alpha: 0.25),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.label_outline, size: 14, color: AppColors.violet500),
const SizedBox(width: AppSpacing.xs),
Text(
tag,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: AppColors.violet600,
),
),
],
),
);
}).toList(),
);
}
Widget _buildEmptyTip(String text) {
return Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.surfaceSecondary,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: AppColors.borderTertiary),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.inbox_outlined, size: 16, color: AppColors.slate400),
const SizedBox(width: AppSpacing.sm),
Text(
text,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: AppColors.slate500,
),
),
],
),
);
}
String _text(String? value) {
final raw = value?.trim() ?? '';
return raw.isEmpty ? '未设置' : raw;
}
String _listText(List<String> values) {
if (values.isEmpty) return '未设置';
return values.join('');
}
String _intListText(List<int> values, {required String suffix}) {
if (values.isEmpty) return '未设置';
return values.map((value) => '$value$suffix').join('');
}
String _timeWindowSummary(List<TimeWindow> windows) {
if (windows.isEmpty) return '未设置';
return '${windows.length} 个时段';
}
}
@@ -10,6 +10,7 @@ class SettingsPageScaffold extends StatelessWidget {
required this.body,
this.footer,
this.onBack,
this.trailing,
this.resizeOnKeyboard = true,
this.maintainBottomViewPadding = false,
});
@@ -18,6 +19,7 @@ class SettingsPageScaffold extends StatelessWidget {
final Widget body;
final Widget? footer;
final VoidCallback? onBack;
final Widget? trailing;
final bool resizeOnKeyboard;
final bool maintainBottomViewPadding;
@@ -31,7 +33,11 @@ class SettingsPageScaffold extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
BackTitlePageHeader(title: title, onBack: onBack),
BackTitlePageHeader(
title: title,
onBack: onBack,
trailing: trailing,
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(