هو تطبيق أندرويد و iOS لإدارة السجلات المالية الشخصية (Ledger) والمعاملات اليومية. يركز التطبيق على الخصوصية (Private) والعمل بدون إنترنت (Offline-first) مع توفير أدوات احترافية مثل تصدير التقارير بصيغة PDF والنسخ الاحتياطي المشفر.
It's an Android & iOS application for managing personal financial ledgers and daily transactions. The app focuses on privacy and an offline-first experience, providing professional tools like PDF report export and encrypted backups.
تم تصميم التطبيق ليكون بديلاً رقمياً متطوراً لدفاتر الحسابات الورقية التقليدية، مع التركيز على السرعة والدقة في إدخال البيانات.
The app is designed to be a sophisticated digital alternative to traditional paper account books, focusing on speed and accuracy in data entry.
قبل ما نفتح الكود، لازم نتأكد إن كل المكتبات جاهزة. بنستخدم أمر `flutter pub get` الأساسي.
Before we open the code, we must ensure all libraries are ready. We use the fundamental `flutter pub get` command.
flutter pub get
بيحمل كل الـ dependencies المذكورة في ملف `pubspec.yaml`. بيقارن النسخ المطلوبة وبينزلها من موقع pub.dev للفولدر المحلي على جهازك.
Downloads all dependencies listed in `pubspec.yaml`. It compares requested versions and downloads them from pub.dev to your local cache.
من غيره الكود مش هيقدر يشوف المكاتب الخارجية زي Drift أو BLoC، وهتلاقي errors مالية الشاشة في الـ IDE.
Without it, the code won't recognize external libraries like Drift or BLoC, leading to errors everywhere in your IDE.
الأمر ده بيولد ملف `pubspec.lock`. ده ملف "البصمة" اللي بيضمن إن كل المطورين شغالين بظبط على نفس النسخ عشان ميبقاش في bug عند واحد وشغال عند التاني.
This command generates `pubspec.lock`. This "fingerprint" file ensures all developers work on the exact same versions to prevent "works on my machine" bugs.
التطبيق بيعتمد على مجموعة من المكتبات القوية. في ملف `pubspec.yaml` بنلاحظ استخدام علامة الـ `^` (Caret) قبل أرقام النسخ.
The app relies on a set of powerful libraries. In `pubspec.yaml`, we notice the use of the `^` (Caret) symbol before version numbers.
ماذا تعني علامة الـ ^؟ هي بتسمح بتحديث المكتبة لأي نسخة "متوافقة" (Semantic Versioning) بشرط متكونش Breaking Change.
What does the ^ mean? It allows upgrading to any "compatible" version (Semantic Versioning) as long as it's not a breaking change.
| Package | Purpose | Why? |
|---|---|---|
| drift | Local Database | Type-safe, reactive SQL. |
| flutter_bloc | State Mgmt | Standard, predictable flow. |
| get_it | DI | Decouple services from UI. |
| firebase_core | Firebase | The base for cloud services. |
| share_plus | Sharing | Export PDFs and share data. |
| path_provider | Storage | Find local directories on device. |
بنستخدم Clean Architecture مقسمة لثلاث طبقات أساسية. ده بيضمن إننا لو حبينا نغير قاعدة البيانات مستقبلاً، مش هنحتاج نغير حاجة في الـ UI.
We use Clean Architecture divided into three main layers. This ensures that if we want to change the database in the future, we won't need to change anything in the UI.
دي أوطى طبقة، مسؤولة عن "إزاي بنجيب البيانات". فيها الـ Models (DTOs)، الـ DataSources، والـ Repositories الحقيقية.
The lowest layer, responsible for "how to fetch data". It contains Models (DTOs), DataSources, and concrete Repositories.
دي "قلب المشروع". مفيهاش أي علاقة بـ Flutter أو أي مكتبة خارجية. فيها الـ Entities والـ UseCases والـ Repository Interfaces.
The "heart of the project". It has zero dependencies on Flutter or external libs. Contains Entities, UseCases, and Repository Interfaces.
ملاحظة: طبقة الدومين هي الأكثر استقراراً. لو الـ UI اتغير أو الـ DB اتغير، الدومين بيفضل زي ما هو.
Note: Domain layer is the most stable. If UI or DB changes, Domain remains untouched.
دي الطبقة اللي بتشوفها. فيها الـ UI (Widgets) والـ State Management (BLoC/Cubit).
The layer you see. Contains UI (Widgets) and State Management (BLoC/Cubit).
يبدأ التطبيق في `main.dart` حيث يتم تجهيز كل شيء قبل ظهور أول شاشة للمستخدم. يتم تهيئة الـ Dependency Injection، الـ Database، وخدمات Firebase.
The app starts in `main.dart` where everything is prepared before the first screen appears. DI, Database, and Firebase services are initialized here.
void main() async {
try {
// Ensuring Flutter is ready for async calls
WidgetsFlutterBinding.ensureInitialized();
// 1. Initialize Dependency Injection (GetIt)
await initDI();
try {
// 2. Initialize Firebase with a 3-second timeout
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
).timeout(
const Duration(seconds: 3),
onTimeout: () => throw Exception('Firebase init timeout'),
);
// Setup Crashlytics
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
} catch (e) {
debugPrint('Firebase failed: $e');
}
// 3. Global Bloc Observer for debugging
Bloc.observer = AppBlocObserver();
// 4. Run the Application
runApp(const LedgerApp());
} catch (e) {
debugPrint('Critical Failure: $e');
}
}
بينادي على حاوية حقن التبعيات (GetIt) عشان يسجل كل الـ Repositories والـ Blocs.
Calls the DI container to register all Repositories and Blocs.
لازم يتم قبل الـ `runApp` لأن كل الـ Widgets هتطلب الـ dependencies دي فوراً.
Must happen before `runApp` because widgets will request these dependencies immediately.
بيحدد وقت أقصى لمحاولة الاتصال بـ Firebase.
Sets a maximum timeout for Firebase connection attempt.
لو الموبايل معلهوش خدمات جوجل (زي هواوي)، التطبيق ممكن يعلق (Hang) للأبد في الـ Splash screen.
On devices without GMS (like Huawei), the app might hang forever on the splash screen.
Main() ──► initDI() ──► Firebase Init ──► Error Handlers ──► runApp(LedgerApp)
│
└─► MultiBlocProvider
├─ AppSettingsCubit
├─ AppPreferencesCubit
└─ BooksBloc
التطبيق محتاج يوصل للصور والخطوط. ده بيتم تعريفه في قسم `flutter:` داخل `pubspec.yaml`.
The app needs access to images and fonts. This is defined in the `flutter:` section of `pubspec.yaml`.
flutter:
uses-material-design: true
assets:
- assets/images/
- assets/icons/
- assets/splash/
- assets/lottie/
التسلسل الهرمي: بنحاول دايماً نخلي الـ Icons في فولدر منفصل والـ Images في فولدر منفصل عشان التنظيم.
Hierarchy: We always keep Icons and Images in separate folders for better organization.
في `main.dart` بنستخدم `protected` blocks عشان نضمن إن أي مشكلة في البداية متبوظش تجربة المستخدم.
In `main.dart`, we use protected blocks to ensure startup issues don't ruin the user experience.
// Capture Async errors outside Flutter Context
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
// Custom Error Widget for UI crashes
ErrorWidget.builder = (FlutterErrorDetails details) {
return Scaffold(
body: Center(
child: Text('هناك خطأ ما، تم إبلاغ المطورين.'),
),
);
};
أول حاجة لازم تحمل المكاتب المطلوبة.
flutter pub get
بما إننا بنستخدم Drift، لازم نولّد الكود الخاص بالجداول.
dart run build_runner build --delete-conflicting-outputs
flutter run
بنشوف استخدام كبير للـ `BuildContext` extension عشان نسهل الوصول للألوان والخطوط.
We see heavy use of `BuildContext` extensions to simplify access to colors and text themes.
extension BuildContextHelper on BuildContext {
AppColors get colors => Theme.of(this).extension()!;
TextTheme get textTheme => Theme.of(this).textTheme;
}
بنستخدم FVM (Flutter Version Management) للتأكد إن كل المطورين شغالين بظبط على نفس نسخة Flutter (وهي 3.24.5 في مشروعنا).
We use FVM (Flutter Version Management) to ensure all developers use the exact same Flutter version (3.24.5 in our project).
fvm flutter run
ليه FVM؟ فلاتر بتتطور بسرعة جداً. لو واحد شغال بـ 3.27 والتاني بـ 3.24، ممكن الكود يشتغل عند واحد ويبوظ عند التاني. FVM بيثبت النسخة للمشروع ده تحديداً.
Why FVM? Flutter evolves fast. If one dev uses 3.27 and another 3.24, code might break. FVM locks the version for this specific project.
المشروع ده بيتبع مبادئ الـ "MRE" والـ "Clean Code". الهدف هو إن الكود يكون بيشرح نفسه (Self-documenting).
The project follows "MRE" and "Clean Code" principles. The goal is self-documenting code.
قواعد ذهبية ممنوع كسرها:
| Term | Definition |
|---|---|
| DTO | Data Transfer Object - كائن لنقل البيانات من الـ Database. |
| Entity | أبسط شكل للبيانات في طبقة الدومين (بدون إضافات). |
| UseCase | كلاس بيوصف "فعل" واحد بيقوم بيه المستخدم (زي "إضافة دفتر"). |
| Singleton | نمط بيضمن إن كلاس معين منه نسخة واحدة بس في الذاكرة. |
| Lazy Singleton | نسخة واحدة بس، بس مش بتتعمل غير لما نحتاجها فعلاً (توفير رام). |
أنهينا نظرة عامة شاملة على المشروع، من أول "ما هو التطبيق" لحد "إزاي نشغله" و "إيه الأدوات اللي بنستخدمها".
We finished a comprehensive project overview, from "What is the app" to "How to run it" and "What tools we use".
تذكر: القاعدة الأولى هي Clean Architecture، القواعد التانية هي تفاصيل.
Remember: Rule #1 is Clean Architecture; everything else is detail.