import 'dart:async'; import 'dart:developer' as dev; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:device_info_plus/device_info_plus.dart'; // 检查这些导入是否正确 import '../models/device_matching.dart'; // ✅ 存在 import '../models/saf_permission_state.dart'; // ✅ 存在 import '../services/database_service.dart'; import '../services/battery_data_service.dart'; import '../services/saf_storage_service.dart'; // ✅ 存在 import '../logic/charge_parser.dart'; // ✅ 存在 import '../logic/capacity_extractor.dart'; // ✅ 存在(注意:不是 utils/) import '../utils/time_utils.dart'; // ✅ 存在 import '../utils/charge_utils.dart'; // ✅ 存在 import '../widgets/trend_chart.dart'; // ⚠️ 需要确认文件内容完整 import '../widgets/battery_summary.dart'; // ⚠️ 需要确认文件内容完整 import '../widgets/battery_info_panel.dart'; // ⚠️ 需要确认文件内容完整 import '../widgets/charge_entry.dart'; // ⚠️ 需要确认文件内容完整 import '../widgets/charge_detail_page.dart'; // ✅ 存在 // --- 潘通色定义 --- const Color kHealthGreen = Color(0xFF00B140); const Color kHealthYellow = Color(0xFFEFDF00); const Color kHealthOrange = Color(0xFFD75A16); class BatteryHealthPage extends StatefulWidget { const BatteryHealthPage({super.key}); @override State createState() => _BatteryHealthPageState(); } class _BatteryHealthPageState extends State with SingleTickerProviderStateMixin { // ---------- 数据 ---------- double _currentCap = 0; int _cycleCount = 0; double _batteryTemp = 0; double _batteryVolt = 0; int _designCap = 5050; String _deviceModel = "魅族21 Pro (默认)"; double _maxTemp = 0; double _minTemp = 0; double _maxVolt = 0; double _minVolt = 0; bool _hasData = false; // ---------- 趋势图数据 ---------- List> _trendData = []; // ---------- 充电状态 ---------- bool _isCharging = false; String _chargeType = ''; double _chargePower = 0.0; double _chargeVolt = 0.0; double _chargeCurr = 0.0; int? _wiredLevel; bool _isWireless = false; DateTime? _chargingStartTime; // ---------- SAF 状态 ---------- final SafStorageService _safService = SafStorageService(); final DatabaseService _dbService = DatabaseService(); final BatteryDataService _dataService = BatteryDataService(); SafPermissionState _safState = SafPermissionState.unauthorized; // 动画控制 late AnimationController _chargeAnimationController; late Animation _bottomSlide; late Animation _entryFadeIn; // 平台通道 static const _methodChannel = MethodChannel('com.lxh.battery_health/charge'); static const _eventChannel = EventChannel( 'com.lxh.battery_health/charge_events', ); StreamSubscription? _chargeEventSubscription; // 轮询定时器(15秒) Timer? _pollTimer; // ---------- 生命周期 ---------- @override void initState() { super.initState(); dev.log('🔵 ========== initState 开始 ==========', name: 'BatteryHealth'); _initAnimations(); _getInitialChargeState(); _chargeEventSubscription = _eventChannel.receiveBroadcastStream().listen(( event, ) { final bool isCharging = event['isCharging'] ?? false; final String systemType = event['chargeType'] ?? 'NONE'; _applySystemChargeState(isCharging, systemType); }); _pollTimer = Timer.periodic(const Duration(seconds: 15), (timer) { if (_isCharging) { _fetchChargeDataFromLog(); } }); WidgetsBinding.instance.addPostFrameCallback((_) async { dev.log('🔵 initState -> addPostFrameCallback 开始', name: 'BatteryHealth'); await _safService.init(); dev.log( '🔵 _safService.init() 完成,cachedUri: ${_safService.cachedUri}', name: 'BatteryHealth', ); _safState = await _safService.getPermissionState(); dev.log('🔵 _safState: $_safState', name: 'BatteryHealth'); if (_safState == SafPermissionState.unauthorized || _safState == SafPermissionState.expired) { dev.log('🔵 需要授权,弹窗', name: 'BatteryHealth'); if (mounted) _showSafAuthorizationDialog(); } else { dev.log('🔵 已有授权,调用 readBatteryCapacity', name: 'BatteryHealth'); readBatteryCapacity(); } }); dev.log('🔵 ========== initState 结束 ==========', name: 'BatteryHealth'); } @override void dispose() { _chargeEventSubscription?.cancel(); _pollTimer?.cancel(); _chargeAnimationController.dispose(); _dbService.database.then((db) => db.close()).ignore(); super.dispose(); } // ---------- 动画初始化 ---------- void _initAnimations() { _chargeAnimationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 500), ); _bottomSlide = Tween(begin: 0, end: 24).animate( CurvedAnimation( parent: _chargeAnimationController, curve: Curves.easeInOutCubic, ), ); _entryFadeIn = Tween(begin: 0, end: 1).animate( CurvedAnimation( parent: _chargeAnimationController, curve: const Interval(0.0, 0.5, curve: Curves.easeIn), ), ); } // ---------- 设备信息 ---------- Future _getDeviceInfo() async { dev.log('🔵 _getDeviceInfo 开始', name: 'BatteryHealth'); final deviceInfo = DeviceInfoPlugin(); try { final androidInfo = await deviceInfo.androidInfo; final model = androidInfo.model; dev.log('🔵 设备型号: $model', name: 'BatteryHealth'); setState(() { _deviceModel = DeviceMatching.getModelName(model); _designCap = DeviceMatching.getDesignCapacity(model); }); } catch (e) { dev.log('🔵 获取设备信息失败: $e', name: 'BatteryHealth'); setState(() { _deviceModel = '魅族21 Pro (默认)'; _designCap = 5050; }); } } // ---------- SAF 授权引导 ---------- void _showSafAuthorizationDialog() { dev.log('🔵 _showSafAuthorizationDialog 被调用', name: 'BatteryHealth'); if (!mounted) return; showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: const Text('需要文件夹访问权限'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: const [ Text('请授权访问日志文件夹,以读取电池数据。'), SizedBox(height: 8), Text( '目标路径:\n/storage/emulated/0/mbattery_charger/', style: TextStyle(fontSize: 12, color: Colors.grey), ), SizedBox(height: 8), Text('点击"选择文件夹"后,请在系统文件管理器中找到并选中上述文件夹。'), ], ), actions: [ TextButton( onPressed: () { dev.log('🔵 用户取消授权', name: 'BatteryHealth'); Navigator.pop(context, 'cancel'); setState(() { _safState = SafPermissionState.denied; }); }, child: const Text('取消'), ), ElevatedButton( onPressed: () { dev.log('🔵 用户点击选择文件夹', name: 'BatteryHealth'); Navigator.pop(context, 'select'); }, child: const Text('选择文件夹'), ), ], ), ).then((result) async { dev.log('🔵 对话框关闭,result: $result', name: 'BatteryHealth'); if (result == 'select') { dev.log('🔵 开始请求授权', name: 'BatteryHealth'); final authResult = await _safService.requestPermission(); dev.log( '🔵 授权结果: success=${authResult.success}, state=${authResult.state}', name: 'BatteryHealth', ); if (!mounted) return; if (authResult.success) { dev.log('🔵 授权成功,调用 readBatteryCapacity', name: 'BatteryHealth'); setState(() { _safState = SafPermissionState.authorized; }); readBatteryCapacity(); } else { dev.log('🔵 授权失败: ${authResult.errorMessage}', name: 'BatteryHealth'); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(authResult.errorMessage ?? '授权失败,请重试'), backgroundColor: Colors.red, ), ); _showSafAuthorizationDialog(); } } }); } // ---------- 核心:系统API定调 ---------- void _applySystemChargeState(bool isCharging, String systemChargeType) { dev.log( '[_applySystemChargeState] isCharging: $isCharging, systemChargeType: $systemChargeType', name: 'BatteryHealth', ); setState(() { _isCharging = isCharging; if (!isCharging) { dev.log('[_applySystemChargeState] 充电断开,重置状态', name: 'BatteryHealth'); _chargeType = ''; _isWireless = false; _chargePower = 0.0; _chargeVolt = 0.0; _chargeCurr = 0.0; _wiredLevel = null; _chargingStartTime = null; _updateChargeState(); return; } if (_chargingStartTime == null) { _chargingStartTime = DateTime.now().toUtc(); dev.log( '[_applySystemChargeState] 充电开始时间: $_chargingStartTime', name: 'BatteryHealth', ); } switch (systemChargeType) { case "WIRELESS": _isWireless = true; _chargeType = "无线"; _wiredLevel = null; break; case "AC": _isWireless = false; _chargeType = "PPS"; break; case "USB": _isWireless = false; _chargeType = "SDP"; break; default: _isWireless = false; _chargeType = ''; break; } _chargePower = 0.0; _chargeVolt = 0.0; _chargeCurr = 0.0; _updateChargeState(); }); if (_isCharging) { _fetchChargeDataFromLog(); } } // ---------- 读取当前数据 ---------- Future readBatteryCapacity() async { dev.log('🔴🔴🔴 readBatteryCapacity 被调用了 🔴🔴🔴', name: 'BatteryHealth'); _safState = await _safService.getPermissionState(); dev.log('🔵 SAF状态: $_safState', name: 'BatteryHealth'); if (_safState != SafPermissionState.authorized) { dev.log('🔵 未授权,弹窗', name: 'BatteryHealth'); if (mounted) _showSafAuthorizationDialog(); return; } await _getDeviceInfo(); await _dataService.scanAndUpdateDB(); final fileName = TimeUtils.getTodayFileName(); dev.log('🔵 今日文件名: $fileName', name: 'BatteryHealth'); final exists = await _safService.logFileExists(fileName); dev.log('🔵 文件存在: $exists', name: 'BatteryHealth'); if (!exists) { dev.log('🔵 文件不存在,尝试从数据库读取', name: 'BatteryHealth'); final recent = await _dbService.queryRecentData(1); if (recent.isNotEmpty) { dev.log('🔵 数据库有数据: ${recent.first}', name: 'BatteryHealth'); setState(() { _currentCap = recent.first['capacity'].toDouble(); _maxVolt = recent.first['volt_max'] ?? 0; _minVolt = recent.first['volt_min'] ?? 0; // 🟢 数据库目前不存温度,后续升级可扩展,暂时保留为 0 _maxTemp = 0; _minTemp = 0; _cycleCount = 0; _hasData = true; }); await _updateTrendChart(); } else { dev.log('🔵 数据库无数据', name: 'BatteryHealth'); setState(() => _hasData = false); } if (_isCharging) _fetchChargeDataFromLog(); _chargeAnimationController.reset(); _updateChargeState(); return; } try { final lines = await _safService.readLogFile(fileName); dev.log('🔵 读取到 ${lines.length} 行', name: 'BatteryHealth'); if (lines.isEmpty) { dev.log('🔵 文件为空', name: 'BatteryHealth'); setState(() => _hasData = false); if (_isCharging) _fetchChargeDataFromLog(); _chargeAnimationController.reset(); _updateChargeState(); return; } // 🟢 提取所有有效行(包含 tbat=) final validLines = lines.where((line) => line.contains('tbat=')).toList(); if (validLines.isEmpty) { dev.log('🔵 无有效数据行', name: 'BatteryHealth'); setState(() => _hasData = false); _chargeAnimationController.reset(); _updateChargeState(); return; } // 🟢 1.4.7 核心改动:字段提取与容量提取解耦 // 无论 cap 是否为 null,都提取常规字段 final lastLine = validLines.last; final cycle = _extractCycle(lastLine); final temp = _extractTemp(lastLine); final volt = _extractVolt(lastLine); final (maxTemp, minTemp) = _extractTempExtremes(validLines); final (maxVolt, minVolt) = _extractVoltExtremes(validLines); dev.log( '🔵 字段提取: cycle=$cycle, temp=$temp, volt=$volt', name: 'BatteryHealth', ); // 🟢 容量提取(可能为 null) final cap = CapacityExtractor.extractCapacity(lines); int? finalCap; if (cap != null) { // ✅ 今日有充满记录,直接使用 finalCap = cap; dev.log('🔵 从日志提取容量: $cap', name: 'BatteryHealth'); } else { // ⚠️ 今日无充满记录,尝试从数据库复制前一天容量 dev.log('🔵 今日无充满记录,尝试复制前一天容量', name: 'BatteryHealth'); final recent = await _dbService.queryRecentData(1); if (recent.isNotEmpty) { finalCap = recent.first['capacity'] as int; dev.log('🔵 复制前一天容量: $finalCap', name: 'BatteryHealth'); } else { finalCap = null; dev.log('🔵 无历史数据可复制', name: 'BatteryHealth'); } } // 🟢 一次性 setState 更新所有字段 setState(() { _currentCap = (finalCap ?? _currentCap).toDouble(); _cycleCount = cycle; _batteryTemp = temp; _batteryVolt = volt; _maxTemp = maxTemp; _minTemp = minTemp; _maxVolt = maxVolt; _minVolt = minVolt; _hasData = true; }); if (_isCharging) { final parseResult = ChargeParser.parse(lines, _chargingStartTime); _applyParseResult(parseResult); } await _updateTrendChart(); _chargeAnimationController.reset(); _updateChargeState(); dev.log( '🔵 ========== readBatteryCapacity 完成 ==========', name: 'BatteryHealth', ); } catch (e) { dev.log('🔵 readBatteryCapacity 异常: $e', name: 'BatteryHealth'); setState(() => _hasData = false); _chargeAnimationController.reset(); _updateChargeState(); } } // ---------- 辅助提取方法 ---------- int _extractCycle(String line) { final reg = RegExp(r'cycle=(\d+)'); final match = reg.firstMatch(line); return match != null ? int.parse(match.group(1)!) : 0; } double _extractTemp(String line) { final reg = RegExp(r'tbat=(\d+)'); final match = reg.firstMatch(line); return match != null ? double.parse(match.group(1)!) : 0; } double _extractVolt(String line) { final reg = RegExp(r'vbat=(\d+)'); final match = reg.firstMatch(line); return match != null ? int.parse(match.group(1)!) / 1000000 : 0; } (double, double) _extractTempExtremes(List lines) { final reg = RegExp(r'tbat=(\d+)'); double? maxTemp, minTemp; for (String line in lines) { final match = reg.firstMatch(line); if (match != null) { final v = double.parse(match.group(1)!); if (maxTemp == null || v > maxTemp) maxTemp = v; if (minTemp == null || v < minTemp) minTemp = v; } } return (maxTemp ?? 0, minTemp ?? 0); } (double, double) _extractVoltExtremes(List lines) { final reg = RegExp(r'vbat=(\d+)'); double? maxVolt, minVolt; for (String line in lines) { final match = reg.firstMatch(line); if (match != null) { final v = int.parse(match.group(1)!) / 1000000; if (maxVolt == null || v > maxVolt) maxVolt = v; if (minVolt == null || v < minVolt) minVolt = v; } } return (maxVolt ?? 0, minVolt ?? 0); } void _applyParseResult(ParseResult result) { setState(() { _isWireless = result.isWireless; _chargeType = result.chargeType; _chargePower = result.power; _chargeVolt = result.volt; _chargeCurr = result.curr; _wiredLevel = result.wiredLevel; }); } // ---------- 轮询读取日志 ---------- Future _fetchChargeDataFromLog() async { if (!_isCharging) { dev.log('[_fetchChargeDataFromLog] 未充电,跳过读取', name: 'BatteryHealth'); return; } if (_chargingStartTime != null) { final elapsed = DateTime.now().toUtc().difference(_chargingStartTime!); if (elapsed.inMinutes > 5) { dev.log( '[_fetchChargeDataFromLog] 充电开始已超过 5 分钟,跳过本次轮询', name: 'BatteryHealth', ); return; } } _safState = await _safService.getPermissionState(); if (_safState != SafPermissionState.authorized) { dev.log('[_fetchChargeDataFromLog] 未授权,跳过', name: 'BatteryHealth'); return; } final fileName = TimeUtils.getTodayFileName(); final exists = await _safService.logFileExists(fileName); if (!exists) { dev.log( '[_fetchChargeDataFromLog] ⚠️ 文件不存在: $fileName', name: 'BatteryHealth', ); return; } try { final lines = await _safService.readLogFile(fileName); if (lines.isEmpty) { dev.log('[_fetchChargeDataFromLog] 文件为空', name: 'BatteryHealth'); return; } dev.log( '[_fetchChargeDataFromLog] ✅ 读取成功,共 ${lines.length} 行', name: 'BatteryHealth', ); final result = ChargeParser.parse(lines, _chargingStartTime); _applyParseResult(result); } catch (e) { dev.log('[_fetchChargeDataFromLog] ❌ 读取异常: $e', name: 'BatteryHealth'); } } // ---------- 趋势图 ---------- Future _updateTrendChart() async { final data = await _dbService.queryRecentData(15); setState(() { _trendData = data.reversed.toList(); }); } // ---------- 平台通道 ---------- Future _getInitialChargeState() async { try { final result = await _methodChannel.invokeMethod('getChargeState'); final isCharging = result['isCharging'] ?? false; final systemType = result['chargeType'] ?? 'NONE'; _applySystemChargeState(isCharging, systemType); } catch (e) { // 静默降级 } } // ---------- 动画控制 ---------- void _startChargeAnimation() { if (_chargeAnimationController.isAnimating) return; _chargeAnimationController.forward(); } void _stopChargeAnimation() { if (_chargeAnimationController.isAnimating) return; _chargeAnimationController.reverse(); } void _updateChargeState() { if (!mounted) return; if (_isCharging) { _startChargeAnimation(); } else { _stopChargeAnimation(); } } // ---------- 显示辅助 ---------- String _getDisplayPower() { if (!_isCharging) return ''; if (_isWireless) { return ChargeUtils.getDisplayPowerWireless(_chargePower); } else { return ChargeUtils.getDisplayPowerWired( _chargePower, _wiredLevel, _chargeType, ); } } String _getChargeTypeLabel() { return ChargeUtils.getChargeTypeLabel(_isWireless, _chargeType); } // ---------- Build ---------- @override Widget build(BuildContext context) { return AnnotatedRegion( value: const SystemUiOverlayStyle( statusBarColor: Colors.white, statusBarIconBrightness: Brightness.dark, statusBarBrightness: Brightness.dark, ), child: Scaffold( backgroundColor: Colors.white, appBar: AppBar( backgroundColor: Colors.white, elevation: 0, scrolledUnderElevation: 0, shadowColor: Colors.transparent, surfaceTintColor: Colors.transparent, title: const Text( '电池健康度', style: TextStyle( fontSize: 24, fontWeight: FontWeight.w600, color: Colors.black, ), ), actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: readBatteryCapacity, tooltip: '刷新数据', color: Colors.black, ), ], ), body: _hasData ? _buildDataLayout() : _buildEmptyState(), ), ); } Widget _buildEmptyState() { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.battery_unknown, size: 64, color: Colors.grey), SizedBox(height: 16), Text('暂无数据', style: TextStyle(fontSize: 18, color: Colors.grey)), Text('点击右上角刷新尝试', style: TextStyle(fontSize: 14, color: Colors.grey)), ], ), ); } Widget _buildDataLayout() { final healthPercent = (_designCap > 0) ? (_currentCap / _designCap) * 100 : 0; String chargeSummary = ''; if (_isCharging) { chargeSummary = '充电中 · ${_getChargeTypeLabel()} · ${_getDisplayPower()}'; } return Padding( padding: const EdgeInsets.all(20.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _deviceModel, style: const TextStyle( fontSize: 14, color: Colors.grey, fontWeight: FontWeight.w400, ), ), const SizedBox(height: 4), BatterySummary( healthPercent: healthPercent.toDouble(), // 显式转换 cycleCount: _cycleCount, currentCap: _currentCap, designCap: _designCap, ), const SizedBox(height: 24), TrendChart(data: _trendData, designCap: _designCap), const SizedBox(height: 8), AnimatedBuilder( animation: _chargeAnimationController, builder: (context, child) { final entryHeight = 28 * _entryFadeIn.value; final chargeEntry = Opacity( opacity: _entryFadeIn.value, child: ChargeEntry( summary: chargeSummary, isCharging: _isCharging, onTap: () { if (_isCharging) { Navigator.push( context, MaterialPageRoute( builder: (context) => ChargeDetailPage( chargeType: _chargeType, isWireless: _isWireless, power: _chargePower, volt: _chargeVolt, curr: _chargeCurr, wiredLevel: _wiredLevel, ), ), ); } }, ), ); return Column( children: [ SizedBox(height: entryHeight, child: chargeEntry), Transform.translate( offset: Offset(0, _bottomSlide.value), child: BatteryInfoPanel( batteryVolt: _batteryVolt, batteryTemp: _batteryTemp, maxVolt: _maxVolt, minVolt: _minVolt, maxTemp: _maxTemp, minTemp: _minTemp, ), ), ], ); }, ), const Spacer(), ], ), ); } }