Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
125fb969e4 | ||
|
|
8be4ff7e40 |
@@ -0,0 +1,51 @@
|
|||||||
|
package com.lxh.battery_health
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.os.BatteryManager
|
||||||
|
|
||||||
|
class BatteryManagerHelper(private val context: Context) {
|
||||||
|
|
||||||
|
fun getBatteryData(): Map<String, Any?> {
|
||||||
|
val bm = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
|
||||||
|
|
||||||
|
// 电流(µA)。部分机型充电为负、放电为正,部分相反
|
||||||
|
val rawCurrentUa = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW)
|
||||||
|
|
||||||
|
// 电压 / 状态从 ACTION_BATTERY_CHANGED 广播里拿(比 BATTERY_PROPERTY_VOLTAGE 兼容性好)
|
||||||
|
val intent = context.registerReceiver(
|
||||||
|
null,
|
||||||
|
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
|
||||||
|
)
|
||||||
|
val voltageMv = intent?.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1) ?: -1
|
||||||
|
val status = intent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
|
||||||
|
val plugged = intent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) ?: -1
|
||||||
|
val temperatureTenth = intent?.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1) ?: -1
|
||||||
|
|
||||||
|
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
||||||
|
status == BatteryManager.BATTERY_STATUS_FULL
|
||||||
|
|
||||||
|
// 符号规约:充电为正
|
||||||
|
val normalizedUa = when {
|
||||||
|
rawCurrentUa == Int.MIN_VALUE -> 0
|
||||||
|
isCharging && rawCurrentUa < 0 -> -rawCurrentUa
|
||||||
|
!isCharging && rawCurrentUa > 0 -> -rawCurrentUa
|
||||||
|
else -> rawCurrentUa
|
||||||
|
}
|
||||||
|
|
||||||
|
// 功率 W = V × A = (mV / 1000) × (µA / 1_000_000)
|
||||||
|
val powerW = if (voltageMv > 0 && normalizedUa != 0) {
|
||||||
|
(voltageMv / 1000.0) * (normalizedUa / 1_000_000.0)
|
||||||
|
} else 0.0
|
||||||
|
|
||||||
|
return mapOf(
|
||||||
|
"voltageMv" to voltageMv,
|
||||||
|
"currentUa" to normalizedUa,
|
||||||
|
"powerW" to powerW,
|
||||||
|
"temperatureC" to if (temperatureTenth > 0) temperatureTenth / 10.0 else 0.0,
|
||||||
|
"plugged" to plugged, // 0=未插, 1=AC, 2=USB, 4=WIRELESS
|
||||||
|
"isCharging" to isCharging,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -255,26 +255,35 @@ private fun clearRefreshRate() {
|
|||||||
|
|
||||||
// 充电状态通道
|
// 充电状态通道
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHARGE_CHANNEL)
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHARGE_CHANNEL)
|
||||||
.setMethodCallHandler { call, result ->
|
.setMethodCallHandler { call, result ->
|
||||||
if (call.method == "getChargeState") {
|
when (call.method) {
|
||||||
val isCharging = getBatteryStatus()
|
"getChargeState" -> {
|
||||||
val chargeType = if (isCharging) {
|
val isCharging = getBatteryStatus()
|
||||||
val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
val chargeType = if (isCharging) {
|
||||||
val plug = batteryIntent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) ?: -1
|
val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
||||||
when (plug) {
|
val plug = batteryIntent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) ?: -1
|
||||||
BatteryManager.BATTERY_PLUGGED_AC -> "AC"
|
when (plug) {
|
||||||
BatteryManager.BATTERY_PLUGGED_USB -> "USB"
|
BatteryManager.BATTERY_PLUGGED_AC -> "AC"
|
||||||
BatteryManager.BATTERY_PLUGGED_WIRELESS -> "WIRELESS"
|
BatteryManager.BATTERY_PLUGGED_USB -> "USB"
|
||||||
else -> "UNKNOWN"
|
BatteryManager.BATTERY_PLUGGED_WIRELESS -> "WIRELESS"
|
||||||
}
|
else -> "UNKNOWN"
|
||||||
} else {
|
|
||||||
"NONE"
|
|
||||||
}
|
}
|
||||||
result.success(mapOf("isCharging" to isCharging, "chargeType" to chargeType))
|
|
||||||
} else {
|
} else {
|
||||||
result.notImplemented()
|
"NONE"
|
||||||
|
}
|
||||||
|
result.success(mapOf("isCharging" to isCharging, "chargeType" to chargeType))
|
||||||
|
}
|
||||||
|
// 🆕 新增分支
|
||||||
|
"getBatteryManagerData" -> {
|
||||||
|
try {
|
||||||
|
result.success(BatteryManagerHelper(applicationContext).getBatteryData())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
result.error("BM_ERROR", e.message, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SAF 通道
|
// SAF 通道
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SAF_CHANNEL)
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SAF_CHANNEL)
|
||||||
|
|||||||
+41
-1
@@ -1,4 +1,44 @@
|
|||||||
## 1.8.0
|
## [1.9.0] - 2026-10-11
|
||||||
|
|
||||||
|
### ✨ 新增功能
|
||||||
|
|
||||||
|
- **BatteryManager 实时数据接入**
|
||||||
|
- 新增原生 `BatteryManagerHelper`,通过 Android 官方 API 读取电池端真实功率
|
||||||
|
- 主界面充电行改为显示电池端实时功率(1 秒刷新),反映电池实际吸收的能量
|
||||||
|
- 充电详情页拆分为两个数据源分区:
|
||||||
|
- **BMS 日志**:协议、标定档位、实际功率、电压、电流(原有)
|
||||||
|
- **BatteryManager(电池端)**:电池端电压、电流、功率(新增)
|
||||||
|
- 与专业功率计(PowerZ)实测数据一致,误差在 0.02W 以内
|
||||||
|
|
||||||
|
### 🎨 体验优化
|
||||||
|
|
||||||
|
- **充电行显示格式调整**
|
||||||
|
- 有线充电:`充电中 · PPS · 5.98W / 7.5W`(实际功率 / 标定档位)
|
||||||
|
- 无线充电:`充电中 · 无线 · 12.3W`
|
||||||
|
- USB 慢充:`充电中 · USB · 2.1W`
|
||||||
|
- 瓦特单位统一为大写 `W`
|
||||||
|
- 7.5W 等带小数的档位保留一位小数,其余整数值去掉 `.0`
|
||||||
|
|
||||||
|
- **字体细节优化**
|
||||||
|
- 充电行数字与拉丁字母改用 Flyme 字体,中文字符保持 HarmonyOS Sans SC
|
||||||
|
- 解决混合文本下的字体割裂感
|
||||||
|
- 充电详情页所有功率数值同步应用 Flyme 字体
|
||||||
|
|
||||||
|
### 🐛 问题修复
|
||||||
|
|
||||||
|
- 修复主界面充电行在充电器接入初期显示"读取中..."后数值迟迟不刷新的问题
|
||||||
|
- 修复详情页功率数值字体与其它行不统一的问题
|
||||||
|
- 修复 `wired_level` 解析后标定档位显示取整误差(如 `≈8W/L-1` 修正为 `≈7.5W/L-1`)
|
||||||
|
|
||||||
|
### 📝 技术说明
|
||||||
|
|
||||||
|
- `BATTERY_PROPERTY_CURRENT_NOW` 读数为**电池端净电流**(充电器输入减去系统功耗),而非充电器输出端数据
|
||||||
|
- 电池接近满电时数值会明显偏低(涓流阶段),属正常现象
|
||||||
|
- 与系统状态栏显示的充电功率为不同概念,前者反映电池实际吸收,后者反映充电器输出
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [1.8.0] - 2026-10-11
|
||||||
|
|
||||||
### 新增
|
### 新增
|
||||||
- **夜间模式**:支持浅色 / 深色 / 跟随系统三种模式,可在右上角菜单切换
|
- **夜间模式**:支持浅色 / 深色 / 跟随系统三种模式,可在右上角菜单切换
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import '../theme/theme_service.dart';
|
import '../theme/theme_service.dart';
|
||||||
import '../theme/app_colors.dart';
|
import '../theme/app_colors.dart';
|
||||||
|
import '../services/battery_manager_service.dart';
|
||||||
|
|
||||||
// --- 本地导入 ---
|
// --- 本地导入 ---
|
||||||
import '../models/device_matching.dart';
|
import '../models/device_matching.dart';
|
||||||
@@ -64,6 +65,9 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
|
|||||||
int? _wiredLevel;
|
int? _wiredLevel;
|
||||||
bool _isWireless = false;
|
bool _isWireless = false;
|
||||||
DateTime? _chargingStartTime;
|
DateTime? _chargingStartTime;
|
||||||
|
double _bmPower = 0.0;
|
||||||
|
double _bmVolt = 0.0;
|
||||||
|
double _bmCurr = 0.0;
|
||||||
|
|
||||||
// ---------- SAF 状态 ----------
|
// ---------- SAF 状态 ----------
|
||||||
final SafStorageService _safService = SafStorageService();
|
final SafStorageService _safService = SafStorageService();
|
||||||
@@ -80,6 +84,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
|
|||||||
|
|
||||||
// 轮询定时器(15秒)
|
// 轮询定时器(15秒)
|
||||||
Timer? _pollTimer;
|
Timer? _pollTimer;
|
||||||
|
Timer? _bmTimer;
|
||||||
|
|
||||||
// ---------- 生命周期 ----------
|
// ---------- 生命周期 ----------
|
||||||
@override
|
@override
|
||||||
@@ -124,7 +129,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
|
|||||||
readBatteryCapacity();
|
readBatteryCapacity();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
_startBmPolling(); // ← 调用,不是定义
|
||||||
dev.log('🔵 ========== initState 结束 ==========', name: 'BatteryHealth');
|
dev.log('🔵 ========== initState 结束 ==========', name: 'BatteryHealth');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +137,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_chargeEventSubscription?.cancel();
|
_chargeEventSubscription?.cancel();
|
||||||
_pollTimer?.cancel();
|
_pollTimer?.cancel();
|
||||||
|
_bmTimer?.cancel();
|
||||||
_dbService.database.then((db) => db.close()).ignore();
|
_dbService.database.then((db) => db.close()).ignore();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -252,6 +258,9 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
|
|||||||
_chargeCurr = 0.0;
|
_chargeCurr = 0.0;
|
||||||
_wiredLevel = null;
|
_wiredLevel = null;
|
||||||
_chargingStartTime = null;
|
_chargingStartTime = null;
|
||||||
|
_bmPower = 0.0; // 🆕
|
||||||
|
_bmVolt = 0.0; // 🆕
|
||||||
|
_bmCurr = 0.0; // 🆕
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,18 +619,34 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- BM 轮询(1秒) ----------
|
||||||
|
void _startBmPolling() {
|
||||||
|
_bmTimer?.cancel();
|
||||||
|
_bmTimer = Timer.periodic(const Duration(seconds: 1), (_) async {
|
||||||
|
if (!_isCharging) return;
|
||||||
|
try {
|
||||||
|
final d = await BatteryManagerService.fetch();
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_bmPower = d.powerW;
|
||||||
|
_bmVolt = d.voltageV;
|
||||||
|
_bmCurr = d.currentA;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// 静默降级,保留上一次的值
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 显示辅助 ----------
|
// ---------- 显示辅助 ----------
|
||||||
String _getDisplayPower() {
|
String _getDisplayPower() {
|
||||||
if (!_isCharging) return '';
|
if (!_isCharging) return '';
|
||||||
if (_isWireless) {
|
return ChargeUtils.getMainDisplayPowerBm(
|
||||||
return ChargeUtils.getDisplayPowerWireless(_chargePower);
|
_bmPower,
|
||||||
} else {
|
_wiredLevel,
|
||||||
return ChargeUtils.getMainDisplayPowerWired(
|
_chargeType,
|
||||||
_chargePower,
|
_isWireless,
|
||||||
_wiredLevel,
|
);
|
||||||
_chargeType,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getChargeTypeLabel() {
|
String _getChargeTypeLabel() {
|
||||||
@@ -820,6 +845,9 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
|
|||||||
volt: _chargeVolt,
|
volt: _chargeVolt,
|
||||||
curr: _chargeCurr,
|
curr: _chargeCurr,
|
||||||
wiredLevel: _wiredLevel,
|
wiredLevel: _wiredLevel,
|
||||||
|
bmPower: _bmPower, // 🆕
|
||||||
|
bmVolt: _bmVolt, // 🆕
|
||||||
|
bmCurr: _bmCurr, // 🆕
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
/// BatteryManager 实时数据(电池端:电压 / 电流 / 功率)
|
||||||
|
class BatteryManagerData {
|
||||||
|
final int voltageMv;
|
||||||
|
final int currentUa;
|
||||||
|
final double powerW;
|
||||||
|
final double temperatureC;
|
||||||
|
final int plugged; // 0=未插, 1=AC, 2=USB, 4=WIRELESS
|
||||||
|
final bool isCharging;
|
||||||
|
|
||||||
|
const BatteryManagerData({
|
||||||
|
required this.voltageMv,
|
||||||
|
required this.currentUa,
|
||||||
|
required this.powerW,
|
||||||
|
required this.temperatureC,
|
||||||
|
required this.plugged,
|
||||||
|
required this.isCharging,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory BatteryManagerData.empty() => const BatteryManagerData(
|
||||||
|
voltageMv: 0,
|
||||||
|
currentUa: 0,
|
||||||
|
powerW: 0,
|
||||||
|
temperatureC: 0,
|
||||||
|
plugged: 0,
|
||||||
|
isCharging: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory BatteryManagerData.fromMap(dynamic raw) {
|
||||||
|
if (raw is! Map) return BatteryManagerData.empty();
|
||||||
|
return BatteryManagerData(
|
||||||
|
voltageMv: (raw['voltageMv'] as num?)?.toInt() ?? 0,
|
||||||
|
currentUa: (raw['currentUa'] as num?)?.toInt() ?? 0,
|
||||||
|
powerW: (raw['powerW'] as num?)?.toDouble() ?? 0.0,
|
||||||
|
temperatureC: (raw['temperatureC'] as num?)?.toDouble() ?? 0.0,
|
||||||
|
plugged: (raw['plugged'] as num?)?.toInt() ?? 0,
|
||||||
|
isCharging: raw['isCharging'] as bool? ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 电压 V
|
||||||
|
double get voltageV => voltageMv / 1000.0;
|
||||||
|
|
||||||
|
/// 电流 A
|
||||||
|
double get currentA => currentUa / 1_000_000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
class BatteryManagerService {
|
||||||
|
static const _channel = MethodChannel('com.lxh.battery_health/charge');
|
||||||
|
|
||||||
|
/// 主动拉取一次
|
||||||
|
static Future<BatteryManagerData> fetch() async {
|
||||||
|
final result = await _channel.invokeMethod('getBatteryManagerData');
|
||||||
|
return BatteryManagerData.fromMap(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
-20
@@ -1,7 +1,7 @@
|
|||||||
import '../constants/charge_maps.dart';
|
import '../constants/charge_maps.dart';
|
||||||
|
|
||||||
class ChargeUtils {
|
class ChargeUtils {
|
||||||
// ============ 无线充电(保持不变) ============
|
// ============ 无线充电 ============
|
||||||
static String getDisplayPowerWireless(double power) {
|
static String getDisplayPowerWireless(double power) {
|
||||||
if (power == 0) return '读取中...';
|
if (power == 0) return '读取中...';
|
||||||
const List<int> levels = [5, 7, 15, 27, 40, 50, 66];
|
const List<int> levels = [5, 7, 15, 27, 40, 50, 66];
|
||||||
@@ -13,7 +13,7 @@ class ChargeUtils {
|
|||||||
return '≈${matchedLevel}W';
|
return '≈${matchedLevel}W';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ 原有有线逻辑(保留兼容,但主界面不再直接调用) ============
|
// ============ 旧的有线逻辑(保留兼容) ============
|
||||||
static String getDisplayPowerWired(
|
static String getDisplayPowerWired(
|
||||||
double power,
|
double power,
|
||||||
int? wiredLevel,
|
int? wiredLevel,
|
||||||
@@ -34,57 +34,81 @@ class ChargeUtils {
|
|||||||
return best > 0 ? '≈${best.toInt()}W' : '≈${power.round()}W';
|
return best > 0 ? '≈${best.toInt()}W' : '≈${power.round()}W';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ 🟢 1.5.0 新增:主界面显示(实际/标定) ============
|
// ============ 主界面:实际 / 标定(旧版) ============
|
||||||
/// 示例:≈30w/65w
|
|
||||||
static String getMainDisplayPowerWired(
|
static String getMainDisplayPowerWired(
|
||||||
double power,
|
double power,
|
||||||
int? wiredLevel,
|
int? wiredLevel,
|
||||||
String chargeType,
|
String chargeType,
|
||||||
) {
|
) {
|
||||||
// USB 慢充锁定 7.5W
|
if (chargeType == "SDP") return "≈7.5W";
|
||||||
if (chargeType == "SDP") return "≈7.5w";
|
|
||||||
|
|
||||||
if (power == 0) return "读取中...";
|
if (power == 0) return "读取中...";
|
||||||
|
|
||||||
final actual = power.round();
|
final actual = power.round();
|
||||||
|
|
||||||
// 有档位且能查到标定值
|
|
||||||
if (wiredLevel != null && ChargeMaps.wiredPower.containsKey(wiredLevel)) {
|
if (wiredLevel != null && ChargeMaps.wiredPower.containsKey(wiredLevel)) {
|
||||||
final requested = ChargeMaps.wiredPower[wiredLevel]!.round();
|
final requested = ChargeMaps.wiredPower[wiredLevel]!.round();
|
||||||
return "≈${actual}w/${requested}w";
|
return "≈${actual}W/${requested}W";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 无档位信息,仅显示实际功率
|
return "≈${actual}W";
|
||||||
return "≈${actual}w";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ 🟢 1.5.0 新增:二级页面标定档位显示 ============
|
// ============ 主界面:BM 电池端功率 + 标定档位 ============
|
||||||
/// 示例:≈80w/L-10
|
/// 示例:5.98W / 7.5W、12.3W、读取中...
|
||||||
|
static String getMainDisplayPowerBm(
|
||||||
|
double bmPower,
|
||||||
|
int? wiredLevel,
|
||||||
|
String chargeType,
|
||||||
|
bool isWireless,
|
||||||
|
) {
|
||||||
|
if (bmPower <= 0 || bmPower > 100) return '读取中...';
|
||||||
|
|
||||||
|
final powerStr = '${bmPower.toStringAsFixed(1)}W';
|
||||||
|
|
||||||
|
if (isWireless || chargeType == 'SDP') return powerStr;
|
||||||
|
|
||||||
|
if (wiredLevel != null && ChargeMaps.wiredPower.containsKey(wiredLevel)) {
|
||||||
|
final requested = ChargeMaps.wiredPower[wiredLevel]!;
|
||||||
|
final requestedStr = requested == requested.roundToDouble()
|
||||||
|
? requested.toInt().toString()
|
||||||
|
: requested.toStringAsFixed(1);
|
||||||
|
return '$powerStr / ${requestedStr}W';
|
||||||
|
}
|
||||||
|
|
||||||
|
return powerStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 详情页:标定档位 ============
|
||||||
|
/// 示例:≈80W/L-10、≈7.5W/L-1
|
||||||
static String getDetailRequestedPowerWired(
|
static String getDetailRequestedPowerWired(
|
||||||
double power,
|
double power,
|
||||||
int? wiredLevel,
|
int? wiredLevel,
|
||||||
String chargeType,
|
String chargeType,
|
||||||
) {
|
) {
|
||||||
if (chargeType == "SDP") return "≈7.5w";
|
if (chargeType == "SDP") return "≈7.5W";
|
||||||
|
|
||||||
if (power == 0) return "读取中...";
|
if (power == 0) return "读取中...";
|
||||||
|
|
||||||
if (wiredLevel != null && ChargeMaps.wiredPower.containsKey(wiredLevel)) {
|
if (wiredLevel != null && ChargeMaps.wiredPower.containsKey(wiredLevel)) {
|
||||||
final requested = ChargeMaps.wiredPower[wiredLevel]!.round();
|
final requested = ChargeMaps.wiredPower[wiredLevel]!;
|
||||||
return "≈${requested}w/L-$wiredLevel";
|
final str = requested == requested.roundToDouble()
|
||||||
|
? requested.toInt().toString()
|
||||||
|
: requested.toStringAsFixed(1);
|
||||||
|
return "≈${str}W/L-$wiredLevel";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "≈${power.round()}w";
|
return "≈${power.round()}W";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ 🟢 1.5.0 新增:二级页面实际功率显示 ============
|
// ============ 详情页:实际功率 ============
|
||||||
static String getDetailActualPowerWired(double power, String chargeType) {
|
static String getDetailActualPowerWired(double power, String chargeType) {
|
||||||
if (chargeType == "SDP") return "≈7.5w";
|
if (chargeType == "SDP") return "≈7.5W";
|
||||||
if (power == 0) return "读取中...";
|
if (power == 0) return "读取中...";
|
||||||
return "≈${power.round()}w";
|
return "≈${power.round()}W";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ 充电类型标签(保持不变) ============
|
// ============ 充电类型标签 ============
|
||||||
static String getChargeTypeLabel(bool isWireless, String chargeType) {
|
static String getChargeTypeLabel(bool isWireless, String chargeType) {
|
||||||
if (isWireless) {
|
if (isWireless) {
|
||||||
return chargeType == "无线" ? "无线" : chargeType;
|
return chargeType == "无线" ? "无线" : chargeType;
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ class ChargeDetailPage extends StatelessWidget {
|
|||||||
final double volt;
|
final double volt;
|
||||||
final double curr;
|
final double curr;
|
||||||
final int? wiredLevel;
|
final int? wiredLevel;
|
||||||
|
final double bmPower;
|
||||||
|
final double bmVolt;
|
||||||
|
final double bmCurr;
|
||||||
|
|
||||||
const ChargeDetailPage({
|
const ChargeDetailPage({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -19,6 +22,9 @@ class ChargeDetailPage extends StatelessWidget {
|
|||||||
required this.volt,
|
required this.volt,
|
||||||
required this.curr,
|
required this.curr,
|
||||||
this.wiredLevel,
|
this.wiredLevel,
|
||||||
|
this.bmPower = 0,
|
||||||
|
this.bmVolt = 0,
|
||||||
|
this.bmCurr = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
String _getProtocolDisplay() {
|
String _getProtocolDisplay() {
|
||||||
@@ -62,18 +68,31 @@ class ChargeDetailPage extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
// ========== BMS 来源 ==========
|
||||||
|
_buildSectionTitle(context, 'BMS 日志'),
|
||||||
_buildTextRow(context, '协议', _getProtocolDisplay()),
|
_buildTextRow(context, '协议', _getProtocolDisplay()),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
if (!isWireless) ...[
|
if (!isWireless) ...[
|
||||||
_buildTextRow(context, '标定档位', _getRequestedDisplay()),
|
_buildTextRow(
|
||||||
|
context,
|
||||||
|
'标定档位',
|
||||||
|
_getRequestedDisplay(),
|
||||||
|
valueFont: 'Flyme',
|
||||||
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
_buildTextRow(context, '实际功率', _getActualDisplay()),
|
_buildTextRow(
|
||||||
|
context,
|
||||||
|
'实际功率',
|
||||||
|
_getActualDisplay(),
|
||||||
|
valueFont: 'Flyme',
|
||||||
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
] else ...[
|
] else ...[
|
||||||
_buildTextRow(
|
_buildTextRow(
|
||||||
context,
|
context,
|
||||||
'功率',
|
'功率',
|
||||||
ChargeUtils.getDisplayPowerWireless(power),
|
ChargeUtils.getDisplayPowerWireless(power),
|
||||||
|
valueFont: 'Flyme',
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
],
|
],
|
||||||
@@ -81,20 +100,19 @@ class ChargeDetailPage extends StatelessWidget {
|
|||||||
const Divider(),
|
const Divider(),
|
||||||
_buildDetailRow(context, '电流', curr, 'A'),
|
_buildDetailRow(context, '电流', curr, 'A'),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
if (isWireless) _buildTextRow(context, '类型', '无线'),
|
|
||||||
if (!isWireless && wiredLevel == null)
|
const SizedBox(height: 24),
|
||||||
_buildTextRow(context, '档位', '自动'),
|
|
||||||
|
// ========== BM 来源 ==========
|
||||||
|
_buildSectionTitle(context, 'BatteryManager(电池端)'),
|
||||||
|
_buildDetailRow(context, '电压', bmVolt, 'V'),
|
||||||
|
const Divider(),
|
||||||
|
_buildDetailRow(context, '电流', bmCurr, 'A'),
|
||||||
|
const Divider(),
|
||||||
|
bmPower > 0
|
||||||
|
? _buildDetailRow(context, '功率', bmPower, 'W')
|
||||||
|
: _buildTextRow(context, '功率', '读取中...'),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Center(
|
|
||||||
child: Text(
|
|
||||||
'数据来自 BMS 实时日志',
|
|
||||||
style: TextStyle(
|
|
||||||
fontFamily: 'HarmonyOS_Sans_SC',
|
|
||||||
fontSize: 12,
|
|
||||||
color: c.textTertiary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -102,7 +120,28 @@ class ChargeDetailPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTextRow(BuildContext context, String label, String value) {
|
Widget _buildSectionTitle(BuildContext context, String title) {
|
||||||
|
final c = context.colors;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'HarmonyOS_Sans_SC',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: c.textTertiary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTextRow(
|
||||||
|
BuildContext context,
|
||||||
|
String label,
|
||||||
|
String value, {
|
||||||
|
String valueFont = 'HarmonyOS_Sans_SC',
|
||||||
|
}) {
|
||||||
final c = context.colors;
|
final c = context.colors;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6.0),
|
padding: const EdgeInsets.symmetric(vertical: 6.0),
|
||||||
@@ -121,7 +160,7 @@ class ChargeDetailPage extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
value,
|
value,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: 'HarmonyOS_Sans_SC',
|
fontFamily: valueFont,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: c.textPrimary,
|
color: c.textPrimary,
|
||||||
|
|||||||
@@ -21,13 +21,17 @@ class ChargeEntry extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Flexible(
|
||||||
summary,
|
child: RichText(
|
||||||
style: TextStyle(
|
overflow: TextOverflow.ellipsis,
|
||||||
fontFamily: 'HarmonyOS_Sans_SC',
|
text: TextSpan(
|
||||||
fontSize: 16,
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w500,
|
fontSize: 16,
|
||||||
color: c.textPrimary,
|
fontWeight: FontWeight.w500,
|
||||||
|
color: c.textPrimary,
|
||||||
|
),
|
||||||
|
children: _buildSpans(summary),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
@@ -57,4 +61,54 @@ class ChargeEntry extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 按 CJK / 非 CJK 拆分字符串,分别应用 HarmonyOS / Flyme
|
||||||
|
List<TextSpan> _buildSpans(String text) {
|
||||||
|
final spans = <TextSpan>[];
|
||||||
|
if (text.isEmpty) return spans;
|
||||||
|
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
bool? isCJK;
|
||||||
|
|
||||||
|
void flush() {
|
||||||
|
if (buffer.isEmpty) return;
|
||||||
|
spans.add(
|
||||||
|
TextSpan(
|
||||||
|
text: buffer.toString(),
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: (isCJK ?? false) ? 'HarmonyOS_Sans_SC' : 'Flyme',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final rune in text.runes) {
|
||||||
|
final cjk = _isCJKRune(rune);
|
||||||
|
if (isCJK == null || cjk == isCJK) {
|
||||||
|
buffer.writeCharCode(rune);
|
||||||
|
isCJK = cjk;
|
||||||
|
} else {
|
||||||
|
flush();
|
||||||
|
buffer.writeCharCode(rune);
|
||||||
|
isCJK = cjk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
return spans;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isCJKRune(int rune) {
|
||||||
|
// CJK 统一表意文字
|
||||||
|
if (rune >= 0x4E00 && rune <= 0x9FFF) return true;
|
||||||
|
// CJK 扩展 A
|
||||||
|
if (rune >= 0x3400 && rune <= 0x4DBF) return true;
|
||||||
|
// 中文标点(。、,:;等)
|
||||||
|
if (rune >= 0x3000 && rune <= 0x303F) return true;
|
||||||
|
// CJK 兼容符号
|
||||||
|
if (rune >= 0xFE30 && rune <= 0xFE4F) return true;
|
||||||
|
// 全角字符
|
||||||
|
if (rune >= 0xFF00 && rune <= 0xFFEF) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user