Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

فواتير الكهرباء — تطبيق أندرويد

تطوير م. معين العباسي نظام فواتير الكهرباء لشركة العباسي لتوليد الطاقة الكهربائية. تطبيق أندرويد أصلي مبني بـ Flutter، يعمل بلا إنترنت، وكل البيانات محفوظة داخل جهاز المستخدم فقط.

الإصدار 1.1.0 (versionCode 2)
معرّف الحزمة com.abbasisoft.billing
المنصة Android (وواجهة ويب للمعاينة)
Flutter / Dart 3.35.4 / 3.9.2 (مثبّتة)
قاعدة البيانات SQLite محليّة عبر Drift
الخدمات السحابية لا يوجد — التطبيق محلي بالكامل

📚 الوثائق

الملف المحتوى
CHANGELOG.md سجل التغييرات وروابط تحميل كل إصدار
docs/STORAGE_AND_PERMISSIONS.md المجلد العام، MediaStore، منطق الصلاحيات
docs/INVOICE_FORENSICS.md تحليل الفاتورة وقواعد تعديلها الملزمة

📞 التواصل

القناة العنوان
واتساب +967 770 941 666
الموقع alabbasi.uk

🧾 الفاتورة — القاعدة الحاكمة

الفاتورة مطابقة 100% للنسخة الأصلية (bill.docx / bill.pdf). هذا ليس هدفاً جمالياً بل شرطاً وظيفياً: الفاتورة مستند رسمي يُقدَّم للمشتركين، فأي انزياح في موضع أو لون أو مقاس يجعلها مستنداً مختلفاً.

لذا يخضع ملف lib/features/invoices/invoice_pdf.dart لقواعد صارمة:

  • كل قياس مشتَقّ ولا يُكتب رقماً سحرياً — ثوابت _X (المحاور الأفقية) و _Y (المحاور الرأسية) مرتبطة بسلسلة اشتقاق، وارتفاع الصفحة نفسه مشتَقّ من آخر عنصر فيها (_Y.bottomBar).
  • الألوان لا تُقارَب بالعين — استُخرجت من bill.docx نفسه (‎0000FF‎ ظهر 31 مرة ⇒ هو أزرق العنوان والمبلغ معاً).
  • كل تعديل يُتحقَّق منه بكسليّاً من داخل ملف الـ PDF لا من صورة.

📄 التفاصيل الكاملة والتحليل الجنائي في docs/INVOICE_FORENSICS.md.


📂 حفظ الملفات — مجلد عام باسم التطبيق (أسلوب واتساب)

عند حفظ فاتورة أو نسخة احتياطية تُكتب الملفات في مجلد عام يراه المستخدم، ويبقى في الجهاز حتى لو أُزيل التطبيق:

Documents/
└── فواتير الكهرباء/
    ├── الفواتير/
    │   └── عبدالله محمد الشامي - 2026-01-05 - 0731.pdf
    └── النسخ الاحتياطي/
        └── نسخة احتياطية - 2026-01-05 - 2130.json

اسم ملف الفاتورة = اسم المشترك + التاريخ (+ رقم الفاتورة كي لا تتزاحم فاتورتان لنفس المشترك في اليوم نفسه). التاريخ المستخدم هو تاريخ إصدار الفاتورة لا تاريخ اليوم، فيبقى الاسم ثابتاً لو أُعيد الحفظ.

كيف نُفِّذ ذلك تقنياً؟

منذ Android 10 (API 29) فُرض التخزين المحدود (Scoped Storage)، فلم يبقَ للتطبيق حقّ الكتابة عبر مسارات ملفات عادية في الذاكرة المشتركة، ويرمي النظام SecurityException. الطريق المعتمد رسمياً هو MediaStore عبر ContentResolver، ولا تُغلّفه أي حزمة Dart بشكل يسمح باختيار مجلد فرعي داخل Documents — لذلك كُتبت طبقة أصليّة بـ Kotlin:

الملف الدور
android/.../PublicStoragePlugin.kt الكتابة عبر MediaStore (API 29+) أو مسار ملف تقليدي (API ≤ 28)
android/.../MainActivity.kt تسجيل MethodChannel
android/.../res/values/strings.xml اسم التطبيق الظاهر على الجهاز — انظر التنبيه أدناه
lib/core/services/public_storage.dart واجهة Dart + إدارة الصلاحية

⚠️ تنبيه: اسم التطبيق يجب أن يبقى في strings.xml. كان التطبيق يظهر على الجهاز باسم "My App" لأن android:label في المانيفست كان نصاً حرفياً، وأدوات البناء الآلية تعيد كتابته. الحل هو نقل الاسم إلى مورد نصّي:

<!-- android/app/src/main/res/values/strings.xml -->
<string name="app_name">فواتير الكهرباء</string>
<!-- AndroidManifest.xml -->
<application android:label="@string/app_name" …>

لا تُرجع النص الحرفي إلى android:label أبداً — سيعود الخطأ.

