feat: 饮食记录逻辑重构 + 通知管理分隔线修复 + 多页面 UI 调整

- 饮食: 新增 DietCommentaryPolicy + diet_record_logic, 饮食记录逻辑抽取复用
- 通知管理: 分隔线改为仿通知中心样式, 跳过图标区域只留文字下方
- 侧边栏: 对话记录操作栏浮层修复 + 常用功能间距收紧
- 智能体: 欢迎卡片去 400ms 延迟 + 胶囊去阴影
- 后端: AI 会话仓库新增方法 + 饮食评论策略 + 健康记录规则调整
- 其他: api_client IP 适配 + 多页面 UI 微调 + 新增测试
This commit is contained in:
MingNian
2026-07-12 22:49:38 +08:00
parent d82e006cf4
commit 335a3e6440
33 changed files with 2056 additions and 1283 deletions

View File

@@ -4,6 +4,7 @@ import '../../core/app_colors.dart';
import '../../core/navigation_provider.dart';
import '../../providers/auth_provider.dart';
import '../../providers/data_providers.dart';
import '../../widgets/app_gradient_widgets.dart';
class LoginPage extends ConsumerStatefulWidget {
const LoginPage({super.key});
@@ -13,6 +14,11 @@ class LoginPage extends ConsumerStatefulWidget {
class _LoginPageState extends ConsumerState<LoginPage> {
static const _loginBg = 'assets/branding/login_background_v1.png';
static const _loginActionGradient = AppColors.primaryGradient;
void _openStaticText(String type) {
pushRoute(ref, 'staticText', params: {'type': type});
}
final _phoneCtrl = TextEditingController();
final _codeCtrl = TextEditingController();
@@ -174,23 +180,51 @@ class _LoginPageState extends ConsumerState<LoginPage> {
const SizedBox(height: 18),
RichText(
textAlign: TextAlign.center,
text: const TextSpan(
style: TextStyle(
text: TextSpan(
style: const TextStyle(
fontSize: 15,
height: 1.55,
fontWeight: FontWeight.w700,
color: AppColors.textSecondary,
),
children: [
TextSpan(text: '请阅读并同意'),
TextSpan(
text: '《服务协议》',
style: TextStyle(color: AppColors.primary),
const TextSpan(text: '请阅读并同意'),
WidgetSpan(
alignment: PlaceholderAlignment.baseline,
baseline: TextBaseline.alphabetic,
child: GestureDetector(
onTap: () {
Navigator.pop(ctx, false);
_openStaticText('terms');
},
child: AppGradientText(
'《服务协议》',
style: TextStyle(
fontSize: 15,
height: 1.55,
fontWeight: FontWeight.w700,
),
),
),
),
TextSpan(text: ''),
TextSpan(
text: '《隐私政策》',
style: TextStyle(color: AppColors.primary),
const TextSpan(text: ''),
WidgetSpan(
alignment: PlaceholderAlignment.baseline,
baseline: TextBaseline.alphabetic,
child: GestureDetector(
onTap: () {
Navigator.pop(ctx, false);
_openStaticText('privacy');
},
child: AppGradientText(
'《隐私政策》',
style: TextStyle(
fontSize: 15,
height: 1.55,
fontWeight: FontWeight.w700,
),
),
),
),
],
),
@@ -199,26 +233,9 @@ class _LoginPageState extends ConsumerState<LoginPage> {
Row(
children: [
Expanded(
child: SizedBox(
height: 50,
child: OutlinedButton(
onPressed: () => Navigator.pop(ctx, false),
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primary,
side: BorderSide(
color: AppColors.primary.withValues(alpha: 0.62),
width: 1.2,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
textStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w800,
),
),
child: const Text('不同意'),
),
child: AppGradientOutlineButton(
label: '不同意',
onTap: () => Navigator.pop(ctx, false),
),
),
const SizedBox(width: 12),
@@ -229,7 +246,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
height: 50,
alignment: Alignment.center,
decoration: BoxDecoration(
gradient: AppColors.doctorGradient,
gradient: _loginActionGradient,
borderRadius: BorderRadius.circular(18),
boxShadow: AppColors.buttonShadow,
),
@@ -328,9 +345,9 @@ class _LoginPageState extends ConsumerState<LoginPage> {
),
),
trailing: _selectedDoctorId == d['id']?.toString()
? const Icon(
Icons.check_circle,
color: AppColors.primary,
? const AppGradientIcon(
icon: Icons.check_circle,
size: 22,
)
: null,
onTap: () {
@@ -368,6 +385,7 @@ class _LoginPageState extends ConsumerState<LoginPage> {
}
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.white,
body: Stack(
children: [
@@ -475,6 +493,9 @@ class _LoginPageState extends ConsumerState<LoginPage> {
agreed: _agreed,
onTap: () =>
setState(() => _agreed = !_agreed),
onTermsTap: () => _openStaticText('terms'),
onPrivacyTap: () =>
_openStaticText('privacy'),
),
if (_error != null) ...[
const SizedBox(height: 12),
@@ -496,13 +517,13 @@ class _LoginPageState extends ConsumerState<LoginPage> {
_error = null;
_successMsg = null;
}),
child: Text(
_isLogin ? '没有账号?去注册' : '已有账号?去登录',
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w800,
color: AppColors.primary,
child: Center(
child: AppGradientText(
_isLogin ? '没有账号?去注册' : '已有账号?去登录',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w800,
),
),
),
),
@@ -645,7 +666,7 @@ class _TextField extends StatelessWidget {
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(color: AppColors.primary, width: 1.2),
borderSide: const BorderSide(color: AppColors.auraIndigo, width: 1.2),
),
),
);
@@ -657,10 +678,10 @@ class _GradientPrefixIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ShaderMask(
shaderCallback: AppColors.doctorGradient.createShader,
blendMode: BlendMode.srcIn,
child: Icon(icon, size: 23, color: Colors.white),
return AppGradientIcon(
icon: icon,
size: 23,
gradient: _LoginPageState._loginActionGradient,
);
}
}
@@ -730,7 +751,7 @@ class _SmsButton extends StatelessWidget {
height: 50,
alignment: Alignment.center,
decoration: BoxDecoration(
gradient: disabled ? null : AppColors.doctorGradient,
gradient: disabled ? null : _LoginPageState._loginActionGradient,
color: disabled ? AppColors.cardInner : null,
borderRadius: BorderRadius.circular(14),
border: Border.all(
@@ -757,64 +778,67 @@ class _SmsButton extends StatelessWidget {
class _Agreement extends StatelessWidget {
final bool agreed;
final VoidCallback onTap;
const _Agreement({required this.agreed, required this.onTap});
final VoidCallback onTermsTap;
final VoidCallback onPrivacyTap;
const _Agreement({
required this.agreed,
required this.onTap,
required this.onTermsTap,
required this.onPrivacyTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
const textStyle = TextStyle(fontSize: 13, color: AppColors.textHint);
const linkStyle = TextStyle(fontSize: 13, fontWeight: FontWeight.w700);
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Container(
width: 18,
height: 18,
margin: const EdgeInsets.only(right: 7, top: 1),
margin: const EdgeInsets.only(right: 7),
decoration: BoxDecoration(
color: agreed ? AppColors.primary : Colors.white,
gradient: agreed ? _LoginPageState._loginActionGradient : null,
color: agreed ? null : Colors.white,
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: agreed ? AppColors.primary : AppColors.border,
color: agreed ? Colors.transparent : AppColors.border,
),
),
child: agreed
? const Icon(Icons.check, size: 13, color: Colors.white)
: null,
),
Flexible(
child: RichText(
text: const TextSpan(
children: [
TextSpan(
text: '已阅读并同意',
style: TextStyle(fontSize: 12, color: AppColors.textHint),
),
TextSpan(
text: '《服务协议》',
style: TextStyle(
fontSize: 12,
color: AppColors.primary,
fontWeight: FontWeight.w700,
),
),
TextSpan(
text: '',
style: TextStyle(fontSize: 12, color: AppColors.textHint),
),
TextSpan(
text: '《隐私政策》',
style: TextStyle(
fontSize: 12,
color: AppColors.primary,
fontWeight: FontWeight.w700,
),
),
],
),
Expanded(
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: const Text('已阅读并同意', style: textStyle),
),
),
GestureDetector(
onTap: onTermsTap,
behavior: HitTestBehavior.opaque,
child: const AppGradientText('《服务协议》', style: linkStyle),
),
const Text('', style: textStyle),
GestureDetector(
onTap: onPrivacyTap,
behavior: HitTestBehavior.opaque,
child: const AppGradientText('《隐私政策》', style: linkStyle),
),
],
),
],
),
),
],
);
}
}
@@ -882,7 +906,7 @@ class _PrimaryButton extends StatelessWidget {
height: 52,
alignment: Alignment.center,
decoration: BoxDecoration(
gradient: AppColors.doctorGradient,
gradient: _LoginPageState._loginActionGradient,
borderRadius: BorderRadius.circular(14),
boxShadow: AppColors.buttonShadow,
),