Merge branch 'refs/heads/master' into offers_discounts
# Conflicts: # lib/presentation/my_family/widget/family_cards.dartpull/319/head
commit
4fd2312e61
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,444 @@
|
||||
# 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<void> callInitializations() async {
|
||||
// ... Firebase, dependencies setup ...
|
||||
|
||||
// Restore dark mode BEFORE first frame
|
||||
getIt.get<ProfileSettingsViewModel>().loadDarkMode();
|
||||
}
|
||||
```
|
||||
|
||||
### Theme Application in Widget Tree
|
||||
|
||||
```dart
|
||||
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
|
||||
|
||||
```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<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**:
|
||||
```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<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
|
||||
|
||||
```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<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
|
||||
|
||||
Loading…
Reference in New Issue