update theme selection

This commit is contained in:
Phuoc Nguyen
2025-12-01 11:31:26 +07:00
parent 4ecb236532
commit 250c453413
18 changed files with 1351 additions and 304 deletions

View File

@@ -5,23 +5,18 @@ import 'package:worker/core/theme/colors.dart';
import 'package:worker/core/theme/typography.dart';
/// App theme configuration for Material 3 design system
/// Provides both light and dark theme variants
/// Uses ColorScheme.fromSeed() to auto-generate harmonious colors from brand seed color
class AppTheme {
// Prevent instantiation
AppTheme._();
// ==================== Light Theme ====================
/// Light theme configuration
static ThemeData lightTheme() {
/// [seedColor] - Optional custom seed color, defaults to AppColors.defaultSeedColor
static ThemeData lightTheme([Color? seedColor]) {
final ColorScheme colorScheme = ColorScheme.fromSeed(
seedColor: AppColors.primaryBlue,
seedColor: seedColor ?? AppColors.defaultSeedColor,
brightness: Brightness.light,
primary: AppColors.primaryBlue,
secondary: AppColors.lightBlue,
tertiary: AppColors.accentCyan,
error: AppColors.danger,
surface: AppColors.white,
);
return ThemeData(
@@ -29,37 +24,37 @@ class AppTheme {
colorScheme: colorScheme,
fontFamily: AppTypography.fontFamily,
// ==================== App Bar Theme ====================
// AppBar uses colorScheme colors
appBarTheme: AppBarTheme(
elevation: 0,
centerTitle: true,
backgroundColor: AppColors.primaryBlue,
foregroundColor: AppColors.white,
centerTitle: false,
backgroundColor: colorScheme.surface,
foregroundColor: colorScheme.onSurface,
titleTextStyle: AppTypography.titleLarge.copyWith(
color: AppColors.white,
color: colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
iconTheme: const IconThemeData(color: AppColors.white, size: 24),
systemOverlayStyle: SystemUiOverlayStyle.light,
iconTheme: IconThemeData(color: colorScheme.onSurface, size: 24),
systemOverlayStyle: SystemUiOverlayStyle.dark,
),
// ==================== Card Theme ====================
cardTheme: const CardThemeData(
elevation: 2,
shape: RoundedRectangleBorder(
// Card Theme
cardTheme: CardThemeData(
elevation: 1,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
clipBehavior: Clip.antiAlias,
color: AppColors.white,
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
color: colorScheme.surface,
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
// ==================== Elevated Button Theme ====================
// Elevated Button Theme
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
foregroundColor: AppColors.white,
elevation: 2,
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
elevation: 1,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
textStyle: AppTypography.buttonText,
@@ -67,21 +62,21 @@ class AppTheme {
),
),
// ==================== Text Button Theme ====================
// Text Button Theme
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: AppColors.primaryBlue,
foregroundColor: colorScheme.primary,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
textStyle: AppTypography.buttonText,
),
),
// ==================== Outlined Button Theme ====================
// Outlined Button Theme
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primaryBlue,
side: const BorderSide(color: AppColors.primaryBlue, width: 1.5),
foregroundColor: colorScheme.primary,
side: BorderSide(color: colorScheme.outline, width: 1),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
textStyle: AppTypography.buttonText,
@@ -89,214 +84,164 @@ class AppTheme {
),
),
// ==================== Input Decoration Theme ====================
// Input Decoration Theme
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColors.white,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
fillColor: colorScheme.surfaceContainerLowest,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100, width: 1),
borderSide: BorderSide(color: colorScheme.outline, width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100, width: 1),
borderSide: BorderSide(color: colorScheme.outline, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.primaryBlue, width: 2),
borderSide: BorderSide(color: colorScheme.primary, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.danger, width: 1),
borderSide: BorderSide(color: colorScheme.error, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.danger, width: 2),
borderSide: BorderSide(color: colorScheme.error, width: 2),
),
labelStyle: AppTypography.bodyMedium.copyWith(color: AppColors.grey500),
hintStyle: AppTypography.bodyMedium.copyWith(color: AppColors.grey500),
errorStyle: AppTypography.bodySmall.copyWith(color: AppColors.danger),
labelStyle: AppTypography.bodyMedium.copyWith(
color: colorScheme.onSurfaceVariant,
),
hintStyle: AppTypography.bodyMedium.copyWith(
color: colorScheme.onSurfaceVariant,
),
errorStyle: AppTypography.bodySmall.copyWith(color: colorScheme.error),
),
// ==================== Bottom Navigation Bar Theme ====================
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: AppColors.white,
selectedItemColor: AppColors.primaryBlue,
unselectedItemColor: AppColors.grey500,
selectedIconTheme: IconThemeData(
size: 28,
color: AppColors.primaryBlue,
// Bottom Navigation Bar Theme
bottomNavigationBarTheme: BottomNavigationBarThemeData(
backgroundColor: colorScheme.surface,
selectedItemColor: colorScheme.primary,
unselectedItemColor: colorScheme.onSurfaceVariant,
selectedIconTheme: IconThemeData(size: 28, color: colorScheme.primary),
unselectedIconTheme: IconThemeData(
size: 24,
color: colorScheme.onSurfaceVariant,
),
unselectedIconTheme: IconThemeData(size: 24, color: AppColors.grey500),
selectedLabelStyle: TextStyle(
selectedLabelStyle: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
fontFamily: AppTypography.fontFamily,
),
unselectedLabelStyle: TextStyle(
unselectedLabelStyle: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
fontFamily: AppTypography.fontFamily,
),
type: BottomNavigationBarType.fixed,
elevation: 8,
elevation: 3,
),
// ==================== Floating Action Button Theme ====================
floatingActionButtonTheme: const FloatingActionButtonThemeData(
backgroundColor: AppColors.accentCyan,
foregroundColor: AppColors.white,
elevation: 6,
shape: CircleBorder(),
iconSize: 24,
// Floating Action Button Theme
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: colorScheme.primaryContainer,
foregroundColor: colorScheme.onPrimaryContainer,
elevation: 3,
shape: const CircleBorder(),
),
// ==================== Chip Theme ====================
// Chip Theme
chipTheme: ChipThemeData(
backgroundColor: AppColors.grey50,
selectedColor: AppColors.primaryBlue,
disabledColor: AppColors.grey100,
secondarySelectedColor: AppColors.lightBlue,
backgroundColor: colorScheme.surfaceContainerLow,
selectedColor: colorScheme.primaryContainer,
disabledColor: colorScheme.surfaceContainerLowest,
labelStyle: AppTypography.labelMedium,
secondaryLabelStyle: AppTypography.labelMedium.copyWith(
color: AppColors.white,
),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
// ==================== Dialog Theme ====================
dialogTheme:
const DialogThemeData(
backgroundColor: AppColors.white,
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
).copyWith(
titleTextStyle: AppTypography.headlineMedium.copyWith(
color: AppColors.grey900,
),
contentTextStyle: AppTypography.bodyLarge.copyWith(
color: AppColors.grey900,
),
),
// Dialog Theme
dialogTheme: DialogThemeData(
backgroundColor: colorScheme.surface,
elevation: 3,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
titleTextStyle: AppTypography.headlineMedium.copyWith(
color: colorScheme.onSurface,
),
contentTextStyle: AppTypography.bodyLarge.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
// ==================== Snackbar Theme ====================
// Snackbar Theme
snackBarTheme: SnackBarThemeData(
backgroundColor: AppColors.grey900,
backgroundColor: colorScheme.inverseSurface,
contentTextStyle: AppTypography.bodyMedium.copyWith(
color: AppColors.white,
color: colorScheme.onInverseSurface,
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
behavior: SnackBarBehavior.floating,
elevation: 4,
elevation: 3,
),
// ==================== Divider Theme ====================
dividerTheme: const DividerThemeData(
color: AppColors.grey100,
// Divider Theme
dividerTheme: DividerThemeData(
color: colorScheme.outlineVariant,
thickness: 1,
space: 1,
),
// ==================== Icon Theme ====================
iconTheme: const IconThemeData(color: AppColors.grey900, size: 24),
// Icon Theme
iconTheme: IconThemeData(color: colorScheme.onSurface, size: 24),
// ==================== List Tile Theme ====================
// List Tile Theme
listTileTheme: ListTileThemeData(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
titleTextStyle: AppTypography.titleMedium.copyWith(
color: AppColors.grey900,
color: colorScheme.onSurface,
),
subtitleTextStyle: AppTypography.bodyMedium.copyWith(
color: AppColors.grey500,
color: colorScheme.onSurfaceVariant,
),
iconColor: AppColors.grey500,
iconColor: colorScheme.onSurfaceVariant,
),
// ==================== Switch Theme ====================
switchTheme: SwitchThemeData(
thumbColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return AppColors.primaryBlue;
}
return AppColors.grey500;
}),
trackColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return AppColors.lightBlue;
}
return AppColors.grey100;
}),
// Progress Indicator Theme
progressIndicatorTheme: ProgressIndicatorThemeData(
color: colorScheme.primary,
linearTrackColor: colorScheme.surfaceContainerHighest,
circularTrackColor: colorScheme.surfaceContainerHighest,
),
// ==================== Checkbox Theme ====================
checkboxTheme: CheckboxThemeData(
fillColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return AppColors.primaryBlue;
}
return AppColors.white;
}),
checkColor: WidgetStateProperty.all(AppColors.white),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
),
// ==================== Radio Theme ====================
radioTheme: RadioThemeData(
fillColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return AppColors.primaryBlue;
}
return AppColors.grey500;
}),
),
// ==================== Progress Indicator Theme ====================
progressIndicatorTheme: const ProgressIndicatorThemeData(
color: AppColors.primaryBlue,
linearTrackColor: AppColors.grey100,
circularTrackColor: AppColors.grey100,
),
// ==================== Badge Theme ====================
// Badge Theme
badgeTheme: const BadgeThemeData(
backgroundColor: AppColors.danger,
textColor: AppColors.white,
textColor: Colors.white,
smallSize: 6,
largeSize: 16,
),
// ==================== Tab Bar Theme ====================
tabBarTheme:
const TabBarThemeData(
labelColor: AppColors.primaryBlue,
unselectedLabelColor: AppColors.grey500,
indicatorColor: AppColors.primaryBlue,
).copyWith(
labelStyle: AppTypography.labelLarge,
unselectedLabelStyle: AppTypography.labelLarge,
),
// Tab Bar Theme
tabBarTheme: TabBarThemeData(
labelColor: colorScheme.primary,
unselectedLabelColor: colorScheme.onSurfaceVariant,
indicatorColor: colorScheme.primary,
labelStyle: AppTypography.labelLarge,
unselectedLabelStyle: AppTypography.labelLarge,
),
);
}
// ==================== Dark Theme ====================
/// Dark theme configuration
static ThemeData darkTheme() {
/// [seedColor] - Optional custom seed color, defaults to AppColors.defaultSeedColor
static ThemeData darkTheme([Color? seedColor]) {
final ColorScheme colorScheme = ColorScheme.fromSeed(
seedColor: AppColors.primaryBlue,
seedColor: seedColor ?? AppColors.defaultSeedColor,
brightness: Brightness.dark,
primary: AppColors.lightBlue,
secondary: AppColors.accentCyan,
tertiary: AppColors.primaryBlue,
error: AppColors.danger,
surface: const Color(0xFF1E1E1E),
);
return ThemeData(
@@ -304,37 +249,37 @@ class AppTheme {
colorScheme: colorScheme,
fontFamily: AppTypography.fontFamily,
// ==================== App Bar Theme ====================
// AppBar Theme
appBarTheme: AppBarTheme(
elevation: 0,
centerTitle: true,
backgroundColor: const Color(0xFF1E1E1E),
foregroundColor: AppColors.white,
centerTitle: false,
backgroundColor: colorScheme.surface,
foregroundColor: colorScheme.onSurface,
titleTextStyle: AppTypography.titleLarge.copyWith(
color: AppColors.white,
color: colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
iconTheme: const IconThemeData(color: AppColors.white, size: 24),
iconTheme: IconThemeData(color: colorScheme.onSurface, size: 24),
systemOverlayStyle: SystemUiOverlayStyle.light,
),
// ==================== Card Theme ====================
cardTheme: const CardThemeData(
elevation: 2,
shape: RoundedRectangleBorder(
// Card Theme
cardTheme: CardThemeData(
elevation: 1,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
clipBehavior: Clip.antiAlias,
color: Color(0xFF1E1E1E),
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
color: colorScheme.surfaceContainer,
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
// ==================== Elevated Button Theme ====================
// Elevated Button Theme
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.lightBlue,
foregroundColor: AppColors.white,
elevation: 2,
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
elevation: 1,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
textStyle: AppTypography.buttonText,
@@ -342,78 +287,89 @@ class AppTheme {
),
),
// ==================== Input Decoration Theme ====================
// Input Decoration Theme
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: const Color(0xFF2A2A2A),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
fillColor: colorScheme.surfaceContainerHighest,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Color(0xFF3A3A3A), width: 1),
borderSide: BorderSide(color: colorScheme.outline, width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Color(0xFF3A3A3A), width: 1),
borderSide: BorderSide(color: colorScheme.outline, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.lightBlue, width: 2),
borderSide: BorderSide(color: colorScheme.primary, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.danger, width: 1),
borderSide: BorderSide(color: colorScheme.error, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.danger, width: 2),
borderSide: BorderSide(color: colorScheme.error, width: 2),
),
labelStyle: AppTypography.bodyMedium.copyWith(color: AppColors.grey500),
hintStyle: AppTypography.bodyMedium.copyWith(color: AppColors.grey500),
errorStyle: AppTypography.bodySmall.copyWith(color: AppColors.danger),
labelStyle: AppTypography.bodyMedium.copyWith(
color: colorScheme.onSurfaceVariant,
),
hintStyle: AppTypography.bodyMedium.copyWith(
color: colorScheme.onSurfaceVariant,
),
errorStyle: AppTypography.bodySmall.copyWith(color: colorScheme.error),
),
// ==================== Bottom Navigation Bar Theme ====================
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: Color(0xFF1E1E1E),
selectedItemColor: AppColors.lightBlue,
unselectedItemColor: AppColors.grey500,
selectedIconTheme: IconThemeData(size: 28, color: AppColors.lightBlue),
unselectedIconTheme: IconThemeData(size: 24, color: AppColors.grey500),
selectedLabelStyle: TextStyle(
// Bottom Navigation Bar Theme
bottomNavigationBarTheme: BottomNavigationBarThemeData(
backgroundColor: colorScheme.surface,
selectedItemColor: colorScheme.primary,
unselectedItemColor: colorScheme.onSurfaceVariant,
selectedIconTheme: IconThemeData(size: 28, color: colorScheme.primary),
unselectedIconTheme: IconThemeData(
size: 24,
color: colorScheme.onSurfaceVariant,
),
selectedLabelStyle: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
fontFamily: AppTypography.fontFamily,
),
unselectedLabelStyle: TextStyle(
unselectedLabelStyle: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
fontFamily: AppTypography.fontFamily,
),
type: BottomNavigationBarType.fixed,
elevation: 8,
elevation: 3,
),
// ==================== Floating Action Button Theme ====================
floatingActionButtonTheme: const FloatingActionButtonThemeData(
backgroundColor: AppColors.accentCyan,
foregroundColor: AppColors.white,
elevation: 6,
shape: CircleBorder(),
iconSize: 24,
// Floating Action Button Theme
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: colorScheme.primaryContainer,
foregroundColor: colorScheme.onPrimaryContainer,
elevation: 3,
shape: const CircleBorder(),
),
// ==================== Snackbar Theme ====================
// Snackbar Theme
snackBarTheme: SnackBarThemeData(
backgroundColor: const Color(0xFF2A2A2A),
backgroundColor: colorScheme.inverseSurface,
contentTextStyle: AppTypography.bodyMedium.copyWith(
color: AppColors.white,
color: colorScheme.onInverseSurface,
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
behavior: SnackBarBehavior.floating,
elevation: 4,
elevation: 3,
),
// Badge Theme
badgeTheme: const BadgeThemeData(
backgroundColor: AppColors.danger,
textColor: Colors.white,
smallSize: 6,
largeSize: 16,
),
);
}

View File

@@ -1,66 +1,164 @@
import 'package:flutter/material.dart';
/// Seed color option for theme customization
class SeedColorOption {
const SeedColorOption({
required this.id,
required this.name,
required this.color,
});
final String id;
final String name;
final Color color;
}
/// App color palette following the Worker app design system.
///
/// Primary colors are used for main UI elements, tier colors for membership cards,
/// status colors for feedback, and neutral colors for text and backgrounds.
/// Uses Material 3 ColorScheme.fromSeed() for primary/surface colors.
/// Only status colors and tier gradients are defined here as they come from backend.
///
/// ## Usage Guide:
/// - For themed colors, use `Theme.of(context).colorScheme.xxx`
/// - For status colors, use `AppColors.success/warning/danger/info`
/// - For tier gradients, use `AppColors.xxxGradient`
///
/// ## ColorScheme Quick Reference:
/// ```dart
/// final cs = Theme.of(context).colorScheme;
/// cs.primary // Brand color for buttons, links
/// cs.onPrimary // Text/icons on primary
/// cs.primaryContainer // Softer brand backgrounds
/// cs.surface // Card/container backgrounds
/// cs.onSurface // Primary text color
/// cs.onSurfaceVariant // Secondary text color
/// cs.outline // Borders
/// cs.error // Error states
/// ```
class AppColors {
// Primary Colors
/// Main brand color - Used for primary buttons, app bar, etc.
static const primaryBlue = Color(0xFF005B9A);
AppColors._();
/// Light variant of primary color - Used for highlights and accents
static const lightBlue = Color(0xFF38B6FF);
// ==================== Brand Seed Colors ====================
/// Default brand color - Used as seed for ColorScheme.fromSeed()
static const Color defaultSeedColor = Color(0xFF005B9A);
/// Accent color for special actions - Used for FAB, links, etc.
static const accentCyan = Color(0xFF35C6F4);
/// Available seed colors for theme customization
/// User can select one of these to change the app's color scheme
static const List<SeedColorOption> seedColorOptions = [
SeedColorOption(
id: 'blue',
name: 'Xanh dương',
color: Color(0xFF005B9A),
),
SeedColorOption(
id: 'teal',
name: 'Xanh ngọc',
color: Color(0xFF009688),
),
SeedColorOption(
id: 'green',
name: 'Xanh lá',
color: Color(0xFF4CAF50),
),
SeedColorOption(
id: 'purple',
name: 'Tím',
color: Color(0xFF673AB7),
),
SeedColorOption(
id: 'indigo',
name: 'Chàm',
color: Color(0xFF3F51B5),
),
SeedColorOption(
id: 'orange',
name: 'Cam',
color: Color(0xFFFF5722),
),
SeedColorOption(
id: 'red',
name: 'Đỏ',
color: Color(0xFFE53935),
),
SeedColorOption(
id: 'pink',
name: 'Hồng',
color: Color(0xFFE91E63),
),
];
// Status Colors
/// Get seed color by ID, returns default if not found
static Color getSeedColorById(String? id) {
if (id == null) return defaultSeedColor;
return seedColorOptions
.firstWhere(
(option) => option.id == id,
orElse: () => seedColorOptions.first,
)
.color;
}
// ==================== Convenience Aliases (for backward compatibility) ====================
// DEPRECATED: Prefer using Theme.of(context).colorScheme instead
// These are kept for gradual migration
/// @Deprecated('Use Theme.of(context).colorScheme.primary instead')
static const Color primaryBlue = defaultSeedColor;
/// Alias for backward compatibility
static const Color seedColor = defaultSeedColor;
/// @Deprecated('Use Theme.of(context).colorScheme.primaryContainer')
static const Color lightBlue = Color(0xFF38B6FF);
/// @Deprecated('Use Theme.of(context).colorScheme.tertiary')
static const Color accentCyan = Color(0xFF35C6F4);
/// @Deprecated('Use Colors.white or colorScheme.surface instead')
static const Color white = Colors.white;
/// @Deprecated('Use Theme.of(context).colorScheme.surfaceContainerLowest')
static const Color grey50 = Color(0xFFf8f9fa);
/// @Deprecated('Use Theme.of(context).colorScheme.outline')
static const Color grey100 = Color(0xFFe9ecef);
/// @Deprecated('Use Theme.of(context).colorScheme.onSurfaceVariant')
static const Color grey500 = Color(0xFF6c757d);
/// @Deprecated('Use Theme.of(context).colorScheme.onSurface')
static const Color grey900 = Color(0xFF343a40);
// ==================== Status Colors (from backend) ====================
/// Success state - Used for completed actions, positive feedback
static const success = Color(0xFF28a745);
static const Color success = Color(0xFF28a745);
/// Warning state - Used for caution messages, pending states
static const warning = Color(0xFFffc107);
static const Color warning = Color(0xFFffc107);
/// Danger/Error state - Used for errors, destructive actions
static const danger = Color(0xFFdc3545);
static const Color danger = Color(0xFFdc3545);
/// Info state - Used for informational messages
static const info = Color(0xFF17a2b8);
static const Color info = Color(0xFF17a2b8);
// Neutral Colors
/// Lightest background shade
static const grey50 = Color(0xFFf8f9fa);
/// Light background/border shade
static const grey100 = Color(0xFFe9ecef);
/// Medium grey for secondary text
static const grey500 = Color(0xFF6c757d);
/// Dark grey for primary text
static const grey900 = Color(0xFF343a40);
/// Pure white
static const white = Color(0xFFFFFFFF);
// Tier Gradients for Membership Cards
// ==================== Tier Gradients for Membership Cards ====================
/// Diamond tier gradient (purple-blue)
static const diamondGradient = LinearGradient(
static const LinearGradient diamondGradient = LinearGradient(
colors: [Color(0xFF4A00E0), Color(0xFF8E2DE2)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
/// Platinum tier gradient (grey-silver)
static const platinumGradient = LinearGradient(
static const LinearGradient platinumGradient = LinearGradient(
colors: [Color(0xFF7F8C8D), Color(0xFFBDC3C7)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
/// Gold tier gradient (yellow-orange)
static const goldGradient = LinearGradient(
static const LinearGradient goldGradient = LinearGradient(
colors: [Color(0xFFf7b733), Color(0xFFfc4a1a)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,

View File

@@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:worker/core/database/app_settings_box.dart';
import 'package:worker/core/theme/colors.dart';
part 'theme_provider.g.dart';
/// Theme settings state
class ThemeSettings {
const ThemeSettings({
required this.seedColorId,
required this.themeMode,
});
final String seedColorId;
final ThemeMode themeMode;
/// Get the actual Color from the seed color ID
Color get seedColor => AppColors.getSeedColorById(seedColorId);
/// Get the SeedColorOption from the ID
SeedColorOption get seedColorOption => AppColors.seedColorOptions.firstWhere(
(option) => option.id == seedColorId,
orElse: () => AppColors.seedColorOptions.first,
);
ThemeSettings copyWith({
String? seedColorId,
ThemeMode? themeMode,
}) {
return ThemeSettings(
seedColorId: seedColorId ?? this.seedColorId,
themeMode: themeMode ?? this.themeMode,
);
}
}
/// Provider for managing theme settings with Hive persistence
/// Uses AppSettingsBox for storage
@Riverpod(keepAlive: true)
class ThemeSettingsNotifier extends _$ThemeSettingsNotifier {
@override
ThemeSettings build() {
return _loadFromSettings();
}
ThemeSettings _loadFromSettings() {
return ThemeSettings(
seedColorId: AppSettingsBox.getSeedColorId(),
themeMode: ThemeMode.values[AppSettingsBox.getThemeModeIndex()],
);
}
/// Update seed color
Future<void> setSeedColor(String colorId) async {
await AppSettingsBox.setSeedColorId(colorId);
state = state.copyWith(seedColorId: colorId);
}
/// Update theme mode (light/dark/system)
Future<void> setThemeMode(ThemeMode mode) async {
await AppSettingsBox.setThemeModeIndex(mode.index);
state = state.copyWith(themeMode: mode);
}
/// Toggle between light and dark mode
Future<void> toggleThemeMode() async {
final newMode =
state.themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
await setThemeMode(newMode);
}
}
/// Provider for the current seed color (convenience provider)
@riverpod
Color currentSeedColor(Ref ref) {
return ref.watch(
themeSettingsProvider.select((settings) => settings.seedColor),
);
}
/// Provider for available seed color options
@riverpod
List<SeedColorOption> seedColorOptions(Ref ref) {
return AppColors.seedColorOptions;
}

View File

@@ -0,0 +1,171 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'theme_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Provider for managing theme settings with Hive persistence
/// Uses AppSettingsBox for storage
@ProviderFor(ThemeSettingsNotifier)
const themeSettingsProvider = ThemeSettingsNotifierProvider._();
/// Provider for managing theme settings with Hive persistence
/// Uses AppSettingsBox for storage
final class ThemeSettingsNotifierProvider
extends $NotifierProvider<ThemeSettingsNotifier, ThemeSettings> {
/// Provider for managing theme settings with Hive persistence
/// Uses AppSettingsBox for storage
const ThemeSettingsNotifierProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'themeSettingsProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$themeSettingsNotifierHash();
@$internal
@override
ThemeSettingsNotifier create() => ThemeSettingsNotifier();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(ThemeSettings value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<ThemeSettings>(value),
);
}
}
String _$themeSettingsNotifierHash() =>
r'5befe194684b8c1857302c9573f5eee38199fa97';
/// Provider for managing theme settings with Hive persistence
/// Uses AppSettingsBox for storage
abstract class _$ThemeSettingsNotifier extends $Notifier<ThemeSettings> {
ThemeSettings build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<ThemeSettings, ThemeSettings>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<ThemeSettings, ThemeSettings>,
ThemeSettings,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}
/// Provider for the current seed color (convenience provider)
@ProviderFor(currentSeedColor)
const currentSeedColorProvider = CurrentSeedColorProvider._();
/// Provider for the current seed color (convenience provider)
final class CurrentSeedColorProvider
extends $FunctionalProvider<Color, Color, Color>
with $Provider<Color> {
/// Provider for the current seed color (convenience provider)
const CurrentSeedColorProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'currentSeedColorProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$currentSeedColorHash();
@$internal
@override
$ProviderElement<Color> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
Color create(Ref ref) {
return currentSeedColor(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Color value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<Color>(value),
);
}
}
String _$currentSeedColorHash() => r'c6807df84f2ac257b2650b2f1aa04d2572cbde37';
/// Provider for available seed color options
@ProviderFor(seedColorOptions)
const seedColorOptionsProvider = SeedColorOptionsProvider._();
/// Provider for available seed color options
final class SeedColorOptionsProvider
extends
$FunctionalProvider<
List<SeedColorOption>,
List<SeedColorOption>,
List<SeedColorOption>
>
with $Provider<List<SeedColorOption>> {
/// Provider for available seed color options
const SeedColorOptionsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'seedColorOptionsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$seedColorOptionsHash();
@$internal
@override
$ProviderElement<List<SeedColorOption>> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
List<SeedColorOption> create(Ref ref) {
return seedColorOptions(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(List<SeedColorOption> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<List<SeedColorOption>>(value),
);
}
}
String _$seedColorOptionsHash() => r'2cb0f7bf9e87394716f44a70b212b4d62f828152';