增加作弊数据检测

This commit is contained in:
2026-07-30 21:57:08 +08:00
parent c85a523ec0
commit c8717e5e1f
6 changed files with 135 additions and 26 deletions
+13
View File
@@ -0,0 +1,13 @@
enum DataAbnormalType {
/// 无异常
none,
/// 容量低于设计容量 60%
capacityTooLow,
/// 容量高于设计容量 115%
capacityTooHigh,
/// 电压异常(< 3.0V 或 > 5.0V
voltageAbnormal,
}
+63 -7
View File
@@ -7,6 +7,7 @@ import 'package:device_info_plus/device_info_plus.dart';
// --- 本地导入 --- // --- 本地导入 ---
import '../models/device_matching.dart'; import '../models/device_matching.dart';
import '../models/saf_permission_state.dart'; import '../models/saf_permission_state.dart';
import '../models/data_abnormal_type.dart'; // 🆕 1.7.0
import '../services/database_service.dart'; import '../services/database_service.dart';
import '../services/battery_data_service.dart'; import '../services/battery_data_service.dart';
import '../services/saf_storage_service.dart'; import '../services/saf_storage_service.dart';
@@ -381,8 +382,11 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
} }
} }
// 🟢 1.7.0 容量有效性校验
final validatedCap = _validateCapacity(finalCap, _designCap);
setState(() { setState(() {
_currentCap = (finalCap ?? _currentCap).toDouble(); _currentCap = (validatedCap ?? _currentCap).toDouble();
_cycleCount = cycle; _cycleCount = cycle;
_batteryTemp = temp; _batteryTemp = temp;
_batteryVolt = volt; _batteryVolt = volt;
@@ -409,6 +413,53 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
} }
} }
// ---------- 🆕 1.7.0 容量有效性校验 ----------
int? _validateCapacity(int? capacity, int designCap) {
if (capacity == null) return null;
// 容量必须大于 0
if (capacity <= 0) {
dev.log('⚠️ 容量异常:<= 0,已忽略', name: 'BatteryHealth');
return null;
}
// 容量不能低于设计容量的 60%
if (capacity < designCap * 0.6) {
dev.log(
'⚠️ 容量异常:低于设计容量 60%$capacity < ${designCap * 0.6}),已忽略',
name: 'BatteryHealth',
);
return null;
}
// 容量不能超过设计容量的 115%
if (capacity > designCap * 1.15) {
dev.log(
'⚠️ 容量异常:高于设计容量 115%($capacity > ${designCap * 1.15}),已忽略',
name: 'BatteryHealth',
);
return null;
}
return capacity;
}
// ---------- 🆕 1.7.0 异常检测(供 UI 使用) ----------
DataAbnormalType _checkAbnormalType(
double currentCap,
int designCap,
double voltage,
) {
// 容量异常:高于 115% 或低于 60%
if (currentCap > designCap * 1.15) return DataAbnormalType.capacityTooHigh;
if (currentCap < designCap * 0.6) return DataAbnormalType.capacityTooLow;
// 电压异常:低于 3.0V 或高于 5.0V
if (voltage < 3.0 || voltage > 5.0) return DataAbnormalType.voltageAbnormal;
return DataAbnormalType.none;
}
// ---------- 辅助提取方法 ---------- // ---------- 辅助提取方法 ----------
int _extractCycle(String line) { int _extractCycle(String line) {
final reg = RegExp(r'cycle=(\d+)'); final reg = RegExp(r'cycle=(\d+)');
@@ -568,6 +619,13 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
? '充电中 · ${_getChargeTypeLabel()} · ${_getDisplayPower()}' ? '充电中 · ${_getChargeTypeLabel()} · ${_getDisplayPower()}'
: ''; : '';
// 🆕 1.7.0 异常检测
final abnormalType = _checkAbnormalType(
_currentCap,
_designCap,
_batteryVolt,
);
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: const SystemUiOverlayStyle( value: const SystemUiOverlayStyle(
statusBarColor: Color.fromRGBO(157, 212, 237, 0.1), statusBarColor: Color.fromRGBO(157, 212, 237, 0.1),
@@ -635,14 +693,12 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
body: _hasData body: _hasData
? Column( ? Column(
children: [ children: [
// 🟢 可滚动区域(带 RepaintBoundary
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// 设备型号
Text( Text(
_deviceModel, _deviceModel,
style: const TextStyle( style: const TextStyle(
@@ -653,19 +709,19 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
// 🟢 BatterySummary 用 RepaintBoundary 隔离 // 🟢 BatterySummary(带异常检测)
RepaintBoundary( RepaintBoundary(
child: BatterySummary( child: BatterySummary(
healthPercent: healthPercent, healthPercent: healthPercent,
cycleCount: _cycleCount, cycleCount: _cycleCount,
currentCap: _currentCap, currentCap: _currentCap,
designCap: _designCap, designCap: _designCap,
abnormalType: abnormalType, // 🆕 1.7.0
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
// 🟢 TrendChart 用 RepaintBoundary 隔离
RepaintBoundary( RepaintBoundary(
child: TrendChart( child: TrendChart(
data: _trendData, data: _trendData,
@@ -675,7 +731,6 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
const SizedBox(height: 8), const SizedBox(height: 8),
// 🟢 充电条目 - 保留 AnimatedOpacity,但加 RepaintBoundary
RepaintBoundary( RepaintBoundary(
child: AnimatedOpacity( child: AnimatedOpacity(
opacity: _isCharging ? 1.0 : 0.0, opacity: _isCharging ? 1.0 : 0.0,
@@ -706,7 +761,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
const SizedBox(height: 4), const SizedBox(height: 4),
// 🟢 BatteryInfoPanel 也用 RepaintBoundary // 🟢 BatteryInfoPanel(带异常检测)
RepaintBoundary( RepaintBoundary(
child: BatteryInfoPanel( child: BatteryInfoPanel(
batteryVolt: _batteryVolt, batteryVolt: _batteryVolt,
@@ -715,6 +770,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
minVolt: _minVolt, minVolt: _minVolt,
maxTemp: _maxTemp, maxTemp: _maxTemp,
minTemp: _minTemp, minTemp: _minTemp,
abnormalType: abnormalType, // 🆕 1.7.0
), ),
), ),
], ],
-1
View File
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // 🟢 添加这一行 import 'package:flutter/services.dart'; // 🟢 添加这一行
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
class ChangelogPage extends StatefulWidget { class ChangelogPage extends StatefulWidget {
+30 -7
View File
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import '../models/data_abnormal_type.dart';
class BatteryInfoPanel extends StatelessWidget { class BatteryInfoPanel extends StatelessWidget {
final double batteryVolt; final double batteryVolt;
@@ -8,6 +9,7 @@ class BatteryInfoPanel extends StatelessWidget {
final double minVolt; final double minVolt;
final double maxTemp; final double maxTemp;
final double minTemp; final double minTemp;
final DataAbnormalType abnormalType;
const BatteryInfoPanel({ const BatteryInfoPanel({
super.key, super.key,
@@ -17,10 +19,15 @@ class BatteryInfoPanel extends StatelessWidget {
required this.minVolt, required this.minVolt,
required this.maxTemp, required this.maxTemp,
required this.minTemp, required this.minTemp,
this.abnormalType = DataAbnormalType.none,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool isVoltageAbnormal =
abnormalType == DataAbnormalType.voltageAbnormal;
final Color voltColor = isVoltageAbnormal ? Colors.red : Colors.black;
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -32,6 +39,7 @@ class BatteryInfoPanel extends StatelessWidget {
_buildIcon('voltage'), _buildIcon('voltage'),
'电压', '电压',
'${batteryVolt.toStringAsFixed(3)}V', '${batteryVolt.toStringAsFixed(3)}V',
textColor: voltColor,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
@@ -52,7 +60,12 @@ class BatteryInfoPanel extends StatelessWidget {
color: Colors.red, color: Colors.red,
), ),
), ),
TextSpan(text: '${maxVolt.toStringAsFixed(3)}V'), TextSpan(
text: '${maxVolt.toStringAsFixed(3)}V',
style: TextStyle(
color: isVoltageAbnormal ? Colors.red : Colors.red,
),
),
], ],
), ),
), ),
@@ -73,7 +86,12 @@ class BatteryInfoPanel extends StatelessWidget {
color: Colors.blue, color: Colors.blue,
), ),
), ),
TextSpan(text: '${minVolt.toStringAsFixed(3)}V'), TextSpan(
text: '${minVolt.toStringAsFixed(3)}V',
style: TextStyle(
color: isVoltageAbnormal ? Colors.red : Colors.blue,
),
),
], ],
), ),
), ),
@@ -96,7 +114,7 @@ class BatteryInfoPanel extends StatelessWidget {
children: [ children: [
RichText( RichText(
text: TextSpan( text: TextSpan(
style: TextStyle( style: const TextStyle(
fontFamily: 'Flyme', fontFamily: 'Flyme',
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@@ -117,7 +135,7 @@ class BatteryInfoPanel extends StatelessWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
RichText( RichText(
text: TextSpan( text: TextSpan(
style: TextStyle( style: const TextStyle(
fontFamily: 'Flyme', fontFamily: 'Flyme',
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@@ -165,7 +183,12 @@ class BatteryInfoPanel extends StatelessWidget {
} }
} }
Widget _buildInfoItem(Widget icon, String label, String value) { Widget _buildInfoItem(
Widget icon,
String label,
String value, {
Color textColor = Colors.black,
}) {
return Row( return Row(
children: [ children: [
icon, icon,
@@ -181,11 +204,11 @@ class BatteryInfoPanel extends StatelessWidget {
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
value, value,
style: const TextStyle( style: TextStyle(
fontFamily: 'Flyme', fontFamily: 'Flyme',
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.black, color: textColor,
), ),
), ),
], ],
+28 -10
View File
@@ -1,10 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../models/data_abnormal_type.dart';
class BatterySummary extends StatelessWidget { class BatterySummary extends StatelessWidget {
final double healthPercent; final double healthPercent;
final int cycleCount; final int cycleCount;
final double currentCap; final double currentCap;
final int designCap; final int designCap;
final DataAbnormalType abnormalType;
const BatterySummary({ const BatterySummary({
super.key, super.key,
@@ -12,11 +14,25 @@ class BatterySummary extends StatelessWidget {
required this.cycleCount, required this.cycleCount,
required this.currentCap, required this.currentCap,
required this.designCap, required this.designCap,
this.abnormalType = DataAbnormalType.none,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool isLowHealth = healthPercent < 70; final bool isAbnormal = abnormalType != DataAbnormalType.none;
final bool isCapacityAbnormal =
abnormalType == DataAbnormalType.capacityTooLow ||
abnormalType == DataAbnormalType.capacityTooHigh;
// 健康度颜色:异常时红色,否则按阈值
final Color healthColor = isAbnormal
? Colors.red
: (healthPercent < 70 ? Colors.red : Colors.black);
// 容量颜色:异常时红色,否则灰色
final Color capacityColor = isCapacityAbnormal
? Colors.red
: const Color(0xFF5F5F5F);
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
@@ -33,7 +49,7 @@ class BatterySummary extends StatelessWidget {
fontSize: 40, fontSize: 40,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
height: 1.0, height: 1.0,
color: isLowHealth ? Colors.red : Colors.black, color: healthColor,
), ),
), ),
const SizedBox(width: 2), const SizedBox(width: 2),
@@ -44,7 +60,9 @@ class BatterySummary extends StatelessWidget {
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w300, fontWeight: FontWeight.w300,
height: 1.0, height: 1.0,
color: isLowHealth ? Colors.red : Colors.grey.shade600, color: healthColor == Colors.red
? Colors.red
: Colors.grey.shade600,
), ),
), ),
], ],
@@ -56,7 +74,7 @@ class BatterySummary extends StatelessWidget {
children: [ children: [
RichText( RichText(
text: TextSpan( text: TextSpan(
style: TextStyle( style: const TextStyle(
fontFamily: 'Flyme', fontFamily: 'Flyme',
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@@ -65,9 +83,9 @@ class BatterySummary extends StatelessWidget {
), ),
children: [ children: [
TextSpan(text: '$cycleCount'), TextSpan(text: '$cycleCount'),
TextSpan( const TextSpan(
text: ' cycles', text: ' cycles',
style: const TextStyle( style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC', fontFamily: 'HarmonyOS_Sans_SC',
color: Colors.black, color: Colors.black,
), ),
@@ -83,7 +101,7 @@ class BatterySummary extends StatelessWidget {
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w300, fontWeight: FontWeight.w300,
height: 1.0, height: 1.0,
color: const Color(0xFF5F5F5F), color: capacityColor,
), ),
children: [ children: [
TextSpan(text: currentCap.toStringAsFixed(0)), TextSpan(text: currentCap.toStringAsFixed(0)),
@@ -91,17 +109,17 @@ class BatterySummary extends StatelessWidget {
text: ' / ', text: ' / ',
style: TextStyle( style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC', fontFamily: 'HarmonyOS_Sans_SC',
color: const Color(0xFF5F5F5F).withValues(alpha: 0.5), color: capacityColor.withOpacity(0.5),
), ),
), ),
TextSpan(text: designCap.toString()), TextSpan(text: designCap.toString()),
TextSpan( TextSpan(
text: ' mAh', text: ' mAh',
style: const TextStyle( style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC', fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF5F5F5F), color: capacityColor,
), ),
), ),
], ],
+1 -1
View File
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.6.0 version: 1.7.0
environment: environment:
sdk: ^3.9.2 sdk: ^3.9.2