- 新增 TastePreferenceService 用于管理用户口味偏好设置 - 实现菜谱分享功能,包括 RecipeShareService 和分享页面 - 更新平台工具类以支持鸿蒙系统检测 - 优化收藏页和农场商店页面的UI交互 - 添加新的参考文献和关于页面内容 - 更新API文档至v3.3.0版本
489 lines
15 KiB
Dart
489 lines
15 KiB
Dart
/*
|
||
* 文件: profile_home.dart
|
||
* 名称: 个人中心首页标签
|
||
* 作用: iOS 26 风格的用户信息展示、功能入口和消息预览
|
||
* 更新: 2026-04-09 重构为 Liquid Glass 风格,统一使用 DesignTokens
|
||
* 更新: 2026-04-12 添加滚动物理特性,支持上下滑动
|
||
*/
|
||
|
||
import 'package:flutter/cupertino.dart';
|
||
import 'package:get/get.dart';
|
||
import 'package:mom_kitchen/src/config/design_tokens.dart';
|
||
import 'package:mom_kitchen/src/controllers/user/profile_controller.dart';
|
||
import 'package:mom_kitchen/src/pages/profile/settings/personalization_page.dart';
|
||
import 'package:mom_kitchen/src/config/app_routes.dart';
|
||
|
||
class ProfileHomeTab extends StatelessWidget {
|
||
const ProfileHomeTab({super.key});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final profileController = Get.find<ProfileController>();
|
||
final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark;
|
||
|
||
return Obx(
|
||
() => SingleChildScrollView(
|
||
physics: const AlwaysScrollableScrollPhysics(
|
||
parent: BouncingScrollPhysics(),
|
||
),
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: DesignTokens.space4,
|
||
vertical: DesignTokens.space2,
|
||
),
|
||
child: Column(
|
||
children: [
|
||
_buildUserCard(profileController, isDark),
|
||
const SizedBox(height: DesignTokens.space4),
|
||
_buildFeatureGrid(isDark),
|
||
const SizedBox(height: DesignTokens.space4),
|
||
_buildToolsGrid(isDark),
|
||
const SizedBox(height: DesignTokens.space4),
|
||
_buildMessagePreview(isDark),
|
||
const SizedBox(height: DesignTokens.space5),
|
||
_buildAppFooter(isDark),
|
||
const SizedBox(height: DesignTokens.space6),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildToolsGrid(bool isDark) {
|
||
final tools = [
|
||
_FeatureItem(
|
||
CupertinoIcons.timer,
|
||
'烹饪计时',
|
||
DesignTokens.orange,
|
||
AppRoutes.cookingTimer,
|
||
),
|
||
_FeatureItem(
|
||
CupertinoIcons.arrow_2_circlepath,
|
||
'用量换算',
|
||
DesignTokens.secondary,
|
||
AppRoutes.unitConverter,
|
||
),
|
||
_FeatureItem(
|
||
CupertinoIcons.chart_bar,
|
||
'BMI计算',
|
||
DesignTokens.green,
|
||
AppRoutes.bmiCalculator,
|
||
),
|
||
_FeatureItem(
|
||
CupertinoIcons.arrow_up_arrow_down,
|
||
'份量缩放',
|
||
DesignTokens.dynamicPrimary,
|
||
AppRoutes.servingScaler,
|
||
),
|
||
];
|
||
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: DesignTokens.space1,
|
||
vertical: DesignTokens.space2,
|
||
),
|
||
child: Text(
|
||
'🛠️ 实用工具',
|
||
style: TextStyle(
|
||
fontSize: DesignTokens.fontMd,
|
||
fontWeight: FontWeight.w600,
|
||
color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: DesignTokens.space2),
|
||
SizedBox(
|
||
height: 100,
|
||
child: ListView.separated(
|
||
scrollDirection: Axis.horizontal,
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: DesignTokens.space3,
|
||
),
|
||
itemCount: tools.length,
|
||
separatorBuilder: (_, _) =>
|
||
const SizedBox(width: DesignTokens.space3),
|
||
itemBuilder: (context, index) {
|
||
final tool = tools[index];
|
||
return GestureDetector(
|
||
onTap: () {
|
||
if (tool.route != null) {
|
||
Get.toNamed(tool.route!);
|
||
}
|
||
},
|
||
behavior: HitTestBehavior.opaque,
|
||
child: Container(
|
||
width: 80,
|
||
decoration: BoxDecoration(
|
||
color: isDark ? DarkDesignTokens.card : DesignTokens.card,
|
||
borderRadius: DesignTokens.borderRadiusLg,
|
||
boxShadow: DesignTokens.shadowsSm,
|
||
),
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Container(
|
||
width: 48,
|
||
height: 48,
|
||
decoration: BoxDecoration(
|
||
color: tool.color.withValues(alpha: 0.12),
|
||
borderRadius: DesignTokens.borderRadiusMd,
|
||
),
|
||
child: Icon(tool.icon, size: 24, color: tool.color),
|
||
),
|
||
const SizedBox(height: DesignTokens.space2),
|
||
Text(
|
||
tool.label,
|
||
style: TextStyle(
|
||
fontSize: DesignTokens.fontXs,
|
||
color: isDark
|
||
? DarkDesignTokens.text2
|
||
: DesignTokens.text2,
|
||
),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildUserCard(ProfileController controller, bool isDark) {
|
||
final user = controller.user.value;
|
||
final isLoggedIn = controller.isLoggedIn.value;
|
||
|
||
return Container(
|
||
padding: const EdgeInsets.all(DesignTokens.space4),
|
||
decoration: BoxDecoration(
|
||
color: isDark ? DarkDesignTokens.card : DesignTokens.card,
|
||
borderRadius: DesignTokens.borderRadiusLg,
|
||
boxShadow: DesignTokens.shadowsSm,
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Container(
|
||
width: 56,
|
||
height: 56,
|
||
decoration: BoxDecoration(
|
||
shape: BoxShape.circle,
|
||
color: DesignTokens.primaryLight,
|
||
),
|
||
child: isLoggedIn && user != null && user.avatar != null
|
||
? ClipOval(
|
||
child: Image.network(
|
||
user.avatar!,
|
||
width: 56,
|
||
height: 56,
|
||
fit: BoxFit.cover,
|
||
errorBuilder: (context, error, stackTrace) {
|
||
return Icon(
|
||
CupertinoIcons.person_fill,
|
||
size: 28,
|
||
color: DesignTokens.dynamicPrimary,
|
||
);
|
||
},
|
||
),
|
||
)
|
||
: Icon(
|
||
CupertinoIcons.person_fill,
|
||
size: 28,
|
||
color: DesignTokens.dynamicPrimary,
|
||
),
|
||
),
|
||
const SizedBox(width: DesignTokens.space3),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
isLoggedIn && user != null && user.isNotEmpty
|
||
? user.name
|
||
: '未登录',
|
||
style: TextStyle(
|
||
fontSize: DesignTokens.fontLg,
|
||
fontWeight: FontWeight.w700,
|
||
color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
|
||
),
|
||
),
|
||
const SizedBox(height: DesignTokens.space1),
|
||
Text(
|
||
isLoggedIn && user != null && user.isNotEmpty
|
||
? user.email
|
||
: '点击登录',
|
||
style: TextStyle(
|
||
fontSize: DesignTokens.fontSm,
|
||
color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(width: DesignTokens.space2),
|
||
_buildMiniButton(
|
||
isLoggedIn ? '编辑' : '登录',
|
||
isDark,
|
||
isLoggedIn
|
||
? null
|
||
: () => controller.login('test@example.com', '123456'),
|
||
),
|
||
const SizedBox(width: DesignTokens.space2),
|
||
_buildMiniButton(
|
||
'个性化',
|
||
isDark,
|
||
() => Get.to(() => const PersonalizationPage()),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildMiniButton(String text, bool isDark, VoidCallback? onPressed) {
|
||
return CupertinoButton(
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: DesignTokens.space3,
|
||
vertical: DesignTokens.space1,
|
||
),
|
||
minimumSize: Size.zero,
|
||
borderRadius: DesignTokens.borderRadiusSm,
|
||
color: DesignTokens.dynamicPrimary,
|
||
onPressed: onPressed,
|
||
child: Text(
|
||
text,
|
||
style: const TextStyle(
|
||
fontSize: DesignTokens.fontXs,
|
||
fontWeight: FontWeight.w500,
|
||
color: CupertinoColors.white,
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildFeatureGrid(bool isDark) {
|
||
final items = [
|
||
_FeatureItem(
|
||
CupertinoIcons.star,
|
||
'技巧',
|
||
DesignTokens.orange,
|
||
AppRoutes.cookingTips,
|
||
),
|
||
_FeatureItem(
|
||
CupertinoIcons.heart,
|
||
'迷你卡片',
|
||
DesignTokens.red,
|
||
AppRoutes.miniCard,
|
||
),
|
||
_FeatureItem(
|
||
CupertinoIcons.cart,
|
||
'分享记录',
|
||
DesignTokens.green,
|
||
AppRoutes.shoppingList,
|
||
),
|
||
_FeatureItem(
|
||
CupertinoIcons.bookmark,
|
||
'评分记录',
|
||
DesignTokens.secondary,
|
||
AppRoutes.favorites,
|
||
),
|
||
];
|
||
|
||
return Container(
|
||
padding: const EdgeInsets.all(DesignTokens.space3),
|
||
decoration: BoxDecoration(
|
||
color: isDark ? DarkDesignTokens.card : DesignTokens.card,
|
||
borderRadius: DesignTokens.borderRadiusLg,
|
||
boxShadow: DesignTokens.shadowsSm,
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||
children: items.map((item) {
|
||
return GestureDetector(
|
||
onTap: () {
|
||
if (item.route != null) {
|
||
Get.toNamed(item.route!);
|
||
}
|
||
},
|
||
behavior: HitTestBehavior.opaque,
|
||
child: SizedBox(
|
||
width: 64,
|
||
child: Column(
|
||
children: [
|
||
Container(
|
||
width: 40,
|
||
height: 40,
|
||
decoration: BoxDecoration(
|
||
color: item.color.withValues(alpha: 0.12),
|
||
borderRadius: DesignTokens.borderRadiusMd,
|
||
),
|
||
child: Icon(item.icon, size: 20, color: item.color),
|
||
),
|
||
const SizedBox(height: DesignTokens.space2),
|
||
Text(
|
||
item.label,
|
||
style: TextStyle(
|
||
fontSize: DesignTokens.fontXs,
|
||
color: isDark
|
||
? DarkDesignTokens.text2
|
||
: DesignTokens.text2,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}).toList(),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildMessagePreview(bool isDark) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: DesignTokens.space1,
|
||
vertical: DesignTokens.space2,
|
||
),
|
||
child: Text(
|
||
'最新消息',
|
||
style: TextStyle(
|
||
fontSize: DesignTokens.fontMd,
|
||
fontWeight: FontWeight.w600,
|
||
color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: DesignTokens.space2),
|
||
GestureDetector(
|
||
onTap: () => Get.toNamed(AppRoutes.dataCenter),
|
||
child: _buildMessageItem('📊 数据管理中心', '分类标签 · 过敏原管理', isDark),
|
||
),
|
||
const SizedBox(height: DesignTokens.space2),
|
||
_buildMessageItem('营销信息', '新品上线,立即查看', isDark),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildMessageItem(String title, String subtitle, bool isDark) {
|
||
return Container(
|
||
padding: EdgeInsets.all(DesignTokens.space3),
|
||
decoration: BoxDecoration(
|
||
color: isDark ? DarkDesignTokens.card : DesignTokens.card,
|
||
borderRadius: DesignTokens.borderRadiusMd,
|
||
boxShadow: DesignTokens.shadowsSm,
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Container(
|
||
width: 8,
|
||
height: 8,
|
||
decoration: BoxDecoration(
|
||
shape: BoxShape.circle,
|
||
color: DesignTokens.dynamicPrimary,
|
||
),
|
||
),
|
||
const SizedBox(width: DesignTokens.space3),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
title,
|
||
style: TextStyle(
|
||
fontSize: DesignTokens.fontMd,
|
||
fontWeight: FontWeight.w600,
|
||
color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
|
||
),
|
||
),
|
||
const SizedBox(height: 2),
|
||
Text(
|
||
subtitle,
|
||
style: TextStyle(
|
||
fontSize: DesignTokens.fontSm,
|
||
color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
Icon(
|
||
CupertinoIcons.chevron_right,
|
||
size: 16,
|
||
color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildAppFooter(bool isDark) {
|
||
return Column(
|
||
children: [
|
||
Container(
|
||
width: 40,
|
||
height: 4,
|
||
decoration: BoxDecoration(
|
||
color: DesignTokens.dynamicPrimary.withValues(alpha: 0.2),
|
||
borderRadius: DesignTokens.borderRadiusSm,
|
||
),
|
||
),
|
||
const SizedBox(height: DesignTokens.space4),
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Text('🍳 ', style: TextStyle(fontSize: DesignTokens.fontMd)),
|
||
Text(
|
||
'小妈厨房',
|
||
style: TextStyle(
|
||
fontSize: DesignTokens.fontLg,
|
||
fontWeight: FontWeight.w700,
|
||
color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: DesignTokens.space1),
|
||
Text(
|
||
'用心开发 · 只为更好的体验 ❤️',
|
||
style: TextStyle(
|
||
fontSize: DesignTokens.fontSm,
|
||
color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2,
|
||
letterSpacing: 0.5,
|
||
),
|
||
),
|
||
SizedBox(height: DesignTokens.space2),
|
||
Text(
|
||
'v0.88.5 · 2026 Liquid Glass',
|
||
style: TextStyle(
|
||
fontSize: DesignTokens.fontXs,
|
||
color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
|
||
),
|
||
),
|
||
SizedBox(height: DesignTokens.space3),
|
||
Text(
|
||
'使用Flutter SDk开发,UI高度定制化的跨平台应用 ',
|
||
style: TextStyle(
|
||
fontSize: DesignTokens.fontXs,
|
||
color: DesignTokens.dynamicPrimary.withValues(alpha: 0.6),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|
||
|
||
class _FeatureItem {
|
||
final IconData icon;
|
||
final String label;
|
||
final Color color;
|
||
final String? route;
|
||
|
||
const _FeatureItem(this.icon, this.label, this.color, this.route);
|
||
}
|