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 'drawer_shell.dart'; class HealthDrawer extends ConsumerWidget { const HealthDrawer({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final auth = ref.watch(authProvider); final latestHealth = ref.watch(latestHealthProvider); return DrawerShell( widthFactor: 0.9, child: SafeArea( child: CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ SliverPadding( padding: const EdgeInsets.fromLTRB(18, 18, 18, 24), sliver: SliverList.list( children: [ _AccountHeader(user: auth.user, ref: ref), const SizedBox(height: 18), _HealthDashboard(latestHealth: latestHealth, ref: ref), const SizedBox(height: 18), _NavigationSection(ref: ref), ], ), ), ], ), ), ); } } class _AccountHeader extends StatelessWidget { final dynamic user; final WidgetRef ref; const _AccountHeader({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 Row( children: [ InkWell( onTap: () => pushRoute(ref, 'profile'), borderRadius: BorderRadius.circular(33), child: Container( width: 66, height: 66, decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, border: Border.all(color: Colors.white, width: 2), boxShadow: [ BoxShadow( color: const Color(0xFF7C5CFF).withValues(alpha: 0.16), blurRadius: 20, offset: const Offset(0, 10), ), ], ), child: const Icon( Icons.person_rounded, color: Color(0xFF94A3B8), size: 34, ), ), ), const SizedBox(width: 14), Expanded( child: InkWell( onTap: () => pushRoute(ref, 'profile'), borderRadius: BorderRadius.circular(14), child: Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( name, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 22, fontWeight: FontWeight.w900, color: AppColors.textPrimary, ), ), const SizedBox(height: 5), Text( phone, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textSecondary, ), ), ], ), ), ), ), _IconButton( icon: Icons.settings_rounded, onTap: () => pushRoute(ref, 'settings'), ), ], ); } } class _HealthDashboard extends StatelessWidget { final AsyncValue> latestHealth; final WidgetRef ref; const _HealthDashboard({required this.latestHealth, required this.ref}); @override Widget build(BuildContext context) { return _Panel( title: '健康仪表盘', trailing: _TextAction( label: '详情', light: true, onTap: () => pushRoute(ref, 'trend'), ), titleColor: Colors.white, backgroundGradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF60A5FA), Color(0xFF8B5CF6), Color(0xFFF472B6)], ), child: latestHealth.when( data: (data) => _DashboardMetrics(data: data, ref: ref), loading: () => const SizedBox( height: 118, child: Center(child: CircularProgressIndicator(strokeWidth: 2)), ), error: (error, stackTrace) => _DashboardMetrics(data: const {}, ref: ref), ), ); } } class _DashboardMetrics extends StatelessWidget { final Map data; final WidgetRef ref; const _DashboardMetrics({required this.data, required this.ref}); @override Widget build(BuildContext context) { final items = [ _MetricInfo( label: '血压', value: _bpText(data['BloodPressure']), unit: 'mmHg', routeType: 'blood_pressure', ), _MetricInfo( label: '心率', value: _metricVal(data['HeartRate']), unit: 'bpm', routeType: 'heart_rate', ), _MetricInfo( label: '血糖', value: _metricVal(data['Glucose']), unit: 'mmol/L', routeType: 'glucose', ), _MetricInfo( label: '血氧', value: _metricVal(data['SpO2']), unit: '%', routeType: 'spo2', ), ]; return Row( children: [ for (var i = 0; i < items.length; i++) ...[ Expanded( child: _MetricTile( info: items[i], onTap: () => pushRoute(ref, 'trend', params: {'type': items[i].routeType}), ), ), if (i != items.length - 1) const SizedBox(width: 6), ], ], ); } 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 VoidCallback onTap; const _MetricTile({required this.info, required this.onTap}); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(18), child: Container( height: 100, padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 10), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.22), borderRadius: BorderRadius.circular(18), border: Border.all(color: Colors.white.withValues(alpha: 0.40)), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( height: 32, child: FittedBox( fit: BoxFit.scaleDown, child: Text( info.value, maxLines: 1, textAlign: TextAlign.center, style: const TextStyle( fontSize: 26, fontWeight: FontWeight.w900, color: Colors.white, ), ), ), ), if (info.unit != null) Text( info.unit!, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xE0FFFFFF), ), ), const SizedBox(height: 2), Text( info.label, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w800, color: Colors.white, ), ), ], ), ), ); } } class _NavigationSection extends StatelessWidget { final WidgetRef ref; const _NavigationSection({required this.ref}); @override Widget build(BuildContext context) { final items = [ _NavItem( icon: Icons.folder_shared_rounded, title: '档案', route: 'healthArchive', colors: const [Color(0xFF93C5FD), Color(0xFF60A5FA)], ), _NavItem( icon: Icons.description_rounded, title: '报告', route: 'reports', colors: const [Color(0xFFBFDBFE), Color(0xFF93C5FD)], ), _NavItem( icon: Icons.medication_rounded, title: '用药', route: 'medications', colors: const [Color(0xFFDDD6FE), Color(0xFFC4B5FD)], ), _NavItem( icon: Icons.restaurant_rounded, title: '饮食', route: 'dietRecords', colors: const [Color(0xFFFBCFE8), Color(0xFFF472B6)], ), _NavItem( icon: Icons.calendar_month_rounded, title: '日历', route: 'calendar', colors: const [Color(0xFFC4B5FD), Color(0xFF8B5CF6)], ), _NavItem( icon: Icons.event_available_rounded, title: '随访', route: 'followups', colors: const [Color(0xFFFBCFE8), Color(0xFFF9A8D4)], ), _NavItem( icon: Icons.directions_run_rounded, title: '运动', route: 'exercisePlan', colors: const [Color(0xFFA5F3FC), Color(0xFF67E8F9)], ), _NavItem( icon: Icons.bluetooth_connected_rounded, title: '蓝牙设备', route: 'devices', colors: const [Color(0xFF60A5FA), Color(0xFFC4B5FD)], ), ]; return _Panel( title: '功能入口', backgroundGradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFAFFFFFF), Color(0xF5EFF6FF)], ), child: GridView.builder( itemCount: items.length, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisExtent: 98, mainAxisSpacing: 10, crossAxisSpacing: 10, ), itemBuilder: (context, index) { final item = items[index]; return _NavTile(item: item, onTap: () => pushRoute(ref, item.route)); }, ), ); } } class _NavTile extends StatelessWidget { final _NavItem item; final VoidCallback onTap; const _NavTile({required this.item, required this.onTap}); String get _title => switch (item.route) { 'healthArchive' => '健康档案', 'reports' => '报告管理', 'medications' => '用药管理', 'dietRecords' => '饮食记录', 'calendar' => '健康日历', 'followups' => '复查随访', 'exercisePlan' => '运动计划', 'devices' => '蓝牙设备', _ => item.title, }; List get _colors => switch (item.route) { 'healthArchive' => const [Color(0xFF93C5FD), Color(0xFF60A5FA)], 'reports' => const [Color(0xFF60A5FA), Color(0xFF2563EB)], 'medications' => const [Color(0xFFA78BFA), Color(0xFF7C3AED)], 'dietRecords' => const [Color(0xFFFBCFE8), Color(0xFFF472B6)], 'calendar' => const [Color(0xFFC4B5FD), Color(0xFF8B5CF6)], 'followups' => const [Color(0xFFF472B6), Color(0xFFDB2777)], 'exercisePlan' => const [Color(0xFF7DD3FC), Color(0xFF60A5FA)], 'devices' => const [Color(0xFF60A5FA), Color(0xFFC4B5FD)], _ => item.colors, }; @override Widget build(BuildContext context) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(20), child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), border: Border.all(color: AppColors.borderLight, width: 1.1), boxShadow: AppColors.cardShadowLight, ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 44, height: 44, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: _colors, ), borderRadius: BorderRadius.circular(16), ), child: Icon(item.icon, color: Colors.white, size: 24), ), const SizedBox(height: 9), Text( _title, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w900, color: AppColors.textPrimary, ), ), ], ), ), ); } } class _Panel extends StatelessWidget { final String title; final Widget child; final Widget? trailing; final LinearGradient? backgroundGradient; final Color titleColor; const _Panel({ required this.title, required this.child, this.trailing, this.backgroundGradient, this.titleColor = AppColors.textPrimary, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( gradient: backgroundGradient ?? LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Colors.white.withValues(alpha: 0.92), const Color(0xFFF8F4FF).withValues(alpha: 0.84), ], ), borderRadius: BorderRadius.circular(28), border: Border.all( color: Colors.white.withValues(alpha: 0.82), width: 1.2, ), boxShadow: [ BoxShadow( color: const Color(0xFF6D5DF6).withValues(alpha: 0.08), blurRadius: 22, offset: const Offset(0, 12), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( title, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w900, color: titleColor, ), ), ), ?trailing, ], ), const SizedBox(height: 12), child, ], ), ); } } class _TextAction extends StatelessWidget { final String label; final VoidCallback onTap; final bool light; const _TextAction({ required this.label, required this.onTap, this.light = false, }); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(999), child: Container( padding: const EdgeInsets.symmetric(horizontal: 11, vertical: 6), decoration: BoxDecoration( color: light ? Colors.white.withValues(alpha: 0.24) : const Color(0xFFEDE9FE), borderRadius: BorderRadius.circular(999), border: light ? Border.all(color: Colors.white.withValues(alpha: 0.34)) : null, ), child: Text( label, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w900, color: light ? Colors.white : Color(0xFF6D28D9), ), ), ), ); } } class _IconButton extends StatelessWidget { final IconData icon; final VoidCallback onTap; const _IconButton({required this.icon, required this.onTap}); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(18), child: Container( width: 42, height: 42, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.84), borderRadius: BorderRadius.circular(18), border: Border.all(color: Colors.white, width: 1.2), ), child: Icon(icon, size: 22, color: const Color(0xFF475569)), ), ); } } class _MetricInfo { final String label; final String value; final String? unit; final String routeType; const _MetricInfo({ required this.label, required this.value, this.unit, required this.routeType, }); } class _NavItem { final IconData icon; final String title; final String route; final List colors; const _NavItem({ required this.icon, required this.title, required this.route, required this.colors, }); }