From 0d8c6b081dddda45bd1a5a8bda851ce3a48d28cb Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Mon, 20 Jul 2026 23:16:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=97=A5=E5=BF=97=E9=93=BA?= =?UTF-8?q?=E8=B7=AF=E9=83=A8=E5=88=86=EF=BC=8Cdev=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/CHANGELOG.md | 77 ++++++++++ lib/pages/battery_health_page.dart | 214 ++++++++++++--------------- lib/pages/changelog_page.dart | 99 +++++++++++++ lib/widgets/battery_data_layout.dart | 151 +++++++++++++++++++ pubspec.lock | 16 ++ pubspec.yaml | 1 + 6 files changed, 437 insertions(+), 121 deletions(-) create mode 100644 assets/CHANGELOG.md create mode 100644 lib/pages/changelog_page.dart create mode 100644 lib/widgets/battery_data_layout.dart diff --git a/assets/CHANGELOG.md b/assets/CHANGELOG.md new file mode 100644 index 0000000..5bb9396 --- /dev/null +++ b/assets/CHANGELOG.md @@ -0,0 +1,77 @@ +## [1.5.3] - 2026-07-19 +- 新增:机型匹配优化 + +### ✅ 已确认完美支持 +- 魅族 20 / 20 Pro / 20 INFINITY(基于remaining_cap + status=full联合判断) +- 魅族 21 / 21 Pro(直接读取learned_cap字段信息) + +### ⚠️ 部分支持(测试中) +- 魅族 18 / 18s / 18 Pro / 18s Pro(日志存在,字段解析适配中) +- 魅族 16s(需回退原厂固件验证) + +### ❌ 确认不支持 +- 魅族 21 Note(星纪团队固件,日志格式乱码无法识别,导致异常) +- 魅族 22(系统未生成 `mbattery_charger` 目录) + +### 📌 说明 +- 适配状态仅针对 BMS 日志可读性,与手机硬件本身无关 +- 部分旧机型(如 16 系)若无日志生成,则无法使用本工具 +- 后续若发现其他机型可用,将持续补充 + +---- + +## [1.5.2] - 2026-07-14 + +### 新增 +- 浪漫雅圆字体,工程内文件为FlymeFont.ttf + +### 🎨 UI 优化 + +- **字体体系重构**:数字全局替换为 FlymeFont(浪漫雅圆),中文保留 HarmonyOS Sans SC + - 健康度百分比、容量数值、循环次数、电压/温度读数均使用 FlymeFont + - 中文/单位/符号保持 HarmonyOS Sans SC,确保阅读舒适度 +- **温标符号修正**:温度显示由 `°C`(度符号 + C)改为专用字符 `℃`(U+2103),与 FlymeFont 原生适配 +- **容量行视觉权重优化**: + - 当前容量与设计容量数字使用 FlymeFont(字号 20,字重 300) + - `mAh` 单位改用 HarmonyOS Sans SC(字号 16,字重 500),与 `cycles` 保持一致 + - 整行颜色统一为 `rgb(95, 95, 95)`,弱化视觉重量,突出健康度大数字 +- **电压/温度极值显示优化**:数值与单位统一使用 FlymeFont,箭头符号保留 HarmonyOS + +### 🐛 修复 + +- 修复 FlymeFont 数字 1 在部分场景下显示为红色的问题(字体内部颜色预设) +- 修复容量行 `designCap`(设计容量)未正确应用 FlymeFont 的问题 +- 修复部分 `TextStyle` 中 `color` 与 `foreground` 混用导致的渲染断言错误 +- 统一主界面与二级页面状态栏样式 + +### 📊 影响范围 + +- `battery_summary.dart`:健康度/循环/容量显示样式重构 +- `battery_info_panel.dart`:电压/温度显示字体统一 +- `charge_detail_page.dart`:数值字体统一 +- `trend_chart.dart`:坐标轴数字字体统一 + +### 📌 备注 + +- 本次更新纯 UI 优化,不影响数据读取与计算逻辑 + +---- + +## [1.5.0] - 2026-07-09 + +### 🐛 修复 +- 修复有线充电功率计算单位错误(µV × µA 计算时少除 1000,导致功率放大 1000 倍,属于潜藏bug,外面看不出问题) +- 修复魅族 21 系列 `learned_cap` 因受 `status=Full` 限制导致容量未及时更新的问题 +- `learned_cap` 优先级提升:在所有状态行中优先匹配,不再局限于 `status=Full` + +### ⚡ 优化 +- 有线充电功率显示重构: + - 主界面:`≈{实际功率}w/{请求功率}w`(例:`≈30w/65w`) + - 二级页面:拆分为“请求档位”(`≈80w/L-10`)和“实际功率”(`≈30w`)两行 + - USB 慢充保持 `≈7.5w` 不变 +- UI 统一: + - 统一主界面与二级页面状态栏背景色 + - 统一主界面与二级页面 AppBar 背景色为 `Colors.white` + - 状态栏图标保持深色(`Brightness.dark`) + +## 更早期日志请移步至项目Git Release页面查看 \ No newline at end of file diff --git a/lib/pages/battery_health_page.dart b/lib/pages/battery_health_page.dart index fc6b455..e62945d 100644 --- a/lib/pages/battery_health_page.dart +++ b/lib/pages/battery_health_page.dart @@ -4,21 +4,22 @@ 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 '../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'; // ✅ 存在 +import '../services/saf_storage_service.dart'; +import '../logic/charge_parser.dart'; +import '../logic/capacity_extractor.dart'; +import '../utils/time_utils.dart'; +import '../utils/charge_utils.dart'; +import '../widgets/battery_summary.dart'; +import '../widgets/battery_info_panel.dart'; +import '../widgets/charge_entry.dart'; +import '../widgets/charge_detail_page.dart'; +import '../widgets/battery_data_layout.dart'; // 🆕 1.6.0 UI 拆分 +import '../pages/changelog_page.dart'; // 🆕 1.6.0 更新日志 // --- 潘通色定义 --- const Color kHealthGreen = Color(0xFF00B140); @@ -613,7 +614,6 @@ class _BatteryHealthPageState extends State if (_isWireless) { return ChargeUtils.getDisplayPowerWireless(_chargePower); } else { - // 🟢 1.5.0 改用新格式:实际/标定 return ChargeUtils.getMainDisplayPowerWired( _chargePower, _wiredLevel, @@ -629,9 +629,17 @@ class _BatteryHealthPageState extends State // ---------- Build ---------- @override Widget build(BuildContext context) { + // 计算健康度百分比(显式转为 double) + final double healthPercent = (_designCap > 0) + ? (_currentCap / _designCap) * 100 + : 0.0; + + final String chargeSummary = _isCharging + ? '充电中 · ${_getChargeTypeLabel()} · ${_getDisplayPower()}' + : ''; + return AnnotatedRegion( value: const SystemUiOverlayStyle( - // 🟢 1.5.0 统一状态栏颜色 statusBarColor: Color.fromRGBO(157, 212, 237, 0.1), statusBarIconBrightness: Brightness.dark, statusBarBrightness: Brightness.dark, @@ -653,120 +661,84 @@ class _BatteryHealthPageState extends State ), ), actions: [ + // 刷新按钮(左移) IconButton( icon: const Icon(Icons.refresh), onPressed: readBatteryCapacity, tooltip: '刷新数据', color: Colors.black, ), + // 三点菜单 + PopupMenuButton( + icon: const Icon(Icons.more_vert), + color: Colors.white, + onSelected: (value) { + switch (value) { + case 'changelog': + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const ChangelogPage(), + ), + ); + break; + case 'dark_mode': + // 1.7 再实现 + break; + case 'about': + // 1.7 再实现 + break; + } + }, + itemBuilder: (context) => [ + const PopupMenuItem(value: 'changelog', child: Text('更新日志')), + const PopupMenuItem( + value: 'dark_mode', + enabled: false, + child: Text('深色模式(开发中)'), + ), + const PopupMenuItem( + value: 'about', + enabled: false, + child: Text('关于(开发中)'), + ), + ], + ), ], ), - 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(), - ], + body: _hasData + ? AnimatedBuilder( + animation: _chargeAnimationController, + builder: (context, child) { + // 🟢 修复:传入 healthPercent 为 double,移除未定义的动画参数 + // 动画在 BatteryDataLayout 内部不处理,由父级 AnimatedBuilder 控制 + return BatteryDataLayout( + deviceModel: _deviceModel, + healthPercent: healthPercent, + cycleCount: _cycleCount, + currentCap: _currentCap, + designCap: _designCap, + trendData: _trendData, + isCharging: _isCharging, + chargeSummary: chargeSummary, + batteryVolt: _batteryVolt, + batteryTemp: _batteryTemp, + maxVolt: _maxVolt, + minVolt: _minVolt, + maxTemp: _maxTemp, + minTemp: _minTemp, + chargeType: _chargeType, + isWireless: _isWireless, + chargePower: _chargePower, + chargeVolt: _chargeVolt, + chargeCurr: _chargeCurr, + wiredLevel: _wiredLevel, + // 🟢 动画参数由父级 AnimatedBuilder 控制, + // BatteryDataLayout 不再需要透传 animation 参数 + ); + }, + ) + : const EmptyStateLayout(), ), ); } diff --git a/lib/pages/changelog_page.dart b/lib/pages/changelog_page.dart new file mode 100644 index 0000000..ac15fe5 --- /dev/null +++ b/lib/pages/changelog_page.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show rootBundle; +import 'package:flutter_markdown/flutter_markdown.dart'; + +class ChangelogPage extends StatefulWidget { + const ChangelogPage({super.key}); + + @override + State createState() => _ChangelogPageState(); +} + +class _ChangelogPageState extends State { + String _markdownContent = ''; + bool _isLoading = true; + String? _error; + + @override + void initState() { + super.initState(); + _loadChangelog(); + } + + Future _loadChangelog() async { + try { + final content = await rootBundle.loadString('assets/CHANGELOG.md'); + setState(() { + _markdownContent = content; + _isLoading = false; + }); + } catch (e) { + setState(() { + _error = '无法加载更新日志: $e'; + _isLoading = false; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + title: const Text( + '更新日志', + style: TextStyle( + fontFamily: 'HarmonyOS_Sans_SC', + color: Colors.black, + ), + ), + backgroundColor: Colors.white, + elevation: 0, + foregroundColor: Colors.black, + iconTheme: const IconThemeData(color: Colors.black), + ), + body: _isLoading + ? const Center(child: CircularProgressIndicator()) + : _error != null + ? Center( + child: Text( + _error!, + style: const TextStyle( + fontFamily: 'HarmonyOS_Sans_SC', + color: Colors.grey, + ), + ), + ) + : SingleChildScrollView( + padding: const EdgeInsets.all(20), + child: Markdown( + data: _markdownContent, + styleSheet: MarkdownStyleSheet( + h1: const TextStyle( + fontFamily: 'HarmonyOS_Sans_SC', + fontSize: 24, + fontWeight: FontWeight.w700, + color: Colors.black, + ), + h2: const TextStyle( + fontFamily: 'HarmonyOS_Sans_SC', + fontSize: 20, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + p: const TextStyle( + fontFamily: 'HarmonyOS_Sans_SC', + fontSize: 16, + color: Colors.black87, + ), + listBullet: const TextStyle( + fontFamily: 'HarmonyOS_Sans_SC', + fontSize: 16, + color: Colors.black87, + ), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/battery_data_layout.dart b/lib/widgets/battery_data_layout.dart new file mode 100644 index 0000000..a5cb912 --- /dev/null +++ b/lib/widgets/battery_data_layout.dart @@ -0,0 +1,151 @@ +import 'package:flutter/material.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'; + +/// 电池数据布局(含数据展示、趋势图、充电状态、电压温度面板) +class BatteryDataLayout extends StatelessWidget { + final String deviceModel; + final double healthPercent; + final int cycleCount; + final double currentCap; + final int designCap; + final List> trendData; + final bool isCharging; + final String chargeSummary; + final double batteryVolt; + final double batteryTemp; + final double maxVolt; + final double minVolt; + final double maxTemp; + final double minTemp; + final String chargeType; + final bool isWireless; + final double chargePower; + final double chargeVolt; + final double chargeCurr; + final int? wiredLevel; + + const BatteryDataLayout({ + super.key, + required this.deviceModel, + required this.healthPercent, + required this.cycleCount, + required this.currentCap, + required this.designCap, + required this.trendData, + required this.isCharging, + required this.chargeSummary, + required this.batteryVolt, + required this.batteryTemp, + required this.maxVolt, + required this.minVolt, + required this.maxTemp, + required this.minTemp, + required this.chargeType, + required this.isWireless, + required this.chargePower, + required this.chargeVolt, + required this.chargeCurr, + this.wiredLevel, + }); + + @override + Widget build(BuildContext context) { + 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, + cycleCount: cycleCount, + currentCap: currentCap, + designCap: designCap, + ), + + const SizedBox(height: 24), + + TrendChart(data: trendData, designCap: designCap), + + const SizedBox(height: 8), + + // 充电状态 + 信息面板(带进场动画) + _buildChargeAndInfoPanel(context), + + const Spacer(), + ], + ), + ); + } + + Widget _buildChargeAndInfoPanel(BuildContext context) { + return Column( + children: [ + // 充电状态入口(保持原有的透明度/位移动画由父级控制) + 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, + ), + ), + ); + } + }, + ), + const SizedBox(height: 4), + BatteryInfoPanel( + batteryVolt: batteryVolt, + batteryTemp: batteryTemp, + maxVolt: maxVolt, + minVolt: minVolt, + maxTemp: maxTemp, + minTemp: minTemp, + ), + ], + ); + } +} + +/// 空状态布局(无数据时显示) +class EmptyStateLayout extends StatelessWidget { + const EmptyStateLayout({super.key}); + + @override + Widget build(BuildContext context) { + 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)), + ], + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 2523685..eced6ff 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -126,6 +126,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "5.0.0" + flutter_markdown: + dependency: "direct main" + description: + name: flutter_markdown + sha256: "08fb8315236099ff8e90cb87bb2b935e0a724a3af1623000a9cec930468e0f27" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.7+1" flutter_svg: dependency: "direct main" description: @@ -224,6 +232,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" + markdown: + dependency: transitive + description: + name: markdown + sha256: ee85086ad7698b42522c6ad42fe195f1b9898e4d974a1af4576c1a3a176cada9 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.3.1" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index eca1326..e22e306 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,6 +38,7 @@ dependencies: path: ^1.9.0 flutter_svg: ^2.0.10+1 shared_preferences: ^2.2.0 + flutter_markdown: ^0.7.0 # The following adds the Cupertino Icons font to your application.