Files
AutoJudge-Flutter/lib/providers/theme_provider.dart
2025-11-13 09:14:49 +08:00

391 lines
11 KiB
Dart

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// Color scheme options for the app
enum AppColorScheme { blue, green, purple, orange }
/// Provider for managing app theme and appearance settings
///
/// Handles theme mode (light/dark/system), color scheme selection,
/// and persistence of user preferences
///
/// Usage example:
/// ```dart
/// final themeProvider = Provider.of<ThemeProvider>(context);
///
/// // Change theme mode
/// themeProvider.setThemeMode(ThemeMode.dark);
///
/// // Change color scheme
/// themeProvider.setColorScheme(AppColorScheme.green);
///
/// // Get current theme data
/// final lightTheme = themeProvider.lightTheme;
/// final darkTheme = themeProvider.darkTheme;
/// ```
class ThemeProvider extends ChangeNotifier {
static const String _themeModeKey = 'theme_mode';
static const String _colorSchemeKey = 'color_scheme';
ThemeMode _themeMode = ThemeMode.system;
AppColorScheme _colorScheme = AppColorScheme.blue;
ThemeProvider() {
_loadPreferences();
}
/// Get current theme mode
ThemeMode get themeMode => _themeMode;
/// Get current color scheme
AppColorScheme get colorScheme => _colorScheme;
/// Get light theme data
ThemeData get lightTheme => _buildLightTheme();
/// Get dark theme data
ThemeData get darkTheme => _buildDarkTheme();
/// Set theme mode
///
/// [mode] - The theme mode to set (light, dark, or system)
/// Saves preference and notifies listeners
Future<void> setThemeMode(ThemeMode mode) async {
if (_themeMode == mode) return;
_themeMode = mode;
notifyListeners();
await savePreferences();
}
/// Set color scheme
///
/// [scheme] - The color scheme to set
/// Saves preference and notifies listeners
Future<void> setColorScheme(AppColorScheme scheme) async {
if (_colorScheme == scheme) return;
_colorScheme = scheme;
notifyListeners();
await savePreferences();
}
/// Save preferences to local storage
Future<void> savePreferences() async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_themeModeKey, _themeMode.name);
await prefs.setString(_colorSchemeKey, _colorScheme.name);
} catch (e) {
debugPrint('Failed to save theme preferences: $e');
}
}
/// Load preferences from local storage
Future<void> _loadPreferences() async {
try {
final prefs = await SharedPreferences.getInstance();
// Load theme mode
final themeModeStr = prefs.getString(_themeModeKey);
if (themeModeStr != null) {
_themeMode = ThemeMode.values.firstWhere(
(mode) => mode.name == themeModeStr,
orElse: () => ThemeMode.system,
);
}
// Load color scheme
final colorSchemeStr = prefs.getString(_colorSchemeKey);
if (colorSchemeStr != null) {
_colorScheme = AppColorScheme.values.firstWhere(
(scheme) => scheme.name == colorSchemeStr,
orElse: () => AppColorScheme.blue,
);
}
notifyListeners();
} catch (e) {
debugPrint('Failed to load theme preferences: $e');
}
}
/// Build light theme based on current color scheme
ThemeData _buildLightTheme() {
final colorScheme = _getColorScheme(Brightness.light);
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
colorScheme: colorScheme,
// AppBar theme
appBarTheme: AppBarTheme(
centerTitle: true,
elevation: 0,
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
),
// Card theme
cardTheme: const CardThemeData(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
// Input decoration theme
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: colorScheme.surfaceContainerHighest.withValues(alpha: 0.3),
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(color: colorScheme.primary, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: colorScheme.error, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: colorScheme.error, width: 2),
),
),
// Elevated button theme
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
elevation: 2,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
// Text button theme
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
),
// Floating action button theme
floatingActionButtonTheme: FloatingActionButtonThemeData(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
),
// Progress indicator theme
progressIndicatorTheme: ProgressIndicatorThemeData(
color: colorScheme.primary,
),
// Divider theme
dividerTheme: DividerThemeData(
color: colorScheme.outlineVariant,
thickness: 1,
),
);
}
/// Build dark theme based on current color scheme
ThemeData _buildDarkTheme() {
final colorScheme = _getColorScheme(Brightness.dark);
return ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorScheme: colorScheme,
// AppBar theme
appBarTheme: AppBarTheme(
centerTitle: true,
elevation: 0,
backgroundColor: colorScheme.surface,
foregroundColor: colorScheme.onSurface,
),
// Card theme
cardTheme: const CardThemeData(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
// Input decoration theme
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: colorScheme.surfaceContainerHighest.withValues(alpha: 0.3),
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(color: colorScheme.primary, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: colorScheme.error, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: colorScheme.error, width: 2),
),
),
// Elevated button theme
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
elevation: 2,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
// Text button theme
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
),
// Floating action button theme
floatingActionButtonTheme: FloatingActionButtonThemeData(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
),
// Progress indicator theme
progressIndicatorTheme: ProgressIndicatorThemeData(
color: colorScheme.primary,
),
// Divider theme
dividerTheme: DividerThemeData(
color: colorScheme.outlineVariant,
thickness: 1,
),
);
}
/// Get color scheme based on brightness and selected color
ColorScheme _getColorScheme(Brightness brightness) {
switch (_colorScheme) {
case AppColorScheme.blue:
return _getBlueColorScheme(brightness);
case AppColorScheme.green:
return _getGreenColorScheme(brightness);
case AppColorScheme.purple:
return _getPurpleColorScheme(brightness);
case AppColorScheme.orange:
return _getOrangeColorScheme(brightness);
}
}
/// Blue color scheme
ColorScheme _getBlueColorScheme(Brightness brightness) {
if (brightness == Brightness.light) {
return ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.light,
);
} else {
return ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
);
}
}
/// Green color scheme
ColorScheme _getGreenColorScheme(Brightness brightness) {
if (brightness == Brightness.light) {
return ColorScheme.fromSeed(
seedColor: Colors.green,
brightness: Brightness.light,
);
} else {
return ColorScheme.fromSeed(
seedColor: Colors.green,
brightness: Brightness.dark,
);
}
}
/// Purple color scheme
ColorScheme _getPurpleColorScheme(Brightness brightness) {
if (brightness == Brightness.light) {
return ColorScheme.fromSeed(
seedColor: Colors.purple,
brightness: Brightness.light,
);
} else {
return ColorScheme.fromSeed(
seedColor: Colors.purple,
brightness: Brightness.dark,
);
}
}
/// Orange color scheme
ColorScheme _getOrangeColorScheme(Brightness brightness) {
if (brightness == Brightness.light) {
return ColorScheme.fromSeed(
seedColor: Colors.orange,
brightness: Brightness.light,
);
} else {
return ColorScheme.fromSeed(
seedColor: Colors.orange,
brightness: Brightness.dark,
);
}
}
/// Get color scheme name for display
String getColorSchemeName(AppColorScheme scheme) {
switch (scheme) {
case AppColorScheme.blue:
return '蓝色';
case AppColorScheme.green:
return '绿色';
case AppColorScheme.purple:
return '紫色';
case AppColorScheme.orange:
return '橙色';
}
}
/// Get theme mode name for display
String getThemeModeName(ThemeMode mode) {
switch (mode) {
case ThemeMode.light:
return '浅色模式';
case ThemeMode.dark:
return '深色模式';
case ThemeMode.system:
return '跟随系统';
}
}
}