fix: 欢迎卡片UI优化 + 蓝牙录入按钮 + 多处修复

- 欢迎卡片:去掉白框圆角错位、图片满宽、按钮去箭头改圆角居中
- 渐变actionOutlineGradient改紫蓝双色循环
- 新增健康Agent"蓝牙录入"按钮→蓝牙设备页
- "查看健康情况"改名"健康概览"
- 饮食编辑框删除bug修复(控制器缓存)
- 启动脚本加adb reverse自动转发
- 登录闪屏修复
This commit is contained in:
MingNian
2026-06-17 10:58:59 +08:00
parent 9279b7e283
commit 4f1ad82345
28 changed files with 1641 additions and 1098 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -3,7 +3,7 @@ import 'package:dio/dio.dart';
import 'local_database.dart'; import 'local_database.dart';
/// API 基础地址 /// API 基础地址
const String baseUrl = 'http://localhost:5000'; const String baseUrl = 'http://localhost:5000'; // adb reverse 或同WiFi直连时改为PC的IP
/// Dio HTTP 客户端封装——带 token 注入、401 自动刷新 /// Dio HTTP 客户端封装——带 token 注入、401 自动刷新
class ApiClient { class ApiClient {

View File

@@ -10,14 +10,23 @@ class AppColors {
static const Color primaryDark = Color(0xFF5B3FE8); static const Color primaryDark = Color(0xFF5B3FE8);
static const Color blueMeasure = Color(0xFF4F7CFF); static const Color blueMeasure = Color(0xFF4F7CFF);
static const Color doctorBlue = Color(0xFF3B82F6); static const Color doctorBlue = Color(0xFF3B82F6);
static const Color auraLavender = Color(0xFFA78BFA);
static const Color auraIndigo = Color(0xFF818CF8);
static const Color auraOrchid = Color(0xFFC084FC);
static const Color auraDeepIndigo = Color(0xFF6366F1);
static const Color mintAccent = Color(0xFF2DD4BF);
static const Color peachAccent = Color(0xFFFFA6A6);
static const Color roseAccent = Color(0xFFF43F5E);
static const Color meadowAccent = Color(0xFF72F874);
static const Color sageAccent = Color(0xFF9CD9C9);
static const Color background = Color(0xFFF7F8FC); static const Color background = Color(0xFFFAF8FF);
static const Color backgroundSoft = Color(0xFFFBFCFF); static const Color backgroundSoft = Color(0xFFFFFCFF);
static const Color cardBackground = Color(0xFFFFFFFF); static const Color cardBackground = Color(0xFFFFFFFF);
static const Color cardInner = Color(0xFFF8FAFF); static const Color cardInner = Color(0xFFF7F5FF);
static const Color pageGrey = background; static const Color pageGrey = background;
static const Color iconBg = Color(0xFFF0F4FF); static const Color iconBg = Color(0xFFF2EEFF);
static const Color avatarBg = Color(0xFFF3F0FF); static const Color avatarBg = Color(0xFFF4EFFF);
static const Color textPrimary = Color(0xFF1F2937); static const Color textPrimary = Color(0xFF1F2937);
static const Color textSecondary = Color(0xFF667085); static const Color textSecondary = Color(0xFF667085);
@@ -39,20 +48,76 @@ class AppColors {
static const LinearGradient primaryGradient = LinearGradient( static const LinearGradient primaryGradient = LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [Color(0xFF7C5CFF), Color(0xFF4F7CFF)], colors: [Color(0xFF8B5CF6), Color(0xFF7C5CFF), Color(0xFF6366F1)],
stops: [0.0, 0.5, 1.0],
); );
static const LinearGradient bgGradient = LinearGradient( static const LinearGradient bgGradient = LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [Color(0xFFFFFFFF), Color(0xFFF7F8FC), Color(0xFFF3F0FF)], colors: [
stops: [0.0, 0.68, 1.0], Color(0xFFFFFCFF),
Color(0xFFF9F5FF),
Color(0xFFF5F1FF),
Color(0xFFF2F6FF),
],
stops: [0.0, 0.42, 0.74, 1.0],
); );
static const LinearGradient drawerGradient = LinearGradient( static const LinearGradient drawerGradient = LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [Color(0xFFFFFFFF), Color(0xFFF6F3FF)], colors: [Color(0xFFFFFFFF), Color(0xFFFBF7FF), Color(0xFFF0F4FF)],
stops: [0.0, 0.52, 1.0],
);
static const LinearGradient surfaceGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFFFFFFFF), Color(0xFFFCFAFF)],
);
static const LinearGradient softGlassGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xF7FFFFFF), Color(0xECFFFFFF)],
);
static const LinearGradient roseVioletGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFF8B5CF6), Color(0xFFF43F5E)],
);
static const LinearGradient sageVioletGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFF9CD9C9), Color(0xFFAE2EF9)],
);
static const LinearGradient meadowVioletGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFF72F874), Color(0xFF8C6BD9)],
);
static const LinearGradient calmHealthGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFF9CD9C9), Color(0xFF818CF8)],
);
static const LinearGradient warmCareGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFFFFA6A6), Color(0xFFC084FC)],
);
static const LinearGradient actionOutlineGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFFC084FC), Color(0xFF818CF8), Color(0xFFC084FC), Color(0xFF818CF8)],
stops: [0.0, 0.3, 0.6, 1.0],
); );
static const LinearGradient doctorGradient = LinearGradient( static const LinearGradient doctorGradient = LinearGradient(

View File

@@ -2,6 +2,23 @@ import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:shadcn_ui/shadcn_ui.dart';
import 'app_colors.dart'; import 'app_colors.dart';
class AppBackground extends StatelessWidget {
final Widget child;
final bool safeArea;
const AppBackground({super.key, required this.child, this.safeArea = false});
@override
Widget build(BuildContext context) {
final content = safeArea ? SafeArea(child: child) : child;
return DecoratedBox(
decoration: const BoxDecoration(gradient: AppColors.bgGradient),
child: content,
);
}
}
class GradientScaffold extends StatelessWidget { class GradientScaffold extends StatelessWidget {
final PreferredSizeWidget? appBar; final PreferredSizeWidget? appBar;
final Widget? body; final Widget? body;
@@ -21,8 +38,7 @@ class GradientScaffold extends StatelessWidget {
}); });
@override @override
Widget build(BuildContext context) => Container( Widget build(BuildContext context) => AppBackground(
decoration: const BoxDecoration(gradient: AppColors.bgGradient),
child: Scaffold( child: Scaffold(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
appBar: appBar, appBar: appBar,
@@ -123,34 +139,34 @@ class AppTheme {
surface: surface, surface: surface,
brightness: Brightness.light, brightness: Brightness.light,
), ),
scaffoldBackgroundColor: bg, scaffoldBackgroundColor: Colors.transparent,
splashColor: primary.withValues(alpha: 0.06), splashColor: primary.withValues(alpha: 0.06),
highlightColor: Colors.transparent, highlightColor: Colors.transparent,
hoverColor: primaryLight.withValues(alpha: 0.55), hoverColor: primaryLight.withValues(alpha: 0.55),
fontFamily: null, fontFamily: null,
appBarTheme: const AppBarTheme( appBarTheme: AppBarTheme(
backgroundColor: surface, backgroundColor: Colors.white.withValues(alpha: 0.9),
foregroundColor: text, foregroundColor: text,
elevation: 0, elevation: 0,
centerTitle: true, centerTitle: true,
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
surfaceTintColor: Colors.transparent, surfaceTintColor: Colors.transparent,
titleTextStyle: TextStyle( titleTextStyle: const TextStyle(
fontSize: 17, fontSize: 17,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: text, color: text,
), ),
toolbarHeight: 48, toolbarHeight: 52,
), ),
cardTheme: CardThemeData( cardTheme: CardThemeData(
color: surface, color: Colors.white,
elevation: 0, elevation: 0,
surfaceTintColor: Colors.transparent, surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(rLg), borderRadius: BorderRadius.circular(rLg),
side: const BorderSide(color: border), side: const BorderSide(color: AppColors.borderLight),
), ),
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
), ),
@@ -161,15 +177,15 @@ class AppTheme {
contentPadding: const EdgeInsets.symmetric(horizontal: sLg, vertical: 13), contentPadding: const EdgeInsets.symmetric(horizontal: sLg, vertical: 13),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(rMd), borderRadius: BorderRadius.circular(rMd),
borderSide: const BorderSide(color: border), borderSide: const BorderSide(color: AppColors.borderLight),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(rMd), borderRadius: BorderRadius.circular(rMd),
borderSide: const BorderSide(color: border), borderSide: const BorderSide(color: AppColors.borderLight),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(rMd), borderRadius: BorderRadius.circular(rMd),
borderSide: const BorderSide(color: primary, width: 1.2), borderSide: const BorderSide(color: AppColors.auraIndigo, width: 1.3),
), ),
errorBorder: OutlineInputBorder( errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(rMd), borderRadius: BorderRadius.circular(rMd),
@@ -183,7 +199,7 @@ class AppTheme {
backgroundColor: primary, backgroundColor: primary,
foregroundColor: Colors.white, foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 50), minimumSize: const Size(double.infinity, 50),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rMd)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(rLg)),
textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700), textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
elevation: 0, elevation: 0,
), ),

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/app_colors.dart'; import '../../core/app_colors.dart';
import '../../core/app_theme.dart';
import '../../core/navigation_provider.dart'; import '../../core/navigation_provider.dart';
import '../../providers/auth_provider.dart'; import '../../providers/auth_provider.dart';
import '../../providers/data_providers.dart'; import '../../providers/data_providers.dart';
@@ -243,18 +244,17 @@ class _LoginPageState extends ConsumerState<LoginPage> {
} }
return Scaffold( return Scaffold(
body: Container( body: AppBackground(
decoration: const BoxDecoration(gradient: AppColors.bgGradient), safeArea: true,
child: SafeArea(
child: Center( child: Center(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24),
child: Container( child: Container(
padding: const EdgeInsets.fromLTRB(22, 24, 22, 22), padding: const EdgeInsets.fromLTRB(22, 24, 22, 22),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, gradient: AppColors.softGlassGradient,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadow, boxShadow: AppColors.cardShadow,
), ),
child: Column( child: Column(
@@ -347,7 +347,6 @@ class _LoginPageState extends ConsumerState<LoginPage> {
), ),
), ),
), ),
),
); );
} }
} }

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/app_colors.dart'; import '../../core/app_colors.dart';
import '../../core/app_theme.dart';
import '../../core/navigation_provider.dart'; import '../../core/navigation_provider.dart';
import '../../providers/omron_device_provider.dart'; import '../../providers/omron_device_provider.dart';
@@ -88,8 +89,7 @@ class _DeviceManagementPageState extends ConsumerState<DeviceManagementPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final device = ref.watch(omronDeviceProvider); final device = ref.watch(omronDeviceProvider);
return Scaffold( return GradientScaffold(
backgroundColor: AppColors.background,
appBar: AppBar( appBar: AppBar(
title: const Text('蓝牙设备'), title: const Text('蓝牙设备'),
leading: IconButton( leading: IconButton(
@@ -118,14 +118,15 @@ class _DeviceManagementPageState extends ConsumerState<DeviceManagementPage> {
width: 82, width: 82,
height: 82, height: 82,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.iconBg, gradient: AppColors.calmHealthGradient,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight,
), ),
child: const Icon( child: const Icon(
Icons.bluetooth_disabled, Icons.bluetooth_disabled,
size: 40, size: 40,
color: AppColors.primary, color: Colors.white,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -156,10 +157,10 @@ class _DeviceManagementPageState extends ConsumerState<DeviceManagementPage> {
duration: const Duration(milliseconds: 250), duration: const Duration(milliseconds: 250),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, gradient: AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(18), borderRadius: BorderRadius.circular(18),
border: Border.all( border: Border.all(
color: d.isConnected ? AppColors.success : AppColors.border, color: d.isConnected ? AppColors.success : AppColors.borderLight,
), ),
boxShadow: AppColors.cardShadowLight, boxShadow: AppColors.cardShadowLight,
), ),
@@ -169,15 +170,16 @@ class _DeviceManagementPageState extends ConsumerState<DeviceManagementPage> {
width: 50, width: 50,
height: 50, height: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
color: d.isConnected gradient: d.isConnected
? AppColors.successLight ? AppColors.calmHealthGradient
: AppColors.cardInner, : AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
border: Border.all(color: AppColors.borderLight),
), ),
child: Icon( child: Icon(
Icons.bluetooth, Icons.bluetooth,
size: 24, size: 24,
color: d.isConnected ? AppColors.success : AppColors.textHint, color: d.isConnected ? Colors.white : AppColors.textHint,
), ),
), ),
const SizedBox(width: 14), const SizedBox(width: 14),
@@ -250,9 +252,9 @@ class _DeviceManagementPageState extends ConsumerState<DeviceManagementPage> {
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, gradient: AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(18), borderRadius: BorderRadius.circular(18),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight, boxShadow: AppColors.cardShadowLight,
), ),
child: Row( child: Row(
@@ -334,9 +336,9 @@ class _DeviceManagementPageState extends ConsumerState<DeviceManagementPage> {
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, gradient: AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(18), borderRadius: BorderRadius.circular(18),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight, boxShadow: AppColors.cardShadowLight,
), ),
child: Column( child: Column(

View File

@@ -5,6 +5,7 @@ import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import '../../core/app_colors.dart'; import '../../core/app_colors.dart';
import '../../core/app_theme.dart';
import '../../core/navigation_provider.dart'; import '../../core/navigation_provider.dart';
import '../../providers/auth_provider.dart'; import '../../providers/auth_provider.dart';
import '../../providers/omron_device_provider.dart'; import '../../providers/omron_device_provider.dart';
@@ -183,10 +184,9 @@ class _DeviceScanPageState extends ConsumerState<DeviceScanPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return GradientScaffold(
backgroundColor: Colors.white,
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.white, backgroundColor: Colors.white.withValues(alpha: 0.9),
elevation: 0, elevation: 0,
title: Text( title: Text(
_connected ? _deviceName : '添加设备', _connected ? _deviceName : '添加设备',
@@ -256,7 +256,7 @@ class _DeviceScanPageState extends ConsumerState<DeviceScanPage>
height: 80 * _pulseAnim.value, height: 80 * _pulseAnim.value,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: AppColors.primary.withValues(alpha: 0.08), color: AppColors.auraIndigo.withValues(alpha: 0.10),
), ),
), ),
), ),
@@ -264,13 +264,14 @@ class _DeviceScanPageState extends ConsumerState<DeviceScanPage>
width: 64, width: 64,
height: 64, height: 64,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.avatarBg, gradient: AppColors.calmHealthGradient,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
boxShadow: AppColors.buttonShadow,
), ),
child: const Icon( child: const Icon(
Icons.bluetooth_searching, Icons.bluetooth_searching,
size: 32, size: 32,
color: AppColors.primary, color: Colors.white,
), ),
), ),
], ],
@@ -301,19 +302,18 @@ class _DeviceScanPageState extends ConsumerState<DeviceScanPage>
width: 100, width: 100,
height: 100, height: 100,
decoration: BoxDecoration( decoration: BoxDecoration(
color: _readingReceived gradient: _readingReceived
? AppColors.successLight ? AppColors.calmHealthGradient
: AppColors.avatarBg, : AppColors.primaryGradient,
borderRadius: BorderRadius.circular(32), borderRadius: BorderRadius.circular(32),
boxShadow: AppColors.buttonShadow,
), ),
child: Icon( child: Icon(
_readingReceived _readingReceived
? Icons.check_rounded ? Icons.check_rounded
: Icons.bluetooth_connected, : Icons.bluetooth_connected,
size: 48, size: 48,
color: _readingReceived color: _readingReceived ? Colors.white : Colors.white,
? const Color(0xFF10B981)
: AppColors.primary,
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
@@ -456,9 +456,9 @@ class _DeviceScanPageState extends ConsumerState<DeviceScanPage>
margin: const EdgeInsets.only(bottom: 8), margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, gradient: AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight, boxShadow: AppColors.cardShadowLight,
), ),
child: Row( child: Row(
@@ -467,14 +467,10 @@ class _DeviceScanPageState extends ConsumerState<DeviceScanPage>
width: 44, width: 44,
height: 44, height: 44,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.avatarBg, gradient: AppColors.calmHealthGradient,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: const Icon( child: const Icon(Icons.bluetooth, size: 22, color: Colors.white),
Icons.bluetooth,
size: 22,
color: AppColors.primary,
),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
@@ -515,8 +511,9 @@ class _DeviceScanPageState extends ConsumerState<DeviceScanPage>
vertical: 10, vertical: 10,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primary, gradient: AppColors.primaryGradient,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: AppColors.buttonShadow,
), ),
child: const Text( child: const Text(
'连接', '连接',

View File

@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/navigation_provider.dart'; import '../../core/navigation_provider.dart';
import '../../core/app_colors.dart'; import '../../core/app_colors.dart';
import '../../core/app_theme.dart';
import '../../providers/auth_provider.dart'; import '../../providers/auth_provider.dart';
import '../../utils/sse_handler.dart'; import '../../utils/sse_handler.dart';
@@ -303,7 +304,6 @@ class DietNotifier extends Notifier<DietState> {
// ─────────── 饮食主题色(暖橙系,不再用紫色)─────────── // ─────────── 饮食主题色(暖橙系,不再用紫色)───────────
const _dietAccent = AppColors.primary; const _dietAccent = AppColors.primary;
const _dietAccentLight = AppColors.primarySoft; const _dietAccentLight = AppColors.primarySoft;
const _dietBg = AppColors.background;
class DietCapturePage extends ConsumerStatefulWidget { class DietCapturePage extends ConsumerStatefulWidget {
const DietCapturePage({super.key}); const DietCapturePage({super.key});
@@ -320,10 +320,9 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final state = ref.watch(dietProvider); final state = ref.watch(dietProvider);
return Scaffold( return GradientScaffold(
backgroundColor: _dietBg,
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.white, backgroundColor: Colors.white.withValues(alpha: 0.9),
title: const Text( title: const Text(
'饮食分析', '饮食分析',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600),
@@ -352,15 +351,24 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
child: Column( child: Column(
children: [ children: [
// 图片自适应显示 // 图片自适应显示
ClipRRect( Container(
borderRadius: BorderRadius.circular(16), padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(18),
child: Image.file( child: Image.file(
File(state.imagePath!), File(state.imagePath!),
width: screenW - 32, width: screenW - 48,
height: 220, height: 220,
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
), ),
),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildMealSelector(ref), _buildMealSelector(ref),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -398,9 +406,9 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
return Container( return Container(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, gradient: AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight, boxShadow: AppColors.cardShadowLight,
), ),
child: Row( child: Row(
@@ -413,7 +421,8 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: sel ? _dietAccentLight : Colors.white, gradient: sel ? AppColors.warmCareGradient : null,
color: sel ? null : Colors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: Column( child: Column(
@@ -426,7 +435,7 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: sel ? _dietAccent : AppColors.textHint, color: sel ? Colors.white : AppColors.textHint,
), ),
), ),
], ],
@@ -876,11 +885,19 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
// ─────────── 保存按钮 ─────────── // ─────────── 保存按钮 ───────────
Widget _buildSaveButton() { Widget _buildSaveButton() {
return SizedBox( return Container(
width: double.infinity, width: double.infinity,
height: 52, height: 52,
child: ElevatedButton.icon( decoration: BoxDecoration(
onPressed: () async { gradient: AppColors.warmCareGradient,
borderRadius: BorderRadius.circular(16),
boxShadow: AppColors.buttonShadow,
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () async {
try { try {
await ref.read(dietProvider.notifier).saveRecord(); await ref.read(dietProvider.notifier).saveRecord();
if (mounted) popRoute(ref); if (mounted) popRoute(ref);
@@ -888,16 +905,20 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
debugPrint('[Diet] 保存记录失败: $e'); debugPrint('[Diet] 保存记录失败: $e');
} }
}, },
icon: const Icon(Icons.check_circle_outline, size: 22), child: const Row(
label: const Text( mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.check_circle_outline, size: 22, color: Colors.white),
SizedBox(width: 8),
Text(
'保存记录', '保存记录',
style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600), style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w800,
color: Colors.white,
), ),
style: ElevatedButton.styleFrom( ),
backgroundColor: _dietAccent, ],
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
), ),
), ),
), ),

View File

@@ -5,6 +5,7 @@ import 'package:image_picker/image_picker.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:shadcn_ui/shadcn_ui.dart';
import '../../core/app_colors.dart'; import '../../core/app_colors.dart';
import '../../core/app_theme.dart';
import '../../core/navigation_provider.dart'; import '../../core/navigation_provider.dart';
import '../../providers/auth_provider.dart'; import '../../providers/auth_provider.dart';
import '../../providers/chat_provider.dart'; import '../../providers/chat_provider.dart';
@@ -85,9 +86,8 @@ class _HomePageState extends ConsumerState<HomePage> {
return Scaffold( return Scaffold(
drawer: const HealthDrawer(), drawer: const HealthDrawer(),
body: Container( body: AppBackground(
decoration: const BoxDecoration(gradient: AppColors.bgGradient), safeArea: true,
child: SafeArea(
child: Column( child: Column(
children: [ children: [
_buildHeader(user), _buildHeader(user),
@@ -101,7 +101,6 @@ class _HomePageState extends ConsumerState<HomePage> {
], ],
), ),
), ),
),
); );
} }
@@ -110,10 +109,19 @@ class _HomePageState extends ConsumerState<HomePage> {
? user.name ? user.name
: '用户'; : '用户';
return Container( return Container(
padding: const EdgeInsets.fromLTRB(14, 10, 14, 8), padding: const EdgeInsets.fromLTRB(14, 10, 14, 10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.72), color: Colors.white.withValues(alpha: 0.34),
border: const Border(bottom: BorderSide(color: AppColors.divider)), border: Border(
bottom: BorderSide(color: Colors.white.withValues(alpha: 0.62)),
),
boxShadow: [
BoxShadow(
color: const Color(0xFF101828).withValues(alpha: 0.025),
blurRadius: 16,
offset: const Offset(0, 6),
),
],
), ),
child: Row( child: Row(
children: [ children: [
@@ -178,7 +186,7 @@ class _HomePageState extends ConsumerState<HomePage> {
Widget _buildAgentBar() { Widget _buildAgentBar() {
return SizedBox( return SizedBox(
height: 42, height: 46,
child: ListView.separated( child: ListView.separated(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 14), padding: const EdgeInsets.symmetric(horizontal: 14),
@@ -186,21 +194,36 @@ class _HomePageState extends ConsumerState<HomePage> {
separatorBuilder: (_, _) => const SizedBox(width: 8), separatorBuilder: (_, _) => const SizedBox(width: 8),
itemBuilder: (_, i) { itemBuilder: (_, i) {
final (agent, label, icon) = _agentDefs[i]; final (agent, label, icon) = _agentDefs[i];
final gradient = _agentGradient(agent);
return GestureDetector( return GestureDetector(
onTap: () => onTap: () =>
ref.read(chatProvider.notifier).triggerAgent(agent, label), ref.read(chatProvider.notifier).triggerAgent(agent, label),
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 9), padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 9),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white.withValues(alpha: 0.86),
borderRadius: BorderRadius.circular(999), borderRadius: BorderRadius.circular(999),
border: Border.all(color: AppColors.border), border: Border.all(color: Colors.white, width: 1.2),
boxShadow: AppColors.cardShadowLight, boxShadow: [
BoxShadow(
color: const Color(0xFF6366F1).withValues(alpha: 0.10),
blurRadius: 14,
offset: const Offset(0, 7),
),
],
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(icon, size: 16, color: AppColors.primary), Container(
width: 16,
height: 16,
decoration: BoxDecoration(
gradient: gradient,
borderRadius: BorderRadius.circular(6),
),
child: Icon(icon, size: 11, color: Colors.white),
),
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
label, label,
@@ -219,17 +242,39 @@ class _HomePageState extends ConsumerState<HomePage> {
); );
} }
LinearGradient _agentGradient(ActiveAgent agent) {
return switch (agent) {
ActiveAgent.consultation => AppColors.sageVioletGradient,
ActiveAgent.health => AppColors.meadowVioletGradient,
ActiveAgent.diet => AppColors.warmCareGradient,
ActiveAgent.medication => AppColors.primaryGradient,
ActiveAgent.report => AppColors.roseVioletGradient,
ActiveAgent.exercise => AppColors.calmHealthGradient,
_ => AppColors.primaryGradient,
};
}
Widget _buildBottomBar(BuildContext context) { Widget _buildBottomBar(BuildContext context) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.76), color: Colors.white.withValues(alpha: 0.28),
border: const Border(top: BorderSide(color: AppColors.divider)), border: Border(
top: BorderSide(color: Colors.white.withValues(alpha: 0.62)),
),
boxShadow: [
BoxShadow(
color: const Color(0xFF101828).withValues(alpha: 0.045),
blurRadius: 20,
offset: const Offset(0, -8),
),
],
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const SizedBox(height: 8), const SizedBox(height: 10),
_buildAgentBar(), _buildAgentBar(),
const SizedBox(height: 8),
if (_pickedImagePath != null) _buildImagePreview(), if (_pickedImagePath != null) _buildImagePreview(),
_buildInputBar(), _buildInputBar(),
], ],
@@ -242,9 +287,9 @@ class _HomePageState extends ConsumerState<HomePage> {
margin: const EdgeInsets.fromLTRB(14, 8, 14, 0), margin: const EdgeInsets.fromLTRB(14, 8, 14, 0),
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.cardInner, color: Colors.white.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
), ),
child: Row( child: Row(
children: [ children: [
@@ -275,7 +320,7 @@ class _HomePageState extends ConsumerState<HomePage> {
Widget _buildInputBar() { Widget _buildInputBar() {
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(12, 8, 12, 10), padding: const EdgeInsets.fromLTRB(12, 0, 12, 10),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
@@ -306,15 +351,18 @@ class _HomePageState extends ConsumerState<HomePage> {
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(22), borderRadius: BorderRadius.circular(22),
borderSide: const BorderSide(color: AppColors.border), borderSide: const BorderSide(color: AppColors.borderLight),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(22), borderRadius: BorderRadius.circular(22),
borderSide: const BorderSide(color: AppColors.border), borderSide: const BorderSide(color: AppColors.borderLight),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(22), borderRadius: BorderRadius.circular(22),
borderSide: const BorderSide(color: AppColors.primary), borderSide: const BorderSide(
color: AppColors.auraIndigo,
width: 1.2,
),
), ),
), ),
onSubmitted: (_) => _sendMessage(), onSubmitted: (_) => _sendMessage(),
@@ -442,9 +490,10 @@ class _HeaderIconButton extends StatelessWidget {
width: 38, width: 38,
height: 38, height: 38,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, gradient: AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight,
), ),
child: Icon(icon, size: 20, color: AppColors.textPrimary), child: Icon(icon, size: 20, color: AppColors.textPrimary),
), ),
@@ -466,11 +515,12 @@ class _RoundToolButton extends StatelessWidget {
height: 42, height: 42,
margin: const EdgeInsets.only(bottom: 2), margin: const EdgeInsets.only(bottom: 2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, gradient: AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(21), borderRadius: BorderRadius.circular(21),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight,
), ),
child: Icon(icon, size: 21, color: AppColors.textSecondary), child: Icon(icon, size: 21, color: AppColors.primaryDark),
), ),
); );
} }

View File

@@ -133,7 +133,9 @@ class ChatMessagesView extends ConsumerWidget {
case MessageType.dataConfirm: case MessageType.dataConfirm:
return _buildDataConfirmCard(context, ref, msg); return _buildDataConfirmCard(context, ref, msg);
default: default:
if (!msg.isUser && chatState.isStreaming && msg.content.length < 5) { if (!msg.isUser &&
chatState.isStreaming &&
msg.content.trim().isEmpty) {
return _buildThinkingBubble(context, chatState.thinkingText); return _buildThinkingBubble(context, chatState.thinkingText);
} }
return _buildTextBubble(context, msg, chatState); return _buildTextBubble(context, msg, chatState);
@@ -152,14 +154,9 @@ class ChatMessagesView extends ConsumerWidget {
) { ) {
final info = _agentInfo(agent); final info = _agentInfo(agent);
final actions = agent.actions; final actions = agent.actions;
final features = _getAgentFeatures(agent);
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
final agentColors = _agentColors(agent); final agentColors = _agentColors(agent);
final iconGradient = LinearGradient( final artworkPath = _agentArtworkPath(agent);
colors: agentColors.gradient,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
return _AnimatedCardEntry( return _AnimatedCardEntry(
child: Align( child: Align(
@@ -168,10 +165,20 @@ class ChatMessagesView extends ConsumerWidget {
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: 16),
constraints: BoxConstraints(maxWidth: screenWidth * 0.95), constraints: BoxConstraints(maxWidth: screenWidth * 0.95),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.cardBackground, color: Colors.white,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
border: Border.all(color: AppColors.border), boxShadow: [
boxShadow: AppColors.cardShadow, BoxShadow(
color: const Color(0xFF6B5CE7).withValues(alpha: 0.12),
blurRadius: 28,
offset: const Offset(0, 14),
),
BoxShadow(
color: const Color(0xFF101828).withValues(alpha: 0.06),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
), ),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: Column( child: Column(
@@ -180,41 +187,32 @@ class ChatMessagesView extends ConsumerWidget {
// ── 标题区域 ── // ── 标题区域 ──
Stack( Stack(
children: [ children: [
Positioned.fill(
child: Opacity(
opacity: 0.72,
child: Image.asset(
'assets/branding/agent_welcome_abstract.png',
fit: BoxFit.cover,
),
),
),
Positioned.fill( Positioned.fill(
child: DecoratedBox( child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
Colors.white.withValues(alpha: 0.95), Colors.white,
agentColors.bg.withValues(alpha: 0.70), agentColors.bg.withValues(alpha: 0.86),
Colors.white.withValues(alpha: 0.28), agentColors.accent.withValues(alpha: 0.12),
], ],
stops: const [0.0, 0.46, 1.0], stops: const [0.0, 0.58, 1.0],
begin: Alignment.centerLeft, begin: Alignment.topLeft,
end: Alignment.centerRight, end: Alignment.bottomRight,
), ),
), ),
), ),
), ),
Positioned(
right: -18,
top: -22,
child: _SignalHalo(colors: agentColors),
),
Positioned( Positioned(
right: 18, right: 18,
bottom: 14, top: 18,
child: _MiniSignalStrip(colors: agentColors), child: _MiniSignalStrip(colors: agentColors),
), ),
Positioned(
right: 18,
bottom: 18,
child: _LinearAccentMark(colors: agentColors),
),
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.fromLTRB(18, 20, 18, 22), padding: const EdgeInsets.fromLTRB(18, 20, 18, 22),
@@ -288,119 +286,13 @@ class ChatMessagesView extends ConsumerWidget {
], ],
), ),
// ── 功能特性区 ── // ── 插画展示 ──
if (features.isNotEmpty) ...[ Image.asset(
Padding( artworkPath,
padding: const EdgeInsets.fromLTRB(16, 14, 16, 0), width: double.infinity,
child: Container( height: 206,
padding: const EdgeInsets.all(14), fit: BoxFit.cover,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
border: Border.all(color: AppColors.border),
boxShadow: AppColors.cardShadowLight,
), ),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 4,
height: 16,
decoration: BoxDecoration(
gradient: iconGradient,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
const Text(
'核心功能',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.textSecondary,
),
),
],
),
const SizedBox(height: 10),
Row(
children: features.map((feature) {
return Expanded(
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 3,
),
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 6,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: agentColors.accent.withValues(
alpha: 0.16,
),
),
boxShadow: [
BoxShadow(
color: agentColors.accent.withValues(
alpha: 0.05,
),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: agentColors.iconBg,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: agentColors.accent.withValues(
alpha: 0.12,
),
),
),
child: Icon(
feature.icon,
size: 20,
color: agentColors.accent,
),
),
const SizedBox(height: 6),
Text(
feature.title,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
),
Text(
feature.desc,
style: const TextStyle(
fontSize: 11,
color: AppColors.textHint,
),
),
],
),
),
);
}).toList(),
),
],
),
),
),
],
// ── 医生选择区(问诊专用)── // ── 医生选择区(问诊专用)──
if (agent == ActiveAgent.consultation) ...[ if (agent == ActiveAgent.consultation) ...[
@@ -411,41 +303,16 @@ class ChatMessagesView extends ConsumerWidget {
), ),
], ],
// ── 快捷操作按钮区 ──
if (actions.isNotEmpty) ...[ if (actions.isNotEmpty) ...[
const SizedBox(height: 14), const SizedBox(height: 14),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Container(
width: 6,
height: 18,
decoration: BoxDecoration(
gradient: iconGradient,
borderRadius: BorderRadius.circular(3),
),
),
const SizedBox(width: 8),
Text(
'快捷操作',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: AppColors.textPrimary,
),
),
],
),
const SizedBox(height: 12),
Row(
children: [ children: [
for (int i = 0; i < actions.length; i++) ...[ for (int i = 0; i < actions.length; i++) ...[
if (i > 0) const SizedBox(width: 10), if (i > 0) const SizedBox(width: 10),
Expanded( Flexible(
child: _buildActionButton( child: _buildActionButton(
actions[i], actions[i],
context, context,
@@ -456,8 +323,6 @@ class ChatMessagesView extends ConsumerWidget {
], ],
], ],
), ),
],
),
), ),
], ],
@@ -527,81 +392,44 @@ class ChatMessagesView extends ConsumerWidget {
} }
} }
}, },
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(28),
child: Container( child: Container(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12), padding: const EdgeInsets.all(1.4),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: AppColors.actionOutlineGradient,
colors: [Colors.white, colors.bg.withValues(alpha: 0.82)], borderRadius: BorderRadius.circular(28),
begin: Alignment.topLeft,
end: Alignment.bottomRight,
), ),
borderRadius: BorderRadius.circular(20), child: Container(
border: Border.all(color: colors.accent.withValues(alpha: 0.18)), padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 18),
boxShadow: AppColors.cardShadowLight, decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(26.5),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min,
children: [ children: [
Container( Icon(a.icon, size: 24, color: AppColors.textPrimary),
width: 40, const SizedBox(width: 10),
height: 40, Text(
decoration: BoxDecoration(
color: colors.iconBg,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: colors.accent.withValues(alpha: 0.14),
),
),
child: Icon(a.icon, size: 22, color: colors.accent),
),
const SizedBox(width: 12),
Expanded(
child: Text(
a.label, a.label,
style: const TextStyle( style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w700, color: AppColors.textPrimary),
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
), ),
),
),
Icon(Icons.chevron_right, size: 18, color: AppColors.border),
], ],
), ),
), ),
),
); );
} }
List<_AgentFeature> _getAgentFeatures(ActiveAgent agent) { String _agentArtworkPath(ActiveAgent agent) {
return switch (agent) { return switch (agent) {
ActiveAgent.health => [ ActiveAgent.consultation => 'assets/branding/agent_consultation_card.png',
_AgentFeature(Icons.trending_up, '趋势分析', '查看历史数据'), ActiveAgent.health => 'assets/branding/agent_health_card.png',
_AgentFeature(Icons.add, '快速录入', '血压心率血糖'), ActiveAgent.diet => 'assets/branding/agent_diet_card.png',
], ActiveAgent.medication => 'assets/branding/agent_medication_card.png',
ActiveAgent.diet => [ ActiveAgent.report => 'assets/branding/agent_report_card.png',
_AgentFeature(Icons.camera_alt, '拍照识别', '自动识别食物'), ActiveAgent.exercise => 'assets/branding/agent_exercise_card.png',
_AgentFeature(Icons.bar_chart, '营养分析', '热量营养成分'), _ => 'assets/branding/agent_health_card.png',
],
ActiveAgent.medication => [
_AgentFeature(Icons.list, '用药清单', '管理所有药品'),
_AgentFeature(Icons.alarm, '智能提醒', '按时服药提醒'),
],
ActiveAgent.consultation => [
_AgentFeature(Icons.message, '在线问诊', '专业医生解答'),
_AgentFeature(Icons.local_hospital, '科室选择', '精准匹配'),
],
ActiveAgent.report => [
_AgentFeature(Icons.upload, '上传报告', '支持多种格式'),
_AgentFeature(Icons.search, 'AI解读', '智能分析报告'),
],
ActiveAgent.exercise => [
_AgentFeature(Icons.calendar_month, '运动计划', '科学规划'),
_AgentFeature(Icons.check_circle, '打卡记录', '坚持完成'),
],
_ => [
_AgentFeature(Icons.health_and_safety, '健康管理', '全方位呵护'),
_AgentFeature(Icons.chat, '智能咨询', '随时答疑'),
],
}; };
} }
@@ -855,10 +683,20 @@ class ChatMessagesView extends ConsumerWidget {
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: 16),
constraints: BoxConstraints(maxWidth: screenWidth * 0.95), constraints: BoxConstraints(maxWidth: screenWidth * 0.95),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.cardBackground, color: Colors.white,
borderRadius: BorderRadius.circular(28), borderRadius: BorderRadius.circular(28),
border: Border.all(color: Color(0xFFE2E8F0), width: 2), boxShadow: [
boxShadow: AppColors.cardShadow, BoxShadow(
color: const Color(0xFF6366F1).withValues(alpha: 0.10),
blurRadius: 28,
offset: const Offset(0, 14),
),
BoxShadow(
color: const Color(0xFF101828).withValues(alpha: 0.06),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
), ),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: Column( child: Column(
@@ -868,7 +706,13 @@ class ChatMessagesView extends ConsumerWidget {
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.fromLTRB(20, 20, 20, 18), padding: const EdgeInsets.fromLTRB(20, 20, 20, 18),
decoration: const BoxDecoration(color: Color(0xFFF8F6FF)), decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFFFFFFFF), Color(0xFFF7F4FF)],
),
),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -876,11 +720,11 @@ class ChatMessagesView extends ConsumerWidget {
width: 56, width: 56,
height: 56, height: 56,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.cardInner, gradient: AppColors.actionOutlineGradient,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.primaryPurple.withAlpha(35), color: AppColors.auraIndigo.withValues(alpha: 0.18),
blurRadius: 10, blurRadius: 10,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
@@ -929,13 +773,18 @@ class ChatMessagesView extends ConsumerWidget {
// ── 主要数据展示区(大卡片) ── // ── 主要数据展示区(大卡片) ──
Padding( Padding(
padding: const EdgeInsets.fromLTRB(20, 14, 20, 0), padding: const EdgeInsets.fromLTRB(20, 14, 20, 0),
child: Container(
padding: const EdgeInsets.all(1.4),
decoration: BoxDecoration(
gradient: AppColors.actionOutlineGradient,
borderRadius: BorderRadius.circular(20),
),
child: Container( child: Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.backgroundSecondary, color: Colors.white,
borderRadius: BorderRadius.circular(18), borderRadius: BorderRadius.circular(18),
border: Border.all(color: AppColors.border),
), ),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -949,9 +798,10 @@ class ChatMessagesView extends ConsumerWidget {
), ),
child: Center( child: Center(
child: isHealth child: isHealth
? Text( ? Icon(
_getMetricIcon(backendType), _getMetricIconData(backendType),
style: const TextStyle(fontSize: 36), size: 34,
color: AppColors.primaryDark,
) )
: Icon( : Icon(
isMedication isMedication
@@ -1012,6 +862,7 @@ class ChatMessagesView extends ConsumerWidget {
), ),
), ),
), ),
),
// ── 异常提示 ── // ── 异常提示 ──
if (abnormal) ...[ if (abnormal) ...[
@@ -1098,36 +949,38 @@ class ChatMessagesView extends ConsumerWidget {
child: Container( child: Container(
width: double.infinity, width: double.infinity,
height: 56, height: 56,
padding: const EdgeInsets.all(1.4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, gradient: AppColors.actionOutlineGradient,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all( boxShadow: [
color: AppColors.borderLight, BoxShadow(
width: 1.5, color: AppColors.auraIndigo.withValues(alpha: 0.18),
blurRadius: 18,
offset: const Offset(0, 8),
), ),
boxShadow: AppColors.cardShadow, ],
), ),
child: msg.confirmed child: msg.confirmed
? Container( ? Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: AppColors.successButtonGradient, gradient: AppColors.successButtonGradient,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(18),
boxShadow: AppColors.buttonShadow,
), ),
child: Row( child: const Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon( Icon(
Icons.check_circle_outline, Icons.check_circle_outline,
size: 22, size: 22,
color: Colors.white, color: Colors.white,
), ),
const SizedBox(width: 8), SizedBox(width: 8),
const Text( Text(
'录入成功', '录入成功',
style: TextStyle( style: TextStyle(
fontSize: 19, fontSize: 19,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w700,
color: Colors.white, color: Colors.white,
), ),
), ),
@@ -1142,7 +995,12 @@ class ChatMessagesView extends ConsumerWidget {
.read(chatProvider.notifier) .read(chatProvider.notifier)
.confirmMessage(msg.id); .confirmMessage(msg.id);
}, },
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(18),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@@ -1150,21 +1008,21 @@ class ChatMessagesView extends ConsumerWidget {
width: 32, width: 32,
height: 32, height: 32,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: AppColors.purpleBlueGradient, gradient: AppColors.actionOutlineGradient,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(10),
), ),
child: const Icon( child: const Icon(
Icons.check, Icons.check_rounded,
size: 18, size: 19,
color: Colors.white, color: Colors.white,
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Text( const Text(
'确认录入', '确认录入',
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w800,
color: AppColors.textPrimary, color: AppColors.textPrimary,
), ),
), ),
@@ -1174,6 +1032,7 @@ class ChatMessagesView extends ConsumerWidget {
), ),
), ),
), ),
),
const SizedBox(height: 20), const SizedBox(height: 20),
], ],
), ),
@@ -1249,7 +1108,9 @@ class ChatMessagesView extends ConsumerWidget {
key: ValueKey('editing_${msg.id}_${field.label}'), key: ValueKey('editing_${msg.id}_${field.label}'),
initialValue: field.value, initialValue: field.value,
onDone: (value) { onDone: (value) {
ref.read(chatProvider.notifier).finishEditingField(msg.id, field.label, value); ref
.read(chatProvider.notifier)
.finishEditingField(msg.id, field.label, value);
}, },
); );
} }
@@ -1612,22 +1473,22 @@ class ChatMessagesView extends ConsumerWidget {
} }
} }
String _getMetricIcon(String type) { IconData _getMetricIconData(String type) {
switch (type.toLowerCase()) { switch (type.toLowerCase()) {
case 'blood_pressure': case 'blood_pressure':
return '🩺'; return Icons.bloodtype_outlined;
case 'heart_rate': case 'heart_rate':
return '💓'; return Icons.monitor_heart_outlined;
case 'glucose': case 'glucose':
return '💉'; return Icons.water_drop_outlined;
case 'spo2': case 'spo2':
return '🫁'; return Icons.air_outlined;
case 'weight': case 'weight':
return '⚖️'; return Icons.monitor_weight_outlined;
case 'exercise': case 'exercise':
return '🏃'; return Icons.directions_run_outlined;
default: default:
return '📊'; return Icons.query_stats_outlined;
} }
} }
@@ -1692,46 +1553,46 @@ class ChatMessagesView extends ConsumerWidget {
static _AgentColors _agentColors(ActiveAgent agent) { static _AgentColors _agentColors(ActiveAgent agent) {
return switch (agent) { return switch (agent) {
ActiveAgent.consultation => _AgentColors( ActiveAgent.consultation => _AgentColors(
gradient: [AppColors.primary, AppColors.blueMeasure], gradient: [AppColors.sageAccent, AppColors.primary],
bg: const Color(0xFFF3F6FF), bg: const Color(0xFFF2FBF9),
border: const Color(0xFFDCE5FF), border: const Color(0xFFD9F0EA),
iconBg: const Color(0xFFEFF4FF), iconBg: const Color(0xFFE8F8F4),
accent: AppColors.blueMeasure, accent: const Color(0xFF4F9E90),
), ),
ActiveAgent.health => _AgentColors( ActiveAgent.health => _AgentColors(
gradient: [AppColors.primary, const Color(0xFF26B99A)], gradient: [AppColors.meadowAccent, AppColors.auraDeepIndigo],
bg: const Color(0xFFF1FBF8), bg: const Color(0xFFF3FBF4),
border: const Color(0xFFD8F1EA), border: const Color(0xFFDFF2E4),
iconBg: const Color(0xFFE9F8F4), iconBg: const Color(0xFFEFF9F0),
accent: const Color(0xFF26B99A), accent: const Color(0xFF4EA65B),
), ),
ActiveAgent.diet => _AgentColors( ActiveAgent.diet => _AgentColors(
gradient: [AppColors.primary, const Color(0xFFFFA14F)], gradient: [AppColors.peachAccent, AppColors.auraOrchid],
bg: const Color(0xFFFFF7F0), bg: const Color(0xFFFFF7F0),
border: const Color(0xFFFFE8D4), border: const Color(0xFFFFE8D4),
iconBg: const Color(0xFFFFF0E4), iconBg: const Color(0xFFFFF0E4),
accent: const Color(0xFFFF8A3D), accent: const Color(0xFFE26A64),
), ),
ActiveAgent.medication => _AgentColors( ActiveAgent.medication => _AgentColors(
gradient: [AppColors.primary, AppColors.blueMeasure], gradient: [AppColors.auraLavender, AppColors.blueMeasure],
bg: const Color(0xFFF4F6FF), bg: const Color(0xFFF4F6FF),
border: const Color(0xFFDDE6FF), border: const Color(0xFFDDE6FF),
iconBg: const Color(0xFFEFF3FF), iconBg: const Color(0xFFEFF3FF),
accent: AppColors.primary, accent: AppColors.primary,
), ),
ActiveAgent.report => _AgentColors( ActiveAgent.report => _AgentColors(
gradient: [AppColors.primary, const Color(0xFF9B7CFF)], gradient: [AppColors.primary, AppColors.roseAccent],
bg: const Color(0xFFF8F5FF), bg: const Color(0xFFF8F5FF),
border: const Color(0xFFE9E0FF), border: const Color(0xFFE9E0FF),
iconBg: const Color(0xFFF2EDFF), iconBg: const Color(0xFFF2EDFF),
accent: AppColors.primary, accent: AppColors.primary,
), ),
ActiveAgent.exercise => _AgentColors( ActiveAgent.exercise => _AgentColors(
gradient: [AppColors.primary, const Color(0xFF21A6B7)], gradient: [AppColors.sageAccent, AppColors.auraDeepIndigo],
bg: const Color(0xFFF0FBFD), bg: const Color(0xFFF0FBFD),
border: const Color(0xFFD6EEF2), border: const Color(0xFFD6EEF2),
iconBg: const Color(0xFFE8F7FA), iconBg: const Color(0xFFE8F7FA),
accent: const Color(0xFF21A6B7), accent: const Color(0xFF2B8EA0),
), ),
_ => _AgentColors( _ => _AgentColors(
gradient: [AppColors.primary, AppColors.blueMeasure], gradient: [AppColors.primary, AppColors.blueMeasure],
@@ -2022,7 +1883,10 @@ class ChatMessagesView extends ConsumerWidget {
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.fromLTRB(16, 12, 16, 10), padding: const EdgeInsets.fromLTRB(16, 12, 16, 10),
decoration: const BoxDecoration(gradient: AppColors.drawerGradient), decoration: const BoxDecoration(
gradient: AppColors.drawerGradient,
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
),
child: Row( child: Row(
children: [ children: [
Container( Container(
@@ -2197,23 +2061,21 @@ class _AgentMark extends StatelessWidget {
} }
} }
class _SignalHalo extends StatelessWidget { class _LinearAccentMark extends StatelessWidget {
final _AgentColors colors; final _AgentColors colors;
const _SignalHalo({required this.colors}); const _LinearAccentMark({required this.colors});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
width: 118, width: 86,
height: 118, height: 8,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, borderRadius: BorderRadius.circular(999),
gradient: RadialGradient( gradient: LinearGradient(
colors: [ colors: colors.gradient
colors.accent.withValues(alpha: 0.18), .map((color) => color.withValues(alpha: 0.34))
colors.accent.withValues(alpha: 0.06), .toList(),
Colors.transparent,
],
), ),
), ),
); );
@@ -2279,14 +2141,6 @@ class _AgentAction {
}); });
} }
class _AgentFeature {
final IconData icon;
final String title;
final String desc;
const _AgentFeature(this.icon, this.title, this.desc);
}
class _ConfirmField { class _ConfirmField {
final String label; final String label;
final String value; final String value;
@@ -2303,11 +2157,17 @@ class _ConfirmField {
final _agentActions = <ActiveAgent, List<_AgentAction>>{ final _agentActions = <ActiveAgent, List<_AgentAction>>{
ActiveAgent.health: [ ActiveAgent.health: [
_AgentAction( _AgentAction(
label: '查看健康情况', label: '健康概览',
icon: Icons.trending_up, icon: Icons.trending_up,
isWide: true, isWide: true,
route: 'trend', route: 'trend',
), ),
_AgentAction(
label: '蓝牙录入',
icon: Icons.bluetooth,
isWide: true,
route: 'devices',
),
], ],
ActiveAgent.diet: [ ActiveAgent.diet: [
_AgentAction( _AgentAction(
@@ -2446,30 +2306,52 @@ class _ExpandableAdviceState extends State<_ExpandableAdvice> {
class _EditFieldCell extends StatefulWidget { class _EditFieldCell extends StatefulWidget {
final String initialValue; final String initialValue;
final ValueChanged<String> onDone; final ValueChanged<String> onDone;
const _EditFieldCell({super.key, required this.initialValue, required this.onDone}); const _EditFieldCell({
super.key,
required this.initialValue,
required this.onDone,
});
@override State<_EditFieldCell> createState() => _EditFieldCellState(); @override
State<_EditFieldCell> createState() => _EditFieldCellState();
} }
class _EditFieldCellState extends State<_EditFieldCell> { class _EditFieldCellState extends State<_EditFieldCell> {
late final TextEditingController _ctrl; late final TextEditingController _ctrl;
@override void initState() { @override
void initState() {
super.initState(); super.initState();
_ctrl = TextEditingController(text: widget.initialValue); _ctrl = TextEditingController(text: widget.initialValue);
} }
@override void dispose() { _ctrl.dispose(); super.dispose(); } @override
void dispose() {
_ctrl.dispose();
super.dispose();
}
@override Widget build(BuildContext context) => TextField( @override
Widget build(BuildContext context) => TextField(
controller: _ctrl, controller: _ctrl,
autofocus: true, autofocus: true,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1E293B)), style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF1E293B),
),
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
filled: true, fillColor: Colors.white, filled: true,
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: const Color(0xFF6366F1).withAlpha(80))), fillColor: Colors.white,
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: Color(0xFF6366F1), width: 1.5)), enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: const Color(0xFF6366F1).withAlpha(80)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Color(0xFF6366F1), width: 1.5),
),
), ),
onSubmitted: widget.onDone, onSubmitted: widget.onDone,
onTapOutside: (_) => widget.onDone(_ctrl.text), onTapOutside: (_) => widget.onDone(_ctrl.text),

View File

@@ -55,7 +55,7 @@ class _MedicationListPageState extends ConsumerState<MedicationListPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return GradientScaffold(
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
@@ -79,7 +79,7 @@ class _MedicationListPageState extends ConsumerState<MedicationListPage>
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () => pushRoute(ref, 'medicationEdit'), onPressed: () => pushRoute(ref, 'medicationEdit'),
backgroundColor: AppColors.primary, backgroundColor: AppColors.primaryDark,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: const Icon(Icons.add, size: 28, color: Colors.white), child: const Icon(Icons.add, size: 28, color: Colors.white),
), ),
@@ -119,9 +119,9 @@ class _MedicationListPageState extends ConsumerState<MedicationListPage>
child: Container( child: Container(
padding: const EdgeInsets.all(AppTheme.sLg), padding: const EdgeInsets.all(AppTheme.sLg),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppTheme.surface, gradient: AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(AppTheme.rMd), borderRadius: BorderRadius.circular(AppTheme.rLg),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight, boxShadow: AppColors.cardShadowLight,
), ),
child: Row( child: Row(
@@ -130,15 +130,16 @@ class _MedicationListPageState extends ConsumerState<MedicationListPage>
width: 44, width: 44,
height: 44, height: 44,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.iconBg, gradient: isActive
borderRadius: BorderRadius.circular(AppTheme.rSm), ? AppColors.primaryGradient
: AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(AppTheme.rMd),
border: Border.all(color: AppColors.borderLight),
), ),
child: Icon( child: Icon(
Icons.medication_outlined, Icons.medication_outlined,
size: 25, size: 25,
color: isActive color: isActive ? Colors.white : AppTheme.textSub,
? AppColors.primary
: AppTheme.textSub,
), ),
), ),
const SizedBox(width: AppTheme.sMd), const SizedBox(width: AppTheme.sMd),

View File

@@ -17,7 +17,7 @@ class ProfilePage extends ConsumerWidget {
return GradientScaffold( return GradientScaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: AppColors.cardBackground, backgroundColor: Colors.white.withValues(alpha: 0.9),
title: const Text('个人信息'), title: const Text('个人信息'),
), ),
body: SafeArea( body: SafeArea(
@@ -31,7 +31,7 @@ class ProfilePage extends ConsumerWidget {
Center( Center(
child: CircleAvatar( child: CircleAvatar(
radius: 50, radius: 50,
backgroundColor: AppColors.cardInner, backgroundColor: AppColors.primaryLight,
backgroundImage: user?.avatarUrl != null backgroundImage: user?.avatarUrl != null
? NetworkImage(user!.avatarUrl!) ? NetworkImage(user!.avatarUrl!)
: null, : null,
@@ -60,9 +60,9 @@ class ProfilePage extends ConsumerWidget {
vertical: 6, vertical: 6,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, gradient: AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight, boxShadow: AppColors.cardShadowLight,
), ),
child: Row( child: Row(
@@ -92,9 +92,9 @@ class ProfilePage extends ConsumerWidget {
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.cardBackground, gradient: AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(AppTheme.rLg), borderRadius: BorderRadius.circular(AppTheme.rLg),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight, boxShadow: AppColors.cardShadowLight,
), ),
child: Column( child: Column(
@@ -159,10 +159,11 @@ class ProfilePage extends ConsumerWidget {
width: 40, width: 40,
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.iconBg, gradient: AppColors.primaryGradient,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(12),
boxShadow: AppColors.cardShadowLight,
), ),
child: Icon(icon, size: 22, color: AppColors.primary), child: Icon(icon, size: 22, color: Colors.white),
), ),
const SizedBox(width: 14), const SizedBox(width: 14),
Expanded( Expanded(

View File

@@ -68,8 +68,7 @@ class _DietRecordListPageState extends ConsumerState<DietRecordListPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_loading) if (_loading)
return Scaffold( return GradientScaffold(
backgroundColor: AppColors.background,
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 0, elevation: 0,
@@ -92,8 +91,7 @@ class _DietRecordListPageState extends ConsumerState<DietRecordListPage> {
(s, r) => s + ((r['totalCalories'] as num?)?.toInt() ?? 0), (s, r) => s + ((r['totalCalories'] as num?)?.toInt() ?? 0),
); );
return Scaffold( return GradientScaffold(
backgroundColor: AppColors.background,
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 0, elevation: 0,
@@ -1349,8 +1347,7 @@ class _HealthArchivePageState extends ConsumerState<HealthArchivePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_loading) { if (_loading) {
return Scaffold( return GradientScaffold(
backgroundColor: AppColors.background,
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 0, elevation: 0,
@@ -1367,8 +1364,7 @@ class _HealthArchivePageState extends ConsumerState<HealthArchivePage> {
body: const Center(child: CircularProgressIndicator()), body: const Center(child: CircularProgressIndicator()),
); );
} }
return Scaffold( return GradientScaffold(
backgroundColor: AppColors.background,
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 0, elevation: 0,
@@ -1856,8 +1852,7 @@ class _HealthCalendarPageState extends ConsumerState<HealthCalendarPage> {
final isToday = final isToday =
_selectedDate != null && _dateKey(_selectedDate!) == _dateKey(today); _selectedDate != null && _dateKey(_selectedDate!) == _dateKey(today);
return Scaffold( return GradientScaffold(
backgroundColor: AppColors.background,
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 0, elevation: 0,

View File

@@ -27,7 +27,15 @@ class ChatMessage {
bool get isUser => role == 'user'; bool get isUser => role == 'user';
} }
enum ActiveAgent { default_, consultation, health, diet, medication, report, exercise } enum ActiveAgent {
default_,
consultation,
health,
diet,
medication,
report,
exercise,
}
class ChatState { class ChatState {
final ActiveAgent activeAgent; final ActiveAgent activeAgent;
@@ -42,9 +50,13 @@ class ChatState {
this.isStreaming = false, this.isStreaming = false,
this.thinkingText, this.thinkingText,
}); });
ChatState copyWith({ActiveAgent? activeAgent, List<ChatMessage>? messages, ChatState copyWith({
String? conversationId, bool? isStreaming, String? thinkingText}) => ActiveAgent? activeAgent,
ChatState( List<ChatMessage>? messages,
String? conversationId,
bool? isStreaming,
String? thinkingText,
}) => ChatState(
activeAgent: activeAgent ?? this.activeAgent, activeAgent: activeAgent ?? this.activeAgent,
messages: messages ?? this.messages, messages: messages ?? this.messages,
conversationId: conversationId ?? this.conversationId, conversationId: conversationId ?? this.conversationId,
@@ -60,18 +72,29 @@ class SelectedAgentNotifier extends Notifier<ActiveAgent?> {
} }
final selectedAgentProvider = final selectedAgentProvider =
NotifierProvider<SelectedAgentNotifier, ActiveAgent?>(SelectedAgentNotifier.new); NotifierProvider<SelectedAgentNotifier, ActiveAgent?>(
final chatProvider = NotifierProvider<ChatNotifier, ChatState>(ChatNotifier.new); SelectedAgentNotifier.new,
);
final chatProvider = NotifierProvider<ChatNotifier, ChatState>(
ChatNotifier.new,
);
ActiveAgent _parseAgent(String? type) { ActiveAgent _parseAgent(String? type) {
switch (type?.toLowerCase()) { switch (type?.toLowerCase()) {
case 'consultation': return ActiveAgent.consultation; case 'consultation':
case 'health': return ActiveAgent.health; return ActiveAgent.consultation;
case 'diet': return ActiveAgent.diet; case 'health':
case 'medication': return ActiveAgent.medication; return ActiveAgent.health;
case 'report': return ActiveAgent.report; case 'diet':
case 'exercise': return ActiveAgent.exercise; return ActiveAgent.diet;
default: return ActiveAgent.default_; case 'medication':
return ActiveAgent.medication;
case 'report':
return ActiveAgent.report;
case 'exercise':
return ActiveAgent.exercise;
default:
return ActiveAgent.default_;
} }
} }
@@ -85,7 +108,10 @@ class ChatNotifier extends Notifier<ChatState> {
void confirmMessage(String id) { void confirmMessage(String id) {
final msgs = state.messages.toList(); final msgs = state.messages.toList();
final i = msgs.indexWhere((m) => m.id == id); final i = msgs.indexWhere((m) => m.id == id);
if (i >= 0) { msgs[i].confirmed = true; state = state.copyWith(messages: msgs); } if (i >= 0) {
msgs[i].confirmed = true;
state = state.copyWith(messages: msgs);
}
ref.invalidate(medicationListProvider); ref.invalidate(medicationListProvider);
ref.invalidate(latestHealthProvider); ref.invalidate(latestHealthProvider);
} }
@@ -93,7 +119,10 @@ class ChatNotifier extends Notifier<ChatState> {
void startEditingField(String msgId, String fieldLabel) { void startEditingField(String msgId, String fieldLabel) {
final msgs = state.messages.toList(); final msgs = state.messages.toList();
final i = msgs.indexWhere((m) => m.id == msgId); final i = msgs.indexWhere((m) => m.id == msgId);
if (i >= 0) { msgs[i].metadata?['_editingField'] = fieldLabel; state = state.copyWith(messages: msgs); } if (i >= 0) {
msgs[i].metadata?['_editingField'] = fieldLabel;
state = state.copyWith(messages: msgs);
}
} }
void finishEditingField(String msgId, String fieldLabel, String value) { void finishEditingField(String msgId, String fieldLabel, String value) {
@@ -116,13 +145,18 @@ class ChatNotifier extends Notifier<ChatState> {
void insertTaskCard() { void insertTaskCard() {
if (state.messages.any((m) => m.type == MessageType.taskCard)) return; if (state.messages.any((m) => m.type == MessageType.taskCard)) return;
state = state.copyWith(messages: [ChatMessage( state = state.copyWith(
messages: [
ChatMessage(
id: 'task_card', id: 'task_card',
role: 'assistant', role: 'assistant',
content: '', content: '',
createdAt: DateTime.now(), createdAt: DateTime.now(),
type: MessageType.taskCard, type: MessageType.taskCard,
), ...state.messages]); ),
...state.messages,
],
);
} }
void setAgent(ActiveAgent a) { void setAgent(ActiveAgent a) {
@@ -175,7 +209,9 @@ class ChatNotifier extends Notifier<ChatState> {
id: map['id']?.toString() ?? '', id: map['id']?.toString() ?? '',
role: map['role']?.toString() ?? 'user', role: map['role']?.toString() ?? 'user',
content: map['content']?.toString() ?? '', content: map['content']?.toString() ?? '',
createdAt: DateTime.tryParse(map['createdAt']?.toString() ?? '') ?? DateTime.now(), createdAt:
DateTime.tryParse(map['createdAt']?.toString() ?? '') ??
DateTime.now(),
type: MessageType.text, type: MessageType.text,
); );
}).toList(); }).toList();
@@ -190,14 +226,19 @@ class ChatNotifier extends Notifier<ChatState> {
} }
void insertAgentWelcome(ActiveAgent agent) { void insertAgentWelcome(ActiveAgent agent) {
state = state.copyWith(messages: [...state.messages, ChatMessage( state = state.copyWith(
messages: [
...state.messages,
ChatMessage(
id: 'welcome_${agent.name}_${DateTime.now().millisecondsSinceEpoch}', id: 'welcome_${agent.name}_${DateTime.now().millisecondsSinceEpoch}',
role: 'assistant', role: 'assistant',
content: '', content: '',
createdAt: DateTime.now(), createdAt: DateTime.now(),
type: MessageType.agentWelcome, type: MessageType.agentWelcome,
metadata: {'agent': agent.name}, metadata: {'agent': agent.name},
)]); ),
],
);
} }
/// 点击胶囊:先出用户标签 → 0.4 秒后出欢迎卡片,不走 AI /// 点击胶囊:先出用户标签 → 0.4 秒后出欢迎卡片,不走 AI
@@ -275,7 +316,9 @@ class ChatNotifier extends Notifier<ChatState> {
createdAt: DateTime.now(), createdAt: DateTime.now(),
); );
state = state.copyWith( state = state.copyWith(
messages: [...state.messages, userMsg], isStreaming: true); messages: [...state.messages, userMsg],
isStreaming: true,
);
await _sendToAI(text); await _sendToAI(text);
} }
@@ -289,7 +332,10 @@ class ChatNotifier extends Notifier<ChatState> {
); );
// 立即加入空 AI 消息,让思考动画有载体 // 立即加入空 AI 消息,让思考动画有载体
state = state.copyWith(messages: [...state.messages, aiMsg], isStreaming: true); state = state.copyWith(
messages: [...state.messages, aiMsg],
isStreaming: true,
);
try { try {
final token = await ref.read(apiClientProvider).accessToken; final token = await ref.read(apiClientProvider).accessToken;
@@ -309,25 +355,25 @@ class ChatNotifier extends Notifier<ChatState> {
await for (final event in stream) { await for (final event in stream) {
_processEvent(event, aiMsg); _processEvent(event, aiMsg);
} }
if (state.isStreaming) {
_done(aiMsg);
}
} catch (e) { } catch (e) {
_addError(aiMsg, '网络异常,请稍后重试'); _addError(aiMsg, '网络异常,请稍后重试');
} }
} }
void _addError(ChatMessage aiMsg, String errorText) { void _addError(ChatMessage aiMsg, String errorText) {
state = state.copyWith( aiMsg.content = errorText;
messages: [ aiMsg.type = MessageType.text;
...state.messages, final u = state.messages.toList();
ChatMessage( final i = u.indexWhere((x) => x.id == aiMsg.id);
id: 'err_${DateTime.now().millisecondsSinceEpoch}', if (i >= 0) {
role: 'assistant', u[i] = aiMsg;
content: errorText, } else {
createdAt: DateTime.now(), u.add(aiMsg);
), }
], state = state.copyWith(messages: u, isStreaming: false, thinkingText: null);
isStreaming: false,
thinkingText: null,
);
} }
void _processEvent(Map<String, dynamic> j, ChatMessage aiMsg) { void _processEvent(Map<String, dynamic> j, ChatMessage aiMsg) {
@@ -385,7 +431,16 @@ class ChatNotifier extends Notifier<ChatState> {
void _done(ChatMessage m) { void _done(ChatMessage m) {
final u = state.messages.toList(); final u = state.messages.toList();
if (!u.any((x) => x.id == m.id) && m.content.isNotEmpty) u.add(m); final content = m.content.trim();
if (content.isEmpty && m.type == MessageType.text) {
m.content = '暂时没有收到回复,请稍后再试。';
}
final i = u.indexWhere((x) => x.id == m.id);
if (i >= 0) {
u[i] = m;
} else {
u.add(m);
}
state = state.copyWith(messages: u, isStreaming: false, thinkingText: null); state = state.copyWith(messages: u, isStreaming: false, thinkingText: null);
} }
} }

View File

@@ -15,7 +15,7 @@ class AdminDrawer extends ConsumerWidget {
return Drawer( return Drawer(
width: MediaQuery.of(context).size.width * 0.8, width: MediaQuery.of(context).size.width * 0.8,
child: Container( child: Container(
color: AppColors.background, decoration: const BoxDecoration(gradient: AppColors.drawerGradient),
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
@@ -23,10 +23,10 @@ class AdminDrawer extends ConsumerWidget {
margin: const EdgeInsets.all(14), margin: const EdgeInsets.all(14),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, gradient: AppColors.softGlassGradient,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight, boxShadow: AppColors.cardShadow,
), ),
child: Row( child: Row(
children: [ children: [
@@ -36,6 +36,7 @@ class AdminDrawer extends ConsumerWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: AppColors.primaryGradient, gradient: AppColors.primaryGradient,
borderRadius: BorderRadius.circular(18), borderRadius: BorderRadius.circular(18),
boxShadow: AppColors.buttonShadow,
), ),
child: const Icon( child: const Icon(
Icons.admin_panel_settings, Icons.admin_panel_settings,
@@ -135,7 +136,7 @@ class _MenuItem extends StatelessWidget {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4),
child: Material( child: Material(
color: selected ? AppColors.primarySoft : Colors.white, color: Colors.transparent,
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
@@ -143,14 +144,22 @@ class _MenuItem extends StatelessWidget {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: selected
? AppColors.primaryGradient
: AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
border: Border.all( border: Border.all(
color: selected ? AppColors.primaryLight : AppColors.border, color: selected
? AppColors.primaryLight
: AppColors.borderLight,
), ),
boxShadow: selected
? AppColors.buttonShadow
: AppColors.cardShadowLight,
), ),
child: Row( child: Row(
children: [ children: [
Icon(icon, color: color, size: 20), Icon(icon, color: selected ? Colors.white : color, size: 20),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Text( child: Text(
@@ -158,7 +167,7 @@ class _MenuItem extends StatelessWidget {
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: color, color: selected ? Colors.white : color,
), ),
), ),
), ),

View File

@@ -34,9 +34,10 @@ class AppCard extends StatelessWidget {
), ),
padding: padding, padding: padding,
decoration: BoxDecoration( decoration: BoxDecoration(
color: backgroundColor ?? AppColors.cardBackground, gradient: backgroundColor == null ? AppColors.surfaceGradient : null,
color: backgroundColor,
borderRadius: radius, borderRadius: radius,
border: border ?? Border.all(color: AppColors.border), border: border ?? Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight, boxShadow: AppColors.cardShadowLight,
), ),
child: child, child: child,

View File

@@ -27,11 +27,12 @@ class AppEmptyState extends StatelessWidget {
width: 80, width: 80,
height: 80, height: 80,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.iconBg, gradient: AppColors.softGlassGradient,
borderRadius: BorderRadius.circular(40), borderRadius: BorderRadius.circular(40),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight,
), ),
child: Icon(icon, size: 34, color: AppColors.primary), child: Icon(icon, size: 34, color: AppColors.primaryDark),
), ),
const SizedBox(height: 18), const SizedBox(height: 18),
Text( Text(

View File

@@ -24,11 +24,11 @@ class AppMenuItem extends StatelessWidget {
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
child: Container( child: Container(
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 5), margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 13), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, gradient: AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight, boxShadow: AppColors.cardShadowLight,
), ),
child: Row( child: Row(
@@ -37,10 +37,14 @@ class AppMenuItem extends StatelessWidget {
width: 40, width: 40,
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.iconBg, gradient: LinearGradient(
borderRadius: BorderRadius.circular(12), begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.primaryLight, AppColors.infoLight],
), ),
child: Icon(icon, size: 21, color: AppColors.primary), borderRadius: BorderRadius.circular(13),
),
child: Icon(icon, size: 20, color: AppColors.primaryDark),
), ),
const SizedBox(width: 14), const SizedBox(width: 14),
Expanded( Expanded(

View File

@@ -23,10 +23,13 @@ class AppTabChip extends StatelessWidget {
margin: const EdgeInsets.only(right: AppTheme.sSm), margin: const EdgeInsets.only(right: AppTheme.sSm),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 9), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 9),
decoration: BoxDecoration( decoration: BoxDecoration(
color: selected ? AppColors.primary : Colors.white, gradient: selected
? AppColors.primaryGradient
: AppColors.surfaceGradient,
color: selected ? null : Colors.white,
borderRadius: BorderRadius.circular(AppTheme.rPill), borderRadius: BorderRadius.circular(AppTheme.rPill),
border: Border.all( border: Border.all(
color: selected ? AppColors.primary : AppColors.border, color: selected ? AppColors.primaryLight : AppColors.borderLight,
), ),
boxShadow: selected boxShadow: selected
? AppColors.buttonShadow ? AppColors.buttonShadow

View File

@@ -23,7 +23,9 @@ class GradientBorderButton extends StatelessWidget {
return Container( return Container(
height: height, height: height,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: isSuccess ? AppColors.primaryGradient : AppColors.primaryGradient, gradient: isSuccess
? AppColors.calmHealthGradient
: AppColors.primaryGradient,
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
boxShadow: AppColors.buttonShadow, boxShadow: AppColors.buttonShadow,
), ),
@@ -78,9 +80,10 @@ class CardActionButton extends StatelessWidget {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.cardInner, gradient: AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.borderLight), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight,
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -109,11 +112,19 @@ class SwipeDeleteTile extends StatefulWidget {
final VoidCallback onDelete; final VoidCallback onDelete;
final VoidCallback? onTap; final VoidCallback? onTap;
final EdgeInsetsGeometry margin; final EdgeInsetsGeometry margin;
const SwipeDeleteTile({super.key, required this.child, required this.onDelete, this.onTap, this.margin = const EdgeInsets.symmetric(horizontal: 16, vertical: 4)}); const SwipeDeleteTile({
@override State<SwipeDeleteTile> createState() => _SwipeDeleteTileState(); super.key,
required this.child,
required this.onDelete,
this.onTap,
this.margin = const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
});
@override
State<SwipeDeleteTile> createState() => _SwipeDeleteTileState();
} }
class _SwipeDeleteTileState extends State<SwipeDeleteTile> with SingleTickerProviderStateMixin { class _SwipeDeleteTileState extends State<SwipeDeleteTile>
with SingleTickerProviderStateMixin {
double _dx = 0; double _dx = 0;
static const _maxSlide = 80.0; static const _maxSlide = 80.0;
static const _threshold = 24.0; // 30% of 80 static const _threshold = 24.0; // 30% of 80
@@ -130,21 +141,42 @@ class _SwipeDeleteTileState extends State<SwipeDeleteTile> with SingleTickerProv
} }
} }
@override Widget build(BuildContext context) { @override
Widget build(BuildContext context) {
final isSwiped = _dx < 0; final isSwiped = _dx < 0;
return Padding( return Padding(
padding: widget.margin, padding: widget.margin,
child: Stack(children: [ child: Stack(
Positioned.fill(child: Container( children: [
decoration: BoxDecoration(color: AppColors.error, borderRadius: BorderRadius.circular(16)), Positioned.fill(
child: Container(
decoration: BoxDecoration(
color: AppColors.error,
borderRadius: BorderRadius.circular(16),
),
child: const Align( child: const Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Padding(padding: EdgeInsets.only(right: 20), child: Icon(Icons.delete_outline, color: AppColors.textOnGradient, size: 28)), child: Padding(
padding: EdgeInsets.only(right: 20),
child: Icon(
Icons.delete_outline,
color: AppColors.textOnGradient,
size: 28,
),
),
),
),
), ),
)),
GestureDetector( GestureDetector(
behavior: isSwiped ? HitTestBehavior.opaque : HitTestBehavior.deferToChild, behavior: isSwiped
onTap: isSwiped ? () { widget.onDelete(); setState(() => _dx = 0); } : widget.onTap, ? HitTestBehavior.opaque
: HitTestBehavior.deferToChild,
onTap: isSwiped
? () {
widget.onDelete();
setState(() => _dx = 0);
}
: widget.onTap,
onHorizontalDragUpdate: _onDragUpdate, onHorizontalDragUpdate: _onDragUpdate,
onHorizontalDragEnd: _onDragEnd, onHorizontalDragEnd: _onDragEnd,
child: Transform.translate( child: Transform.translate(
@@ -154,7 +186,8 @@ class _SwipeDeleteTileState extends State<SwipeDeleteTile> with SingleTickerProv
: widget.child, : widget.child,
), ),
), ),
]), ],
),
); );
} }
} }
@@ -180,14 +213,12 @@ class IconBox extends StatelessWidget {
width: size, width: size,
height: size, height: size,
decoration: BoxDecoration( decoration: BoxDecoration(
color: backgroundColor ?? AppColors.iconBg, gradient: backgroundColor == null ? AppColors.calmHealthGradient : null,
color: backgroundColor,
borderRadius: BorderRadius.circular(size / 3), borderRadius: BorderRadius.circular(size / 3),
boxShadow: AppColors.cardShadowLight,
), ),
child: Icon( child: Icon(icon, size: size * 0.55, color: iconColor ?? Colors.white),
icon,
size: size * 0.55,
color: iconColor ?? AppColors.primary,
),
); );
} }
} }

View File

@@ -17,7 +17,7 @@ class DoctorDrawer extends ConsumerWidget {
return Drawer( return Drawer(
width: MediaQuery.of(context).size.width * 0.8, width: MediaQuery.of(context).size.width * 0.8,
child: Container( child: Container(
color: AppColors.background, decoration: const BoxDecoration(gradient: AppColors.drawerGradient),
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
@@ -126,10 +126,10 @@ class _DrawerHeader extends StatelessWidget {
margin: const EdgeInsets.all(14), margin: const EdgeInsets.all(14),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, gradient: AppColors.softGlassGradient,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColors.border), border: Border.all(color: AppColors.borderLight),
boxShadow: AppColors.cardShadowLight, boxShadow: AppColors.cardShadow,
), ),
child: InkWell( child: InkWell(
onTap: onTap, onTap: onTap,
@@ -142,6 +142,7 @@ class _DrawerHeader extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: AppColors.doctorGradient, gradient: AppColors.doctorGradient,
borderRadius: BorderRadius.circular(18), borderRadius: BorderRadius.circular(18),
boxShadow: AppColors.buttonShadow,
), ),
child: Icon(icon, color: Colors.white, size: 26), child: Icon(icon, color: Colors.white, size: 26),
), ),
@@ -202,7 +203,7 @@ class _DrawerItem extends StatelessWidget {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4),
child: Material( child: Material(
color: selected ? AppColors.primarySoft : Colors.white, color: Colors.transparent,
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
@@ -210,14 +211,22 @@ class _DrawerItem extends StatelessWidget {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: selected
? AppColors.primaryGradient
: AppColors.surfaceGradient,
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
border: Border.all( border: Border.all(
color: selected ? AppColors.primaryLight : AppColors.border, color: selected
? AppColors.primaryLight
: AppColors.borderLight,
), ),
boxShadow: selected
? AppColors.buttonShadow
: AppColors.cardShadowLight,
), ),
child: Row( child: Row(
children: [ children: [
Icon(icon, size: 20, color: color), Icon(icon, size: 20, color: selected ? Colors.white : color),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Text( child: Text(
@@ -225,7 +234,7 @@ class _DrawerItem extends StatelessWidget {
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: color, color: selected ? Colors.white : color,
), ),
), ),
), ),

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
chcp 65001 >nul chcp 65001 >nul
title HealthManager title HealthManager
set "PATH=D:\PostgreSQL\18\pgsql\bin;%PATH%" set "PATH=D:\PostgreSQL\18\pgsql\bin;D:\Android\platform-tools;%PATH%"
echo ======================================== echo ========================================
echo HealthManager - Start Services echo HealthManager - Start Services
@@ -14,11 +14,16 @@ echo [1/2] Starting PostgreSQL...
if errorlevel 1 (echo PG already running) else (echo PG started) if errorlevel 1 (echo PG already running) else (echo PG started)
echo. echo.
echo [2/2] Starting .NET Backend (http://192.168.1.29:5000)... echo [2/3] Starting .NET Backend...
start "Backend" cmd /k "cd /d D:\health_project\backend\src\Health.WebApi && dotnet run" start "Backend" cmd /k "cd /d D:\health_project\backend\src\Health.WebApi && dotnet run"
echo Waiting for backend... echo Waiting for backend...
timeout /t 8 /nobreak >nul timeout /t 8 /nobreak >nul
echo. echo.
echo All services started. Open the app on your phone. echo [3/3] Setup phone connection...
adb reverse tcp:5000 tcp:5000 2>nul
if errorlevel 1 (echo USB not connected, skip) else (echo Phone -> PC forwarding OK)
echo.
echo All done. Open the app on your phone.
pause pause