Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4829cd1da2 | ||
|
|
7989495521 | ||
|
|
1d033670f7 | ||
|
|
2512f88f30 |
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
# ============ Flutter 核心 ============
|
||||
-keep class io.flutter.** { *; }
|
||||
-keep class io.flutter.plugins.** { *; }
|
||||
-keep class io.flutter.plugin.** { *; }
|
||||
-keep class io.flutter.util.** { *; }
|
||||
-keep class io.flutter.view.** { *; }
|
||||
-keep class io.flutter.embedding.** { *; }
|
||||
|
||||
# 保留 Flutter 的 JNI 调用类
|
||||
-keep class io.flutter.embedding.engine.FlutterJNI { *; }
|
||||
-dontwarn io.flutter.embedding.**
|
||||
|
||||
# ============ Kotlin ============
|
||||
-keep class kotlin.** { *; }
|
||||
-keep class kotlin.Metadata { *; }
|
||||
-dontwarn kotlin.**
|
||||
-keepclassmembers class **$WhenMappings {
|
||||
<fields>;
|
||||
}
|
||||
-keepclassmembers class kotlin.Metadata {
|
||||
public <methods>;
|
||||
}
|
||||
|
||||
# ============ sqflite / SQLite ============
|
||||
-keep class com.tekartik.sqflite.** { *; }
|
||||
-dontwarn com.tekartik.sqflite.**
|
||||
|
||||
# ============ shared_preferences ============
|
||||
-keep class io.flutter.plugins.sharedpreferences.** { *; }
|
||||
|
||||
# ============ path_provider ============
|
||||
-keep class io.flutter.plugins.pathprovider.** { *; }
|
||||
|
||||
# ============ permission_handler ============
|
||||
-keep class com.baseflow.permissionhandler.** { *; }
|
||||
|
||||
# ============ url_launcher ============
|
||||
-keep class io.flutter.plugins.urllauncher.** { *; }
|
||||
|
||||
# ============ device_info_plus ============
|
||||
-keep class dev.fluttercommunity.plus.device_info.** { *; }
|
||||
|
||||
# ============ 你的原生通道(重要!) ============
|
||||
# 你的 MethodChannel / EventChannel 相关类需要保留
|
||||
-keep class com.lxh.battery_health.** { *; }
|
||||
|
||||
# ============ AndroidX 常见 ============
|
||||
-keep class androidx.lifecycle.** { *; }
|
||||
-dontwarn androidx.lifecycle.**
|
||||
|
||||
# ============ 保留 Parcelable / Serializable ============
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
public static final android.os.Parcelable$Creator *;
|
||||
}
|
||||
-keepclassmembers class * implements java.io.Serializable {
|
||||
static final long serialVersionUID;
|
||||
private static final java.io.ObjectStreamField[] serialPersistentFields;
|
||||
private void writeObject(java.io.ObjectOutputStream);
|
||||
private void readObject(java.io.ObjectInputStream);
|
||||
java.lang.Object writeReplace();
|
||||
java.lang.Object readResolve();
|
||||
}
|
||||
|
||||
# ============ 异常堆栈保留(方便排查 release 崩溃) ============
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-renamesourcefileattribute SourceFile
|
||||
-keepattributes Signature
|
||||
-keepattributes *Annotation*
|
||||
-keepattributes Exceptions
|
||||
-keepattributes InnerClasses
|
||||
-keepattributes EnclosingMethod
|
||||
|
||||
# ============ 移除日志(可选) ============
|
||||
# 如果你想在 release 里保留日志,注释掉下面这几行
|
||||
-assumenosideeffects class android.util.Log {
|
||||
public static *** v(...);
|
||||
public static *** d(...);
|
||||
public static *** i(...);
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,30 @@
|
||||
## 1.8.0
|
||||
|
||||
### 新增
|
||||
- **夜间模式**:支持浅色 / 深色 / 跟随系统三种模式,可在右上角菜单切换
|
||||
- **主题系统重构**:统一颜色抽象,所有界面颜色由主题管理
|
||||
|
||||
### 优化
|
||||
- 深色主题下潘通色系重新调色,保证对比度
|
||||
- AppBar 与内容区背景统一,消除浅色模式下的分界线
|
||||
|
||||
----
|
||||
|
||||
## [1.7.1] - 2026-09-16
|
||||
|
||||
### 新增
|
||||
- **平均温度显示**:温度面板新增平均温度,与最高/最低温度同排展示(`↑ xx.x℃ ↓ xx.x℃ ⌀ xx.x℃`),更直观了解电池工作温度区间
|
||||
|
||||
### 优化
|
||||
- **数据库升级至 v2**:新增 `temp_avg` 字段,历史数据自动迁移,每日平均温度随扫描自动入库
|
||||
- **构建工具链同步**:适配最新 Flutter SDK 的 Android 构建要求(AGP 9.0 / Kotlin 2.2 / Gradle 9.3)
|
||||
- **Release 打包加固**:补齐 ProGuard 混淆规则,release 版本构建更稳定
|
||||
|
||||
### 修复
|
||||
- 修复 Release 版本因缺少混淆规则文件导致打包失败的问题
|
||||
|
||||
----
|
||||
|
||||
## [1.7.0] - 2026-07-31
|
||||
|
||||
### ✨ 新增功能
|
||||
|
||||
+65
-45
@@ -1,11 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'pages/battery_health_page.dart';
|
||||
import 'services/saf_storage_service.dart';
|
||||
import 'theme/app_colors.dart';
|
||||
import 'theme/theme_service.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await SafStorageService().init();
|
||||
await ThemeService.init(); // 🆕
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
@@ -15,53 +18,70 @@ class MyApp extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: '电池健康度',
|
||||
theme: ThemeData(
|
||||
// 🟢 中文用 HarmonyOS Sans SC
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
// 🟢 数字回退到 FlymeFont
|
||||
fontFamilyFallback: const ['Flyme'],
|
||||
// 🟢 统一文字颜色为黑色
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: const Color(0xFFFFFFFF),
|
||||
).copyWith(onSurface: Colors.black),
|
||||
textTheme: const TextTheme(
|
||||
// 大标题/数字(健康度、容量等)
|
||||
displayMedium: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontFamilyFallback: ['Flyme'],
|
||||
color: Colors.black,
|
||||
),
|
||||
// 标题(如“容量百分比变动趋势”)
|
||||
headlineMedium: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontFamilyFallback: ['Flyme'],
|
||||
color: Colors.black,
|
||||
),
|
||||
// 正文(电压、温度、循环次数等)
|
||||
bodyMedium: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontFamilyFallback: ['Flyme'],
|
||||
color: Colors.black,
|
||||
),
|
||||
// 小字(日期、辅助信息)
|
||||
bodySmall: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontFamilyFallback: ['Flyme'],
|
||||
color: Colors.black87,
|
||||
),
|
||||
// 数字专用(大号数字,如健康度百分比)
|
||||
displayLarge: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontFamilyFallback: ['Flyme'],
|
||||
color: Colors.black,
|
||||
),
|
||||
return ValueListenableBuilder<ThemeMode>(
|
||||
valueListenable: ThemeService.notifier,
|
||||
builder: (context, mode, _) {
|
||||
return MaterialApp(
|
||||
title: '电池健康度',
|
||||
themeMode: mode,
|
||||
theme: _buildTheme(Brightness.light, kLightColors),
|
||||
darkTheme: _buildTheme(Brightness.dark, kDarkColors),
|
||||
home: const BatteryHealthPage(),
|
||||
debugShowCheckedModeBanner: false,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
ThemeData _buildTheme(Brightness brightness, AppColors colors) {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: brightness,
|
||||
scaffoldBackgroundColor: colors.background,
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontFamilyFallback: const ['Flyme'],
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: colors.background,
|
||||
brightness: brightness,
|
||||
),
|
||||
extensions: [colors], // 🆕 注册 ThemeExtension
|
||||
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: colors.background, // ← 关键:和 Scaffold 一致
|
||||
foregroundColor: colors.textPrimary,
|
||||
// ← 关掉 M3 的滑动变色
|
||||
elevation: 0,
|
||||
|
||||
iconTheme: IconThemeData(color: colors.textPrimary),
|
||||
),
|
||||
|
||||
textTheme: TextTheme(
|
||||
displayMedium: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontFamilyFallback: const ['Flyme'],
|
||||
color: colors.textPrimary,
|
||||
),
|
||||
useMaterial3: true,
|
||||
headlineMedium: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontFamilyFallback: const ['Flyme'],
|
||||
color: colors.textPrimary,
|
||||
),
|
||||
bodyMedium: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontFamilyFallback: const ['Flyme'],
|
||||
color: colors.textPrimary,
|
||||
),
|
||||
bodySmall: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontFamilyFallback: const ['Flyme'],
|
||||
color: colors.textSecondary,
|
||||
),
|
||||
displayLarge: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontFamilyFallback: const ['Flyme'],
|
||||
color: colors.textPrimary,
|
||||
),
|
||||
),
|
||||
home: const BatteryHealthPage(),
|
||||
debugShowCheckedModeBanner: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+19
-37
@@ -1,16 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../theme/app_colors.dart';
|
||||
|
||||
class AboutPage extends StatelessWidget {
|
||||
const AboutPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final c = context.colors;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: AppBar(
|
||||
title: const Text(
|
||||
'关于',
|
||||
@@ -19,10 +19,8 @@ class AboutPage extends StatelessWidget {
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
backgroundColor: c.background,
|
||||
elevation: 0,
|
||||
foregroundColor: Theme.of(context).iconTheme.color,
|
||||
iconTheme: IconThemeData(color: Theme.of(context).iconTheme.color),
|
||||
),
|
||||
body: Center(
|
||||
child: Padding(
|
||||
@@ -30,15 +28,12 @@ class AboutPage extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Logo
|
||||
Container(
|
||||
width: 120,
|
||||
height: 120,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: isDark
|
||||
? Colors.grey.shade800
|
||||
: const Color(0xFFF0F4F8),
|
||||
color: c.surface,
|
||||
),
|
||||
child: ClipOval(
|
||||
child: Image.asset(
|
||||
@@ -50,38 +45,39 @@ class AboutPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
Text(
|
||||
'MEIZU Battery Healthy',
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: c.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'版本 1.7.0',
|
||||
'版本 1.8.0',
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade500,
|
||||
color: c.textTertiary,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Divider(
|
||||
color: Colors.grey.shade300,
|
||||
color: c.divider,
|
||||
thickness: 0.5,
|
||||
indent: 40,
|
||||
endIndent: 40,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
Text(
|
||||
'专机专配 · 仅针对魅族独家开发',
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
color: c.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -90,13 +86,12 @@ class AboutPage extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 13,
|
||||
color: Colors.grey.shade400,
|
||||
color: c.textTertiary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// 源码链接按钮
|
||||
InkWell(
|
||||
onTap: () => _launchUrl(context), // 注意这里传入了 context
|
||||
onTap: () => _launchUrl(context),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
@@ -104,34 +99,24 @@ class AboutPage extends StatelessWidget {
|
||||
vertical: 12,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark
|
||||
? Colors.grey.shade800
|
||||
: const Color(0xFFF0F4F8),
|
||||
color: c.surface,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.code,
|
||||
size: 18,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
Icon(Icons.code, size: 18, color: c.textPrimary),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'查看源码 / 反馈',
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
color: c.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.open_in_new,
|
||||
size: 14,
|
||||
color: Colors.grey.shade500,
|
||||
),
|
||||
Icon(Icons.open_in_new, size: 14, color: c.textTertiary),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -142,7 +127,7 @@ class AboutPage extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade400,
|
||||
color: c.textTertiary,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -155,14 +140,11 @@ class AboutPage extends StatelessWidget {
|
||||
Future<void> _launchUrl(BuildContext context) async {
|
||||
const url = 'https://git.whitetop.xyz/lxh2875931338/MEIZU-Battery-Healthy';
|
||||
final uri = Uri.parse(url);
|
||||
|
||||
try {
|
||||
// ✅ 直接尝试打开,不再检查 canLaunchUrl
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
} catch (e) {
|
||||
// 如果打开失败,复制链接并提示
|
||||
debugPrint('打开链接失败: $e');
|
||||
await Clipboard.setData(ClipboardData(text: url));
|
||||
await Clipboard.setData(const ClipboardData(text: url));
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
|
||||
@@ -3,6 +3,8 @@ 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 '../theme/theme_service.dart';
|
||||
import '../theme/app_colors.dart';
|
||||
|
||||
// --- 本地导入 ---
|
||||
import '../models/device_matching.dart';
|
||||
@@ -626,6 +628,56 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
|
||||
return ChargeUtils.getChargeTypeLabel(_isWireless, _chargeType);
|
||||
}
|
||||
|
||||
// 🆕 夜间模式切换弹窗 ← 加在这里
|
||||
void _showThemePicker() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (ctx) {
|
||||
final current = ThemeService.notifier.value;
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.brightness_auto),
|
||||
title: const Text('跟随系统'),
|
||||
trailing: current == ThemeMode.system
|
||||
? const Icon(Icons.check)
|
||||
: null,
|
||||
onTap: () {
|
||||
ThemeService.setMode(ThemeMode.system);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.light_mode),
|
||||
title: const Text('浅色'),
|
||||
trailing: current == ThemeMode.light
|
||||
? const Icon(Icons.check)
|
||||
: null,
|
||||
onTap: () {
|
||||
ThemeService.setMode(ThemeMode.light);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.dark_mode),
|
||||
title: const Text('深色'),
|
||||
trailing: current == ThemeMode.dark
|
||||
? const Icon(Icons.check)
|
||||
: null,
|
||||
onTap: () {
|
||||
ThemeService.setMode(ThemeMode.dark);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// ---------- Build ----------
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -645,25 +697,23 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
|
||||
);
|
||||
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: const SystemUiOverlayStyle(
|
||||
statusBarColor: Color.fromRGBO(157, 212, 237, 0.1),
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarColor: context.colors.statusBarTint,
|
||||
statusBarIconBrightness: Brightness.dark,
|
||||
statusBarBrightness: Brightness.dark,
|
||||
),
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
// backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
// backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
shadowColor: Colors.transparent,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
title: const Text(
|
||||
|
||||
title: Text(
|
||||
'电池健康度',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black,
|
||||
color: context.colors.textPrimary,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
@@ -671,11 +721,11 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: readBatteryCapacity,
|
||||
tooltip: '刷新数据',
|
||||
color: Colors.black,
|
||||
color: context.colors.icon,
|
||||
),
|
||||
PopupMenuButton<String>(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
color: Colors.white,
|
||||
color: context.colors.surface,
|
||||
onSelected: (value) {
|
||||
switch (value) {
|
||||
case 'changelog':
|
||||
@@ -695,7 +745,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
|
||||
);
|
||||
break;
|
||||
case 'dark_mode':
|
||||
// 夜间模式切换逻辑(1.7.0 再实现)
|
||||
_showThemePicker();
|
||||
break;
|
||||
}
|
||||
},
|
||||
@@ -705,11 +755,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
|
||||
value: 'about', // 🟢 新增
|
||||
child: Text('关于'),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
value: 'dark_mode',
|
||||
enabled: false, // 暂时灰显
|
||||
child: Text('夜间模式(开发中)'),
|
||||
),
|
||||
const PopupMenuItem(value: 'dark_mode', child: Text('夜间模式')),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -725,9 +771,9 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
|
||||
children: [
|
||||
Text(
|
||||
_deviceModel,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
color: context.colors.textTertiary,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
@@ -804,19 +850,29 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
|
||||
),
|
||||
],
|
||||
)
|
||||
: const Center(
|
||||
: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.battery_unknown, size: 64, color: Colors.grey),
|
||||
SizedBox(height: 16),
|
||||
Icon(
|
||||
Icons.battery_unknown,
|
||||
size: 64,
|
||||
color: context.colors.textTertiary,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'暂无数据',
|
||||
style: TextStyle(fontSize: 18, color: Colors.grey),
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: context.colors.textTertiary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'点击右上角刷新尝试',
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: context.colors.textTertiary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart'; // 🟢 添加这一行
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import '../theme/app_colors.dart';
|
||||
|
||||
class ChangelogPage extends StatefulWidget {
|
||||
const ChangelogPage({super.key});
|
||||
@@ -15,33 +16,27 @@ class _ChangelogPageState extends State<ChangelogPage>
|
||||
String _markdownContent = '';
|
||||
bool _isLoading = true;
|
||||
String? _error;
|
||||
Widget? _cachedMarkdown;
|
||||
|
||||
// ---------- 滚动 ----------
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
double _scrollProgress = 0.0;
|
||||
bool _isScrolling = false;
|
||||
|
||||
// ---------- 呼吸动画(维持高刷感知) ----------
|
||||
// ---------- 呼吸动画 ----------
|
||||
late AnimationController _breathController;
|
||||
late Animation<double> _breathAnimation;
|
||||
|
||||
// ---------- 原生刷新率控制 ----------
|
||||
// 🟢 改用 static final(不是 const)
|
||||
static final _frameRateChannel = MethodChannel(
|
||||
'com.lxh.battery_health/frame_rate',
|
||||
);
|
||||
|
||||
// ---------- 生命周期 ----------
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadChangelog();
|
||||
|
||||
// 进入页面时申请高刷(原生层)
|
||||
_frameRateChannel.invokeMethod('setHighRefreshRate');
|
||||
|
||||
// 呼吸动画:让进度条在滚动时轻微抖动,维持高刷感知
|
||||
_breathController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
@@ -70,16 +65,13 @@ class _ChangelogPageState extends State<ChangelogPage>
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// 离开页面时恢复默认刷新率
|
||||
_frameRateChannel.invokeMethod('clearRefreshRate');
|
||||
|
||||
_scrollController.removeListener(_updateProgress);
|
||||
_scrollController.dispose();
|
||||
_breathController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// ---------- 方法 ----------
|
||||
void _updateProgress() {
|
||||
if (!_scrollController.hasClients) return;
|
||||
final maxScroll = _scrollController.position.maxScrollExtent;
|
||||
@@ -96,7 +88,6 @@ class _ChangelogPageState extends State<ChangelogPage>
|
||||
setState(() {
|
||||
_markdownContent = content;
|
||||
_isLoading = false;
|
||||
_cachedMarkdown = _buildMarkdown(content);
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
@@ -106,78 +97,74 @@ class _ChangelogPageState extends State<ChangelogPage>
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildMarkdown(String content) {
|
||||
Widget _buildMarkdown(BuildContext context, String content) {
|
||||
final c = context.colors;
|
||||
return Markdown(
|
||||
data: content,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
styleSheet: MarkdownStyleSheet(
|
||||
h1: const TextStyle(
|
||||
h1: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.black,
|
||||
color: c.textPrimary,
|
||||
),
|
||||
h2: const TextStyle(
|
||||
h2: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
color: c.textPrimary,
|
||||
),
|
||||
h3: const TextStyle(
|
||||
h3: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
color: c.textSecondary,
|
||||
),
|
||||
p: const TextStyle(
|
||||
p: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 16,
|
||||
color: Colors.black87,
|
||||
color: c.textSecondary,
|
||||
),
|
||||
listBullet: const TextStyle(
|
||||
listBullet: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 16,
|
||||
color: Colors.black87,
|
||||
color: c.textSecondary,
|
||||
),
|
||||
code: const TextStyle(
|
||||
code: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 14,
|
||||
color: Colors.black87,
|
||||
backgroundColor: Color(0xFFF5F5F5),
|
||||
color: c.textSecondary,
|
||||
backgroundColor: c.surface,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ---------- Build ----------
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final c = context.colors;
|
||||
final breathOffset = _isScrolling ? _breathAnimation.value : 0.0;
|
||||
final displayProgress = (_scrollProgress + breathOffset).clamp(0.0, 1.0);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
title: const Text(
|
||||
title: Text(
|
||||
'更新日志',
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
color: Colors.black,
|
||||
color: c.textPrimary,
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
foregroundColor: Colors.black,
|
||||
iconTheme: const IconThemeData(color: Colors.black),
|
||||
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(2.0),
|
||||
child: LinearProgressIndicator(
|
||||
value: displayProgress,
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||
Color.fromRGBO(157, 212, 237, 1.0),
|
||||
),
|
||||
backgroundColor: c.divider,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(c.progressBar),
|
||||
minHeight: 2.0,
|
||||
),
|
||||
),
|
||||
@@ -188,16 +175,16 @@ class _ChangelogPageState extends State<ChangelogPage>
|
||||
? Center(
|
||||
child: Text(
|
||||
_error!,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
color: Colors.grey,
|
||||
color: c.textTertiary,
|
||||
),
|
||||
),
|
||||
)
|
||||
: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: _cachedMarkdown ?? const SizedBox.shrink(),
|
||||
child: _buildMarkdown(context, _markdownContent),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@immutable
|
||||
class AppColors extends ThemeExtension<AppColors> {
|
||||
final Color background; // 页面背景
|
||||
final Color surface; // 卡片/面板背景
|
||||
final Color textPrimary; // 主文字
|
||||
final Color textSecondary; // 次要文字
|
||||
final Color textTertiary; // 弱化文字
|
||||
final Color divider; // 分割线
|
||||
final Color icon; // 图标色
|
||||
|
||||
final Color healthGreen; // 健康度 ≥80%
|
||||
final Color healthYellow; // 70~80%
|
||||
final Color healthOrange;
|
||||
final Color healthRed; // <70% 或异常
|
||||
final Color barEmpty; // 无数据柱
|
||||
|
||||
final Color voltUp; // 电压↑
|
||||
final Color voltDown; // 电压↓
|
||||
final Color tempUp;
|
||||
final Color tempDown;
|
||||
|
||||
final Color progressBar; // 更新日志进度条
|
||||
final Color statusBarTint; // 状态栏背景
|
||||
|
||||
const AppColors({
|
||||
required this.background,
|
||||
required this.surface,
|
||||
required this.textPrimary,
|
||||
required this.textSecondary,
|
||||
required this.textTertiary,
|
||||
required this.divider,
|
||||
required this.icon,
|
||||
required this.healthGreen,
|
||||
required this.healthYellow,
|
||||
required this.healthOrange,
|
||||
required this.healthRed,
|
||||
required this.barEmpty,
|
||||
required this.voltUp,
|
||||
required this.voltDown,
|
||||
required this.tempUp,
|
||||
required this.tempDown,
|
||||
required this.progressBar,
|
||||
required this.statusBarTint,
|
||||
});
|
||||
|
||||
@override
|
||||
AppColors copyWith({
|
||||
Color? background,
|
||||
Color? surface,
|
||||
Color? textPrimary,
|
||||
Color? textSecondary,
|
||||
Color? textTertiary,
|
||||
Color? divider,
|
||||
Color? icon,
|
||||
Color? healthGreen,
|
||||
Color? healthYellow,
|
||||
Color? healthOrange,
|
||||
Color? healthRed,
|
||||
Color? barEmpty,
|
||||
Color? voltUp,
|
||||
Color? voltDown,
|
||||
Color? tempUp,
|
||||
Color? tempDown,
|
||||
Color? progressBar,
|
||||
Color? statusBarTint,
|
||||
}) {
|
||||
return AppColors(
|
||||
background: background ?? this.background,
|
||||
surface: surface ?? this.surface,
|
||||
textPrimary: textPrimary ?? this.textPrimary,
|
||||
textSecondary: textSecondary ?? this.textSecondary,
|
||||
textTertiary: textTertiary ?? this.textTertiary,
|
||||
divider: divider ?? this.divider,
|
||||
icon: icon ?? this.icon,
|
||||
healthGreen: healthGreen ?? this.healthGreen,
|
||||
healthYellow: healthYellow ?? this.healthYellow,
|
||||
healthOrange: healthOrange ?? this.healthOrange,
|
||||
healthRed: healthRed ?? this.healthRed,
|
||||
barEmpty: barEmpty ?? this.barEmpty,
|
||||
voltUp: voltUp ?? this.voltUp,
|
||||
voltDown: voltDown ?? this.voltDown,
|
||||
tempUp: tempUp ?? this.tempUp,
|
||||
tempDown: tempDown ?? this.tempDown,
|
||||
progressBar: progressBar ?? this.progressBar,
|
||||
statusBarTint: statusBarTint ?? this.statusBarTint,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AppColors lerp(ThemeExtension<AppColors>? other, double t) {
|
||||
if (other is! AppColors) return this;
|
||||
return AppColors(
|
||||
background: Color.lerp(background, other.background, t)!,
|
||||
surface: Color.lerp(surface, other.surface, t)!,
|
||||
textPrimary: Color.lerp(textPrimary, other.textPrimary, t)!,
|
||||
textSecondary: Color.lerp(textSecondary, other.textSecondary, t)!,
|
||||
textTertiary: Color.lerp(textTertiary, other.textTertiary, t)!,
|
||||
divider: Color.lerp(divider, other.divider, t)!,
|
||||
icon: Color.lerp(icon, other.icon, t)!,
|
||||
healthGreen: Color.lerp(healthGreen, other.healthGreen, t)!,
|
||||
healthYellow: Color.lerp(healthYellow, other.healthYellow, t)!,
|
||||
healthOrange: Color.lerp(healthOrange, other.healthOrange, t)!,
|
||||
healthRed: Color.lerp(healthRed, other.healthRed, t)!,
|
||||
barEmpty: Color.lerp(barEmpty, other.barEmpty, t)!,
|
||||
voltUp: Color.lerp(voltUp, other.voltUp, t)!,
|
||||
voltDown: Color.lerp(voltDown, other.voltDown, t)!,
|
||||
tempUp: Color.lerp(tempUp, other.tempUp, t)!,
|
||||
tempDown: Color.lerp(tempDown, other.tempDown, t)!,
|
||||
progressBar: Color.lerp(progressBar, other.progressBar, t)!,
|
||||
statusBarTint: Color.lerp(statusBarTint, other.statusBarTint, t)!,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 浅色主题(保持现有视觉)
|
||||
const AppColors kLightColors = AppColors(
|
||||
background: Colors.white,
|
||||
surface: Color(0xFFF0F4F8),
|
||||
textPrimary: Colors.black,
|
||||
textSecondary: Color(0xFF5F5F5F),
|
||||
textTertiary: Colors.grey,
|
||||
divider: Color(0xFFE0E0E0),
|
||||
icon: Colors.black,
|
||||
healthGreen: Color(0xFF00B140),
|
||||
healthYellow: Color(0xFFEFDF00),
|
||||
healthOrange: Color(0xFFD75A16),
|
||||
healthRed: Colors.red,
|
||||
barEmpty: Color(0xFFE0E0E0),
|
||||
voltUp: Colors.red,
|
||||
voltDown: Colors.blue,
|
||||
tempUp: Colors.red,
|
||||
tempDown: Colors.blue,
|
||||
progressBar: Color.fromRGBO(157, 212, 237, 1.0),
|
||||
statusBarTint: Color.fromRGBO(157, 212, 237, 0.1),
|
||||
);
|
||||
|
||||
/// 深色主题(色相保留,明度调整)
|
||||
const AppColors kDarkColors = AppColors(
|
||||
background: Color(0xFF121212),
|
||||
surface: Color(0xFF1E1E1E),
|
||||
textPrimary: Color(0xFFE8E8E8),
|
||||
textSecondary: Color(0xFFA0A0A0),
|
||||
textTertiary: Color(0xFF707070),
|
||||
divider: Color(0xFF2A2A2A),
|
||||
icon: Color(0xFFE8E8E8),
|
||||
healthGreen: Color(0xFF00D14D),
|
||||
healthYellow: Color(0xFFD4C520),
|
||||
healthOrange: Color(0xFFFF7838),
|
||||
healthRed: Color(0xFFFF5252),
|
||||
barEmpty: Color(0xFF2A2A2A),
|
||||
voltUp: Color(0xFFFF5252),
|
||||
voltDown: Color(0xFF5AA9FF),
|
||||
tempUp: Color(0xFFFF5252),
|
||||
tempDown: Color(0xFF5AA9FF),
|
||||
progressBar: Color(0xFF5A9BBD),
|
||||
statusBarTint: Color(0xFF121212),
|
||||
);
|
||||
|
||||
/// 便捷扩展:context.colors.textPrimary
|
||||
extension AppColorsX on BuildContext {
|
||||
AppColors get colors => Theme.of(this).extension<AppColors>()!;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class ThemeService {
|
||||
static const _prefKey = 'theme_mode';
|
||||
static final ValueNotifier<ThemeMode> notifier = ValueNotifier(
|
||||
ThemeMode.system,
|
||||
);
|
||||
|
||||
/// 启动时调用(main.dart)
|
||||
static Future<void> init() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final saved = prefs.getString(_prefKey);
|
||||
notifier.value = _parse(saved);
|
||||
}
|
||||
|
||||
/// 菜单里调用
|
||||
static Future<void> setMode(ThemeMode mode) async {
|
||||
notifier.value = mode;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_prefKey, _name(mode));
|
||||
}
|
||||
|
||||
static ThemeMode _parse(String? s) {
|
||||
switch (s) {
|
||||
case 'light':
|
||||
return ThemeMode.light;
|
||||
case 'dark':
|
||||
return ThemeMode.dark;
|
||||
default:
|
||||
return ThemeMode.system;
|
||||
}
|
||||
}
|
||||
|
||||
static String _name(ThemeMode m) {
|
||||
switch (m) {
|
||||
case ThemeMode.light:
|
||||
return 'light';
|
||||
case ThemeMode.dark:
|
||||
return 'dark';
|
||||
case ThemeMode.system:
|
||||
return 'system';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import '../models/data_abnormal_type.dart';
|
||||
import '../theme/app_colors.dart';
|
||||
|
||||
class BatteryInfoPanel extends StatelessWidget {
|
||||
final double batteryVolt;
|
||||
@@ -9,9 +10,8 @@ class BatteryInfoPanel extends StatelessWidget {
|
||||
final double minVolt;
|
||||
final double maxTemp;
|
||||
final double minTemp;
|
||||
final DataAbnormalType abnormalType;
|
||||
|
||||
final double tempAvg;
|
||||
final DataAbnormalType abnormalType;
|
||||
|
||||
const BatteryInfoPanel({
|
||||
super.key,
|
||||
@@ -27,19 +27,22 @@ class BatteryInfoPanel extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final c = context.colors;
|
||||
final bool isVoltageAbnormal =
|
||||
abnormalType == DataAbnormalType.voltageAbnormal;
|
||||
final Color voltColor = isVoltageAbnormal ? Colors.red : Colors.black;
|
||||
final Color voltColor = isVoltageAbnormal ? c.healthRed : c.textPrimary;
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// ---------- 电压列 ----------
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildInfoItem(
|
||||
_buildIcon('voltage'),
|
||||
context,
|
||||
_buildIcon(context, 'voltage'),
|
||||
'电压',
|
||||
'${batteryVolt.toStringAsFixed(3)}V',
|
||||
textColor: voltColor,
|
||||
@@ -53,22 +56,17 @@ class BatteryInfoPanel extends StatelessWidget {
|
||||
fontFamily: 'Flyme',
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.red,
|
||||
color: c.voltUp,
|
||||
),
|
||||
children: [
|
||||
const TextSpan(
|
||||
TextSpan(
|
||||
text: '↑ ',
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: '${maxVolt.toStringAsFixed(3)}V',
|
||||
style: TextStyle(
|
||||
color: isVoltageAbnormal ? Colors.red : Colors.red,
|
||||
color: c.voltUp,
|
||||
),
|
||||
),
|
||||
TextSpan(text: '${maxVolt.toStringAsFixed(3)}V'),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -79,22 +77,17 @@ class BatteryInfoPanel extends StatelessWidget {
|
||||
fontFamily: 'Flyme',
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.blue,
|
||||
color: c.voltDown,
|
||||
),
|
||||
children: [
|
||||
const TextSpan(
|
||||
TextSpan(
|
||||
text: '↓ ',
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: '${minVolt.toStringAsFixed(3)}V',
|
||||
style: TextStyle(
|
||||
color: isVoltageAbnormal ? Colors.red : Colors.blue,
|
||||
color: c.voltDown,
|
||||
),
|
||||
),
|
||||
TextSpan(text: '${minVolt.toStringAsFixed(3)}V'),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -103,33 +96,34 @@ class BatteryInfoPanel extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
// ---------- 温度列 ----------
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildInfoItem(
|
||||
_buildIcon('temperature'),
|
||||
context,
|
||||
_buildIcon(context, 'temperature'),
|
||||
'温度',
|
||||
'${batteryTemp.toStringAsFixed(0)}℃',
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// 🆕 一行显示:↑ max ↓ min [avg] avg
|
||||
Row(
|
||||
children: [
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontFamily: 'Flyme',
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.red,
|
||||
color: c.tempUp,
|
||||
),
|
||||
children: [
|
||||
const TextSpan(
|
||||
TextSpan(
|
||||
text: '↑ ',
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
color: Colors.red,
|
||||
color: c.tempUp,
|
||||
),
|
||||
),
|
||||
TextSpan(text: '${maxTemp.toStringAsFixed(1)}℃'),
|
||||
@@ -139,18 +133,18 @@ class BatteryInfoPanel extends StatelessWidget {
|
||||
const SizedBox(width: 6),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontFamily: 'Flyme',
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.blue,
|
||||
color: c.tempDown,
|
||||
),
|
||||
children: [
|
||||
const TextSpan(
|
||||
TextSpan(
|
||||
text: '↓ ',
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
color: Colors.blue,
|
||||
color: c.tempDown,
|
||||
),
|
||||
),
|
||||
TextSpan(text: '${minTemp.toStringAsFixed(1)}℃'),
|
||||
@@ -158,15 +152,15 @@ class BatteryInfoPanel extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
_buildIcon('avg', size: 14),
|
||||
_buildIcon(context, 'avg', size: 14),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
'${tempAvg.toStringAsFixed(1)}℃',
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontFamily: 'Flyme',
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey,
|
||||
color: c.textTertiary,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -178,50 +172,54 @@ class BatteryInfoPanel extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIcon(String type, {double size = 18}) {
|
||||
Widget _buildIcon(BuildContext context, String type, {double size = 18}) {
|
||||
final c = context.colors;
|
||||
final filter = ColorFilter.mode(c.textTertiary, BlendMode.srcIn);
|
||||
switch (type) {
|
||||
case 'voltage':
|
||||
return SvgPicture.asset(
|
||||
'assets/icons/si--lightning-fill.svg',
|
||||
width: size,
|
||||
height: size,
|
||||
colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
|
||||
colorFilter: filter,
|
||||
);
|
||||
case 'temperature':
|
||||
return SvgPicture.asset(
|
||||
'assets/icons/mdi--temperature.svg',
|
||||
width: size,
|
||||
height: size,
|
||||
colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
|
||||
colorFilter: filter,
|
||||
);
|
||||
case 'avg': // 🆕
|
||||
case 'avg':
|
||||
return SvgPicture.asset(
|
||||
'assets/icons/avg.svg',
|
||||
width: size,
|
||||
height: size,
|
||||
colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
|
||||
colorFilter: filter,
|
||||
);
|
||||
default:
|
||||
return Icon(Icons.help_outline, size: size, color: Colors.grey);
|
||||
return Icon(Icons.help_outline, size: size, color: c.textTertiary);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildInfoItem(
|
||||
BuildContext context,
|
||||
Widget icon,
|
||||
String label,
|
||||
String value, {
|
||||
Color textColor = Colors.black,
|
||||
Color? textColor,
|
||||
}) {
|
||||
final c = context.colors;
|
||||
return Row(
|
||||
children: [
|
||||
icon,
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
color: c.textTertiary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
@@ -231,7 +229,7 @@ class BatteryInfoPanel extends StatelessWidget {
|
||||
fontFamily: 'Flyme',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textColor,
|
||||
color: textColor ?? c.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../models/data_abnormal_type.dart';
|
||||
import '../theme/app_colors.dart';
|
||||
|
||||
class BatterySummary extends StatelessWidget {
|
||||
final double healthPercent;
|
||||
@@ -19,25 +20,23 @@ class BatterySummary extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final c = context.colors;
|
||||
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);
|
||||
? c.healthRed
|
||||
: (healthPercent < 70 ? c.healthRed : c.textPrimary);
|
||||
|
||||
// 容量颜色:异常时红色,否则灰色
|
||||
final Color capacityColor = isCapacityAbnormal
|
||||
? Colors.red
|
||||
: const Color(0xFF5F5F5F);
|
||||
? c.healthRed
|
||||
: c.textSecondary;
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
// 健康度大数字
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
@@ -60,34 +59,33 @@ class BatterySummary extends StatelessWidget {
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w300,
|
||||
height: 1.0,
|
||||
color: healthColor == Colors.red
|
||||
? Colors.red
|
||||
: Colors.grey.shade600,
|
||||
color: healthColor == c.healthRed
|
||||
? c.healthRed
|
||||
: c.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
// cycles + mAh
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontFamily: 'Flyme',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.2,
|
||||
color: Colors.black,
|
||||
color: c.textPrimary,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: '$cycleCount'),
|
||||
const TextSpan(
|
||||
TextSpan(
|
||||
text: ' cycles',
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
color: Colors.black,
|
||||
color: c.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../utils/charge_utils.dart';
|
||||
import '../theme/app_colors.dart';
|
||||
|
||||
class ChargeDetailPage extends StatelessWidget {
|
||||
final String chargeType;
|
||||
@@ -41,50 +42,48 @@ class ChargeDetailPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final c = context.colors;
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: const SystemUiOverlayStyle(
|
||||
statusBarColor: Color.fromRGBO(157, 212, 237, 0.1),
|
||||
statusBarIconBrightness: Brightness.dark,
|
||||
statusBarBrightness: Brightness.dark,
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarColor: c.statusBarTint,
|
||||
statusBarIconBrightness: isDark ? Brightness.light : Brightness.dark,
|
||||
statusBarBrightness: Theme.of(context).brightness,
|
||||
),
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
title: const Text('充电详情'),
|
||||
backgroundColor: Colors.white,
|
||||
backgroundColor: c.background,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
shadowColor: Colors.transparent,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
foregroundColor: Colors.black,
|
||||
iconTheme: const IconThemeData(color: Colors.black),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTextRow('协议', _getProtocolDisplay()),
|
||||
_buildTextRow(context, '协议', _getProtocolDisplay()),
|
||||
const Divider(),
|
||||
|
||||
if (!isWireless) ...[
|
||||
_buildTextRow('标定档位', _getRequestedDisplay()),
|
||||
_buildTextRow(context, '标定档位', _getRequestedDisplay()),
|
||||
const Divider(),
|
||||
_buildTextRow('实际功率', _getActualDisplay()),
|
||||
_buildTextRow(context, '实际功率', _getActualDisplay()),
|
||||
const Divider(),
|
||||
] else ...[
|
||||
_buildTextRow('功率', ChargeUtils.getDisplayPowerWireless(power)),
|
||||
_buildTextRow(
|
||||
context,
|
||||
'功率',
|
||||
ChargeUtils.getDisplayPowerWireless(power),
|
||||
),
|
||||
const Divider(),
|
||||
],
|
||||
|
||||
_buildDetailRow('电压', volt, 'V'),
|
||||
_buildDetailRow(context, '电压', volt, 'V'),
|
||||
const Divider(),
|
||||
_buildDetailRow('电流', curr, 'A'),
|
||||
_buildDetailRow(context, '电流', curr, 'A'),
|
||||
const Divider(),
|
||||
|
||||
if (isWireless) _buildTextRow('类型', '无线'),
|
||||
if (!isWireless && wiredLevel == null) _buildTextRow('档位', '自动'),
|
||||
|
||||
if (isWireless) _buildTextRow(context, '类型', '无线'),
|
||||
if (!isWireless && wiredLevel == null)
|
||||
_buildTextRow(context, '档位', '自动'),
|
||||
const Spacer(),
|
||||
Center(
|
||||
child: Text(
|
||||
@@ -92,7 +91,7 @@ class ChargeDetailPage extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade500,
|
||||
color: c.textTertiary,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -103,8 +102,8 @@ class ChargeDetailPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// 纯文本行(协议、功率、档位等)
|
||||
Widget _buildTextRow(String label, String value) {
|
||||
Widget _buildTextRow(BuildContext context, String label, String value) {
|
||||
final c = context.colors;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6.0),
|
||||
child: Row(
|
||||
@@ -112,20 +111,20 @@ class ChargeDetailPage extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.grey,
|
||||
color: c.textTertiary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC', // 复杂字符串保持 Harmony
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black87,
|
||||
color: c.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -133,8 +132,13 @@ class ChargeDetailPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// 数值行(电压/电流,数字用 Flyme,单位用 Harmony)
|
||||
Widget _buildDetailRow(String label, double value, String unit) {
|
||||
Widget _buildDetailRow(
|
||||
BuildContext context,
|
||||
String label,
|
||||
double value,
|
||||
String unit,
|
||||
) {
|
||||
final c = context.colors;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6.0),
|
||||
child: Row(
|
||||
@@ -142,24 +146,24 @@ class ChargeDetailPage extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.grey,
|
||||
color: c.textTertiary,
|
||||
),
|
||||
),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black87,
|
||||
color: c.textPrimary,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: value.toStringAsFixed(2),
|
||||
style: const TextStyle(fontFamily: 'Flyme'), // 🟢 数字用 Flyme
|
||||
style: const TextStyle(fontFamily: 'Flyme'),
|
||||
),
|
||||
TextSpan(
|
||||
text: unit,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../theme/app_colors.dart';
|
||||
|
||||
class ChargeEntry extends StatelessWidget {
|
||||
final String summary;
|
||||
@@ -14,6 +15,7 @@ class ChargeEntry extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final c = context.colors;
|
||||
return GestureDetector(
|
||||
onTap: isCharging ? onTap : null,
|
||||
child: Row(
|
||||
@@ -21,11 +23,11 @@ class ChargeEntry extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
summary,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC', // 🟢 混合文本用 Harmony
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black87,
|
||||
color: c.textPrimary,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
@@ -36,7 +38,7 @@ class ChargeEntry extends StatelessWidget {
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey.shade700,
|
||||
color: c.textTertiary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
@@ -46,7 +48,7 @@ class ChargeEntry extends StatelessWidget {
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey.shade700,
|
||||
color: c.textTertiary,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../theme/app_colors.dart';
|
||||
|
||||
// --- 潘通色定义 ---
|
||||
const Color kHealthGreen = Color(0xFF00B140);
|
||||
const Color kHealthYellow = Color(0xFFEFDF00);
|
||||
const Color kHealthOrange = Color(0xFFD75A16);
|
||||
|
||||
/// 趋势图柱状图组件
|
||||
class TrendChart extends StatelessWidget {
|
||||
final List<Map<String, dynamic>> data;
|
||||
final int designCap;
|
||||
@@ -14,6 +9,8 @@ class TrendChart extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final c = context.colors;
|
||||
|
||||
List<Map<String, dynamic>> displayData = data.isNotEmpty
|
||||
? data
|
||||
: _getMockData();
|
||||
@@ -48,12 +45,13 @@ class TrendChart extends StatelessWidget {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
Text(
|
||||
'容量百分比变动趋势(近15天)',
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: c.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -64,13 +62,13 @@ class TrendChart extends StatelessWidget {
|
||||
height: 180,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: const [
|
||||
children: [
|
||||
Text(
|
||||
'100',
|
||||
style: TextStyle(
|
||||
fontFamily: 'Flyme',
|
||||
fontSize: 10,
|
||||
color: Colors.grey,
|
||||
color: c.textTertiary,
|
||||
height: 1.0,
|
||||
),
|
||||
),
|
||||
@@ -79,7 +77,7 @@ class TrendChart extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontFamily: 'Flyme',
|
||||
fontSize: 10,
|
||||
color: Colors.grey,
|
||||
color: c.textTertiary,
|
||||
height: 1.0,
|
||||
),
|
||||
),
|
||||
@@ -106,14 +104,14 @@ class TrendChart extends StatelessWidget {
|
||||
Color barColor;
|
||||
if (value > 0) {
|
||||
if (value >= 80) {
|
||||
barColor = kHealthGreen;
|
||||
barColor = c.healthGreen;
|
||||
} else if (value >= 70) {
|
||||
barColor = kHealthYellow;
|
||||
barColor = c.healthYellow;
|
||||
} else {
|
||||
barColor = Colors.red;
|
||||
barColor = c.healthRed;
|
||||
}
|
||||
} else {
|
||||
barColor = Colors.grey.shade300;
|
||||
barColor = c.barEmpty;
|
||||
}
|
||||
double clampedValue = value.clamp(70.0, 100.0);
|
||||
double heightFactor =
|
||||
@@ -144,10 +142,10 @@ class TrendChart extends StatelessWidget {
|
||||
bottom: 2,
|
||||
child: Text(
|
||||
sevenDaysAgoDate,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 9,
|
||||
color: Colors.grey,
|
||||
color: c.textTertiary,
|
||||
height: 1.0,
|
||||
),
|
||||
),
|
||||
@@ -158,10 +156,10 @@ class TrendChart extends StatelessWidget {
|
||||
bottom: 2,
|
||||
child: Text(
|
||||
todayDate,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 9,
|
||||
color: Colors.black,
|
||||
color: c.textPrimary,
|
||||
height: 1.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
@@ -176,14 +174,14 @@ class TrendChart extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
if (percents.every((v) => v == 0))
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 8.0),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
'暂无历史数据,请点击刷新按钮更新',
|
||||
style: TextStyle(
|
||||
fontFamily: 'HarmonyOS_Sans_SC',
|
||||
fontSize: 12,
|
||||
color: Colors.grey,
|
||||
color: c.textTertiary,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+1
-1
@@ -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
|
||||
# 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.
|
||||
version: 1.7.2
|
||||
version: 1.8.0
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.2
|
||||
|
||||
Reference in New Issue
Block a user