import 'dart:io'; import 'package:flutter/material.dart'; import '../../services/storage_service.dart'; /// 主题模式 enum AppThemeMode { light, dark, system, } /// 主题Provider - 管理主题模式和主题色 class ThemeProvider extends ChangeNotifier { AppThemeMode _themeMode = AppThemeMode.system; Color _seedColor = const Color(0xFF3B82F6); static const Color lightScaffoldBg = Color(0xFFF8FAFC); static const Color darkScaffoldBg = Color(0xFF0F172A); AppThemeMode get themeMode => _themeMode; Color get seedColor => _seedColor; bool get isDark => _themeMode == AppThemeMode.dark; /// 初始化 Future init() async { await Future.wait([ loadThemeMode(), loadSeedColor(), ]); } /// 加载主题模式 Future loadThemeMode() async { final savedMode = await StorageService.instance.themeMode; if (savedMode != null) { switch (savedMode) { case 'light': _themeMode = AppThemeMode.light; case 'dark': _themeMode = AppThemeMode.dark; default: _themeMode = AppThemeMode.system; } } notifyListeners(); } /// 加载主题色 Future loadSeedColor() async { final saved = await StorageService.instance.getString('theme_seed_color'); if (saved != null && saved.isNotEmpty) { final color = _colorFromHex(saved); if (color != null) { _seedColor = color; notifyListeners(); } } } /// 设置主题模式 Future setThemeMode(AppThemeMode mode) async { _themeMode = mode; notifyListeners(); String modeString; switch (mode) { case AppThemeMode.light: modeString = 'light'; case AppThemeMode.dark: modeString = 'dark'; case AppThemeMode.system: modeString = 'system'; } await StorageService.instance.setThemeMode(modeString); } /// 设置主题色 Future setSeedColor(Color color) async { _seedColor = color; notifyListeners(); await StorageService.instance.setString('theme_seed_color', _colorToHex(color)); } /// 切换主题 Future toggleTheme() async { final newMode = isDark ? AppThemeMode.light : AppThemeMode.dark; await setThemeMode(newMode); } /// 构建亮色主题 ThemeData buildLightTheme() { return _buildTheme(Brightness.light); } /// 构建暗色主题 ThemeData buildDarkTheme() { return _buildTheme(Brightness.dark); } ThemeData _buildTheme(Brightness brightness) { final isLight = brightness == Brightness.light; final colorScheme = ColorScheme.fromSeed( seedColor: _seedColor, brightness: brightness, ); final bodyColor = isLight ? Colors.black87 : Colors.white; final displayColor = isLight ? Colors.black87 : Colors.white; final baseTextTheme = ThemeData(brightness: brightness).textTheme; var textTheme = baseTextTheme.apply( bodyColor: bodyColor, displayColor: displayColor, fontFamily: _getPlatformFont(), ); if (_getPlatformFont() == 'NotoSansSC') { textTheme = textTheme.copyWith( bodyLarge: textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500), bodyMedium: textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500), bodySmall: textTheme.bodySmall?.copyWith(fontWeight: FontWeight.w500), titleLarge: textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700), titleMedium: textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500), titleSmall: textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500), labelLarge: textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w700), ); } return ThemeData( textTheme: textTheme, useMaterial3: true, colorScheme: colorScheme, scaffoldBackgroundColor: isLight ? lightScaffoldBg : darkScaffoldBg, appBarTheme: AppBarTheme( centerTitle: true, elevation: 0, scrolledUnderElevation: 0, backgroundColor: isLight ? lightScaffoldBg.withValues(alpha: 0.85) : darkScaffoldBg.withValues(alpha: 0.85), surfaceTintColor: Colors.transparent, foregroundColor: isLight ? Colors.black87 : Colors.white, ), cardTheme: CardThemeData( elevation: 0, shadowColor: Colors.black12, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide( color: isLight ? Colors.black.withValues(alpha: 0.06) : Colors.white.withValues(alpha: 0.08), ), ), color: isLight ? Colors.white : const Color(0xFF1E293B), ), inputDecorationTheme: InputDecorationTheme( filled: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 16, ), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), floatingActionButtonTheme: FloatingActionButtonThemeData( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), ), chipTheme: ChipThemeData( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), dialogTheme: DialogThemeData( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), ), bottomSheetTheme: const BottomSheetThemeData( shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(16), ), ), ), navigationBarTheme: NavigationBarThemeData( elevation: 0, backgroundColor: isLight ? lightScaffoldBg.withValues(alpha: 0.9) : darkScaffoldBg.withValues(alpha: 0.9), indicatorColor: colorScheme.primary.withValues(alpha: 0.12), labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, ), navigationRailTheme: NavigationRailThemeData( elevation: 0, backgroundColor: isLight ? lightScaffoldBg : darkScaffoldBg, indicatorColor: colorScheme.primary.withValues(alpha: 0.12), ), ); } /// Color → hex string (不含alpha) static String _colorToHex(Color color) { return '#${color.toARGB32().toRadixString(16).padLeft(8, '0').substring(2)}'; } /// hex string → Color static Color? _colorFromHex(String hex) { final clean = hex.replaceFirst('#', ''); if (clean.length == 6) { return Color(int.parse('FF$clean', radix: 16)); } if (clean.length == 8) { return Color(int.parse(clean, radix: 16)); } return null; } String? _getPlatformFont() { if (Platform.isWindows || Platform.isLinux) return 'NotoSansSC'; if (Platform.isMacOS) return 'PingFang SC'; return null; } }