import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../core/app_colors.dart'; import '../core/navigation_provider.dart'; import '../providers/auth_provider.dart'; import '../providers/data_providers.dart'; import 'service_package_card.dart'; class HealthDrawer extends ConsumerWidget { const HealthDrawer({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final auth = ref.watch(authProvider); final user = auth.user; final latestHealth = ref.watch(latestHealthProvider); return Drawer( width: MediaQuery.of(context).size.width * 0.88, backgroundColor: Colors.transparent, elevation: 0, child: Container( decoration: const BoxDecoration(gradient: AppColors.drawerGradient), child: SafeArea( child: CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ SliverPadding( padding: const EdgeInsets.fromLTRB(16, 14, 16, 22), sliver: SliverList.list( children: [ _AccountHero(user: user, ref: ref), const SizedBox(height: 14), _PrimaryActions(ref: ref), const SizedBox(height: 14), _HealthDashboard(latestHealth: latestHealth, ref: ref), const SizedBox(height: 14), _NavigationSection(ref: ref), const SizedBox(height: 14), const _ServiceSection(), ], ), ), ], ), ), ), ); } } class _AccountHero extends StatelessWidget { final dynamic user; final WidgetRef ref; const _AccountHero({required this.user, required this.ref}); @override Widget build(BuildContext context) { final name = (user?.name?.toString().isNotEmpty ?? false) ? user.name.toString() : '未设置昵称'; final phone = (user?.phone?.toString().isNotEmpty ?? false) ? user.phone.toString() : '未登录'; return Container( padding: const EdgeInsets.fromLTRB(16, 16, 12, 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(28), border: Border.all(color: Colors.white, width: 1.8), boxShadow: [ BoxShadow( color: const Color(0xFF6366F1).withValues(alpha: 0.12), blurRadius: 30, offset: const Offset(0, 16), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ GestureDetector( onTap: () => pushRoute(ref, 'profile'), child: Container( width: 62, height: 62, padding: const EdgeInsets.all(2), decoration: BoxDecoration( gradient: AppColors.actionOutlineGradient, borderRadius: BorderRadius.circular(22), ), child: Container( decoration: BoxDecoration( gradient: AppColors.primaryGradient, borderRadius: BorderRadius.circular(20), ), child: const Icon( Icons.person_outline_rounded, color: Colors.white, size: 31, ), ), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'AI 健康管家', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: AppColors.textHint, ), ), const SizedBox(height: 4), Text( name, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w900, color: AppColors.textPrimary, ), ), const SizedBox(height: 3), Text( phone, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textSecondary, ), ), ], ), ), _CircleIconButton( icon: Icons.settings_outlined, onTap: () => pushRoute(ref, 'settings'), ), ], ), const SizedBox(height: 14), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [Color(0xFFF7F4FF), Color(0xFFF2FBF9)], ), borderRadius: BorderRadius.circular(18), border: Border.all(color: AppColors.borderLight), ), child: Row( children: const [ Icon( Icons.verified_user_outlined, size: 18, color: AppColors.primaryDark, ), SizedBox(width: 8), Expanded( child: Text( '健康数据已开启隐私保护', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), ), ], ), ), ], ), ); } } class _PrimaryActions extends StatelessWidget { final WidgetRef ref; const _PrimaryActions({required this.ref}); @override Widget build(BuildContext context) { return Row( children: [ Expanded( child: _FeaturedAction( icon: Icons.bluetooth_connected_rounded, title: '蓝牙设备', subtitle: '连接血压计', gradient: AppColors.calmHealthGradient, onTap: () => pushRoute(ref, 'devices'), ), ), const SizedBox(width: 10), Expanded( child: _FeaturedAction( icon: Icons.folder_shared_outlined, title: '健康档案', subtitle: '资料与病史', gradient: AppColors.primaryGradient, onTap: () => pushRoute(ref, 'healthArchive'), ), ), ], ); } } class _FeaturedAction extends StatelessWidget { final IconData icon; final String title; final String subtitle; final LinearGradient gradient; final VoidCallback onTap; const _FeaturedAction({ required this.icon, required this.title, required this.subtitle, required this.gradient, required this.onTap, }); @override Widget build(BuildContext context) { return Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(22), child: Container( padding: const EdgeInsets.all(1.2), decoration: BoxDecoration( gradient: AppColors.actionOutlineGradient, borderRadius: BorderRadius.circular(22), boxShadow: AppColors.cardShadowLight, ), child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.96), borderRadius: BorderRadius.circular(20), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 42, height: 42, decoration: BoxDecoration( gradient: gradient, borderRadius: BorderRadius.circular(15), ), child: Icon(icon, color: Colors.white, size: 22), ), const SizedBox(height: 12), Text( title, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w900, color: AppColors.textPrimary, ), ), const SizedBox(height: 3), Text( subtitle, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: AppColors.textSecondary, ), ), ], ), ), ), ), ); } } class _HealthDashboard extends StatelessWidget { final AsyncValue> latestHealth; final WidgetRef ref; const _HealthDashboard({required this.latestHealth, required this.ref}); @override Widget build(BuildContext context) { return _SectionShell( title: '健康仪表盘', subtitle: '最近一次健康数据', icon: Icons.query_stats_rounded, trailing: _TextPillButton( label: '详情', onTap: () => pushRoute(ref, 'trend'), ), child: latestHealth.when( data: (data) => _DashboardBody(data: data, ref: ref), loading: () => const Padding( padding: EdgeInsets.all(24), child: Center(child: CircularProgressIndicator(strokeWidth: 2)), ), error: (_, __) => _DashboardBody(data: const {}, ref: ref), ), ); } } class _DashboardBody extends StatelessWidget { final Map data; final WidgetRef ref; const _DashboardBody({required this.data, required this.ref}); @override Widget build(BuildContext context) { final bp = _bpText(data['BloodPressure']); final metrics = [ _MetricInfo( icon: Icons.monitor_heart_outlined, label: '心率', value: _metricVal(data['HeartRate'], unit: 'bpm'), routeType: 'heart_rate', ), _MetricInfo( icon: Icons.water_drop_outlined, label: '血糖', value: _metricVal(data['Glucose'], unit: 'mmol/L'), routeType: 'glucose', ), _MetricInfo( icon: Icons.air_outlined, label: '血氧', value: _metricVal(data['SpO2'], unit: '%'), routeType: 'spo2', ), _MetricInfo( icon: Icons.monitor_weight_outlined, label: '体重', value: _metricVal(data['Weight'], unit: 'kg'), routeType: 'weight', ), ]; return Column( children: [ Material( color: Colors.transparent, child: InkWell( onTap: () => pushRoute(ref, 'trend', params: {'type': 'blood_pressure'}), borderRadius: BorderRadius.circular(22), child: Container( width: double.infinity, padding: const EdgeInsets.all(1.3), decoration: BoxDecoration( gradient: AppColors.actionOutlineGradient, borderRadius: BorderRadius.circular(22), ), child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), ), child: Row( children: [ Container( width: 50, height: 50, decoration: BoxDecoration( gradient: AppColors.roseVioletGradient, borderRadius: BorderRadius.circular(18), ), child: const Icon( Icons.bloodtype_outlined, color: Colors.white, size: 25, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '血压', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: AppColors.textSecondary, ), ), const SizedBox(height: 3), Text( bp, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.w900, color: AppColors.textPrimary, ), ), ], ), ), const Icon( Icons.arrow_forward_ios_rounded, size: 16, color: AppColors.textHint, ), ], ), ), ), ), ), const SizedBox(height: 10), LayoutBuilder( builder: (context, constraints) { final width = (constraints.maxWidth - 10) / 2; return Wrap( spacing: 10, runSpacing: 10, children: metrics .map( (m) => _MetricTile( info: m, width: width, onTap: () => pushRoute( ref, 'trend', params: {'type': m.routeType}, ), ), ) .toList(), ); }, ), ], ); } static String _bpText(dynamic bp) { if (bp is Map) { final systolic = bp['systolic'] ?? bp['value'] ?? '--'; final diastolic = bp['diastolic']; if (diastolic == null) return systolic.toString(); return '$systolic/$diastolic'; } return '--'; } static String _metricVal(dynamic val, {String unit = ''}) { if (val == null) return '--'; dynamic raw = val; if (val is Map) raw = val['value'] ?? val['val'] ?? val['data']; if (raw == null) return '--'; final text = raw is num ? raw.toStringAsFixed(raw is double ? 1 : 0) : raw.toString(); if (text.isEmpty || text == '--') return '--'; return unit.isEmpty ? text : '$text $unit'; } } class _MetricTile extends StatelessWidget { final _MetricInfo info; final double width; final VoidCallback onTap; const _MetricTile({ required this.info, required this.width, required this.onTap, }); @override Widget build(BuildContext context) { return SizedBox( width: width, child: Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(18), child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFFDFBFF), borderRadius: BorderRadius.circular(18), border: Border.all(color: AppColors.borderLight), ), child: Row( children: [ Container( width: 34, height: 34, decoration: BoxDecoration( color: AppColors.primarySoft, borderRadius: BorderRadius.circular(12), ), child: Icon( info.icon, size: 18, color: AppColors.primaryDark, ), ), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( info.value, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w900, color: AppColors.textPrimary, ), ), const SizedBox(height: 2), Text( info.label, style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: AppColors.textHint, ), ), ], ), ), ], ), ), ), ), ); } } class _NavigationSection extends StatelessWidget { final WidgetRef ref; const _NavigationSection({required this.ref}); @override Widget build(BuildContext context) { final items = [ _NavItem( icon: Icons.description_outlined, title: '报告管理', subtitle: '上传、查看和分析报告', route: 'reports', ), _NavItem( icon: Icons.medication_liquid_outlined, title: '用药管理', subtitle: '药品记录与服药打卡', route: 'medications', ), _NavItem( icon: Icons.restaurant_menu_outlined, title: '饮食记录', subtitle: '查看识别结果和历史饮食', route: 'dietRecords', ), _NavItem( icon: Icons.calendar_month_outlined, title: '健康日历', subtitle: '按日期查看健康记录', route: 'calendar', ), _NavItem( icon: Icons.event_note_outlined, title: '复查随访', subtitle: '随访计划和复查提醒', route: 'followups', ), _NavItem( icon: Icons.directions_run_outlined, title: '运动计划', subtitle: '计划、打卡和进度', route: 'exercisePlan', ), ]; return _SectionShell( title: '功能入口', subtitle: '常用能力集中管理', icon: Icons.apps_rounded, child: Column( children: [ for (int i = 0; i < items.length; i++) ...[ if (i > 0) const SizedBox(height: 8), _NavigationRow( item: items[i], onTap: () => pushRoute(ref, items[i].route), ), ], ], ), ); } } class _NavigationRow extends StatelessWidget { final _NavItem item; final VoidCallback onTap; const _NavigationRow({required this.item, required this.onTap}); @override Widget build(BuildContext context) { return Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(18), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 11), decoration: BoxDecoration( color: const Color(0xFFFDFBFF), borderRadius: BorderRadius.circular(18), border: Border.all(color: AppColors.borderLight), ), child: Row( children: [ Container( width: 38, height: 38, decoration: BoxDecoration( gradient: AppColors.actionOutlineGradient, borderRadius: BorderRadius.circular(14), ), child: Icon(item.icon, size: 20, color: Colors.white), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item.title, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w900, color: AppColors.textPrimary, ), ), const SizedBox(height: 2), Text( item.subtitle, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: AppColors.textSecondary, ), ), ], ), ), const Icon( Icons.arrow_forward_ios_rounded, size: 15, color: AppColors.textHint, ), ], ), ), ), ); } } class _ServiceSection extends StatelessWidget { const _ServiceSection(); @override Widget build(BuildContext context) { return _SectionShell( title: '服务权益', subtitle: '管理服务包和随访权益', icon: Icons.workspace_premium_outlined, child: const ServicePackageCard(compact: true), ); } } class _SectionShell extends StatelessWidget { final String title; final String subtitle; final IconData icon; final Widget child; final Widget? trailing; const _SectionShell({ required this.title, required this.subtitle, required this.icon, required this.child, this.trailing, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.96), borderRadius: BorderRadius.circular(26), border: Border.all(color: Colors.white, width: 1.5), boxShadow: AppColors.cardShadowLight, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 38, height: 38, decoration: BoxDecoration( gradient: AppColors.primaryGradient, borderRadius: BorderRadius.circular(14), ), child: Icon(icon, size: 20, color: Colors.white), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w900, color: AppColors.textPrimary, ), ), const SizedBox(height: 2), Text( subtitle, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: AppColors.textSecondary, ), ), ], ), ), if (trailing != null) trailing!, ], ), const SizedBox(height: 14), child, ], ), ); } } class _TextPillButton extends StatelessWidget { final String label; final VoidCallback onTap; const _TextPillButton({required this.label, required this.onTap}); @override Widget build(BuildContext context) { return Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(999), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7), decoration: BoxDecoration( color: AppColors.primarySoft, borderRadius: BorderRadius.circular(999), border: Border.all(color: AppColors.borderLight), ), child: Text( label, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w900, color: AppColors.primaryDark, ), ), ), ), ); } } class _CircleIconButton extends StatelessWidget { final IconData icon; final VoidCallback onTap; const _CircleIconButton({required this.icon, required this.onTap}); @override Widget build(BuildContext context) { return Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(18), child: Container( width: 40, height: 40, decoration: BoxDecoration( color: const Color(0xFFF7F4FF), borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.borderLight), ), child: Icon(icon, size: 21, color: AppColors.primaryDark), ), ), ); } } class _MetricInfo { final IconData icon; final String label; final String value; final String routeType; const _MetricInfo({ required this.icon, required this.label, required this.value, required this.routeType, }); } class _NavItem { final IconData icon; final String title; final String subtitle; final String route; const _NavItem({ required this.icon, required this.title, required this.subtitle, required this.route, }); }