نقطة مهمة في الصلاحيات: على API 29+ لا يُطلب من المستخدم أي صلاحية لأن التطبيق هو مُنشئ الملف، ولذلك حُدّد في المانيفست:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                 android:maxSdkVersion="28" />

فلا يُزعج مستخدمو الأجهزة الحديثة بنافذة صلاحية لا معنى لها، وتُطلب الصلاحية فعلاً فقط على Android 9 وما قبله حيث تكون لازمة. وإن رفض المستخدم تُعرض نافذة تشرح لماذا مع زرّ يفتح إعدادات التطبيق.

المرجع: developer.android.com — Access media files from shared storage


🧭 التنقّل

  • درج جانبي يُفتح من اليمين (اتجاه التطبيق RTL، فيكون drawer هو درج البداية تلقائياً بلا حاجة إلى endDrawer): الرئيسية، المشتركون، فاتورة جديدة، أرشيف الفواتير، الإعدادات والنسخ الاحتياطي، المطوّر والتواصل — ويُذيَّل باسم المطوّر ورقم الإصدار.
  • شريط سفلي عائم بأربعة عناصر وزرّ ذهبي مركزي لإنشاء فاتورة.
  • صفحة المطوّر والتواصل (/about): واتساب، الموقع، اتصال هاتفي (ضغطة طويلة تنسخ)، ومعلومات التطبيق والخصوصية.

url_launcher يفشل بصمت على Android 11+ ما لم يُعلَن عن النوايا في <queries>، فأُضيفت نوايا https و tel و SEND إلى المانيفست.


🗂️ بنية المشروع

lib/
├── app/                    theme.dart · router.dart
├── core/
│   ├── app_info.dart       ثوابت الهوية والمطوّر — مصدر واحد للحقيقة
│   ├── database/           tables.dart · billing_repository.dart (Drift)
│   ├── providers.dart      مزوّدات Riverpod
│   ├── services/
│   │   └── public_storage.dart   الحفظ في المجلد العام
│   ├── utils/              formatters · invoice_calculator · arabic_number_words
│   └── widgets/            app_shell (+ الدرج) · luxe_button · luxe_card · …
└── features/
    ├── about/              صفحة المطوّر والتواصل
    ├── dashboard/          لوحة التحكم والإحصائيات
    ├── invoices/           النموذج · الأرشيف · المعاينة · invoice_pdf.dart
    ├── onboarding/         شاشة البداية · الترخيص
    ├── settings/           الإعدادات · النسخ الاحتياطي
    └── subscribers/        المشتركون

مصدر واحد للحقيقة

كل نص عن المطوّر أو التواصل أو الإصدار يُقرأ من lib/core/app_info.dart حصراً. سبب ذلك عملي: نص المطوّر كان مكرَّراً في ملفين، فكان تغييره يستلزم تعديل كل نسخة على حِدة، ونسيان إحداها يُنتج تطبيقاً يعرض اسمين مختلفين للمطوّر في شاشتين.


🔐 الخصوصية

  • لا يجمع التطبيق أي بيانات ولا يُرسل شيئاً إلى أي جهة.
  • لا يحتاج إنترنت للعمل.
  • كل المشتركين والفواتير والإعدادات في قاعدة بيانات محليّة.
  • الملفات المُصدَّرة تُكتب في مجلد عام يملكه المستخدم.

🛠️ البناء والتشغيل

flutter pub get
flutter analyze          # يجب أن يكون: No issues found!
flutter test             # 14 اختباراً

# معاينة ويب
flutter build web --release

# إصدار أندرويد (ثلاث معماريات منفصلة لتقليل الحجم)
flutter build apk --release --split-per-abi \
  --obfuscate --split-debug-info=build/symbols

تقليص حجم الحزمة

الحزمة الأولى كانت 65 MB، ونزلت إلى ~10 MB لكل معمارية:

الإجراء الأثر
--split-per-abi حزمة لكل معمارية بدل حزمة تضمّ الثلاث
--obfuscate --split-debug-info إخراج رموز التنقيح من الحزمة
useLegacyPackaging = true ضغط المكتبات الأصلية
تقليص الخطوط بـ fontTools Amiri: 431KB→291KB، Tinos: 550KB→20KB
resourceConfigurations = ["ar","en"] إسقاط موارد اللغات غير المستخدمة

✅ الاختبارات

flutter test — 14 اختباراً:

  • تنسيق الأرقام مطابقاً لـ toLocaleString('en-US') في نسخة الويب.
  • معادلات الفاتورة (الاستهلاك، القيمة الأساسية، الإجمالي، الصافي).
  • تحويل الأرقام إلى كلمات عربية.
  • تنقية أسماء الملفات — تحمي قابلية الحفظ: أي محرف ممنوع في نظام الملفات يجعل الحفظ يفشل بصمت، ولن يلاحظ المستخدم إلا بعد أن يفقد فاتورة.
  • أسماء ملفات الفواتير والنسخ الاحتياطية.

كما يوجد مِسبار تحقّق بصري: test/pdf_render_probe.dart يُخرج فاتورة حقيقية إلى /tmp/probe/out.pdf لتفحّصها بكسليّاً.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages