You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
HMG_Patient_App_New/THEME_IMPLEMENTATION_GUIDE.md

12 KiB

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)

    AppColors.primaryRedColor  // Uses AppColors.isDarkMode flag
    
    • Checks global AppColors.isDarkMode boolean
    • Returns dark or light palette value accordingly
  2. AppColorsDark Class (Dark Palette)

    AppColors.dark.primaryRedColor  // Always dark variant
    
    • Constant dark mode color values
    • Used as source for dark theme colors
  3. BuildContext Extension (Theme-Aware)

    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:

// 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)

Future<void> callInitializations() async {
  // ... Firebase, dependencies setup ...
  
  // Restore dark mode BEFORE first frame
  getIt.get<ProfileSettingsViewModel>().loadDarkMode();
}

Theme Application in Widget Tree

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<ProfileSettingsViewModel>(
      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<ProfileSettingsViewModel> 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

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

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:

// 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)

Consumer<ProfileSettingsViewModel>(
  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:
static Color get newFeatureColor => 
  isDarkMode ? dark.newFeatureColor : const Color(0xFFXXXXXX);
  1. Add Dark Variant:
class AppColorsDark {
  Color get newFeatureColor => const Color(0xFFYYYYYY);
}
  1. Optional: Add to BuildContext Extension:
extension AppColorsContext on BuildContext {
  Color get newFeatureColor => 
    _isDark ? AppColors.dark.newFeatureColor : const Color(0xFFXXXXXX);
}

Using Colors in Widgets

Recommended Approach:

// 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:

// Don't hardcode colors
Container(
  color: Color(0xFFED1C2B), // ❌ Won't adapt to dark mode
)

Testing Theme Changes

// Toggle theme programmatically
context.read<ProfileSettingsViewModel>().toggleDarkMode(true);

// Check current state
final isDark = context.read<ProfileSettingsViewModel>().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

// 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

// Light Theme
systemOverlayStyle: SystemUiOverlayStyle.dark  // Dark status bar icons

// Dark Theme
systemOverlayStyle: SystemUiOverlayStyle.light  // Light status bar icons

Platform-Specific Adjustments

// SafeArea handling
SafeArea(
  top: false,
  bottom: Platform.isIOS ? false : true,
)

11. Advanced Features

Gradient Support

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

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

// Wrap with Consumer or use context.watch
Consumer<ProfileSettingsViewModel>(
  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