Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce7b0fcf14 | ||
|
|
e8163fe265 | ||
|
|
5805413f51 | ||
|
|
544c37eee8 | ||
|
|
72c6575765 | ||
|
|
81fa8c5dc8 | ||
|
|
f4d4f02853 | ||
|
|
1f31369893 | ||
|
|
cb4f2a3159 | ||
|
|
e61d56c8e1 | ||
|
|
d1bc39604b | ||
|
|
fc8f874bf9 | ||
|
|
a6acdce532 | ||
|
|
039c977d63 | ||
|
|
f599b5ffc7 | ||
|
|
67771242b8 | ||
|
|
90d4a871d3 | ||
|
|
35cb3d34d8 | ||
|
|
c280cfc004 | ||
|
|
12fb6d07e6 | ||
|
|
a742c5bfd6 | ||
|
|
9c80ca9918 | ||
|
|
960d4c8604 | ||
|
|
09dfd665b5 | ||
|
|
139c8667eb | ||
|
|
60be22f418 | ||
|
|
313e994025 | ||
|
|
f9851e3b4c | ||
|
|
5af684a892 | ||
|
|
0dc5df7b5e | ||
|
|
432ca50204 | ||
|
|
78ad245ee5 | ||
|
|
a4d6e34a40 | ||
|
|
a8e895d5da | ||
|
|
0a2ca71cb4 | ||
|
|
8183e53b86 | ||
|
|
0c239c431d | ||
|
|
956bb4a674 | ||
|
|
3d9fb16767 | ||
|
|
ef639e804d | ||
|
|
be60f34148 | ||
|
|
41493f5dd6 | ||
|
|
90487a42ad | ||
|
|
590326baa0 | ||
|
|
9198a2f974 | ||
|
|
dd8b95035f | ||
|
|
dbbd7786f2 | ||
|
|
4008d4a435 | ||
|
|
e5cbd89323 | ||
|
|
75451ebc3f | ||
|
|
902cf00152 | ||
|
|
5fd1f32d4c | ||
|
|
c3d526c011 | ||
|
|
5bccd13b85 | ||
|
|
9e3750692e | ||
|
|
10f885b2f1 | ||
|
|
bb37b31cab | ||
|
|
0f424062fe | ||
|
|
0e5be7adbb | ||
|
|
68265a5bc6 | ||
|
|
368f68cc97 | ||
|
|
b0c2bfc03b | ||
|
|
e7bc20bc98 | ||
|
|
ae00aa943e | ||
|
|
24f6b5a589 | ||
|
|
1235abc0cb | ||
|
|
882541a56a | ||
|
|
1fe97bf3eb | ||
|
|
58c3c48f69 | ||
|
|
445a1aa0e3 | ||
|
|
0a61e32033 | ||
|
|
9624cfb4c8 | ||
|
|
be4fe3dabf | ||
|
|
8d7ffcf2eb | ||
|
|
c9c03892d1 | ||
|
|
7e0aae023a | ||
|
|
b41db82537 | ||
|
|
db9004c9ed | ||
|
|
12049fd7da | ||
|
|
ab482d85e4 | ||
|
|
8c62d81f04 | ||
|
|
5ecfaafa48 | ||
|
|
a9b1819e9a | ||
|
|
ff9fdbb24e | ||
|
|
a4536395c5 | ||
|
|
36939a50c1 | ||
|
|
739f934a26 | ||
|
|
310d4b57e9 | ||
|
|
a1c7029838 | ||
|
|
f4ab43d49a | ||
|
|
1c0cd88a98 | ||
|
|
a9b9c90d20 | ||
|
|
7ab10209dc | ||
|
|
43d6b88106 | ||
|
|
d8a50bb105 | ||
|
|
78b703f625 | ||
|
|
ccf5f13a09 | ||
|
|
4e29915383 | ||
|
|
c4c59bc828 | ||
|
|
051b9322f6 | ||
|
|
d10e1c712c | ||
|
|
22568fe220 | ||
|
|
9fc60b2cf5 | ||
|
|
a8700c656f | ||
|
|
484ad9773d | ||
|
|
295e6e9b06 | ||
|
|
6b7ddf2ded | ||
|
|
771ec6acc5 | ||
|
|
4e047ee8f2 | ||
|
|
9e276d6de6 | ||
|
|
439572453f | ||
|
|
5b58ead215 | ||
|
|
8cbb87cb51 | ||
|
|
8230b42ac1 | ||
|
|
eb94f6ff77 | ||
|
|
39cf087488 | ||
|
|
6da006e154 | ||
|
|
a8d0efbbea | ||
|
|
9a1cb3adfd | ||
|
|
c338deea5a | ||
|
|
3d09b8235d | ||
|
|
598467b58f | ||
|
|
135a379b32 | ||
|
|
074c08fc6f | ||
|
|
d4379bf0d3 | ||
|
|
728f82181b | ||
|
|
294a60ecdb | ||
|
|
b3f70193a3 | ||
|
|
03039a9611 | ||
|
|
5901329ef2 | ||
|
|
731194cba0 | ||
|
|
8aab91c77f | ||
|
|
a18d19588d | ||
|
|
1401db11d0 | ||
|
|
abe862f04d | ||
|
|
2c529c579c | ||
|
|
6c8cb53af0 | ||
|
|
597ef1809d | ||
|
|
cdcfa71c96 | ||
|
|
f941c53035 | ||
|
|
a20866eec8 | ||
|
|
fe5a71bcad | ||
|
|
ca0e18990e | ||
|
|
39c1e14232 | ||
|
|
e6fd319ff4 | ||
|
|
86bdee399d | ||
|
|
974de0a641 | ||
|
|
2bcf2d2ba2 | ||
|
|
d97a98abdb | ||
|
|
070e80ea30 | ||
|
|
06461635bf | ||
|
|
1c39d4c9cb | ||
|
|
710695b50b | ||
|
|
82170b4190 | ||
|
|
4991d50508 | ||
|
|
8b09ab767b | ||
|
|
8d22e78821 | ||
|
|
cad7b7aa2b | ||
|
|
eaf90d8c93 |
@@ -46,6 +46,11 @@ apps/**/.dart_tool/
|
|||||||
apps/**/build/
|
apps/**/build/
|
||||||
apps/**/.flutter-plugins*
|
apps/**/.flutter-plugins*
|
||||||
|
|
||||||
|
# Local source books and large curriculum references.
|
||||||
|
# Register them through backend/scripts/import_grade10_book_sources.php; do not
|
||||||
|
# push raw PDFs through the application repository.
|
||||||
|
books/*.pdf
|
||||||
|
|
||||||
# IDE & Editor files
|
# IDE & Editor files
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<div dir="rtl" align="right">
|
||||||
|
|
||||||
|
# تعليمات العمل الدائمة — صَقِل
|
||||||
|
|
||||||
|
## المرجع والسياق
|
||||||
|
|
||||||
|
هذه تعليمات المشروع لأي نموذج ينفذ أعمالاً لاحقة. تعليمات المستخدم في المهمة الحالية مقدمة عليها. لا يبدأ تنفيذ ميزة لمجرد وجودها في خارطة الطريق؛ نفّذ نطاق المهمة المطلوبة فقط.
|
||||||
|
|
||||||
|
قبل تنفيذ أي مهمة من المرحلة القادمة اقرأ:
|
||||||
|
|
||||||
|
1. `docs/NEXT_PHASE_MASTER_SPEC.md` — قرارات المنتج، تجربة التعليم، ونموذج الدخل.
|
||||||
|
2. `docs/CURRENT_STATE_AUDIT_2026-09-09.md` — تدقيق الكود المحلي والفجوات المثبتة بتاريخ المراجعة.
|
||||||
|
3. `docs/NEXT_PHASE_EXECUTION_BACKLOG.md` — ترتيب التنفيذ ومعايير القبول؛ اختر المهمة ذات الصلة وتحقق من متطلباتها السابقة.
|
||||||
|
4. `docs/VIDEO_REVIEW_AND_SOCRATIC_PROMPTS.md` عند العمل على المحتوى أو الفيديو أو الفحص أو محادثة الدرس.
|
||||||
|
|
||||||
|
الوثائق القديمة مرجع تاريخي. لا تتبنى منها ادعاءات «100% مكتمل»، إيرادات، إعفاءات، شراكات، أو تميز على المنافسين من دون دليل حديث. عند اختلاف وصف التنفيذ والكود، سجّل الفرق ولا تفترض أن السيرفر مطابق للنسخة المحلية.
|
||||||
|
|
||||||
|
## قواعد ملزمة للمنتج
|
||||||
|
|
||||||
|
- لا بيانات ديمو في تجربة المستخدم، ولا نجاح مالي أو تعليمي مصطنع. البيانات التعليمية المزروعة مقبولة عندما تكون حقيقية، مرتبطة بمصدر ونسخة ومراجعة. Fixtures الاختبارات معزولة عن التشغيل ولا تدخل الأرباح والتقييمات.
|
||||||
|
- المحتوى المنشور يأتي من حزمة معتمدة على الخادم. يمكن تخزين نسخة محلية موثقة منها للعمل دون اتصال؛ يمنع استخدام درس ثابت بديل عند فقد الملف المطلوب.
|
||||||
|
- اعتمد هوية منهجية ثابتة تشمل الصف والمبحث والفصل والوحدة والدرس ونسخة المنهج. العنوان و`lesson_01` وحدهما ليسا هوية.
|
||||||
|
- الدرس المنهجي الواحد يقبل حصصاً من معلمين متعددين. لنفس المعلم حصة أساسية منشورة واحدة لكل درس؛ الإعادة تكون نسخة مرشحة للاستبدال تخضع للفحص الكامل. لا تلغ النسخة الحالية قبل نجاح البديلة.
|
||||||
|
- صفر حصص: حالة فارغة صادقة مع الموارد الموجودة. حصة واحدة متاحة: تشغيل مباشر. أكثر من حصة متاحة: قائمة المعلمين مرتبة بالتقييم الموثوق، مع عدد التقييمات والتصفح على دفعات.
|
||||||
|
- افصل صلاحيات المدرسة عن السوق المستقل، وميزانية دخل كل منهما. لا تفترض أن كل طالب مدرسي يولّد ربح مشاهدة أو أن راتب المعلم يلغي استحقاقه التعاقدي.
|
||||||
|
- دخل المعلم من الدقائق المستحقة وفق سياسة موثقة، وليس من بيع باقات المعلم. معاملات السعر ونسبة الصندوق تحتاج قراراً تجارياً مثبتاً، ولا تُخترع داخل الكود.
|
||||||
|
- المشاهدة والتقييم والأرباح والإتقان تُحتسب وتُراجع على الخادم. لا تعتمد عداد العميل أو نجاح سؤال واحد لإثبات دخل أو إتقان.
|
||||||
|
- البصمة/Face ID تأكيد للعملية على الجهاز. لا تخزن بيانات بيومترية ولا تعتمد `biometric_success=true`. اكتمال الدفع والسحب يحتاج دليلاً من مزود الدفع وتسوية قابلة للتتبع.
|
||||||
|
- أسئلة الفيديو ومحادثته مرتبطة بنسخة الفيديو والتفريغ الزمني وMarkdown المعتمد. لا توليد من العنوان فقط، ولا توقيتات تخمينية، ولا اعتماد تلقائي عند نقص الدليل.
|
||||||
|
- الإنجليزية تتبع الكتاب والدرس المحددين. يمنع اختراع ترجمة أو IPA، أو خلط قواعد الوحدات، أو تقديم نص متقدم بوصفه تبسيطاً لطالب مبتدئ.
|
||||||
|
- PDF الكتاب وأوراق العمل Markdown وصور الخرائط الذهنية أصول مستقلة ذات نسخ؛ لا تحل صورة أو ملخص مكان الأصل دون بيان واضح.
|
||||||
|
|
||||||
|
## طريقة التنفيذ والتسليم
|
||||||
|
|
||||||
|
- حافظ على PHP الأصلي وFlutter/Cubits الحاليين؛ لا تحول المشروع إلى Laravel أو تعيد بناء التطبيقات دون طلب.
|
||||||
|
- ابدأ بتتبع مسار الميزة من الواجهة إلى قاعدة البيانات/التخزين. أصلح السبب في طبقة الخادم عندما يتعلق القرار بالصلاحية أو المال أو النشر.
|
||||||
|
- اجعل تغييرات قاعدة البيانات migrations واضحة مع خطة ترحيل وعودة. لا تنشئ الجداول داخل طلبات القراءة.
|
||||||
|
- للمزايا المالية أو الصلاحيات أو الاستبدال: اختبارات فشل وتزامن وidempotency إلزامية. اختبارات شكل الشاشة وحدها لا تكفي.
|
||||||
|
- لا تشغّل سكربتات تحمل اسم `test` قبل قراءتها؛ بعضها يستدعي خدمات مدفوعة أو يكتب بيانات. لا تفترض وجود `php artisan test` في هذا المشروع.
|
||||||
|
- سلّم: ما تغير، الملفات، الاختبارات التي شغلت فعلاً ونتائجها، ما لم يُختبر، وخطوة المتابعة. لا تحوّل المخطط إلى علامة «منجز» دون دليل.
|
||||||
|
- حافظ على تغييرات المستخدم الموجودة. لا تحذف بيانات إنتاج، ولا تنشر، ولا تحوّل أموالاً ضمن مهمة تخطيط أو مراجعة.
|
||||||
|
- لا تضف التوسع الجغرافي أو المهني أو ميزات المرحلة المؤجلة إلى مهمة إثبات الصف العاشر دون طلب جديد.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<div dir="rtl" align="right">
|
||||||
|
|
||||||
|
# وثائق منصة صَقِل
|
||||||
|
|
||||||
|
## مراجع العمل الحالية — 2026-09-09
|
||||||
|
|
||||||
|
1. [مواصفات المنتج والتعليم والدخل والمرحلة القادمة](../docs/NEXT_PHASE_MASTER_SPEC.md)
|
||||||
|
2. [تدقيق الوضع الفعلي: المنفذ والنواقص وأدلتها](../docs/CURRENT_STATE_AUDIT_2026-09-09.md)
|
||||||
|
3. [عقود وبرومبتات تدقيق الفيديو وMarkdown والأسئلة والمحادثة](../docs/VIDEO_REVIEW_AND_SOCRATIC_PROMPTS.md)
|
||||||
|
4. [مهام التنفيذ واختبارات القبول وتكليف نموذج التنفيذ](../docs/NEXT_PHASE_EXECUTION_BACKLOG.md)
|
||||||
|
5. [تعليمات العمل الدائمة لأي نموذج](../AGENTS.md)
|
||||||
|
|
||||||
|
هذه المراجع أُعدت للتخطيط والتسليم إلى التنفيذ اللاحق. وجودها لا يعني تنفيذ الميزات أو اختبار اتصال الإنتاج.
|
||||||
|
|
||||||
|
## الأرشيف
|
||||||
|
|
||||||
|
[دليل التوسع والمنح السابق](STRATEGIC_EXPANSION_AND_GRANTS_MASTER_PLAYBOOK.md) يحفظ التصورات القديمة. لا يُعتمد مصدراً حالياً للجاهزية أو نسب الأرباح أو رسوم/إعفاءات الترخيص أو الشراكات والمنح. التوثيق الجديد يوضح التعارضات ويقدم الأولويات البديلة.
|
||||||
|
|
||||||
|
لا تنشئ نسخة ثانية من وثائق المرحلة الجديدة هنا؛ روابط هذا الفهرس ترجع إلى الملفات الأصلية داخل `docs/` لتجنب اختلاف النسخ.
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
<div dir="rtl" align="right">
|
||||||
|
|
||||||
|
# دليل التوسع الاستراتيجي وهندسة المنح المؤسسية لمنصة صَقِل
|
||||||
|
|
||||||
|
> نسخة تاريخية من مرجع التوسع؛ ليست شهادة جاهزية أو مصدراً محققاً للمنح والترخيص والشراكات وأرقام الأسواق. لا تُعتمد ادعاءات «100%» وTRL والإعفاء التلقائي أو التميز على المنافسين. ابدأ بـ[مواصفات 2026-09-09](../docs/NEXT_PHASE_MASTER_SPEC.md) و[تدقيق الكود](../docs/CURRENT_STATE_AUDIT_2026-09-09.md). إثبات الصف العاشر بمواد محدودة يسبق التوسع المهني والجغرافي في الدفعة الجديدة.
|
||||||
|
|
||||||
|
# SAQEL EDTECH 2.0: MASTER STRATEGIC EXPANSION & GRANTS PLAYBOOK
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## الفهرس العام للمرجع الاستراتيجي
|
||||||
|
1. **البيان التأسيسي والهوية المعمارية (Founding Manifesto & Tech Moat)**
|
||||||
|
2. **التدقيق الهيكلي لحالة المنصة الحالية (Current Platform Audit & TRL)**
|
||||||
|
3. **تشريح المشهد الرقمي الحكومي في الأردن (Jordan Government EdTech Ecosystem)**
|
||||||
|
- حقيقة وأعطال منصة "درسك" (Darsak)
|
||||||
|
- التشريح الكامل لمنصة "سراج" (Siraj Platform & OpenAI)
|
||||||
|
- تفكيك صفقة وشراكة Amira Learning الأمريكية
|
||||||
|
4. **المتاهة التنظيمية والمناورة التكتيكية (Regulatory Strategy & Bylaw No. 20/2024)**
|
||||||
|
- رسوم ترخيص الـ 25,000 دينار وحظر المعلمين الحكوميين
|
||||||
|
- المسارات البديلة الذكية لتجاوز رسوم الترخيص (B2B, JONEPS, LTI 1.3)
|
||||||
|
5. **مقارنة اقتصاديات الوحدة ونماذج الأعمال (Unit Economics: B2G vs B2B vs B2C)**
|
||||||
|
6. **دليل اقتناص المنح الوطنية والدولية (Non-Dilutive Grant Capital Playbook)**
|
||||||
|
- منحة Grow.jo / YTJ للبنك الدولي ووزارة الاقتصاد الرقمي ($100K)
|
||||||
|
- برنامج دعم الرواتب وخلق الوظائف (Grow Talent)
|
||||||
|
- منح ISSF, GIZ develoPPP, USAID, Al Ghurair
|
||||||
|
7. **خطة الشراكة الوطنية مع مؤسسة التدريب المهني (VTC 90-Day Execution Blueprint)**
|
||||||
|
- برنامج تشخيص مركبات الهايبرد والكهرباء (EV Diagnostic Lab)
|
||||||
|
- معاهد التميز: ماركا، إربد، الزرقاء
|
||||||
|
- صيغة مذكرة التفاهم (MoU) صفرية التكلفة على الحكومة
|
||||||
|
8. **دراسة الجدوى للتوسع الإقليمي (Regional Expansion Feasibility)**
|
||||||
|
- العراق (الفرصة الأسرع B2C)
|
||||||
|
- السعودية (الصفقة الكبرى B2G/B2B)
|
||||||
|
- مصر (الحجم الفلكي والهوامش المنخفضة)
|
||||||
|
- سوريا (المحاذير القانونية والعقوبات الدولية)
|
||||||
|
9. **حوكمة الكود البرمجي وبروتوكول فحص الصف العاشر (Architectural Governance & Testing Protocol)**
|
||||||
|
- تطبيق نمط MVC الموحد في فلاتر والباك إند
|
||||||
|
- بروتوكول الاختبار الشامل للصف العاشر
|
||||||
|
- مصنع تعبئة التوجيهي والمناهج المؤتمت (The Tawjihi Ingestion Factory)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. البيان التأسيسي والهوية المعمارية (Founding Manifesto & Tech Moat)
|
||||||
|
|
||||||
|
تأسست منصة **صَقِل (Saqel)** انطلاقاً من رؤية هندسية ريادية يقودها **المهندس حمزة عايد** (معمار تقني ومستثمر واستراتيجي تشغيلي لمنظومات التنقل والحلول الذكية في الشرق الأوسط)، بهدف إحداث نقلة هيكلية من الجيل الأول للتعليم الرقمي (EdTech 1.0) القائم على استهلاك الفيديو السلبي والحفظ والتلقين، إلى الجيل الثاني (EdTech 2.0) القائم على:
|
||||||
|
- **التعلم النشط والتحفيز السقراطي المستمر (Socratic Active Learning).**
|
||||||
|
- **الفصل المعماري الصارم بين المحرك البرمجي ومحتوى المناهج (Decoupling Engine from Content).**
|
||||||
|
- **حماية الهوامش التشغيلية وتصفير تكاليف الخروج السحابي (Zero-Egress via Cloudflare R2 & Native Code).**
|
||||||
|
- **المحاكاة الإجرائية اللحظية من جانب العميل (Client-side 120fps Canvas Engines).**
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```
|
||||||
|
+------------------------------------------------------------------------------------+
|
||||||
|
| THE EDTECH 1.0 vs EDTECH 2.0 DIVIDE |
|
||||||
|
+------------------------------------------------------------------------------------+
|
||||||
|
| Dimension | EdTech 1.0 (Darsak, JoAcademy) | Saqel (EdTech 2.0) |
|
||||||
|
+-----------------------+----------------------------------+-------------------------+
|
||||||
|
| Learning Methodology | Passive Video Streaming (Binge) | In-Video Socratic Stops |
|
||||||
|
| Mistake Handling | Student continues lost | Auto-Rewind 45s Loop |
|
||||||
|
| Remediation Flow | None (Re-watch 40min lecture) | Smart Error Notebook |
|
||||||
|
| Practical Science | Static textbook diagrams | Native 120fps Canvas |
|
||||||
|
| Regulatory Risk | Hostage to Star Public Teachers | Pure Algorithmic IP |
|
||||||
|
| Hosting Economics | Expensive AWS egress / bandwidth | Zero-Egress Cloudflare |
|
||||||
|
+-----------------------+----------------------------------+-------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
<div dir="rtl" align="right">
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. التدقيق الهيكلي لحالة المنصة الحالية (Current Platform Audit & TRL)
|
||||||
|
|
||||||
|
وفقاً لتدقيق الكود الميداني، وصلت منصة صَقِل إلى مستوى نضج تكنولوجي متقدم:
|
||||||
|
|
||||||
|
### ✅ الأصول البرمجية المكتملة فعلياً (100% Implemented):
|
||||||
|
1. **منظومة تطبيقات فلاتر (4 Apps Ecosystem):**
|
||||||
|
- تطبيق الطالب الموحد (`student_app`): واجهة مزدوجة (طالب / ولي أمر)، عارض الكتب والمذكرات، المشغل السقراطي، الامتحانات التكيفية، دفتر الأخطاء الذكي، و 6 مختبرات تفاعلية.
|
||||||
|
- تطبيق المعلم (`teacher_app`): استوديو رفع الدروس، تدقيق زمن التركيز (معيار MIT 15-25 دقيقة)، محفظة عوائد كليك، بطاقة السمعة، وإدارة الأسئلة.
|
||||||
|
- تطبيق الإدارة المدرسية والقيادة (`admin_app`): شاشات غرفة القيادة المركزية ومدير المدرسة لمتابعة النزاهة ودفع الامتحانات لمختبرات الحاسوب.
|
||||||
|
- تطبيق الإدارة العليا (`super_admin_app`): إدارة عنقود الذكاء الاصطناعي، رادار المنظومة، وحركات الخزينة وسحوبات كليك.
|
||||||
|
2. **الباك إند وواجهات البرمجة (PHP Native Backend):**
|
||||||
|
- أكثر من 70 مسار API يعمل بكفاءة عالية وبدون أطر عمل ثقيلة.
|
||||||
|
- محرك تحويل الفيديو غير المتزامن وتقطيع HLS عبر FFmpeg والرفع لـ Cloudflare R2.
|
||||||
|
- تشفير الأرقام الوطنية للمواطنين (AES-256-GCM مع فهارس عمياء Blind Index).
|
||||||
|
3. **المختبرات التفاعلية الستة المدمجة (Native 120fps CustomPainters):**
|
||||||
|
- معمل جيوجبرا للرياضيات (أنظمة المعادلات، الدوائر، المثلثات).
|
||||||
|
- معمل الفيزياء (المتجهات، المقذوفات، الحركة في خط مستقيم).
|
||||||
|
- معمل الإنجليزي (استوديو النطق الصوتي IPA، محاكي الأزمنة).
|
||||||
|
- معمل اللغة العربية (شجرة الإعراب التفاعلية، بحور العروض).
|
||||||
|
- معمل التربية الإسلامية (حاسبة المواريث الشرعية بالدينار الأردني).
|
||||||
|
- معمل التاريخ (الخط الزمني التفاعلي مع وسوم الأسئلة الوزارية).
|
||||||
|
4. **منظومة التدريب المهني (VTC):**
|
||||||
|
- شجرة الـ 140 مهنة وفق المعايير المهنية الوطنية المبنية على الكفايات (CBT).
|
||||||
|
- محاكي فحص وعزل الجهد العالي للمركبات الكهربائية (`ev_diagnostic_lab.html` - 44KB).
|
||||||
|
- بوابة العرض المؤسسي الرسمية لمدير عام VTC على الرابط `/vtc`.
|
||||||
|
|
||||||
|
### ⚠️ الواقع الفعلي لمحتوى المناهج:
|
||||||
|
- **الصف العاشر الأساسي:** حقيقي وأصيل 100%؛ تفريغ كامل لـ 4 وحدات رياضيات، وحدتي فيزياء، 5 وحدات إنجليزي، ودروس العربي والدين والتاريخ بملفات ماركداون وبنوك أسئلة.
|
||||||
|
- **الصف الثاني الثانوي (التوجيهي):** هيكل وفهرس بيانات وصفية (Outlines) في ملف `manifest.json`، ومحرك جاهز، ويحتاج إلى ضخ نصوص الدروس والفيديوهات.
|
||||||
|
- **مستوى الجاهزية التكنولوجية (TRL):** يقع المشروع في تصنيف **TRL 6-7** (نظام متكامل ومثبت برمجياً بانتظار تشغيل الدفعة الأولى من المستخدمين الحقيقيين).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. تشريح المشهد الرقمي الحكومي في الأردن (Jordan Government EdTech Ecosystem)
|
||||||
|
|
||||||
|
### 3.1 حقيقة منصة "درسك" (Darsak Platform)
|
||||||
|
- **تاريخ الإطلاق:** آذار 2020 كاستجابة طارئة لجائحة كورونا، وطُوّرت مجاناً بواسطة شركة "موضوع" بالتعاون مع AWS ووزارة الاقتصاد الرقمي ووزارة التربية.
|
||||||
|
- **البنية التقنية:** مكتبة فيديوهات غير متزامنة على سحابة أمازون، مع قنوات تلفزيونية فضائية عبر قمر نايل سات (درسك 1، 2، والتوجيهي).
|
||||||
|
- **الفجوات ونقاط الفشل:**
|
||||||
|
1. تلقين سلبي واستنساخ الحصة التقليدية أمام الكاميرا دون تفاعل أو تلعيب.
|
||||||
|
2. انهيار نزاهة الامتحانات المؤتمتة وتفشي الغش عبر مجموعات واتساب.
|
||||||
|
3. تعميق الفاقد التعليمي؛ حيث أثبتت الدراسات تراجع المهارات الأساسية للطلبة الذين اعتمدوا عليها.
|
||||||
|
4. الوضع الحالي: تحولت لمجرد مستودع رقمي مهجور بعد العودة للتعليم الوجاهي.
|
||||||
|
|
||||||
|
### 3.2 منصة "سراج" (Siraj Platform) التابعة لوزارة التربية والتعليم
|
||||||
|
- **المظلة:** منبثقة عن "المجلس الوطني لتكنولوجيا المستقبل" برئاسة سمو ولي العهد الأمير الحسين بن عبدالله الثاني (كانون الثاني 2025).
|
||||||
|
- **المحرك والشراكات:**
|
||||||
|
- شراكة مع **OpenAI** (مبادرة Education for Nations) عبر خوادم Microsoft Azure السحابية.
|
||||||
|
- نموذج استرجاع مقيد (Restricted RAG) يعتمد حصرياً على نصوص كتب المركز الوطني لتطوير المناهج لتقليل الهلوسة مع ذكر أرقام الصفحات.
|
||||||
|
- بناء النموذج الأولي في أقل من شهر على بيئة Replit السحابية بقيادة المهندس عامر أبو ليلى وبدعم أمجد مسعد.
|
||||||
|
- **الفجوات القاتلة في "سراج":**
|
||||||
|
1. **روبوت محادثة نصي/صوتي فقط (Chatbot):** لا يمتلك فيديو تفاعلي، ولا فحوصات سقراطية متزامنة، ولا مختبرات كانفاس.
|
||||||
|
2. **تحولها لأداة غش سريع:** اعتمد الطلبة عليها لنسخ حلول الواجبات المدرسية الجاهزة دون فهم.
|
||||||
|
3. **عجز المدارس الحكومية:** فرضت كعبء إداري على المعلمين لتوثيق الاستخدام، بينما تفتقر المدارس لشبكات الإنترنت والأجهزة الذكية، مع اكتظاظ يصل إلى 50 طالباً بالصف.
|
||||||
|
4. **ضعف معالجة الرياضيات:** خلط اتجاهات المعادلات (RTL/LTR) في مناهج كولينز، وغياب محرك تحقق رمزي.
|
||||||
|
|
||||||
|
### 3.3 تفكيك صفقة وشراكة Amira Learning الأمريكية
|
||||||
|
- **تاريخ الإعلان:** 29 كانون الثاني 2026 برعاية رسمية واختتام المرحلة الأولى في حزيران 2026.
|
||||||
|
- **التحالف الرباعي:**
|
||||||
|
1. **وزارة التربية والتعليم:** ممثلة بـ **مركز الملكة رانيا العبدالله لتكنولوجيا التعليم والمعلومات (QRCIT)** بقيادة المهندس منيب طشمان.
|
||||||
|
2. **شركة Amira Learning (أمريكا):** محرك التعرف الصوتي (ASR) المستند لأبحاث 30 عاماً في جامعة كارنيغي ميلون (CMU) لتقييم الطلاقة اللفظية بالذكاء الاصطناعي مع نماذج Anthropic Claude.
|
||||||
|
3. **مجموعة Carter Education Group:** الشريك البيداغوجي الأمريكي لتأهيل المعلمين.
|
||||||
|
4. **شركة NorthStar Education (عمّان):** المشغل والموزع الميداني المحلي في الأردن.
|
||||||
|
- **النطاق والتمويل:** 17 مدرسة حكومية في عمّان وإربد والعقبة، استهدفت أكثر من 2,000 طالب في **الصفين الرابع والسابع الأساسي** (مهارات قراءة الإنجليزية) عبر برنامج تجريبي مجاني مدته 12 أسبوعاً (Pilot PoC)، تمهيداً للمنافسة على تمويلات **البنك الدولي (برنامج مسار MASAR)** ومنح **USAID**.
|
||||||
|
- **الدرس لـ صَقِل:** الاختراق الحكومي لا يتم بالبيع المباشر، بل بتقديم **تحالف تجريبي مدته 12 أسبوعاً مع مركز الملكة رانيا (QRCIT)** لقياس أثر ميزة كويزات الفيديو التفاعلية بالأرقام.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. المتاهة التنظيمية والمناورة التكتيكية (Regulatory Strategy & Bylaw No. 20/2024)
|
||||||
|
|
||||||
|
### 4.1 القيود الصارمة لنظام ترخيص المنصات
|
||||||
|
1. **الرسوم السيادية:** استيفاء **25,000 دينار أردني** رسم ترخيص أولي للمنصة الخاصة لمدة 5 سنوات، و **5,000 دينار** رسم تجديد دوري بموجب نظام التعليم الإلكتروني رقم 19 ونظام المراكز الثقافية المعدل رقم 20 لسنة 2024.
|
||||||
|
2. **حظر المعلمين الحكوميين:** المنع البات لمعلمي القطاع الحكومي من التدريس على المنصات الخاصة بموجب المادتين 68 و69 من نظام الخدمة المدنية ونظام إدارة الموارد البشرية رقم 33 لسنة 2024 تحت طائلة العزل والملاحقة المسلكية، مما يسقط نموذج المنصات التقليدية (جو أكاديمي ووتد) القائم على احتكار نجوم المدارس الحكومية.
|
||||||
|
|
||||||
|
### 4.2 المسارات البديلة الذكية لتجاوز رسم الـ 25,000 دينار
|
||||||
|
بدلاً من استنزاف رأس المال التأسيسي، تتبع صَقِل ثلاثة مسارات نظامية:
|
||||||
|
- **المسار الأول (B2B SaaS Provider):** التسجيل كشركة برمجيات وتطوير بنى تحتية في وزارة الصناعة والاقتصاد الرقمي، وتزويد منصات ومراكز ثقافية مرخصة مسبقاً بمحرك صَقِل ومختبراتها بنظام تقاسم الإيرادات (30% إلى 40%).
|
||||||
|
- **المسار الثاني (Government EdTech Vendor via JONEPS):** التسجيل في نظام الشراء الإلكتروني الأردني كمورد حلول رقمية للتأهل لمناقصات التحول الرقمي الحكومية وعطاءات وزارة الاقتصاد الرقمي دون اشتراط رخصة مركز ثقافي.
|
||||||
|
- **المسار الثالث (LTI 1.3 & OpenEMIS Plugin):** التكامل مع منظومة التعلم الرسمية لوزارة التربية (JoLearn) عبر معايير بروتوكول التشغيل البيني لأدوات التعلم المعززة، وتمرير علامات ومؤشرات إتقان الطلبة لسجلات الوزارة آلياً.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. مقارنة اقتصاديات الوحدة ونماذج الأعمال (Unit Economics Analysis)
|
||||||
|
|
||||||
|
| مؤشر الأداء الاقتصادي | نموذج التعاقد الحكومي والمؤسسي (B2G) | نموذج تزويد المدارس الخاصة (B2B SaaS) | نموذج الاشتراكات المباشرة للتوجيهي (B2C) |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **متوسط العائد لكل مقعد سنوياً** | 8 – 15 دينار أردني | 18 – 35 دينار أردني | 60 – 120 دينار أردني (شامل المواد) |
|
||||||
|
| **تكلفة اكتساب العميل (CAC)** | منعدمة للمقعد الفردي | 1,500 – 3,000 دينار لكل مدرسة | 12 – 18 دينار لكل طالب عبر الإعلانات |
|
||||||
|
| **عمولة التوزيع وشبكة المكتبات** | 0% (ربط مدرسي مباشر) | 0% (إلزامية مدرسية) | 10% – 15% من قيمة البطاقات |
|
||||||
|
| **حصة المعلم من المبيعات** | 0% (معلمو المؤسسات برواتبهم) | 0% (أداة مدرسية داعمة) | 40% – 50% من صافي البطاقة |
|
||||||
|
| **تكلفة الاستضافة والبث لكل مقعد** | 0.30 – 0.50 دينار سنوياً | 0.40 – 0.65 دينار سنوياً | 1.20 – 2.50 دينار سنوياً |
|
||||||
|
| **هامش الربح الإجمالي (Gross Margin)** | **78% – 85%** | **70% – 80%** | **25% – 35% فقط** |
|
||||||
|
| **الموقف من رسوم ترخيص الوزارة** | **معفى بالكامل** | **معفى بالكامل** | **ملزم بدفع 25 ألف دينار فوراً** |
|
||||||
|
|
||||||
|
### الخلاصة المالية:
|
||||||
|
إن التعاقد مع 4 إلى 5 مدارس خاصة كبرى (600 طالب بالمدرسة) أو معهدين للتدريب المهني يحقق نقطة تعادل مالية وتدفقاً نقدياً ذاتياً مستقراً بهامش ربح يتجاوز 75%، دون إهدار أموال الشركة في عمولات المكتبات وحصص المعلمين النجوم وحملات إعلانات السوشيال ميديا المرهقة.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. دليل اقتناص المنح الوطنية والدولية (Non-Dilutive Grant Capital Playbook)
|
||||||
|
|
||||||
|
### 6.1 منحة Grow.jo / YTJ (وزارة الاقتصاد الرقمي والبنك الدولي)
|
||||||
|
- **سقف التمويل:** يصل إلى **100,000 دولار أمريكي** (نحو 71,000 دينار أردني).
|
||||||
|
- **نسبة التغطية (Matching Ratio):**
|
||||||
|
- **80% منحة / 20% مساهمة ذاتية:** للمشاريع الناشئة والحلول المبتكرة ذات البعد التنموي في المحافظات (إربد، الزرقاء، الجنوب).
|
||||||
|
- **50% منحة / 50% مساهمة ذاتية:** للشركات القائمة في العاصمة عمّان.
|
||||||
|
- **البنود القابلة للتمويل:**
|
||||||
|
1. تطوير البرمجيات وتطبيقات فلاتر ومحركات الكانفاس التفاعلية.
|
||||||
|
2. تكاليف الاستضافة السحابية وواجهات الذكاء الاصطناعي (Cloudflare R2, LLM APIs).
|
||||||
|
3. رقمنة المناهج وبناء بنوك الأسئلة وبطاقات الكفايات المهنية (CBT).
|
||||||
|
4. دراسات الامتثال لشهادات المعايير المهنية والتوسع الإقليمي.
|
||||||
|
- **برنامج دعم الرواتب (Grow Talent):**
|
||||||
|
- تغطية **50% من رواتب الموظفين الجدد لمدة 6 أشهر**.
|
||||||
|
- اشتراطات: جنسية أردنية، تسجيل بالضمان الاجتماعي، 50% كوتا إناث، و 30% كوتا خارج عمان.
|
||||||
|
|
||||||
|
### 6.2 خارطة المنح التكميلية
|
||||||
|
- **الصندوق الأردني للريادة (ISSF):** منح تطوير الأعمال (BDS) بسقف 20,000 دولار عبر الحاضنات (iPARK و HTU CORE)، مع مسارات استثمار مشترك.
|
||||||
|
- **الوكالة الألمانية (GIZ develoPPP):** تمويل مطابق يصل إلى **2 مليون يورو** لشركات التكنولوجيا التي تنشر حلولاً تدريبية ورقمنة للمهن في الدول النامية بالشراكة مع VTC.
|
||||||
|
- **الوكالة الأمريكية للتنمية (USAID):** منح تطوير القوى العاملة (30 ألف إلى 150 ألف دولار) لدعم التشغيل في محافظتي إربد والزرقاء.
|
||||||
|
- **صندوق عبد العزيز الغرير لتعليم اللاجئين:** منح مخصصة للتعليم المدمج والمهني للمجتمعات الأقل حظاً في الأردن.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. خطة الشراكة الوطنية مع مؤسسة التدريب المهني (VTC 90-Day Execution Blueprint)
|
||||||
|
|
||||||
|
تمتلك مؤسسة التدريب المهني 33 معهداً و9 معاهد تميز وتدرب سنوياً 10,000 إلى 12,000 متدرب. تشكل صَقِل شريك التحول الرقمي الأمثل للمؤسسة:
|
||||||
|
|
||||||
|
### 7.1 البرنامج التجريبي المعتمد (EV Diagnostic Pilot)
|
||||||
|
- **التخصص:** تشخيص وصيانة المركبات الكهربائية والهجينة (مستوى مهني رابع - 720 ساعة).
|
||||||
|
- **المختبر الرقمي:** محاكي فحص الجهد العالي وعزل مفتاح الخدمة (MSD Lockout/Tagout)، تفريغ المكثفات، قراءة أعطال العزل P0AA6، وفحص شبكة CAN-Bus.
|
||||||
|
- **المعاهد الثلاثة المستهدفة:**
|
||||||
|
1. **معهد التميز للمركبات (ماركا - عمان):** المرجع الفني لكفايات صيانة السيارات في المملكة.
|
||||||
|
2. **معهد تدريب مهني إربد (حكما):** تغطية إقليم الشمال واستقطاب متدربي المحافظات.
|
||||||
|
3. **معهد تدريب مهني الزرقاء:** المعقل الصناعي وقاعدة تجارة وصيانة السيارات الكهربائية في المنطقة الحرة.
|
||||||
|
|
||||||
|
### 7.2 خارطة الـ 90 يوماً للتنفيذ الميداني
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```
|
||||||
|
[الشهر الأول: 1 - 30 يوماً]
|
||||||
|
تقديم الاستدعاء الرسمي وتوقيع مذكرة التفاهم (MoU صفرية التكلفة)
|
||||||
|
تدريب 12 مدرباً معتمداً في المعاهد الثلاثة (Train-the-Trainer)
|
||||||
|
|
|
||||||
|
v
|
||||||
|
[الشهر الثاني: 31 - 60 يوماً]
|
||||||
|
إدراج 60 متدرباً (20 متدرباً في كل معهد)
|
||||||
|
تطبيق "حارس السلامة": حظر لمس السيارة الحقيقية قبل إتقان المحاكي الافتراضي
|
||||||
|
|
|
||||||
|
v
|
||||||
|
[الشهر الثالث: 61 - 90 يوماً]
|
||||||
|
قياس مؤشرات تسارع اكتساب الكفاءة (توفير 40% من زمن التدريب)
|
||||||
|
إصدار شهادات الكفاءة المشتركة بجواز المهارات الرقمي (Digital Passport with QR)
|
||||||
|
تقديم التقرير المشترك لرفع مقترح الشراء وتأمين منح GIZ و Grow.jo
|
||||||
|
```
|
||||||
|
|
||||||
|
<div dir="rtl" align="right">
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. دراسة الجدوى للتوسع الإقليمي (Regional Expansion Feasibility)
|
||||||
|
|
||||||
|
### 🟢🟢 1. جمهورية العراق — أعلى عائد نقدي سريع (Priority 1: B2C Fast Cash Flow)
|
||||||
|
- **حجم السوق:** أكثر من **1.1 مليون طالب في السادس الإعدادي** ينفقون ما بين 1,000 إلى 3,000 دولار سنوياً على الدروس الخصوصية والمعاهد والملازم.
|
||||||
|
- **معدل العائد (ARPU):** 18 إلى 38 دولاراً لكل مادة، مدفوعة نقداً عبر شبكة بطاقات الشحن في كبرى المكتبات ودور الطباعة (شارع المتنبي، دار الأعرجي، مكتبة المستنصرية).
|
||||||
|
- **المتطلب التقني لصَقِل:** بناء معمارية العمل دون اتصال بالإنترنت (Offline-First Sync) لتجاوز أزمة انقطاع الكهرباء وقطع الإنترنت الحكومي أثناء الامتحانات الوزارية.
|
||||||
|
|
||||||
|
### 🟢 2. المملكة العربية السعودية — الصفقة المؤسسية الكبرى (Priority 2: B2G & B2B)
|
||||||
|
- **حجم السوق:** 1.4 مليون طالب بالمرحلة الثانوية، وسوق تقنيات تعليم يتجاوز 1.5 مليار دولار ضمن رؤية 2030 وبرنامج تنمية القدرات البشرية.
|
||||||
|
- **معدل العائد (ARPU):** الأعلى إقليمياً (100 إلى 500 دولار سنوياً)، ودفع رقمي فوري عبر مدى، أبل باي، وتمارا.
|
||||||
|
- **المتطلب التنظيمي:** تأسيس كيان تجاري عبر وزارة الاستثمار (MISA)، ترخيص المركز الوطني للتعليم الإلكتروني (NeLC)، وتوطين البيانات داخل خوادم سحابية بالمملكة للامتثال لضوابط الهيئة الوطنية للأمن السيبراني (NCA).
|
||||||
|
|
||||||
|
### 🟡 3. جمهورية مصر العربية — حجم فلكي وهوامش ضعيفة (Priority 3: High Volume Micro-Sub)
|
||||||
|
- **حجم السوق:** 28.9 مليون طالب، ونحو 800,000 طالب في الثانوية العامة مزودين بأجهزة تابلت حكومية جاهزة.
|
||||||
|
- **التحديات:** تآكل سعر صرف الجنيه المصري، انخفاض العائد للـ 1-3 دولار للمادة شهرياً، وقيود تحويل العملة الصعبة للخارج.
|
||||||
|
- **نموذج الدخول:** اشتراكات منخفضة التكلفة (Micro-subscriptions) عبر فوري وإنستاباي، أو تزويد بنك المعرفة المصري (EKB) بالمحرك السقراطي.
|
||||||
|
|
||||||
|
### ⛔ 4. الجمهورية العربية السورية — مستحيلة تجارياً (Commercially Unfeasible)
|
||||||
|
- **العائق القاتل:** عقوبات قانون قيصر (Caesar Act) وقوائم الحظر الأمريكية (OFAC)؛ يترتب على تشغيل المنصة في سوريا حظر فوري لحسابات الشركة السحابية في **Google Cloud (Gemini API), Cloudflare, AWS**.
|
||||||
|
- **الواقع الميداني:** تقنين كهربائي يصل إلى 22 ساعة يومياً، وانهيار القوة الشرائية (متوسط راتب المعلم 15-30 دولاراً).
|
||||||
|
- **الخيار الوحيد:** منح إنسانية غير ربحية عبر منظمات دولية (اليونيسف أو Education Cannot Wait).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. حوكمة الكود البرمجي وبروتوكول فحص الصف العاشر (Architectural Governance & Testing Protocol)
|
||||||
|
|
||||||
|
لضمان سلامة البناء وتجهيز النظام للانطلاق، يتم الالتزام بالمعايير الآتية:
|
||||||
|
|
||||||
|
### 9.1 الالتزام الصارم بنمط MVC في تطبيقات Flutter
|
||||||
|
- **طبقة البيانات (Data Layer / Model):** تقتصر على معالجة البيانات، ونماذج الكائنات، والتخزين المحلي والنداءات الشبكية الصرفة.
|
||||||
|
- **طبقة منطق الأعمال (Logic Layer / Controller):** تدار حصرياً عبر Cubits المعتمدة لفصل منطق الحسابات والتحقق عن الواجهات.
|
||||||
|
- **طبقة العرض (Presentation Layer / View):** شاشات وودجت مخصصة ذات واجهات فاخرة (Cupertino Dark Aesthetic) ترتبط بالـ Cubits عبر مستمعات التغير.
|
||||||
|
|
||||||
|
### 9.2 بروتوكول الاختبار الشامل للصف العاشر (The 8-Stage Test)
|
||||||
|
يتم التحقق من جاهزية المحرك بتطبيق الرحلة الكاملة على منهاج الصف العاشر:
|
||||||
|
1. تسجيل الدخول بالرقم الوطني والـ OTP وتخزين التوكن المشفر.
|
||||||
|
2. تصفح شجرة مواد الصف العاشر (رياضيات، فيزياء، إنجليزي، عربي، دين، تاريخ).
|
||||||
|
3. فتح عارض الدروس والتأكد من دعم الرموز الرياضية والناطق الصوتي.
|
||||||
|
4. تشغيل الفيديو والتأكد من ظهور العلامة المائية المشفرة بالـ IP ورقم الهاتف.
|
||||||
|
5. الإجابة الخاطئة عمداً في الفحص السقراطي والتأكد من إرجاع الفيديو 45 ثانية وتوثيق الخطأ في دفتر الأخطاء.
|
||||||
|
6. فتح معمل جيوجبرا للرياضيات ومعمل المتجهات للفيزياء واختبار استجابة الـ 120fps.
|
||||||
|
7. تقديم الامتحان التكيفي وحساب مؤشر الجاهزية وإرسال رسالة واتساب تجريبية لولي الأمر عبر نبّه.
|
||||||
|
8. فتح دفتر الأخطاء وتقديم الكويز العلاجي وتحقيق الشفاء المعرفي التام للمفهوم.
|
||||||
|
|
||||||
|
### 9.3 مصنع تعبئة التوجيهي المؤتمت (The Tawjihi Ingestion Factory)
|
||||||
|
بعد اجتياز الاختبار بنجاح:
|
||||||
|
- تشغيل سكربت قراءة ملفات الماركداون لتحويل كتب التوجيهي إلى بنية رقمية متوافقة.
|
||||||
|
- تشغيل سكربت مزامنة المناهج لضخ مواد العلمي والصناعي والأدبي في قاعدة البيانات آلياً.
|
||||||
|
- معالجة الفيديوهات عبر عامل الرفع غير المتزامن لتقطيع HLS والربط بالدروس فورياً.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **الخلاصة التنفيذية:**
|
||||||
|
> بهذا الملف المرجعي الموثق، تمتلك منصة صَقِل بوصلة هندسية واستثمارية متكاملة تضمن حماية رأس المال، واقتناص المنح الوطنية غير المشروطة، واختراق المؤسسات الحكومية والخاصة بثقة تقنية عميقة.
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
|
<div dir="rtl" align="right">
|
||||||
|
|
||||||
# تحليل جدوى منصة "صَقِل" (Saqel) التعليمية لطلبة التوجيهي في الأردن
|
# تحليل جدوى منصة "صَقِل" (Saqel) التعليمية لطلبة التوجيهي في الأردن
|
||||||
|
|
||||||
|
> دراسة تاريخية ذات افتراضات غير محققة حالياً؛ لا تستخدم أرقام السوق أو التراخيص/الإعفاءات أو الأرباح أو ادعاءات غياب التفاعل عند المنافسين كحقائق. النموذج التجاري المطلوب الآن هو دخل المعلم بالدقائق المستحقة، وفق [مواصفات المرحلة القادمة](docs/NEXT_PHASE_MASTER_SPEC.md). راجع [تدقيق 2026-09-09](docs/CURRENT_STATE_AUDIT_2026-09-09.md)؛ Laravel/Docker و`artisan` ليسوا وصف التنفيذ الحالي. لم تُعد هذه المهمة تدقيقاً قانونياً أو دراسة مالية نهائية.
|
||||||
|
|
||||||
> ملخص تنفيذي مختصر — منصة صَقِل: الفهم لا الحفظ والتفاعل الذكي عبر EdTech 2.0
|
> ملخص تنفيذي مختصر — منصة صَقِل: الفهم لا الحفظ والتفاعل الذكي عبر EdTech 2.0
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -152,6 +157,8 @@
|
|||||||
|
|
||||||
### 8.2 كيف نضيفه تقنياً؟ (المعمارية)
|
### 8.2 كيف نضيفه تقنياً؟ (المعمارية)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
```
|
```
|
||||||
[الطالب/المعلم] → [Laravel API] → [طبقة AI Service] → [Gemini API]
|
[الطالب/المعلم] → [Laravel API] → [طبقة AI Service] → [Gemini API]
|
||||||
↓ ↓
|
↓ ↓
|
||||||
@@ -160,6 +167,8 @@
|
|||||||
المناهج كـ Embeddings) + حد استخدام يومي/طالب
|
المناهج كـ Embeddings) + حد استخدام يومي/طالب
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<div dir="rtl" align="right">
|
||||||
|
|
||||||
**القرارات الهندسية:**
|
**القرارات الهندسية:**
|
||||||
1. **Laravel يكفي** — نداء Gemini عبر HTTP مباشرة (لا حاجة لخدمة Python منفصلة في البداية).
|
1. **Laravel يكفي** — نداء Gemini عبر HTTP مباشرة (لا حاجة لخدمة Python منفصلة في البداية).
|
||||||
2. **RAG (Retrieval-Augmented Generation)**: نفريغ الدروس نصياً + تخزينها كـ Embeddings في `pgvector` → إجابات AI **مقيّدة بمحتوى المنهج** وتذكر مصدرها (اسم الدرس والدقيقة) → تقليل الهلوسة بشدة.
|
2. **RAG (Retrieval-Augmented Generation)**: نفريغ الدروس نصياً + تخزينها كـ Embeddings في `pgvector` → إجابات AI **مقيّدة بمحتوى المنهج** وتذكر مصدرها (اسم الدرس والدقيقة) → تقليل الهلوسة بشدة.
|
||||||
@@ -213,6 +222,8 @@
|
|||||||
|
|
||||||
### 9.3 حلقة "المعلم المباشر" بالذكاء الاصطناعي (AI Tutor Loop)
|
### 9.3 حلقة "المعلم المباشر" بالذكاء الاصطناعي (AI Tutor Loop)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
```
|
```
|
||||||
[أحداث الطالب: مشاهدات + إجابات]
|
[أحداث الطالب: مشاهدات + إجابات]
|
||||||
→ [محرك الإتقان يحدّث نقاط الضعف]
|
→ [محرك الإتقان يحدّث نقاط الضعف]
|
||||||
@@ -223,6 +234,8 @@
|
|||||||
→ [الحلقة تعيد القياس بعد الكويز العلاجي — قياس قبل/بعد]
|
→ [الحلقة تعيد القياس بعد الكويز العلاجي — قياس قبل/بعد]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<div dir="rtl" align="right">
|
||||||
|
|
||||||
- المخرجات **JSON منظمة** (ليست نصاً حرّاً): `weak_concepts[]`, `remedial_lesson_ids[]`, `recommended_actions[]`, `motivation_note`.
|
- المخرجات **JSON منظمة** (ليست نصاً حرّاً): `weak_concepts[]`, `remedial_lesson_ids[]`, `recommended_actions[]`, `motivation_note`.
|
||||||
- تقارير ولي الأمر الأسبوعية تُولَّد آلياً بلغة طبيعية من نفس البيانات.
|
- تقارير ولي الأمر الأسبوعية تُولَّد آلياً بلغة طبيعية من نفس البيانات.
|
||||||
|
|
||||||
@@ -249,7 +262,7 @@
|
|||||||
|
|
||||||
- **محلياً**: كتابة الكود + `php artisan test` (SQLite in-memory) — لا حاجة Docker.
|
- **محلياً**: كتابة الكود + `php artisan test` (SQLite in-memory) — لا حاجة Docker.
|
||||||
- **السيرفر**: `docker compose up -d --build` يشغّل: php-fpm، nginx، mysql:8، queue worker (لتشغيل مهام AI في الخلفية).
|
- **السيرفر**: `docker compose up -d --build` يشغّل: php-fpm، nginx، mysql:8، queue worker (لتشغيل مهام AI في الخلفية).
|
||||||
- أسرار الإنتاج (GEMINI_API_KEY، كلمات مرور DB) عبر `.env` على السيرفر فقط — لا تُرفع للمستودع.
|
- أسرار الإنتاج (GEMINI_KEY، كلمات مرور DB) عبر `.env` على السيرفر فقط — لا تُرفع للمستودع.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -325,3 +338,5 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
*الملف أُنشئ كأساس لاتخاذ القرار — أي تعديل على الاتجاه يُوثَّق هنا.*
|
*الملف أُنشئ كأساس لاتخاذ القرار — أي تعديل على الاتجاه يُوثَّق هنا.*
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
<div dir="rtl" align="right">
|
||||||
|
|
||||||
# صَقِل | Saqel Platform 🎓
|
# صَقِل | Saqel Platform 🎓
|
||||||
|
|
||||||
**منصة صَقِل (Saqel)** — الجيل الثاني من منصات التعليم التفاعلي لطلبة الثانوية العامة (التوجيهي) في الأردن، والقائمة على فلسفة "صقل الفهم لا الحفظ" بدعم من الذكاء الاصطناعي (EdTech 2.0).
|
**منصة صَقِل (Saqel)** — الجيل الثاني من منصات التعليم التفاعلي لطلبة الثانوية العامة (التوجيهي) في الأردن، والقائمة على فلسفة "صقل الفهم لا الحفظ" بدعم من الذكاء الاصطناعي (EdTech 2.0).
|
||||||
@@ -6,29 +8,62 @@
|
|||||||
|
|
||||||
## 🏛️ الهيكل التقني للمشروع (Tech Stack)
|
## 🏛️ الهيكل التقني للمشروع (Tech Stack)
|
||||||
|
|
||||||
- **Backend:** PHP 8.3+ / Laravel 11 / RESTful APIs
|
- **Backend:** PHP 8.3+ / Native PHP REST API with PDO and a lightweight internal Router
|
||||||
- **Database:** SQLite (Local Development/Testing) / MySQL 8 (Production)
|
- **Database:** SQLite (Local Development/Testing) / MySQL 8 (Production)
|
||||||
- **AI Engine:** Google Gemini Flash API (Multimodal / Socratic Tutor / Concept Diagnosis)
|
- **AI Engine:** Google Gemini Flash API (Multimodal / Socratic Tutor / Concept Diagnosis)
|
||||||
- **Video & DRM:** Bunny Stream (HLS + Dynamic Watermark + DRM)
|
- **Video & DRM:** Bunny Stream (HLS + Dynamic Watermark + DRM)
|
||||||
- **Deployment:** Docker & Docker Compose (`saqel_app`, `saqel_nginx`, `saqel_mysql`, `saqel_queue`)
|
- **Deployment:** PHP-FPM/Nginx on the production server; Cloudflare R2 for video and static media
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 البدء السريع (Quick Start)
|
## 🚀 البدء السريع (Quick Start)
|
||||||
|
|
||||||
### 1. تشغيل الاختبارات محلياً:
|
### 1. تشغيل الاختبارات محلياً:
|
||||||
|
|
||||||
|
توجد اختبارات Flutter داخل مجلد `test` لكل تطبيق. مثال لتطبيق الطالب، من جذر المستودع:
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd backend
|
cd apps/student_app
|
||||||
php artisan test
|
flutter test
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. تشغيل السيرفر عبر Docker:
|
<div dir="rtl" align="right">
|
||||||
|
|
||||||
|
الباك إند PHP أصلي ولا يوجد أمر `artisan` في النسخة المدققة. اقرأ سكربتات الاختبار قبل تشغيلها؛ بعضها يستدعي خدمات خارجية أو يكتب بيانات. خطة اختبارات التكامل المطلوبة موضحة في وثائق المرحلة القادمة.
|
||||||
|
|
||||||
|
### 2. تشغيل الـAPI محليًا:
|
||||||
|
|
||||||
|
من جذر المستودع:
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d --build
|
cd backend
|
||||||
|
php -S 127.0.0.1:8000 -t public
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<div dir="rtl" align="right">
|
||||||
|
|
||||||
|
الإنتاج يعمل عبر PHP-FPM وNginx مباشرة، ولا يعتمد على Laravel أو Docker.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📂 مسار التوثيق والتحليلات
|
## 📂 مسار التوثيق والتحليلات
|
||||||
|
|
||||||
|
**ابدأ بمراجع المرحلة القادمة المعتمدة للتخطيط بتاريخ 2026-09-09:**
|
||||||
|
|
||||||
|
- [مواصفات المنتج والتعليم ودخل المعلمين](docs/NEXT_PHASE_MASTER_SPEC.md)
|
||||||
|
- [تدقيق الكود: المنفذ والفجوات الفعلية](docs/CURRENT_STATE_AUDIT_2026-09-09.md)
|
||||||
|
- [برومبتات فحص الفيديو وMarkdown والأسئلة ومناقشة الدرس](docs/VIDEO_REVIEW_AND_SOCRATIC_PROMPTS.md)
|
||||||
|
- [مهام التنفيذ واختبارات القبول](docs/NEXT_PHASE_EXECUTION_BACKLOG.md)
|
||||||
|
- [تعليمات النماذج الدائمة](AGENTS.md)
|
||||||
|
|
||||||
|
هذه الدفعة توثيق فقط، ولا تعني تنفيذ الميزات المخططة أو تحقق تشغيل الإنتاج. الوثائق التالية تاريخية؛ لا تُعتمد ادعاءاتها عن الاكتمال أو المنافسة أو المال عند تعارضها مع التدقيق الأحدث:
|
||||||
|
|
||||||
- [وثيقة المخطط المعماري والاستراتيجي الشامل (docs/PLATFORM_MASTER_PLAN.md)](./docs/PLATFORM_MASTER_PLAN.md)
|
- [وثيقة المخطط المعماري والاستراتيجي الشامل (docs/PLATFORM_MASTER_PLAN.md)](./docs/PLATFORM_MASTER_PLAN.md)
|
||||||
- [تحليل الجدوى الشامل (FEASIBILITY_ANALYSIS.md)](./FEASIBILITY_ANALYSIS.md)
|
- [تحليل الجدوى الشامل (FEASIBILITY_ANALYSIS.md)](./FEASIBILITY_ANALYSIS.md)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Failed
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.build/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
.swiftpm/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
/coverage/
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: "ee80f08bbf97172ec030b8751ceab557177a34a6"
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
base_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
- platform: android
|
||||||
|
create_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
base_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
- platform: ios
|
||||||
|
create_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
base_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
- platform: linux
|
||||||
|
create_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
base_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
- platform: macos
|
||||||
|
create_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
base_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
- platform: web
|
||||||
|
create_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
base_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
- platform: windows
|
||||||
|
create_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
base_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at https://dart.dev/lints.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
|
# producing the lint.
|
||||||
|
rules:
|
||||||
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
.cxx/
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/to/reference-keystore
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.saqel.admin_app"
|
||||||
|
compileSdk = flutter.compileSdkVersion
|
||||||
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId = "com.saqel.admin_app"
|
||||||
|
// You can update the following values to match your application needs.
|
||||||
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
|
minSdk = flutter.minSdkVersion
|
||||||
|
targetSdk = flutter.targetSdkVersion
|
||||||
|
versionCode = flutter.versionCode
|
||||||
|
versionName = flutter.versionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
compilerOptions {
|
||||||
|
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source = "../.."
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application
|
||||||
|
android:label="admin_app"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
|
<!-- Required to query activities that can process text, see:
|
||||||
|
https://developer.android.com/training/package-visibility and
|
||||||
|
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||||
|
|
||||||
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
|
<data android:mimeType="text/plain"/>
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.saqel.admin_app
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity : FlutterActivity()
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val newBuildDir: Directory =
|
||||||
|
rootProject.layout.buildDirectory
|
||||||
|
.dir("../../build")
|
||||||
|
.get()
|
||||||
|
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||||
|
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(":app")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register<Delete>("clean") {
|
||||||
|
delete(rootProject.layout.buildDirectory)
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||||
|
android.useAndroidX=true
|
||||||
|
# This newDsl flag was added by the Flutter template
|
||||||
|
android.newDsl=false
|
||||||
|
# This builtInKotlin flag was added by the Flutter template
|
||||||
|
android.builtInKotlin=false
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
pluginManagement {
|
||||||
|
val flutterSdkPath =
|
||||||
|
run {
|
||||||
|
val properties = java.util.Properties()
|
||||||
|
file("local.properties").inputStream().use { properties.load(it) }
|
||||||
|
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||||
|
flutterSdkPath
|
||||||
|
}
|
||||||
|
|
||||||
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
|
id("com.android.application") version "9.0.1" apply false
|
||||||
|
id("org.jetbrains.kotlin.android") version "2.3.20" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
include(":app")
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
**/dgph
|
||||||
|
*.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
*.perspectivev3
|
||||||
|
**/*sync/
|
||||||
|
.sconsign.dblite
|
||||||
|
.tags*
|
||||||
|
**/.vagrant/
|
||||||
|
**/DerivedData/
|
||||||
|
Icon?
|
||||||
|
**/Pods/
|
||||||
|
**/.symlinks/
|
||||||
|
profile
|
||||||
|
xcuserdata
|
||||||
|
**/.generated/
|
||||||
|
Flutter/App.framework
|
||||||
|
Flutter/Flutter.framework
|
||||||
|
Flutter/Flutter.podspec
|
||||||
|
Flutter/Generated.xcconfig
|
||||||
|
Flutter/ephemeral/
|
||||||
|
Flutter/app.flx
|
||||||
|
Flutter/app.zip
|
||||||
|
Flutter/flutter_assets/
|
||||||
|
Flutter/flutter_export_environment.sh
|
||||||
|
ServiceDefinitions.json
|
||||||
|
Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!default.mode1v3
|
||||||
|
!default.mode2v3
|
||||||
|
!default.pbxuser
|
||||||
|
!default.perspectivev3
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.flutter.flutter.app</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Uncomment this line to define a global platform for your project
|
||||||
|
# platform :ios, '13.0'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
|
||||||
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
target 'RunnerTests' do
|
||||||
|
inherit! :search_paths
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_ios_build_settings(target)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,623 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 54;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||||
|
remoteInfo = Runner;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||||
|
);
|
||||||
|
path = RunnerTests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146E51CF9000F007C117D = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146EF1CF9000F007C117D /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */,
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
331C807D294A63A400263BE5 /* Sources */,
|
||||||
|
331C807F294A63A400263BE5 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = RunnerTests;
|
||||||
|
productName = RunnerTests;
|
||||||
|
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = YES;
|
||||||
|
LastUpgradeCheck = 1510;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
331C8080294A63A400263BE5 = {
|
||||||
|
CreatedOnToolsVersion = 14.0;
|
||||||
|
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D = {
|
||||||
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
331C807F294A63A400263BE5 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||||
|
);
|
||||||
|
name = "Thin Binary";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
|
};
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Run Script";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
331C807D294A63A400263BE5 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
|
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||||
|
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C146FB1CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C147001CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = 63CVT8G5P8;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.saqel.adminApp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
331C8088294A63A400263BE5 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.saqel.adminApp.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
331C8089294A63A400263BE5 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.saqel.adminApp.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
331C808A294A63A400263BE5 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.saqel.adminApp.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
97C147061CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = 63CVT8G5P8;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.saqel.adminApp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147071CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = 63CVT8G5P8;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.saqel.adminApp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
331C8088294A63A400263BE5 /* Debug */,
|
||||||
|
331C8089294A63A400263BE5 /* Release */,
|
||||||
|
331C808A294A63A400263BE5 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147031CF9000F007C117D /* Debug */,
|
||||||
|
97C147041CF9000F007C117D /* Release */,
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147061CF9000F007C117D /* Debug */,
|
||||||
|
97C147071CF9000F007C117D /* Release */,
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1510"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||||
|
BuildableName = "RunnerTests.xctest"
|
||||||
|
BlueprintName = "RunnerTests"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
enableGPUValidationMode = "1"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@main
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||||
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"filename" : "Icon-App-1024x1024@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
@@ -0,0 +1,5 @@
|
|||||||
|
# Launch Screen Assets
|
||||||
|
|
||||||
|
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||||
|
|
||||||
|
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="53" y="375"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Flutter View Controller-->
|
||||||
|
<scene sceneID="tne-QT-ifu">
|
||||||
|
<objects>
|
||||||
|
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Admin App</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>admin_app</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<false/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneClassName</key>
|
||||||
|
<string>UIWindowScene</string>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>flutter</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||||
|
<key>UISceneStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#import "GeneratedPluginRegistrant.h"
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SceneDelegate: FlutterSceneDelegate {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class RunnerTests: XCTestCase {
|
||||||
|
|
||||||
|
func testExample() {
|
||||||
|
// If you add code to the Runner application, consider adding tests here.
|
||||||
|
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import '../utils/app_logger.dart';
|
||||||
|
|
||||||
|
/// Ultra-Resilient Storage Service for Admin App
|
||||||
|
class StorageService {
|
||||||
|
final FlutterSecureStorage _secureStorage;
|
||||||
|
|
||||||
|
StorageService({FlutterSecureStorage? secureStorage})
|
||||||
|
: _secureStorage = secureStorage ??
|
||||||
|
const FlutterSecureStorage(
|
||||||
|
mOptions: MacOsOptions(
|
||||||
|
accessibility: KeychainAccessibility.first_unlock,
|
||||||
|
),
|
||||||
|
aOptions: AndroidOptions(
|
||||||
|
encryptedSharedPreferences: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String _keyToken = 'saqel_admin_jwt_token';
|
||||||
|
static const String _keyUser = 'saqel_admin_user_data';
|
||||||
|
|
||||||
|
Future<void> _writeSafe(String key, String value) async {
|
||||||
|
try {
|
||||||
|
await _secureStorage.write(key: key, value: value);
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
AppLogger.log('Keychain notice ($key): ${e.message}', tag: 'STORAGE');
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> _readSafe(String key) async {
|
||||||
|
try {
|
||||||
|
final val = await _secureStorage.read(key: key);
|
||||||
|
if (val != null && val.isNotEmpty) return val;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> saveToken(String token) async => await _writeSafe(_keyToken, token);
|
||||||
|
Future<String?> getToken() async => await _readSafe(_keyToken);
|
||||||
|
|
||||||
|
Future<void> clearSession() async {
|
||||||
|
try {
|
||||||
|
await _secureStorage.delete(key: _keyToken);
|
||||||
|
await _secureStorage.delete(key: _keyUser);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
/// Enterprise Logging Utility for Saqel Admin & Schools Management
|
||||||
|
/// Active in Debug mode (kDebugMode), completely silent in Production
|
||||||
|
class AppLogger {
|
||||||
|
AppLogger._();
|
||||||
|
|
||||||
|
static void log(String message, {String tag = 'ADMIN_APP'}) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint('[$tag] $message');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void request({
|
||||||
|
required String method,
|
||||||
|
required Uri uri,
|
||||||
|
Map<String, String>? headers,
|
||||||
|
dynamic body,
|
||||||
|
}) {
|
||||||
|
if (!kDebugMode) return;
|
||||||
|
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
buffer.writeln(
|
||||||
|
'\n🌐 ─── [HTTP REQUEST (ADMIN)] ───────────────────────────────────');
|
||||||
|
buffer.writeln('➡️ Method: $method');
|
||||||
|
buffer.writeln('🔗 URL: $uri');
|
||||||
|
if (headers != null && headers.isNotEmpty) {
|
||||||
|
final safeHeaders = Map<String, String>.from(headers);
|
||||||
|
if (safeHeaders.containsKey('Authorization')) {
|
||||||
|
final auth = safeHeaders['Authorization']!;
|
||||||
|
safeHeaders['Authorization'] = auth.length > 20
|
||||||
|
? '${auth.substring(0, 15)}...'
|
||||||
|
: 'Bearer [REDACTED]';
|
||||||
|
}
|
||||||
|
buffer.writeln('📋 Headers: ${jsonEncode(safeHeaders)}');
|
||||||
|
}
|
||||||
|
if (body != null) {
|
||||||
|
buffer.writeln('📦 Body: ${body is String ? body : jsonEncode(body)}');
|
||||||
|
}
|
||||||
|
buffer.writeln(
|
||||||
|
'───────────────────────────────────────────────────────────────');
|
||||||
|
debugPrint(buffer.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void response({
|
||||||
|
required String method,
|
||||||
|
required Uri uri,
|
||||||
|
required int statusCode,
|
||||||
|
dynamic responseBody,
|
||||||
|
Duration? duration,
|
||||||
|
}) {
|
||||||
|
if (!kDebugMode) return;
|
||||||
|
|
||||||
|
final isSuccess = statusCode >= 200 && statusCode < 300;
|
||||||
|
final emoji = isSuccess ? '✅' : '⚠️';
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
|
||||||
|
buffer.writeln(
|
||||||
|
'\n$emoji ─── [HTTP RESPONSE $statusCode (ADMIN)] ───────────────────────');
|
||||||
|
buffer.writeln('⬅️ Method: $method');
|
||||||
|
buffer.writeln('🔗 URL: $uri');
|
||||||
|
if (duration != null) {
|
||||||
|
buffer.writeln('⏱️ Time: ${duration.inMilliseconds}ms');
|
||||||
|
}
|
||||||
|
if (responseBody != null) {
|
||||||
|
buffer.writeln(
|
||||||
|
'📦 Data: ${responseBody is String ? responseBody : jsonEncode(responseBody)}');
|
||||||
|
}
|
||||||
|
buffer.writeln(
|
||||||
|
'───────────────────────────────────────────────────────────────');
|
||||||
|
debugPrint(buffer.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void error(String message,
|
||||||
|
{dynamic error, StackTrace? stackTrace, String tag = 'ADMIN_ERROR'}) {
|
||||||
|
if (!kDebugMode) return;
|
||||||
|
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
buffer.writeln(
|
||||||
|
'\n🚨 ─── [ERROR: $tag] ──────────────────────────────────────────');
|
||||||
|
buffer.writeln('❌ Message: $message');
|
||||||
|
if (error != null) {
|
||||||
|
buffer.writeln('⚠️ Error: $error');
|
||||||
|
}
|
||||||
|
if (stackTrace != null) {
|
||||||
|
buffer.writeln('📍 Trace: \n$stackTrace');
|
||||||
|
}
|
||||||
|
buffer.writeln(
|
||||||
|
'───────────────────────────────────────────────────────────────');
|
||||||
|
debugPrint(buffer.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Ultra-Luxury Apple-Grade SnackBar / Toast Notification System for Saqel Apps
|
||||||
|
class SaqelToast {
|
||||||
|
SaqelToast._();
|
||||||
|
|
||||||
|
static const Color _teacherEmerald = Color(0xFF10B981);
|
||||||
|
static const Color _errorRed = Color(0xFFEF4444);
|
||||||
|
static const Color _appleBlue = Color(0xFF0071E3);
|
||||||
|
static const Color _whatsappGreen = Color(0xFF25D366);
|
||||||
|
|
||||||
|
/// Show Success Toast (Emerald / Green)
|
||||||
|
static void showSuccess(
|
||||||
|
BuildContext context,
|
||||||
|
String message, {
|
||||||
|
String? title,
|
||||||
|
Duration duration = const Duration(seconds: 3),
|
||||||
|
}) {
|
||||||
|
_show(
|
||||||
|
context: context,
|
||||||
|
message: message,
|
||||||
|
title: title,
|
||||||
|
icon: CupertinoIcons.checkmark_circle_fill,
|
||||||
|
iconColor: _teacherEmerald,
|
||||||
|
borderColor: _teacherEmerald.withAlpha(90),
|
||||||
|
backgroundColor: const Color(0xE60D1A14),
|
||||||
|
duration: duration,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show Error Toast (Crimson / Red)
|
||||||
|
static void showError(
|
||||||
|
BuildContext context,
|
||||||
|
String message, {
|
||||||
|
String? title,
|
||||||
|
Duration duration = const Duration(seconds: 4),
|
||||||
|
}) {
|
||||||
|
_show(
|
||||||
|
context: context,
|
||||||
|
message: message,
|
||||||
|
title: title,
|
||||||
|
icon: CupertinoIcons.exclamationmark_circle_fill,
|
||||||
|
iconColor: _errorRed,
|
||||||
|
borderColor: _errorRed.withAlpha(90),
|
||||||
|
backgroundColor: const Color(0xE61A0D0D),
|
||||||
|
duration: duration,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show Info Toast (Apple Blue / Cyan)
|
||||||
|
static void showInfo(
|
||||||
|
BuildContext context,
|
||||||
|
String message, {
|
||||||
|
String? title,
|
||||||
|
Duration duration = const Duration(seconds: 3),
|
||||||
|
}) {
|
||||||
|
_show(
|
||||||
|
context: context,
|
||||||
|
message: message,
|
||||||
|
title: title,
|
||||||
|
icon: CupertinoIcons.info_circle_fill,
|
||||||
|
iconColor: _appleBlue,
|
||||||
|
borderColor: _appleBlue.withAlpha(90),
|
||||||
|
backgroundColor: const Color(0xE60D1424),
|
||||||
|
duration: duration,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show WhatsApp Toast (WhatsApp Green)
|
||||||
|
static void showWhatsApp(
|
||||||
|
BuildContext context,
|
||||||
|
String message, {
|
||||||
|
String? title,
|
||||||
|
Duration duration = const Duration(seconds: 3),
|
||||||
|
}) {
|
||||||
|
_show(
|
||||||
|
context: context,
|
||||||
|
message: message,
|
||||||
|
title: title,
|
||||||
|
icon: CupertinoIcons.chat_bubble_fill,
|
||||||
|
iconColor: _whatsappGreen,
|
||||||
|
borderColor: _whatsappGreen.withAlpha(100),
|
||||||
|
backgroundColor: const Color(0xE6091A12),
|
||||||
|
duration: duration,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _show({
|
||||||
|
required BuildContext context,
|
||||||
|
required String message,
|
||||||
|
String? title,
|
||||||
|
required IconData icon,
|
||||||
|
required Color iconColor,
|
||||||
|
required Color borderColor,
|
||||||
|
required Color backgroundColor,
|
||||||
|
required Duration duration,
|
||||||
|
}) {
|
||||||
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
|
messenger.hideCurrentSnackBar();
|
||||||
|
|
||||||
|
messenger.showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
duration: duration,
|
||||||
|
elevation: 0,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
margin: const EdgeInsets.only(bottom: 24, left: 16, right: 16),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
content: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
|
borderRadius: BorderRadius.circular(18),
|
||||||
|
border: Border.all(color: borderColor, width: 1.2),
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Color(0x99000000),
|
||||||
|
blurRadius: 25,
|
||||||
|
offset: Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: iconColor.withAlpha(35),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(icon, color: iconColor, size: 20),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 14),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (title != null && title.isNotEmpty) ...[
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
],
|
||||||
|
Text(
|
||||||
|
message,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12.5,
|
||||||
|
color: Color(0xFFE2E8F0),
|
||||||
|
height: 1.4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,330 @@
|
|||||||
|
/// Directorate Model (المظلة المركزية: مديرية الثقافة العسكرية)
|
||||||
|
class DirectorateOverview {
|
||||||
|
final String code;
|
||||||
|
final String name;
|
||||||
|
final String commanderName;
|
||||||
|
final int totalSchools;
|
||||||
|
final int totalStudents;
|
||||||
|
final int totalTeachers;
|
||||||
|
final double annualContractValue;
|
||||||
|
final int recordedLessonsMonth;
|
||||||
|
final double complianceRate;
|
||||||
|
final double aiAverageScore;
|
||||||
|
final int activeUnifiedExams;
|
||||||
|
|
||||||
|
DirectorateOverview({
|
||||||
|
required this.code,
|
||||||
|
required this.name,
|
||||||
|
required this.commanderName,
|
||||||
|
required this.totalSchools,
|
||||||
|
required this.totalStudents,
|
||||||
|
required this.totalTeachers,
|
||||||
|
required this.annualContractValue,
|
||||||
|
required this.recordedLessonsMonth,
|
||||||
|
required this.complianceRate,
|
||||||
|
required this.aiAverageScore,
|
||||||
|
required this.activeUnifiedExams,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory DirectorateOverview.fromJson(Map<String, dynamic> json) {
|
||||||
|
return DirectorateOverview(
|
||||||
|
code: json['code']?.toString() ?? '',
|
||||||
|
name: json['name']?.toString() ?? '',
|
||||||
|
commanderName: json['commander_name']?.toString() ?? '',
|
||||||
|
totalSchools: (json['total_schools'] as num?)?.toInt() ?? 0,
|
||||||
|
totalStudents: (json['total_students'] as num?)?.toInt() ?? 0,
|
||||||
|
totalTeachers: (json['total_teachers'] as num?)?.toInt() ?? 0,
|
||||||
|
annualContractValue: (json['annual_contract_value'] as num?)?.toDouble() ?? 0,
|
||||||
|
recordedLessonsMonth: (json['recorded_lessons_month'] as num?)?.toInt() ?? 0,
|
||||||
|
complianceRate: (json['compliance_rate'] as num?)?.toDouble() ?? 0,
|
||||||
|
aiAverageScore: (json['ai_average_score'] as num?)?.toDouble() ?? 0,
|
||||||
|
activeUnifiedExams: (json['active_unified_exams'] as num?)?.toInt() ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// School Item Model (المدرسة التابعة وتصنيف الوزن)
|
||||||
|
class SchoolItem {
|
||||||
|
final int id;
|
||||||
|
final String code;
|
||||||
|
final String name;
|
||||||
|
final String governorate;
|
||||||
|
final String weightTier; // tier_a_small, tier_b_medium, tier_c_large
|
||||||
|
final int studentCount;
|
||||||
|
final int teacherCount;
|
||||||
|
final double compliance;
|
||||||
|
final String status; // excellent, good, warning_under_quota
|
||||||
|
final double annualFee;
|
||||||
|
|
||||||
|
SchoolItem({
|
||||||
|
required this.id,
|
||||||
|
required this.code,
|
||||||
|
required this.name,
|
||||||
|
required this.governorate,
|
||||||
|
required this.weightTier,
|
||||||
|
required this.studentCount,
|
||||||
|
required this.teacherCount,
|
||||||
|
required this.compliance,
|
||||||
|
required this.status,
|
||||||
|
required this.annualFee,
|
||||||
|
});
|
||||||
|
|
||||||
|
String get weightTierArabic {
|
||||||
|
switch (weightTier) {
|
||||||
|
case 'tier_a_small':
|
||||||
|
return 'الفئة (أ) - صغيرة/نائية';
|
||||||
|
case 'tier_c_large':
|
||||||
|
return 'الفئة (ج) - كبرى/مركزية';
|
||||||
|
case 'tier_b_medium':
|
||||||
|
default:
|
||||||
|
return 'الفئة (ب) - قياسية';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SchoolItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SchoolItem(
|
||||||
|
id: json['id'] ?? 0,
|
||||||
|
code: json['code'] ?? '',
|
||||||
|
name: json['name'] ?? '',
|
||||||
|
governorate: json['governorate']?.toString() ?? '',
|
||||||
|
weightTier: json['weight_tier']?.toString() ?? '',
|
||||||
|
studentCount: (json['student_count'] as num?)?.toInt() ?? 0,
|
||||||
|
teacherCount: (json['teacher_count'] as num?)?.toInt() ?? 0,
|
||||||
|
compliance: (json['compliance'] as num?)?.toDouble() ?? 0,
|
||||||
|
status: json['status']?.toString() ?? 'unknown',
|
||||||
|
annualFee: (json['annual_fee'] as num?)?.toDouble() ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detailed school data used by the principal and supervisor dashboards.
|
||||||
|
class SchoolDetail {
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
final String governorate;
|
||||||
|
final int studentCount;
|
||||||
|
final int teacherCount;
|
||||||
|
final String weightTier;
|
||||||
|
final String directorName;
|
||||||
|
final String biweeklyCycle;
|
||||||
|
final String cycleProgress;
|
||||||
|
|
||||||
|
const SchoolDetail({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.governorate,
|
||||||
|
required this.studentCount,
|
||||||
|
required this.teacherCount,
|
||||||
|
required this.weightTier,
|
||||||
|
required this.directorName,
|
||||||
|
required this.biweeklyCycle,
|
||||||
|
required this.cycleProgress,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SchoolDetail.fromJson(Map<String, dynamic> json) => SchoolDetail(
|
||||||
|
id: (json['id'] as num?)?.toInt() ?? 0,
|
||||||
|
name: json['name']?.toString() ?? '',
|
||||||
|
governorate: json['governorate']?.toString() ?? '',
|
||||||
|
studentCount: (json['student_count'] as num?)?.toInt() ?? 0,
|
||||||
|
teacherCount: (json['teacher_count'] as num?)?.toInt() ?? 0,
|
||||||
|
weightTier: json['weight_tier']?.toString() ?? '',
|
||||||
|
directorName: json['director_name']?.toString() ?? '',
|
||||||
|
biweeklyCycle: json['biweekly_cycle']?.toString() ?? '',
|
||||||
|
cycleProgress: json['cycle_progress']?.toString() ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A real computer-lab resource returned by the school dashboard API.
|
||||||
|
class ComputerLabItem {
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
final int deviceCount;
|
||||||
|
final int onlineDevices;
|
||||||
|
final String status;
|
||||||
|
|
||||||
|
const ComputerLabItem({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.deviceCount,
|
||||||
|
required this.onlineDevices,
|
||||||
|
required this.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ComputerLabItem.fromJson(Map<String, dynamic> json) => ComputerLabItem(
|
||||||
|
id: (json['id'] as num?)?.toInt() ?? 0,
|
||||||
|
name: json['name']?.toString() ?? '',
|
||||||
|
deviceCount: (json['device_count'] as num?)?.toInt() ?? 0,
|
||||||
|
onlineDevices: (json['online_devices'] as num?)?.toInt() ?? 0,
|
||||||
|
status: json['status']?.toString() ?? 'unknown',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Teacher Item Model (المعلم ومؤشر دورية الأسبوعين)
|
||||||
|
class TeacherItem {
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
final String subject;
|
||||||
|
final String grade;
|
||||||
|
final String quotaStatus; // completed, pending, overdue
|
||||||
|
final String lastLesson;
|
||||||
|
final double aiScore;
|
||||||
|
final String recordedAt;
|
||||||
|
|
||||||
|
TeacherItem({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.subject,
|
||||||
|
required this.grade,
|
||||||
|
required this.quotaStatus,
|
||||||
|
required this.lastLesson,
|
||||||
|
required this.aiScore,
|
||||||
|
required this.recordedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool get isCompleted => quotaStatus == 'completed';
|
||||||
|
|
||||||
|
factory TeacherItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
return TeacherItem(
|
||||||
|
id: json['id'] ?? 0,
|
||||||
|
name: json['name'] ?? '',
|
||||||
|
subject: json['subject'] ?? '',
|
||||||
|
grade: json['grade'] ?? '',
|
||||||
|
quotaStatus: json['quota_status'] ?? 'pending',
|
||||||
|
lastLesson: json['last_lesson'] ?? '',
|
||||||
|
aiScore: (json['ai_score'] as num?)?.toDouble() ?? 0,
|
||||||
|
recordedAt: json['recorded_at']?.toString() ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recorded Classroom Lesson Result (نتيجة فحص الحصة المرئية)
|
||||||
|
class RecordedLessonResult {
|
||||||
|
final String lessonUuid;
|
||||||
|
final String teacherName;
|
||||||
|
final String subject;
|
||||||
|
final String lessonTitle;
|
||||||
|
final double fileSizeMb;
|
||||||
|
final int durationMinutes;
|
||||||
|
final double aiAlignmentScore;
|
||||||
|
final String teacherTalkRatio;
|
||||||
|
final String status;
|
||||||
|
final String reportSummary;
|
||||||
|
final List<Map<String, String>> socraticCheckpoints;
|
||||||
|
|
||||||
|
RecordedLessonResult({
|
||||||
|
required this.lessonUuid,
|
||||||
|
required this.teacherName,
|
||||||
|
required this.subject,
|
||||||
|
required this.lessonTitle,
|
||||||
|
required this.fileSizeMb,
|
||||||
|
required this.durationMinutes,
|
||||||
|
required this.aiAlignmentScore,
|
||||||
|
required this.teacherTalkRatio,
|
||||||
|
required this.status,
|
||||||
|
required this.reportSummary,
|
||||||
|
required this.socraticCheckpoints,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory RecordedLessonResult.fromJson(Map<String, dynamic> json) {
|
||||||
|
var rawCheckpoints = json['socratic_checkpoints'] as List<dynamic>? ?? [];
|
||||||
|
List<Map<String, String>> checkpoints = rawCheckpoints.map((c) {
|
||||||
|
if (c is Map) {
|
||||||
|
return {
|
||||||
|
'timestamp': c['timestamp']?.toString() ?? '',
|
||||||
|
'question': c['question']?.toString() ?? '',
|
||||||
|
'objective': c['objective']?.toString() ?? '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return <String, String>{};
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return RecordedLessonResult(
|
||||||
|
lessonUuid: json['lesson_uuid'] ?? '',
|
||||||
|
teacherName: json['teacher_name'] ?? '',
|
||||||
|
subject: json['subject'] ?? '',
|
||||||
|
lessonTitle: json['lesson_title'] ?? '',
|
||||||
|
fileSizeMb: (json['file_size_mb'] as num?)?.toDouble() ?? 0,
|
||||||
|
durationMinutes: (json['duration_minutes'] as num?)?.toInt() ?? 0,
|
||||||
|
aiAlignmentScore: (json['ai_alignment_score'] as num?)?.toDouble() ?? 0,
|
||||||
|
teacherTalkRatio: json['teacher_talk_ratio']?.toString() ?? '',
|
||||||
|
status: json['status']?.toString() ?? 'unknown',
|
||||||
|
reportSummary: json['report_summary']?.toString() ?? '',
|
||||||
|
socraticCheckpoints: checkpoints,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unified Exam Model (الامتحان الموحد)
|
||||||
|
class UnifiedExamModel {
|
||||||
|
final String id;
|
||||||
|
final String title;
|
||||||
|
final String subject;
|
||||||
|
final String gradeLevel;
|
||||||
|
final String scheduledTime;
|
||||||
|
final int durationMinutes;
|
||||||
|
final bool pushedToLab;
|
||||||
|
final String status;
|
||||||
|
final List<String> forms;
|
||||||
|
final Map<String, dynamic> antiCheating;
|
||||||
|
|
||||||
|
UnifiedExamModel({
|
||||||
|
required this.id,
|
||||||
|
required this.title,
|
||||||
|
required this.subject,
|
||||||
|
required this.gradeLevel,
|
||||||
|
required this.scheduledTime,
|
||||||
|
required this.durationMinutes,
|
||||||
|
required this.pushedToLab,
|
||||||
|
required this.status,
|
||||||
|
required this.forms,
|
||||||
|
required this.antiCheating,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory UnifiedExamModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return UnifiedExamModel(
|
||||||
|
id: json['id'] ?? '',
|
||||||
|
title: json['title'] ?? '',
|
||||||
|
subject: json['subject'] ?? '',
|
||||||
|
gradeLevel: json['grade_level'] ?? '',
|
||||||
|
scheduledTime: json['scheduled_time'] ?? '',
|
||||||
|
durationMinutes: json['duration_minutes'] ?? 60,
|
||||||
|
pushedToLab: json['pushed_to_lab'] == true,
|
||||||
|
status: json['status'] ?? 'scheduled',
|
||||||
|
forms: (json['forms'] as List<dynamic>?)?.map((e) => e.toString()).toList() ?? ['نموذج أ', 'نموذج ب'],
|
||||||
|
antiCheating: json['anti_cheating'] as Map<String, dynamic>? ?? {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exam Anomaly Alert Model (تنبيهات الشذوذ الإحصائي والغش)
|
||||||
|
class ExamAnomalyAlert {
|
||||||
|
final String id;
|
||||||
|
final String type; // speed_impossible, error_clustering, performance_leap
|
||||||
|
final String schoolName;
|
||||||
|
final String subject;
|
||||||
|
final String description;
|
||||||
|
final String severity;
|
||||||
|
final String timestamp;
|
||||||
|
|
||||||
|
ExamAnomalyAlert({
|
||||||
|
required this.id,
|
||||||
|
required this.type,
|
||||||
|
required this.schoolName,
|
||||||
|
required this.subject,
|
||||||
|
required this.description,
|
||||||
|
required this.severity,
|
||||||
|
required this.timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ExamAnomalyAlert.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ExamAnomalyAlert(
|
||||||
|
id: json['id'] ?? '',
|
||||||
|
type: json['type'] ?? 'speed_impossible',
|
||||||
|
schoolName: json['school_name'] ?? '',
|
||||||
|
subject: json['subject'] ?? '',
|
||||||
|
description: json['description'] ?? '',
|
||||||
|
severity: json['severity'] ?? 'medium',
|
||||||
|
timestamp: json['timestamp'] ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import '../models/directorate_models.dart';
|
||||||
|
import '../../services/directorate_api_service.dart';
|
||||||
|
|
||||||
|
/// Repository for Directorate and School Principal operations
|
||||||
|
class DirectorateRepository {
|
||||||
|
final DirectorateApiService _apiService;
|
||||||
|
|
||||||
|
DirectorateRepository({DirectorateApiService? apiService})
|
||||||
|
: _apiService = apiService ?? DirectorateApiService();
|
||||||
|
|
||||||
|
/// Fetch full directorate command dashboard
|
||||||
|
Future<Map<String, dynamic>> getDirectorateDashboard() async {
|
||||||
|
return await DirectorateApiService.fetchDirectorateDashboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch school principal dashboard by school ID
|
||||||
|
Future<Map<String, dynamic>> getSchoolDashboard({int schoolId = 1}) async {
|
||||||
|
return await DirectorateApiService.fetchSchoolDashboard(schoolId: schoolId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Record field lesson observation
|
||||||
|
Future<Map<String, dynamic>> recordLesson({
|
||||||
|
required int schoolId,
|
||||||
|
required int teacherId,
|
||||||
|
required String subject,
|
||||||
|
required String gradeLevel,
|
||||||
|
required String topic,
|
||||||
|
required String filePath,
|
||||||
|
required String fileName,
|
||||||
|
}) async {
|
||||||
|
final result = await DirectorateApiService.recordLesson(
|
||||||
|
schoolId: schoolId,
|
||||||
|
teacherId: teacherId,
|
||||||
|
subject: subject,
|
||||||
|
gradeLevel: gradeLevel,
|
||||||
|
lessonTitle: topic,
|
||||||
|
durationMinutes: 0,
|
||||||
|
filePath: filePath,
|
||||||
|
fileName: fileName,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
'status': 'success',
|
||||||
|
'school_id': schoolId,
|
||||||
|
'data': result,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push unified exam to computer lab
|
||||||
|
Future<Map<String, dynamic>> pushExamToLab({
|
||||||
|
required int schoolId,
|
||||||
|
required String examTitle,
|
||||||
|
required String labName,
|
||||||
|
required int deviceCount,
|
||||||
|
}) async {
|
||||||
|
final examId = int.tryParse(examTitle);
|
||||||
|
if (examId == null) throw StateError('معرف الامتحان يجب أن يكون رقمياً.');
|
||||||
|
final pushed = await DirectorateApiService.pushExamToLab(examId: examId, schoolId: schoolId);
|
||||||
|
return {
|
||||||
|
'status': pushed ? 'success' : 'error',
|
||||||
|
'school_id': schoolId,
|
||||||
|
'lab_name': labName,
|
||||||
|
'device_count': deviceCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluate session integrity
|
||||||
|
Future<Map<String, dynamic>> evaluateIntegrity({
|
||||||
|
required int sessionId,
|
||||||
|
required String schoolCode,
|
||||||
|
}) async {
|
||||||
|
final result = await DirectorateApiService.evaluateExamSessionIntegrity();
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
'session_id': sessionId,
|
||||||
|
'school_code': schoolCode,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispatch reports to parents via WhatsApp
|
||||||
|
Future<Map<String, dynamic>> dispatchParentReports({
|
||||||
|
required int schoolId,
|
||||||
|
required String grade,
|
||||||
|
}) async {
|
||||||
|
final result = await DirectorateApiService.dispatchParentReports(schoolId: schoolId);
|
||||||
|
return {...result, 'grade': grade};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import '../../data/models/directorate_models.dart';
|
||||||
|
import '../../data/repositories/directorate_repository.dart';
|
||||||
|
|
||||||
|
abstract class DirectorateState {}
|
||||||
|
|
||||||
|
class DirectorateInitial extends DirectorateState {}
|
||||||
|
|
||||||
|
class DirectorateLoading extends DirectorateState {}
|
||||||
|
|
||||||
|
class DirectorateLoaded extends DirectorateState {
|
||||||
|
final DirectorateOverview overview;
|
||||||
|
final List<SchoolItem> schools;
|
||||||
|
final Map<String, dynamic> rawData;
|
||||||
|
|
||||||
|
DirectorateLoaded({
|
||||||
|
required this.overview,
|
||||||
|
required this.schools,
|
||||||
|
required this.rawData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class SchoolPrincipalLoaded extends DirectorateState {
|
||||||
|
final SchoolDetail school;
|
||||||
|
final List<TeacherItem> teachers;
|
||||||
|
final List<ComputerLabItem> labs;
|
||||||
|
final Map<String, dynamic> rawData;
|
||||||
|
|
||||||
|
SchoolPrincipalLoaded({
|
||||||
|
required this.school,
|
||||||
|
required this.teachers,
|
||||||
|
required this.labs,
|
||||||
|
required this.rawData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirectorateError extends DirectorateState {
|
||||||
|
final String message;
|
||||||
|
DirectorateError(this.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirectorateActionSuccess extends DirectorateState {
|
||||||
|
final String message;
|
||||||
|
final dynamic result;
|
||||||
|
DirectorateActionSuccess(this.message, [this.result]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cubit managing state for Directorate and School Principal operations
|
||||||
|
class DirectorateCubit extends Cubit<DirectorateState> {
|
||||||
|
final DirectorateRepository _repository;
|
||||||
|
|
||||||
|
DirectorateCubit({DirectorateRepository? repository})
|
||||||
|
: _repository = repository ?? DirectorateRepository(),
|
||||||
|
super(DirectorateInitial());
|
||||||
|
|
||||||
|
/// Load directorate command overview
|
||||||
|
Future<void> loadDirectorateOverview() async {
|
||||||
|
emit(DirectorateLoading());
|
||||||
|
try {
|
||||||
|
final res = await _repository.getDirectorateDashboard();
|
||||||
|
if (res['status'] == 'success' || res.containsKey('directorate')) {
|
||||||
|
final overview = DirectorateOverview.fromJson(res['directorate'] ?? {});
|
||||||
|
final schoolsList = (res['schools'] as List? ?? [])
|
||||||
|
.map((s) => SchoolItem.fromJson(s))
|
||||||
|
.toList();
|
||||||
|
emit(DirectorateLoaded(
|
||||||
|
overview: overview,
|
||||||
|
schools: schoolsList,
|
||||||
|
rawData: res,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
emit(DirectorateError(res['message'] ?? 'فشل تحميل بيانات المديرية'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(DirectorateError('خطأ في الاتصال: $e'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load school principal dashboard
|
||||||
|
Future<void> loadSchoolDashboard({int schoolId = 1}) async {
|
||||||
|
emit(DirectorateLoading());
|
||||||
|
try {
|
||||||
|
final res = await _repository.getSchoolDashboard(schoolId: schoolId);
|
||||||
|
if (res['status'] == 'success' || res.containsKey('school')) {
|
||||||
|
final school = SchoolDetail.fromJson(res['school'] ?? {});
|
||||||
|
final teachers = (res['teachers'] as List? ?? [])
|
||||||
|
.map((t) => TeacherItem.fromJson(t))
|
||||||
|
.toList();
|
||||||
|
final labs = (res['computer_labs'] as List? ?? [])
|
||||||
|
.map((l) => ComputerLabItem.fromJson(l))
|
||||||
|
.toList();
|
||||||
|
emit(SchoolPrincipalLoaded(
|
||||||
|
school: school,
|
||||||
|
teachers: teachers,
|
||||||
|
labs: labs,
|
||||||
|
rawData: res,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
emit(DirectorateError(res['message'] ?? 'فشل تحميل بيانات المدرسة'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(DirectorateError('خطأ في الاتصال: $e'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push exam to lab
|
||||||
|
Future<void> pushExam({
|
||||||
|
required int schoolId,
|
||||||
|
required String examTitle,
|
||||||
|
required String labName,
|
||||||
|
required int deviceCount,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final res = await _repository.pushExamToLab(
|
||||||
|
schoolId: schoolId,
|
||||||
|
examTitle: examTitle,
|
||||||
|
labName: labName,
|
||||||
|
deviceCount: deviceCount,
|
||||||
|
);
|
||||||
|
emit(DirectorateActionSuccess('تم دفع الامتحان للمختبر بنجاح', res));
|
||||||
|
// Reload dashboard
|
||||||
|
await loadSchoolDashboard(schoolId: schoolId);
|
||||||
|
} catch (e) {
|
||||||
|
emit(DirectorateError('فشل دفع الامتحان: $e'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'logic/cubits/directorate_cubit.dart';
|
||||||
|
import 'presentation/screens/directorate_command_screen.dart';
|
||||||
|
import 'presentation/screens/school_principal_screen.dart';
|
||||||
|
import 'presentation/screens/admin_auth_screen.dart';
|
||||||
|
import 'services/directorate_api_service.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(const SaqelAdminApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class SaqelAdminApp extends StatelessWidget {
|
||||||
|
const SaqelAdminApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
title: 'صَقِل - الإدارة المركزية والمدارس',
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
theme: ThemeData(
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
scaffoldBackgroundColor: const Color(0xFF070B12),
|
||||||
|
fontFamily: '-apple-system',
|
||||||
|
fontFamilyFallback: const [
|
||||||
|
'SF Pro Display',
|
||||||
|
'SF Pro Text',
|
||||||
|
'SF Arabic',
|
||||||
|
'Geeza Pro',
|
||||||
|
'Cairo',
|
||||||
|
'system-ui',
|
||||||
|
],
|
||||||
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xFF8B5CF6),
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
surface: const Color(0xFF0F172A),
|
||||||
|
primary: const Color(0xFF8B5CF6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
builder: (context, child) {
|
||||||
|
return Directionality(
|
||||||
|
textDirection: TextDirection.rtl,
|
||||||
|
child: child ?? const SizedBox(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
home: const AdminAuthGate(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdminAuthGate extends StatefulWidget {
|
||||||
|
const AdminAuthGate({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AdminAuthGate> createState() => _AdminAuthGateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AdminAuthGateState extends State<AdminAuthGate> {
|
||||||
|
late Future<bool> _session;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_session = DirectorateApiService.hasValidSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _authenticated() => setState(() => _session = Future.value(true));
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FutureBuilder<bool>(
|
||||||
|
future: _session,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState != ConnectionState.done) {
|
||||||
|
return const Scaffold(body: Center(child: CircularProgressIndicator()));
|
||||||
|
}
|
||||||
|
if (snapshot.data != true) return AdminAuthScreen(onAuthenticated: _authenticated);
|
||||||
|
return BlocProvider(
|
||||||
|
create: (_) => DirectorateCubit(),
|
||||||
|
child: const UnifiedSupervisorShell(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unified Shell allowing seamless switching between:
|
||||||
|
/// 1. School Principal / Field Supervisor View (تطبيق مدير المدرسة والمشرف)
|
||||||
|
/// 2. Directorate Commander View (لوحة القائد العام لمديرية الثقافة العسكرية)
|
||||||
|
class UnifiedSupervisorShell extends StatefulWidget {
|
||||||
|
const UnifiedSupervisorShell({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<UnifiedSupervisorShell> createState() => _UnifiedSupervisorShellState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UnifiedSupervisorShellState extends State<UnifiedSupervisorShell> {
|
||||||
|
int _currentModeIndex = 0; // 0 = School Principal, 1 = Directorate Commander
|
||||||
|
|
||||||
|
final List<Widget> _screens = const [
|
||||||
|
SchoolPrincipalScreen(),
|
||||||
|
DirectorateCommandScreen(),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFF070B12),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Top Luxury Persona Switcher
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFF0E1626),
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(color: Color(0xFF1E293B)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: const LinearGradient(
|
||||||
|
colors: [Color(0xFF8B5CF6), Color(0xFF4F46E5)],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: const Center(
|
||||||
|
child: Text(
|
||||||
|
'ص',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w900,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
const Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'منظومة صَقِل الإشرافية والسيادية',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13.5,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'التبديل بين الصلاحيات التنفيذية والقيادية',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Color(0xFF94A3B8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Segmented Switcher Pill
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF1E293B),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_buildModeTab(
|
||||||
|
index: 0,
|
||||||
|
title: 'مدير المدرسة / المشرف',
|
||||||
|
icon: CupertinoIcons.building_2_fill,
|
||||||
|
),
|
||||||
|
_buildModeTab(
|
||||||
|
index: 1,
|
||||||
|
title: 'لوحة المديرية',
|
||||||
|
icon: CupertinoIcons.shield_lefthalf_fill,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Active View
|
||||||
|
Expanded(
|
||||||
|
child: IndexedStack(
|
||||||
|
index: _currentModeIndex,
|
||||||
|
children: _screens,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildModeTab({
|
||||||
|
required int index,
|
||||||
|
required String title,
|
||||||
|
required IconData icon,
|
||||||
|
}) {
|
||||||
|
final isSelected = _currentModeIndex == index;
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => setState(() => _currentModeIndex = index),
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected ? const Color(0xFF8B5CF6) : Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
size: 14,
|
||||||
|
color: isSelected ? Colors.white : const Color(0xFF94A3B8),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500,
|
||||||
|
color: isSelected ? Colors.white : const Color(0xFF94A3B8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../services/directorate_api_service.dart';
|
||||||
|
|
||||||
|
class AdminAuthScreen extends StatefulWidget {
|
||||||
|
final VoidCallback onAuthenticated;
|
||||||
|
|
||||||
|
const AdminAuthScreen({super.key, required this.onAuthenticated});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AdminAuthScreen> createState() => _AdminAuthScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AdminAuthScreenState extends State<AdminAuthScreen> {
|
||||||
|
final _phone = TextEditingController();
|
||||||
|
final _otp = TextEditingController();
|
||||||
|
String _role = 'school_admin';
|
||||||
|
bool _otpSent = false;
|
||||||
|
bool _busy = false;
|
||||||
|
String? _error;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_phone.dispose();
|
||||||
|
_otp.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _submit() async {
|
||||||
|
setState(() {
|
||||||
|
_busy = true;
|
||||||
|
_error = null;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
if (!_otpSent) {
|
||||||
|
await DirectorateApiService.requestOtp(phoneNumber: _phone.text.trim(), role: _role);
|
||||||
|
if (mounted) setState(() => _otpSent = true);
|
||||||
|
} else {
|
||||||
|
await DirectorateApiService.verifyOtp(
|
||||||
|
phoneNumber: _phone.text.trim(),
|
||||||
|
otp: _otp.text.trim(),
|
||||||
|
role: _role,
|
||||||
|
);
|
||||||
|
widget.onAuthenticated();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) setState(() => _error = e.toString().replaceFirst('Bad state: ', ''));
|
||||||
|
} finally {
|
||||||
|
if (mounted) setState(() => _busy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 430),
|
||||||
|
child: Card(
|
||||||
|
margin: const EdgeInsets.all(24),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(28),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
const Text('دخول الإدارة', style: TextStyle(fontSize: 28, fontWeight: FontWeight.w800)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text('الدخول متاح فقط للحسابات المخوّلة مسبقاً على خادم صَقِل.'),
|
||||||
|
const SizedBox(height: 22),
|
||||||
|
DropdownButtonFormField<String>(
|
||||||
|
initialValue: _role,
|
||||||
|
decoration: const InputDecoration(labelText: 'الصلاحية'),
|
||||||
|
items: const [
|
||||||
|
DropdownMenuItem(value: 'school_admin', child: Text('مدير مدرسة')),
|
||||||
|
DropdownMenuItem(value: 'supervisor', child: Text('مشرف')),
|
||||||
|
DropdownMenuItem(value: 'directorate_admin', child: Text('مدير مديرية')),
|
||||||
|
],
|
||||||
|
onChanged: _otpSent ? null : (value) => setState(() => _role = value!),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
TextField(
|
||||||
|
controller: _phone,
|
||||||
|
enabled: !_otpSent,
|
||||||
|
keyboardType: TextInputType.phone,
|
||||||
|
decoration: const InputDecoration(labelText: 'رقم الهاتف', hintText: '079XXXXXXX'),
|
||||||
|
),
|
||||||
|
if (_otpSent) ...[
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
TextField(
|
||||||
|
controller: _otp,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: const InputDecoration(labelText: 'رمز التحقق'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (_error != null) ...[
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
Text(_error!, style: const TextStyle(color: Colors.redAccent)),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 22),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: _busy ? null : _submit,
|
||||||
|
child: Text(_busy ? 'جارٍ التحقق…' : (_otpSent ? 'دخول' : 'إرسال الرمز')),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,683 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../data/models/directorate_models.dart';
|
||||||
|
import '../../services/directorate_api_service.dart';
|
||||||
|
|
||||||
|
class DirectorateCommandScreen extends StatefulWidget {
|
||||||
|
const DirectorateCommandScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DirectorateCommandScreen> createState() =>
|
||||||
|
_DirectorateCommandScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DirectorateCommandScreenState extends State<DirectorateCommandScreen>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late TabController _tabController;
|
||||||
|
bool _isLoading = true;
|
||||||
|
Map<String, dynamic>? _data;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_tabController = TabController(length: 3, vsync: this);
|
||||||
|
_loadDashboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadDashboard() async {
|
||||||
|
setState(() => _isLoading = true);
|
||||||
|
final res = await DirectorateApiService.fetchDirectorateDashboard();
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_data = res;
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tabController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (_isLoading) {
|
||||||
|
return const Scaffold(
|
||||||
|
backgroundColor: Color(0xFF070B12),
|
||||||
|
body: Center(
|
||||||
|
child:
|
||||||
|
CupertinoActivityIndicator(color: Color(0xFF8B5CF6), radius: 18),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final dir = _data?['directorate'] ?? {};
|
||||||
|
final schools = (_data?['schools'] as List? ?? [])
|
||||||
|
.map((s) => SchoolItem.fromJson(s))
|
||||||
|
.toList();
|
||||||
|
final anomalies = (_data?['anomalies'] as List? ?? [])
|
||||||
|
.map((a) => ExamAnomalyAlert.fromJson(a))
|
||||||
|
.toList();
|
||||||
|
final radar = _data?['radar'] ?? {};
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFF070B12),
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: const Color(0xFF0E1626),
|
||||||
|
elevation: 0,
|
||||||
|
centerTitle: false,
|
||||||
|
title: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFF10B981),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
dir['name']?.toString() ?? 'مديرية غير محددة',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color: Colors.white),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
const Text(
|
||||||
|
'تعرض البيانات المتاحة ضمن نطاق صلاحيات الحساب.',
|
||||||
|
style: TextStyle(fontSize: 12, color: Color(0xFF94A3B8)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
bottom: TabBar(
|
||||||
|
controller: _tabController,
|
||||||
|
indicatorColor: const Color(0xFF8B5CF6),
|
||||||
|
indicatorWeight: 3,
|
||||||
|
labelColor: Colors.white,
|
||||||
|
unselectedLabelColor: const Color(0xFF64748B),
|
||||||
|
labelStyle:
|
||||||
|
const TextStyle(fontWeight: FontWeight.w700, fontSize: 13.5),
|
||||||
|
tabs: const [
|
||||||
|
Tab(
|
||||||
|
icon: Icon(CupertinoIcons.chart_bar_alt_fill, size: 18),
|
||||||
|
text: 'الرؤية الكلية والمدارس',
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
icon: Icon(CupertinoIcons.star_circle_fill, size: 18),
|
||||||
|
text: 'رادار المعلمين والتسييل',
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
icon: Icon(CupertinoIcons.shield_lefthalf_fill, size: 18),
|
||||||
|
text: 'نزاهة الامتحانات الموحدة',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: TabBarView(
|
||||||
|
controller: _tabController,
|
||||||
|
children: [
|
||||||
|
_buildMacroOverviewTab(dir, schools),
|
||||||
|
_buildTeachersRadarTab(radar),
|
||||||
|
_buildExamIntegrityTab(anomalies),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// TAB 1: Macro Directorate Metrics & 43 Schools Matrix
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
Widget _buildMacroOverviewTab(
|
||||||
|
Map<String, dynamic> dir, List<SchoolItem> schools) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(18),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
// Commander KPI Ribbon
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(18),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: const LinearGradient(
|
||||||
|
colors: [Color(0xFF1E1B4B), Color(0xFF0F172A)],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border:
|
||||||
|
Border.all(color: const Color(0xFF6366F1).withOpacity(0.4)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _kpiCell(
|
||||||
|
'إجمالي المدارس',
|
||||||
|
'${dir['total_schools'] ?? 'غير متاح'} مدرسة',
|
||||||
|
CupertinoIcons.building_2_fill,
|
||||||
|
const Color(0xFF818CF8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _kpiCell(
|
||||||
|
'الطلبة المسجلون',
|
||||||
|
'${dir['total_students'] ?? 'غير متاح'}',
|
||||||
|
CupertinoIcons.person_3_fill,
|
||||||
|
const Color(0xFF38BDF8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _kpiCell(
|
||||||
|
'معلمو الثقافة العسكرية',
|
||||||
|
'${dir['total_teachers'] ?? 'غير متاح'} معلم',
|
||||||
|
CupertinoIcons.person_badge_plus_fill,
|
||||||
|
const Color(0xFFFBBF24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _kpiCell(
|
||||||
|
'نسبة كوتة الحصص',
|
||||||
|
dir['compliance_rate'] == null ? 'غير متاح' : '${dir['compliance_rate']}%',
|
||||||
|
CupertinoIcons.check_mark_circled_solid,
|
||||||
|
const Color(0xFF34D399),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Schools Weight Tier Breakdown
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'المدارس ضمن النطاق:',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Colors.white),
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'القيمة التعاقدية غير معروضة',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: const Color(0xFFA78BFA)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// School List Cards
|
||||||
|
...schools.map((s) => Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF0F172A),
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
border: Border.all(color: const Color(0xFF1E293B)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 44,
|
||||||
|
height: 44,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _statusColor(s.status).withOpacity(0.15),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Icon(CupertinoIcons.building_2_fill,
|
||||||
|
color: _statusColor(s.status), size: 22),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 14),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
s.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Colors.white),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
s.governorate,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12, color: Color(0xFF94A3B8)),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 6, vertical: 1),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF1E293B),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
s.weightTierArabic,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 11, color: Color(0xFFA78BFA)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${s.compliance}% التزام',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.5,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: _statusColor(s.status),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'${s.studentCount} طالب · ${s.annualFee.toInt()} د.أ/سنة',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 11, color: Color(0xFF64748B)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// TAB 2: Teachers Radar & Marketplace Monetization
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
Widget _buildTeachersRadarTab(Map<String, dynamic> radar) {
|
||||||
|
final topTeachers = (radar['top_teachers'] as List? ?? []);
|
||||||
|
final needingSupport = (radar['needing_support'] as List? ?? []);
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(18),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
// Revenue and teacher ranking cannot be inferred without a verified ledger.
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: const LinearGradient(
|
||||||
|
colors: [Color(0xFF14532D), Color(0xFF0F172A)],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border:
|
||||||
|
Border.all(color: const Color(0xFF22C55E).withOpacity(0.4)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Row(
|
||||||
|
children: [
|
||||||
|
Icon(CupertinoIcons.money_dollar_circle_fill,
|
||||||
|
color: Color(0xFF4ADE80), size: 22),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'بيانات المعلمين المتاحة',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.5,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Colors.white),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text(
|
||||||
|
'لا توجد سياسة دخل أو اعتماد معلّم منشورة في هذه الشاشة. لا يظهر ترتيب إلا إذا أعاده الخادم بدليل موثق.',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.5, color: Color(0xFFCBD5E1), height: 1.5),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Top Teachers List
|
||||||
|
const Text(
|
||||||
|
'المعلمون الذين أعادهم الخادم:',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
if (topTeachers.isEmpty)
|
||||||
|
const Text('لا توجد بيانات ترتيب معلمين متاحة.', style: TextStyle(color: Color(0xFF94A3B8)))
|
||||||
|
else ...topTeachers.map((t) => Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
|
padding: const EdgeInsets.all(14),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF0F172A),
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
border: Border.all(
|
||||||
|
color: const Color(0xFF10B981).withOpacity(0.4)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const CircleAvatar(
|
||||||
|
radius: 20,
|
||||||
|
backgroundColor: Color(0xFF065F46),
|
||||||
|
child: Icon(CupertinoIcons.star_fill,
|
||||||
|
color: Color(0xFF34D399), size: 18),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
t['name'] ?? '',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Colors.white),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
'${t['subject']} · ${t['school']}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12, color: Color(0xFF94A3B8)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8, vertical: 3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF10B981).withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${t['score'] ?? 'غير متاح'}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color: Color(0xFF34D399)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
|
||||||
|
// Early Intervention Radar
|
||||||
|
const Text(
|
||||||
|
'حالات الدعم التي أعادها الخادم:',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
if (needingSupport.isEmpty)
|
||||||
|
const Text('لا توجد بيانات دعم متاحة.', style: TextStyle(color: Color(0xFF94A3B8)))
|
||||||
|
else ...needingSupport.map((ns) => Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
|
padding: const EdgeInsets.all(14),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF0F172A),
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
border: Border.all(
|
||||||
|
color: const Color(0xFFEF4444).withOpacity(0.3)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const CircleAvatar(
|
||||||
|
radius: 20,
|
||||||
|
backgroundColor: Color(0xFF7F1D1D),
|
||||||
|
child: Icon(CupertinoIcons.exclamationmark_triangle_fill,
|
||||||
|
color: Color(0xFFF87171), size: 18),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
ns['name'] ?? '',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Colors.white),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
'${ns['school']} · ${ns['issue']}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12, color: Color(0xFFF87171)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${ns['score']}%',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Color(0xFFF87171)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// TAB 3: Kingdom Exam Integrity & Statistical Fraud Detection
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
Widget _buildExamIntegrityTab(List<ExamAnomalyAlert> anomalies) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(18),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
// Live Exam Integrity Header
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(18),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: const LinearGradient(
|
||||||
|
colors: [Color(0xFF1E293B), Color(0xFF0F172A)],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border:
|
||||||
|
Border.all(color: const Color(0xFF0284C7).withOpacity(0.5)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Row(
|
||||||
|
children: [
|
||||||
|
Icon(CupertinoIcons.shield_fill,
|
||||||
|
color: Color(0xFF38BDF8), size: 20),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'غرفة عمليات نزاهة الامتحانات الموحدة',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color: Colors.white),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text(
|
||||||
|
'تظهر هنا فقط الشذوذات التي سجلها الخادم من جلسات امتحان فعلية ضمن نطاق الحساب.',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.5, color: Color(0xFF94A3B8), height: 1.5),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
const Text(
|
||||||
|
'تنبيهات الشذوذ الإحصائي وشبهات التواطؤ:',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
if (anomalies.isEmpty)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF0F172A),
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
border: Border.all(color: const Color(0xFF1E293B)),
|
||||||
|
),
|
||||||
|
child: const Center(child: Text('لا توجد سجلات شذوذ متاحة من الخادم.', style: TextStyle(color: Color(0xFF94A3B8), fontSize: 13.5))),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
...anomalies.map((anm) => Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF1E1B2E),
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
border: Border.all(
|
||||||
|
color: const Color(0xFFEF4444).withOpacity(0.5)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
CupertinoIcons
|
||||||
|
.exclamationmark_triangle_fill,
|
||||||
|
color: Color(0xFFEF4444),
|
||||||
|
size: 18),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
anm.schoolName,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Colors.white),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
anm.timestamp,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 11.5, color: Color(0xFF94A3B8)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
anm.description,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: Color(0xFFFCA5A5),
|
||||||
|
height: 1.4),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: null,
|
||||||
|
icon: const Icon(CupertinoIcons.play_circle_fill,
|
||||||
|
size: 16),
|
||||||
|
label: const Text(
|
||||||
|
'عرض العينة غير متاح من هذه الشاشة',
|
||||||
|
style: TextStyle(fontSize: 12.5),
|
||||||
|
),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFF0284C7),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 10, horizontal: 14),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _kpiCell(
|
||||||
|
String title, String value, IconData icon, Color accentColor) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: accentColor.withOpacity(0.15),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(icon, color: accentColor, size: 20),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(fontSize: 11.5, color: Color(0xFF94A3B8)),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color: Colors.white),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _statusColor(String status) {
|
||||||
|
switch (status) {
|
||||||
|
case 'excellent':
|
||||||
|
return const Color(0xFF10B981);
|
||||||
|
case 'warning_under_quota':
|
||||||
|
return const Color(0xFFEF4444);
|
||||||
|
case 'good':
|
||||||
|
default:
|
||||||
|
return const Color(0xFF38BDF8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
|
||||||
|
import '../../services/directorate_api_service.dart';
|
||||||
|
|
||||||
|
/// School dashboard deliberately renders only server-provided records.
|
||||||
|
/// Uploads, exam dispatch, parent messaging, and roster imports stay unavailable
|
||||||
|
/// here until each action has an auditable production workflow.
|
||||||
|
class SchoolPrincipalScreen extends StatefulWidget {
|
||||||
|
const SchoolPrincipalScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SchoolPrincipalScreen> createState() => _SchoolPrincipalScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SchoolPrincipalScreenState extends State<SchoolPrincipalScreen> {
|
||||||
|
Future<Map<String, dynamic>>? _dashboard;
|
||||||
|
bool _busy = false;
|
||||||
|
String? _operationMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reload() => setState(() => _dashboard = DirectorateApiService.fetchSchoolDashboard());
|
||||||
|
|
||||||
|
Future<PlatformFile?> _pickVideo() async {
|
||||||
|
final picked = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: const ['mp4', 'mov', 'webm'], withData: true);
|
||||||
|
return picked?.files.isNotEmpty == true ? picked!.files.single : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _run(String label, Future<String> Function() operation) async {
|
||||||
|
setState(() { _busy = true; _operationMessage = null; });
|
||||||
|
try {
|
||||||
|
final result = await operation();
|
||||||
|
if (mounted) setState(() => _operationMessage = result);
|
||||||
|
_reload();
|
||||||
|
} catch (error) {
|
||||||
|
if (mounted) setState(() => _operationMessage = '$label تعذر: $error');
|
||||||
|
} finally { if (mounted) setState(() => _busy = false); }
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _recordLesson(int schoolId, List<Map<String, dynamic>> teachers) async {
|
||||||
|
if (teachers.isEmpty) { setState(() => _operationMessage = 'لا يوجد معلمون متاحون من الخادم.'); return; }
|
||||||
|
final title = TextEditingController();
|
||||||
|
var selected = teachers.first;
|
||||||
|
final confirmed = await showDialog<bool>(context: context, builder: (dialogContext) => StatefulBuilder(builder: (_, setDialogState) => AlertDialog(
|
||||||
|
title: const Text('رفع حصة مرصودة'),
|
||||||
|
content: Column(mainAxisSize: MainAxisSize.min, children: [DropdownButton<Map<String, dynamic>>(value: selected, isExpanded: true, items: teachers.map((teacher) => DropdownMenuItem(value: teacher, child: Text(teacher['name']?.toString() ?? 'معلم'))).toList(), onChanged: (value) { if (value != null) setDialogState(() => selected = value); }), TextField(controller: title, decoration: const InputDecoration(labelText: 'عنوان الحصة'))]),
|
||||||
|
actions: [TextButton(onPressed: () => Navigator.pop(dialogContext, false), child: const Text('إلغاء')), FilledButton(onPressed: () => Navigator.pop(dialogContext, true), child: const Text('اختيار ملف'))],
|
||||||
|
)));
|
||||||
|
if (confirmed != true || title.text.trim().isEmpty) return;
|
||||||
|
final file = await _pickVideo(); if (file == null) return;
|
||||||
|
await _run('رفع الحصة', () async {
|
||||||
|
final result = await DirectorateApiService.recordLesson(schoolId: schoolId, teacherId: (selected['id'] as num).toInt(), subject: selected['subject']?.toString() ?? '', gradeLevel: selected['grade']?.toString() ?? 'grade_10', lessonTitle: title.text.trim(), durationMinutes: 0, filePath: file.path, fileBytes: file.bytes, fileName: file.name);
|
||||||
|
return 'تم تسجيل الحصة بحالة ${result.status}.';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _uploadPanorama(int sessionId) async {
|
||||||
|
final file = await _pickVideo(); if (file == null) return;
|
||||||
|
await _run('رفع العينة', () async { await DirectorateApiService.uploadPanoramicSample(sessionId: sessionId, filePath: file.path, fileBytes: file.bytes, fileName: file.name); return 'تم حفظ العينة وربطها بالجلسة.'; });
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _importRoster(int schoolId) async {
|
||||||
|
final picked = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: const ['json'], withData: true);
|
||||||
|
final file = picked?.files.isNotEmpty == true ? picked!.files.single : null;
|
||||||
|
if (file?.bytes == null) return;
|
||||||
|
final decoded = json.decode(utf8.decode(file!.bytes!));
|
||||||
|
if (decoded is! List) throw StateError('ملف الكشف يجب أن يحتوي قائمة JSON من السجلات.');
|
||||||
|
final records = decoded.whereType<Map>().map((row) => Map<String, dynamic>.from(row)).toList();
|
||||||
|
await _run('استيراد الكشف', () async { final result = await DirectorateApiService.importSchoolRoster(schoolId: schoolId, records: records); return 'استُورد ${result['imported_count'] ?? 0} سجل؛ الفاشل ${result['failed_count'] ?? 0}.'; });
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Scaffold(
|
||||||
|
backgroundColor: const Color(0xFF070B12),
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: const Color(0xFF0E1626),
|
||||||
|
title: const Text('لوحة المدرسة'),
|
||||||
|
actions: [IconButton(onPressed: _reload, icon: const Icon(CupertinoIcons.refresh))],
|
||||||
|
),
|
||||||
|
body: FutureBuilder<Map<String, dynamic>>(
|
||||||
|
future: _dashboard,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState != ConnectionState.done) return const Center(child: CupertinoActivityIndicator());
|
||||||
|
if (snapshot.hasError) return Center(child: Padding(padding: const EdgeInsets.all(24), child: Text('تعذر تحميل بيانات المدرسة: ${snapshot.error}', textAlign: TextAlign.center, style: const TextStyle(color: Colors.white70))));
|
||||||
|
final data = snapshot.data ?? const <String, dynamic>{};
|
||||||
|
final school = data['school'] is Map ? Map<String, dynamic>.from(data['school'] as Map) : const <String, dynamic>{};
|
||||||
|
final teachers = data['teachers'] is List ? (data['teachers'] as List).whereType<Map>().map((item) => Map<String, dynamic>.from(item)).toList() : const <Map<String, dynamic>>[];
|
||||||
|
final exams = data['active_exams'] is List ? (data['active_exams'] as List).whereType<Map>().map((item) => Map<String, dynamic>.from(item)).toList() : const <Map<String, dynamic>>[];
|
||||||
|
return RefreshIndicator(onRefresh: () async => _reload(), child: ListView(padding: const EdgeInsets.all(16), children: [
|
||||||
|
Text(school['name']?.toString() ?? 'مدرسة غير محددة', style: const TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text('${school['governorate'] ?? 'الموقع غير متاح'} • الطلبة: ${school['student_count'] ?? 'غير متاح'} • المعلمون: ${school['teacher_count'] ?? 'غير متاح'}', style: const TextStyle(color: Colors.white60)),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
_operations(school, teachers, exams),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
const Text('المعلمون', style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (teachers.isEmpty) const Text('لا توجد بيانات معلمين متاحة من الخادم.', style: TextStyle(color: Colors.white60)) else ...teachers.map(_teacher),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
const Text('الامتحانات الفعالة', style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (exams.isEmpty) const Text('لا توجد امتحانات فعالة في البيانات المستلمة.', style: TextStyle(color: Colors.white60)) else ...exams.map(_exam),
|
||||||
|
]));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _notice(String title, String body) => Container(padding: const EdgeInsets.all(14), decoration: BoxDecoration(color: const Color(0xFF4338CA).withOpacity(.2), borderRadius: BorderRadius.circular(12)), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text(title, style: const TextStyle(color: Color(0xFFC4B5FD), fontWeight: FontWeight.bold)), const SizedBox(height: 5), Text(body, style: const TextStyle(color: Colors.white70, height: 1.4))]));
|
||||||
|
Widget _operations(Map<String, dynamic> school, List<Map<String, dynamic>> teachers, List<Map<String, dynamic>> exams) {
|
||||||
|
final schoolId = (school['id'] as num?)?.toInt();
|
||||||
|
if (schoolId == null) return _notice('الإجراءات غير متاحة', 'لم يعِد الخادم هوية مدرسة صالحة.');
|
||||||
|
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||||
|
_notice('إجراءات خادمية فعلية', 'كل زر يرسل طلباً حقيقياً للخادم. لا توجد موافقات أو نتائج محلية.'),
|
||||||
|
if (_operationMessage != null) Padding(padding: const EdgeInsets.only(top: 8), child: Text(_operationMessage!, style: const TextStyle(color: Color(0xFFC4B5FD)))),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Wrap(spacing: 8, runSpacing: 8, children: [
|
||||||
|
OutlinedButton(onPressed: _busy ? null : () => _recordLesson(schoolId, teachers), child: const Text('رفع حصة مرصودة')),
|
||||||
|
OutlinedButton(onPressed: _busy ? null : () => _run('إرسال التقارير', () async { final result = await DirectorateApiService.dispatchParentReports(schoolId: schoolId); return 'أُرسل ${result['total_dispatched'] ?? 0} تقرير؛ فشل ${result['failed_count'] ?? 0}.'; }), child: const Text('إرسال تقارير الأولياء')),
|
||||||
|
OutlinedButton(onPressed: _busy ? null : () => _importRoster(schoolId), child: const Text('استيراد كشف JSON')),
|
||||||
|
]),
|
||||||
|
if (exams.isNotEmpty) Padding(padding: const EdgeInsets.only(top: 8), child: Wrap(spacing: 8, runSpacing: 8, children: exams.where((exam) => exam['id'] is num).map((exam) => OutlinedButton(onPressed: _busy ? null : () => _run('تفعيل الامتحان', () async { await DirectorateApiService.pushExamToLab(examId: (exam['id'] as num).toInt(), schoolId: schoolId); return 'تم تفعيل الامتحان للمختبر المسجل.'; }), child: Text('تفعيل: ${exam['title'] ?? 'امتحان'}'))).toList())),
|
||||||
|
if (exams.any((exam) => exam['session_id'] is num)) Padding(padding: const EdgeInsets.only(top: 8), child: OutlinedButton(onPressed: _busy ? null : () => _uploadPanorama((exams.firstWhere((exam) => exam['session_id'] is num)['session_id'] as num).toInt()), child: const Text('رفع عينة بانورامية'))),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
Widget _teacher(Map<String, dynamic> item) => Card(color: const Color(0xFF0F172A), child: ListTile(title: Text(item['name']?.toString() ?? 'معلم بدون اسم', style: const TextStyle(color: Colors.white)), subtitle: Text('${item['subject'] ?? 'المبحث غير متاح'} • ${item['quota_status'] ?? 'الحالة غير متاحة'}', style: const TextStyle(color: Colors.white60))));
|
||||||
|
Widget _exam(Map<String, dynamic> item) => Card(color: const Color(0xFF0F172A), child: ListTile(title: Text(item['title']?.toString() ?? 'امتحان بدون عنوان', style: const TextStyle(color: Colors.white)), subtitle: Text('${item['subject'] ?? 'المبحث غير متاح'} • ${item['status'] ?? 'الحالة غير متاحة'}', style: const TextStyle(color: Colors.white60))));
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io' show Platform;
|
||||||
|
import 'dart:math';
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
/// Enterprise Real Device Fingerprint & Hardware Identity Service (Directorate Admin)
|
||||||
|
class DeviceFingerprintService {
|
||||||
|
static final DeviceInfoPlugin _deviceInfoPlugin = DeviceInfoPlugin();
|
||||||
|
static String? _cachedFingerprint;
|
||||||
|
static Map<String, String>? _cachedMetadata;
|
||||||
|
|
||||||
|
static const String _storageKeyDeviceId = 'saqel_admin_device_persistent_id';
|
||||||
|
|
||||||
|
/// Returns the deterministic, physical/hardware-based device fingerprint.
|
||||||
|
/// Format: saqel_{platform}_{sha256_hash_first_32_chars}
|
||||||
|
static Future<String> getFingerprint() async {
|
||||||
|
if (_cachedFingerprint != null && _cachedFingerprint!.isNotEmpty) {
|
||||||
|
return _cachedFingerprint!;
|
||||||
|
}
|
||||||
|
|
||||||
|
final metadata = await getDeviceMetadata();
|
||||||
|
_cachedFingerprint = metadata['device_fingerprint']!;
|
||||||
|
return _cachedFingerprint!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts comprehensive physical/hardware metadata from the running platform.
|
||||||
|
static Future<Map<String, String>> getDeviceMetadata() async {
|
||||||
|
if (_cachedMetadata != null) {
|
||||||
|
return _cachedMetadata!;
|
||||||
|
}
|
||||||
|
|
||||||
|
String platform = 'unknown';
|
||||||
|
String hardwareId = '';
|
||||||
|
String model = '';
|
||||||
|
String brand = '';
|
||||||
|
String osVersion = '';
|
||||||
|
String deviceName = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (kIsWeb) {
|
||||||
|
platform = 'web';
|
||||||
|
final web = await _deviceInfoPlugin.webBrowserInfo;
|
||||||
|
hardwareId = '${web.vendor}_${web.platform}_${web.hardwareConcurrency}';
|
||||||
|
model = web.browserName.name;
|
||||||
|
brand = web.vendor ?? 'Web';
|
||||||
|
osVersion = web.userAgent ?? 'UnknownWeb';
|
||||||
|
deviceName = 'Web Browser';
|
||||||
|
} else if (Platform.isAndroid) {
|
||||||
|
platform = 'android';
|
||||||
|
final android = await _deviceInfoPlugin.androidInfo;
|
||||||
|
hardwareId = android.id.isNotEmpty ? android.id : android.fingerprint;
|
||||||
|
model = android.model;
|
||||||
|
brand = android.brand;
|
||||||
|
osVersion = 'Android ${android.version.release} (SDK ${android.version.sdkInt})';
|
||||||
|
deviceName = '${android.brand} ${android.model}';
|
||||||
|
} else if (Platform.isIOS) {
|
||||||
|
platform = 'ios';
|
||||||
|
final ios = await _deviceInfoPlugin.iosInfo;
|
||||||
|
hardwareId = ios.identifierForVendor ?? '';
|
||||||
|
model = ios.model;
|
||||||
|
brand = 'Apple';
|
||||||
|
osVersion = 'iOS ${ios.systemVersion}';
|
||||||
|
deviceName = ios.name;
|
||||||
|
} else if (Platform.isMacOS) {
|
||||||
|
platform = 'macos';
|
||||||
|
final mac = await _deviceInfoPlugin.macOsInfo;
|
||||||
|
hardwareId = mac.systemGUID ?? '';
|
||||||
|
model = mac.model;
|
||||||
|
brand = 'Apple';
|
||||||
|
osVersion = 'macOS ${mac.osRelease}';
|
||||||
|
deviceName = mac.computerName;
|
||||||
|
} else if (Platform.isWindows) {
|
||||||
|
platform = 'windows';
|
||||||
|
final win = await _deviceInfoPlugin.windowsInfo;
|
||||||
|
hardwareId = win.deviceId;
|
||||||
|
model = win.productName;
|
||||||
|
brand = 'Microsoft';
|
||||||
|
osVersion = 'Windows ${win.majorVersion}.${win.minorVersion}.${win.buildNumber}';
|
||||||
|
deviceName = win.computerName;
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
platform = 'linux';
|
||||||
|
final linux = await _deviceInfoPlugin.linuxInfo;
|
||||||
|
hardwareId = linux.machineId ?? '';
|
||||||
|
model = linux.name;
|
||||||
|
brand = linux.id;
|
||||||
|
osVersion = linux.versionId ?? '';
|
||||||
|
deviceName = linux.prettyName;
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
platform = kIsWeb
|
||||||
|
? 'web'
|
||||||
|
: (Platform.isAndroid
|
||||||
|
? 'android'
|
||||||
|
: (Platform.isIOS ? 'ios' : 'desktop'));
|
||||||
|
}
|
||||||
|
|
||||||
|
final persistentId = await _getOrCreatePersistentInstallationId();
|
||||||
|
|
||||||
|
final rawSeed =
|
||||||
|
'saqel_hw:$platform:$hardwareId:$brand:$model:$osVersion:$persistentId';
|
||||||
|
final hash = sha256.convert(utf8.encode(rawSeed)).toString();
|
||||||
|
final fingerprint = 'saqel_${platform}_${hash.substring(0, 32)}';
|
||||||
|
|
||||||
|
_cachedMetadata = {
|
||||||
|
'device_fingerprint': fingerprint,
|
||||||
|
'platform': platform,
|
||||||
|
'hardware_id': hardwareId,
|
||||||
|
'model': model,
|
||||||
|
'brand': brand,
|
||||||
|
'os_version': osVersion,
|
||||||
|
'device_name': deviceName,
|
||||||
|
'persistent_install_id': persistentId,
|
||||||
|
};
|
||||||
|
|
||||||
|
return _cachedMetadata!;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<String> _getOrCreatePersistentInstallationId() async {
|
||||||
|
try {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
var id = prefs.getString(_storageKeyDeviceId);
|
||||||
|
if (id != null && id.isNotEmpty) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
final rand = Random.secure();
|
||||||
|
final bytes = List<int>.generate(16, (_) => rand.nextInt(256));
|
||||||
|
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
||||||
|
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
||||||
|
final hex = bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||||
|
id =
|
||||||
|
'${hex.substring(0, 8)}-${hex.substring(8, 12)}-${hex.substring(12, 16)}-${hex.substring(16, 20)}-${hex.substring(20)}';
|
||||||
|
|
||||||
|
await prefs.setString(_storageKeyDeviceId, id);
|
||||||
|
return id;
|
||||||
|
} catch (_) {
|
||||||
|
return 'fallback-${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void resetCache() {
|
||||||
|
_cachedFingerprint = null;
|
||||||
|
_cachedMetadata = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
import '../core/services/storage_service.dart';
|
||||||
|
import '../core/utils/app_logger.dart';
|
||||||
|
import '../data/models/directorate_models.dart';
|
||||||
|
import 'device_fingerprint_service.dart';
|
||||||
|
|
||||||
|
/// Authenticated API client for principals, supervisors, and directorates.
|
||||||
|
/// Operational data always comes from the server; connection errors stay errors.
|
||||||
|
class DirectorateApiService {
|
||||||
|
static const String baseUrl = String.fromEnvironment(
|
||||||
|
'SAQEL_API_BASE_URL',
|
||||||
|
defaultValue: 'https://saqel.intaleqapp.com',
|
||||||
|
);
|
||||||
|
|
||||||
|
static final StorageService _storage = StorageService();
|
||||||
|
|
||||||
|
static Future<Map<String, String>> _headers() async {
|
||||||
|
final token = await _storage.getToken();
|
||||||
|
if (token == null || token.isEmpty) {
|
||||||
|
throw StateError('جلسة الإدارة غير متوفرة. يرجى تسجيل الدخول.');
|
||||||
|
}
|
||||||
|
final fingerprint = await DeviceFingerprintService.getFingerprint();
|
||||||
|
final metadata = await DeviceFingerprintService.getDeviceMetadata();
|
||||||
|
return {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'X-Device-Fingerprint': fingerprint,
|
||||||
|
'X-Device-Platform': metadata['platform'] ?? 'unknown',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic> _decode(http.Response response) {
|
||||||
|
AppLogger.response(
|
||||||
|
method: response.request?.method ?? 'HTTP',
|
||||||
|
uri: response.request?.url ?? Uri.parse(baseUrl),
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
responseBody: response.body,
|
||||||
|
);
|
||||||
|
Map<String, dynamic> data = {};
|
||||||
|
if (response.bodyBytes.isNotEmpty) {
|
||||||
|
final decoded = json.decode(utf8.decode(response.bodyBytes));
|
||||||
|
if (decoded is Map) data = Map<String, dynamic>.from(decoded);
|
||||||
|
}
|
||||||
|
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||||
|
final error = StateError(
|
||||||
|
data['message']?.toString() ??
|
||||||
|
'فشل طلب الخادم (${response.statusCode}).',
|
||||||
|
);
|
||||||
|
AppLogger.error('Admin API request failed',
|
||||||
|
error: error, tag: 'ADMIN_API');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> requestOtp({
|
||||||
|
required String phoneNumber,
|
||||||
|
required String role,
|
||||||
|
}) async {
|
||||||
|
final response = await http
|
||||||
|
.post(
|
||||||
|
Uri.parse('$baseUrl/api/auth/otp/request'),
|
||||||
|
headers: const {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: json.encode({'phone_number': phoneNumber, 'role': role}),
|
||||||
|
)
|
||||||
|
.timeout(const Duration(seconds: 30));
|
||||||
|
_decode(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Map<String, dynamic>> verifyOtp({
|
||||||
|
required String phoneNumber,
|
||||||
|
required String otp,
|
||||||
|
required String role,
|
||||||
|
}) async {
|
||||||
|
final fingerprint = await DeviceFingerprintService.getFingerprint();
|
||||||
|
final metadata = await DeviceFingerprintService.getDeviceMetadata();
|
||||||
|
final response = await http
|
||||||
|
.post(
|
||||||
|
Uri.parse('$baseUrl/api/auth/otp/verify'),
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Device-Fingerprint': fingerprint,
|
||||||
|
'X-Device-Platform': metadata['platform'] ?? 'unknown',
|
||||||
|
},
|
||||||
|
body: json.encode({
|
||||||
|
'phone_number': phoneNumber,
|
||||||
|
'otp': otp,
|
||||||
|
'role': role,
|
||||||
|
'device_fingerprint': fingerprint,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.timeout(const Duration(seconds: 30));
|
||||||
|
final data = _decode(response);
|
||||||
|
final payload = Map<String, dynamic>.from(data['data'] as Map? ?? const {});
|
||||||
|
final token = payload['token']?.toString() ?? '';
|
||||||
|
if (token.isEmpty) throw StateError('لم يُرجع الخادم جلسة إدارية صالحة.');
|
||||||
|
await _storage.saveToken(token);
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> hasValidSession() async {
|
||||||
|
try {
|
||||||
|
final response = await http
|
||||||
|
.get(
|
||||||
|
Uri.parse('$baseUrl/api/auth/me'),
|
||||||
|
headers: await _headers(),
|
||||||
|
)
|
||||||
|
.timeout(const Duration(seconds: 15));
|
||||||
|
_decode(response);
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Map<String, dynamic>> fetchDirectorateDashboard() async {
|
||||||
|
final response = await http
|
||||||
|
.get(
|
||||||
|
Uri.parse('$baseUrl/api/directorate/dashboard'),
|
||||||
|
headers: await _headers(),
|
||||||
|
)
|
||||||
|
.timeout(const Duration(seconds: 15));
|
||||||
|
return _decode(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Map<String, dynamic>> fetchSchoolDashboard({int? schoolId}) async {
|
||||||
|
final uri = Uri.parse('$baseUrl/api/supervisor/school-dashboard').replace(
|
||||||
|
queryParameters: schoolId == null ? null : {'school_id': '$schoolId'},
|
||||||
|
);
|
||||||
|
final response = await http
|
||||||
|
.get(uri, headers: await _headers())
|
||||||
|
.timeout(const Duration(seconds: 15));
|
||||||
|
return _decode(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<RecordedLessonResult> recordLesson({
|
||||||
|
required int schoolId,
|
||||||
|
required int teacherId,
|
||||||
|
required String subject,
|
||||||
|
required String gradeLevel,
|
||||||
|
required String lessonTitle,
|
||||||
|
required int durationMinutes,
|
||||||
|
String? filePath,
|
||||||
|
List<int>? fileBytes,
|
||||||
|
required String fileName,
|
||||||
|
}) async {
|
||||||
|
final request = http.MultipartRequest(
|
||||||
|
'POST', Uri.parse('$baseUrl/api/supervisor/record-lesson'));
|
||||||
|
final headers = await _headers();
|
||||||
|
headers.remove('Content-Type');
|
||||||
|
request.headers.addAll(headers);
|
||||||
|
request.fields.addAll({
|
||||||
|
'school_id': '$schoolId',
|
||||||
|
'teacher_id': '$teacherId',
|
||||||
|
'subject': subject,
|
||||||
|
'grade_level': gradeLevel,
|
||||||
|
'lesson_title': lessonTitle,
|
||||||
|
'duration_minutes': '$durationMinutes',
|
||||||
|
});
|
||||||
|
if (fileBytes != null) {
|
||||||
|
request.files.add(
|
||||||
|
http.MultipartFile.fromBytes('video', fileBytes, filename: fileName));
|
||||||
|
} else if (filePath != null) {
|
||||||
|
request.files.add(await http.MultipartFile.fromPath('video', filePath,
|
||||||
|
filename: fileName));
|
||||||
|
} else {
|
||||||
|
throw StateError('لم يتم اختيار ملف فيديو حقيقي.');
|
||||||
|
}
|
||||||
|
final streamed = await request.send().timeout(const Duration(minutes: 10));
|
||||||
|
final response = await http.Response.fromStream(streamed);
|
||||||
|
final data = _decode(response);
|
||||||
|
return RecordedLessonResult.fromJson(
|
||||||
|
Map<String, dynamic>.from(data['data'] as Map? ?? const {}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> pushExamToLab(
|
||||||
|
{required int examId, required int schoolId}) async {
|
||||||
|
final response = await http
|
||||||
|
.post(
|
||||||
|
Uri.parse('$baseUrl/api/supervisor/exam/push-to-lab'),
|
||||||
|
headers: await _headers(),
|
||||||
|
body: json.encode({'exam_id': examId, 'school_id': schoolId}),
|
||||||
|
)
|
||||||
|
.timeout(const Duration(seconds: 30));
|
||||||
|
return _decode(response)['status'] == 'success';
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> uploadPanoramicSample({
|
||||||
|
required int sessionId,
|
||||||
|
String? filePath,
|
||||||
|
List<int>? fileBytes,
|
||||||
|
required String fileName,
|
||||||
|
}) async {
|
||||||
|
final request = http.MultipartRequest(
|
||||||
|
'POST', Uri.parse('$baseUrl/api/supervisor/exam/upload-panoramic'));
|
||||||
|
final headers = await _headers();
|
||||||
|
headers.remove('Content-Type');
|
||||||
|
request.headers.addAll(headers);
|
||||||
|
request.fields['session_id'] = '$sessionId';
|
||||||
|
if (fileBytes != null) {
|
||||||
|
request.files.add(
|
||||||
|
http.MultipartFile.fromBytes('video', fileBytes, filename: fileName));
|
||||||
|
} else if (filePath != null) {
|
||||||
|
request.files.add(await http.MultipartFile.fromPath('video', filePath,
|
||||||
|
filename: fileName));
|
||||||
|
} else {
|
||||||
|
throw StateError('لم يتم اختيار عينة فيديو حقيقية.');
|
||||||
|
}
|
||||||
|
final response = await http.Response.fromStream(
|
||||||
|
await request.send().timeout(const Duration(minutes: 10)),
|
||||||
|
);
|
||||||
|
return _decode(response)['status'] == 'success';
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Map<String, dynamic>> fetchDualForms({
|
||||||
|
String subject = 'الفيزياء',
|
||||||
|
String gradeLevel = 'الصف العاشر الأساسي',
|
||||||
|
}) async {
|
||||||
|
final uri = Uri.parse('$baseUrl/api/unified-exams/dual-forms').replace(
|
||||||
|
queryParameters: {'subject': subject, 'grade_level': gradeLevel},
|
||||||
|
);
|
||||||
|
final response = await http
|
||||||
|
.get(uri, headers: await _headers())
|
||||||
|
.timeout(const Duration(seconds: 30));
|
||||||
|
final data = _decode(response);
|
||||||
|
return Map<String, dynamic>.from(data['data'] as Map? ?? const {});
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Map<String, dynamic>> evaluateExamSessionIntegrity({
|
||||||
|
List<Map<String, dynamic>> submissions = const [],
|
||||||
|
}) async {
|
||||||
|
final response = await http
|
||||||
|
.post(
|
||||||
|
Uri.parse('$baseUrl/api/unified-exams/evaluate-integrity'),
|
||||||
|
headers: await _headers(),
|
||||||
|
body: json.encode({'submissions': submissions}),
|
||||||
|
)
|
||||||
|
.timeout(const Duration(seconds: 30));
|
||||||
|
final data = _decode(response);
|
||||||
|
return Map<String, dynamic>.from(data['data'] as Map? ?? const {});
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Map<String, dynamic>> dispatchParentReports({required int schoolId}) async {
|
||||||
|
final response = await http
|
||||||
|
.post(
|
||||||
|
Uri.parse('$baseUrl/api/parent-reports/dispatch'),
|
||||||
|
headers: await _headers(),
|
||||||
|
body: json.encode({'school_id': schoolId}),
|
||||||
|
)
|
||||||
|
.timeout(const Duration(minutes: 2));
|
||||||
|
return _decode(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Map<String, dynamic>> importSchoolRoster({
|
||||||
|
required int schoolId,
|
||||||
|
required List<Map<String, dynamic>> records,
|
||||||
|
}) async {
|
||||||
|
final response = await http
|
||||||
|
.post(
|
||||||
|
Uri.parse('$baseUrl/api/school-roster/import'),
|
||||||
|
headers: await _headers(),
|
||||||
|
body: json.encode({'school_id': schoolId, 'records': records}),
|
||||||
|
)
|
||||||
|
.timeout(const Duration(minutes: 2));
|
||||||
|
return _decode(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
flutter/ephemeral
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
# Project-level configuration.
|
||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
project(runner LANGUAGES CXX)
|
||||||
|
|
||||||
|
# The name of the executable created for the application. Change this to change
|
||||||
|
# the on-disk name of your application.
|
||||||
|
set(BINARY_NAME "admin_app")
|
||||||
|
# The unique GTK application identifier for this application. See:
|
||||||
|
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||||
|
set(APPLICATION_ID "com.saqel.admin_app")
|
||||||
|
|
||||||
|
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||||
|
# versions of CMake.
|
||||||
|
cmake_policy(SET CMP0063 NEW)
|
||||||
|
|
||||||
|
# Load bundled libraries from the lib/ directory relative to the binary.
|
||||||
|
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||||
|
|
||||||
|
# Root filesystem for cross-building.
|
||||||
|
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
|
||||||
|
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
|
||||||
|
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Define build configuration options.
|
||||||
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||||
|
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||||
|
STRING "Flutter build mode" FORCE)
|
||||||
|
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||||
|
"Debug" "Profile" "Release")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Compilation settings that should be applied to most targets.
|
||||||
|
#
|
||||||
|
# Be cautious about adding new options here, as plugins use this function by
|
||||||
|
# default. In most cases, you should add new options to specific targets instead
|
||||||
|
# of modifying this function.
|
||||||
|
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||||
|
target_compile_features(${TARGET} PUBLIC cxx_std_14)
|
||||||
|
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
|
||||||
|
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
|
||||||
|
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Flutter library and tool build rules.
|
||||||
|
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||||
|
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||||
|
|
||||||
|
# System-level dependencies.
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||||
|
|
||||||
|
# Application build; see runner/CMakeLists.txt.
|
||||||
|
add_subdirectory("runner")
|
||||||
|
|
||||||
|
# Run the Flutter tool portions of the build. This must not be removed.
|
||||||
|
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||||
|
|
||||||
|
# Only the install-generated bundle's copy of the executable will launch
|
||||||
|
# correctly, since the resources must in the right relative locations. To avoid
|
||||||
|
# people trying to run the unbundled copy, put it in a subdirectory instead of
|
||||||
|
# the default top-level location.
|
||||||
|
set_target_properties(${BINARY_NAME}
|
||||||
|
PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Generated plugin build rules, which manage building the plugins and adding
|
||||||
|
# them to the application.
|
||||||
|
include(flutter/generated_plugins.cmake)
|
||||||
|
|
||||||
|
|
||||||
|
# === Installation ===
|
||||||
|
# By default, "installing" just makes a relocatable bundle in the build
|
||||||
|
# directory.
|
||||||
|
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
|
||||||
|
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||||
|
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Start with a clean build bundle directory every time.
|
||||||
|
install(CODE "
|
||||||
|
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
|
||||||
|
" COMPONENT Runtime)
|
||||||
|
|
||||||
|
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||||
|
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
|
||||||
|
|
||||||
|
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
||||||
|
install(FILES "${bundled_library}"
|
||||||
|
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
endforeach(bundled_library)
|
||||||
|
|
||||||
|
# Copy the native assets provided by the build.dart from all packages.
|
||||||
|
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
|
||||||
|
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
|
||||||
|
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||||
|
# from a previous install.
|
||||||
|
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||||
|
install(CODE "
|
||||||
|
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||||
|
" COMPONENT Runtime)
|
||||||
|
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||||
|
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||||
|
|
||||||
|
# Install the AOT library on non-Debug builds only.
|
||||||
|
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||||
|
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
endif()
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# This file controls Flutter-level build steps. It should not be edited.
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||||
|
|
||||||
|
# Configuration provided via flutter tool.
|
||||||
|
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||||
|
|
||||||
|
# TODO: Move the rest of this into files in ephemeral. See
|
||||||
|
# https://github.com/flutter/flutter/issues/57146.
|
||||||
|
|
||||||
|
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
|
||||||
|
# which isn't available in 3.10.
|
||||||
|
function(list_prepend LIST_NAME PREFIX)
|
||||||
|
set(NEW_LIST "")
|
||||||
|
foreach(element ${${LIST_NAME}})
|
||||||
|
list(APPEND NEW_LIST "${PREFIX}${element}")
|
||||||
|
endforeach(element)
|
||||||
|
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# === Flutter Library ===
|
||||||
|
# System-level dependencies.
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||||
|
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||||
|
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||||
|
|
||||||
|
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
||||||
|
|
||||||
|
# Published to parent scope for install step.
|
||||||
|
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||||
|
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||||
|
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||||
|
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||||
|
"fl_basic_message_channel.h"
|
||||||
|
"fl_binary_codec.h"
|
||||||
|
"fl_binary_messenger.h"
|
||||||
|
"fl_dart_project.h"
|
||||||
|
"fl_engine.h"
|
||||||
|
"fl_json_message_codec.h"
|
||||||
|
"fl_json_method_codec.h"
|
||||||
|
"fl_message_codec.h"
|
||||||
|
"fl_method_call.h"
|
||||||
|
"fl_method_channel.h"
|
||||||
|
"fl_method_codec.h"
|
||||||
|
"fl_method_response.h"
|
||||||
|
"fl_plugin_registrar.h"
|
||||||
|
"fl_plugin_registry.h"
|
||||||
|
"fl_standard_message_codec.h"
|
||||||
|
"fl_standard_method_codec.h"
|
||||||
|
"fl_string_codec.h"
|
||||||
|
"fl_value.h"
|
||||||
|
"fl_view.h"
|
||||||
|
"flutter_linux.h"
|
||||||
|
)
|
||||||
|
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
||||||
|
add_library(flutter INTERFACE)
|
||||||
|
target_include_directories(flutter INTERFACE
|
||||||
|
"${EPHEMERAL_DIR}"
|
||||||
|
)
|
||||||
|
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
||||||
|
target_link_libraries(flutter INTERFACE
|
||||||
|
PkgConfig::GTK
|
||||||
|
PkgConfig::GLIB
|
||||||
|
PkgConfig::GIO
|
||||||
|
)
|
||||||
|
add_dependencies(flutter flutter_assemble)
|
||||||
|
|
||||||
|
# === Flutter tool backend ===
|
||||||
|
# _phony_ is a non-existent file to force this command to run every time,
|
||||||
|
# since currently there's no way to get a full input/output list from the
|
||||||
|
# flutter tool.
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E env
|
||||||
|
${FLUTTER_TOOL_ENVIRONMENT}
|
||||||
|
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
||||||
|
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
add_custom_target(flutter_assemble DEPENDS
|
||||||
|
"${FLUTTER_LIBRARY}"
|
||||||
|
${FLUTTER_LIBRARY_HEADERS}
|
||||||
|
)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||||
|
|
||||||
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
|
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||||
|
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||||
|
#define GENERATED_PLUGIN_REGISTRANT_
|
||||||
|
|
||||||
|
#include <flutter_linux/flutter_linux.h>
|
||||||
|
|
||||||
|
// Registers Flutter plugins.
|
||||||
|
void fl_register_plugins(FlPluginRegistry* registry);
|
||||||
|
|
||||||
|
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#
|
||||||
|
# Generated file, do not edit.
|
||||||
|
#
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
flutter_secure_storage_linux
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
jni
|
||||||
|
)
|
||||||
|
|
||||||
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|
||||||
|
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||||
|
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||||
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||||
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||||
|
endforeach(plugin)
|
||||||
|
|
||||||
|
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||||
|
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
|
||||||
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||||
|
endforeach(ffi_plugin)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
project(runner LANGUAGES CXX)
|
||||||
|
|
||||||
|
# Define the application target. To change its name, change BINARY_NAME in the
|
||||||
|
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
|
||||||
|
# work.
|
||||||
|
#
|
||||||
|
# Any new source files that you add to the application should be added here.
|
||||||
|
add_executable(${BINARY_NAME}
|
||||||
|
"main.cc"
|
||||||
|
"my_application.cc"
|
||||||
|
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply the standard set of build settings. This can be removed for applications
|
||||||
|
# that need different build settings.
|
||||||
|
apply_standard_settings(${BINARY_NAME})
|
||||||
|
|
||||||
|
# Add preprocessor definitions for the application ID.
|
||||||
|
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||||
|
|
||||||
|
# Add dependency libraries. Add any application-specific dependencies here.
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||||
|
|
||||||
|
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#include "my_application.h"
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
g_autoptr(MyApplication) app = my_application_new();
|
||||||
|
return g_application_run(G_APPLICATION(app), argc, argv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
#include "my_application.h"
|
||||||
|
|
||||||
|
#include <flutter_linux/flutter_linux.h>
|
||||||
|
#ifdef GDK_WINDOWING_X11
|
||||||
|
#include <gdk/gdkx.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "flutter/generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
struct _MyApplication {
|
||||||
|
GtkApplication parent_instance;
|
||||||
|
char** dart_entrypoint_arguments;
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||||
|
|
||||||
|
// Called when first Flutter frame received.
|
||||||
|
static void first_frame_cb(MyApplication* self, FlView* view) {
|
||||||
|
gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements GApplication::activate.
|
||||||
|
static void my_application_activate(GApplication* application) {
|
||||||
|
MyApplication* self = MY_APPLICATION(application);
|
||||||
|
GtkWindow* window =
|
||||||
|
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||||
|
|
||||||
|
// Use a header bar when running in GNOME as this is the common style used
|
||||||
|
// by applications and is the setup most users will be using (e.g. Ubuntu
|
||||||
|
// desktop).
|
||||||
|
// If running on X and not using GNOME then just use a traditional title bar
|
||||||
|
// in case the window manager does more exotic layout, e.g. tiling.
|
||||||
|
// If running on Wayland assume the header bar will work (may need changing
|
||||||
|
// if future cases occur).
|
||||||
|
gboolean use_header_bar = TRUE;
|
||||||
|
#ifdef GDK_WINDOWING_X11
|
||||||
|
GdkScreen* screen = gtk_window_get_screen(window);
|
||||||
|
if (GDK_IS_X11_SCREEN(screen)) {
|
||||||
|
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||||
|
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||||
|
use_header_bar = FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (use_header_bar) {
|
||||||
|
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||||
|
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||||
|
gtk_header_bar_set_title(header_bar, "admin_app");
|
||||||
|
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||||
|
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||||
|
} else {
|
||||||
|
gtk_window_set_title(window, "admin_app");
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_window_set_default_size(window, 1280, 720);
|
||||||
|
|
||||||
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
|
fl_dart_project_set_dart_entrypoint_arguments(
|
||||||
|
project, self->dart_entrypoint_arguments);
|
||||||
|
|
||||||
|
FlView* view = fl_view_new(project);
|
||||||
|
GdkRGBA background_color;
|
||||||
|
// Background defaults to black, override it here if necessary, e.g. #00000000
|
||||||
|
// for transparent.
|
||||||
|
gdk_rgba_parse(&background_color, "#000000");
|
||||||
|
fl_view_set_background_color(view, &background_color);
|
||||||
|
gtk_widget_show(GTK_WIDGET(view));
|
||||||
|
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||||
|
|
||||||
|
// Show the window when Flutter renders.
|
||||||
|
// Requires the view to be realized so we can start rendering.
|
||||||
|
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb),
|
||||||
|
self);
|
||||||
|
gtk_widget_realize(GTK_WIDGET(view));
|
||||||
|
|
||||||
|
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||||
|
|
||||||
|
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements GApplication::local_command_line.
|
||||||
|
static gboolean my_application_local_command_line(GApplication* application,
|
||||||
|
gchar*** arguments,
|
||||||
|
int* exit_status) {
|
||||||
|
MyApplication* self = MY_APPLICATION(application);
|
||||||
|
// Strip out the first argument as it is the binary name.
|
||||||
|
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
||||||
|
|
||||||
|
g_autoptr(GError) error = nullptr;
|
||||||
|
if (!g_application_register(application, nullptr, &error)) {
|
||||||
|
g_warning("Failed to register: %s", error->message);
|
||||||
|
*exit_status = 1;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_application_activate(application);
|
||||||
|
*exit_status = 0;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements GApplication::startup.
|
||||||
|
static void my_application_startup(GApplication* application) {
|
||||||
|
// MyApplication* self = MY_APPLICATION(object);
|
||||||
|
|
||||||
|
// Perform any actions required at application startup.
|
||||||
|
|
||||||
|
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements GApplication::shutdown.
|
||||||
|
static void my_application_shutdown(GApplication* application) {
|
||||||
|
// MyApplication* self = MY_APPLICATION(object);
|
||||||
|
|
||||||
|
// Perform any actions required at application shutdown.
|
||||||
|
|
||||||
|
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements GObject::dispose.
|
||||||
|
static void my_application_dispose(GObject* object) {
|
||||||
|
MyApplication* self = MY_APPLICATION(object);
|
||||||
|
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
||||||
|
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void my_application_class_init(MyApplicationClass* klass) {
|
||||||
|
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||||
|
G_APPLICATION_CLASS(klass)->local_command_line =
|
||||||
|
my_application_local_command_line;
|
||||||
|
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
|
||||||
|
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
|
||||||
|
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void my_application_init(MyApplication* self) {}
|
||||||
|
|
||||||
|
MyApplication* my_application_new() {
|
||||||
|
// Set the program name to the application ID, which helps various systems
|
||||||
|
// like GTK and desktop environments map this running application to its
|
||||||
|
// corresponding .desktop file. This ensures better integration by allowing
|
||||||
|
// the application to be recognized beyond its binary name.
|
||||||
|
g_set_prgname(APPLICATION_ID);
|
||||||
|
|
||||||
|
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||||
|
"application-id", APPLICATION_ID, "flags",
|
||||||
|
G_APPLICATION_NON_UNIQUE, nullptr));
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef FLUTTER_MY_APPLICATION_H_
|
||||||
|
#define FLUTTER_MY_APPLICATION_H_
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE(MyApplication,
|
||||||
|
my_application,
|
||||||
|
MY,
|
||||||
|
APPLICATION,
|
||||||
|
GtkApplication)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* my_application_new:
|
||||||
|
*
|
||||||
|
* Creates a new Flutter-based application.
|
||||||
|
*
|
||||||
|
* Returns: a new #MyApplication.
|
||||||
|
*/
|
||||||
|
MyApplication* my_application_new();
|
||||||
|
|
||||||
|
#endif // FLUTTER_MY_APPLICATION_H_
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Flutter-related
|
||||||
|
**/Flutter/ephemeral/
|
||||||
|
**/Pods/
|
||||||
|
|
||||||
|
# Xcode-related
|
||||||
|
**/dgph
|
||||||
|
**/xcuserdata/
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
import FlutterMacOS
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
import device_info_plus
|
||||||
|
import file_picker
|
||||||
|
import flutter_secure_storage_macos
|
||||||
|
import shared_preferences_foundation
|
||||||
|
|
||||||
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||||
|
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||||
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
platform :osx, '10.15'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_macos_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
|
||||||
|
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
target 'RunnerTests' do
|
||||||
|
inherit! :search_paths
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_macos_build_settings(target)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,705 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 54;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXAggregateTarget section */
|
||||||
|
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
|
||||||
|
isa = PBXAggregateTarget;
|
||||||
|
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
|
||||||
|
buildPhases = (
|
||||||
|
33CC111E2044C6BF0003C045 /* ShellScript */,
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = "Flutter Assemble";
|
||||||
|
productName = FLX;
|
||||||
|
};
|
||||||
|
/* End PBXAggregateTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
|
||||||
|
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
|
||||||
|
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
|
||||||
|
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||||
|
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||||
|
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 33CC10EC2044A3C60003C045;
|
||||||
|
remoteInfo = Runner;
|
||||||
|
};
|
||||||
|
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
|
||||||
|
remoteInfo = FLX;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
33CC110E2044A8840003C045 /* Bundle Framework */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Bundle Framework";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
|
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||||
|
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||||
|
33CC10ED2044A3C60003C045 /* admin_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "admin_app.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||||
|
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
|
||||||
|
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
|
||||||
|
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
||||||
|
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
||||||
|
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
331C80D2294CF70F00263BE5 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
33CC10EA2044A3C60003C045 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
331C80D6294CF71000263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
331C80D7294CF71000263BE5 /* RunnerTests.swift */,
|
||||||
|
);
|
||||||
|
path = RunnerTests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33BA886A226E78AF003329D5 /* Configs */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Configs;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33CC10E42044A3C60003C045 = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
33FAB671232836740065AC1E /* Runner */,
|
||||||
|
33CEB47122A05771004F2AC0 /* Flutter */,
|
||||||
|
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||||
|
33CC10EE2044A3C60003C045 /* Products */,
|
||||||
|
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33CC10EE2044A3C60003C045 /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
33CC10ED2044A3C60003C045 /* admin_app.app */,
|
||||||
|
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33CC11242044D66E0003C045 /* Resources */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
33CC10F22044A3C60003C045 /* Assets.xcassets */,
|
||||||
|
33CC10F42044A3C60003C045 /* MainMenu.xib */,
|
||||||
|
33CC10F72044A3C60003C045 /* Info.plist */,
|
||||||
|
);
|
||||||
|
name = Resources;
|
||||||
|
path = ..;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33CEB47122A05771004F2AC0 /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
|
||||||
|
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
|
||||||
|
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
|
||||||
|
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33FAB671232836740065AC1E /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
|
||||||
|
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
|
||||||
|
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
|
||||||
|
33E51914231749380026EE4D /* Release.entitlements */,
|
||||||
|
33CC11242044D66E0003C045 /* Resources */,
|
||||||
|
33BA886A226E78AF003329D5 /* Configs */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
331C80D4294CF70F00263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
331C80D1294CF70F00263BE5 /* Sources */,
|
||||||
|
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||||
|
331C80D3294CF70F00263BE5 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
331C80DA294CF71000263BE5 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = RunnerTests;
|
||||||
|
productName = RunnerTests;
|
||||||
|
productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
|
33CC10EC2044A3C60003C045 /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
33CC10E92044A3C60003C045 /* Sources */,
|
||||||
|
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||||
|
33CC10EB2044A3C60003C045 /* Resources */,
|
||||||
|
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||||
|
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
33CC11202044C79F0003C045 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 33CC10ED2044A3C60003C045 /* admin_app.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
33CC10E52044A3C60003C045 /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = YES;
|
||||||
|
LastSwiftUpdateCheck = 0920;
|
||||||
|
LastUpgradeCheck = 1510;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
331C80D4294CF70F00263BE5 = {
|
||||||
|
CreatedOnToolsVersion = 14.0;
|
||||||
|
TestTargetID = 33CC10EC2044A3C60003C045;
|
||||||
|
};
|
||||||
|
33CC10EC2044A3C60003C045 = {
|
||||||
|
CreatedOnToolsVersion = 9.2;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
ProvisioningStyle = Automatic;
|
||||||
|
SystemCapabilities = {
|
||||||
|
com.apple.Sandbox = {
|
||||||
|
enabled = 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
33CC111A2044C6BA0003C045 = {
|
||||||
|
CreatedOnToolsVersion = 9.2;
|
||||||
|
ProvisioningStyle = Manual;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 33CC10E42044A3C60003C045;
|
||||||
|
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
33CC10EC2044A3C60003C045 /* Runner */,
|
||||||
|
331C80D4294CF70F00263BE5 /* RunnerTests */,
|
||||||
|
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
331C80D3294CF70F00263BE5 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
33CC10EB2044A3C60003C045 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
|
||||||
|
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
|
||||||
|
};
|
||||||
|
33CC111E2044C6BF0003C045 /* ShellScript */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
Flutter/ephemeral/FlutterInputs.xcfilelist,
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
Flutter/ephemeral/tripwire,
|
||||||
|
);
|
||||||
|
outputFileListPaths = (
|
||||||
|
Flutter/ephemeral/FlutterOutputs.xcfilelist,
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
331C80D1294CF70F00263BE5 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
33CC10E92044A3C60003C045 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
|
||||||
|
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
|
||||||
|
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 33CC10EC2044A3C60003C045 /* Runner */;
|
||||||
|
targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
|
||||||
|
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
33CC10F52044A3C60003C045 /* Base */,
|
||||||
|
);
|
||||||
|
name = MainMenu.xib;
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
331C80DB294CF71000263BE5 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.saqel.adminApp.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/admin_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/admin_app";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
331C80DC294CF71000263BE5 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.saqel.adminApp.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/admin_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/admin_app";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
331C80DD294CF71000263BE5 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.saqel.adminApp.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/admin_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/admin_app";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
338D0CE9231458BD00FA5F75 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEAD_CODE_STRIPPING = YES;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
338D0CEA231458BD00FA5F75 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
);
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
338D0CEB231458BD00FA5F75 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
33CC10F92044A3C60003C045 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEAD_CODE_STRIPPING = YES;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
33CC10FA2044A3C60003C045 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEAD_CODE_STRIPPING = YES;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
33CC10FC2044A3C60003C045 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
);
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
33CC10FD2044A3C60003C045 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
);
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
33CC111C2044C6BA0003C045 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
33CC111D2044C6BA0003C045 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
331C80DB294CF71000263BE5 /* Debug */,
|
||||||
|
331C80DC294CF71000263BE5 /* Release */,
|
||||||
|
331C80DD294CF71000263BE5 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
33CC10F92044A3C60003C045 /* Debug */,
|
||||||
|
33CC10FA2044A3C60003C045 /* Release */,
|
||||||
|
338D0CE9231458BD00FA5F75 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
33CC10FC2044A3C60003C045 /* Debug */,
|
||||||
|
33CC10FD2044A3C60003C045 /* Release */,
|
||||||
|
338D0CEA231458BD00FA5F75 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
33CC111C2044C6BA0003C045 /* Debug */,
|
||||||
|
33CC111D2044C6BA0003C045 /* Release */,
|
||||||
|
338D0CEB231458BD00FA5F75 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1510"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
|
BuildableName = "admin_app.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
|
BuildableName = "admin_app.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "331C80D4294CF70F00263BE5"
|
||||||
|
BuildableName = "RunnerTests.xctest"
|
||||||
|
BlueprintName = "RunnerTests"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
enableGPUValidationMode = "1"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
|
BuildableName = "admin_app.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
|
BuildableName = "admin_app.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||