import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/app_colors.dart'; import '../../core/navigation_provider.dart'; import '../../providers/omron_device_provider.dart'; class DeviceManagementPage extends ConsumerStatefulWidget { const DeviceManagementPage({super.key}); @override ConsumerState createState() => _DeviceManagementPageState(); } class _DeviceManagementPageState extends ConsumerState { bool _reconnecting = false; OverlayEntry? _toast; @override void dispose() { _hideToast(); super.dispose(); } void _showToast(String msg, {bool success = false}) { _hideToast(); _toast = OverlayEntry( builder: (_) => Positioned( top: MediaQuery.of(context).padding.top + 8, left: 16, right: 16, child: TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), duration: const Duration(milliseconds: 250), builder: (context, v, child) => Opacity( opacity: v, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: success ? AppColors.success : AppColors.error, borderRadius: BorderRadius.circular(24), boxShadow: AppColors.cardShadow, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( success ? Icons.check_circle : Icons.info_outline, color: Colors.white, size: 18, ), const SizedBox(width: 8), Expanded( child: Text( msg, style: const TextStyle(color: Colors.white, fontSize: 14), ), ), ], ), ), ), ), ), ); Overlay.of(context).insert(_toast!); Future.delayed(const Duration(seconds: 2), _hideToast); } void _hideToast() { _toast?.remove(); _toast = null; } Future _reconnect(String mac) async { if (_reconnecting) return; setState(() => _reconnecting = true); final ok = await ref.read(omronBleServiceProvider).reconnectByMac(mac); if (!mounted) return; setState(() => _reconnecting = false); if (ok) { _showToast('设备已连接', success: true); } else { _showToast('设备未在线,请确认血压计已开机并处于通信模式'); } } @override Widget build(BuildContext context) { final device = ref.watch(omronDeviceProvider); return Scaffold( backgroundColor: AppColors.background, appBar: AppBar( title: const Text('蓝牙设备'), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => popRoute(ref), ), actions: [ IconButton( icon: const Icon(Icons.add, color: AppColors.primary), onPressed: () => pushRoute(ref, 'deviceScan'), tooltip: '添加设备', ), ], ), body: device.isBound ? _buildDeviceList(device) : _buildEmpty(), ); } Widget _buildEmpty() => Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 82, height: 82, decoration: BoxDecoration( color: AppColors.iconBg, borderRadius: BorderRadius.circular(24), border: Border.all(color: AppColors.border), ), child: const Icon( Icons.bluetooth_disabled, size: 40, color: AppColors.primary, ), ), const SizedBox(height: 16), const Text( '暂无设备', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w800, color: AppColors.textPrimary, ), ), const SizedBox(height: 6), const Text( '点击右上角 + 添加血压计', style: TextStyle(fontSize: 14, color: AppColors.textSecondary), ), ], ), ), ); Widget _buildDeviceList(DeviceBindState d) => ListView( padding: const EdgeInsets.all(16), children: [ GestureDetector( onTap: d.isConnected ? null : () => _reconnect(d.mac ?? ''), child: AnimatedContainer( duration: const Duration(milliseconds: 250), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(18), border: Border.all( color: d.isConnected ? AppColors.success : AppColors.border, ), boxShadow: AppColors.cardShadowLight, ), child: Row( children: [ Container( width: 50, height: 50, decoration: BoxDecoration( color: d.isConnected ? AppColors.successLight : AppColors.cardInner, borderRadius: BorderRadius.circular(15), ), child: Icon( Icons.bluetooth, size: 24, color: d.isConnected ? AppColors.success : AppColors.textHint, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( d.name ?? '血压计', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w800, color: AppColors.textPrimary, ), ), const SizedBox(height: 2), Text( d.mac ?? '', style: const TextStyle( fontSize: 12, color: AppColors.textHint, ), ), if (d.lastSync != null) Text( '上次同步: ${d.lastSync}', style: const TextStyle( fontSize: 11, color: AppColors.textHint, ), ), ], ), ), if (_reconnecting) const SizedBox( width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2), ) else ...[ Container( width: 8, height: 8, decoration: BoxDecoration( color: d.isConnected ? AppColors.success : AppColors.textHint, shape: BoxShape.circle, ), ), const SizedBox(width: 8), Text( d.isConnected ? '已连接' : '未连接', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: d.isConnected ? AppColors.success : AppColors.textHint, ), ), ], ], ), ), ), if (d.lastReading != null) ...[ const SizedBox(height: 12), Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(18), border: Border.all(color: AppColors.border), boxShadow: AppColors.cardShadowLight, ), child: Row( children: [ const Text( '最近测量', style: TextStyle(fontSize: 14, color: AppColors.textSecondary), ), const Spacer(), Text( d.lastReading!.display, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.w900, color: AppColors.textPrimary, ), ), const SizedBox(width: 4), const Text( 'mmHg', style: TextStyle(fontSize: 12, color: AppColors.textHint), ), if (d.lastReading!.pulse != null) ...[ const SizedBox(width: 16), Text( '${d.lastReading!.pulse} bpm', style: const TextStyle( fontSize: 14, color: AppColors.primary, fontWeight: FontWeight.w700, ), ), ], ], ), ), ], const SizedBox(height: 16), SizedBox( width: double.infinity, child: OutlinedButton( onPressed: () async { final ok = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('解绑设备'), content: const Text('解绑后需要重新扫描连接,确定吗?'), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('取消'), ), TextButton( onPressed: () => Navigator.pop(ctx, true), style: TextButton.styleFrom( foregroundColor: AppColors.error, ), child: const Text('确定'), ), ], ), ); if (ok == true) { await ref.read(omronDeviceProvider.notifier).unbind(); } }, style: OutlinedButton.styleFrom( foregroundColor: AppColors.error, side: const BorderSide(color: AppColors.errorLight), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), padding: const EdgeInsets.symmetric(vertical: 12), ), child: const Text('解绑设备'), ), ), const SizedBox(height: 16), Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(18), border: Border.all(color: AppColors.border), boxShadow: AppColors.cardShadowLight, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '使用说明', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w800, color: AppColors.textPrimary, ), ), const SizedBox(height: 8), _tip('1', '血压计装好电池,绑好袖带'), _tip('2', '按开始键测量,结束后设备自动进入通信模式'), _tip('3', '点击设备卡片即可自动连接并同步数据'), ], ), ), ], ); Widget _tip(String n, String t) => Padding( padding: const EdgeInsets.only(bottom: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '$n.', style: const TextStyle( fontSize: 13, color: AppColors.primary, fontWeight: FontWeight.w800, ), ), const SizedBox(width: 4), Expanded( child: Text( t, style: const TextStyle( fontSize: 13, color: AppColors.textSecondary, ), ), ), ], ), ); }