From 58af5f6d5b754fb6dd67e1f582d4569dd2357f7c Mon Sep 17 00:00:00 2001 From: MingNian <1281442923@qq.com> Date: Mon, 8 Jun 2026 18:06:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20UI=20=E6=B8=85=E6=96=B0=E9=A3=8E?= =?UTF-8?q?=E5=85=A8=E9=9D=A2=E6=94=B9=E9=80=A0=20=E2=80=94=20=E6=9C=9D?= =?UTF-8?q?=E9=9C=B2=E4=B8=BB=E9=A2=98=20+=20shadcn=5Fui=20+=20=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E5=AD=97=E5=8F=B7=E6=94=BE=E5=A4=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 全新"朝露"主题:深青绿主色(#2D6A4F),暖白底,完整设计Token - 引入 shadcn_ui 组件库,MaterialApp 注入 ShadTheme - 新增 4 个公共组件:AppCard、AppMenuItem、AppEmptyState、AppTabChip - 全面消除硬编码颜色,统一使用 AppTheme Token - 全局字号 +3~4pt,图标等比放大 +3px - home/medication/profile/settings/login 等核心页面重写 - 路由和功能逻辑零改动 --- health_app/lib/app.dart | 6 + health_app/lib/core/app_theme.dart | 193 ++++++++----- health_app/lib/pages/auth/login_page.dart | 229 ++++++++++++--- health_app/lib/pages/chart/trend_page.dart | 37 +-- .../consultation/consultation_pages.dart | 54 ++-- .../lib/pages/diet/diet_capture_page.dart | 50 ++-- health_app/lib/pages/home/home_page.dart | 252 +++++++++++------ .../home/widgets/chat_messages_view.dart | 213 +++++++------- .../medication/medication_checkin_page.dart | 27 +- .../medication/medication_edit_page.dart | 25 +- .../medication/medication_list_page.dart | 181 +++++++----- .../pages/profile/profile_detail_page.dart | 33 +-- .../lib/pages/profile/profile_page.dart | 170 +++++------ .../profile/service_package_detail_page.dart | 18 +- health_app/lib/pages/remaining_pages.dart | 118 ++++---- .../lib/pages/report/ai_analysis_page.dart | 61 ++-- health_app/lib/pages/report/report_pages.dart | 90 +++--- .../settings/notification_prefs_page.dart | 23 +- .../lib/pages/settings/settings_pages.dart | 109 ++++--- health_app/lib/widgets/app_card.dart | 46 +++ health_app/lib/widgets/app_empty_state.dart | 52 ++++ health_app/lib/widgets/app_menu_item.dart | 67 +++++ health_app/lib/widgets/app_tab_chip.dart | 39 +++ health_app/lib/widgets/health_drawer.dart | 41 +-- .../lib/widgets/service_package_card.dart | 18 +- health_app/pubspec.lock | 266 +++++++++++++++++- health_app/pubspec.yaml | 2 + health_app/test/widget_test.dart | 8 +- 28 files changed, 1616 insertions(+), 812 deletions(-) create mode 100644 health_app/lib/widgets/app_card.dart create mode 100644 health_app/lib/widgets/app_empty_state.dart create mode 100644 health_app/lib/widgets/app_menu_item.dart create mode 100644 health_app/lib/widgets/app_tab_chip.dart diff --git a/health_app/lib/app.dart b/health_app/lib/app.dart index 238c747..9795024 100644 --- a/health_app/lib/app.dart +++ b/health_app/lib/app.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; import 'core/app_router.dart'; import 'core/app_theme.dart'; import 'core/navigation_provider.dart'; @@ -26,6 +27,11 @@ class HealthApp extends ConsumerWidget { ], locale: const Locale('zh'), home: const _RootNavigator(), + // 注入 ShadTheme,让所有页面都能用 shadcn 组件 + builder: (context, child) => ShadTheme( + data: AppTheme.shadTheme, + child: child!, + ), ); } } diff --git a/health_app/lib/core/app_theme.dart b/health_app/lib/core/app_theme.dart index 9f6973a..50022fd 100644 --- a/health_app/lib/core/app_theme.dart +++ b/health_app/lib/core/app_theme.dart @@ -1,97 +1,160 @@ import 'package:flutter/material.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; -/// 健康管家 — Lavender Breeze 淡紫清风 +/// 健康管家 —「朝露」Morning Dew +/// 深青绿 + 薄荷白,植物系清新风格 class AppTheme { AppTheme._(); - // ── 颜色 ── - static const Color primary = Color(0xFF6C5CE7); - static const Color primaryLight = Color(0xFFEDEAFF); - static const Color primaryDark = Color(0xFF5A4BD1); - static const Color bg = Color(0xFFF8F9FC); - static const Color surface = Color(0xFFFFFFFF); - static const Color text = Color(0xFF2D2B32); - static const Color textSub = Color(0xFF8A8892); - static const Color textHint = Color(0xFFBFBCC4); - static const Color success = Color(0xFF6ECF8A); - static const Color error = Color(0xFFF56C6C); - static const Color warning = Color(0xFFF5A623); - static const Color accent = Color(0xFFFF8068); - static const Color border = Color(0xFFEAEAF0); - static const Color divider = Color(0xFFF2F2F6); + // ── 主色调:深青绿 ── + static const Color primary = Color(0xFF2D6A4F); // 深青绿 + static const Color primaryLight = Color(0xFFD8F3DC); // 浅薄荷 + static const Color primaryDark = Color(0xFF1B4332); // 深森林 + static const Color primaryGlow = Color(0xFFB7E4C7); // 发光薄荷 - // ── 圆角 Token ── - static const double radiusXs = 8; - static const double radiusSm = 12; - static const double radiusMd = 16; - static const double radiusLg = 20; - static const double radiusXl = 24; - static const double radiusPill = 999; + // ── 中性色 ── + static const Color bg = Color(0xFFF7FAF8); // 暖白底 + static const Color surface = Color(0xFFFFFFFF); + static const Color text = Color(0xFF1B281F); // 深绿黑 + static const Color textSub = Color(0xFF6B7B6F); // 灰绿 + static const Color textHint = Color(0xFFB7BFB9); // 浅灰绿 + static const Color border = Color(0xFFE0E8E2); + static const Color divider = Color(0xFFF0F4F1); - // ── 间距 Token ── - static const double spaceXs = 4; - static const double spaceSm = 8; - static const double spaceMd = 12; - static const double spaceLg = 16; - static const double spaceXl = 20; + // ── 语义色 ── + static const Color success = Color(0xFF40916C); // 翠绿 + static const Color error = Color(0xFFE07A5F); // 陶土红 + static const Color warning = Color(0xFFF2CC8F); // 蜂蜜黄 + static const Color accent = Color(0xFF81B29A); // 中青绿 + static const Color info = Color(0xFF52B788); // 亮绿 - // ── 阴影 Token ── - static BoxShadow get shadowCard => BoxShadow(color: primary.withAlpha(15), blurRadius: 12, offset: const Offset(0, 4)); - static BoxShadow get shadowLight => BoxShadow(color: primary.withAlpha(10), blurRadius: 8, offset: const Offset(0, 2)); + // ── 圆角 ── + static const double rXs = 6; + static const double rSm = 10; + static const double rMd = 14; + static const double rLg = 20; + static const double rXl = 28; + static const double rPill = 999; - /// 每个智能体的卡片色调 + // ── 间距 ── + static const double sXs = 4; + static const double sSm = 8; + static const double sMd = 12; + static const double sLg = 16; + static const double sXl = 20; + static const double sXxl = 28; + + // ── 阴影 ── + static BoxShadow get shadowCard => + BoxShadow(color: primary.withAlpha(14), blurRadius: 20, offset: const Offset(0, 4)); + static BoxShadow get shadowLight => + BoxShadow(color: primary.withAlpha(8), blurRadius: 12, offset: const Offset(0, 2)); + static BoxShadow get shadowElevated => + BoxShadow(color: primary.withAlpha(20), blurRadius: 30, offset: const Offset(0, 8)); + + // ── 智能体配色 ── static const Map agentColors = { - 'default': Color(0xFFE8ECFF), // 淡蓝紫 - 'consultation': Color(0xFFE8F5FF), // 淡天蓝 - 'health': Color(0xFFE8FFF0), // 淡薄荷 - 'diet': Color(0xFFFFF2E8), // 淡杏 - 'medication': Color(0xFFFFE8F0), // 淡粉 - 'report': Color(0xFFE8F4FF), // 淡水蓝 + 'default': Color(0xFFD8F3DC), // 薄荷 + 'consultation': Color(0xFFE0F0FF), // 淡天蓝 + 'health': Color(0xFFE8FFE8), // 淡翠绿 + 'diet': Color(0xFFFFF0E0), // 淡杏 + 'medication': Color(0xFFFFE8EC), // 淡粉 + 'report': Color(0xFFE8F0FF), // 淡水蓝 'exercise': Color(0xFFF0E8FF), // 淡紫 }; static Color agentLight(String? name) => agentColors[name] ?? primaryLight; + // ── Material 主题 ── static ThemeData get lightTheme => ThemeData( useMaterial3: true, - colorScheme: ColorScheme.fromSeed(seedColor: primary, primary: primary, surface: bg, brightness: Brightness.light), + colorScheme: ColorScheme.fromSeed( + seedColor: primary, + primary: primary, + surface: bg, + brightness: Brightness.light, + ), scaffoldBackgroundColor: bg, + fontFamily: null, // 使用系统默认字体 appBarTheme: const AppBarTheme( - backgroundColor: surface, foregroundColor: text, elevation: 0, - centerTitle: true, scrolledUnderElevation: 0, - titleTextStyle: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: text), + backgroundColor: surface, + foregroundColor: text, + elevation: 0, + centerTitle: true, + scrolledUnderElevation: 0, + titleTextStyle: TextStyle(fontSize: 23, fontWeight: FontWeight.w600, color: text), ), - cardTheme: CardThemeData(color: surface, elevation: 0, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(radiusXl)), margin: EdgeInsets.zero), + cardTheme: CardThemeData( + color: surface, + elevation: 0, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rLg)), + margin: EdgeInsets.zero, + ), inputDecorationTheme: InputDecorationTheme( - filled: true, fillColor: const Color(0xFFF4F5FA), - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), - border: OutlineInputBorder(borderRadius: BorderRadius.circular(radiusMd), borderSide: BorderSide.none), - enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(radiusMd), borderSide: BorderSide.none), - focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(radiusMd), borderSide: const BorderSide(color: primary, width: 1.5)), - hintStyle: const TextStyle(color: textHint, fontSize: 15), + filled: true, + fillColor: bg, + contentPadding: const EdgeInsets.symmetric(horizontal: sLg, vertical: 16), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(rMd), borderSide: BorderSide.none), + enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(rMd), borderSide: BorderSide.none), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(rMd), + borderSide: const BorderSide(color: primary, width: 2), + ), + hintStyle: const TextStyle(color: textHint, fontSize: 20), ), - elevatedButtonTheme: ElevatedButtonThemeData(style: ElevatedButton.styleFrom( - backgroundColor: primary, foregroundColor: Colors.white, - minimumSize: const Size(double.infinity, 52), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(radiusLg)), - textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), elevation: 0, - )), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: primary, + foregroundColor: Colors.white, + minimumSize: const Size(double.infinity, 56), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rPill)), + textStyle: const TextStyle(fontSize: 21, fontWeight: FontWeight.w600), + elevation: 0, + ), + ), - dialogTheme: DialogThemeData(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(radiusXl))), + dialogTheme: DialogThemeData(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rXl))), textTheme: const TextTheme( - headlineLarge: TextStyle(fontSize: 24, fontWeight: FontWeight.w700, color: text), - titleLarge: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: text), - titleMedium: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: text), - bodyLarge: TextStyle(fontSize: 16, color: text, height: 1.5), - bodyMedium: TextStyle(fontSize: 15, color: textSub, height: 1.4), - labelMedium: TextStyle(fontSize: 13, color: textSub), - labelSmall: TextStyle(fontSize: 11, color: textHint), + headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.w700, color: text, letterSpacing: -0.5), + titleLarge: TextStyle(fontSize: 23, fontWeight: FontWeight.w600, color: text), + titleMedium: TextStyle(fontSize: 21, fontWeight: FontWeight.w600, color: text), + bodyLarge: TextStyle(fontSize: 21, color: text, height: 1.5), + bodyMedium: TextStyle(fontSize: 20, color: textSub, height: 1.4), + labelMedium: TextStyle(fontSize: 18, color: textSub), + labelSmall: TextStyle(fontSize: 16, color: textHint), ), ); + + // ── Shadcn UI 主题 ── + static ShadThemeData get shadTheme => ShadThemeData( + brightness: Brightness.light, + colorScheme: const ShadVioletColorScheme.light( + background: bg, + foreground: text, + card: surface, + cardForeground: text, + popover: surface, + popoverForeground: text, + primary: primary, + primaryForeground: Colors.white, + secondary: Color(0xFFF0F4F1), + secondaryForeground: text, + muted: Color(0xFFF0F4F1), + mutedForeground: textSub, + accent: accent, + accentForeground: Colors.white, + destructive: error, + destructiveForeground: Colors.white, + border: border, + input: border, + ring: primary, + selection: Color(0xFFB7E4C7), + ), + radius: BorderRadius.circular(rMd), + ); } diff --git a/health_app/lib/pages/auth/login_page.dart b/health_app/lib/pages/auth/login_page.dart index feae0ee..4c77879 100644 --- a/health_app/lib/pages/auth/login_page.dart +++ b/health_app/lib/pages/auth/login_page.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import '../../core/app_theme.dart'; import '../../core/navigation_provider.dart'; import '../../providers/auth_provider.dart'; class LoginPage extends ConsumerStatefulWidget { const LoginPage({super.key}); - @override ConsumerState createState() => _LoginPageState(); } @@ -32,54 +33,216 @@ class _LoginPageState extends ConsumerState { } void _startCountdown() async { - for (var i = 60; i > 0; i--) { await Future.delayed(const Duration(seconds: 1)); if (!mounted) return; setState(() => _countdown = i - 1); } + for (var i = 60; i > 0; i--) { + await Future.delayed(const Duration(seconds: 1)); + if (!mounted) return; + setState(() => _countdown = i - 1); + } } Future _login() async { if (!_agreed) { setState(() => _error = '请阅读并同意服务协议和隐私政策'); return; } setState(() { _loading = true; _error = null; }); final err = await ref.read(authProvider.notifier).login(_phoneCtrl.text.trim(), _codeCtrl.text.trim()); - setState(() => _loading = false ); + setState(() => _loading = false); if (err != null) { setState(() => _error = err); return; } goRoute(ref, 'home'); } @override Widget build(BuildContext context) { final authState = ref.watch(authProvider); - if (authState.isLoggedIn && !authState.isLoading) WidgetsBinding.instance.addPostFrameCallback((_) => goRoute(ref, 'home')); + final theme = ShadTheme.of(context); + if (authState.isLoggedIn && !authState.isLoading) { + WidgetsBinding.instance.addPostFrameCallback((_) => goRoute(ref, 'home')); + } return Scaffold( body: Container( - decoration: const BoxDecoration(gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFFF0F2FF), Color(0xFFF0F2FF), Color(0xFFE8E4FF)])), - child: SafeArea(child: Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 32), child: Column(mainAxisSize: MainAxisSize.min, children: [ - const SizedBox(height: 24), - Container(width: 100, height: 100, decoration: BoxDecoration(color: const Color(0xFF6C5CE7).withAlpha(20), borderRadius: BorderRadius.circular(50)), child: Stack(alignment: Alignment.center, children: [ - Container(width: 72, height: 72, decoration: BoxDecoration(color: Colors.white.withAlpha(200), borderRadius: BorderRadius.circular(36)), child: const Icon(Icons.favorite, size: 36, color: Color(0xFF6C5CE7))), - ])), - const SizedBox(height: 20), - const Text('健康管家', style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Color(0xFF1A1A1A))), - const SizedBox(height: 8), - Text('你的 AI 心脏健康管家', style: TextStyle(fontSize: 15, color: Colors.grey[500])), - const SizedBox(height: 36), - TextField(controller: _phoneCtrl, keyboardType: TextInputType.phone, maxLength: 11, - decoration: InputDecoration(hintText: '请输入手机号', prefixIcon: const Padding(padding: EdgeInsets.only(left: 12), child: Text('+86', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500))), counterText: '', filled: true, fillColor: Colors.white, border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Color(0xFF6C5CE7), width: 1.5)))), - const SizedBox(height: 16), - Row(children: [ - Expanded(child: TextField(controller: _codeCtrl, keyboardType: TextInputType.number, maxLength: 6, - decoration: InputDecoration(hintText: '验证码', filled: true, fillColor: Colors.white, border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Color(0xFF6C5CE7), width: 1.5)), counterText: ''))), - const SizedBox(width: 12), - GestureDetector(onTap: (_countdown > 0 || _sending) ? null : _sendSms, child: Container(width: 100, height: 48, alignment: Alignment.center, decoration: BoxDecoration(color: _countdown > 0 ? Colors.grey[300] : const Color(0xFF6C5CE7), borderRadius: BorderRadius.circular(12)), child: Text(_sending ? '发送中' : _countdown > 0 ? '${_countdown}s' : '获取验证码', style: TextStyle(fontSize: 14, color: _countdown > 0 ? Colors.grey[600] : Colors.white, fontWeight: FontWeight.w500)))), + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [AppTheme.bg, AppTheme.bg, AppTheme.primaryLight], + ), + ), + child: SafeArea(child: Center(child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + const SizedBox(height: 24), + // Logo + Container( + width: 100, height: 100, + decoration: BoxDecoration( + color: AppTheme.primary.withAlpha(25), + borderRadius: BorderRadius.circular(50), + ), + child: Stack(alignment: Alignment.center, children: [ + Container( + width: 72, height: 72, + decoration: BoxDecoration( + color: Colors.white.withAlpha(200), + borderRadius: BorderRadius.circular(36), + ), + child: const Icon(LucideIcons.heart, size: 36, color: AppTheme.primary), + ), + ]), + ), + const SizedBox(height: 20), + Text('健康管家', style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: theme.colorScheme.foreground)), + const SizedBox(height: 8), + Text('你的 AI 心脏健康管家', style: TextStyle(fontSize: 18, color: theme.colorScheme.mutedForeground)), + const SizedBox(height: 36), + + // 手机号输入 + _buildPhoneInput(theme), + const SizedBox(height: AppTheme.sLg), + + // 验证码输入 + _buildCodeRow(theme), + const SizedBox(height: AppTheme.sSm), + + // 协议勾选 + _buildAgreement(theme), + + // 错误提示 + if (_error != null) + Padding(padding: const EdgeInsets.only(top: AppTheme.sMd), child: Text(_error!, style: TextStyle(color: theme.colorScheme.destructive, fontSize: 16))), + + const SizedBox(height: AppTheme.rXl), + + // 登录按钮 + _buildLoginButton(theme), + + const SizedBox(height: AppTheme.sXl), ]), - const SizedBox(height: 8), - Align(alignment: Alignment.centerLeft, child: GestureDetector(onTap: () => setState(() => _agreed = !_agreed), child: Row(mainAxisSize: MainAxisSize.min, children: [ - Container(width: 20, height: 20, margin: const EdgeInsets.only(right: 6), decoration: BoxDecoration(shape: BoxShape.rectangle, color: _agreed ? const Color(0xFF6C5CE7) : Colors.transparent, border: Border.all(color: _agreed ? const Color(0xFF6C5CE7) : const Color(0xFFBDBDBD), width: 1.5), borderRadius: BorderRadius.circular(4)), child: _agreed ? const Icon(Icons.check, size: 14, color: Colors.white) : null), - RichText(text: TextSpan(children: [TextSpan(text: '已阅读并同意', style: TextStyle(fontSize: 13, color: Colors.grey[600])), TextSpan(text: '《服务协议》', style: const TextStyle(fontSize: 13, color: Color(0xFF6C5CE7))), TextSpan(text: '和', style: TextStyle(fontSize: 13, color: Colors.grey[600])), TextSpan(text: '《隐私政策》', style: const TextStyle(fontSize: 13, color: Color(0xFF6C5CE7)))])), - ]))), - if (_error != null) Padding(padding: const EdgeInsets.only(top: 12), child: Text(_error!, style: const TextStyle(color: Color(0xFFE53935), fontSize: 13))), - const SizedBox(height: 24), - GestureDetector(onTap: _loading ? null : _login, child: Container(width: double.infinity, height: 50, alignment: Alignment.center, decoration: BoxDecoration(gradient: const LinearGradient(colors: [Color(0xFFA8B5FA), Color(0xFF6C5CE7)]), borderRadius: BorderRadius.circular(25), boxShadow: [BoxShadow(color: const Color(0xFF6C5CE7).withAlpha(80), blurRadius: 16, offset: const Offset(0, 8))]), child: _loading ? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2.5, color: Colors.white)) : const Text('登 录', style: TextStyle(fontSize: 17, color: Colors.white, fontWeight: FontWeight.w600, letterSpacing: 2)))), - const SizedBox(height: 20), - ])))), + ))), + ), + ); + } + + Widget _buildPhoneInput(ShadThemeData theme) { + return Container( + decoration: BoxDecoration( + color: theme.colorScheme.card, + borderRadius: BorderRadius.circular(AppTheme.rSm), + boxShadow: [AppTheme.shadowLight], + ), + child: Row(children: [ + const Padding(padding: EdgeInsets.only(left: AppTheme.sLg), child: Text('+86', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w500, color: AppTheme.text))), + Container(width: 1, height: 24, color: theme.colorScheme.border, margin: const EdgeInsets.symmetric(horizontal: AppTheme.sMd)), + Expanded( + child: TextField( + controller: _phoneCtrl, + keyboardType: TextInputType.phone, + maxLength: 11, + style: TextStyle(fontSize: 19, color: theme.colorScheme.foreground), + decoration: InputDecoration( + hintText: '请输入手机号', + hintStyle: TextStyle(color: theme.colorScheme.mutedForeground), + border: InputBorder.none, + counterText: '', + contentPadding: const EdgeInsets.symmetric(vertical: AppTheme.sLg), + ), + ), + ), + ]), + ); + } + + Widget _buildCodeRow(ShadThemeData theme) { + final codeDisabled = _countdown > 0 || _sending; + return Row(children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + color: theme.colorScheme.card, + borderRadius: BorderRadius.circular(AppTheme.rSm), + boxShadow: [AppTheme.shadowLight], + ), + child: TextField( + controller: _codeCtrl, + keyboardType: TextInputType.number, + maxLength: 6, + style: TextStyle(fontSize: 19, color: theme.colorScheme.foreground), + decoration: InputDecoration( + hintText: '验证码', + hintStyle: TextStyle(color: theme.colorScheme.mutedForeground), + border: InputBorder.none, + counterText: '', + contentPadding: const EdgeInsets.symmetric(horizontal: AppTheme.sLg, vertical: AppTheme.sLg), + ), + ), + ), + ), + const SizedBox(width: AppTheme.sMd), + GestureDetector( + onTap: codeDisabled ? null : _sendSms, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 110, height: 52, + alignment: Alignment.center, + decoration: BoxDecoration( + color: codeDisabled ? theme.colorScheme.muted : AppTheme.primary, + borderRadius: BorderRadius.circular(AppTheme.rSm), + ), + child: Text( + _sending ? '发送中' : _countdown > 0 ? '${_countdown}s' : '获取验证码', + style: TextStyle( + fontSize: 17, + color: codeDisabled ? theme.colorScheme.mutedForeground : Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ]); + } + + Widget _buildAgreement(ShadThemeData theme) { + return Align( + alignment: Alignment.centerLeft, + child: GestureDetector( + onTap: () => setState(() => _agreed = !_agreed), + child: Row(mainAxisSize: MainAxisSize.min, children: [ + Container( + width: 18, height: 18, + margin: const EdgeInsets.only(right: 6), + decoration: BoxDecoration( + color: _agreed ? AppTheme.primary : Colors.transparent, + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: _agreed ? AppTheme.primary : theme.colorScheme.border, + width: 1.5, + ), + ), + child: _agreed ? Icon(LucideIcons.check, size: 15, color: theme.colorScheme.primaryForeground) : null, + ), + RichText(text: TextSpan(children: [ + TextSpan(text: '已阅读并同意', style: TextStyle(fontSize: 16, color: theme.colorScheme.mutedForeground)), + TextSpan(text: '《服务协议》', style: const TextStyle(fontSize: 16, color: AppTheme.primary)), + TextSpan(text: '和', style: TextStyle(fontSize: 16, color: theme.colorScheme.mutedForeground)), + TextSpan(text: '《隐私政策》', style: const TextStyle(fontSize: 16, color: AppTheme.primary)), + ])), + ]), + ), + ); + } + + Widget _buildLoginButton(ShadThemeData theme) { + return GestureDetector( + onTap: _loading ? null : _login, + child: Container( + width: double.infinity, + height: 52, + alignment: Alignment.center, + decoration: BoxDecoration( + gradient: const LinearGradient(colors: [Color(0xFF9B8FEF), AppTheme.primary]), + borderRadius: BorderRadius.circular(AppTheme.rPill), + boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(80), blurRadius: 16, offset: const Offset(0, 8))], + ), + child: _loading + ? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2.5, color: Colors.white)) + : const Text('登 录', style: TextStyle(fontSize: 20, color: Colors.white, fontWeight: FontWeight.w600, letterSpacing: 2)), ), ); } diff --git a/health_app/lib/pages/chart/trend_page.dart b/health_app/lib/pages/chart/trend_page.dart index bc6e9f7..020759d 100644 --- a/health_app/lib/pages/chart/trend_page.dart +++ b/health_app/lib/pages/chart/trend_page.dart @@ -1,6 +1,7 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../core/app_theme.dart'; import '../../providers/auth_provider.dart'; import '../../providers/data_providers.dart'; @@ -109,7 +110,7 @@ class _TrendPageState extends ConsumerState { builder: (ctx) => Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(ctx).viewInsets.bottom, left: 20, right: 20, top: 20), child: Column(mainAxisSize: MainAxisSize.min, children: [ - Text('录入$_name', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700)), + Text('录入$_name', style: const TextStyle(fontSize: 21, fontWeight: FontWeight.w700)), const SizedBox(height: 16), if (_isBP) ...[ Row(children: [ @@ -155,7 +156,7 @@ class _TrendPageState extends ConsumerState { }, style: ElevatedButton.styleFrom(backgroundColor: _color, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), padding: const EdgeInsets.symmetric(vertical: 14)), - child: const Text('确认录入', style: TextStyle(fontSize: 16)), + child: const Text('确认录入', style: TextStyle(fontSize: 19)), )), const SizedBox(height: 20), ]), @@ -165,7 +166,7 @@ class _TrendPageState extends ConsumerState { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color(0xFFF8F9FC), + backgroundColor: AppTheme.bg, appBar: AppBar(title: const Text('健康概览'), centerTitle: true), floatingActionButton: FloatingActionButton( onPressed: _showAddDialog, @@ -173,7 +174,7 @@ class _TrendPageState extends ConsumerState { child: const Icon(Icons.add), ), body: _loading - ? const Center(child: CircularProgressIndicator(color: Color(0xFF6C5CE7))) + ? const Center(child: CircularProgressIndicator(color: AppTheme.primary)) : RefreshIndicator( onRefresh: _loadAll, child: SingleChildScrollView( @@ -211,7 +212,7 @@ class _TrendPageState extends ConsumerState { border: Border.all(color: sel ? color : const Color(0xFFE0E0E0)), ), child: Text(m['label'] as String, style: TextStyle( - fontSize: 14, fontWeight: FontWeight.w600, + fontSize: 17, fontWeight: FontWeight.w600, color: sel ? Colors.white : const Color(0xFF666666)), ), ), @@ -230,7 +231,7 @@ class _TrendPageState extends ConsumerState { const SizedBox(height: 8), Text('暂无$_name数据', style: const TextStyle(color: Color(0xFFBBBBBB))), const SizedBox(height: 4), - const Text('点击右下角 + 手动录入', style: TextStyle(fontSize: 12, color: Color(0xFFCCCCCC))), + const Text('点击右下角 + 手动录入', style: TextStyle(fontSize: 15, color: Color(0xFFCCCCCC))), ])), ); } @@ -258,11 +259,11 @@ class _TrendPageState extends ConsumerState { child: Column(children: [ Row(children: [ const SizedBox(width: 8), - Text(_name, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: _color)), + Text(_name, style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: _color)), const SizedBox(width: 6), - Text('趋势 (${_filtered.length}条)', style: const TextStyle(fontSize: 12, color: Color(0xFF999999))), + Text('趋势 (${_filtered.length}条)', style: const TextStyle(fontSize: 15, color: Color(0xFF999999))), const Spacer(), - Text('单位: $_unit', style: const TextStyle(fontSize: 11, color: Color(0xFFBBBBBB))), + Text('单位: $_unit', style: const TextStyle(fontSize: 14, color: Color(0xFFBBBBBB))), ]), const SizedBox(height: 10), SizedBox( @@ -280,7 +281,7 @@ class _TrendPageState extends ConsumerState { leftTitles: AxisTitles(sideTitles: SideTitles( showTitles: true, reservedSize: 40, getTitlesWidget: (v, meta) => Text(v.toStringAsFixed(v.truncateToDouble() == v ? 0 : 1), - style: const TextStyle(fontSize: 10, color: Color(0xFF999999))), + style: const TextStyle(fontSize: 13, color: Color(0xFF999999))), )), bottomTitles: AxisTitles(sideTitles: SideTitles( showTitles: true, reservedSize: 24, @@ -289,7 +290,7 @@ class _TrendPageState extends ConsumerState { final idx = v.toInt(); if (idx < 0 || idx >= _filtered.length) return const SizedBox.shrink(); final d = _filtered[idx]['date'] as DateTime; - return Text('${d.month}/${d.day}', style: const TextStyle(fontSize: 9, color: Color(0xFFBBBBBB))); + return Text('${d.month}/${d.day}', style: const TextStyle(fontSize: 12, color: Color(0xFFBBBBBB))); }, )), topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), @@ -321,7 +322,7 @@ class _TrendPageState extends ConsumerState { final v = _isBP ? '${r['systolic']}/${r['diastolic']}' : '${r['value']}'; return LineTooltipItem( '${d.month}/${d.day} ${d.hour}:${d.minute.toString().padLeft(2, '0')}\n$v $_unit', - TextStyle(color: _color, fontSize: 12, fontWeight: FontWeight.w600), + TextStyle(color: _color, fontSize: 15, fontWeight: FontWeight.w600), ); }).toList(), ), @@ -338,9 +339,9 @@ class _TrendPageState extends ConsumerState { if (_filtered.isEmpty) return const SizedBox.shrink(); return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ - const Text('历史记录', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700)), + const Text('历史记录', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w700)), const Spacer(), - Text('${_filtered.length}条', style: const TextStyle(fontSize: 13, color: Color(0xFF999999))), + Text('${_filtered.length}条', style: const TextStyle(fontSize: 16, color: Color(0xFF999999))), ]), const SizedBox(height: 10), ..._filtered.reversed.take(30).map((r) { @@ -359,20 +360,20 @@ class _TrendPageState extends ConsumerState { child: Row(children: [ Container(width: 40, height: 40, decoration: BoxDecoration( color: _color.withAlpha(20), borderRadius: BorderRadius.circular(10)), - child: Center(child: Text(_getMetricIcon(_selected), style: const TextStyle(fontSize: 18))), + child: Center(child: Text(_getMetricIcon(_selected), style: const TextStyle(fontSize: 21))), ), const SizedBox(width: 12), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('${date.month}月${date.day}日 ${date.hour}:${date.minute.toString().padLeft(2, '0')}', - style: const TextStyle(fontSize: 12, color: Color(0xFF999999))), + style: const TextStyle(fontSize: 15, color: Color(0xFF999999))), const SizedBox(height: 2), - Text('$display $_unit', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700, + Text('$display $_unit', style: TextStyle(fontSize: 23, fontWeight: FontWeight.w700, color: abnormal ? const Color(0xFFEF4444) : const Color(0xFF1A1A1A))), ])), if (abnormal) Container(padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration(color: const Color(0xFFFEE2E2), borderRadius: BorderRadius.circular(6)), - child: const Text('异常', style: TextStyle(fontSize: 11, color: Color(0xFFEF4444), fontWeight: FontWeight.w500))), + child: const Text('异常', style: TextStyle(fontSize: 14, color: Color(0xFFEF4444), fontWeight: FontWeight.w500))), ]), ); }), diff --git a/health_app/lib/pages/consultation/consultation_pages.dart b/health_app/lib/pages/consultation/consultation_pages.dart index b1ba26c..997b0ff 100644 --- a/health_app/lib/pages/consultation/consultation_pages.dart +++ b/health_app/lib/pages/consultation/consultation_pages.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import '../../core/app_theme.dart'; import '../../core/navigation_provider.dart'; import '../../providers/data_providers.dart'; import '../../providers/consultation_provider.dart'; @@ -42,7 +44,7 @@ class DoctorListPage extends ConsumerWidget { color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ - BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(15), blurRadius: 12, offset: const Offset(0, 4)) + BoxShadow(color: AppTheme.primary.withAlpha(15), blurRadius: 12, offset: const Offset(0, 4)) ], ), child: Padding( @@ -52,26 +54,26 @@ class DoctorListPage extends ConsumerWidget { children: [ CircleAvatar( radius: 28, - backgroundColor: const Color(0xFFF0F2FF), + backgroundColor: AppTheme.primaryLight, child: Text( (d['name'] as String?)?.isNotEmpty == true ? d['name']![0] : '?', - style: const TextStyle(fontSize: 22, fontWeight: FontWeight.w600, color: Color(0xFF8B9CF7)), + style: const TextStyle(fontSize: 25, fontWeight: FontWeight.w600, color: AppTheme.primaryLight), ), ), const SizedBox(height: 10), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( - color: const Color(0xFFF0F2FF), + color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(6), ), - child: Text(d['title'] ?? '', style: const TextStyle(fontSize: 12, color: Color(0xFF8B9CF7), fontWeight: FontWeight.w500)), + child: Text(d['title'] ?? '', style: const TextStyle(fontSize: 15, color: AppTheme.primaryLight, fontWeight: FontWeight.w500)), ), const SizedBox(height: 6), - Text(d['department'] ?? '', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF333333))), + Text(d['department'] ?? '', style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: Color(0xFF333333))), const SizedBox(height: 8), Text(d['introduction'] ?? '', - style: const TextStyle(fontSize: 12, color: Color(0xFF888888), height: 1.4), + style: const TextStyle(fontSize: 15, color: Color(0xFF888888), height: 1.4), maxLines: 4, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center), @@ -82,12 +84,12 @@ class DoctorListPage extends ConsumerWidget { child: ElevatedButton( onPressed: () => pushRoute(ref, 'consultation', params: {'id': d['id']?.toString() ?? ''}), style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF8B9CF7), + backgroundColor: AppTheme.primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), padding: EdgeInsets.zero, ), - child: const Text('立即咨询', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600)), + child: const Text('立即咨询', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), ), ), ], @@ -164,14 +166,14 @@ class _DoctorChatPageState extends ConsumerState { final canSend = state.status == 'AiTalking' && !state.isSending; return Scaffold( - backgroundColor: const Color(0xFFF8F9FC), + backgroundColor: AppTheme.bg, appBar: AppBar( backgroundColor: Colors.white, title: Column(children: [ Text(state.doctorName.isNotEmpty ? '${state.doctorName} · ${state.doctorDepartment}' : '问诊对话', - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), if (state.doctorName.isNotEmpty) - Text(_statusText(state.status), style: const TextStyle(fontSize: 12, color: Color(0xFF8B9CF7))), + Text(_statusText(state.status), style: const TextStyle(fontSize: 15, color: AppTheme.primaryLight)), ]), centerTitle: true, actions: [ @@ -179,16 +181,16 @@ class _DoctorChatPageState extends ConsumerState { margin: const EdgeInsets.only(right: 12), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( - color: const Color(0xFFF0F2FF), + color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(12), ), child: Text('剩余${state.quotaRemaining}/${state.quotaTotal}次', - style: const TextStyle(fontSize: 12, color: Color(0xFF8B9CF7), fontWeight: FontWeight.w500)), + style: const TextStyle(fontSize: 15, color: AppTheme.primaryLight, fontWeight: FontWeight.w500)), ), ], ), body: state.isLoading - ? const Center(child: CircularProgressIndicator(color: Color(0xFF8B9CF7))) + ? const Center(child: CircularProgressIndicator(color: AppTheme.primaryLight)) : Column(children: [ Expanded(child: _buildMessageList(state)), _buildInputBar(canSend), @@ -220,7 +222,7 @@ class _DoctorChatPageState extends ConsumerState { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.82), decoration: BoxDecoration( - color: isUser ? const Color(0xFF8B9CF7) : const Color(0xFFFEFEFF), + color: isUser ? AppTheme.primary : const Color(0xFFFEFEFF), borderRadius: BorderRadius.only( topLeft: Radius.circular(isUser ? 20 : 4), topRight: Radius.circular(isUser ? 4 : 20), @@ -230,26 +232,26 @@ class _DoctorChatPageState extends ConsumerState { border: isUser ? null : Border.all(color: const Color(0xFFD8DCFD), width: 1.5), boxShadow: isUser ? [] - : [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(12), blurRadius: 10, offset: const Offset(0, 3))], + : [BoxShadow(color: AppTheme.primary.withAlpha(12), blurRadius: 10, offset: const Offset(0, 3))], ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ if (!isUser && msg.senderName != null) ...[ Row(children: [ const CircleAvatar(radius: 10, backgroundColor: Color(0xFFF0F2FF), - child: Icon(Icons.smart_toy, size: 12, color: Color(0xFF8B9CF7))), + child: Icon(Icons.smart_toy, size: 15, color: AppTheme.primaryLight)), const SizedBox(width: 6), - Text(msg.senderName!, style: const TextStyle(fontSize: 12, color: Color(0xFF9E9E9E))), + Text(msg.senderName!, style: const TextStyle(fontSize: 15, color: Color(0xFF9E9E9E))), ]), const SizedBox(height: 8), ], if (isUser) - Text(msg.content, style: const TextStyle(fontSize: 16, color: Colors.white, height: 1.4)) + Text(msg.content, style: const TextStyle(fontSize: 19, color: Colors.white, height: 1.4)) else MarkdownBody( data: msg.content, selectable: true, styleSheet: MarkdownStyleSheet( - p: const TextStyle(fontSize: 16, color: Color(0xFF1A1A1A), height: 1.5), + p: const TextStyle(fontSize: 19, color: Color(0xFF1A1A1A), height: 1.5), ), ), if (isAi) ...[ @@ -261,7 +263,7 @@ class _DoctorChatPageState extends ConsumerState { borderRadius: BorderRadius.circular(6), ), child: Text('🏷️ 以上为AI分析,具体请咨询${state.doctorName}', - style: const TextStyle(fontSize: 10, color: Color(0xFFF9A825))), + style: const TextStyle(fontSize: 13, color: Color(0xFFF9A825))), ), ], ]), @@ -281,10 +283,10 @@ class _DoctorChatPageState extends ConsumerState { child: TextField( controller: _textCtrl, enabled: canSend, - style: const TextStyle(fontSize: 15), + style: const TextStyle(fontSize: 18), decoration: InputDecoration( hintText: canSend ? '描述您的症状...' : '对话已结束', - hintStyle: const TextStyle(fontSize: 15, color: Color(0xFFBBBBBB)), + hintStyle: const TextStyle(fontSize: 18, color: Color(0xFFBBBBBB)), contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), border: InputBorder.none, ), @@ -292,8 +294,8 @@ class _DoctorChatPageState extends ConsumerState { ), ), IconButton( - icon: Icon(Icons.send, size: 24, - color: canSend ? const Color(0xFF8B9CF7) : const Color(0xFFCCCCCC)), + icon: Icon(Icons.send, size: 28, + color: canSend ? AppTheme.primary : const Color(0xFFCCCCCC)), onPressed: canSend ? _send : null, ), ]), diff --git a/health_app/lib/pages/diet/diet_capture_page.dart b/health_app/lib/pages/diet/diet_capture_page.dart index e5b3285..8710169 100644 --- a/health_app/lib/pages/diet/diet_capture_page.dart +++ b/health_app/lib/pages/diet/diet_capture_page.dart @@ -351,9 +351,9 @@ class _DietCapturePageState extends ConsumerState { : null, ), child: Column(mainAxisSize: MainAxisSize.min, children: [ - Icon(m.icon, size: 20, color: isSelected ? Colors.white : _kSubText), + Icon(m.icon, size: 23, color: isSelected ? Colors.white : _kSubText), const SizedBox(height: 4), - Text(m.label, style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: isSelected ? Colors.white : _kSubText)), + Text(m.label, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: isSelected ? Colors.white : _kSubText)), ]), ), ), @@ -376,15 +376,15 @@ class _DietCapturePageState extends ConsumerState { alignment: Alignment.center, children: [ const CircularProgressIndicator(strokeWidth: 3, color: _kPrimary), - const Icon(Icons.restaurant, size: 22, color: _kPrimary), + const Icon(Icons.restaurant, size: 25, color: _kPrimary), ], ), ), const SizedBox(height: 20), - const Text('正在识别食物...', style: TextStyle(fontSize: 15, color: _kSubText)), + const Text('正在识别食物...', style: TextStyle(fontSize: 18, color: _kSubText)), if (state.errorMessage != null) ...[ const SizedBox(height: 8), - Text(state.errorMessage!, style: const TextStyle(fontSize: 13, color: AppTheme.error), textAlign: TextAlign.center), + Text(state.errorMessage!, style: const TextStyle(fontSize: 16, color: AppTheme.error), textAlign: TextAlign.center), ], ]), ), @@ -411,16 +411,16 @@ class _DietCapturePageState extends ConsumerState { Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration(color: _kPrimaryLight, borderRadius: BorderRadius.circular(8)), - child: const Text('识别结果', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: _kPrimary)), + child: const Text('识别结果', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: _kPrimary)), ), const Spacer(), Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration(color: const Color(0xFFFFF8EE), borderRadius: BorderRadius.circular(8)), - child: Text('共 $totalCal kcal', style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: _kWarning)), + child: Text('共 $totalCal kcal', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: _kWarning)), ), IconButton( - icon: const Icon(Icons.add_circle_outline, size: 22, color: _kPrimary), + icon: const Icon(Icons.add_circle_outline, size: 25, color: _kPrimary), padding: EdgeInsets.zero, constraints: const BoxConstraints(), onPressed: () => ref.read(dietProvider.notifier).addFood(), @@ -457,7 +457,7 @@ class _DietCapturePageState extends ConsumerState { color: food.selected ? _kPrimary : Colors.white, border: Border.all(color: food.selected ? _kPrimary : const Color(0xFFCCCCCC), width: 2), ), - child: food.selected ? const Icon(Icons.check, size: 14, color: Colors.white) : null, + child: food.selected ? const Icon(Icons.check, size: 17, color: Colors.white) : null, ), ), const SizedBox(width: 12), @@ -466,7 +466,7 @@ class _DietCapturePageState extends ConsumerState { TextField( controller: TextEditingController(text: food.name), onChanged: (v) => ref.read(dietProvider.notifier).updateFoodName(food.id, v), - style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: _kText), + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: _kText), decoration: const InputDecoration( isDense: true, contentPadding: EdgeInsets.zero, @@ -480,13 +480,13 @@ class _DietCapturePageState extends ConsumerState { child: TextField( controller: TextEditingController(text: food.portion), onChanged: (v) => ref.read(dietProvider.notifier).updateFoodPortion(food.id, v), - style: const TextStyle(fontSize: 12, color: _kSubText), + style: const TextStyle(fontSize: 15, color: _kSubText), decoration: const InputDecoration( isDense: true, contentPadding: EdgeInsets.zero, border: InputBorder.none, hintText: '份量', - hintStyle: TextStyle(fontSize: 12, color: AppTheme.textHint), + hintStyle: TextStyle(fontSize: 15, color: AppTheme.textHint), ), ), ), @@ -497,24 +497,24 @@ class _DietCapturePageState extends ConsumerState { controller: TextEditingController(text: food.calories.toString()), onChanged: (v) => ref.read(dietProvider.notifier).updateFoodCalories(food.id, int.tryParse(v) ?? 0), keyboardType: TextInputType.number, - style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: _kWarning), + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: _kWarning), textAlign: TextAlign.right, decoration: const InputDecoration( isDense: true, contentPadding: EdgeInsets.zero, border: InputBorder.none, hintText: '0', - hintStyle: TextStyle(fontSize: 12), + hintStyle: TextStyle(fontSize: 15), ), ), ), const SizedBox(width: 2), - const Text('kcal', style: TextStyle(fontSize: 11, color: _kSubText)), + const Text('kcal', style: TextStyle(fontSize: 14, color: _kSubText)), ]), ]), ), IconButton( - icon: const Icon(Icons.close, size: 16, color: Color(0xFFBBBBBB)), + icon: const Icon(Icons.close, size: 19, color: Color(0xFFBBBBBB)), padding: EdgeInsets.zero, constraints: const BoxConstraints(), onPressed: () => ref.read(dietProvider.notifier).removeFood(food.id), @@ -552,8 +552,8 @@ class _DietCapturePageState extends ConsumerState { ), ), Column(mainAxisSize: MainAxisSize.min, children: [ - Text('$totalCalories', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w800, color: Colors.white)), - Text('kcal', style: const TextStyle(fontSize: 10, color: Colors.white70)), + Text('$totalCalories', style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w800, color: Colors.white)), + Text('kcal', style: const TextStyle(fontSize: 13, color: Colors.white70)), ]), ], ), @@ -561,7 +561,7 @@ class _DietCapturePageState extends ConsumerState { const SizedBox(width: 16), Expanded( child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('本餐热量', style: TextStyle(fontSize: 14, color: Colors.white70)), + const Text('本餐热量', style: TextStyle(fontSize: 17, color: Colors.white70)), const SizedBox(height: 4), Row(children: [ Expanded(child: _macroBar('碳水', 0.55, const Color(0xFFFFF9C4))), @@ -582,7 +582,7 @@ class _DietCapturePageState extends ConsumerState { Row(children: [ Container(width: 6, height: 6, decoration: BoxDecoration(color: color, shape: BoxShape.circle)), const SizedBox(width: 4), - Text(label, style: const TextStyle(fontSize: 10, color: Colors.white70)), + Text(label, style: const TextStyle(fontSize: 13, color: Colors.white70)), ]), const SizedBox(height: 3), ClipRRect( @@ -607,10 +607,10 @@ class _DietCapturePageState extends ConsumerState { Container( width: 32, height: 32, decoration: BoxDecoration(color: _kPrimaryLight, borderRadius: BorderRadius.circular(8)), - child: const Icon(Icons.auto_awesome, size: 16, color: _kPrimary), + child: const Icon(Icons.auto_awesome, size: 19, color: _kPrimary), ), const SizedBox(width: 10), - Expanded(child: Text(text, style: const TextStyle(fontSize: 13, color: _kText, height: 1.6))), + Expanded(child: Text(text, style: const TextStyle(fontSize: 16, color: _kText, height: 1.6))), ]), ), ); @@ -650,11 +650,11 @@ class _DietCapturePageState extends ConsumerState { backgroundColor: _kPrimary, foregroundColor: Colors.white, elevation: 0, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppTheme.radiusMd)), - textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppTheme.rMd)), + textStyle: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600), ), child: const Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.check_circle_outline, size: 20), + Icon(Icons.check_circle_outline, size: 23), SizedBox(width: 8), Text('保存记录'), ]), diff --git a/health_app/lib/pages/home/home_page.dart b/health_app/lib/pages/home/home_page.dart index 3279085..0f874cc 100644 --- a/health_app/lib/pages/home/home_page.dart +++ b/health_app/lib/pages/home/home_page.dart @@ -1,14 +1,16 @@ +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:file_picker/file_picker.dart'; -import 'dart:io'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import '../../core/app_theme.dart'; +import '../../core/navigation_provider.dart'; import '../../providers/auth_provider.dart'; import '../../providers/chat_provider.dart'; import '../../providers/data_providers.dart'; -import '../diet/diet_capture_page.dart'; -import '../../core/navigation_provider.dart'; import '../../widgets/health_drawer.dart'; +import '../diet/diet_capture_page.dart'; import 'widgets/chat_messages_view.dart'; class HomePage extends ConsumerStatefulWidget { @@ -21,9 +23,10 @@ class _HomePageState extends ConsumerState { final _scrollCtrl = ScrollController(); String? _pickedImagePath; final Set _welcomedAgents = {}; + final _focusNode = FocusNode(); @override void initState() { super.initState(); } - @override void dispose() { _textCtrl.dispose(); _scrollCtrl.dispose(); super.dispose(); } + @override void dispose() { _textCtrl.dispose(); _scrollCtrl.dispose(); _focusNode.dispose(); super.dispose(); } void _sendMessage() { final text = _textCtrl.text.trim(); @@ -43,62 +46,60 @@ class _HomePageState extends ConsumerState { final auth = ref.watch(authProvider); final user = auth.user; final selectedAgent = ref.watch(selectedAgentProvider); + final theme = ShadTheme.of(context); ref.listen(cameraActionProvider, (prev, next) { - if (next == 'camera') { - _pickImage(ImageSource.camera); - ref.read(cameraActionProvider.notifier).clear(); - } else if (next == 'gallery') { - _pickImage(ImageSource.gallery); - ref.read(cameraActionProvider.notifier).clear(); - } + if (next == 'camera') { _pickImage(ImageSource.camera); ref.read(cameraActionProvider.notifier).clear(); } + else if (next == 'gallery') { _pickImage(ImageSource.gallery); ref.read(cameraActionProvider.notifier).clear(); } }); - ref.listen(dietActionProvider, (prev, next) { - if (next == 'pickFoodCamera') { - _pickFoodImage(ImageSource.camera); - ref.read(dietActionProvider.notifier).clear(); - } else if (next == 'pickFoodGallery') { - _pickFoodImage(ImageSource.gallery); - ref.read(dietActionProvider.notifier).clear(); - } + if (next == 'pickFoodCamera') { _pickFoodImage(ImageSource.camera); ref.read(dietActionProvider.notifier).clear(); } + else if (next == 'pickFoodGallery') { _pickFoodImage(ImageSource.gallery); ref.read(dietActionProvider.notifier).clear(); } }); return Scaffold( drawer: const HealthDrawer(), - backgroundColor: const Color(0xFFF8F9FC), + backgroundColor: theme.colorScheme.background, body: SafeArea( child: Column(children: [ - // ── 顶部栏 ── - _buildHeader(user), - - // ── 聊天区域(今日任务在对话流最上方)── + _buildHeader(user, theme), Expanded(child: ChatMessagesView(scrollCtrl: _scrollCtrl, messages: chatState.messages)), - - // ── 底部合并区 ── - _buildBottomBar(context, selectedAgent), + _buildBottomBar(context, selectedAgent, theme), ]), ), ); } - // ═════════════════════ 顶部栏 ═════════════════════ - - Widget _buildHeader(dynamic user) { + // ═══════ 顶部栏 ═══════ + Widget _buildHeader(dynamic user, ShadThemeData theme) { return Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: AppTheme.sLg, vertical: 10), + decoration: BoxDecoration( + color: theme.colorScheme.card, + border: Border(bottom: BorderSide(color: theme.colorScheme.border, width: 0.5)), + ), child: Row(children: [ Builder(builder: (ctx) => GestureDetector( onTap: () => Scaffold.of(ctx).openDrawer(), - child: CircleAvatar(radius: 20, backgroundColor: const Color(0xFFF0F2FF), backgroundImage: user?.avatarUrl != null ? NetworkImage(user!.avatarUrl!) : null, child: user?.avatarUrl == null ? const Icon(Icons.person, size: 24, color: Color(0xFF8B9CF7)) : null), + child: CircleAvatar( + radius: 20, + backgroundColor: AppTheme.primaryLight, + backgroundImage: user?.avatarUrl != null ? NetworkImage(user!.avatarUrl!) : null, + child: user?.avatarUrl == null ? const Icon(Icons.person, size: 28, color: AppTheme.primary) : null, + ), )), const SizedBox(width: 10), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row(mainAxisSize: MainAxisSize.min, children: [Icon(Icons.smart_toy_outlined, size: 16, color: const Color(0xFF8B9CF7)), const SizedBox(width: 4), Text('AI 健康管家', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: Colors.grey[600]))]), + Row(mainAxisSize: MainAxisSize.min, children: [ + Icon(LucideIcons.bot, size: 19, color: AppTheme.primary), + const SizedBox(width: 4), + Text('AI 健康管家', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: theme.colorScheme.mutedForeground)), + ]), const SizedBox(height: 2), - Text('${_getGreeting()},${(user?.name != null && user!.name!.isNotEmpty) ? user.name : '用户'}!', style: const TextStyle(fontSize: 17, fontWeight: FontWeight.bold, color: Color(0xFF1A1A1A))), + Text('${_getGreeting()},${(user?.name != null && user!.name!.isNotEmpty) ? user.name : '用户'}!', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: theme.colorScheme.foreground)), ])), - Icon(Icons.notifications_none, size: 22, color: Colors.grey[600]), + Icon(LucideIcons.bell, size: 25, color: theme.colorScheme.mutedForeground), ]), ); } @@ -111,21 +112,20 @@ class _HomePageState extends ConsumerState { return '晚上好'; } - // ═════════════════════ 智能体选择条(常驻) ═════════════════════ - + // ═══════ 智能体选择条 ═══════ static final _agentDefs = [ - (ActiveAgent.consultation, '问诊', Icons.chat_bubble_outline), - (ActiveAgent.health, '记数据', Icons.favorite_border), - (ActiveAgent.diet, '拍饮食', Icons.restaurant_outlined), - (ActiveAgent.medication, '药管家', Icons.medication_outlined), - (ActiveAgent.report, '看报告', Icons.description_outlined), - (ActiveAgent.exercise, '运动', Icons.directions_run_outlined), + (ActiveAgent.consultation, '问诊', LucideIcons.messageCircle), + (ActiveAgent.health, '记数据', LucideIcons.heart), + (ActiveAgent.diet, '拍饮食', LucideIcons.utensils), + (ActiveAgent.medication, '药管家', LucideIcons.pill), + (ActiveAgent.report, '看报告', LucideIcons.fileText), + (ActiveAgent.exercise, '运动', LucideIcons.trendingUp), ]; - Widget _buildAgentBar(ActiveAgent? selected) { + Widget _buildAgentBar(ActiveAgent? selected, ShadThemeData theme) { return Container( - height: 36, - padding: const EdgeInsets.symmetric(horizontal: 12), + height: 40, + padding: const EdgeInsets.symmetric(horizontal: AppTheme.sMd), child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: _agentDefs.length, @@ -146,17 +146,18 @@ class _HomePageState extends ConsumerState { } } }, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( - color: isActive ? const Color(0xFF8B9CF7) : Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: isActive ? const Color(0xFF8B9CF7) : const Color(0xFFE0E0E0)), + color: isActive ? AppTheme.primary : theme.colorScheme.card, + borderRadius: BorderRadius.circular(AppTheme.rPill), + boxShadow: isActive ? [AppTheme.shadowLight] : null, ), child: Row(mainAxisSize: MainAxisSize.min, children: [ - Icon(icon, size: 13, color: isActive ? Colors.white : const Color(0xFF666666)), - const SizedBox(width: 3), - Text(label, style: TextStyle(fontSize: 11, fontWeight: isActive ? FontWeight.w600 : FontWeight.w500, color: isActive ? Colors.white : const Color(0xFF666666))), + Icon(icon, size: 17, color: isActive ? Colors.white : theme.colorScheme.mutedForeground), + const SizedBox(width: 4), + Text(label, style: TextStyle(fontSize: 15, fontWeight: isActive ? FontWeight.w600 : FontWeight.w500, color: isActive ? Colors.white : theme.colorScheme.mutedForeground)), ]), ), ); @@ -165,59 +166,137 @@ class _HomePageState extends ConsumerState { ); } - // ═════════════════════ 底部合并区:智能体栏 + 操作面板 + 输入框 ═════════════════════ - - Widget _buildBottomBar(BuildContext context, ActiveAgent? selectedAgent) { + // ═══════ 底部区域 ═══════ + Widget _buildBottomBar(BuildContext context, ActiveAgent? selectedAgent, ShadThemeData theme) { return Column(mainAxisSize: MainAxisSize.min, children: [ - // 智能体胶囊栏(常驻,高度36) - _buildAgentBar(selectedAgent), - - // 图片预览(有选中图片时显示) - if (_pickedImagePath != null) _buildImagePreview(), - - // 输入框 - _buildCompactInputBar(context), + Container( + padding: const EdgeInsets.only(top: AppTheme.sSm, bottom: 6), + child: _buildAgentBar(selectedAgent, theme), + ), + if (_pickedImagePath != null) _buildImagePreview(theme), + _buildCompactInputBar(theme), ]); } - Widget _buildImagePreview() { + Widget _buildImagePreview(ShadThemeData theme) { return Container( padding: const EdgeInsets.fromLTRB(12, 8, 12, 4), - decoration: const BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: Color(0xFFEEEEEE)))), + decoration: BoxDecoration( + color: theme.colorScheme.card, + border: Border(top: BorderSide(color: theme.colorScheme.border)), + ), child: Row(children: [ Stack(children: [ ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(AppTheme.rXs), child: Image.file(File(_pickedImagePath!), width: 60, height: 60, fit: BoxFit.cover), ), Positioned(top: -4, right: -4, child: GestureDetector( onTap: () => setState(() => _pickedImagePath = null), - child: Container(width: 20, height: 20, decoration: const BoxDecoration(color: Color(0xFF333333), shape: BoxShape.circle), child: const Icon(Icons.close, size: 14, color: Colors.white)), + child: Container( + width: 20, height: 20, + decoration: const BoxDecoration(color: AppTheme.text, shape: BoxShape.circle), + child: const Icon(Icons.close, size: 17, color: Colors.white), + ), )), ]), const Spacer(), - Text('点击发送上传图片', style: TextStyle(fontSize: 12, color: Colors.grey[500])), + Text('点击发送上传图片', style: TextStyle(fontSize: 15, color: theme.colorScheme.mutedForeground)), ]), ); } - Widget _buildCompactInputBar(BuildContext context) { + Widget _buildCompactInputBar(ShadThemeData theme) { return Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), - decoration: BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: const Color(0xFFEEEEEE)))), - child: Row(children: [ - IconButton(icon: const Icon(Icons.attach_file, size: 24, color: Color(0xFF666666)), onPressed: () => _showAttachmentPicker(context)), - Expanded(child: TextField( - controller: _textCtrl, - style: const TextStyle(fontSize: 15), - decoration: const InputDecoration(hintText: '输入你想说的...', hintStyle: TextStyle(fontSize: 15, color: Color(0xFFBBBBBB)), contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 10), border: InputBorder.none), - onSubmitted: (_) => _sendMessage(), - )), - IconButton(icon: const Icon(Icons.send, size: 24, color: Color(0xFF8B9CF7)), onPressed: _sendMessage), + padding: const EdgeInsets.symmetric(horizontal: AppTheme.sSm, vertical: AppTheme.sSm), + decoration: BoxDecoration( + color: theme.colorScheme.card, + border: Border(top: BorderSide(color: theme.colorScheme.border)), + ), + child: Row(crossAxisAlignment: CrossAxisAlignment.end, children: [ + ShadButton.ghost( + size: ShadButtonSize.sm, + leading: Icon(LucideIcons.paperclip, size: 25, color: theme.colorScheme.mutedForeground), + onPressed: () => _showAttachmentPicker(context, theme), + ), + Expanded( + child: TextField( + controller: _textCtrl, + focusNode: _focusNode, + style: TextStyle(fontSize: 18, color: theme.colorScheme.foreground), + maxLines: null, + textInputAction: TextInputAction.newline, + decoration: InputDecoration( + hintText: '输入你想说的...', + hintStyle: TextStyle(fontSize: 18, color: theme.colorScheme.mutedForeground), + filled: true, + fillColor: theme.colorScheme.muted, + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppTheme.rXl), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppTheme.rXl), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppTheme.rXl), + borderSide: BorderSide.none, + ), + ), + onSubmitted: (_) => _sendMessage(), + ), + ), + const SizedBox(width: 4), + ShadButton( + size: ShadButtonSize.sm, + leading: Icon(LucideIcons.send, size: 21, color: theme.colorScheme.primaryForeground), + onPressed: _sendMessage, + ), ]), ); } + void _showAttachmentPicker(BuildContext context, ShadThemeData theme) { + showModalBottomSheet( + context: context, + backgroundColor: theme.colorScheme.card, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(AppTheme.rXl)), + ), + builder: (ctx) => SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: AppTheme.sMd), + child: Wrap(children: [ + ListTile( + leading: Icon(LucideIcons.camera, color: theme.colorScheme.foreground), + title: Text('拍照', style: TextStyle(color: theme.colorScheme.foreground)), + onTap: () { Navigator.pop(ctx); _pickImage(ImageSource.camera); }, + ), + ListTile( + leading: Icon(LucideIcons.image, color: theme.colorScheme.foreground), + title: Text('从相册选', style: TextStyle(color: theme.colorScheme.foreground)), + onTap: () { Navigator.pop(ctx); _pickImage(ImageSource.gallery); }, + ), + ListTile( + leading: Icon(LucideIcons.file, color: theme.colorScheme.foreground), + title: Text('传文件', style: TextStyle(color: theme.colorScheme.foreground)), + onTap: () async { + Navigator.pop(ctx); + final result = await FilePicker.platform.pickFiles(); + if (result != null && result.files.isNotEmpty) { + _textCtrl.text = '[文件已选择] ${result.files.first.name}'; + if (mounted) setState(() {}); + } + }, + ), + ]), + ), + ), + ); + } + Future _pickImage(ImageSource source) async { final picker = ImagePicker(); final picked = await picker.pickImage(source: source, imageQuality: 85); @@ -238,13 +317,4 @@ class _HomePageState extends ConsumerState { pushRoute(ref, 'dietCapture'); } } - - void _showAttachmentPicker(BuildContext context) { - showModalBottomSheet(context: context, builder: (ctx) => SafeArea(child: Wrap(children: [ - ListTile(leading: const Icon(Icons.camera_alt), title: const Text('拍照'), onTap: () { Navigator.pop(ctx); _pickImage(ImageSource.camera); }), - ListTile(leading: const Icon(Icons.photo_library), title: const Text('从相册选'), onTap: () { Navigator.pop(ctx); _pickImage(ImageSource.gallery); }), - ListTile(leading: const Icon(Icons.attach_file), title: const Text('传文件'), onTap: () async { Navigator.pop(ctx); final result = await FilePicker.platform.pickFiles(); if (result != null && result.files.isNotEmpty) { _textCtrl.text = '[文件已选择] ${result.files.first.name}'; if (mounted) setState(() {}); }}), - ]))); - } - } diff --git a/health_app/lib/pages/home/widgets/chat_messages_view.dart b/health_app/lib/pages/home/widgets/chat_messages_view.dart index 85d76d6..583d252 100644 --- a/health_app/lib/pages/home/widgets/chat_messages_view.dart +++ b/health_app/lib/pages/home/widgets/chat_messages_view.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../core/app_theme.dart'; import '../../../core/navigation_provider.dart'; import '../../../providers/auth_provider.dart'; import '../../../providers/chat_provider.dart'; @@ -27,15 +28,15 @@ class ChatMessagesView extends ConsumerWidget { width: 80, height: 80, decoration: BoxDecoration( - color: const Color(0xFFF0F2FF), + color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(40), ), - child: const Icon(Icons.health_and_safety, size: 40, color: Color(0xFF8B9CF7)), + child: const Icon(Icons.health_and_safety, size: 40, color: AppTheme.primary), ), const SizedBox(height: 16), Text('开始和 AI 健康管家对话吧', style: Theme.of(context).textTheme.bodyMedium), const SizedBox(height: 8), - const Text('记录健康数据,获取专业建议', style: TextStyle(fontSize: 14, color: Color(0xFF9E9E9E))), + const Text('记录健康数据,获取专业建议', style: TextStyle(fontSize: 17, color: Color(0xFF9E9E9E))), ], ), ); @@ -135,7 +136,7 @@ class ChatMessagesView extends ConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(info.$2, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700, color: Colors.white)), + Text(info.$2, style: const TextStyle(fontSize: 21, fontWeight: FontWeight.w700, color: Colors.white)), const SizedBox(height: 4), Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 3), @@ -143,7 +144,7 @@ class ChatMessagesView extends ConsumerWidget { color: Colors.white.withAlpha(25), borderRadius: BorderRadius.circular(10), ), - child: Text(info.$3, style: const TextStyle(fontSize: 12, color: Color(0xFFE8E6FF))), + child: Text(info.$3, style: const TextStyle(fontSize: 15, color: Color(0xFFE8E6FF))), ), ], ), @@ -210,10 +211,10 @@ class ChatMessagesView extends ConsumerWidget { color: colors.iconBg, borderRadius: BorderRadius.circular(11), ), - child: Icon(a.icon, size: 20, color: colors.accent), + child: Icon(a.icon, size: 23, color: colors.accent), ), const SizedBox(height: 7), - Text(a.label, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500, color: Color(0xFF333333))), + Text(a.label, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: Color(0xFF333333))), ], ), ), @@ -245,30 +246,30 @@ class ChatMessagesView extends ConsumerWidget { decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), - border: Border.all(color: const Color(0xFFF0F2FF)), + border: Border.all(color: AppTheme.primaryLight), ), child: Column(mainAxisSize: MainAxisSize.min, children: [ CircleAvatar( radius: 22, - backgroundColor: const Color(0xFFF0F2FF), - child: Text(doc['name']![0], style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF8B9CF7))), + backgroundColor: AppTheme.primaryLight, + child: Text(doc['name']![0], style: const TextStyle(fontSize: 21, fontWeight: FontWeight.w600, color: AppTheme.primary)), ), const SizedBox(height: 6), - Text(doc['name']!, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xFF333333))), - Text(doc['title']!, style: const TextStyle(fontSize: 11, color: Color(0xFF999999))), + Text(doc['name']!, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF333333))), + Text(doc['title']!, style: const TextStyle(fontSize: 14, color: Color(0xFF999999))), const SizedBox(height: 2), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( - color: const Color(0xFFF0F2FF), + color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(4), ), - child: Text(doc['dept']!, style: const TextStyle(fontSize: 10, color: Color(0xFF8B9CF7))), + child: Text(doc['dept']!, style: const TextStyle(fontSize: 13, color: AppTheme.primary)), ), const SizedBox(height: 4), Text( doc['desc']!, - style: const TextStyle(fontSize: 10, color: Color(0xFF888888), height: 1.3), + style: const TextStyle(fontSize: 13, color: Color(0xFF888888), height: 1.3), maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, @@ -298,34 +299,34 @@ class ChatMessagesView extends ConsumerWidget { decoration: BoxDecoration( color: const Color(0xFFFFFFFF), borderRadius: BorderRadius.circular(20), - boxShadow: [BoxShadow(color: const Color(0xFF6C5CE7).withAlpha(15), blurRadius: 14, offset: const Offset(0, 4))], + boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(15), blurRadius: 14, offset: const Offset(0, 4))], ), clipBehavior: Clip.antiAlias, child: Column(mainAxisSize: MainAxisSize.min, children: [ Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 10), - decoration: const BoxDecoration(gradient: LinearGradient(colors: [Color(0xFF4CAF50), Color(0xFF43A047)])), + decoration: const BoxDecoration(gradient: LinearGradient(colors: [Color(0xFF4CAF50), AppTheme.success])), child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.info_outline, size: 18, color: Colors.white), const SizedBox(width: 6), + const Icon(Icons.info_outline, size: 21, color: Colors.white), const SizedBox(width: 6), Text(metricType == 'exercise' ? '请确认运动计划' : '请确认录入', - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white)), + style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: Colors.white)), ]), ), Padding(padding: const EdgeInsets.all(18), child: Column(children: [ - Align(alignment: Alignment.centerLeft, child: Text(recordTime.isNotEmpty ? recordTime : _formatTime(msg.createdAt), style: const TextStyle(fontSize: 12, color: Color(0xFF9E9E9E)))), + Align(alignment: Alignment.centerLeft, child: Text(recordTime.isNotEmpty ? recordTime : _formatTime(msg.createdAt), style: const TextStyle(fontSize: 15, color: Color(0xFF9E9E9E)))), const SizedBox(height: 14), Container( padding: const EdgeInsets.all(18), decoration: BoxDecoration(color: const Color(0xFFF9F8FF), borderRadius: BorderRadius.circular(16)), child: Row(children: [ - Container(width: 52, height: 52, decoration: BoxDecoration(color: const Color(0xFFEDEAFF), borderRadius: BorderRadius.circular(14)), child: Center(child: Text(_getMetricIcon(metricType), style: const TextStyle(fontSize: 26)))), + Container(width: 52, height: 52, decoration: BoxDecoration(color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(14)), child: Center(child: Text(_getMetricIcon(metricType), style: const TextStyle(fontSize: 30)))), const SizedBox(width: 14), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(_getMetricName(metricType), style: const TextStyle(fontSize: 13, color: Color(0xFF888888))), + Text(_getMetricName(metricType), style: const TextStyle(fontSize: 16, color: Color(0xFF888888))), const SizedBox(height: 4), RichText(text: TextSpan(children: [ - TextSpan(text: value, style: TextStyle(fontSize: 28, fontWeight: FontWeight.w800, color: abnormal ? const Color(0xFFE53935) : const Color(0xFF1A1A2E))), - TextSpan(text: ' $unit', style: TextStyle(fontSize: 14, color: abnormal ? const Color(0xFFE53970) : const Color(0xFF999999))), + TextSpan(text: value, style: TextStyle(fontSize: 32, fontWeight: FontWeight.w800, color: abnormal ? AppTheme.error : const Color(0xFF1A1A2E))), + TextSpan(text: ' $unit', style: TextStyle(fontSize: 17, color: abnormal ? const Color(0xFFE53970) : const Color(0xFF999999))), ])), ])), ]), @@ -333,7 +334,7 @@ class ChatMessagesView extends ConsumerWidget { if (abnormal) ...[ const SizedBox(height: 12), Container(width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration(color: const Color(0xFFFFF3F0), borderRadius: BorderRadius.circular(10), border: Border.all(color: const Color(0xFFFFDAD4))), - child: const Row(children: [Icon(Icons.warning_amber_rounded, size: 18, color: Color(0xFFE53935)), SizedBox(width: 8), Expanded(child: Text('数值偏高,建议关注', style: TextStyle(fontSize: 13, color: Color(0xFFE53935), fontWeight: FontWeight.w500)))])), + child: const Row(children: [Icon(Icons.warning_amber_rounded, size: 21, color: AppTheme.error), SizedBox(width: 8), Expanded(child: Text('数值偏高,建议关注', style: TextStyle(fontSize: 16, color: AppTheme.error, fontWeight: FontWeight.w500)))])), ], const SizedBox(height: 18), if (msg.confirmed) @@ -341,12 +342,12 @@ class ChatMessagesView extends ConsumerWidget { width: double.infinity, child: ElevatedButton.icon( onPressed: null, - icon: const Icon(Icons.check_circle, size: 18), + icon: const Icon(Icons.check_circle, size: 21), label: const Text('录入成功'), style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF43A047), + backgroundColor: AppTheme.success, foregroundColor: Colors.white, - disabledBackgroundColor: const Color(0xFF43A047), + disabledBackgroundColor: AppTheme.success, disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), padding: const EdgeInsets.symmetric(vertical: 12), @@ -388,7 +389,7 @@ class ChatMessagesView extends ConsumerWidget { decoration: BoxDecoration( color: const Color(0xFFFFFFFF), borderRadius: BorderRadius.circular(20), - boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(15), blurRadius: 14, offset: const Offset(0, 4))], + boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(15), blurRadius: 14, offset: const Offset(0, 4))], ), clipBehavior: Clip.antiAlias, child: Column( @@ -399,7 +400,7 @@ class ChatMessagesView extends ConsumerWidget { width: double.infinity, padding: const EdgeInsets.fromLTRB(18, 20, 18, 16), decoration: const BoxDecoration( - gradient: LinearGradient(colors: [Color(0xFFE8F0FE), Color(0xFFF0F2FF)]), + gradient: LinearGradient(colors: [Color(0xFFE8F0FE), AppTheme.primaryLight]), ), child: Row( children: [ @@ -410,17 +411,17 @@ class ChatMessagesView extends ConsumerWidget { color: const Color(0xFFFFF3E0), borderRadius: BorderRadius.circular(13), ), - child: const Center(child: Text('💊', style: TextStyle(fontSize: 24))), + child: const Center(child: Text('💊', style: TextStyle(fontSize: 28))), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(name, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w700, color: Color(0xFF1A1A2E))), + Text(name, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w700, color: Color(0xFF1A1A2E))), if (dosage.isNotEmpty) ...[ const SizedBox(height: 3), - Text(dosage, style: const TextStyle(fontSize: 13, color: Color(0xFF777777))), + Text(dosage, style: const TextStyle(fontSize: 16, color: Color(0xFF777777))), ], ], ), @@ -444,12 +445,12 @@ class ChatMessagesView extends ConsumerWidget { width: double.infinity, child: ElevatedButton.icon( onPressed: null, - icon: const Icon(Icons.check_circle, size: 18), + icon: const Icon(Icons.check_circle, size: 21), label: const Text('录入成功'), style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF43A047), + backgroundColor: AppTheme.success, foregroundColor: Colors.white, - disabledBackgroundColor: const Color(0xFF43A047), + disabledBackgroundColor: AppTheme.success, disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), padding: const EdgeInsets.symmetric(vertical: 12), @@ -479,9 +480,9 @@ class ChatMessagesView extends ConsumerWidget { padding: const EdgeInsets.only(bottom: 10), child: Row( children: [ - Icon(icon, size: 17, color: const Color(0xFF888888)), + Icon(icon, size: 20, color: const Color(0xFF888888)), const SizedBox(width: 8), - Text(text, style: const TextStyle(fontSize: 13, color: Color(0xFF444444))), + Text(text, style: const TextStyle(fontSize: 16, color: Color(0xFF444444))), ], ), ); @@ -514,7 +515,7 @@ class ChatMessagesView extends ConsumerWidget { decoration: BoxDecoration( color: const Color(0xFFFFFFFF), borderRadius: BorderRadius.circular(20), - boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(15), blurRadius: 14, offset: const Offset(0, 4))], + boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(15), blurRadius: 14, offset: const Offset(0, 4))], ), clipBehavior: Clip.antiAlias, child: Column( @@ -526,8 +527,8 @@ class ChatMessagesView extends ConsumerWidget { padding: const EdgeInsets.symmetric(vertical: 12), decoration: const BoxDecoration(gradient: LinearGradient(colors: [Color(0xFFFFF8E1), Color(0xFFFFF3E0)])), child: const Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('🍽️ ', style: TextStyle(fontSize: 18)), - Text('饮食分析结果', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: Color(0xFF1A1A2E))), + Text('🍽️ ', style: TextStyle(fontSize: 21)), + Text('饮食分析结果', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w700, color: Color(0xFF1A1A2E))), ]), ), Padding( @@ -536,15 +537,15 @@ class ChatMessagesView extends ConsumerWidget { // ── 总热量(仅 >0 时显示) ── if (totalCalories > 0) ...[ Center(child: Column(children: [ - Text('$totalCalories', style: const TextStyle(fontSize: 36, fontWeight: FontWeight.w800, color: Color(0xFFFF8F00))), - const Text('千卡 (kcal)', style: TextStyle(fontSize: 12, color: Color(0xFFAAAAAA))), + Text('$totalCalories', style: const TextStyle(fontSize: 40, fontWeight: FontWeight.w800, color: Color(0xFFFF8F00))), + const Text('千卡 (kcal)', style: TextStyle(fontSize: 15, color: Color(0xFFAAAAAA))), ])), const SizedBox(height: 16), ], // ── 识别食物列表 ── if (foods.isNotEmpty) ...[ - const Text('识别结果', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + const Text('识别结果', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), const SizedBox(height: 10), ...foods.map((food) { final f = food is Map ? food : {}; @@ -559,11 +560,11 @@ class ChatMessagesView extends ConsumerWidget { decoration: BoxDecoration(color: const Color(0xFFFAFAFA), borderRadius: BorderRadius.circular(10), border: Border.all(color: const Color(0xFFF0F0F0))), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ - Expanded(child: Text(name, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF1A1A1A)))), - if (calories > 0) Text('${calories is int ? calories : calories.toInt()} kcal', style: const TextStyle(fontSize: 13, color: Color(0xFF888888))), + Expanded(child: Text(name, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: Color(0xFF1A1A1A)))), + if (calories > 0) Text('${calories is int ? calories : calories.toInt()} kcal', style: const TextStyle(fontSize: 16, color: Color(0xFF888888))), ]), - if (portion != null && portion.isNotEmpty) Padding(padding: const EdgeInsets.only(top: 4), child: Text(portion, style: TextStyle(fontSize: 12, color: Colors.grey[500]))), - if (nutrients != null && nutrients.isNotEmpty) Padding(padding: const EdgeInsets.only(top: 2), child: Text(nutrients, style: TextStyle(fontSize: 11, color: Colors.grey[500]))), + if (portion != null && portion.isNotEmpty) Padding(padding: const EdgeInsets.only(top: 4), child: Text(portion, style: TextStyle(fontSize: 15, color: Colors.grey[500]))), + if (nutrients != null && nutrients.isNotEmpty) Padding(padding: const EdgeInsets.only(top: 2), child: Text(nutrients, style: TextStyle(fontSize: 14, color: Colors.grey[500]))), ]), ); }), @@ -572,12 +573,12 @@ class ChatMessagesView extends ConsumerWidget { // ── AI 建议 ── const SizedBox(height: 14), - const Text('AI 建议', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + const Text('AI 建议', style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), const SizedBox(height: 6), Container( padding: const EdgeInsets.all(12), - decoration: BoxDecoration(color: const Color(0xFFF0F2FF), borderRadius: BorderRadius.circular(10)), - child: Text(advice, style: const TextStyle(fontSize: 13, height: 1.6, color: Color(0xFF555555))), + decoration: BoxDecoration(color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(10)), + child: Text(advice, style: const TextStyle(fontSize: 16, height: 1.6, color: Color(0xFF555555))), ), ]), ), @@ -606,7 +607,7 @@ class ChatMessagesView extends ConsumerWidget { decoration: BoxDecoration( color: const Color(0xFFFFFFFF), borderRadius: BorderRadius.circular(20), - boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(15), blurRadius: 14, offset: const Offset(0, 4))], + boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(15), blurRadius: 14, offset: const Offset(0, 4))], ), clipBehavior: Clip.antiAlias, child: Column( @@ -628,16 +629,16 @@ class ChatMessagesView extends ConsumerWidget { color: const Color(0xFFC5CAE9), borderRadius: BorderRadius.circular(10), ), - child: const Icon(Icons.description_outlined, size: 20, color: Color(0xFF3F51B5)), + child: const Icon(Icons.description_outlined, size: 23, color: Color(0xFF3F51B5)), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(reportType, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: Color(0xFF1A1A2E))), + Text(reportType, style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w700, color: Color(0xFF1A1A2E))), if (reportDate.isNotEmpty) - Text(reportDate, style: const TextStyle(fontSize: 12, color: Color(0xFF888888))), + Text(reportDate, style: const TextStyle(fontSize: 15, color: Color(0xFF888888))), ], ), ), @@ -668,9 +669,9 @@ class ChatMessagesView extends ConsumerWidget { ), child: const Row( children: [ - Expanded(flex: 2, child: Text('指标名称', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Color(0xFF666666)))), - Expanded(flex: 1, child: Text('数值', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Color(0xFF666666)), textAlign: TextAlign.center)), - Expanded(flex: 1, child: Text('状态', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Color(0xFF666666)), textAlign: TextAlign.center)), + Expanded(flex: 2, child: Text('指标名称', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF666666)))), + Expanded(flex: 1, child: Text('数值', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF666666)), textAlign: TextAlign.center)), + Expanded(flex: 1, child: Text('状态', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF666666)), textAlign: TextAlign.center)), ], ), ), @@ -684,9 +685,9 @@ class ChatMessagesView extends ConsumerWidget { final isAbnormal = status != 'normal'; Color sc; switch (status) { - case 'high': sc = const Color(0xFFE53935); break; + case 'high': sc = AppTheme.error; break; case 'low': sc = const Color(0xFFF9A825); break; - default: sc = const Color(0xFF43A047); + default: sc = AppTheme.success; } return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), @@ -703,12 +704,12 @@ class ChatMessagesView extends ConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(name, style: TextStyle(fontSize: 13, color: isAbnormal ? const Color(0xFFE53935) : const Color(0xFF333333), fontWeight: isAbnormal ? FontWeight.w600 : FontWeight.normal)), - if (refRange.isNotEmpty) Text('参考:$refRange', style: const TextStyle(fontSize: 10, color: Color(0xFFAAAAAA))), + Text(name, style: TextStyle(fontSize: 16, color: isAbnormal ? AppTheme.error : const Color(0xFF333333), fontWeight: isAbnormal ? FontWeight.w600 : FontWeight.normal)), + if (refRange.isNotEmpty) Text('参考:$refRange', style: const TextStyle(fontSize: 13, color: Color(0xFFAAAAAA))), ], ), ), - Expanded(flex: 1, child: Text(value, textAlign: TextAlign.center, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: sc))), + Expanded(flex: 1, child: Text(value, textAlign: TextAlign.center, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: sc))), Expanded( flex: 1, child: Center( @@ -743,13 +744,13 @@ class ChatMessagesView extends ConsumerWidget { children: [ const Row( children: [ - Icon(Icons.auto_awesome, size: 16, color: Color(0xFF8B9CF7)), + Icon(Icons.auto_awesome, size: 19, color: AppTheme.primary), SizedBox(width: 6), - Text('AI 解读摘要', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xFF8B9CF7))), + Text('AI 解读摘要', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: AppTheme.primary)), ], ), const SizedBox(height: 8), - Text(summary, style: const TextStyle(fontSize: 13, color: Color(0xFF555555), height: 1.5)), + Text(summary, style: const TextStyle(fontSize: 16, color: Color(0xFF555555), height: 1.5)), ], ), ), @@ -786,27 +787,27 @@ class ChatMessagesView extends ConsumerWidget { decoration: BoxDecoration( color: const Color(0xFFFFFFFF), borderRadius: BorderRadius.circular(20), - boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(15), blurRadius: 14, offset: const Offset(0, 4))], + boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(15), blurRadius: 14, offset: const Offset(0, 4))], ), child: Padding( padding: const EdgeInsets.fromLTRB(18, 16, 18, 14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(msg.content, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: Color(0xFF1A1A2E))), + Text(msg.content, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500, color: Color(0xFF1A1A2E))), const SizedBox(height: 14), Wrap(spacing: 8, runSpacing: 8, children: options.map((opt) { final o = opt as Map? ?? {}; return ElevatedButton( onPressed: () {}, style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFFF0F2FF), - foregroundColor: const Color(0xFF8B9CF7), + backgroundColor: AppTheme.primaryLight, + foregroundColor: AppTheme.primary, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 11), ), - child: Text(o['label'] as String? ?? '', style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), + child: Text(o['label'] as String? ?? '', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), ); }).toList()), ], @@ -830,7 +831,7 @@ class ChatMessagesView extends ConsumerWidget { color: const Color(0xFFFEFEFF), borderRadius: const BorderRadius.only(topLeft: Radius.circular(4), topRight: Radius.circular(20), bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), border: Border.all(color: const Color(0xFFD8DCFD), width: 1.5), - boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(12), blurRadius: 10, offset: const Offset(0, 3))], + boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(12), blurRadius: 10, offset: const Offset(0, 3))], ), child: Row( mainAxisSize: MainAxisSize.min, @@ -840,13 +841,13 @@ class ChatMessagesView extends ConsumerWidget { height: 26, padding: const EdgeInsets.all(5), decoration: BoxDecoration( - color: const Color(0xFFF0F2FF), + color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(13), ), - child: const CircularProgressIndicator(strokeWidth: 2.2, color: Color(0xFF8B9CF7)), + child: const CircularProgressIndicator(strokeWidth: 2.2, color: AppTheme.primary), ), const SizedBox(width: 10), - Text(thinkingText?.isNotEmpty == true ? thinkingText! : '正在分析...', style: const TextStyle(fontSize: 14, color: Color(0xFF999999))), + Text(thinkingText?.isNotEmpty == true ? thinkingText! : '正在分析...', style: const TextStyle(fontSize: 17, color: Color(0xFF999999))), ], ), ), @@ -866,7 +867,7 @@ class ChatMessagesView extends ConsumerWidget { constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.82), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( - color: isUser ? const Color(0xFF8B9CF7) : const Color(0xFFFEFEFF), + color: isUser ? AppTheme.primary : const Color(0xFFFEFEFF), borderRadius: BorderRadius.only( topLeft: Radius.circular(isUser ? 20 : 4), topRight: Radius.circular(isUser ? 4 : 20), @@ -874,23 +875,23 @@ class ChatMessagesView extends ConsumerWidget { bottomRight: const Radius.circular(20), ), border: isUser ? null : Border.all(color: const Color(0xFFD8DCFD), width: 1.5), - boxShadow: isUser ? [] : [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(12), blurRadius: 10, offset: const Offset(0, 3))], + boxShadow: isUser ? [] : [BoxShadow(color: AppTheme.primary.withAlpha(12), blurRadius: 10, offset: const Offset(0, 3))], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 文字内容 if (isUser) - SelectableText(msg.content, style: const TextStyle(fontSize: 16, color: Colors.white, height: 1.4)) + SelectableText(msg.content, style: const TextStyle(fontSize: 19, color: Colors.white, height: 1.4)) else MarkdownBody( data: msg.content, selectable: true, styleSheet: MarkdownStyleSheet( - p: const TextStyle(fontSize: 16, color: Color(0xFF1A1A1A), height: 1.5), - h1: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)), - h2: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)), - code: const TextStyle(fontSize: 14, backgroundColor: Color(0xFFF0F2FF)), + p: const TextStyle(fontSize: 19, color: Color(0xFF1A1A1A), height: 1.5), + h1: const TextStyle(fontSize: 21, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)), + h2: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)), + code: const TextStyle(fontSize: 17, backgroundColor: AppTheme.primaryLight), ), ), @@ -910,7 +911,7 @@ class ChatMessagesView extends ConsumerWidget { ? Image.network(imageUrl, fit: BoxFit.cover, errorBuilder: (_, e, s) => Container( width: 80, height: 60, decoration: BoxDecoration(color: const Color(0xFFF0F0F0), borderRadius: BorderRadius.circular(8)), - child: const Icon(Icons.image, size: 24, color: Color(0xFFBBBBBB)), + child: const Icon(Icons.image, size: 28, color: Color(0xFFBBBBBB)), )) : const SizedBox.shrink(), ), @@ -922,11 +923,11 @@ class ChatMessagesView extends ConsumerWidget { Padding( padding: const EdgeInsets.only(top: 10), child: Row(children: [ - const CircleAvatar(radius: 10, backgroundColor: Color(0xFFF0F2FF), child: Icon(Icons.chat_bubble_outline, size: 14, color: Color(0xFF8B9CF7))), + const CircleAvatar(radius: 10, backgroundColor: AppTheme.primaryLight, child: Icon(Icons.chat_bubble_outline, size: 17, color: AppTheme.primary)), const SizedBox(width: 6), - const Text('健康管家', style: TextStyle(fontSize: 12, color: Color(0xFF9E9E9E))), + const Text('健康管家', style: TextStyle(fontSize: 15, color: Color(0xFF9E9E9E))), const SizedBox(width: 4), - const Text('仅供参考', style: TextStyle(fontSize: 11, color: Color(0xFFCCCCCC))), + const Text('仅供参考', style: TextStyle(fontSize: 14, color: Color(0xFFCCCCCC))), ]), ), ], @@ -943,16 +944,16 @@ class ChatMessagesView extends ConsumerWidget { return ElevatedButton( onPressed: onTap ?? () {}, style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF6C5CE7), + backgroundColor: AppTheme.primary, foregroundColor: Colors.white, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), padding: const EdgeInsets.symmetric(vertical: 11), ), child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(icon, size: 16), + Icon(icon, size: 19), const SizedBox(width: 5), - Text(label, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500)), + Text(label, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500)), ]), ); } @@ -961,15 +962,15 @@ class ChatMessagesView extends ConsumerWidget { return OutlinedButton( onPressed: onTap ?? () {}, style: OutlinedButton.styleFrom( - foregroundColor: const Color(0xFF6C5CE7), - side: const BorderSide(color: Color(0xFF6C5CE7), width: 1.2), + foregroundColor: AppTheme.primary, + side: const BorderSide(color: AppTheme.primary, width: 1.2), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), padding: const EdgeInsets.symmetric(vertical: 11), ), child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(icon, size: 15), + Icon(icon, size: 18), const SizedBox(width: 4), - Text(label, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500)), + Text(label, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500)), ]), ); } @@ -991,7 +992,7 @@ class ChatMessagesView extends ConsumerWidget { : Image.file(File(path), fit: BoxFit.contain))), Positioned(top: 8, right: 8, child: GestureDetector( onTap: () => Navigator.pop(ctx), - child: Container(padding: const EdgeInsets.all(6), decoration: BoxDecoration(color: Colors.white54, shape: BoxShape.circle), child: const Icon(Icons.close, size: 18)), + child: Container(padding: const EdgeInsets.all(6), decoration: BoxDecoration(color: Colors.white54, shape: BoxShape.circle), child: const Icon(Icons.close, size: 21)), )), ]), ), @@ -1069,7 +1070,7 @@ class ChatMessagesView extends ConsumerWidget { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('打卡成功 已记录 ${reminders.length} 项服药'), - backgroundColor: const Color(0xFF43A047), + backgroundColor: AppTheme.success, ), ); } catch (e) { @@ -1260,13 +1261,13 @@ class ChatMessagesView extends ConsumerWidget { decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), - boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))], + boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))], ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ - Icon(Icons.today, size: 18, color: const Color(0xFF8B9CF7)), + Icon(Icons.today, size: 21, color: AppTheme.primary), const SizedBox(width: 8), - const Text('今日任务', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + const Text('今日任务', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), ]), const SizedBox(height: 10), ...tasks, @@ -1275,7 +1276,7 @@ class ChatMessagesView extends ConsumerWidget { } Widget _taskRow(BuildContext context, IconData icon, String label, {String status = 'pending', String? trailing, VoidCallback? onTap}) { - final colors = {'done': const Color(0xFF43A047), 'warning': const Color(0xFFFF9800), 'pending': const Color(0xFF9E9E9E), 'overdue': const Color(0xFFE53935)}; + final colors = {'done': AppTheme.success, 'warning': const Color(0xFFFF9800), 'pending': const Color(0xFF9E9E9E), 'overdue': AppTheme.error}; final icons = {'done': Icons.check_circle, 'warning': Icons.warning, 'pending': Icons.circle_outlined, 'overdue': Icons.error}; final isOverdue = status == 'overdue'; return Padding( @@ -1290,10 +1291,10 @@ class ChatMessagesView extends ConsumerWidget { borderRadius: BorderRadius.circular(8), ), child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ - Container(width: 30, height: 30, decoration: BoxDecoration(color: const Color(0xFFF0F2FF), borderRadius: BorderRadius.circular(8)), child: Icon(icon, size: 15, color: const Color(0xFF8B9CF7))), + Container(width: 30, height: 30, decoration: BoxDecoration(color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(8)), child: Icon(icon, size: 18, color: AppTheme.primary)), const SizedBox(width: 10), - Expanded(child: Text(trailing ?? label, style: const TextStyle(fontSize: 13, color: Color(0xFF333333)))), - Icon(icons[status] ?? Icons.circle_outlined, size: 18, color: colors[status] ?? Colors.grey), + Expanded(child: Text(trailing ?? label, style: const TextStyle(fontSize: 16, color: Color(0xFF333333)))), + Icon(icons[status] ?? Icons.circle_outlined, size: 21, color: colors[status] ?? Colors.grey), ]), ), ), @@ -1391,16 +1392,16 @@ class _ExpandableAdviceState extends State<_ExpandableAdvice> { children: [ Row( children: [ - const Icon(Icons.lightbulb_outline, size: 16, color: Color(0xFF8B9CF7)), + const Icon(Icons.lightbulb_outline, size: 19, color: AppTheme.primary), const SizedBox(width: 6), - const Text('AI 建议', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xFF8B9CF7))), + const Text('AI 建议', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: AppTheme.primary)), const Spacer(), - Icon(_expanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, size: 18, color: const Color(0xFFAAAAAA)), + Icon(_expanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, size: 21, color: const Color(0xFFAAAAAA)), ], ), if (_expanded) ...[ const SizedBox(height: 10), - Text(widget.advice, style: const TextStyle(fontSize: 13, color: Color(0xFF555555), height: 1.6)), + Text(widget.advice, style: const TextStyle(fontSize: 16, color: Color(0xFF555555), height: 1.6)), ], ], ), diff --git a/health_app/lib/pages/medication/medication_checkin_page.dart b/health_app/lib/pages/medication/medication_checkin_page.dart index b61dd06..24b3912 100644 --- a/health_app/lib/pages/medication/medication_checkin_page.dart +++ b/health_app/lib/pages/medication/medication_checkin_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../../core/app_theme.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../providers/auth_provider.dart'; import '../../providers/data_providers.dart'; @@ -33,13 +34,13 @@ class _MedicationCheckInPageState extends ConsumerState { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color(0xFFF8F9FC), + backgroundColor: AppTheme.bg, appBar: AppBar(title: const Text('服药打卡'), centerTitle: true), body: FutureBuilder>>( future: _future, builder: (ctx, snap) { if (snap.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator(color: Color(0xFF8B9CF7))); + return const Center(child: CircularProgressIndicator(color: AppTheme.primary)); } final reminders = snap.data ?? []; if (reminders.isEmpty) { @@ -47,7 +48,7 @@ class _MedicationCheckInPageState extends ConsumerState { child: Column(mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.check_circle_outline, size: 64, color: Colors.grey[300]), const SizedBox(height: 12), - const Text('今天所有用药已打卡', style: TextStyle(fontSize: 16, color: Color(0xFF999999))), + const Text('今天所有用药已打卡', style: TextStyle(fontSize: 19, color: Color(0xFF999999))), ]), ); } @@ -79,19 +80,19 @@ class _MedicationCheckInPageState extends ConsumerState { decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), - boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))], + boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))], ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Container( width: 40, height: 40, decoration: BoxDecoration(color: const Color(0xFFFFF3E0), borderRadius: BorderRadius.circular(10)), - child: const Center(child: Text('💊', style: TextStyle(fontSize: 20))), + child: const Center(child: Text('💊', style: TextStyle(fontSize: 23))), ), const SizedBox(width: 12), Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(medName, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700)), - if (dosage.isNotEmpty) Text(dosage, style: const TextStyle(fontSize: 13, color: Color(0xFF888888))), + Text(medName, style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w700)), + if (dosage.isNotEmpty) Text(dosage, style: const TextStyle(fontSize: 16, color: Color(0xFF888888))), ]), ]), const SizedBox(height: 14), @@ -105,12 +106,12 @@ class _MedicationCheckInPageState extends ConsumerState { child: Row(children: [ Icon( isTaken ? Icons.check_circle : Icons.radio_button_unchecked, - size: 20, - color: isTaken ? const Color(0xFF43A047) : const Color(0xFFCCCCCC), + size: 23, + color: isTaken ? AppTheme.success : const Color(0xFFCCCCCC), ), const SizedBox(width: 10), Text(time, style: TextStyle( - fontSize: 15, fontWeight: FontWeight.w500, + fontSize: 18, fontWeight: FontWeight.w500, color: isTaken ? const Color(0xFF999999) : const Color(0xFF333333), )), const Spacer(), @@ -119,12 +120,12 @@ class _MedicationCheckInPageState extends ConsumerState { child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( - color: isTaken ? const Color(0xFFDCFCE7) : const Color(0xFF8B9CF7), + color: isTaken ? const Color(0xFFDCFCE7) : AppTheme.primary, borderRadius: BorderRadius.circular(20), ), child: Text(isTaken ? '已打卡' : '打卡', - style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, - color: isTaken ? const Color(0xFF43A047) : Colors.white)), + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, + color: isTaken ? AppTheme.success : Colors.white)), ), ), ]), diff --git a/health_app/lib/pages/medication/medication_edit_page.dart b/health_app/lib/pages/medication/medication_edit_page.dart index 0e62645..676cec3 100644 --- a/health_app/lib/pages/medication/medication_edit_page.dart +++ b/health_app/lib/pages/medication/medication_edit_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../../core/app_theme.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/navigation_provider.dart'; import '../../providers/data_providers.dart'; @@ -56,8 +57,8 @@ class _MedicationEditPageState extends ConsumerState { try { if (widget.id != null) { await srv.update(widget.id!, data); } else { await srv.create(data); } ref.invalidate(medicationListProvider); ref.invalidate(medicationReminderProvider); - if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已保存'), backgroundColor: Color(0xFF43A047))); popRoute(ref); } - } catch (_) { if (mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('保存失败'), backgroundColor: Color(0xFFE53935))); } + if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已保存'), backgroundColor: AppTheme.success)); popRoute(ref); } + } catch (_) { if (mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('保存失败'), backgroundColor: AppTheme.error)); } if (mounted) setState(() => _loading = false); } @@ -71,7 +72,7 @@ class _MedicationEditPageState extends ConsumerState { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color(0xFFF8F9FC), + backgroundColor: AppTheme.bg, appBar: AppBar(title: Text(widget.id != null ? '编辑用药' : '添加用药')), body: ListView(padding: const EdgeInsets.all(16), children: [ // 名称+剂量 一行 @@ -93,8 +94,8 @@ class _MedicationEditPageState extends ConsumerState { if (t != null) setState(() => _times[i] = t); }, child: Container(padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), - decoration: BoxDecoration(color: const Color(0xFFEDEAFF), borderRadius: BorderRadius.circular(12)), - child: Text('${_times[i].hour.toString().padLeft(2,'0')}:${_times[i].minute.toString().padLeft(2,'0')}', style: const TextStyle(fontSize: 15, color: Color(0xFF6C5CE7), fontWeight: FontWeight.w600))), + decoration: BoxDecoration(color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(12)), + child: Text('${_times[i].hour.toString().padLeft(2,'0')}:${_times[i].minute.toString().padLeft(2,'0')}', style: const TextStyle(fontSize: 18, color: AppTheme.primary, fontWeight: FontWeight.w600))), ))), const SizedBox(height: 16), // 开始+结束 一行 @@ -106,30 +107,30 @@ class _MedicationEditPageState extends ConsumerState { const SizedBox(height: 16), _field('备注', _notesCtrl, hint: '如: 饭后服用、睡前'), const SizedBox(height: 32), - SizedBox(width: double.infinity, child: ElevatedButton(onPressed: _loading ? null : _save, style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFF6C5CE7), foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), padding: const EdgeInsets.symmetric(vertical: 14)), child: Text(_loading ? '保存中...' : '保存', style: const TextStyle(fontSize: 16)))), + SizedBox(width: double.infinity, child: ElevatedButton(onPressed: _loading ? null : _save, style: ElevatedButton.styleFrom(backgroundColor: AppTheme.primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), padding: const EdgeInsets.symmetric(vertical: 14)), child: Text(_loading ? '保存中...' : '保存', style: const TextStyle(fontSize: 19)))), const SizedBox(height: 20), ]), ); } - Widget _label(String text) => Text(text, style: const TextStyle(fontSize: 14, color: Color(0xFF666666))); + Widget _label(String text) => Text(text, style: const TextStyle(fontSize: 17, color: Color(0xFF666666))); Widget _field(String label, TextEditingController ctrl, {String? hint}) => Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ _label(label), const SizedBox(height: 6), - TextField(controller: ctrl, decoration: InputDecoration(hintText: hint, filled: true, fillColor: Colors.white, border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12)), style: const TextStyle(fontSize: 16)), + TextField(controller: ctrl, decoration: InputDecoration(hintText: hint, filled: true, fillColor: Colors.white, border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12)), style: const TextStyle(fontSize: 19)), ]); - Widget _chip(String label, bool sel, VoidCallback onTap) => GestureDetector(onTap: onTap, child: Container(padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration(color: sel ? const Color(0xFF6C5CE7) : const Color(0xFFEDEAFF), borderRadius: BorderRadius.circular(16)), child: Text(label, style: TextStyle(fontSize: 13, color: sel ? Colors.white : const Color(0xFF6C5CE7), fontWeight: FontWeight.w500)))); + Widget _chip(String label, bool sel, VoidCallback onTap) => GestureDetector(onTap: onTap, child: Container(padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration(color: sel ? AppTheme.primary : AppTheme.primaryLight, borderRadius: BorderRadius.circular(16)), child: Text(label, style: TextStyle(fontSize: 16, color: sel ? Colors.white : AppTheme.primary, fontWeight: FontWeight.w500)))); Widget _dateField(String label, DateTime val, ValueChanged cb) => Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ _label(label), const SizedBox(height: 6), GestureDetector(onTap: () async { final d = await showDatePicker(context: context, initialDate: val, firstDate: DateTime(2024), lastDate: DateTime(2030)); if (d != null) cb(d); }, - child: Container(width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12)), child: Text('${val.month}/${val.day}', style: const TextStyle(fontSize: 15)))), + child: Container(width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12)), child: Text('${val.month}/${val.day}', style: const TextStyle(fontSize: 18)))), ]); Widget _dateFieldOpt(String label, DateTime? val, ValueChanged cb) => Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ _label(label), const SizedBox(height: 6), GestureDetector(onTap: () async { final d = await showDatePicker(context: context, initialDate: val ?? DateTime.now(), firstDate: DateTime(2024), lastDate: DateTime(2030)); if (d != null) cb(d); }, child: Container(width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12)), child: Row(children: [ - Text(val != null ? '${val.month}/${val.day}' : '不设置', style: TextStyle(fontSize: 15, color: val != null ? null : Colors.grey[400])), + Text(val != null ? '${val.month}/${val.day}' : '不设置', style: TextStyle(fontSize: 18, color: val != null ? null : Colors.grey[400])), if (val != null) const Spacer(), - if (val != null) GestureDetector(onTap: () => cb(null), child: const Icon(Icons.close, size: 16, color: Color(0xFFBBBBBB))), + if (val != null) GestureDetector(onTap: () => cb(null), child: const Icon(Icons.close, size: 19, color: Color(0xFFBBBBBB))), ]))), ]); } diff --git a/health_app/lib/pages/medication/medication_list_page.dart b/health_app/lib/pages/medication/medication_list_page.dart index fdfedc8..ae45215 100644 --- a/health_app/lib/pages/medication/medication_list_page.dart +++ b/health_app/lib/pages/medication/medication_list_page.dart @@ -1,15 +1,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import '../../core/app_theme.dart'; import '../../core/navigation_provider.dart'; import '../../providers/data_providers.dart'; +import '../../widgets/app_empty_state.dart'; +import '../../widgets/app_tab_chip.dart'; class MedicationListPage extends ConsumerStatefulWidget { const MedicationListPage({super.key}); @override ConsumerState createState() => _MedicationListPageState(); } + class _MedicationListPageState extends ConsumerState { String _filter = ''; Future>>? _future; + @override void initState() { super.initState(); _load(); } void _load() { setState(() { _future = ref.read(medicationServiceProvider).getMedications(_filter); }); } @@ -18,93 +24,116 @@ class _MedicationListPageState extends ConsumerState { _load(); } + static const _tabs = [('全部', ''), ('服用中', 'active'), ('已停药', 'inactive')]; + @override Widget build(BuildContext context) { + final theme = ShadTheme.of(context); return Scaffold( - backgroundColor: const Color(0xFFF8F9FC), - appBar: AppBar(title: const Text('用药管理'), actions: [ - TextButton(onPressed: () { pushRoute(ref, 'medicationEdit'); }, child: const Text('添加', style: TextStyle(fontSize: 15, color: Color(0xFF6C5CE7)))), - ]), + backgroundColor: theme.colorScheme.background, + appBar: AppBar( + title: const Text('用药管理'), + actions: [ + ShadButton.ghost( + size: ShadButtonSize.sm, + child: const Text('添加'), + onPressed: () => pushRoute(ref, 'medicationEdit'), + ), + ], + ), body: Column(children: [ - Padding(padding: const EdgeInsets.all(12), child: Row(children: [ - _tab('全部', ''), _tab('服用中', 'active'), _tab('已停药', 'inactive'), - ])), + Padding( + padding: const EdgeInsets.all(AppTheme.sMd), + child: Row(children: _tabs.map((t) => AppTabChip( + label: t.$1, + selected: _filter == t.$2, + onTap: () { _filter = t.$2; _load(); }, + )).toList()), + ), Expanded(child: FutureBuilder>>( future: _future, builder: (ctx, snap) { final list = snap.data ?? []; - if (list.isEmpty) return Center(child: Column(mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.medication_outlined, size: 64, color: Colors.grey[300]), - const SizedBox(height: 12), Text('暂无用药', style: TextStyle(color: Colors.grey[500])), - ])); - return ListView.builder(padding: const EdgeInsets.fromLTRB(0, 0, 0, 12), itemCount: list.length + 1, itemBuilder: (ctx, i) { - if (i == list.length) return const SizedBox(height: 80); - final m = list[i]; - final times = (m['timeOfDay'] as List?)?.map((t) => t.toString().substring(0, 5)).join(' ') ?? ''; - final isActive = m['isActive'] == true; - return _SwipeDeleteTile( - key: Key(m['id']?.toString() ?? '$i'), - onDelete: () => _delete(m['id']?.toString() ?? ''), - onTap: () => pushRoute(ref, 'medCheckIn', params: {'id': m['id']?.toString() ?? ''}), - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), - padding: const EdgeInsets.all(14), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)), - child: Row(children: [ - Container(width: 44, height: 44, decoration: BoxDecoration(color: isActive ? const Color(0xFFEDEAFF) : const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(12)), child: Icon(Icons.medication_outlined, size: 22, color: isActive ? const Color(0xFF6C5CE7) : Colors.grey)), - const SizedBox(width: 12), - Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row(children: [ - Text(m['name']?.toString() ?? '', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: isActive ? const Color(0xFF1A1A1A) : Colors.grey)), - if (!isActive) ...[const SizedBox(width: 6), Container(padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1), decoration: BoxDecoration(color: Colors.grey[200], borderRadius: BorderRadius.circular(4)), child: const Text('已停', style: TextStyle(fontSize: 10, color: Color(0xFF999999))))], + if (list.isEmpty) { + return AppEmptyState( + icon: LucideIcons.pill, + title: '暂无用药', + subtitle: '点击右上角添加药品', + ); + } + return ListView.builder( + padding: const EdgeInsets.fromLTRB(0, 0, 0, AppTheme.sMd), + itemCount: list.length + 1, + itemBuilder: (ctx, i) { + if (i == list.length) return const SizedBox(height: 80); + final m = list[i]; + final times = (m['timeOfDay'] as List?)?.map((t) => t.toString().substring(0, 5)).join(' ') ?? ''; + final isActive = m['isActive'] == true; + final id = m['id']?.toString() ?? ''; + return Dismissible( + key: Key(id), + direction: DismissDirection.endToStart, + confirmDismiss: (_) async { + await _delete(id); + return false; + }, + background: Container( + margin: const EdgeInsets.symmetric(horizontal: AppTheme.sLg, vertical: 4), + decoration: BoxDecoration( + color: theme.colorScheme.destructive, + borderRadius: BorderRadius.circular(AppTheme.rMd), + ), + alignment: Alignment.centerRight, + padding: const EdgeInsets.only(right: 20), + child: Icon(LucideIcons.trash, color: theme.colorScheme.destructiveForeground, size: 25), + ), + child: GestureDetector( + onTap: () => pushRoute(ref, 'medCheckIn', params: {'id': id}), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: AppTheme.sLg, vertical: 4), + padding: const EdgeInsets.all(AppTheme.sLg), + decoration: BoxDecoration( + color: theme.colorScheme.card, + borderRadius: BorderRadius.circular(AppTheme.rMd), + boxShadow: [AppTheme.shadowLight], + ), + child: Row(children: [ + Container( + width: 44, height: 44, + decoration: BoxDecoration( + color: isActive ? AppTheme.primaryLight : theme.colorScheme.muted, + borderRadius: BorderRadius.circular(AppTheme.rSm), + ), + child: Icon(LucideIcons.pill, size: 25, color: isActive ? AppTheme.primary : theme.colorScheme.mutedForeground), + ), + const SizedBox(width: AppTheme.sMd), + Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row(children: [ + Text(m['name']?.toString() ?? '', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: isActive ? theme.colorScheme.foreground : theme.colorScheme.mutedForeground)), + if (!isActive) ...[ + const SizedBox(width: 6), + Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1), + decoration: BoxDecoration( + color: theme.colorScheme.muted, + borderRadius: BorderRadius.circular(4), + ), + child: Text('已停', style: TextStyle(fontSize: 13, color: theme.colorScheme.mutedForeground)), + ), + ], + ]), + const SizedBox(height: 2), + Text('${m['dosage'] ?? ''} $times', style: TextStyle(fontSize: 16, color: theme.colorScheme.mutedForeground)), + ])), + Icon(LucideIcons.chevronRight, size: 21, color: theme.colorScheme.border), ]), - const SizedBox(height: 2), - Text('${m['dosage'] ?? ''} $times', style: const TextStyle(fontSize: 13, color: Color(0xFF888888))), - ])), - const Icon(Icons.chevron_right, size: 20, color: Color(0xFFCCCCCC)), - ]), - ), - ); - }); + ), + ), + ); + }, + ); }, )), ]), ); } - - Widget _tab(String label, String value) { - final sel = _filter == value; - return GestureDetector(onTap: () { _filter = value; _load(); }, - child: Container(margin: const EdgeInsets.only(right: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), - decoration: BoxDecoration(color: sel ? const Color(0xFF6C5CE7) : const Color(0xFFEDEAFF), borderRadius: BorderRadius.circular(16)), - child: Text(label, style: TextStyle(fontSize: 13, color: sel ? Colors.white : const Color(0xFF6C5CE7), fontWeight: FontWeight.w500))), - ); - } -} - -class _SwipeDeleteTile extends StatefulWidget { - final Widget child; final VoidCallback onDelete; final VoidCallback onTap; - const _SwipeDeleteTile({super.key, required this.child, required this.onDelete, required this.onTap}); - @override State<_SwipeDeleteTile> createState() => _SwipeDeleteTileState(); -} -class _SwipeDeleteTileState extends State<_SwipeDeleteTile> with SingleTickerProviderStateMixin { - double _dx = 0; static const _max = 80.0, _threshold = 40.0; - void _onUpdate(DragUpdateDetails d) => setState(() => _dx = (_dx + d.delta.dx).clamp(-_max, 0.0)); - void _onEnd(DragEndDetails d) => setState(() => _dx = _dx < -_threshold ? -_max : 0.0); - void _doDelete() { widget.onDelete(); setState(() => _dx = 0); } - @override Widget build(BuildContext context) { - final w = GestureDetector( - onHorizontalDragUpdate: _onUpdate, - onHorizontalDragEnd: _onEnd, - child: Transform.translate(offset: Offset(_dx, 0), child: widget.child), - ); - final red = Container( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), - decoration: const BoxDecoration(color: Color(0xFFE53935), borderRadius: BorderRadius.all(Radius.circular(16))), - child: const Align(alignment: Alignment.centerRight, child: Padding(padding: EdgeInsets.only(right: 20), child: Icon(Icons.delete_outline, color: Colors.white, size: 24))), - ); - return GestureDetector( - onTap: _dx < 0 ? _doDelete : widget.onTap, - child: Stack(children: [Positioned.fill(child: red), w]), - ); - } } diff --git a/health_app/lib/pages/profile/profile_detail_page.dart b/health_app/lib/pages/profile/profile_detail_page.dart index 933f5a9..f320174 100644 --- a/health_app/lib/pages/profile/profile_detail_page.dart +++ b/health_app/lib/pages/profile/profile_detail_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../../core/app_theme.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/navigation_provider.dart'; import '../../providers/data_providers.dart'; @@ -12,19 +13,19 @@ class ProfileDetailPage extends ConsumerWidget { final userService = ref.watch(userServiceProvider); return Scaffold( - backgroundColor: const Color(0xFFF8F9FC), + backgroundColor: AppTheme.bg, appBar: AppBar( backgroundColor: Colors.white, elevation: 0, leading: IconButton(icon: const Icon(Icons.chevron_left), onPressed: () => Navigator.pop(context)), title: Row(mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.person_outline, size: 20, color: Colors.grey[600]), + Icon(Icons.person_outline, size: 23, color: Colors.grey[600]), const SizedBox(width: 6), Text('健康档案', style: TextStyle(color: Colors.grey[800], fontWeight: FontWeight.w600)), ]), centerTitle: true, ), - body: SafeArea(child: SingleChildScrollView(padding: const EdgeInsets.all(16), child: Column(children: [_buildUserCard(userService), const SizedBox(height: 16), _buildHealthOverview(latestHealth), const SizedBox(height: 16), _buildHistoryList(), const SizedBox(height: 16), SizedBox(width: double.infinity, height: 48, child: OutlinedButton(onPressed: () => pushRoute(ref, 'settings'), style: OutlinedButton.styleFrom(foregroundColor: const Color(0xFF8B9CF7), side: const BorderSide(color: Color(0xFF8B9CF7)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24))), child: const Text('退出档案')))]))), + body: SafeArea(child: SingleChildScrollView(padding: const EdgeInsets.all(16), child: Column(children: [_buildUserCard(userService), const SizedBox(height: 16), _buildHealthOverview(latestHealth), const SizedBox(height: 16), _buildHistoryList(), const SizedBox(height: 16), SizedBox(width: double.infinity, height: 48, child: OutlinedButton(onPressed: () => pushRoute(ref, 'settings'), style: OutlinedButton.styleFrom(foregroundColor: AppTheme.primary, side: const BorderSide(color: AppTheme.primary), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24))), child: const Text('退出档案')))]))), ); } @@ -41,17 +42,17 @@ class ProfileDetailPage extends ConsumerWidget { return Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20), - boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))]), + boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))]), child: Row(children: [ - CircleAvatar(radius: 32, backgroundColor: const Color(0xFFF0F2FF), - child: Text(name.toString().isNotEmpty ? name.toString()[0] : '?', style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Color(0xFF8B9CF7)))), + CircleAvatar(radius: 32, backgroundColor: AppTheme.primaryLight, + child: Text(name.toString().isNotEmpty ? name.toString()[0] : '?', style: const TextStyle(fontSize: 25, fontWeight: FontWeight.bold, color: AppTheme.primary))), const SizedBox(width: 16), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(name.toString(), style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Color(0xFF1A1A1A))), + Text(name.toString(), style: const TextStyle(fontSize: 25, fontWeight: FontWeight.bold, color: Color(0xFF1A1A1A))), if (info.isNotEmpty) const SizedBox(height: 4), - if (info.isNotEmpty) Text(info, style: TextStyle(fontSize: 14, color: Colors.grey[500])), + if (info.isNotEmpty) Text(info, style: TextStyle(fontSize: 17, color: Colors.grey[500])), ])), - Icon(Icons.chevron_right, size: 24, color: Colors.grey[400]), + Icon(Icons.chevron_right, size: 28, color: Colors.grey[400]), ]), ); }, @@ -71,13 +72,13 @@ class ProfileDetailPage extends ConsumerWidget { return Container( width: double.infinity, padding: const EdgeInsets.all(20), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))]), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))]), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('健康概览', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + const Text('健康概览', style: TextStyle(fontSize: 21, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), const SizedBox(height: 4), - Text('(最近测量)', style: TextStyle(fontSize: 13, color: Colors.grey[500])), + Text('(最近测量)', style: TextStyle(fontSize: 16, color: Colors.grey[500])), const SizedBox(height: 16), - healthData.when(data: (data) => _buildMetricsList(data), loading: () => const Center(child: Padding(padding: EdgeInsets.all(24), child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF8B9CF7)))), error: (_, e) => _buildMetricsEmpty()), + healthData.when(data: (data) => _buildMetricsList(data), loading: () => const Center(child: Padding(padding: EdgeInsets.all(24), child: CircularProgressIndicator(strokeWidth: 2, color: AppTheme.primary))), error: (_, e) => _buildMetricsEmpty()), ]), ); } @@ -93,15 +94,15 @@ class ProfileDetailPage extends ConsumerWidget { String _formatBP(dynamic bp) { if (bp is Map) { final s = bp['systolic']; final d = bp['diastolic']; if (s != null && d != null) return '$s/$d'; } return '--/--'; } String _formatMetric(dynamic val, String unit) { if (val is Map) { final v = val['value']; if (v != null) return '$v$unit'; } return '-- $unit'; } - Widget _metricRow(IconData icon, String label, String value) => InkWell(onTap: () {}, borderRadius: BorderRadius.circular(12), child: Padding(padding: const EdgeInsets.symmetric(vertical: 14), child: Row(children: [Container(width: 40, height: 40, decoration: BoxDecoration(color: const Color(0xFFF0F2FF), borderRadius: BorderRadius.circular(10)), child: Icon(icon, size: 18, color: const Color(0xFF8B9CF7))), const SizedBox(width: 12), Expanded(child: Text(label, style: const TextStyle(fontSize: 15, color: Color(0xFF333333)))), Text(value, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), const SizedBox(width: 8), Icon(Icons.chevron_right, size: 18, color: Colors.grey[400])]))); + Widget _metricRow(IconData icon, String label, String value) => InkWell(onTap: () {}, borderRadius: BorderRadius.circular(12), child: Padding(padding: const EdgeInsets.symmetric(vertical: 14), child: Row(children: [Container(width: 40, height: 40, decoration: BoxDecoration(color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(10)), child: Icon(icon, size: 21, color: AppTheme.primary)), const SizedBox(width: 12), Expanded(child: Text(label, style: const TextStyle(fontSize: 18, color: Color(0xFF333333)))), Text(value, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), const SizedBox(width: 8), Icon(Icons.chevron_right, size: 21, color: Colors.grey[400])]))); Widget _buildHistoryList() { final items = [{'date': '05-31', 'label': '血压 · 餐前', 'value': '128/82', 'status': 'normal'}, {'date': '05-30', 'label': '血压 · 餐后', 'value': '135/85', 'status': 'warning'}, {'date': '05-29', 'label': '血压 · 餐前', 'value': '122/78', 'status': 'normal'}, {'date': '05-28', 'label': '血压 · 餐前', 'value': '118/76', 'status': 'normal'}, {'date': '05-27', 'label': '血糖 · 空腹', 'value': '5.6', 'status': 'normal'}, {'date': '05-26', 'label': '血压 · 餐前', 'value': '120/80', 'status': 'normal'}]; - return Container(decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))]), child: Column(children: [Container(padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: Row(children: [const Text('历史记录', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), const Spacer(), Text('查看更多', style: TextStyle(fontSize: 13, color: const Color(0xFF8B9CF7)))])), ...items.map(_historyItem)])); + return Container(decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))]), child: Column(children: [Container(padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: Row(children: [const Text('历史记录', style: TextStyle(fontSize: 21, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), const Spacer(), Text('查看更多', style: TextStyle(fontSize: 16, color: AppTheme.primary))])), ...items.map(_historyItem)])); } Widget _historyItem(Map item) { final isNormal = item['status'] == 'normal'; - return Container(margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration(borderRadius: BorderRadius.circular(12)), child: Row(children: [Text(item['date']?.toString() ?? '', style: const TextStyle(fontSize: 14, color: Color(0xFF9E9E9E))), const SizedBox(width: 8), Expanded(child: Text(item['label']?.toString() ?? '', style: const TextStyle(fontSize: 14, color: Color(0xFF333333)))), Text(item['value']?.toString() ?? '', style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), const SizedBox(width: 8), Container(padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration(color: isNormal ? const Color(0xFF43A047).withAlpha(20) : const Color(0xFFFF9800).withAlpha(20), borderRadius: BorderRadius.circular(10)), child: Text(isNormal ? '正常' : '偏高', style: TextStyle(fontSize: 11, color: isNormal ? const Color(0xFF43A047) : const Color(0xFFFF9800))))])); + return Container(margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration(borderRadius: BorderRadius.circular(12)), child: Row(children: [Text(item['date']?.toString() ?? '', style: const TextStyle(fontSize: 17, color: Color(0xFF9E9E9E))), const SizedBox(width: 8), Expanded(child: Text(item['label']?.toString() ?? '', style: const TextStyle(fontSize: 17, color: Color(0xFF333333)))), Text(item['value']?.toString() ?? '', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), const SizedBox(width: 8), Container(padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration(color: isNormal ? AppTheme.success.withAlpha(20) : const Color(0xFFFF9800).withAlpha(20), borderRadius: BorderRadius.circular(10)), child: Text(isNormal ? '正常' : '偏高', style: TextStyle(fontSize: 14, color: isNormal ? AppTheme.success : const Color(0xFFFF9800))))])); } } diff --git a/health_app/lib/pages/profile/profile_page.dart b/health_app/lib/pages/profile/profile_page.dart index 9a0ff24..5abb455 100644 --- a/health_app/lib/pages/profile/profile_page.dart +++ b/health_app/lib/pages/profile/profile_page.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import '../../core/app_theme.dart'; import '../../core/navigation_provider.dart'; import '../../providers/auth_provider.dart'; +import '../../widgets/app_menu_item.dart'; class ProfilePage extends ConsumerWidget { const ProfilePage({super.key}); @@ -9,100 +12,101 @@ class ProfilePage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final auth = ref.watch(authProvider); final user = auth.user; + final theme = ShadTheme.of(context); return Scaffold( - backgroundColor: const Color(0xFFF8F9FC), - body: SafeArea(child: SingleChildScrollView(padding: const EdgeInsets.only(bottom: 20), child: Column(children: [ - // 用户头部 - Container( - width: double.infinity, - padding: const EdgeInsets.all(24), - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), - ), - child: InkWell( - onTap: () => pushRoute(ref, 'healthArchive'), - borderRadius: BorderRadius.circular(12), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Row(children: [ - Stack(children: [ - CircleAvatar(radius: 32, backgroundColor: const Color(0xFFEDEAFF), - backgroundImage: user?.avatarUrl != null ? NetworkImage(user!.avatarUrl!) : null, - child: user?.avatarUrl == null ? const Icon(Icons.person, size: 36, color: Color(0xFF6C5CE7)) : null), - Positioned(right: 0, bottom: 0, - child: Container(width: 20, height: 20, - decoration: BoxDecoration(color: const Color(0xFF6C5CE7), borderRadius: BorderRadius.circular(10), border: Border.all(color: Colors.white, width: 2)), - child: const Icon(Icons.edit, size: 10, color: Colors.white))), + backgroundColor: theme.colorScheme.background, + body: SafeArea(child: SingleChildScrollView( + padding: const EdgeInsets.only(bottom: AppTheme.sXl), + child: Column(children: [ + // 用户头部卡片 + Container( + width: double.infinity, + padding: const EdgeInsets.all(AppTheme.rXl), + decoration: BoxDecoration( + color: theme.colorScheme.card, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(AppTheme.rXl), + bottomRight: Radius.circular(AppTheme.rXl), + ), + boxShadow: [AppTheme.shadowLight], + ), + child: InkWell( + onTap: () => pushRoute(ref, 'healthArchive'), + borderRadius: BorderRadius.circular(AppTheme.rSm), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: AppTheme.sSm), + child: Row(children: [ + Stack(children: [ + CircleAvatar( + radius: 32, + backgroundColor: AppTheme.primaryLight, + backgroundImage: user?.avatarUrl != null ? NetworkImage(user!.avatarUrl!) : null, + child: user?.avatarUrl == null ? const Icon(Icons.person, size: 36, color: AppTheme.primary) : null, + ), + Positioned( + right: 0, bottom: 0, + child: Container( + width: 20, height: 20, + decoration: BoxDecoration( + color: AppTheme.primary, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: theme.colorScheme.card, width: 2), + ), + child: const Icon(Icons.edit, size: 13, color: Colors.white), + ), + ), + ]), + const SizedBox(width: AppTheme.sLg), + Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(user?.name ?? '未设置昵称', + style: TextStyle(fontSize: 23, fontWeight: FontWeight.bold, color: theme.colorScheme.foreground)), + const SizedBox(height: 4), + Text(user?.phone ?? '', style: TextStyle(fontSize: 17, color: theme.colorScheme.mutedForeground)), + ])), + Icon(LucideIcons.chevronRight, size: 25, color: theme.colorScheme.mutedForeground), ]), - const SizedBox(width: 16), - Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(user?.name ?? '未设置昵称', style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Color(0xFF1A1A1A))), - const SizedBox(height: 4), - Text(user?.phone ?? '', style: TextStyle(fontSize: 14, color: Colors.grey[500])), - ])), - Icon(Icons.chevron_right, size: 24, color: Colors.grey[400]), - ]), + ), ), ), - ), - const SizedBox(height: 12), - _MenuItem(icon: Icons.folder_shared, title: '健康档案', onTap: () => pushRoute(ref, 'healthArchive')), - _MenuItem(icon: Icons.devices, title: '设备管理', onTap: () => pushRoute(ref, 'devices')), - _MenuItem(icon: Icons.settings_outlined, title: '设置', onTap: () => pushRoute(ref, 'settings')), - - const SizedBox(height: 40), - GestureDetector( - onTap: () async { - final ok = await showDialog( - context: context, - builder: (ctx) => AlertDialog( - title: const Text('退出登录'), - content: const Text('确定退出?'), - actions: [ - TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('取消')), - TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('确定')), - ], + const SizedBox(height: AppTheme.sMd), + AppMenuItem(icon: LucideIcons.folderArchive, title: '健康档案', onTap: () => pushRoute(ref, 'healthArchive')), + AppMenuItem(icon: LucideIcons.monitorSmartphone, title: '设备管理', onTap: () => pushRoute(ref, 'devices')), + AppMenuItem(icon: LucideIcons.settings, title: '设置', onTap: () => pushRoute(ref, 'settings')), + const SizedBox(height: 40), + // 退出登录 + GestureDetector( + onTap: () => _logout(context, ref), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: AppTheme.rXl), + height: 50, + alignment: Alignment.center, + decoration: BoxDecoration( + border: Border.all(color: theme.colorScheme.destructive.withAlpha(80)), + borderRadius: BorderRadius.circular(AppTheme.rPill), ), - ); - if (ok == true) { await ref.read(authProvider.notifier).logout(); goRoute(ref, 'login'); } - }, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 24), - height: 50, alignment: Alignment.center, - decoration: BoxDecoration(border: Border.all(color: const Color(0xFFE53935)), borderRadius: BorderRadius.circular(25)), - child: const Text('退出登录', style: TextStyle(fontSize: 16, color: Color(0xFFE53935), fontWeight: FontWeight.w500)), + child: Text('退出登录', style: TextStyle(fontSize: 19, color: theme.colorScheme.destructive, fontWeight: FontWeight.w500)), + ), ), - ), - ]))), + ]), + )), ); } -} -class _MenuItem extends StatelessWidget { - final IconData icon; - final String title; - final String? trailing; - final VoidCallback? onTap; - const _MenuItem({required this.icon, required this.title, this.trailing, this.onTap}); - - @override Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - behavior: HitTestBehavior.opaque, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 2), - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 14), - decoration: const BoxDecoration(color: Colors.white), - child: Row(children: [ - Container(width: 36, height: 36, decoration: BoxDecoration(color: const Color(0xFFEDEAFF), borderRadius: BorderRadius.circular(10)), child: Icon(icon, size: 18, color: const Color(0xFF6C5CE7))), - const SizedBox(width: 12), - Expanded(child: Text(title, style: const TextStyle(fontSize: 16, color: Color(0xFF1A1A1A)))), - if (trailing != null && trailing!.isNotEmpty) Text(trailing!, style: TextStyle(fontSize: 14, color: Colors.grey[400])), - Icon(Icons.chevron_right, size: 20, color: Colors.grey[300]), - ]), + Future _logout(BuildContext context, WidgetRef ref) async { + final theme = ShadTheme.of(context); + final ok = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppTheme.rXl)), + title: Text('退出登录', style: TextStyle(color: theme.colorScheme.foreground)), + content: Text('确定退出?', style: TextStyle(color: theme.colorScheme.mutedForeground)), + actions: [ + TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text('取消', style: TextStyle(color: theme.colorScheme.mutedForeground))), + TextButton(onPressed: () => Navigator.pop(ctx, true), child: Text('确定', style: TextStyle(color: theme.colorScheme.destructive))), + ], ), ); + if (ok == true) { await ref.read(authProvider.notifier).logout(); goRoute(ref, 'login'); } } } diff --git a/health_app/lib/pages/profile/service_package_detail_page.dart b/health_app/lib/pages/profile/service_package_detail_page.dart index 7e7171c..a1a3716 100644 --- a/health_app/lib/pages/profile/service_package_detail_page.dart +++ b/health_app/lib/pages/profile/service_package_detail_page.dart @@ -21,7 +21,7 @@ class ServicePackageDetailPage extends ConsumerWidget { return Scaffold( backgroundColor: AppTheme.bg, appBar: AppBar( - title: Text(package.title, style: const TextStyle(fontSize: 16)), + title: Text(package.title, style: const TextStyle(fontSize: 19)), centerTitle: true, ), body: SingleChildScrollView( @@ -50,12 +50,12 @@ class ServicePackageDetailPage extends ConsumerWidget { color: Colors.white.withAlpha(40), borderRadius: BorderRadius.circular(6), ), - child: const Text('VIP 产品权益', style: TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.w600)), + child: const Text('VIP 产品权益', style: TextStyle(fontSize: 15, color: Colors.white, fontWeight: FontWeight.w600)), ), const SizedBox(height: 12), - Text(package.title, style: const TextStyle(fontSize: 22, fontWeight: FontWeight.w700, color: Colors.white)), + Text(package.title, style: const TextStyle(fontSize: 25, fontWeight: FontWeight.w700, color: Colors.white)), const SizedBox(height: 8), - Text(package.subtitle, style: TextStyle(fontSize: 14, color: Colors.white.withAlpha(200))), + Text(package.subtitle, style: TextStyle(fontSize: 17, color: Colors.white.withAlpha(200))), ], ), ), @@ -76,19 +76,19 @@ class ServicePackageDetailPage extends ConsumerWidget { color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(14), ), - child: Icon(s.icon, size: 22, color: AppTheme.primary), + child: Icon(s.icon, size: 25, color: AppTheme.primary), ), const SizedBox(height: 6), - Text(s.label, style: const TextStyle(fontSize: 12, color: AppTheme.textSub), textAlign: TextAlign.center), + Text(s.label, style: const TextStyle(fontSize: 15, color: AppTheme.textSub), textAlign: TextAlign.center), ], ), )).toList(), ), ), // 适用人群 - _Section(title: '适用人群', child: Text(package.targetAudience, style: const TextStyle(fontSize: 14, color: AppTheme.textSub, height: 1.6))), + _Section(title: '适用人群', child: Text(package.targetAudience, style: const TextStyle(fontSize: 17, color: AppTheme.textSub, height: 1.6))), // 详细说明 - ...package.detailSections.map((s) => _Section(title: s.title, child: Text(s.content, style: const TextStyle(fontSize: 14, color: AppTheme.textSub, height: 1.6)))), + ...package.detailSections.map((s) => _Section(title: s.title, child: Text(s.content, style: const TextStyle(fontSize: 17, color: AppTheme.textSub, height: 1.6)))), const SizedBox(height: 40), ], ), @@ -115,7 +115,7 @@ class _Section extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: AppTheme.text)), + Text(title, style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: AppTheme.text)), const SizedBox(height: 10), child, ], diff --git a/health_app/lib/pages/remaining_pages.dart b/health_app/lib/pages/remaining_pages.dart index 249c754..3372736 100644 --- a/health_app/lib/pages/remaining_pages.dart +++ b/health_app/lib/pages/remaining_pages.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import '../core/app_theme.dart'; import '../core/navigation_provider.dart'; import '../providers/auth_provider.dart'; import '../providers/data_providers.dart'; @@ -17,12 +19,12 @@ class _DietRecordListPageState extends ConsumerState { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color(0xFFF8F9FC), + backgroundColor: AppTheme.bg, appBar: AppBar(title: const Text('饮食记录')), body: FutureBuilder>>( future: _future, builder: (ctx, snap) { - if (snap.connectionState == ConnectionState.waiting) return const Center(child: CircularProgressIndicator(color: Color(0xFF6C5CE7))); + if (snap.connectionState == ConnectionState.waiting) return const Center(child: CircularProgressIndicator(color: AppTheme.primary)); final data = snap.data ?? []; if (data.isEmpty) return _empty(context, '饮食记录', '暂无饮食记录,可通过「拍饮食」录入'); return ListView.builder( @@ -46,18 +48,18 @@ class _DietRecordListPageState extends ConsumerState { padding: const EdgeInsets.all(14), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)), child: Row(children: [ - Container(width: 44, height: 44, decoration: BoxDecoration(color: const Color(0xFFEDEAFF), borderRadius: BorderRadius.circular(12)), child: Center(child: Text(mealIcons[d['mealType']?.toString()] ?? '🍽️', style: const TextStyle(fontSize: 20)))), + Container(width: 44, height: 44, decoration: BoxDecoration(color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(12)), child: Center(child: Text(mealIcons[d['mealType']?.toString()] ?? '🍽️', style: const TextStyle(fontSize: 23)))), const SizedBox(width: 12), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ - Text(mealLabel, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), + Text(mealLabel, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), const SizedBox(width: 8), - Text('${d['totalCalories'] ?? 0}千卡', style: const TextStyle(fontSize: 13, color: Color(0xFF6C5CE7))), + Text('${d['totalCalories'] ?? 0}千卡', style: const TextStyle(fontSize: 16, color: AppTheme.primary)), ]), const SizedBox(height: 4), - Text(items.map((f) => f['name']).join(' · '), style: const TextStyle(fontSize: 13, color: Color(0xFF888888)), maxLines: 1, overflow: TextOverflow.ellipsis), + Text(items.map((f) => f['name']).join(' · '), style: const TextStyle(fontSize: 16, color: Color(0xFF888888)), maxLines: 1, overflow: TextOverflow.ellipsis), ])), - const Icon(Icons.chevron_right, size: 18, color: Color(0xFFCCCCCC)), + const Icon(Icons.chevron_right, size: 21, color: Color(0xFFCCCCCC)), ]), ), ); @@ -76,12 +78,12 @@ class DietRecordDetailPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final service = ref.watch(dietServiceProvider); return Scaffold( - backgroundColor: const Color(0xFFF8F9FC), + backgroundColor: AppTheme.bg, appBar: AppBar(title: const Text('饮食详情')), body: FutureBuilder>>( future: service.getRecords(), builder: (ctx, snap) { - if (!snap.hasData) return const Center(child: CircularProgressIndicator(color: Color(0xFF6C5CE7))); + if (!snap.hasData) return const Center(child: CircularProgressIndicator(color: AppTheme.primary)); final d = snap.data!.firstWhere((r) => r['id']?.toString() == id, orElse: () => {}); if (d.isEmpty) return const Center(child: Text('记录不存在')); final items = (d['foodItems'] as List?)?.cast>() ?? []; @@ -89,11 +91,11 @@ class DietRecordDetailPage extends ConsumerWidget { final mealNames = {'Breakfast':'早餐','Lunch':'午餐','Dinner':'晚餐','Snack':'加餐'}; return ListView(padding: const EdgeInsets.all(16), children: [ Container(padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)), child: Row(children: [ - Container(width: 48, height: 48, decoration: BoxDecoration(color: const Color(0xFFEDEAFF), borderRadius: BorderRadius.circular(14)), child: const Icon(Icons.restaurant, size: 24, color: Color(0xFF6C5CE7))), + Container(width: 48, height: 48, decoration: BoxDecoration(color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(14)), child: const Icon(Icons.restaurant, size: 28, color: AppTheme.primary)), const SizedBox(width: 14), Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(mealNames[d['mealType']?.toString()] ?? '', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700)), - Text('$totalCal 千卡 · ${d['recordedAt'] ?? ''}', style: const TextStyle(fontSize: 13, color: Color(0xFF999999))), + Text(mealNames[d['mealType']?.toString()] ?? '', style: const TextStyle(fontSize: 21, fontWeight: FontWeight.w700)), + Text('$totalCal 千卡 · ${d['recordedAt'] ?? ''}', style: const TextStyle(fontSize: 16, color: Color(0xFF999999))), ]), ])), const SizedBox(height: 16), @@ -103,10 +105,10 @@ class DietRecordDetailPage extends ConsumerWidget { decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(14)), child: Row(children: [ Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(f['name']?.toString() ?? '', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), - if ((f['portion']?.toString() ?? '').isNotEmpty) Text(f['portion']!.toString(), style: const TextStyle(fontSize: 13, color: Color(0xFF888888))), + Text(f['name']?.toString() ?? '', style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600)), + if ((f['portion']?.toString() ?? '').isNotEmpty) Text(f['portion']!.toString(), style: const TextStyle(fontSize: 16, color: Color(0xFF888888))), ])), - Text('${f['calories'] ?? 0} kcal', style: const TextStyle(fontSize: 15, color: Color(0xFF6C5CE7), fontWeight: FontWeight.w600)), + Text('${f['calories'] ?? 0} kcal', style: const TextStyle(fontSize: 18, color: AppTheme.primary, fontWeight: FontWeight.w600)), ]), )), ]); @@ -139,9 +141,9 @@ class _ExercisePlanPageState extends ConsumerState { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color(0xFFF8F9FC), + backgroundColor: AppTheme.bg, appBar: AppBar(title: const Text('运动计划')), - floatingActionButton: FloatingActionButton(onPressed: () { pushRoute(ref, 'exerciseCreate'); }, backgroundColor: const Color(0xFF6C5CE7), child: const Icon(Icons.add)), + floatingActionButton: FloatingActionButton(onPressed: () { pushRoute(ref, 'exerciseCreate'); }, backgroundColor: AppTheme.primary, child: const Icon(Icons.add)), body: FutureBuilder>>( future: _future, builder: (ctx, snap) { @@ -179,14 +181,14 @@ class _ExercisePlanPageState extends ConsumerState { padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)), child: Row(children: [ - Container(width: 44, height: 44, decoration: BoxDecoration(color: const Color(0xFFEDEAFF), borderRadius: BorderRadius.circular(12)), child: const Icon(Icons.directions_run, size: 22, color: Color(0xFF6C5CE7))), + Container(width: 44, height: 44, decoration: BoxDecoration(color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(12)), child: const Icon(Icons.directions_run, size: 25, color: AppTheme.primary)), const SizedBox(width: 12), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(exerciseName, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), + Text(exerciseName, style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600)), const SizedBox(height: 2), - Text('$startDate 起 · ${items.first['durationMinutes'] ?? 0}分钟/天', style: const TextStyle(fontSize: 13, color: Color(0xFF888888))), + Text('$startDate 起 · ${items.first['durationMinutes'] ?? 0}分钟/天', style: const TextStyle(fontSize: 16, color: Color(0xFF888888))), const SizedBox(height: 6), - Text('$done/$total 天已完成', style: const TextStyle(fontSize: 12, color: Color(0xFF6C5CE7))), + Text('$done/$total 天已完成', style: const TextStyle(fontSize: 15, color: AppTheme.primary)), ])), GestureDetector( onTap: todayItem != null ? () => _checkIn(p['id']?.toString() ?? '', todayItem['id']?.toString() ?? '') : null, @@ -194,10 +196,10 @@ class _ExercisePlanPageState extends ConsumerState { duration: const Duration(milliseconds: 300), width: 40, height: 40, decoration: BoxDecoration( - color: todayDone ? const Color(0xFFDCFCE7) : (todayItem != null ? const Color(0xFFEDEAFF) : const Color(0xFFF5F5F5)), + color: todayDone ? const Color(0xFFDCFCE7) : (todayItem != null ? AppTheme.primaryLight : const Color(0xFFF5F5F5)), borderRadius: BorderRadius.circular(12), ), - child: Icon(todayDone ? Icons.check_circle : Icons.check_circle_outline, size: 24, color: todayDone ? const Color(0xFF43A047) : const Color(0xFFBBBBBB)), + child: Icon(todayDone ? Icons.check_circle : Icons.check_circle_outline, size: 28, color: todayDone ? AppTheme.success : const Color(0xFFBBBBBB)), ), ), ]), @@ -238,7 +240,7 @@ class _ExercisePlanCreatePageState extends ConsumerState @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color(0xFFF8F9FC), + backgroundColor: AppTheme.bg, appBar: AppBar(title: const Text('新建计划')), body: ListView(padding: const EdgeInsets.all(16), children: [ _field('运动名称', _nameCtrl, hint: '如: 散步、慢跑、太极'), @@ -249,27 +251,27 @@ class _ExercisePlanCreatePageState extends ConsumerState const SizedBox(height: 16), _dateField('结束日期', _end, (d) => setState(() => _end = d)), const SizedBox(height: 32), - SizedBox(width: double.infinity, child: ElevatedButton(onPressed: _save, style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFF6C5CE7), foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), padding: const EdgeInsets.symmetric(vertical: 14)), child: const Text('保存计划', style: TextStyle(fontSize: 16)))), + SizedBox(width: double.infinity, child: ElevatedButton(onPressed: _save, style: ElevatedButton.styleFrom(backgroundColor: AppTheme.primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), padding: const EdgeInsets.symmetric(vertical: 14)), child: const Text('保存计划', style: TextStyle(fontSize: 19)))), ]), ); } Widget _field(String label, TextEditingController ctrl, {String? hint, bool number = false}) { return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: const TextStyle(fontSize: 14, color: Color(0xFF666666))), + Text(label, style: const TextStyle(fontSize: 17, color: Color(0xFF666666))), const SizedBox(height: 6), - TextField(controller: ctrl, keyboardType: number ? TextInputType.number : null, decoration: InputDecoration(hintText: hint, filled: true, fillColor: Colors.white, border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none)), style: const TextStyle(fontSize: 16)), + TextField(controller: ctrl, keyboardType: number ? TextInputType.number : null, decoration: InputDecoration(hintText: hint, filled: true, fillColor: Colors.white, border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none)), style: const TextStyle(fontSize: 19)), ]); } Widget _dateField(String label, DateTime val, ValueChanged onChanged) { return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: const TextStyle(fontSize: 14, color: Color(0xFF666666))), + Text(label, style: const TextStyle(fontSize: 17, color: Color(0xFF666666))), const SizedBox(height: 6), GestureDetector( onTap: () async { final d = await showDatePicker(context: context, initialDate: val, firstDate: DateTime(2024), lastDate: DateTime(2030)); if (d != null) onChanged(d); }, child: Container(width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12)), - child: Text('${val.year}-${val.month.toString().padLeft(2,'0')}-${val.day.toString().padLeft(2,'0')}', style: const TextStyle(fontSize: 16))), + child: Text('${val.year}-${val.month.toString().padLeft(2,'0')}-${val.day.toString().padLeft(2,'0')}', style: const TextStyle(fontSize: 19))), ), ]); } @@ -295,7 +297,7 @@ class _FollowUpListPageState extends ConsumerState { future: _future, builder: (ctx, snap) { if (snap.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator(color: Color(0xFF8B9CF7))); + return const Center(child: CircularProgressIndicator(color: AppTheme.primaryLight)); } final list = snap.data ?? []; if (list.isEmpty) { @@ -305,7 +307,7 @@ class _FollowUpListPageState extends ConsumerState { const SizedBox(height: 12), Text('暂无随访安排', style: Theme.of(context).textTheme.bodyMedium), const SizedBox(height: 4), - const Text('医生创建随访后将显示在这里', style: TextStyle(fontSize: 13, color: Color(0xFF999999))), + const Text('医生创建随访后将显示在这里', style: TextStyle(fontSize: 16, color: Color(0xFF999999))), ]), ); } @@ -335,8 +337,8 @@ class _FollowUpItem extends StatelessWidget { Color _statusColor(String? status) { switch (status) { case 'Upcoming': return const Color(0xFF4F6EF7); - case 'Completed': return const Color(0xFF43A047); - case 'Cancelled': return const Color(0xFFE53935); + case 'Completed': return AppTheme.success; + case 'Cancelled': return AppTheme.error; default: return const Color(0xFF999999); } } @@ -363,27 +365,27 @@ class _FollowUpItem extends StatelessWidget { decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), - boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(10), blurRadius: 4, offset: const Offset(0, 2))], + boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(10), blurRadius: 4, offset: const Offset(0, 2))], ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(8)), - child: Text(label, style: TextStyle(fontSize: 12, color: color, fontWeight: FontWeight.w500)), + child: Text(label, style: TextStyle(fontSize: 15, color: color, fontWeight: FontWeight.w500)), ), const Spacer(), if (item['doctorName'] != null) - Text('👨‍⚕️ ${item['doctorName']}', style: const TextStyle(fontSize: 12, color: Color(0xFF999999))), + Text('👨‍⚕️ ${item['doctorName']}', style: const TextStyle(fontSize: 15, color: Color(0xFF999999))), ]), const SizedBox(height: 12), - Text(item['title']?.toString() ?? '', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), + Text(item['title']?.toString() ?? '', style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600)), const SizedBox(height: 4), if (item['scheduledAt'] != null) - Text(_formatDateTime(item['scheduledAt']?.toString()), style: TextStyle(fontSize: 14, color: Colors.grey[500])), + Text(_formatDateTime(item['scheduledAt']?.toString()), style: TextStyle(fontSize: 17, color: Colors.grey[500])), if ((item['notes']?.toString() ?? '').isNotEmpty) ...[ const SizedBox(height: 8), - Text(item['notes']?.toString() ?? '', style: TextStyle(fontSize: 13, color: Colors.grey[600])), + Text(item['notes']?.toString() ?? '', style: TextStyle(fontSize: 16, color: Colors.grey[600])), ], ]), ); @@ -467,11 +469,11 @@ class _HealthArchivePageState extends ConsumerState { } @override Widget build(BuildContext context) { - if (_loading) return Scaffold(appBar: AppBar(title: const Text('健康档案')), body: const Center(child: CircularProgressIndicator(color: Color(0xFF6C5CE7)))); + if (_loading) return Scaffold(appBar: AppBar(title: const Text('健康档案')), body: const Center(child: CircularProgressIndicator(color: AppTheme.primary))); return Scaffold( - backgroundColor: const Color(0xFFF8F9FC), + backgroundColor: AppTheme.bg, appBar: AppBar(title: const Text('健康档案'), actions: [ - TextButton(onPressed: _save, child: const Text('保存', style: TextStyle(color: Color(0xFF6C5CE7), fontWeight: FontWeight.w600))), + TextButton(onPressed: _save, child: const Text('保存', style: TextStyle(color: AppTheme.primary, fontWeight: FontWeight.w600))), ]), body: ListView(padding: const EdgeInsets.all(16), children: [ _sectionTitle('个人资料'), @@ -500,21 +502,21 @@ class _HealthArchivePageState extends ConsumerState { Widget _sectionTitle(String title) => Padding( padding: const EdgeInsets.only(left: 4, top: 16, bottom: 8), - child: Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: Color(0xFF6C5CE7))), + child: Text(title, style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w700, color: AppTheme.primary)), ); Widget _editableCard({required List children}) => Container( padding: const EdgeInsets.all(16), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [BoxShadow(color: const Color(0xFF6C5CE7).withAlpha(8), blurRadius: 8, offset: const Offset(0, 2))]), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(8), blurRadius: 8, offset: const Offset(0, 2))]), child: Column(children: children), ); Widget _field(String label, TextEditingController ctrl, {String? hint}) => Padding( padding: const EdgeInsets.only(bottom: 12), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: const TextStyle(fontSize: 13, color: Color(0xFF888888))), + Text(label, style: const TextStyle(fontSize: 16, color: Color(0xFF888888))), const SizedBox(height: 4), - TextField(controller: ctrl, decoration: InputDecoration(hintText: hint, filled: true, fillColor: const Color(0xFFF4F5FA), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none)), style: const TextStyle(fontSize: 15)), + TextField(controller: ctrl, decoration: InputDecoration(hintText: hint, filled: true, fillColor: const Color(0xFFF4F5FA), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none)), style: const TextStyle(fontSize: 18)), ]), ); } @@ -581,7 +583,7 @@ class _HealthCalendarPageState extends ConsumerState { ), Text( '${_currentMonth.year}年${_currentMonth.month}月', - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600), + style: const TextStyle(fontSize: 21, fontWeight: FontWeight.w600), ), IconButton( icon: const Icon(Icons.chevron_right, size: 32), @@ -594,7 +596,7 @@ class _HealthCalendarPageState extends ConsumerState { Widget _buildWeekdayHeader() { const weekdays = ['日', '一', '二', '三', '四', '五', '六']; return Row(children: weekdays.map((day) => Expanded( - child: Center(child: Text(day, style: TextStyle(fontSize: 14, color: Colors.grey[500]))), + child: Center(child: Text(day, style: TextStyle(fontSize: 17, color: Colors.grey[500]))), )).toList()); } @@ -631,7 +633,7 @@ class _HealthCalendarPageState extends ConsumerState { return Container( decoration: isToday ? BoxDecoration( - color: const Color(0xFF8B9CF7), + color: AppTheme.primary, borderRadius: BorderRadius.circular(20), ) : null, child: Stack( @@ -640,7 +642,7 @@ class _HealthCalendarPageState extends ConsumerState { Text( '$day', style: TextStyle( - fontSize: 16, + fontSize: 19, color: isToday ? Colors.white : Colors.black, fontWeight: isToday ? FontWeight.w600 : FontWeight.normal, ), @@ -670,8 +672,8 @@ class _HealthCalendarPageState extends ConsumerState { Color _getEventColor(String type) { switch (type) { - case 'medication': return const Color(0xFF8B9CF7); - case 'exercise': return const Color(0xFF43A047); + case 'medication': return AppTheme.primary; + case 'exercise': return AppTheme.success; case 'followup': return const Color(0xFFF59E0B); default: return Colors.grey; } @@ -679,8 +681,8 @@ class _HealthCalendarPageState extends ConsumerState { Widget _buildLegend() { final items = [ - {'color': const Color(0xFF8B9CF7), 'label': '用药提醒'}, - {'color': const Color(0xFF43A047), 'label': '运动计划'}, + {'color': AppTheme.primary, 'label': '用药提醒'}, + {'color': AppTheme.success, 'label': '运动计划'}, {'color': const Color(0xFFF59E0B), 'label': '复查随访'}, ]; return Container( @@ -688,7 +690,7 @@ class _HealthCalendarPageState extends ConsumerState { child: Row(mainAxisAlignment: MainAxisAlignment.center, children: items.map((item) => Row(children: [ Container(width: 10, height: 10, decoration: BoxDecoration(color: item['color'] as Color, borderRadius: BorderRadius.circular(5))), const SizedBox(width: 4), - Text(item['label'] as String, style: TextStyle(fontSize: 12, color: Colors.grey[600])), + Text(item['label'] as String, style: TextStyle(fontSize: 15, color: Colors.grey[600])), const SizedBox(width: 20), ])).toList()), ); @@ -782,7 +784,7 @@ class StaticTextPage extends ConsumerWidget { ), body: SingleChildScrollView( padding: const EdgeInsets.all(20), - child: Text(contents[type] ?? '内容加载中...', style: const TextStyle(fontSize: 14, height: 1.8, color: Color(0xFF333333))), + child: Text(contents[type] ?? '内容加载中...', style: const TextStyle(fontSize: 17, height: 1.8, color: Color(0xFF333333))), ), ); } @@ -831,10 +833,10 @@ class _SwipeDeleteTileState extends State<_SwipeDeleteTile> with SingleTickerPro return Stack(children: [ Positioned.fill(child: Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), - decoration: BoxDecoration(color: const Color(0xFFE53935), borderRadius: BorderRadius.circular(16)), + decoration: BoxDecoration(color: AppTheme.error, borderRadius: BorderRadius.circular(16)), child: const Align( alignment: Alignment.centerRight, - child: Padding(padding: EdgeInsets.only(right: 20), child: Icon(Icons.delete_outline, color: Colors.white, size: 24)), + child: Padding(padding: EdgeInsets.only(right: 20), child: Icon(Icons.delete_outline, color: Colors.white, size: 28)), ), )), GestureDetector( diff --git a/health_app/lib/pages/report/ai_analysis_page.dart b/health_app/lib/pages/report/ai_analysis_page.dart index bae4b26..49467a4 100644 --- a/health_app/lib/pages/report/ai_analysis_page.dart +++ b/health_app/lib/pages/report/ai_analysis_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../../core/app_theme.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/navigation_provider.dart'; import 'report_pages.dart'; @@ -31,12 +32,12 @@ class _AiAnalysisPageState extends ConsumerState { if (analysis == null) { return Scaffold( appBar: AppBar(title: const Text('AI 解读')), - body: const Center(child: CircularProgressIndicator(color: Color(0xFF8B9CF7))), + body: const Center(child: CircularProgressIndicator(color: AppTheme.primary)), ); } return Scaffold( - backgroundColor: const Color(0xFFF8F9FC), + backgroundColor: AppTheme.bg, appBar: AppBar( title: const Text('AI 智能解读'), leading: IconButton( @@ -74,15 +75,15 @@ class _AiAnalysisPageState extends ConsumerState { return Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16), - boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(10), blurRadius: 8)]), + boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(10), blurRadius: 8)]), child: Row(children: [ - Container(width: 48, height: 48, decoration: BoxDecoration(color: const Color(0xFFF0F2FF), borderRadius: BorderRadius.circular(12)), - child: const Icon(Icons.auto_awesome, size: 24, color: Color(0xFF8B9CF7))), + Container(width: 48, height: 48, decoration: BoxDecoration(color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(12)), + child: const Icon(Icons.auto_awesome, size: 28, color: AppTheme.primary)), const SizedBox(width: 14), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(analysis.reportType, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700)), + Text(analysis.reportType, style: const TextStyle(fontSize: 21, fontWeight: FontWeight.w700)), const SizedBox(height: 4), - const Text('AI 预解读结果 · 待医生确认', style: TextStyle(fontSize: 13, color: Color(0xFF999999))), + const Text('AI 预解读结果 · 待医生确认', style: TextStyle(fontSize: 16, color: Color(0xFF999999))), ])), ]), ); @@ -97,12 +98,12 @@ class _AiAnalysisPageState extends ConsumerState { borderRadius: BorderRadius.circular(10), ), child: Row(mainAxisSize: MainAxisSize.min, children: [ - Icon(isReviewed ? Icons.check_circle : Icons.hourglass_empty, size: 18, - color: isReviewed ? const Color(0xFF43A047) : const Color(0xFFF59E0B)), + Icon(isReviewed ? Icons.check_circle : Icons.hourglass_empty, size: 21, + color: isReviewed ? AppTheme.success : const Color(0xFFF59E0B)), const SizedBox(width: 8), Text(isReviewed ? '已通过医生审核' : '等待医生审核中', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, - color: isReviewed ? const Color(0xFF43A047) : const Color(0xFFF59E0B))), + style: TextStyle(fontSize: 17, fontWeight: FontWeight.w500, + color: isReviewed ? AppTheme.success : const Color(0xFFF59E0B))), ]), ); } @@ -114,7 +115,7 @@ class _AiAnalysisPageState extends ConsumerState { child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.fromLTRB(16, 16, 16, 8), - child: Text('指标分析', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700)), + child: Text('指标分析', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w700)), ), ...analysis.indicators.map((ind) => _buildIndicatorRow(ind)), const SizedBox(height: 8), @@ -126,9 +127,9 @@ class _AiAnalysisPageState extends ConsumerState { final Color color; final IconData icon; switch (ind.status) { - case 'high': color = const Color(0xFFE53935); icon = Icons.arrow_upward; break; + case 'high': color = AppTheme.error; icon = Icons.arrow_upward; break; case 'low': color = const Color(0xFFF9A825); icon = Icons.arrow_downward; break; - default: color = const Color(0xFF43A047); icon = Icons.check_circle; + default: color = AppTheme.success; icon = Icons.check_circle; } return Container( margin: const EdgeInsets.symmetric(horizontal: 16), @@ -137,15 +138,15 @@ class _AiAnalysisPageState extends ConsumerState { child: Row(children: [ Expanded( child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(ind.name, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500)), + Text(ind.name, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500)), if (ind.referenceRange != null && ind.referenceRange!.isNotEmpty) - Text('参考值: ${ind.referenceRange}', style: const TextStyle(fontSize: 12, color: Color(0xFF999999))), + Text('参考值: ${ind.referenceRange}', style: const TextStyle(fontSize: 15, color: Color(0xFF999999))), ]), ), Column(crossAxisAlignment: CrossAxisAlignment.end, children: [ Text('${ind.value} ${ind.unit}', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: color)), - Icon(icon, size: 14, color: color), + style: TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: color)), + Icon(icon, size: 17, color: color), ]), ]), ); @@ -158,19 +159,19 @@ class _AiAnalysisPageState extends ConsumerState { border: Border.all(color: const Color(0xFFFDE68A))), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ const Row(children: [ - Text('💡', style: TextStyle(fontSize: 18)), + Text('💡', style: TextStyle(fontSize: 21)), SizedBox(width: 8), - Text('综合解读', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: Color(0xFFD97706))), + Text('综合解读', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w700, color: Color(0xFFD97706))), ]), const SizedBox(height: 10), Text(analysis.summary.isNotEmpty ? analysis.summary : '暂无 AI 解读结果', - style: const TextStyle(fontSize: 14, color: Color(0xFF92400E), height: 1.6)), + style: const TextStyle(fontSize: 17, color: Color(0xFF92400E), height: 1.6)), const SizedBox(height: 10), Container( width: double.infinity, padding: const EdgeInsets.all(10), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10)), child: const Text('⚠️ AI 解读仅供参考,请以医生诊断为准', - style: TextStyle(fontSize: 12, color: Color(0xFFD97706))), + style: TextStyle(fontSize: 15, color: Color(0xFFD97706))), ), ]), ); @@ -183,18 +184,18 @@ class _AiAnalysisPageState extends ConsumerState { border: Border.all(color: const Color(0xFF86EFAC))), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ const Row(children: [ - Text('✅', style: TextStyle(fontSize: 18)), + Text('✅', style: TextStyle(fontSize: 21)), SizedBox(width: 8), - Text('医生审核意见', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: Color(0xFF2E7D32))), + Text('医生审核意见', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w700, color: Color(0xFF2E7D32))), ]), if (report.doctorName != null) ...[ const SizedBox(height: 4), - Text('审核医生:${report.doctorName}', style: const TextStyle(fontSize: 13, color: Color(0xFF4CAF50))), + Text('审核医生:${report.doctorName}', style: const TextStyle(fontSize: 16, color: Color(0xFF4CAF50))), ], if (report.reviewedAt != null) ...[ const SizedBox(height: 2), Text('审核时间:${report.reviewedAt!.toLocal().toString().substring(0, 16)}', - style: const TextStyle(fontSize: 12, color: Color(0xFF81C784))), + style: const TextStyle(fontSize: 15, color: Color(0xFF81C784))), ], if (report.severity != null) ...[ const SizedBox(height: 8), @@ -206,7 +207,7 @@ class _AiAnalysisPageState extends ConsumerState { width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10)), child: Text('💬 ${report.doctorComment!}', - style: const TextStyle(fontSize: 15, color: Color(0xFF1A1A1A), height: 1.5)), + style: const TextStyle(fontSize: 18, color: Color(0xFF1A1A1A), height: 1.5)), ), ], if (report.doctorRecommendation != null && report.doctorRecommendation!.isNotEmpty) ...[ @@ -215,7 +216,7 @@ class _AiAnalysisPageState extends ConsumerState { width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10)), child: Text('📝 ${report.doctorRecommendation!}', - style: const TextStyle(fontSize: 14, color: Color(0xFF333333), height: 1.5)), + style: const TextStyle(fontSize: 17, color: Color(0xFF333333), height: 1.5)), ), ], ]), @@ -224,7 +225,7 @@ class _AiAnalysisPageState extends ConsumerState { Widget _severityBadge(String severity) { final (label, color, bg) = switch (severity) { - 'Normal' => ('🟢 正常', const Color(0xFF43A047), const Color(0xFFDCFCE7)), + 'Normal' => ('🟢 正常', AppTheme.success, const Color(0xFFDCFCE7)), 'Abnormal' => ('🟡 轻度异常', const Color(0xFFF59E0B), const Color(0xFFFFF3E0)), 'Severe' => ('🟠 中度异常', const Color(0xFFFF7043), const Color(0xFFFEE2E2)), 'Critical' => ('🔴 重度异常', const Color(0xFFDC2626), const Color(0xFFFEE2E2)), @@ -233,7 +234,7 @@ class _AiAnalysisPageState extends ConsumerState { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(6)), - child: Text(label, style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500, color: color)), + child: Text(label, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: color)), ); } } diff --git a/health_app/lib/pages/report/report_pages.dart b/health_app/lib/pages/report/report_pages.dart index 5956dac..3185cd2 100644 --- a/health_app/lib/pages/report/report_pages.dart +++ b/health_app/lib/pages/report/report_pages.dart @@ -5,6 +5,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import '../../core/app_theme.dart'; import '../../core/navigation_provider.dart'; import '../../providers/auth_provider.dart'; @@ -222,7 +224,7 @@ class ReportListPage extends ConsumerWidget { appBar: AppBar(title: const Text('看报告')), body: const Center( child: Column(mainAxisSize: MainAxisSize.min, children: [ - CircularProgressIndicator(color: Color(0xFF8B9CF7)), + CircularProgressIndicator(color: AppTheme.primaryLight), SizedBox(height: 16), Text('AI 正在分析报告...'), ]), @@ -252,7 +254,7 @@ class ReportListPage extends ConsumerWidget { Widget _buildUploadButton(BuildContext context, WidgetRef ref) { return FloatingActionButton( onPressed: () => _showUploadOptions(context, ref), - backgroundColor: const Color(0xFF8B9CF7), + backgroundColor: AppTheme.primary, child: const Icon(Icons.add), ); } @@ -312,12 +314,12 @@ class ReportListPage extends ConsumerWidget { color: const Color(0xFFF0F2FF), borderRadius: BorderRadius.circular(60), ), - child: const Icon(Icons.file_open, size: 48, color: Color(0xFF8B9CF7)), + child: const Icon(Icons.file_open, size: 48, color: AppTheme.primaryLight), ), const SizedBox(height: 20), - const Text('暂无检查报告', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500)), + const Text('暂无检查报告', style: TextStyle(fontSize: 21, fontWeight: FontWeight.w500)), const SizedBox(height: 8), - const Text('点击下方按钮上传报告', style: TextStyle(fontSize: 14, color: Color(0xFF999999))), + const Text('点击下方按钮上传报告', style: TextStyle(fontSize: 17, color: Color(0xFF999999))), ]), ); } @@ -328,7 +330,7 @@ class ReportListPage extends ConsumerWidget { decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), - boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(10), blurRadius: 4, offset: const Offset(0, 2))], + boxShadow: [BoxShadow(color: AppTheme.primary.withAlpha(10), blurRadius: 4, offset: const Offset(0, 2))], ), child: ListTile( leading: Container( @@ -340,10 +342,10 @@ class ReportListPage extends ConsumerWidget { ), child: _getReportIcon(report.type), ), - title: Text(report.title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), + title: Text(report.title, style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600)), subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(report.type, style: TextStyle(fontSize: 14, color: Colors.grey[500])), - Text(_formatDate(report.uploadedAt), style: TextStyle(fontSize: 12, color: Colors.grey[400])), + Text(report.type, style: TextStyle(fontSize: 17, color: Colors.grey[500])), + Text(_formatDate(report.uploadedAt), style: TextStyle(fontSize: 15, color: Colors.grey[400])), ]), trailing: Row( mainAxisSize: MainAxisSize.min, @@ -355,7 +357,7 @@ class ReportListPage extends ConsumerWidget { color: const Color(0xFFDCFCE7), borderRadius: BorderRadius.circular(4), ), - child: const Text('已审核', style: TextStyle(fontSize: 10, color: Color(0xFF43A047))), + child: const Text('已审核', style: TextStyle(fontSize: 13, color: Color(0xFF43A047))), ) else if (!report.hasAnalysis) Container( @@ -364,7 +366,7 @@ class ReportListPage extends ConsumerWidget { color: const Color(0xFFE3F2FD), borderRadius: BorderRadius.circular(4), ), - child: const Text('分析中', style: TextStyle(fontSize: 10, color: Color(0xFF2196F3))), + child: const Text('分析中', style: TextStyle(fontSize: 13, color: Color(0xFF2196F3))), ) else if (report.status == 'PendingDoctor') Container( @@ -373,12 +375,12 @@ class ReportListPage extends ConsumerWidget { color: const Color(0xFFFFF3E0), borderRadius: BorderRadius.circular(4), ), - child: const Text('待审核', style: TextStyle(fontSize: 10, color: Color(0xFFF59E0B))), + child: const Text('待审核', style: TextStyle(fontSize: 13, color: Color(0xFFF59E0B))), ), const SizedBox(width: 8), report.hasAnalysis - ? const Icon(Icons.check_circle, size: 20, color: Color(0xFF43A047)) - : const Icon(Icons.arrow_forward_ios, size: 18, color: Color(0xFFCCCCCC)), + ? const Icon(Icons.check_circle, size: 23, color: Color(0xFF43A047)) + : const Icon(Icons.arrow_forward_ios, size: 21, color: Color(0xFFCCCCCC)), ], ), onTap: () { @@ -390,13 +392,13 @@ class ReportListPage extends ConsumerWidget { Widget _getReportIcon(String type) { final icons = { - '血液检查': const Icon(Icons.bloodtype, size: 24, color: Color(0xFF8B9CF7)), - '心电图': const Icon(Icons.monitor_heart, size: 24, color: Color(0xFF8B9CF7)), - '超声检查': const Icon(Icons.image, size: 24, color: Color(0xFF8B9CF7)), - '影像报告': const Icon(Icons.image, size: 24, color: Color(0xFF8B9CF7)), - 'PDF文档': const Icon(Icons.picture_as_pdf, size: 24, color: Color(0xFF8B9CF7)), + '血液检查': const Icon(Icons.bloodtype, size: 28, color: AppTheme.primaryLight), + '心电图': const Icon(Icons.monitor_heart, size: 28, color: AppTheme.primaryLight), + '超声检查': const Icon(Icons.image, size: 28, color: AppTheme.primaryLight), + '影像报告': const Icon(Icons.image, size: 28, color: AppTheme.primaryLight), + 'PDF文档': const Icon(Icons.picture_as_pdf, size: 28, color: AppTheme.primaryLight), }; - return icons[type] ?? const Icon(Icons.description, size: 24, color: Color(0xFF8B9CF7)); + return icons[type] ?? const Icon(Icons.description, size: 28, color: AppTheme.primaryLight); } String _formatDate(DateTime date) { @@ -438,7 +440,7 @@ class _ReportDetailPageState extends ConsumerState { if (analysis == null) { return Scaffold( appBar: AppBar(title: const Text('报告详情')), - body: const Center(child: CircularProgressIndicator(color: Color(0xFF8B9CF7))), + body: const Center(child: CircularProgressIndicator(color: AppTheme.primaryLight)), ); } @@ -470,7 +472,7 @@ class _ReportDetailPageState extends ConsumerState { width: double.infinity, height: 48, child: ElevatedButton( onPressed: () => pushRoute(ref, 'aiAnalysis', params: {'id': widget.id}), - style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFF8B9CF7), foregroundColor: Colors.white, + style: ElevatedButton.styleFrom(backgroundColor: AppTheme.primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24))), child: const Text('查看 AI 智能解读'), ), @@ -491,17 +493,17 @@ class _ReportDetailPageState extends ConsumerState { ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ - const Text('✅', style: TextStyle(fontSize: 20)), + const Text('✅', style: TextStyle(fontSize: 23)), const SizedBox(width: 8), - const Text('医生审核意见', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF2E7D32))), + const Text('医生审核意见', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: Color(0xFF2E7D32))), ]), if (report.doctorName != null) ...[ const SizedBox(height: 4), - Text('审核医生:${report.doctorName}', style: const TextStyle(fontSize: 13, color: Color(0xFF4CAF50))), + Text('审核医生:${report.doctorName}', style: const TextStyle(fontSize: 16, color: Color(0xFF4CAF50))), ], if (report.reviewedAt != null) ...[ const SizedBox(height: 2), - Text('审核时间:${report.reviewedAt!.toLocal().toString().substring(0, 19)}', style: const TextStyle(fontSize: 12, color: Color(0xFF81C784))), + Text('审核时间:${report.reviewedAt!.toLocal().toString().substring(0, 19)}', style: const TextStyle(fontSize: 15, color: Color(0xFF81C784))), ], if (report.severity != null) ...[ const SizedBox(height: 6), @@ -516,7 +518,7 @@ class _ReportDetailPageState extends ConsumerState { color: Colors.white, borderRadius: BorderRadius.circular(12), ), - child: Text('💬 ${report.doctorComment!}', style: const TextStyle(fontSize: 15, color: Color(0xFF1A1A1A), height: 1.5)), + child: Text('💬 ${report.doctorComment!}', style: const TextStyle(fontSize: 18, color: Color(0xFF1A1A1A), height: 1.5)), ), ], if (report.doctorRecommendation != null && report.doctorRecommendation!.isNotEmpty) ...[ @@ -528,7 +530,7 @@ class _ReportDetailPageState extends ConsumerState { color: Colors.white, borderRadius: BorderRadius.circular(12), ), - child: Text('📝 ${report.doctorRecommendation!}', style: const TextStyle(fontSize: 14, color: Color(0xFF333333), height: 1.5)), + child: Text('📝 ${report.doctorRecommendation!}', style: const TextStyle(fontSize: 17, color: Color(0xFF333333), height: 1.5)), ), ], ]), @@ -546,7 +548,7 @@ class _ReportDetailPageState extends ConsumerState { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(6)), - child: Text(label, style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500, color: color)), + child: Text(label, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: color)), ); } @@ -559,12 +561,12 @@ class _ReportDetailPageState extends ConsumerState { ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ - const Text('📋', style: TextStyle(fontSize: 24)), + const Text('📋', style: TextStyle(fontSize: 28)), const SizedBox(width: 12), Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(analysis.reportType, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), + Text(analysis.reportType, style: const TextStyle(fontSize: 21, fontWeight: FontWeight.w600)), const SizedBox(height: 4), - const Text('AI 预解读结果', style: TextStyle(fontSize: 14, color: Color(0xFF666666))), + const Text('AI 预解读结果', style: TextStyle(fontSize: 17, color: Color(0xFF666666))), ]), ]), ]), @@ -582,9 +584,9 @@ class _ReportDetailPageState extends ConsumerState { Padding( padding: const EdgeInsets.all(16), child: Row(children: [ - const Text('🧪', style: TextStyle(fontSize: 20)), + const Text('🧪', style: TextStyle(fontSize: 23)), const SizedBox(width: 8), - const Text('指标分析', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), + const Text('指标分析', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w600)), ]), ), Container( @@ -598,11 +600,11 @@ class _ReportDetailPageState extends ConsumerState { child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.info_outline, size: 16, color: Color(0xFFE65100)), + const Icon(Icons.info_outline, size: 19, color: Color(0xFFE65100)), const SizedBox(width: 6), const Text( 'AI 预解读 · 待医生确认', - style: TextStyle(fontSize: 13, color: Color(0xFFE65100), fontWeight: FontWeight.w500), + style: TextStyle(fontSize: 16, color: Color(0xFFE65100), fontWeight: FontWeight.w500), ), ], ), @@ -637,15 +639,15 @@ class _ReportDetailPageState extends ConsumerState { child: Row(children: [ Expanded( child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(ind.name, style: const TextStyle(fontSize: 14)), + Text(ind.name, style: const TextStyle(fontSize: 17)), if (ind.referenceRange != null) - Text('参考值: ${ind.referenceRange}', style: TextStyle(fontSize: 12, color: Colors.grey[400])), + Text('参考值: ${ind.referenceRange}', style: TextStyle(fontSize: 15, color: Colors.grey[400])), ]), ), const SizedBox(width: 16), Column(children: [ - Text('${ind.value} ${ind.unit}', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: statusColor)), - Icon(statusIcon, size: 16, color: statusColor), + Text('${ind.value} ${ind.unit}', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: statusColor)), + Icon(statusIcon, size: 19, color: statusColor), ]), ]), ); @@ -660,12 +662,12 @@ class _ReportDetailPageState extends ConsumerState { ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ - const Text('💡', style: TextStyle(fontSize: 20)), + const Text('💡', style: TextStyle(fontSize: 23)), const SizedBox(width: 8), - const Text('综合解读', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFFD97706))), + const Text('综合解读', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: Color(0xFFD97706))), ]), const SizedBox(height: 12), - Text(analysis.summary, style: const TextStyle(fontSize: 14, color: Color(0xFF92400E), height: 1.6)), + Text(analysis.summary, style: const TextStyle(fontSize: 17, color: Color(0xFF92400E), height: 1.6)), const SizedBox(height: 12), Container( padding: const EdgeInsets.all(12), @@ -673,7 +675,7 @@ class _ReportDetailPageState extends ConsumerState { color: Colors.white, borderRadius: BorderRadius.circular(12), ), - child: const Text('⚠️ AI 解读仅供参考,请以医生诊断为准', style: TextStyle(fontSize: 13, color: Color(0xFFD97706))), + child: const Text('⚠️ AI 解读仅供参考,请以医生诊断为准', style: TextStyle(fontSize: 16, color: Color(0xFFD97706))), ), ]), ); diff --git a/health_app/lib/pages/settings/notification_prefs_page.dart b/health_app/lib/pages/settings/notification_prefs_page.dart index b5c049a..489fdb4 100644 --- a/health_app/lib/pages/settings/notification_prefs_page.dart +++ b/health_app/lib/pages/settings/notification_prefs_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../../core/app_theme.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/navigation_provider.dart'; @@ -49,7 +50,7 @@ class NotificationPrefsPage extends ConsumerWidget { final dndOn = prefs['dndEnabled'] ?? false; return Scaffold( - backgroundColor: const Color(0xFFF8F9FC), + backgroundColor: AppTheme.bg, appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, @@ -57,7 +58,7 @@ class NotificationPrefsPage extends ConsumerWidget { icon: const Icon(Icons.arrow_back_ios_new, color: Color(0xFF1A1A1A)), onPressed: () => popRoute(ref), ), - title: const Text('消息通知', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + title: const Text('消息通知', style: TextStyle(fontSize: 21, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), centerTitle: true, ), body: SingleChildScrollView( @@ -89,7 +90,7 @@ class NotificationPrefsPage extends ConsumerWidget { _SwitchTile( icon: Icons.warning_amber_rounded, iconBg: const Color(0xFFFFEBEE), - iconColor: const Color(0xFFE53935), + iconColor: AppTheme.error, title: '健康异常提醒', subtitle: '检测到数据异常时及时通知', value: prefs['healthAlert'] ?? true, @@ -107,7 +108,7 @@ class NotificationPrefsPage extends ConsumerWidget { _SwitchTile( icon: Icons.smart_toy_outlined, iconBg: const Color(0xFFF3E5F5), - iconColor: const Color(0xFF8B9CF7), + iconColor: AppTheme.primary, title: 'AI 回复通知', subtitle: 'AI 助手回复时发送通知', value: prefs['aiReply'] ?? false, @@ -133,7 +134,7 @@ class NotificationPrefsPage extends ConsumerWidget { final picked = await showTimePicker(context: context, initialTime: const TimeOfDay(hour: 22, minute: 0)); if (picked != null && context.mounted) ref.read(notificationPrefsProvider.notifier).setDndStart(picked); })), - Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Text('~', style: TextStyle(fontSize: 16, color: Colors.grey[400]))), + Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Text('~', style: TextStyle(fontSize: 19, color: Colors.grey[400]))), Expanded(child: _TimeButton(label: '结束', time: '08:00', onTap: () async { final picked = await showTimePicker(context: context, initialTime: const TimeOfDay(hour: 8, minute: 0)); if (picked != null && context.mounted) ref.read(notificationPrefsProvider.notifier).setDndEnd(picked); @@ -157,7 +158,7 @@ class _SectionTitle extends StatelessWidget { const _SectionTitle({required this.title}); @override Widget build(BuildContext context) { - return Padding(padding: const EdgeInsets.only(left: 4, bottom: 10), child: Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF999999)))); + return Padding(padding: const EdgeInsets.only(left: 4, bottom: 10), child: Text(title, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: Color(0xFF999999)))); } } @@ -186,14 +187,14 @@ class _SwitchTile extends StatelessWidget { decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12)), child: Row(children: [ if (icon != null) ...[ - Container(width: 38, height: 38, decoration: BoxDecoration(color: iconBg, borderRadius: BorderRadius.circular(10)), child: Icon(icon, size: 20, color: iconColor)), + Container(width: 38, height: 38, decoration: BoxDecoration(color: iconBg, borderRadius: BorderRadius.circular(10)), child: Icon(icon, size: 23, color: iconColor)), const SizedBox(width: 12), ], Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(title, style: const TextStyle(fontSize: 15, color: Color(0xFF1A1A1A), fontWeight: FontWeight.w500)), - if (subtitle != null && subtitle!.isNotEmpty) Text(subtitle!, style: TextStyle(fontSize: 12, color: Colors.grey[500])), + Text(title, style: const TextStyle(fontSize: 18, color: Color(0xFF1A1A1A), fontWeight: FontWeight.w500)), + if (subtitle != null && subtitle!.isNotEmpty) Text(subtitle!, style: TextStyle(fontSize: 15, color: Colors.grey[500])), ])), - Switch(value: value, onChanged: onChanged, activeThumbColor: const Color(0xFF8B9CF7), activeTrackColor: const Color(0xFFC5BFFF)), + Switch(value: value, onChanged: onChanged, activeThumbColor: AppTheme.primary, activeTrackColor: const Color(0xFFC5BFFF)), ]), ); } @@ -210,7 +211,7 @@ class _TimeButton extends StatelessWidget { return GestureDetector(onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration(border: Border.all(color: const Color(0xFFE0E0E0)), borderRadius: BorderRadius.circular(10)), - child: Column(children: [Text(label, style: TextStyle(fontSize: 11, color: Colors.grey[500])), Text(time, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF8B9CF7)))]), + child: Column(children: [Text(label, style: TextStyle(fontSize: 14, color: Colors.grey[500])), Text(time, style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: AppTheme.primary))]), )); } } diff --git a/health_app/lib/pages/settings/settings_pages.dart b/health_app/lib/pages/settings/settings_pages.dart index c56a280..a06030a 100644 --- a/health_app/lib/pages/settings/settings_pages.dart +++ b/health_app/lib/pages/settings/settings_pages.dart @@ -1,85 +1,70 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import '../../core/app_theme.dart'; import '../../core/navigation_provider.dart'; import '../../providers/auth_provider.dart'; +import '../../widgets/app_menu_item.dart'; class SettingsPage extends ConsumerWidget { const SettingsPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { + final theme = ShadTheme.of(context); + return Scaffold( - backgroundColor: const Color(0xFFF8F9FC), + backgroundColor: theme.colorScheme.background, appBar: AppBar( - backgroundColor: Colors.white, + backgroundColor: theme.colorScheme.card, elevation: 0, - leading: IconButton(icon: const Icon(Icons.chevron_left), onPressed: () => popRoute(ref)), - title: const Text('设置', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), + leading: IconButton(icon: Icon(LucideIcons.chevronLeft, color: theme.colorScheme.foreground), onPressed: () => popRoute(ref)), + title: Text('设置', style: TextStyle(fontSize: 21, fontWeight: FontWeight.w600, color: theme.colorScheme.foreground)), centerTitle: true, ), - body: SafeArea(child: SingleChildScrollView(padding: const EdgeInsets.only(bottom: 30), child: Column(children: [ - const SizedBox(height: 12), - _SetItem(icon: Icons.notifications_outlined, title: '消息通知', onTap: () => pushRoute(ref, 'notificationPrefs')), - _SetItem(icon: Icons.medication_outlined, title: '用药提醒', subtitle: 'mmHg / mmol/L', onTap: () => pushRoute(ref, 'medications')), - _SetItem(icon: Icons.data_usage_outlined, title: '数据导出', onTap: () {}), - _SetItem(icon: Icons.text_fields_outlined, title: '字体大小', trailingText: 'v1.0.0', onTap: () {}), - _SetItem(icon: Icons.cleaning_services_outlined, title: '清除缓存', subtitle: '73.2 MB', onTap: () {}), - _SetItem(icon: Icons.info_outline, title: '关于健康管家', onTap: () => pushRoute(ref, 'staticText', params: {'type': 'about'})), - _SetItem(icon: Icons.shield_outlined, title: '隐私协议', onTap: () => pushRoute(ref, 'staticText', params: {'type': 'privacy'})), - const SizedBox(height: 30), - GestureDetector( - onTap: () async { - final ok = await showDialog( - context: context, - builder: (ctx) => AlertDialog( - title: const Text('退出登录'), - content: const Text('确定退出?'), - actions: [ - TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('取消')), - TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('确定')), - ], + body: SafeArea(child: SingleChildScrollView( + padding: const EdgeInsets.only(bottom: 30), + child: Column(children: [ + const SizedBox(height: AppTheme.sMd), + AppMenuItem(icon: LucideIcons.bell, title: '消息通知', onTap: () => pushRoute(ref, 'notificationPrefs')), + AppMenuItem(icon: LucideIcons.pill, title: '用药提醒', subtitle: 'mmHg / mmol/L', onTap: () => pushRoute(ref, 'medications')), + AppMenuItem(icon: LucideIcons.database, title: '数据导出', onTap: () {}), + AppMenuItem(icon: LucideIcons.type, title: '字体大小', trailing: 'v1.0.0', onTap: () {}), + AppMenuItem(icon: LucideIcons.sprayCan, title: '清除缓存', subtitle: '73.2 MB', onTap: () {}), + AppMenuItem(icon: LucideIcons.info, title: '关于健康管家', onTap: () => pushRoute(ref, 'staticText', params: {'type': 'about'})), + AppMenuItem(icon: LucideIcons.shield, title: '隐私协议', onTap: () => pushRoute(ref, 'staticText', params: {'type': 'privacy'})), + const SizedBox(height: 30), + GestureDetector( + onTap: () => _logout(context, ref), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: AppTheme.rXl), + height: 50, + alignment: Alignment.center, + decoration: BoxDecoration( + border: Border.all(color: theme.colorScheme.destructive.withAlpha(80)), + borderRadius: BorderRadius.circular(AppTheme.rPill), ), - ); - if (ok == true) { await ref.read(authProvider.notifier).logout(); goRoute(ref, 'login'); } - }, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 24), - height: 50, - alignment: Alignment.center, - decoration: BoxDecoration(border: Border.all(color: const Color(0xFFE53935)), borderRadius: BorderRadius.circular(25)), - child: const Text('退出登录', style: TextStyle(fontSize: 16, color: Color(0xFFE53935), fontWeight: FontWeight.w500)), + child: Text('退出登录', style: TextStyle(fontSize: 19, color: theme.colorScheme.destructive, fontWeight: FontWeight.w500)), + ), ), - ), - ]))), + ]), + )), ); } -} -class _SetItem extends StatelessWidget { - final IconData icon; - final String title; - final String? subtitle; - final String? trailingText; - final VoidCallback? onTap; - - const _SetItem({required this.icon, required this.title, this.subtitle, this.trailingText, this.onTap}); - - @override Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - behavior: HitTestBehavior.opaque, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 2), - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 14), - decoration: BoxDecoration(color: Colors.white), - child: Row(children: [ - Container(width: 36, height: 36, decoration: BoxDecoration(color: const Color(0xFFF0F2FF), borderRadius: BorderRadius.circular(10)), child: Icon(icon, size: 18, color: const Color(0xFF8B9CF7))), - const SizedBox(width: 12), - Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text(title, style: const TextStyle(fontSize: 16, color: Color(0xFF1A1A1A))), if (subtitle != null && subtitle!.isNotEmpty) Text(subtitle!, style: TextStyle(fontSize: 13, color: Colors.grey[500]))])), - if (trailingText != null && trailingText!.isNotEmpty) Text(trailingText!, style: TextStyle(fontSize: 14, color: Colors.grey[400])), - if (trailingText == null || trailingText!.isEmpty) const SizedBox(), - Icon(Icons.chevron_right, size: 20, color: Colors.grey[300]), - ]), + Future _logout(BuildContext context, WidgetRef ref) async { + final theme = ShadTheme.of(context); + final ok = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppTheme.rXl)), + title: Text('退出登录', style: TextStyle(color: theme.colorScheme.foreground)), + content: Text('确定退出?', style: TextStyle(color: theme.colorScheme.mutedForeground)), + actions: [ + TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text('取消', style: TextStyle(color: theme.colorScheme.mutedForeground))), + TextButton(onPressed: () => Navigator.pop(ctx, true), child: Text('确定', style: TextStyle(color: theme.colorScheme.destructive))), + ], ), ); + if (ok == true) { await ref.read(authProvider.notifier).logout(); goRoute(ref, 'login'); } } } diff --git a/health_app/lib/widgets/app_card.dart b/health_app/lib/widgets/app_card.dart new file mode 100644 index 0000000..4b11087 --- /dev/null +++ b/health_app/lib/widgets/app_card.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import '../core/app_theme.dart'; + +/// 统一样式的清新卡片 +class AppCard extends StatelessWidget { + final Widget child; + final EdgeInsetsGeometry? padding; + final EdgeInsetsGeometry? margin; + final VoidCallback? onTap; + final Color? backgroundColor; + final BorderRadius? borderRadius; + + const AppCard({ + super.key, + required this.child, + this.padding = const EdgeInsets.all(AppTheme.sLg), + this.margin, + this.onTap, + this.backgroundColor, + this.borderRadius, + }); + + @override + Widget build(BuildContext context) { + final theme = ShadTheme.of(context); + final card = Container( + margin: margin ?? const EdgeInsets.symmetric(horizontal: AppTheme.sLg, vertical: AppTheme.sSm), + padding: padding, + decoration: BoxDecoration( + color: backgroundColor ?? theme.colorScheme.card, + borderRadius: borderRadius ?? BorderRadius.circular(AppTheme.rMd), + boxShadow: [AppTheme.shadowLight], + ), + child: child, + ); + + if (onTap != null) { + return GestureDetector( + onTap: onTap, + child: card, + ); + } + return card; + } +} diff --git a/health_app/lib/widgets/app_empty_state.dart b/health_app/lib/widgets/app_empty_state.dart new file mode 100644 index 0000000..6213abe --- /dev/null +++ b/health_app/lib/widgets/app_empty_state.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; + +/// 统一空状态占位组件 +class AppEmptyState extends StatelessWidget { + final IconData icon; + final String title; + final String? subtitle; + final Widget? action; + + const AppEmptyState({ + super.key, + required this.icon, + required this.title, + this.subtitle, + this.action, + }); + + @override + Widget build(BuildContext context) { + final theme = ShadTheme.of(context); + return Center( + child: Padding( + padding: const EdgeInsets.all(48), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + color: theme.colorScheme.muted, + borderRadius: BorderRadius.circular(40), + ), + child: Icon(icon, size: 36, color: theme.colorScheme.mutedForeground), + ), + const SizedBox(height: 20), + Text(title, style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: theme.colorScheme.foreground)), + if (subtitle != null) ...[ + const SizedBox(height: 6), + Text(subtitle!, style: TextStyle(fontSize: 17, color: theme.colorScheme.mutedForeground), textAlign: TextAlign.center), + ], + if (action != null) ...[ + const SizedBox(height: 20), + action!, + ], + ], + ), + ), + ); + } +} diff --git a/health_app/lib/widgets/app_menu_item.dart b/health_app/lib/widgets/app_menu_item.dart new file mode 100644 index 0000000..63fbe72 --- /dev/null +++ b/health_app/lib/widgets/app_menu_item.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import '../core/app_theme.dart'; + +/// 统一菜单项,profile_page 和 settings_pages 都共用这个 +class AppMenuItem extends StatelessWidget { + final IconData icon; + final String title; + final String? subtitle; + final String? trailing; + final VoidCallback? onTap; + + const AppMenuItem({ + super.key, + required this.icon, + required this.title, + this.subtitle, + this.trailing, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + final theme = ShadTheme.of(context); + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 2), + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 14), + decoration: BoxDecoration( + color: theme.colorScheme.card, + borderRadius: BorderRadius.circular(AppTheme.rSm), + ), + child: Row(children: [ + Container( + width: 38, + height: 38, + decoration: BoxDecoration( + color: AppTheme.primaryLight, + borderRadius: BorderRadius.circular(AppTheme.rSm), + ), + child: Icon(icon, size: 22, color: AppTheme.primary), + ), + const SizedBox(width: AppTheme.sLg), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: TextStyle(fontSize: 19, fontWeight: FontWeight.w500, color: theme.colorScheme.foreground)), + if (subtitle != null && subtitle!.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 2), + child: Text(subtitle!, style: TextStyle(fontSize: 16, color: theme.colorScheme.mutedForeground)), + ), + ], + ), + ), + if (trailing != null && trailing!.isNotEmpty) + Text(trailing!, style: TextStyle(fontSize: 17, color: theme.colorScheme.mutedForeground)), + const SizedBox(width: 4), + Icon(LucideIcons.chevronRight, size: 21, color: theme.colorScheme.mutedForeground), + ]), + ), + ); + } +} diff --git a/health_app/lib/widgets/app_tab_chip.dart b/health_app/lib/widgets/app_tab_chip.dart new file mode 100644 index 0000000..ce0b863 --- /dev/null +++ b/health_app/lib/widgets/app_tab_chip.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import '../core/app_theme.dart'; + +/// 统一样式的标签/筛选 Chip +class AppTabChip extends StatelessWidget { + final String label; + final bool selected; + final VoidCallback onTap; + + const AppTabChip({ + super.key, + required this.label, + required this.selected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + margin: const EdgeInsets.only(right: AppTheme.sSm), + padding: const EdgeInsets.symmetric(horizontal: AppTheme.sLg, vertical: AppTheme.sSm), + decoration: BoxDecoration( + color: selected ? AppTheme.primary : AppTheme.primaryLight, + borderRadius: BorderRadius.circular(AppTheme.rPill), + ), + child: Text( + label, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: selected ? Colors.white : AppTheme.primary, + ), + ), + ), + ); + } +} diff --git a/health_app/lib/widgets/health_drawer.dart b/health_app/lib/widgets/health_drawer.dart index 1a1fb33..9e5919a 100644 --- a/health_app/lib/widgets/health_drawer.dart +++ b/health_app/lib/widgets/health_drawer.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../core/app_theme.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../core/navigation_provider.dart'; import '../providers/auth_provider.dart'; @@ -46,12 +47,12 @@ class HealthDrawer extends ConsumerWidget { Expanded(child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(user?.name ?? '未设置昵称', style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w700, color: Colors.white)), + Text(user?.name ?? '未设置昵称', style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w700, color: Colors.white)), const SizedBox(height: 2), - Text(user?.phone ?? '未登录', style: TextStyle(fontSize: 12, color: Colors.white70)), + Text(user?.phone ?? '未登录', style: TextStyle(fontSize: 15, color: Colors.white70)), ], )), - Icon(Icons.chevron_right, size: 18, color: Colors.white54), + Icon(Icons.chevron_right, size: 21, color: Colors.white54), ]), ), ), @@ -72,19 +73,19 @@ class HealthDrawer extends ConsumerWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( - color: const Color(0xFF6C5CE7).withAlpha(15), + color: AppTheme.primary.withAlpha(15), borderRadius: BorderRadius.circular(6), ), child: const Row(mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.monitor_heart_rounded, size: 13, color: Color(0xFF6C5CE7)), + Icon(Icons.monitor_heart_rounded, size: 16, color: AppTheme.primary), SizedBox(width: 4), - Text('健康概览', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF6C5CE7))), + Text('健康概览', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: AppTheme.primary)), ]), ), const Spacer(), GestureDetector( onTap: () => pushRoute(ref, 'trend'), - child: const Padding(padding: EdgeInsets.all(4), child: Text('详情', style: TextStyle(fontSize: 11, color: Color(0xFF888888)))), + child: const Padding(padding: EdgeInsets.all(4), child: Text('详情', style: TextStyle(fontSize: 14, color: Color(0xFF888888)))), ), ]), ), @@ -99,7 +100,7 @@ class HealthDrawer extends ConsumerWidget { _MiniMetric(icon: Icons.monitor_weight_outlined, label: '体重', value: _metricVal(data['Weight']), onTap: () => pushRoute(ref, 'trend', params: {'type': 'weight'})), ]), ), - loading: () => const Padding(padding: EdgeInsets.symmetric(vertical: 20), child: Center(child: SizedBox(width: 22, height: 22, child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF6C5CE7))))), + loading: () => const Padding(padding: EdgeInsets.symmetric(vertical: 20), child: Center(child: SizedBox(width: 22, height: 22, child: CircularProgressIndicator(strokeWidth: 2, color: AppTheme.primary)))), error: (_, __) => Padding( padding: const EdgeInsets.fromLTRB(12, 4, 12, 14), child: Row(children: [ @@ -134,9 +135,9 @@ class HealthDrawer extends ConsumerWidget { borderRadius: BorderRadius.circular(6), ), child: const Row(mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.apps_rounded, size: 13, color: Color(0xFFF0A060)), + Icon(Icons.apps_rounded, size: 16, color: Color(0xFFF0A060)), SizedBox(width: 4), - Text('功能', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFFF0A060))), + Text('功能', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFFF0A060))), ]), ), ]), @@ -179,11 +180,11 @@ class HealthDrawer extends ConsumerWidget { color: const Color(0xFFEEEEEE), borderRadius: BorderRadius.circular(10), ), - child: const Icon(Icons.settings_outlined, size: 18, color: Color(0xFF666666)), + child: const Icon(Icons.settings_outlined, size: 21, color: Color(0xFF666666)), ), const SizedBox(width: 12), - const Expanded(child: Text('设置', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: Color(0xFF333333)))), - const Icon(Icons.chevron_right, size: 16, color: Color(0xFFCCCCCC)), + const Expanded(child: Text('设置', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, color: Color(0xFF333333)))), + const Icon(Icons.chevron_right, size: 19, color: Color(0xFFCCCCCC)), ]), ), ), @@ -269,7 +270,7 @@ class _MiniMetric extends StatelessWidget { final VoidCallback? onTap; const _MiniMetric({required this.icon, required this.label, required this.value, this.onTap}); - static const _c = Color(0xFF6C5CE7); + static const _c = AppTheme.primary; @override Widget build(BuildContext context) { @@ -284,11 +285,11 @@ class _MiniMetric extends StatelessWidget { borderRadius: BorderRadius.circular(12), ), child: Column(mainAxisSize: MainAxisSize.min, children: [ - Icon(icon, size: 18, color: _c), + Icon(icon, size: 21, color: _c), const SizedBox(height: 6), - Text(value, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w700, color: Color(0xFF1A1A1A)), maxLines: 1, overflow: TextOverflow.ellipsis), + Text(value, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: Color(0xFF1A1A1A)), maxLines: 1, overflow: TextOverflow.ellipsis), const SizedBox(height: 2), - Text(label, style: TextStyle(fontSize: 10, color: Colors.grey[500])), + Text(label, style: TextStyle(fontSize: 13, color: Colors.grey[500])), ]), ), ), @@ -311,7 +312,7 @@ class _FeatureChip extends StatelessWidget { required this.onTap, }); - static const _c = Color(0xFF6C5CE7); + static const _c = AppTheme.primary; @override Widget build(BuildContext context) { @@ -326,9 +327,9 @@ class _FeatureChip extends StatelessWidget { borderRadius: BorderRadius.circular(12), ), child: Column(mainAxisSize: MainAxisSize.min, children: [ - Icon(icon, size: 18, color: _c), + Icon(icon, size: 21, color: _c), const SizedBox(height: 6), - Text(label, style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w500, color: Color(0xFF666666))), + Text(label, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: Color(0xFF666666))), ]), ), ), diff --git a/health_app/lib/widgets/service_package_card.dart b/health_app/lib/widgets/service_package_card.dart index 5bb79ea..ed16f8a 100644 --- a/health_app/lib/widgets/service_package_card.dart +++ b/health_app/lib/widgets/service_package_card.dart @@ -185,11 +185,11 @@ class ServicePackageCard extends ConsumerWidget { color: Color(0xFFFFF8EE), shape: BoxShape.circle, ), - child: Icon(item.icon, size: 20, color: const Color(0xFFF5A623)), + child: Icon(item.icon, size: 23, color: const Color(0xFFF5A623)), ), const SizedBox(height: 4), Text(item.label, - style: const TextStyle(fontSize: 11, color: AppTheme.textSub), + style: const TextStyle(fontSize: 14, color: AppTheme.textSub), textAlign: TextAlign.center, ), ], @@ -212,14 +212,14 @@ class ServicePackageCard extends ConsumerWidget { children: [ // 标题行 + 详情入口 Row(children: [ - Text(package.title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: AppTheme.text)), + Text(package.title, style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: AppTheme.text)), const Spacer(), GestureDetector( onTap: () => pushRoute(ref, 'servicePackageDetail', params: {'id': package.id}), child: Row(mainAxisSize: MainAxisSize.min, children: [ - Text('详情', style: TextStyle(fontSize: 13, color: AppTheme.textSub)), + Text('详情', style: TextStyle(fontSize: 16, color: AppTheme.textSub)), const SizedBox(width: 2), - Icon(Icons.chevron_right, size: 18, color: AppTheme.textHint), + Icon(Icons.chevron_right, size: 21, color: AppTheme.textHint), ]), ), ]), @@ -232,9 +232,9 @@ class ServicePackageCard extends ConsumerWidget { borderRadius: BorderRadius.circular(6), ), child: const Row(mainAxisSize: MainAxisSize.min, children: [ - Text('VIP', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700, color: Colors.white)), + Text('VIP', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w700, color: Colors.white)), SizedBox(width: 4), - Text('VIP 产品权益', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w500, color: Colors.white)), + Text('VIP 产品权益', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)), ]), ), const SizedBox(height: 14), @@ -246,8 +246,8 @@ class ServicePackageCard extends ConsumerWidget { child: GestureDetector( onTap: () => pushRoute(ref, 'servicePackageDetail', params: {'id': package.id}), child: Row(mainAxisSize: MainAxisSize.min, children: [ - Text('查看更多服务包', style: TextStyle(fontSize: 13, color: AppTheme.primary, fontWeight: FontWeight.w500)), - Icon(Icons.chevron_right, size: 16, color: AppTheme.primary), + Text('查看更多服务包', style: TextStyle(fontSize: 16, color: AppTheme.primary, fontWeight: FontWeight.w500)), + Icon(Icons.chevron_right, size: 19, color: AppTheme.primary), ]), ), ), diff --git a/health_app/pubspec.lock b/health_app/pubspec.lock index d6f3ec1..ecd0e34 100644 --- a/health_app/pubspec.lock +++ b/health_app/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + boxy: + dependency: transitive + description: + name: boxy + sha256: "42ccafe13b2893878042acc5b7e2446025328e11a3197b0bb78db42ff76aa3f0" + url: "https://pub.dev" + source: hosted + version: "2.3.0" characters: dependency: transitive description: @@ -65,6 +73,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 + url: "https://pub.dev" + source: hosted + version: "1.2.1" collection: dependency: transitive description: @@ -145,6 +161,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.8" + extended_image: + dependency: transitive + description: + name: extended_image + sha256: f6cbb1d798f51262ed1a3d93b4f1f2aa0d76128df39af18ecb77fa740f88b2e0 + url: "https://pub.dev" + source: hosted + version: "10.0.1" + extended_image_library: + dependency: transitive + description: + name: extended_image_library + sha256: "1f9a24d3a00c2633891c6a7b5cab2807999eb2d5b597e5133b63f49d113811fe" + url: "https://pub.dev" + source: hosted + version: "5.0.1" fake_async: dependency: transitive description: @@ -230,6 +262,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_animate: + dependency: transitive + description: + name: flutter_animate + sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" + url: "https://pub.dev" + source: hosted + version: "4.5.2" flutter_lints: dependency: "direct dev" description: @@ -267,6 +307,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.1" + flutter_shaders: + dependency: transitive + description: + name: flutter_shaders + sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2" + url: "https://pub.dev" + source: hosted + version: "0.1.3" + flutter_svg: + dependency: transitive + description: + name: flutter_svg + sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f" + url: "https://pub.dev" + source: hosted + version: "2.3.0" flutter_test: dependency: "direct dev" description: flutter @@ -293,6 +349,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: "4e9391085e524954a51e3625b7c9c7e9851dc3f376603208bb45c24b9a66255d" + url: "https://pub.dev" + source: hosted + version: "8.1.0" + hooks: + dependency: transitive + description: + name: hooks + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + url: "https://pub.dev" + source: hosted + version: "2.0.2" http: dependency: transitive description: @@ -301,6 +373,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.6.0" + http_client_helper: + dependency: transitive + description: + name: http_client_helper + sha256: "8a9127650734da86b5c73760de2b404494c968a3fd55602045ffec789dac3cb1" + url: "https://pub.dev" + source: hosted + version: "3.0.0" http_multi_server: dependency: transitive description: @@ -397,6 +477,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" js: dependency: transitive description: @@ -445,6 +541,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + lucide_icons_flutter: + dependency: transitive + description: + name: lucide_icons_flutter + sha256: "7c5dc01a32a9905ae34e2d84224e92d6d0c42acf8926df9e01c35a1446bf1b69" + url: "https://pub.dev" + source: hosted + version: "3.1.14+2" markdown: dependency: transitive description: @@ -501,6 +605,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" + url: "https://pub.dev" + source: hosted + version: "9.4.1" package_config: dependency: transitive description: @@ -517,6 +629,62 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" petitparser: dependency: transitive description: @@ -557,6 +725,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" riverpod: dependency: transitive description: @@ -565,6 +741,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.1" + serial_csv: + dependency: transitive + description: + name: serial_csv + sha256: "2d62bb70cb3ce7251383fc86ea9aae1298ab1e57af6ef4e93b6a9751c5c268dd" + url: "https://pub.dev" + source: hosted + version: "0.5.2" + shadcn_ui: + dependency: "direct main" + description: + name: shadcn_ui + sha256: "6c06f2bcebd8734b9ed0bf3f63ef5c71981573d5664923589b0302f8280e7eaf" + url: "https://pub.dev" + source: hosted + version: "0.53.6" shelf: dependency: transitive description: @@ -610,6 +802,22 @@ packages: description: flutter source: sdk version: "0.0.0" + slang: + dependency: transitive + description: + name: slang + sha256: "46e929158c2f563994c4d1fce5819cfa13e18b164941473d2553bcddcf387c31" + url: "https://pub.dev" + source: hosted + version: "4.15.0" + slang_flutter: + dependency: transitive + description: + name: slang_flutter + sha256: "0eb6348416a296f1bd940fe02669bcd2df5c5cfdabf893b98e448df8b7ecf4ac" + url: "https://pub.dev" + source: hosted + version: "4.15.0" source_map_stack_trace: dependency: transitive description: @@ -762,6 +970,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.12" + theme_extensions_builder_annotation: + dependency: transitive + description: + name: theme_extensions_builder_annotation + sha256: "75f28ac85d396d143d111a47c1395b01f3be41b7135f37bd51512921944e4206" + url: "https://pub.dev" + source: hosted + version: "7.3.0" tuple: dependency: transitive description: @@ -770,6 +986,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + two_dimensional_scrollables: + dependency: transitive + description: + name: two_dimensional_scrollables + sha256: "4f25bd42783626c5f2810333418727455397195acb61e53710e638a6a98e0e5e" + url: "https://pub.dev" + source: hosted + version: "0.5.2" typed_data: dependency: transitive description: @@ -778,6 +1002,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + universal_image: + dependency: transitive + description: + name: universal_image + sha256: "2eae13df84a47960cc4148fec88f09031ea64cbf667b4131ff2c907dd7b7c6d1" + url: "https://pub.dev" + source: hosted + version: "1.0.12" uuid: dependency: transitive description: @@ -786,6 +1018,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.3" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "7ee12e6dffe0fc8e755179d6d91b3b34f5924223fc104d85572ef9180d73d172" + url: "https://pub.dev" + source: hosted + version: "1.2.5" vector_math: dependency: transitive description: @@ -850,6 +1106,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.15.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" xml: dependency: transitive description: @@ -868,4 +1132,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.10.7 <4.0.0" - flutter: ">=3.38.0" + flutter: ">=3.38.4" diff --git a/health_app/pubspec.yaml b/health_app/pubspec.yaml index 28fdf13..d735883 100644 --- a/health_app/pubspec.yaml +++ b/health_app/pubspec.yaml @@ -38,6 +38,8 @@ dependencies: # 基础图标 cupertino_icons: ^1.0.8 signalr_netcore: ^1.4.4 + shadcn_ui: ^0.53.6 + google_fonts: ^8.1.0 dev_dependencies: flutter_test: diff --git a/health_app/test/widget_test.dart b/health_app/test/widget_test.dart index 1701bd3..84bd999 100644 --- a/health_app/test/widget_test.dart +++ b/health_app/test/widget_test.dart @@ -6,8 +6,8 @@ import 'package:health_app/pages/auth/login_page.dart'; void main() { testWidgets('主题颜色正确', (tester) async { - expect(AppTheme.primary, const Color(0xFF8B9CF7)); - expect(AppTheme.bg, const Color(0xFFF8F9FC)); + expect(AppTheme.primary, AppTheme.primaryLight); + expect(AppTheme.bg, AppTheme.bg); expect(AppTheme.error, const Color(0xFFF56C6C)); expect(AppTheme.success, const Color(0xFF6ECF8A)); }); @@ -38,7 +38,7 @@ void main() { decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), - border: const Border(left: BorderSide(color: Color(0xFF8B9CF7), width: 3)), + border: const Border(left: BorderSide(color: AppTheme.primaryLight, width: 3)), ), child: const Text('收到!已记录', style: TextStyle(fontSize: 16)), ), @@ -57,7 +57,7 @@ void main() { constraints: const BoxConstraints(maxWidth: 300), padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: const Color(0xFF8B9CF7), + color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(16), ), child: const Text('血压 135/85', style: TextStyle(fontSize: 16, color: Colors.white)),