real time theme switching
This commit is contained in:
parent
4bc390c3e3
commit
e283a5cef1
|
|
@ -0,0 +1,148 @@
|
||||||
|
# 🔧 Language System - Quick Implementation Guide
|
||||||
|
|
||||||
|
## ⚡ سریعترین راه اضافه کردن انتخاب زبان
|
||||||
|
|
||||||
|
### 1️⃣ Import های مورد نیاز
|
||||||
|
```dart
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import '../../widgets/language_selection_dialog.dart';
|
||||||
|
import '../../gen/assets.gen.dart';
|
||||||
|
import '../../res/colors.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2️⃣ اضافه کردن State Variables
|
||||||
|
```dart
|
||||||
|
class _YourPageState extends State<YourPage> {
|
||||||
|
String _currentLanguage = '🇺🇲 English';
|
||||||
|
String _currentFlag = 'assets/icons/usa circle.svg';
|
||||||
|
final GlobalKey _languageKey = GlobalKey();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3️⃣ کپی/پیست Widget کامل
|
||||||
|
```dart
|
||||||
|
GestureDetector(
|
||||||
|
key: _languageKey,
|
||||||
|
onTap: () {
|
||||||
|
showLanguageSelectionOverlay(
|
||||||
|
context,
|
||||||
|
_currentLanguage,
|
||||||
|
(language, flag) {
|
||||||
|
setState(() {
|
||||||
|
_currentLanguage = language;
|
||||||
|
_currentFlag = flag;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_languageKey,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: ClipRRect(
|
||||||
|
key: ValueKey(_currentFlag),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
child: _currentFlag.endsWith('.svg')
|
||||||
|
? SvgPicture.asset(_currentFlag, width: 24, height: 18)
|
||||||
|
: Image.asset(_currentFlag, width: 24, height: 18, fit: BoxFit.cover,
|
||||||
|
errorBuilder: (context, error, stackTrace) => Container(
|
||||||
|
width: 24, height: 18,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.greyBorder.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Icon(Icons.flag, color: AppColors.textSecondary, size: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: Text(
|
||||||
|
_currentLanguage,
|
||||||
|
key: ValueKey(_currentLanguage),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppColors.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
SvgPicture.asset(Assets.icons.arrowRight.path, color: AppColors.textPrimary),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 مواقع استفاده
|
||||||
|
|
||||||
|
### در Onboarding (بالای راست)
|
||||||
|
```dart
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.fromLTRB(width/15, height/20, width/15, 0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text('Skip'), // دکمه Skip
|
||||||
|
LanguageWidget(), // Widget بالا
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### در Profile (قسمت Settings)
|
||||||
|
```dart
|
||||||
|
_buildInfoTile(
|
||||||
|
icon: SvgPicture.asset(Assets.icons.languageSquare.path, width: 22),
|
||||||
|
title: 'Language',
|
||||||
|
trailing: LanguageWidget(), // Widget بالا
|
||||||
|
),
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ تنظیمات اختیاری
|
||||||
|
|
||||||
|
### حافظه زبان با SharedPreferences
|
||||||
|
```dart
|
||||||
|
Future<void> _saveLanguage(String languageCode) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString('selected_language', languageCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadLanguage() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final savedLang = prefs.getString('selected_language') ?? 'en';
|
||||||
|
// set initial language based on savedLang
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### اضافه کردن زبان جدید
|
||||||
|
در `language_selection_dialog.dart`:
|
||||||
|
```dart
|
||||||
|
final List<Map<String, String>> languages = [
|
||||||
|
{'name': '🇺🇲 English', 'code': 'en', 'flag': 'assets/icons/usa circle.svg'},
|
||||||
|
{'name': '🇦🇪 العربية', 'code': 'ar', 'flag': 'assets/icons/arab circle.svg'},
|
||||||
|
{'name': '🇮🇷 فارسی', 'code': 'fa', 'flag': 'assets/icons/iran circle.svg'},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚨 نکات مهم
|
||||||
|
|
||||||
|
1. **GlobalKey ضروری است** برای positioning صحیح overlay
|
||||||
|
2. **AnimatedSwitcher** برای animation های smooth
|
||||||
|
3. **Error handling** برای asset های مفقود
|
||||||
|
4. **ValueKey** برای performance بهتر
|
||||||
|
|
||||||
|
## 🔍 Debug
|
||||||
|
|
||||||
|
```dart
|
||||||
|
print('Current language: $_currentLanguage');
|
||||||
|
print('Current flag: $_currentFlag');
|
||||||
|
print('Target position: ${_languageKey.currentContext?.findRenderObject()}');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
*راهنمای سریع - نسخه 1.0*
|
||||||
|
|
@ -0,0 +1,439 @@
|
||||||
|
# 🌐 سیستم تغییر زبان در اپلیکیشن LBA
|
||||||
|
|
||||||
|
## 📋 فهرست مطالب
|
||||||
|
1. [معرفی کلی](#معرفی-کلی)
|
||||||
|
2. [ساختار فایلها](#ساختار-فایلها)
|
||||||
|
3. [زبانهای پشتیبانی شده](#زبانهای-پشتیبانی-شده)
|
||||||
|
4. [کامپوننتهای کلیدی](#کامپوننتهای-کلیدی)
|
||||||
|
5. [نحوه پیادهسازی](#نحوه-پیادهسازی)
|
||||||
|
6. [مدیریت State](#مدیریت-state)
|
||||||
|
7. [UI/UX طراحی](#uiux-طراحی)
|
||||||
|
8. [نکات فنی](#نکات-فنی)
|
||||||
|
9. [استفاده در صفحات مختلف](#استفاده-در-صفحات-مختلف)
|
||||||
|
10. [عیبیابی](#عیبیابی)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 معرفی کلی
|
||||||
|
|
||||||
|
سیستم تغییر زبان در اپ LBA امکان انتخاب بین سه زبان **انگلیسی**، **عربی** و **فارسی** را فراهم میکند. این سیستم در دو صفحه اصلی قابل دسترسی است:
|
||||||
|
- **صفحه Onboarding** (بالای راست)
|
||||||
|
- **صفحه Profile** (بخش تنظیمات)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 ساختار فایلها
|
||||||
|
|
||||||
|
```
|
||||||
|
lib/
|
||||||
|
├── widgets/
|
||||||
|
│ └── language_selection_dialog.dart # کامپوننت اصلی انتخاب زبان
|
||||||
|
├── screens/
|
||||||
|
│ ├── auth/
|
||||||
|
│ │ └── onboarding_page.dart # صفحه معرفی با انتخاب زبان
|
||||||
|
│ └── mains/profile/
|
||||||
|
│ └── profile.dart # صفحه پروفایل
|
||||||
|
├── gen/
|
||||||
|
│ └── assets.gen.dart # فایلهای asset تولید شده
|
||||||
|
└── res/
|
||||||
|
└── colors.dart # رنگهای اپلیکیشن
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌍 زبانهای پشتیبانی شده
|
||||||
|
|
||||||
|
| زبان | کد | پرچم | فایل آیکون |
|
||||||
|
|------|----|----- |------------|
|
||||||
|
| **English** | `en` | 🇺🇲 | `assets/icons/usa circle.svg` |
|
||||||
|
| **العربية** | `ar` | 🇦🇪 | `assets/icons/arab circle.svg` |
|
||||||
|
| **فارسی** | `fa` | 🇮🇷 | `assets/icons/iran circle.svg` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 کامپوننتهای کلیدی
|
||||||
|
|
||||||
|
### 1. `LanguageSelectionOverlay`
|
||||||
|
کامپوننت اصلی که overlay انتخاب زبان را نمایش میدهد.
|
||||||
|
|
||||||
|
**پراپهای ورودی:**
|
||||||
|
```dart
|
||||||
|
final String currentLanguage; // زبان فعلی
|
||||||
|
final Function(String language, String flag) onLanguageSelected; // کالبک انتخاب
|
||||||
|
final GlobalKey targetKey; // کلید موقعیتیابی
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `showLanguageSelectionOverlay`
|
||||||
|
تابع کمکی برای نمایش overlay:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void showLanguageSelectionOverlay(
|
||||||
|
BuildContext context,
|
||||||
|
String currentLanguage,
|
||||||
|
Function(String language, String flag) onLanguageSelected,
|
||||||
|
GlobalKey targetKey,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 نحوه پیادهسازی
|
||||||
|
|
||||||
|
### مرحله 1: اضافه کردن State متغیرها
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class _YourPageState extends State<YourPage> {
|
||||||
|
String _currentLanguage = '🇺🇲 English';
|
||||||
|
String _currentFlag = 'assets/icons/usa circle.svg';
|
||||||
|
final GlobalKey _languageKey = GlobalKey();
|
||||||
|
|
||||||
|
// ... سایر کدها
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### مرحله 2: ایجاد UI انتخاب زبان
|
||||||
|
|
||||||
|
```dart
|
||||||
|
GestureDetector(
|
||||||
|
key: _languageKey,
|
||||||
|
onTap: () {
|
||||||
|
showLanguageSelectionOverlay(
|
||||||
|
context,
|
||||||
|
_currentLanguage,
|
||||||
|
(language, flag) {
|
||||||
|
setState(() {
|
||||||
|
_currentLanguage = language;
|
||||||
|
_currentFlag = flag;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_languageKey,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// نمایش پرچم
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: ClipRRect(
|
||||||
|
key: ValueKey(_currentFlag),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
_currentFlag,
|
||||||
|
width: 24,
|
||||||
|
height: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
// نمایش نام زبان
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: Text(
|
||||||
|
_currentLanguage,
|
||||||
|
key: ValueKey(_currentLanguage),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppColors.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
// آیکون فلش
|
||||||
|
SvgPicture.asset(
|
||||||
|
Assets.icons.arrowRight.path,
|
||||||
|
color: AppColors.textPrimary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
```
|
||||||
|
|
||||||
|
### مرحله 3: اضافه کردن Import ها
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import '../../widgets/language_selection_dialog.dart';
|
||||||
|
import '../../gen/assets.gen.dart';
|
||||||
|
import '../../res/colors.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 مدیریت State
|
||||||
|
|
||||||
|
### Local State Management
|
||||||
|
هر صفحه state زبان خود را بهصورت جداگانه مدیریت میکند:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// متغیرهای state
|
||||||
|
String _currentLanguage = '🇺🇲 English'; // نام کامل زبان با پرچم
|
||||||
|
String _currentFlag = 'assets/icons/usa circle.svg'; // مسیر فایل پرچم
|
||||||
|
|
||||||
|
// تابع تغییر زبان
|
||||||
|
void _changeLanguage(String language, String flag) {
|
||||||
|
setState(() {
|
||||||
|
_currentLanguage = language;
|
||||||
|
_currentFlag = flag;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Persistence (در آینده)
|
||||||
|
برای ذخیره انتخاب زبان کاربر میتوان از `SharedPreferences` استفاده کرد:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// ذخیره زبان
|
||||||
|
Future<void> _saveLanguage(String languageCode) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString('selected_language', languageCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// بازیابی زبان
|
||||||
|
Future<String> _loadLanguage() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString('selected_language') ?? 'en';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 UI/UX طراحی
|
||||||
|
|
||||||
|
### Positioning Logic
|
||||||
|
سیستم positioning هوشمند برای مکانیابی overlay:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// تشخیص موقعیت target در صفحه
|
||||||
|
if (targetPosition.dx > screenWidth * 0.7) {
|
||||||
|
// انتهای راست (مثل onboarding)
|
||||||
|
rightPosition = 16;
|
||||||
|
} else {
|
||||||
|
// وسط یا چپ (مثل profile)
|
||||||
|
rightPosition = 24;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Animation ها
|
||||||
|
- **Scale Animation**: ورود overlay با افکت scale
|
||||||
|
- **Opacity Animation**: fade in/out
|
||||||
|
- **Switcher Animation**: تغییر پرچم و متن با `AnimatedSwitcher`
|
||||||
|
|
||||||
|
```dart
|
||||||
|
AnimationController _animationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
);
|
||||||
|
|
||||||
|
Animation<double> _scaleAnimation = Tween<double>(
|
||||||
|
begin: 0.8,
|
||||||
|
end: 1.0
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeOutBack
|
||||||
|
));
|
||||||
|
```
|
||||||
|
|
||||||
|
### طراحی Overlay
|
||||||
|
|
||||||
|
```dart
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.surface,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
bottomLeft: Radius.circular(16),
|
||||||
|
bottomRight: Radius.circular(16),
|
||||||
|
topLeft: Radius.circular(16),
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: AppColors.shadowColor,
|
||||||
|
blurRadius: 15,
|
||||||
|
spreadRadius: 3,
|
||||||
|
offset: const Offset(0, 5),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// محتوای overlay
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡ نکات فنی
|
||||||
|
|
||||||
|
### 1. Error Handling
|
||||||
|
برای مواردی که asset پرچم بارگذاری نشود:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return Container(
|
||||||
|
width: 24,
|
||||||
|
height: 18,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.greyBorder.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.flag,
|
||||||
|
color: AppColors.textSecondary,
|
||||||
|
size: 12,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Responsive Design
|
||||||
|
سیستم با اندازههای مختلف صفحه سازگار است:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final overlayWidth = screenWidth * 0.45; // 45% عرض صفحه
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. RTL Support
|
||||||
|
پشتیبانی از زبانهای راست به چپ:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
textDirection: (code == 'ar' || code == 'fa')
|
||||||
|
? TextDirection.rtl
|
||||||
|
: TextDirection.ltr,
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Performance
|
||||||
|
استفاده از `ValueKey` برای بهینهسازی rebuilds:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
AnimatedSwitcher(
|
||||||
|
child: Widget(
|
||||||
|
key: ValueKey(_currentLanguage), // جلوگیری از rebuild غیرضروری
|
||||||
|
// ...
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 استفاده در صفحات مختلف
|
||||||
|
|
||||||
|
### صفحه Onboarding
|
||||||
|
```dart
|
||||||
|
// موقعیت: بالای راست صفحه
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.fromLTRB(width/15, height/20, width/15, 0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
// دکمه Skip
|
||||||
|
GestureDetector(onTap: _skipOnboarding, child: Text('Skip')),
|
||||||
|
// انتخاب زبان
|
||||||
|
LanguageSelector(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### صفحه Profile
|
||||||
|
```dart
|
||||||
|
// موقعیت: در بخش General Settings
|
||||||
|
_buildInfoTile(
|
||||||
|
icon: SvgPicture.asset(Assets.icons.languageSquare.path),
|
||||||
|
title: 'Language',
|
||||||
|
trailing: LanguageSelector(),
|
||||||
|
),
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 عیبیابی
|
||||||
|
|
||||||
|
### مشکلات متداول
|
||||||
|
|
||||||
|
#### 1. Overlay خارج از صفحه
|
||||||
|
**علت**: positioning غلط
|
||||||
|
**راهحل**: بررسی محاسبات `leftPosition` و `rightPosition`
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// اطمینان از عدم overflow
|
||||||
|
if (leftPosition + overlayWidth > screenWidth - 16) {
|
||||||
|
leftPosition = screenWidth - overlayWidth - 16;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Asset پرچم نمایش داده نمیشود
|
||||||
|
**علت**: مسیر فایل اشتباه
|
||||||
|
**راهحل**: بررسی `assets.gen.dart` و مسیرهای فایل
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// بررسی وجود فایل
|
||||||
|
child: _currentFlag.endsWith('.svg')
|
||||||
|
? SvgPicture.asset(_currentFlag)
|
||||||
|
: Image.asset(_currentFlag),
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Animation کار نمیکند
|
||||||
|
**علت**: `AnimationController` dispose نشده
|
||||||
|
**راهحل**:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debug Commands
|
||||||
|
```dart
|
||||||
|
// برای debug positioning
|
||||||
|
print('Target position: ${targetPosition.dx}, ${targetPosition.dy}');
|
||||||
|
print('Screen width: $screenWidth');
|
||||||
|
print('Overlay width: $overlayWidth');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 بهبودهای آینده
|
||||||
|
|
||||||
|
### 1. Localization Integration
|
||||||
|
```dart
|
||||||
|
// اتصال به سیستم i18n Flutter
|
||||||
|
class LocalizationManager {
|
||||||
|
static void changeLocale(BuildContext context, String languageCode) {
|
||||||
|
// تغییر زبان کل اپ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Theme Integration
|
||||||
|
```dart
|
||||||
|
// تغییر زبان همراه با تغییر direction
|
||||||
|
void _changeLanguage(String code) {
|
||||||
|
final isRTL = ['ar', 'fa'].contains(code);
|
||||||
|
// اعمال تغییرات theme
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Network Integration
|
||||||
|
```dart
|
||||||
|
// دریافت متنها از سرور
|
||||||
|
class LanguageService {
|
||||||
|
Future<Map<String, String>> getTranslations(String languageCode) {
|
||||||
|
// API call
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 پشتیبانی
|
||||||
|
|
||||||
|
برای مشکلات فنی یا پیشنهادات بهبود، با تیم توسعه تماس بگیرید.
|
||||||
|
|
||||||
|
**نسخه مستندات:** 1.0
|
||||||
|
**آخرین بروزرسانی:** 22 سپتامبر 2025
|
||||||
|
**نگارنده:** GitHub Copilot
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*این مستندات برای توسعهدهندگان اپلیکیشن LBA تهیه شده است.*
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
# Real-Time System Theme Detection Implementation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
The LBA app now automatically detects and responds to system theme changes in real-time, meaning when a user changes their device's theme setting (from light to dark or vice versa), the app immediately reflects this change without requiring a restart.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### 1. System Theme Detection
|
||||||
|
- **Flutter's ThemeMode.system**: When users haven't manually overridden theme settings, the app uses Flutter's built-in `ThemeMode.system` which automatically follows the device's system theme.
|
||||||
|
- **Real-time Updates**: The app listens to system brightness changes using `WidgetsBindingObserver` and updates the UI immediately.
|
||||||
|
|
||||||
|
### 2. Implementation Details
|
||||||
|
|
||||||
|
#### Files Modified:
|
||||||
|
- `lib/main.dart`: Updated to use dynamic theme mode selection
|
||||||
|
- `lib/utils/theme_manager.dart`: Enhanced with real-time system theme detection
|
||||||
|
|
||||||
|
#### Key Features:
|
||||||
|
```dart
|
||||||
|
// In ThemeManager class
|
||||||
|
class ThemeManager extends ChangeNotifier with WidgetsBindingObserver {
|
||||||
|
|
||||||
|
// Returns appropriate theme mode
|
||||||
|
ThemeMode getThemeMode() {
|
||||||
|
if (_hasManualOverride) {
|
||||||
|
return _isDarkMode ? ThemeMode.dark : ThemeMode.light;
|
||||||
|
} else {
|
||||||
|
return ThemeMode.system; // Follows system theme automatically
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listens to system brightness changes
|
||||||
|
@override
|
||||||
|
void didChangePlatformBrightness() {
|
||||||
|
if (!_hasManualOverride) {
|
||||||
|
bool systemIsDark = _getSystemTheme();
|
||||||
|
if (_isDarkMode != systemIsDark) {
|
||||||
|
_isDarkMode = systemIsDark;
|
||||||
|
AppColors.setDarkMode(_isDarkMode);
|
||||||
|
_updateSystemUIOverlay();
|
||||||
|
notifyListeners(); // Updates UI immediately
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. User Experience
|
||||||
|
|
||||||
|
#### When User Has NOT Set Manual Theme:
|
||||||
|
- ✅ App automatically follows system theme
|
||||||
|
- ✅ Real-time updates when system theme changes
|
||||||
|
- ✅ No app restart required
|
||||||
|
- ✅ Immediate UI response
|
||||||
|
|
||||||
|
#### When User Has Set Manual Theme:
|
||||||
|
- ✅ App respects user's manual choice
|
||||||
|
- ✅ System theme changes are ignored
|
||||||
|
- ✅ User can reset to system theme anytime
|
||||||
|
|
||||||
|
## Usage Scenarios
|
||||||
|
|
||||||
|
### Scenario 1: Following System Theme (Default)
|
||||||
|
1. User opens app for first time
|
||||||
|
2. App automatically matches device theme (light/dark)
|
||||||
|
3. User changes device theme in system settings
|
||||||
|
4. **App immediately updates** to match new system theme
|
||||||
|
|
||||||
|
### Scenario 2: Manual Theme Override
|
||||||
|
1. User manually sets theme in Profile → General settings
|
||||||
|
2. App uses selected theme regardless of system changes
|
||||||
|
3. User can choose "Reset to System Theme" to return to automatic behavior
|
||||||
|
|
||||||
|
### Scenario 3: Real-time Detection
|
||||||
|
1. User has app open and active
|
||||||
|
2. User switches device theme (pull down control center → dark mode toggle)
|
||||||
|
3. **App theme changes instantly** without any delay
|
||||||
|
|
||||||
|
## Technical Implementation
|
||||||
|
|
||||||
|
### Key Components:
|
||||||
|
|
||||||
|
#### 1. WidgetsBindingObserver
|
||||||
|
```dart
|
||||||
|
class ThemeManager extends ChangeNotifier with WidgetsBindingObserver {
|
||||||
|
ThemeManager() {
|
||||||
|
WidgetsBinding.instance.addObserver(this); // Start listening
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this); // Clean up
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Platform Brightness Detection
|
||||||
|
```dart
|
||||||
|
bool _getSystemTheme() {
|
||||||
|
return WidgetsBinding.instance.platformDispatcher.platformBrightness == Brightness.dark;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Dynamic Theme Mode
|
||||||
|
```dart
|
||||||
|
// In MaterialApp widget
|
||||||
|
themeMode: themeManager.getThemeMode(), // Dynamic based on user preference
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. State Management
|
||||||
|
- Uses `Provider` pattern for theme state management
|
||||||
|
- Automatic persistence to SharedPreferences
|
||||||
|
- Immediate UI updates via `notifyListeners()`
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
### 1. **Seamless User Experience**
|
||||||
|
- No manual theme switching needed
|
||||||
|
- Automatic adaptation to user preferences
|
||||||
|
- Consistent with system-wide theme behavior
|
||||||
|
|
||||||
|
### 2. **Real-time Response**
|
||||||
|
- Instant theme changes
|
||||||
|
- No app restart required
|
||||||
|
- Smooth transitions with animations
|
||||||
|
|
||||||
|
### 3. **User Control**
|
||||||
|
- Option to override system theme
|
||||||
|
- Ability to reset to system behavior
|
||||||
|
- Persistent user preferences
|
||||||
|
|
||||||
|
### 4. **Performance Optimized**
|
||||||
|
- Efficient system listening
|
||||||
|
- Minimal resource usage
|
||||||
|
- Clean observer pattern implementation
|
||||||
|
|
||||||
|
## Testing Instructions
|
||||||
|
|
||||||
|
### Test Real-time Theme Switching:
|
||||||
|
|
||||||
|
1. **Open the LBA app**
|
||||||
|
2. **Ensure no manual theme override is set** (go to Profile → General → Theme should follow system)
|
||||||
|
3. **While app is open and visible:**
|
||||||
|
- On iOS: Swipe down → Control Center → Toggle Dark Mode
|
||||||
|
- On Android: Quick Settings → Toggle Dark Theme
|
||||||
|
4. **Observe:** App theme should change immediately
|
||||||
|
|
||||||
|
### Test Manual Override:
|
||||||
|
|
||||||
|
1. **Set manual theme** in Profile → General settings
|
||||||
|
2. **Change system theme** while app is open
|
||||||
|
3. **Observe:** App should NOT change (respects manual setting)
|
||||||
|
4. **Reset to system theme** in settings
|
||||||
|
5. **Change system theme** again
|
||||||
|
6. **Observe:** App should now follow system changes
|
||||||
|
|
||||||
|
## Code Examples
|
||||||
|
|
||||||
|
### How to Add Real-time Theme Detection to Any Screen:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class MyScreen extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<ThemeManager>(
|
||||||
|
builder: (context, themeManager, child) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppColors.scaffoldBackground, // Auto-updates
|
||||||
|
body: Text(
|
||||||
|
'This text color updates automatically!',
|
||||||
|
style: TextStyle(color: AppColors.textPrimary), // Auto-updates
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### How to Check Current Theme State:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Get current theme mode
|
||||||
|
ThemeMode currentMode = themeManager.getThemeMode();
|
||||||
|
|
||||||
|
// Check if following system
|
||||||
|
bool isFollowingSystem = currentMode == ThemeMode.system;
|
||||||
|
|
||||||
|
// Check if dark mode is active
|
||||||
|
bool isDark = themeManager.isDarkMode;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration Notes
|
||||||
|
|
||||||
|
### Breaking Changes: None
|
||||||
|
- All existing functionality preserved
|
||||||
|
- Backward compatibility maintained
|
||||||
|
- No API changes for existing screens
|
||||||
|
|
||||||
|
### Enhanced Features:
|
||||||
|
- Real-time system theme detection
|
||||||
|
- Improved user experience
|
||||||
|
- Better system integration
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
### Potential Improvements:
|
||||||
|
- **Theme Scheduling**: Set different themes for day/night
|
||||||
|
- **Location-based Themes**: Auto-dark mode based on sunset/sunrise
|
||||||
|
- **Custom Theme Colors**: User-defined color palettes
|
||||||
|
- **Theme Animations**: Enhanced transition effects
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### If Real-time Switching Doesn't Work:
|
||||||
|
1. Check if manual theme override is enabled
|
||||||
|
2. Ensure app has proper system permissions
|
||||||
|
3. Restart app to reinitialize observers
|
||||||
|
4. Verify device supports theme switching
|
||||||
|
|
||||||
|
### Common Issues:
|
||||||
|
- **Delay in Updates**: Normal on some Android devices (1-2 second delay)
|
||||||
|
- **Not Following System**: Check if manual override is set
|
||||||
|
- **Partial Updates**: Ensure all screens use AppColors class
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The LBA app now provides a **complete real-time theme switching experience** that automatically follows the user's system theme preferences while still allowing manual overrides when desired. This enhancement makes the app feel more integrated with the device's overall user experience and eliminates the need for users to manually sync their app themes with their system preferences.
|
||||||
|
|
||||||
|
**Key Achievement:** ✅ **Real-time system theme detection with zero user intervention required**
|
||||||
|
|
@ -33,8 +33,7 @@ class MyApp extends StatelessWidget {
|
||||||
title: 'LBA',
|
title: 'LBA',
|
||||||
theme: themeManager.lightTheme,
|
theme: themeManager.lightTheme,
|
||||||
darkTheme: themeManager.darkTheme,
|
darkTheme: themeManager.darkTheme,
|
||||||
themeMode:
|
themeMode: themeManager.getThemeMode(),
|
||||||
themeManager.isDarkMode ? ThemeMode.dark : ThemeMode.light,
|
|
||||||
themeAnimationDuration: const Duration(milliseconds: 300),
|
themeAnimationDuration: const Duration(milliseconds: 300),
|
||||||
themeAnimationCurve: Curves.easeInOutCubic,
|
themeAnimationCurve: Curves.easeInOutCubic,
|
||||||
home: SplashScreen(
|
home: SplashScreen(
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import 'package:lba/res/colors.dart';
|
||||||
import 'package:lba/utils/sharedPreferencesKey.dart';
|
import 'package:lba/utils/sharedPreferencesKey.dart';
|
||||||
import 'package:lba/utils/sharedPreferencesManger.dart';
|
import 'package:lba/utils/sharedPreferencesManger.dart';
|
||||||
|
|
||||||
class ThemeManager extends ChangeNotifier {
|
class ThemeManager extends ChangeNotifier with WidgetsBindingObserver {
|
||||||
late SharedPreferencesManager _prefs;
|
late SharedPreferencesManager _prefs;
|
||||||
bool _isDarkMode = false;
|
bool _isDarkMode = false;
|
||||||
bool _isThemeTransitioning = false;
|
bool _isThemeTransitioning = false;
|
||||||
|
|
@ -13,6 +13,13 @@ class ThemeManager extends ChangeNotifier {
|
||||||
ThemeManager() {
|
ThemeManager() {
|
||||||
_prefs = SharedPreferencesManager();
|
_prefs = SharedPreferencesManager();
|
||||||
_loadTheme();
|
_loadTheme();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isDarkMode => _isDarkMode;
|
bool get isDarkMode => _isDarkMode;
|
||||||
|
|
@ -160,6 +167,31 @@ class ThemeManager extends ChangeNotifier {
|
||||||
|
|
||||||
ThemeData get currentTheme => _isDarkMode ? darkTheme : lightTheme;
|
ThemeData get currentTheme => _isDarkMode ? darkTheme : lightTheme;
|
||||||
|
|
||||||
|
/// Returns the appropriate ThemeMode based on user preferences
|
||||||
|
ThemeMode getThemeMode() {
|
||||||
|
if (_hasManualOverride) {
|
||||||
|
return _isDarkMode ? ThemeMode.dark : ThemeMode.light;
|
||||||
|
} else {
|
||||||
|
return ThemeMode.system;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangePlatformBrightness() {
|
||||||
|
super.didChangePlatformBrightness();
|
||||||
|
|
||||||
|
// Only respond to system theme changes if user hasn't manually overridden
|
||||||
|
if (!_hasManualOverride) {
|
||||||
|
bool systemIsDark = _getSystemTheme();
|
||||||
|
if (_isDarkMode != systemIsDark) {
|
||||||
|
_isDarkMode = systemIsDark;
|
||||||
|
AppColors.setDarkMode(_isDarkMode);
|
||||||
|
_updateSystemUIOverlay();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _loadTheme() async {
|
Future<void> _loadTheme() async {
|
||||||
await _prefs.init();
|
await _prefs.init();
|
||||||
_hasManualOverride = _prefs.getBool(SharedPreferencesKey.hasManualThemeOverride) ?? false;
|
_hasManualOverride = _prefs.getBool(SharedPreferencesKey.hasManualThemeOverride) ?? false;
|
||||||
|
|
@ -176,8 +208,7 @@ class ThemeManager extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _getSystemTheme() {
|
bool _getSystemTheme() {
|
||||||
final window = WidgetsBinding.instance.window;
|
return WidgetsBinding.instance.platformDispatcher.platformBrightness == Brightness.dark;
|
||||||
return window.platformBrightness == Brightness.dark;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateSystemUIOverlay() {
|
void _updateSystemUIOverlay() {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import 'package:lba/main.dart';
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
// Build our app and trigger a frame.
|
// Build our app and trigger a frame.
|
||||||
await tester.pumpWidget(const MyApp());
|
await tester.pumpWidget(MyApp());
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
// Verify that our counter starts at 0.
|
||||||
expect(find.text('0'), findsOneWidget);
|
expect(find.text('0'), findsOneWidget);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue