391 lines
11 KiB
Dart
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 '跟随系统';
|
|
}
|
|
}
|
|
}
|