# Theme Implementation Guide - HMG Patient App ## Overview This Flutter application implements a comprehensive **dual-theme system** supporting both **Light Mode** and **Dark Mode**. The theme system is built with a combination of Flutter's native theming and a custom color management system. --- ## Architecture ### 1. **Core Components** #### A. AppTheme Class (`lib/theme/app_theme.dart`) - **Purpose**: Defines the Material theme configurations for both light and dark modes - **Key Methods**: - `getTheme(isArabic)` - Returns light theme configuration - `getDarkTheme(isArabic)` - Returns dark theme configuration **Features**: - Dynamic font family selection based on locale (Arabic: `CairoArabic`, English: `Poppins`) - Platform-specific page transitions (Android: Zoom, iOS: Cupertino) - Consistent styling for AppBar, BottomSheet, FloatingActionButton - Transparent splash colors for better UX - System overlay styles (Dark for light theme, Light for dark theme) #### B. AppColors Class (`lib/theme/colors.dart`) - **Purpose**: Centralized color management with dual-palette support - **Architecture**: Three-tier color system **Three Access Patterns**: 1. **Static Getters (Global State-Based)** ```dart AppColors.primaryRedColor // Uses AppColors.isDarkMode flag ``` - Checks global `AppColors.isDarkMode` boolean - Returns dark or light palette value accordingly 2. **AppColorsDark Class (Dark Palette)** ```dart AppColors.dark.primaryRedColor // Always dark variant ``` - Constant dark mode color values - Used as source for dark theme colors 3. **BuildContext Extension (Theme-Aware)** ```dart context.primaryRedColor // Uses Theme brightness ``` - Reads `Theme.of(context).brightness` - Automatically adapts to MaterialApp's themeMode --- ## 2. **Theme State Management** ### ProfileSettingsViewModel Located in `lib/features/profile_settings/profile_settings_view_model.dart` **Responsibilities**: - Manages dark mode state (`_isDarkMode` boolean) - Persists preference to local storage (CacheService) - Triggers UI rebuilds on theme changes **Key Methods**: ```dart // Called at app startup (before first frame) void loadDarkMode() { final saved = _cacheService.getBool(key: _darkModeKey); _isDarkMode = saved ?? false; AppColors.isDarkMode = _isDarkMode; // No notifyListeners() - called before build } // Toggle theme and persist void toggleDarkMode(bool value) { _isDarkMode = value; AppColors.isDarkMode = value; _cacheService.saveBool(key: _darkModeKey, value: value); notifyListeners(); // Triggers rebuild } ``` --- ## 3. **Application Lifecycle** ### Initialization Flow (`main.dart`) ```dart Future callInitializations() async { // ... Firebase, dependencies setup ... // Restore dark mode BEFORE first frame getIt.get().loadDarkMode(); } ``` ### Theme Application in Widget Tree ```dart class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return Consumer( builder: (context, profileVm, _) { final isArabic = EasyLocalization.of(context)?.locale.languageCode == "ar"; return MaterialApp( key: ValueKey('app_theme_${profileVm.isDarkMode}'), // Force rebuild on theme change theme: AppTheme.getTheme(isArabic), darkTheme: AppTheme.getDarkTheme(isArabic), themeMode: profileVm.isDarkMode ? ThemeMode.dark : ThemeMode.light, // ... other properties ); }, ); } } ``` **Key Mechanism**: - `Consumer` listens for theme changes - When `toggleDarkMode()` is called → `notifyListeners()` → Widget rebuilds - `themeMode` property switches between `ThemeMode.dark` and `ThemeMode.light` - `ValueKey` forces MaterialApp to rebuild entirely on theme change --- ## 4. **Color System Design** ### Dual-Palette Architecture ```dart class AppColors { static bool isDarkMode = false; // Global flag // Pattern: Conditional getter static Color get primaryRedColor => isDarkMode ? dark.primaryRedColor : const Color(0xFFED1C2B); // Dark palette instance static const AppColorsDark dark = AppColorsDark(); } class AppColorsDark { const AppColorsDark(); // Dark mode variants Color get primaryRedColor => const Color(0xFFDE5C5D); Color get scaffoldBgColor => const Color(0xFF191919); Color get textColor => const Color(0xFFFFFFFF); // ... 100+ color definitions } ``` ### BuildContext Extension for Theme-Aware Colors ```dart extension AppColorsContext on BuildContext { bool get _isDark => Theme.of(this).brightness == Brightness.dark; Color get scaffoldBgColor => _isDark ? AppColors.dark.scaffoldBgColor : const Color(0xFFF8F8F8); Color get textColor => _isDark ? AppColors.dark.textColor : const Color(0xFF2E3039); } ``` **Usage in Widgets**: ```dart // Option 1: Static (uses global flag) color: AppColors.primaryRedColor // Option 2: Extension (uses Theme.of(context)) color: context.primaryRedColor // Option 3: Direct dark access color: AppColors.dark.primaryRedColor ``` --- ## 5. **User Interface Implementation** ### Theme Toggle Control (`profile_settings.dart`) ```dart Consumer( builder: (context, profileVm, _) { return actionItem( AppAssets.darkModeIcon, LocaleKeys.darkMode.tr(context: context), () { profileVm.toggleDarkMode(!profileVm.isDarkMode); }, switchValue: profileVm.isDarkMode, onSwitchChanged: (value) { profileVm.toggleDarkMode(value); }, ); }, ) ``` --- ## 6. **Color Categories** ### Organized Color Groups | Category | Light Example | Dark Example | |----------|---------------|--------------| | **Scaffold/Background** | `0xFFF8F8F8` | `0xFF191919` | | **Primary Brand** | `0xFFED1C2B` (Red) | `0xFFDE5C5D` (Softer Red) | | **Text** | `0xFF2E3039` (Dark) | `0xFFFFFFFF` (White) | | **Success** | `0xFF18C273` (Green) | `0xFF18C273` (Same) | | **Error** | `0xFFED1C2B` | `0xFFD63D48` | | **Card Surface** | `0xFFFFFFFF` | `0xFF1E1E1E` | | **Borders** | `0x332E3039` | `0x55ECECEC` | | **Shimmer Base** | `0xFFE0E0E0` | `0xFF2C2C2C` | ### Special Purpose Colors - **Rating Stars**: Constant across themes (`0xFFFFA726`) - **Health Calculators**: Feature-specific colors maintained in both themes - **Status Colors**: Pending, Processing, Completed, Rejected - **Info Banners**: Warning backgrounds with adjusted opacity for dark mode --- ## 7. **Best Practices for Developers** ### Adding New Colors 1. **Define in Light Theme**: ```dart static Color get newFeatureColor => isDarkMode ? dark.newFeatureColor : const Color(0xFFXXXXXX); ``` 2. **Add Dark Variant**: ```dart class AppColorsDark { Color get newFeatureColor => const Color(0xFFYYYYYY); } ``` 3. **Optional: Add to BuildContext Extension**: ```dart extension AppColorsContext on BuildContext { Color get newFeatureColor => _isDark ? AppColors.dark.newFeatureColor : const Color(0xFFXXXXXX); } ``` ### Using Colors in Widgets **Recommended Approach**: ```dart // For colors that should adapt to theme Container( color: AppColors.scaffoldBgColor, // Uses global isDarkMode ) // Or using context extension Container( color: context.scaffoldBgColor, // Uses Theme.brightness ) ``` **Avoid**: ```dart // Don't hardcode colors Container( color: Color(0xFFED1C2B), // ❌ Won't adapt to dark mode ) ``` ### Testing Theme Changes ```dart // Toggle theme programmatically context.read().toggleDarkMode(true); // Check current state final isDark = context.read().isDarkMode; ``` --- ## 8. **Persistence Layer** ### CacheService Integration - Uses shared_preferences or similar local storage - Key: `_darkModeKey` (defined in ProfileSettingsViewModel) - Automatically loads on app startup - Saves immediately on toggle **Flow**: 1. App launches → `loadDarkMode()` reads from cache 2. User toggles → `toggleDarkMode(value)` saves to cache 3. App restart → Previous preference restored --- ## 9. **Font Handling** ### Locale-Specific Fonts ```dart // In AppTheme fontFamily: isArabic ? 'CairoArabic' : 'Poppins' textTheme: const TextTheme( displayLarge: TextStyle(fontFamily: 'CairoArabic'), bodyLarge: TextStyle(fontFamily: 'CairoArabic'), ) ``` **Rationale**: Arabic text requires specialized fonts for proper rendering --- ## 10. **System Integration** ### AppBar System Overlay ```dart // Light Theme systemOverlayStyle: SystemUiOverlayStyle.dark // Dark status bar icons // Dark Theme systemOverlayStyle: SystemUiOverlayStyle.light // Light status bar icons ``` ### Platform-Specific Adjustments ```dart // SafeArea handling SafeArea( top: false, bottom: Platform.isIOS ? false : true, ) ``` --- ## 11. **Advanced Features** ### Gradient Support ```dart static const LinearGradient aiLinearGradient = LinearGradient( colors: [Color(0xFF8A38F5), Color(0xFFE20BBB)], begin: Alignment.topLeft, end: Alignment.bottomRight, ); ``` *Note: Gradients remain constant across themes for brand consistency* ### Transparency Management ```dart static const transparent = Colors.transparent; // Used for splash colors, bottomSheet backgrounds ``` --- ## 12. **Common Pitfalls & Solutions** ### Issue 1: Colors Not Updating **Problem**: Widget doesn't reflect theme change **Solution**: Ensure widget rebuilds on theme change ```dart // Wrap with Consumer or use context.watch Consumer( builder: (context, vm, _) => YourWidget(), ) ``` ### Issue 2: Inconsistent Colors **Problem**: Some UI elements use wrong theme **Solution**: Always use `AppColors.*` or `context.*`, never hardcode ### Issue 3: Theme Not Persisting **Problem**: Theme resets on app restart **Solution**: Verify `loadDarkMode()` is called in `callInitializations()` --- ## 13. **File Structure** ``` lib/ ├── theme/ │ ├── app_theme.dart # Theme configurations │ └── colors.dart # Color definitions & dark palette ├── features/ │ └── profile_settings/ │ └── profile_settings_view_model.dart # Theme state management ├── presentation/ │ └── profile_settings/ │ └── profile_settings.dart # UI with theme toggle └── main.dart # Theme initialization & application ``` --- ## 14. **Summary** ### Workflow 1. **Initialization**: Load saved theme preference → Set `AppColors.isDarkMode` 2. **User Action**: Toggle switch in Settings → Call `toggleDarkMode()` 3. **State Update**: Update flag → Save to cache → Notify listeners 4. **UI Rebuild**: Consumer rebuilds → MaterialApp switches `themeMode` 5. **Color Resolution**: All `AppColors.*` getters return appropriate palette ### Key Advantages ✅ **Centralized**: Single source of truth for colors ✅ **Type-Safe**: Compile-time color checking ✅ **Persistent**: Survives app restarts ✅ **Flexible**: Multiple access patterns for different use cases ✅ **Maintainable**: Easy to add/modify colors ✅ **Performance**: No runtime color calculations, just conditional returns --- ## 15. **Migration Checklist for New Features** When adding a new screen/feature: - [ ] Use `AppColors.*` for all color references - [ ] Test in both light and dark modes - [ ] Ensure text contrast meets accessibility standards - [ ] Add dark variants for any new colors - [ ] Verify with Arabic locale (font rendering) - [ ] Check shimmer/loading states in both themes - [ ] Test on both iOS and Android --- **Last Updated**: January 2024 **Maintained By**: HMG Development Team