新增夜间模式相关代码

This commit is contained in:
2026-10-11 15:44:44 +08:00
parent 2512f88f30
commit 1d033670f7
4 changed files with 317 additions and 51 deletions
+55 -45
View File
@@ -1,11 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.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';
import 'theme/app_colors.dart';
import 'theme/theme_service.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await SafStorageService().init(); await SafStorageService().init();
await ThemeService.init(); // 🆕
runApp(const MyApp()); runApp(const MyApp());
} }
@@ -15,53 +18,60 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return ValueListenableBuilder<ThemeMode>(
title: '电池健康度', valueListenable: ThemeService.notifier,
theme: ThemeData( builder: (context, mode, _) {
// 🟢 中文用 HarmonyOS Sans SC return MaterialApp(
fontFamily: 'HarmonyOS_Sans_SC', title: '电池健康度',
// 🟢 数字回退到 FlymeFont themeMode: mode,
fontFamilyFallback: const ['Flyme'], theme: _buildTheme(Brightness.light, kLightColors),
// 🟢 统一文字颜色为黑色 darkTheme: _buildTheme(Brightness.dark, kDarkColors),
colorScheme: ColorScheme.fromSeed( home: const BatteryHealthPage(),
seedColor: const Color(0xFFFFFFFF), debugShowCheckedModeBanner: false,
).copyWith(onSurface: Colors.black), );
textTheme: const TextTheme( },
// 大标题/数字(健康度、容量等) );
displayMedium: TextStyle( }
fontFamily: 'HarmonyOS_Sans_SC',
fontFamilyFallback: ['Flyme'], ThemeData _buildTheme(Brightness brightness, AppColors colors) {
color: Colors.black, return ThemeData(
), useMaterial3: true,
// 标题(如“容量百分比变动趋势”) brightness: brightness,
headlineMedium: TextStyle( scaffoldBackgroundColor: colors.background,
fontFamily: 'HarmonyOS_Sans_SC', fontFamily: 'HarmonyOS_Sans_SC',
fontFamilyFallback: ['Flyme'], fontFamilyFallback: const ['Flyme'],
color: Colors.black, colorScheme: ColorScheme.fromSeed(
), seedColor: colors.background,
// 正文(电压、温度、循环次数等) brightness: brightness,
bodyMedium: TextStyle( ),
fontFamily: 'HarmonyOS_Sans_SC', extensions: [colors], // 🆕 注册 ThemeExtension
fontFamilyFallback: ['Flyme'], textTheme: TextTheme(
color: Colors.black, displayMedium: TextStyle(
), fontFamily: 'HarmonyOS_Sans_SC',
// 小字(日期、辅助信息) fontFamilyFallback: const ['Flyme'],
bodySmall: TextStyle( color: colors.textPrimary,
fontFamily: 'HarmonyOS_Sans_SC',
fontFamilyFallback: ['Flyme'],
color: Colors.black87,
),
// 数字专用(大号数字,如健康度百分比)
displayLarge: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontFamilyFallback: ['Flyme'],
color: Colors.black,
),
), ),
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,
); );
} }
} }
+53 -6
View File
@@ -3,6 +3,7 @@ import 'dart:developer' as dev;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; 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 '../models/device_matching.dart'; import '../models/device_matching.dart';
@@ -626,6 +627,56 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
return ChargeUtils.getChargeTypeLabel(_isWireless, _chargeType); 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 ---------- // ---------- Build ----------
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -695,7 +746,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
); );
break; break;
case 'dark_mode': case 'dark_mode':
// 夜间模式切换逻辑(1.7.0 再实现) _showThemePicker();
break; break;
} }
}, },
@@ -705,11 +756,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage> {
value: 'about', // 🟢 新增 value: 'about', // 🟢 新增
child: Text('关于'), child: Text('关于'),
), ),
const PopupMenuItem( const PopupMenuItem(value: 'dark_mode', child: Text('夜间模式')),
value: 'dark_mode',
enabled: false, // 暂时灰显
child: Text('夜间模式(开发中)'),
),
], ],
), ),
], ],
+164
View File
@@ -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>()!;
}
+45
View File
@@ -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';
}
}
}