新批次的夜间模式适配代码

This commit is contained in:
2026-07-30 23:38:34 +08:00
parent b6d623a1eb
commit 42a30a73f9
11 changed files with 151 additions and 83 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ plugins {
android { android {
namespace = "com.lxh.battery_health" namespace = "com.lxh.battery_health"
compileSdk = 34 compileSdk = 36
ndkVersion = flutter.ndkVersion ndkVersion = flutter.ndkVersion
compileOptions { compileOptions {
@@ -12,6 +12,7 @@ import android.os.Bundle
import android.provider.DocumentsContract import android.provider.DocumentsContract
import android.provider.OpenableColumns import android.provider.OpenableColumns
import android.view.Window // 🟢 必须包含 import android.view.Window // 🟢 必须包含
import android.app.Activity
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.annotation.NonNull import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
@@ -135,7 +136,7 @@ class MainActivity : FlutterActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
if (requestCode == FOLDER_PICKER_REQUEST && resultCode == ComponentActivity.RESULT_OK) { if (requestCode == FOLDER_PICKER_REQUEST && resultCode == Activity.RESULT_OK) {
val uri = data?.data val uri = data?.data
if (uri != null) { if (uri != null) {
try { try {
+7 -16
View File
@@ -1,21 +1,11 @@
allprojects { // 根项目构建脚本
repositories { plugins {
maven { id("com.android.application") version "8.13.0" apply false
url = uri("https://maven.aliyun.com/repository/public") id("org.jetbrains.kotlin.android") version "2.2.21" apply false
}
maven {
url = uri("https://maven.aliyun.com/repository/google")
}
maven {
url = uri("https://maven.aliyun.com/repository/gradle-plugin")
}
google()
mavenCentral()
}
} }
val newBuildDir: Directory = // 统一重定向构建输出目录
rootProject.layout.buildDirectory val newBuildDir: Directory = rootProject.layout.buildDirectory
.dir("../../build") .dir("../../build")
.get() .get()
rootProject.layout.buildDirectory.value(newBuildDir) rootProject.layout.buildDirectory.value(newBuildDir)
@@ -24,6 +14,7 @@ subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir) project.layout.buildDirectory.value(newSubprojectBuildDir)
} }
subprojects { subprojects {
project.evaluationDependsOn(":app") project.evaluationDependsOn(":app")
} }
+4 -4
View File
@@ -1,7 +1,7 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true org.gradle.daemon=true
android.enableJetifier=true org.gradle.parallel=true
# This builtInKotlin flag was added automatically by Flutter migrator # This builtInKotlin flag was added automatically by Flutter migrator
android.builtInKotlin=false android.builtInKotlin=true
# This newDsl flag was added automatically by Flutter migrator # This newDsl flag was added automatically by Flutter migrator
android.newDsl=false android.newDsl=true
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.14-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.12-all.zip
+27 -9
View File
@@ -1,26 +1,44 @@
pluginManagement { pluginManagement {
val flutterSdkPath = val flutterSdkPath = run {
run {
val properties = java.util.Properties() val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) } file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk") val sdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } require(sdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath sdkPath
} }
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories { repositories {
// 🟢 阿里云 gradle-plugin 镜像(专用)
maven {
url = uri("https://maven.aliyun.com/repository/gradle-plugin")
}
// 🟢 阿里云 public 聚合仓库(兜底)
maven {
url = uri("https://maven.aliyun.com/repository/public")
}
google() google()
mavenCentral() mavenCentral()
gradlePluginPortal() gradlePluginPortal()
} }
} }
plugins { dependencyResolutionManagement {
id("dev.flutter.flutter-plugin-loader") version "1.0.0" repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
id("com.android.application") version "8.9.1" apply false repositories {
id("org.jetbrains.kotlin.android") version "2.1.0" apply false // 🟢 阿里云镜像放在最前面
maven { url = uri("https://maven.aliyun.com/repository/public") }
google()
mavenCentral()
}
} }
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.13.0" apply false
id("org.jetbrains.kotlin.android") version "2.2.21" apply false
}
rootProject.name = "android"
include(":app") include(":app")
+43 -4
View File
@@ -1,16 +1,49 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'pages/battery_health_page.dart'; import 'pages/battery_health_page.dart';
import 'services/saf_storage_service.dart'; import 'services/saf_storage_service.dart';
// 🆕 1.7.0 全局 Key,用于从子页面切换主题
final GlobalKey<_MyAppState> appKey = GlobalKey<_MyAppState>();
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await SafStorageService().init(); await SafStorageService().init();
runApp(const MyApp());
final prefs = await SharedPreferences.getInstance();
final themeModeIndex = prefs.getInt('themeMode') ?? 0;
final themeMode =
ThemeMode.values[themeModeIndex.clamp(0, ThemeMode.values.length - 1)];
runApp(MyApp(key: appKey, initialThemeMode: themeMode));
} }
class MyApp extends StatelessWidget { class MyApp extends StatefulWidget {
const MyApp({super.key}); final ThemeMode initialThemeMode;
const MyApp({super.key, required this.initialThemeMode});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late ThemeMode _themeMode;
@override
void initState() {
super.initState();
_themeMode = widget.initialThemeMode;
}
void switchTheme(ThemeMode mode) {
setState(() {
_themeMode = mode;
});
SharedPreferences.getInstance().then((prefs) {
prefs.setInt('themeMode', mode.index);
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -18,7 +51,7 @@ class MyApp extends StatelessWidget {
title: '电池健康度', title: '电池健康度',
theme: _lightTheme(), theme: _lightTheme(),
darkTheme: _darkTheme(), darkTheme: _darkTheme(),
themeMode: ThemeMode.system, // 默认跟随系统 themeMode: _themeMode,
home: const BatteryHealthPage(), home: const BatteryHealthPage(),
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
); );
@@ -41,6 +74,9 @@ class MyApp extends StatelessWidget {
colorScheme: const ColorScheme.light( colorScheme: const ColorScheme.light(
primary: Color(0xFF1A73E8), primary: Color(0xFF1A73E8),
secondary: Color(0xFF1A73E8), secondary: Color(0xFF1A73E8),
surface: Colors.white,
onSurface: Colors.black, // 🟢 主要文字色
onSurfaceVariant: Colors.black87,
), ),
textTheme: const TextTheme( textTheme: const TextTheme(
bodyLarge: TextStyle(color: Colors.black), bodyLarge: TextStyle(color: Colors.black),
@@ -68,6 +104,9 @@ class MyApp extends StatelessWidget {
colorScheme: const ColorScheme.dark( colorScheme: const ColorScheme.dark(
primary: Color(0xFF4FC3F7), primary: Color(0xFF4FC3F7),
secondary: Color(0xFF4FC3F7), secondary: Color(0xFF4FC3F7),
surface: Color(0xFF121212),
onSurface: Colors.white, // 🟢 主要文字色(夜间模式为白色)
onSurfaceVariant: Colors.white70,
), ),
textTheme: const TextTheme( textTheme: const TextTheme(
bodyLarge: TextStyle(color: Colors.white), bodyLarge: TextStyle(color: Colors.white),
-1
View File
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class AboutPage extends StatelessWidget { class AboutPage extends StatelessWidget {
+13 -1
View File
@@ -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 '../main.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'; import '../models/data_abnormal_type.dart';
@@ -742,7 +743,18 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
); );
break; break;
case 'dark_mode': case 'dark_mode':
// 夜间模式切换逻辑(需要全局状态管理) // 循环切换:浅色 → 深色 → 跟随系统 → 浅色
final brightness = Theme.of(context).brightness;
ThemeMode next;
if (brightness == Brightness.light) {
next = ThemeMode.dark;
} else if (brightness == Brightness.dark) {
next = ThemeMode.system;
} else {
next = ThemeMode.light;
}
// 🆕 通过全局 key 调用 MyApp 的 switchTheme 方法
appKey.currentState?.switchTheme(next);
break; break;
} }
}, },
+32 -19
View File
@@ -24,9 +24,12 @@ class BatteryInfoPanel extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final bool isVoltageAbnormal = final bool isVoltageAbnormal =
abnormalType == DataAbnormalType.voltageAbnormal; abnormalType == DataAbnormalType.voltageAbnormal;
final Color voltColor = isVoltageAbnormal ? Colors.red : Colors.black;
final Color defaultTextColor = Theme.of(context).colorScheme.onSurface;
final Color voltColor = isVoltageAbnormal ? Colors.red : defaultTextColor;
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -36,7 +39,8 @@ class BatteryInfoPanel extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildInfoItem( _buildInfoItem(
_buildIcon('voltage'), context, // 🆕
_buildIcon('voltage', isDark),
'电压', '电压',
'${batteryVolt.toStringAsFixed(3)}V', '${batteryVolt.toStringAsFixed(3)}V',
textColor: voltColor, textColor: voltColor,
@@ -50,12 +54,14 @@ class BatteryInfoPanel extends StatelessWidget {
fontFamily: 'Flyme', fontFamily: 'Flyme',
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.red, color: isVoltageAbnormal
? Colors.red
: defaultTextColor,
), ),
children: [ children: [
const TextSpan( TextSpan(
text: '', text: '',
style: TextStyle( style: const TextStyle(
fontFamily: 'HarmonyOS_Sans_SC', fontFamily: 'HarmonyOS_Sans_SC',
color: Colors.red, color: Colors.red,
), ),
@@ -76,12 +82,14 @@ class BatteryInfoPanel extends StatelessWidget {
fontFamily: 'Flyme', fontFamily: 'Flyme',
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.blue, color: isVoltageAbnormal
? Colors.red
: defaultTextColor,
), ),
children: [ children: [
const TextSpan( TextSpan(
text: '', text: '',
style: TextStyle( style: const TextStyle(
fontFamily: 'HarmonyOS_Sans_SC', fontFamily: 'HarmonyOS_Sans_SC',
color: Colors.blue, color: Colors.blue,
), ),
@@ -105,20 +113,22 @@ class BatteryInfoPanel extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildInfoItem( _buildInfoItem(
_buildIcon('temperature'), context, // 🆕
_buildIcon('temperature', isDark),
'温度', '温度',
'${batteryTemp.toStringAsFixed(0)}', '${batteryTemp.toStringAsFixed(0)}',
textColor: defaultTextColor,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
children: [ children: [
RichText( RichText(
text: TextSpan( text: TextSpan(
style: const TextStyle( style: TextStyle(
fontFamily: 'Flyme', fontFamily: 'Flyme',
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.red, color: defaultTextColor,
), ),
children: [ children: [
const TextSpan( const TextSpan(
@@ -135,11 +145,11 @@ class BatteryInfoPanel extends StatelessWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
RichText( RichText(
text: TextSpan( text: TextSpan(
style: const TextStyle( style: TextStyle(
fontFamily: 'Flyme', fontFamily: 'Flyme',
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.blue, color: defaultTextColor,
), ),
children: [ children: [
const TextSpan( const TextSpan(
@@ -162,43 +172,46 @@ class BatteryInfoPanel extends StatelessWidget {
); );
} }
Widget _buildIcon(String type) { Widget _buildIcon(String type, bool isDark) {
final color = isDark ? Colors.white54 : Colors.grey;
switch (type) { switch (type) {
case 'voltage': case 'voltage':
return SvgPicture.asset( return SvgPicture.asset(
'assets/icons/si--lightning-fill.svg', 'assets/icons/si--lightning-fill.svg',
width: 18, width: 18,
height: 18, height: 18,
colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn), colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
); );
case 'temperature': case 'temperature':
return SvgPicture.asset( return SvgPicture.asset(
'assets/icons/mdi--temperature.svg', 'assets/icons/mdi--temperature.svg',
width: 18, width: 18,
height: 18, height: 18,
colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn), colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
); );
default: default:
return const Icon(Icons.help_outline, size: 18, color: Colors.grey); return Icon(Icons.help_outline, size: 18, color: color);
} }
} }
Widget _buildInfoItem( Widget _buildInfoItem(
BuildContext context, // 🆕
Widget icon, Widget icon,
String label, String label,
String value, { String value, {
Color textColor = Colors.black, Color textColor = Colors.black,
}) { }) {
final labelColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.7);
return Row( return Row(
children: [ children: [
icon, icon,
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
label, label,
style: const TextStyle( style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC', fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 14, fontSize: 14,
color: Colors.grey, color: labelColor,
), ),
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
+13 -18
View File
@@ -19,20 +19,20 @@ class BatterySummary extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final bool isAbnormal = abnormalType != DataAbnormalType.none; final bool isAbnormal = abnormalType != DataAbnormalType.none;
final bool isCapacityAbnormal = final bool isCapacityAbnormal =
abnormalType == DataAbnormalType.capacityTooLow || abnormalType == DataAbnormalType.capacityTooLow ||
abnormalType == DataAbnormalType.capacityTooHigh; abnormalType == DataAbnormalType.capacityTooHigh;
// 健康度颜色:异常时红色,否则按阈值 // 🟢 颜色完全动态
final Color healthColor = isAbnormal final Color defaultTextColor = Theme.of(context).colorScheme.onSurface;
? Colors.red final Color mutedTextColor = isDark
: (healthPercent < 70 ? Colors.red : Colors.black); ? Colors.grey.shade400
: const Color(0xFF5F5F5F);
// 容量颜色:异常时红色,否则灰色
final Color capacityColor = isCapacityAbnormal final Color capacityColor = isCapacityAbnormal
? Colors.red ? Colors.red
: const Color(0xFF5F5F5F); : mutedTextColor;
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
@@ -49,7 +49,7 @@ class BatterySummary extends StatelessWidget {
fontSize: 40, fontSize: 40,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
height: 1.0, height: 1.0,
color: healthColor, color: isAbnormal ? Colors.red : defaultTextColor,
), ),
), ),
const SizedBox(width: 2), const SizedBox(width: 2),
@@ -60,9 +60,7 @@ class BatterySummary extends StatelessWidget {
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w300, fontWeight: FontWeight.w300,
height: 1.0, height: 1.0,
color: healthColor == Colors.red color: isAbnormal ? Colors.red : Colors.grey.shade600,
? Colors.red
: Colors.grey.shade600,
), ),
), ),
], ],
@@ -74,21 +72,18 @@ class BatterySummary extends StatelessWidget {
children: [ children: [
RichText( RichText(
text: TextSpan( text: TextSpan(
style: const TextStyle( style: TextStyle(
fontFamily: 'Flyme', fontFamily: 'Flyme',
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
height: 1.2, height: 1.2,
color: Colors.black, color: defaultTextColor,
), ),
children: [ children: [
TextSpan(text: '$cycleCount'), TextSpan(text: '$cycleCount'),
const TextSpan( TextSpan(
text: ' cycles', text: ' cycles',
style: TextStyle( style: const TextStyle(fontFamily: 'HarmonyOS_Sans_SC'),
fontFamily: 'HarmonyOS_Sans_SC',
color: Colors.black,
),
), ),
], ],
), ),