This commit is contained in:
Phuoc Nguyen
2025-10-17 17:22:28 +07:00
parent 2125e85d40
commit 628c81ce13
86 changed files with 31339 additions and 1710 deletions

View File

@@ -0,0 +1,460 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
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
class AppTheme {
// Prevent instantiation
AppTheme._();
// ==================== Light Theme ====================
/// Light theme configuration
static ThemeData lightTheme() {
final ColorScheme colorScheme = ColorScheme.fromSeed(
seedColor: AppColors.primaryBlue,
brightness: Brightness.light,
primary: AppColors.primaryBlue,
secondary: AppColors.lightBlue,
tertiary: AppColors.accentCyan,
error: AppColors.danger,
surface: AppColors.white,
background: AppColors.grey50,
);
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
fontFamily: AppTypography.fontFamily,
// ==================== App Bar Theme ====================
appBarTheme: AppBarTheme(
elevation: 0,
centerTitle: true,
backgroundColor: AppColors.primaryBlue,
foregroundColor: AppColors.white,
titleTextStyle: AppTypography.titleLarge.copyWith(
color: AppColors.white,
fontWeight: FontWeight.w600,
),
iconTheme: const IconThemeData(
color: AppColors.white,
size: 24,
),
systemOverlayStyle: SystemUiOverlayStyle.light,
),
// ==================== Card Theme ====================
cardTheme: const CardThemeData(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
clipBehavior: Clip.antiAlias,
color: AppColors.white,
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
// ==================== Elevated Button Theme ====================
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
foregroundColor: AppColors.white,
elevation: 2,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
textStyle: AppTypography.buttonText,
minimumSize: const Size(64, 48),
),
),
// ==================== Text Button Theme ====================
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: AppColors.primaryBlue,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
textStyle: AppTypography.buttonText,
),
),
// ==================== Outlined Button Theme ====================
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primaryBlue,
side: const BorderSide(color: AppColors.primaryBlue, width: 1.5),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
textStyle: AppTypography.buttonText,
minimumSize: const Size(64, 48),
),
),
// ==================== Input Decoration Theme ====================
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColors.white,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100, width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.grey100, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.primaryBlue, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.danger, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.danger, width: 2),
),
labelStyle: AppTypography.bodyMedium.copyWith(
color: AppColors.grey500,
),
hintStyle: AppTypography.bodyMedium.copyWith(
color: AppColors.grey500,
),
errorStyle: AppTypography.bodySmall.copyWith(
color: AppColors.danger,
),
),
// ==================== Bottom Navigation Bar Theme ====================
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: AppColors.white,
selectedItemColor: AppColors.primaryBlue,
unselectedItemColor: AppColors.grey500,
selectedIconTheme: IconThemeData(
size: 28,
color: AppColors.primaryBlue,
),
unselectedIconTheme: IconThemeData(
size: 24,
color: AppColors.grey500,
),
selectedLabelStyle: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
fontFamily: AppTypography.fontFamily,
),
unselectedLabelStyle: TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
fontFamily: AppTypography.fontFamily,
),
type: BottomNavigationBarType.fixed,
elevation: 8,
),
// ==================== Floating Action Button Theme ====================
floatingActionButtonTheme: const FloatingActionButtonThemeData(
backgroundColor: AppColors.accentCyan,
foregroundColor: AppColors.white,
elevation: 6,
shape: CircleBorder(),
iconSize: 24,
),
// ==================== Chip Theme ====================
chipTheme: ChipThemeData(
backgroundColor: AppColors.grey50,
selectedColor: AppColors.primaryBlue,
disabledColor: AppColors.grey100,
secondarySelectedColor: AppColors.lightBlue,
labelStyle: AppTypography.labelMedium,
secondaryLabelStyle: AppTypography.labelMedium.copyWith(
color: AppColors.white,
),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
// ==================== 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,
),
),
// ==================== Snackbar Theme ====================
snackBarTheme: SnackBarThemeData(
backgroundColor: AppColors.grey900,
contentTextStyle: AppTypography.bodyMedium.copyWith(
color: AppColors.white,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
behavior: SnackBarBehavior.floating,
elevation: 4,
),
// ==================== Divider Theme ====================
dividerTheme: const DividerThemeData(
color: AppColors.grey100,
thickness: 1,
space: 1,
),
// ==================== Icon Theme ====================
iconTheme: const IconThemeData(
color: AppColors.grey900,
size: 24,
),
// ==================== List Tile Theme ====================
listTileTheme: ListTileThemeData(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
titleTextStyle: AppTypography.titleMedium.copyWith(
color: AppColors.grey900,
),
subtitleTextStyle: AppTypography.bodyMedium.copyWith(
color: AppColors.grey500,
),
iconColor: AppColors.grey500,
),
// ==================== Switch Theme ====================
switchTheme: SwitchThemeData(
thumbColor: MaterialStateProperty.resolveWith((states) {
if (states.contains(MaterialState.selected)) {
return AppColors.primaryBlue;
}
return AppColors.grey500;
}),
trackColor: MaterialStateProperty.resolveWith((states) {
if (states.contains(MaterialState.selected)) {
return AppColors.lightBlue;
}
return AppColors.grey100;
}),
),
// ==================== Checkbox Theme ====================
checkboxTheme: CheckboxThemeData(
fillColor: MaterialStateProperty.resolveWith((states) {
if (states.contains(MaterialState.selected)) {
return AppColors.primaryBlue;
}
return AppColors.white;
}),
checkColor: MaterialStateProperty.all(AppColors.white),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
),
// ==================== Radio Theme ====================
radioTheme: RadioThemeData(
fillColor: MaterialStateProperty.resolveWith((states) {
if (states.contains(MaterialState.selected)) {
return AppColors.primaryBlue;
}
return AppColors.grey500;
}),
),
// ==================== Progress Indicator Theme ====================
progressIndicatorTheme: const ProgressIndicatorThemeData(
color: AppColors.primaryBlue,
linearTrackColor: AppColors.grey100,
circularTrackColor: AppColors.grey100,
),
// ==================== Badge Theme ====================
badgeTheme: const BadgeThemeData(
backgroundColor: AppColors.danger,
textColor: AppColors.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,
),
);
}
// ==================== Dark Theme ====================
/// Dark theme configuration
static ThemeData darkTheme() {
final ColorScheme colorScheme = ColorScheme.fromSeed(
seedColor: AppColors.primaryBlue,
brightness: Brightness.dark,
primary: AppColors.lightBlue,
secondary: AppColors.accentCyan,
tertiary: AppColors.primaryBlue,
error: AppColors.danger,
surface: const Color(0xFF1E1E1E),
background: const Color(0xFF121212),
);
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
fontFamily: AppTypography.fontFamily,
// ==================== App Bar Theme ====================
appBarTheme: AppBarTheme(
elevation: 0,
centerTitle: true,
backgroundColor: const Color(0xFF1E1E1E),
foregroundColor: AppColors.white,
titleTextStyle: AppTypography.titleLarge.copyWith(
color: AppColors.white,
fontWeight: FontWeight.w600,
),
iconTheme: const IconThemeData(
color: AppColors.white,
size: 24,
),
systemOverlayStyle: SystemUiOverlayStyle.light,
),
// ==================== Card Theme ====================
cardTheme: const CardThemeData(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
clipBehavior: Clip.antiAlias,
color: Color(0xFF1E1E1E),
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
// ==================== Elevated Button Theme ====================
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.lightBlue,
foregroundColor: AppColors.white,
elevation: 2,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
textStyle: AppTypography.buttonText,
minimumSize: const Size(64, 48),
),
),
// ==================== Input Decoration Theme ====================
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: const Color(0xFF2A2A2A),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Color(0xFF3A3A3A), width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Color(0xFF3A3A3A), width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.lightBlue, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.danger, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: AppColors.danger, width: 2),
),
labelStyle: AppTypography.bodyMedium.copyWith(
color: AppColors.grey500,
),
hintStyle: AppTypography.bodyMedium.copyWith(
color: AppColors.grey500,
),
errorStyle: AppTypography.bodySmall.copyWith(
color: AppColors.danger,
),
),
// ==================== 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(
fontSize: 12,
fontWeight: FontWeight.w600,
fontFamily: AppTypography.fontFamily,
),
unselectedLabelStyle: TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
fontFamily: AppTypography.fontFamily,
),
type: BottomNavigationBarType.fixed,
elevation: 8,
),
// ==================== Floating Action Button Theme ====================
floatingActionButtonTheme: const FloatingActionButtonThemeData(
backgroundColor: AppColors.accentCyan,
foregroundColor: AppColors.white,
elevation: 6,
shape: CircleBorder(),
iconSize: 24,
),
// ==================== Snackbar Theme ====================
snackBarTheme: SnackBarThemeData(
backgroundColor: const Color(0xFF2A2A2A),
contentTextStyle: AppTypography.bodyMedium.copyWith(
color: AppColors.white,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
behavior: SnackBarBehavior.floating,
elevation: 4,
),
);
}
}

