fix: 欢迎卡片UI优化 + 蓝牙录入按钮 + 多处修复
- 欢迎卡片:去掉白框圆角错位、图片满宽、按钮去箭头改圆角居中 - 渐变actionOutlineGradient改紫蓝双色循环 - 新增健康Agent"蓝牙录入"按钮→蓝牙设备页 - "查看健康情况"改名"健康概览" - 饮食编辑框删除bug修复(控制器缓存) - 启动脚本加adb reverse自动转发 - 登录闪屏修复
This commit is contained in:
BIN
health_app/assets/branding/agent_consultation_card.png
Normal file
BIN
health_app/assets/branding/agent_consultation_card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
BIN
health_app/assets/branding/agent_diet_card.png
Normal file
BIN
health_app/assets/branding/agent_diet_card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
BIN
health_app/assets/branding/agent_exercise_card.png
Normal file
BIN
health_app/assets/branding/agent_exercise_card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
BIN
health_app/assets/branding/agent_health_card.png
Normal file
BIN
health_app/assets/branding/agent_health_card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
BIN
health_app/assets/branding/agent_medication_card.png
Normal file
BIN
health_app/assets/branding/agent_medication_card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
BIN
health_app/assets/branding/agent_report_card.png
Normal file
BIN
health_app/assets/branding/agent_report_card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
@@ -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 {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,106 +244,104 @@ 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(
|
gradient: AppColors.softGlassGradient,
|
||||||
color: Colors.white,
|
borderRadius: BorderRadius.circular(24),
|
||||||
borderRadius: BorderRadius.circular(24),
|
border: Border.all(color: AppColors.borderLight),
|
||||||
border: Border.all(color: AppColors.border),
|
boxShadow: AppColors.cardShadow,
|
||||||
boxShadow: AppColors.cardShadow,
|
),
|
||||||
),
|
child: Column(
|
||||||
child: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
_BrandMark(),
|
||||||
_BrandMark(),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
const Text(
|
||||||
const Text(
|
'健康管家',
|
||||||
'健康管家',
|
style: TextStyle(
|
||||||
style: TextStyle(
|
fontSize: 26,
|
||||||
fontSize: 26,
|
fontWeight: FontWeight.w900,
|
||||||
fontWeight: FontWeight.w900,
|
color: AppColors.textPrimary,
|
||||||
color: AppColors.textPrimary,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
),
|
||||||
Text(
|
const SizedBox(height: 6),
|
||||||
_isLogin ? '登录你的健康账户' : '创建新的健康账户',
|
Text(
|
||||||
|
_isLogin ? '登录你的健康账户' : '创建新的健康账户',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: AppColors.textSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 26),
|
||||||
|
if (_successMsg != null)
|
||||||
|
_NoticeBanner(text: _successMsg!, success: true),
|
||||||
|
if (!_isLogin) ...[
|
||||||
|
_DoctorSelector(
|
||||||
|
name: _selectedDoctorName,
|
||||||
|
onTap: _showDoctorPicker,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_TextField(_nameCtrl, '姓名', TextInputType.name, 20),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
],
|
||||||
|
_TextField(_phoneCtrl, '手机号', TextInputType.phone, 11),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _TextField(
|
||||||
|
_codeCtrl,
|
||||||
|
'验证码',
|
||||||
|
TextInputType.number,
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
_SmsButton(
|
||||||
|
sending: _sending,
|
||||||
|
countdown: _countdown,
|
||||||
|
onTap: (_countdown > 0 || _sending) ? null : _sendSms,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
_Agreement(
|
||||||
|
agreed: _agreed,
|
||||||
|
onTap: () => setState(() => _agreed = !_agreed),
|
||||||
|
),
|
||||||
|
if (_error != null) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_NoticeBanner(text: _error!, success: false),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 22),
|
||||||
|
_PrimaryButton(
|
||||||
|
loading: _loading,
|
||||||
|
label: _isLogin ? '登录' : '注册',
|
||||||
|
onTap: _loading ? null : _submit,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => setState(() {
|
||||||
|
_isLogin = !_isLogin;
|
||||||
|
_error = null;
|
||||||
|
_successMsg = null;
|
||||||
|
}),
|
||||||
|
child: Text(
|
||||||
|
_isLogin ? '没有账号?去注册' : '已有账号?去登录',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: AppColors.textSecondary,
|
fontWeight: FontWeight.w700,
|
||||||
|
color: AppColors.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 26),
|
),
|
||||||
if (_successMsg != null)
|
],
|
||||||
_NoticeBanner(text: _successMsg!, success: true),
|
|
||||||
if (!_isLogin) ...[
|
|
||||||
_DoctorSelector(
|
|
||||||
name: _selectedDoctorName,
|
|
||||||
onTap: _showDoctorPicker,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
_TextField(_nameCtrl, '姓名', TextInputType.name, 20),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
],
|
|
||||||
_TextField(_phoneCtrl, '手机号', TextInputType.phone, 11),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _TextField(
|
|
||||||
_codeCtrl,
|
|
||||||
'验证码',
|
|
||||||
TextInputType.number,
|
|
||||||
6,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
_SmsButton(
|
|
||||||
sending: _sending,
|
|
||||||
countdown: _countdown,
|
|
||||||
onTap: (_countdown > 0 || _sending) ? null : _sendSms,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 14),
|
|
||||||
_Agreement(
|
|
||||||
agreed: _agreed,
|
|
||||||
onTap: () => setState(() => _agreed = !_agreed),
|
|
||||||
),
|
|
||||||
if (_error != null) ...[
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
_NoticeBanner(text: _error!, success: false),
|
|
||||||
],
|
|
||||||
const SizedBox(height: 22),
|
|
||||||
_PrimaryButton(
|
|
||||||
loading: _loading,
|
|
||||||
label: _isLogin ? '登录' : '注册',
|
|
||||||
onTap: _loading ? null : _submit,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () => setState(() {
|
|
||||||
_isLogin = !_isLogin;
|
|
||||||
_error = null;
|
|
||||||
_successMsg = null;
|
|
||||||
}),
|
|
||||||
child: Text(
|
|
||||||
_isLogin ? '没有账号?去注册' : '已有账号?去登录',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: AppColors.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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(
|
||||||
'连接',
|
'连接',
|
||||||
|
|||||||
@@ -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,13 +351,22 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// 图片自适应显示
|
// 图片自适应显示
|
||||||
ClipRRect(
|
Container(
|
||||||
borderRadius: BorderRadius.circular(16),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Image.file(
|
decoration: BoxDecoration(
|
||||||
File(state.imagePath!),
|
color: Colors.white,
|
||||||
width: screenW - 32,
|
borderRadius: BorderRadius.circular(24),
|
||||||
height: 220,
|
border: Border.all(color: AppColors.borderLight),
|
||||||
fit: BoxFit.cover,
|
boxShadow: AppColors.cardShadowLight,
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(18),
|
||||||
|
child: Image.file(
|
||||||
|
File(state.imagePath!),
|
||||||
|
width: screenW - 48,
|
||||||
|
height: 220,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
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,28 +885,40 @@ 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,
|
||||||
try {
|
borderRadius: BorderRadius.circular(16),
|
||||||
await ref.read(dietProvider.notifier).saveRecord();
|
boxShadow: AppColors.buttonShadow,
|
||||||
if (mounted) popRoute(ref);
|
),
|
||||||
} catch (e) {
|
child: Material(
|
||||||
debugPrint('[Diet] 保存记录失败: $e');
|
color: Colors.transparent,
|
||||||
}
|
child: InkWell(
|
||||||
},
|
borderRadius: BorderRadius.circular(16),
|
||||||
icon: const Icon(Icons.check_circle_outline, size: 22),
|
onTap: () async {
|
||||||
label: const Text(
|
try {
|
||||||
'保存记录',
|
await ref.read(dietProvider.notifier).saveRecord();
|
||||||
style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600),
|
if (mounted) popRoute(ref);
|
||||||
),
|
} catch (e) {
|
||||||
style: ElevatedButton.styleFrom(
|
debugPrint('[Diet] 保存记录失败: $e');
|
||||||
backgroundColor: _dietAccent,
|
}
|
||||||
foregroundColor: Colors.white,
|
},
|
||||||
shape: RoundedRectangleBorder(
|
child: const Row(
|
||||||
borderRadius: BorderRadius.circular(14),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.check_circle_outline, size: 22, color: Colors.white),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'保存记录',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 17,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,21 +86,19 @@ 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),
|
Expanded(
|
||||||
Expanded(
|
child: ChatMessagesView(
|
||||||
child: ChatMessagesView(
|
scrollCtrl: _scrollCtrl,
|
||||||
scrollCtrl: _scrollCtrl,
|
messages: chatState.messages,
|
||||||
messages: chatState.messages,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
_buildBottomBar(context),
|
),
|
||||||
],
|
_buildBottomBar(context),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -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),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,40 +187,31 @@ 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(
|
Positioned(
|
||||||
right: -18,
|
right: 18,
|
||||||
top: -22,
|
top: 18,
|
||||||
child: _SignalHalo(colors: agentColors),
|
child: _MiniSignalStrip(colors: agentColors),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 18,
|
right: 18,
|
||||||
bottom: 14,
|
bottom: 18,
|
||||||
child: _MiniSignalStrip(colors: agentColors),
|
child: _LinearAccentMark(colors: agentColors),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
@@ -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,51 +303,24 @@ 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: [
|
children: [
|
||||||
Row(
|
for (int i = 0; i < actions.length; i++) ...[
|
||||||
children: [
|
if (i > 0) const SizedBox(width: 10),
|
||||||
Container(
|
Flexible(
|
||||||
width: 6,
|
child: _buildActionButton(
|
||||||
height: 18,
|
actions[i],
|
||||||
decoration: BoxDecoration(
|
context,
|
||||||
gradient: iconGradient,
|
ref,
|
||||||
borderRadius: BorderRadius.circular(3),
|
agentColors,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
),
|
||||||
Text(
|
],
|
||||||
'快捷操作',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: AppColors.textPrimary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
for (int i = 0; i < actions.length; i++) ...[
|
|
||||||
if (i > 0) const SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: _buildActionButton(
|
|
||||||
actions[i],
|
|
||||||
context,
|
|
||||||
ref,
|
|
||||||
agentColors,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -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),
|
|
||||||
border: Border.all(color: colors.accent.withValues(alpha: 0.18)),
|
|
||||||
boxShadow: AppColors.cardShadowLight,
|
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Container(
|
||||||
children: [
|
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 18),
|
||||||
Container(
|
decoration: BoxDecoration(
|
||||||
width: 40,
|
color: Colors.white,
|
||||||
height: 40,
|
borderRadius: BorderRadius.circular(26.5),
|
||||||
decoration: BoxDecoration(
|
),
|
||||||
color: colors.iconBg,
|
child: Row(
|
||||||
borderRadius: BorderRadius.circular(12),
|
mainAxisSize: MainAxisSize.min,
|
||||||
border: Border.all(
|
children: [
|
||||||
color: colors.accent.withValues(alpha: 0.14),
|
Icon(a.icon, size: 24, color: AppColors.textPrimary),
|
||||||
),
|
const SizedBox(width: 10),
|
||||||
),
|
Text(
|
||||||
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),
|
||||||
),
|
),
|
||||||
@@ -930,85 +774,92 @@ class ChatMessagesView extends ConsumerWidget {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 14, 20, 0),
|
padding: const EdgeInsets.fromLTRB(20, 14, 20, 0),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
padding: const EdgeInsets.all(1.4),
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.backgroundSecondary,
|
gradient: AppColors.actionOutlineGradient,
|
||||||
borderRadius: BorderRadius.circular(18),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(color: AppColors.border),
|
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Container(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
width: double.infinity,
|
||||||
children: [
|
padding: const EdgeInsets.all(20),
|
||||||
Container(
|
decoration: BoxDecoration(
|
||||||
width: 68,
|
color: Colors.white,
|
||||||
height: 68,
|
borderRadius: BorderRadius.circular(18),
|
||||||
decoration: BoxDecoration(
|
),
|
||||||
gradient: AppColors.lightGradient,
|
child: Row(
|
||||||
borderRadius: BorderRadius.circular(18),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 68,
|
||||||
|
height: 68,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: AppColors.lightGradient,
|
||||||
|
borderRadius: BorderRadius.circular(18),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: isHealth
|
||||||
|
? Icon(
|
||||||
|
_getMetricIconData(backendType),
|
||||||
|
size: 34,
|
||||||
|
color: AppColors.primaryDark,
|
||||||
|
)
|
||||||
|
: Icon(
|
||||||
|
isMedication
|
||||||
|
? Icons.medication_liquid_outlined
|
||||||
|
: Icons.directions_run_outlined,
|
||||||
|
size: 36,
|
||||||
|
color: AppColors.iconColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Center(
|
const SizedBox(width: 16),
|
||||||
child: isHealth
|
Expanded(
|
||||||
? Text(
|
child: Column(
|
||||||
_getMetricIcon(backendType),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style: const TextStyle(fontSize: 36),
|
children: [
|
||||||
)
|
Text(
|
||||||
: Icon(
|
mainLabel,
|
||||||
isMedication
|
style: const TextStyle(
|
||||||
? Icons.medication_liquid_outlined
|
fontSize: 17,
|
||||||
: Icons.directions_run_outlined,
|
color: AppColors.textSecondary,
|
||||||
size: 36,
|
|
||||||
color: AppColors.iconColor,
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
mainLabel,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 17,
|
|
||||||
color: AppColors.textSecondary,
|
|
||||||
),
|
),
|
||||||
),
|
if (mainValue.isNotEmpty) ...[
|
||||||
if (mainValue.isNotEmpty) ...[
|
const SizedBox(height: 8),
|
||||||
const SizedBox(height: 8),
|
RichText(
|
||||||
RichText(
|
text: TextSpan(
|
||||||
text: TextSpan(
|
children: [
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: mainValue,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 36,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
color: abnormal
|
|
||||||
? AppColors.error
|
|
||||||
: AppColors.textPrimary,
|
|
||||||
letterSpacing: -0.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (mainUnit.isNotEmpty)
|
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: ' $mainUnit',
|
text: mainValue,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 19,
|
fontSize: 36,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
color: abnormal
|
color: abnormal
|
||||||
? AppColors.error
|
? AppColors.error
|
||||||
: AppColors.iconColor,
|
: AppColors.textPrimary,
|
||||||
fontWeight: FontWeight.w500,
|
letterSpacing: -0.5,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
if (mainUnit.isNotEmpty)
|
||||||
|
TextSpan(
|
||||||
|
text: ' $mainUnit',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 19,
|
||||||
|
color: abnormal
|
||||||
|
? AppColors.error
|
||||||
|
: AppColors.iconColor,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -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,
|
||||||
boxShadow: AppColors.cardShadow,
|
offset: const Offset(0, 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
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,33 +995,39 @@ 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: Row(
|
child: Container(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
decoration: BoxDecoration(
|
||||||
children: [
|
color: Colors.white,
|
||||||
Container(
|
borderRadius: BorderRadius.circular(18),
|
||||||
width: 32,
|
),
|
||||||
height: 32,
|
child: Row(
|
||||||
decoration: BoxDecoration(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
gradient: AppColors.purpleBlueGradient,
|
children: [
|
||||||
borderRadius: BorderRadius.circular(8),
|
Container(
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: AppColors.actionOutlineGradient,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.check_rounded,
|
||||||
|
size: 19,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: const Icon(
|
const SizedBox(width: 10),
|
||||||
Icons.check,
|
const Text(
|
||||||
size: 18,
|
'确认录入',
|
||||||
color: Colors.white,
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color: AppColors.textPrimary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
const SizedBox(width: 10),
|
),
|
||||||
Text(
|
|
||||||
'确认录入',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: AppColors.textPrimary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -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),
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,15 +50,19 @@ 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,
|
||||||
activeAgent: activeAgent ?? this.activeAgent,
|
String? conversationId,
|
||||||
messages: messages ?? this.messages,
|
bool? isStreaming,
|
||||||
conversationId: conversationId ?? this.conversationId,
|
String? thinkingText,
|
||||||
isStreaming: isStreaming ?? this.isStreaming,
|
}) => ChatState(
|
||||||
thinkingText: thinkingText ?? this.thinkingText,
|
activeAgent: activeAgent ?? this.activeAgent,
|
||||||
);
|
messages: messages ?? this.messages,
|
||||||
|
conversationId: conversationId ?? this.conversationId,
|
||||||
|
isStreaming: isStreaming ?? this.isStreaming,
|
||||||
|
thinkingText: thinkingText ?? this.thinkingText,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SelectedAgentNotifier extends Notifier<ActiveAgent?> {
|
class SelectedAgentNotifier extends Notifier<ActiveAgent?> {
|
||||||
@@ -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(
|
||||||
id: 'task_card',
|
messages: [
|
||||||
role: 'assistant',
|
ChatMessage(
|
||||||
content: '',
|
id: 'task_card',
|
||||||
createdAt: DateTime.now(),
|
role: 'assistant',
|
||||||
type: MessageType.taskCard,
|
content: '',
|
||||||
), ...state.messages]);
|
createdAt: DateTime.now(),
|
||||||
|
type: MessageType.taskCard,
|
||||||
|
),
|
||||||
|
...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(
|
||||||
id: 'welcome_${agent.name}_${DateTime.now().millisecondsSinceEpoch}',
|
messages: [
|
||||||
role: 'assistant',
|
...state.messages,
|
||||||
content: '',
|
ChatMessage(
|
||||||
createdAt: DateTime.now(),
|
id: 'welcome_${agent.name}_${DateTime.now().millisecondsSinceEpoch}',
|
||||||
type: MessageType.agentWelcome,
|
role: 'assistant',
|
||||||
metadata: {'agent': agent.name},
|
content: '',
|
||||||
)]);
|
createdAt: DateTime.now(),
|
||||||
|
type: MessageType.agentWelcome,
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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],
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(13),
|
||||||
),
|
),
|
||||||
child: Icon(icon, size: 21, color: AppColors.primary),
|
child: Icon(icon, size: 20, color: AppColors.primaryDark),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 14),
|
const SizedBox(width: 14),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,31 +141,53 @@ 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: const Align(
|
child: Container(
|
||||||
alignment: Alignment.centerRight,
|
decoration: BoxDecoration(
|
||||||
child: Padding(padding: EdgeInsets.only(right: 20), child: Icon(Icons.delete_outline, color: AppColors.textOnGradient, size: 28)),
|
color: AppColors.error,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: const Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(right: 20),
|
||||||
|
child: Icon(
|
||||||
|
Icons.delete_outline,
|
||||||
|
color: AppColors.textOnGradient,
|
||||||
|
size: 28,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)),
|
GestureDetector(
|
||||||
GestureDetector(
|
behavior: isSwiped
|
||||||
behavior: isSwiped ? HitTestBehavior.opaque : HitTestBehavior.deferToChild,
|
? HitTestBehavior.opaque
|
||||||
onTap: isSwiped ? () { widget.onDelete(); setState(() => _dx = 0); } : widget.onTap,
|
: HitTestBehavior.deferToChild,
|
||||||
onHorizontalDragUpdate: _onDragUpdate,
|
onTap: isSwiped
|
||||||
onHorizontalDragEnd: _onDragEnd,
|
? () {
|
||||||
child: Transform.translate(
|
widget.onDelete();
|
||||||
offset: Offset(_dx, 0),
|
setState(() => _dx = 0);
|
||||||
child: isSwiped
|
}
|
||||||
? AbsorbPointer(absorbing: true, child: widget.child)
|
: widget.onTap,
|
||||||
: widget.child,
|
onHorizontalDragUpdate: _onDragUpdate,
|
||||||
|
onHorizontalDragEnd: _onDragEnd,
|
||||||
|
child: Transform.translate(
|
||||||
|
offset: Offset(_dx, 0),
|
||||||
|
child: isSwiped
|
||||||
|
? AbsorbPointer(absorbing: true, child: 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,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user