View File

@@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
/// 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.
class AppColors {
// Primary Colors
/// Main brand color - Used for primary buttons, app bar, etc.
static const primaryBlue = Color(0xFF005B9A);
/// Light variant of primary color - Used for highlights and accents
static const lightBlue = Color(0xFF38B6FF);
/// Accent color for special actions - Used for FAB, links, etc.
static const accentCyan = Color(0xFF35C6F4);
// Status Colors
/// Success state - Used for completed actions, positive feedback
static const success = Color(0xFF28a745);
/// Warning state - Used for caution messages, pending states
static const warning = Color(0xFFffc107);
/// Danger/Error state - Used for errors, destructive actions
static const danger = Color(0xFFdc3545);
/// Info state - Used for informational messages
static const 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
/// Diamond tier gradient (purple-blue)
static const diamondGradient = LinearGradient(
colors: [Color(0xFF4A00E0), Color(0xFF8E2DE2)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
/// Platinum tier gradient (grey-silver)
static const platinumGradient = LinearGradient(
colors: [Color(0xFF7F8C8D), Color(0xFFBDC3C7)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
/// Gold tier gradient (yellow-orange)
static const goldGradient = LinearGradient(
colors: [Color(0xFFf7b733), Color(0xFFfc4a1a)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
}

View File

@@ -0,0 +1,243 @@
import 'package:flutter/material.dart';
/// App typography system following Material 3 type scale
/// Uses Roboto as the primary font family
class AppTypography {
// Prevent instantiation
AppTypography._();
/// Font family used throughout the app
static const String fontFamily = 'Roboto';
// ==================== Display Styles ====================
/// Display Large - 32sp, Bold
/// Used for: Large hero text, splash screens
static const TextStyle displayLarge = TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
fontFamily: fontFamily,
letterSpacing: 0,
height: 1.2,
);
/// Display Medium - 28sp, Semi-bold
/// Used for: Page titles, section headers
static const TextStyle displayMedium = TextStyle(
fontSize: 28,
fontWeight: FontWeight.w600,
fontFamily: fontFamily,
letterSpacing: 0,
height: 1.2,
);
/// Display Small - 24sp, Semi-bold
/// Used for: Sub-section headers
static const TextStyle displaySmall = TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
fontFamily: fontFamily,
letterSpacing: 0,
height: 1.3,
);
// ==================== Headline Styles ====================
/// Headline Large - 24sp, Semi-bold
/// Used for: Main headings, dialog titles
static const TextStyle headlineLarge = TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
fontFamily: fontFamily,
letterSpacing: 0,
height: 1.3,
);
/// Headline Medium - 20sp, Semi-bold
/// Used for: Card titles, list headers
static const TextStyle headlineMedium = TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
fontFamily: fontFamily,
letterSpacing: 0.15,
height: 1.3,
);
/// Headline Small - 18sp, Medium
/// Used for: Small headers, emphasized text
static const TextStyle headlineSmall = TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
fontFamily: fontFamily,
letterSpacing: 0.15,
height: 1.4,
);
// ==================== Title Styles ====================
/// Title Large - 20sp, Medium
/// Used for: App bar titles, prominent labels
static const TextStyle titleLarge = TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
fontFamily: fontFamily,
letterSpacing: 0.15,
height: 1.4,
);
/// Title Medium - 16sp, Medium
/// Used for: List item titles, card headers
static const TextStyle titleMedium = TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
fontFamily: fontFamily,
letterSpacing: 0.15,
height: 1.5,
);
/// Title Small - 14sp, Medium
/// Used for: Small titles, tab labels
static const TextStyle titleSmall = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
fontFamily: fontFamily,
letterSpacing: 0.1,
height: 1.5,
);
// ==================== Body Styles ====================
/// Body Large - 16sp, Regular
/// Used for: Main body text, descriptions
static const TextStyle bodyLarge = TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: fontFamily,
letterSpacing: 0.5,
height: 1.5,
);
/// Body Medium - 14sp, Regular
/// Used for: Secondary body text, captions
static const TextStyle bodyMedium = TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
fontFamily: fontFamily,
letterSpacing: 0.25,
height: 1.5,
);
/// Body Small - 12sp, Regular
/// Used for: Small body text, helper text
static const TextStyle bodySmall = TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
fontFamily: fontFamily,
letterSpacing: 0.4,
height: 1.5,
);
// ==================== Label Styles ====================
/// Label Large - 14sp, Medium
/// Used for: Button text, input labels
static const TextStyle labelLarge = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
fontFamily: fontFamily,
letterSpacing: 0.1,
height: 1.4,
);
/// Label Medium - 12sp, Medium
/// Used for: Small button text, chips
static const TextStyle labelMedium = TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
fontFamily: fontFamily,
letterSpacing: 0.5,
height: 1.4,
);
/// Label Small - 12sp, Regular
/// Used for: Overline text, tags, badges
static const TextStyle labelSmall = TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
fontFamily: fontFamily,
letterSpacing: 0.5,
height: 1.4,
);
// ==================== Special Purpose Styles ====================
/// Points Display - 28sp, Bold
/// Used for: Loyalty points display on member cards
static const TextStyle pointsDisplay = TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
fontFamily: fontFamily,
letterSpacing: 0,
height: 1.2,
);
/// Price Large - 20sp, Bold
/// Used for: Product prices, totals
static const TextStyle priceLarge = TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
fontFamily: fontFamily,
letterSpacing: 0,
height: 1.3,
);
/// Price Medium - 16sp, Semi-bold
/// Used for: List item prices
static const TextStyle priceMedium = TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: fontFamily,
letterSpacing: 0,
height: 1.3,
);
/// Price Small - 14sp, Semi-bold
/// Used for: Small price displays
static const TextStyle priceSmall = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
fontFamily: fontFamily,
letterSpacing: 0,
height: 1.3,
);
/// Button Text - 14sp, Medium
/// Used for: All button labels
static const TextStyle buttonText = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
fontFamily: fontFamily,
letterSpacing: 1.25,
height: 1.0,
);
/// Overline - 10sp, Medium, Uppercase
/// Used for: Section labels, categories
static const TextStyle overline = TextStyle(
fontSize: 10,
fontWeight: FontWeight.w500,
fontFamily: fontFamily,
letterSpacing: 1.5,
height: 1.6,
);
/// Caption - 12sp, Regular
/// Used for: Image captions, timestamps
static const TextStyle caption = TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
fontFamily: fontFamily,
letterSpacing: 0.4,
height: 1.33,
);
}