Compare commits

...
213 Commits
Author SHA1 Message Date
Hamza-Ayed ce7b0fcf14 Harden published curriculum and student flows 2026-09-30 08:22:17 +03:00
Hamza-Ayed e8163fe265 Update Saqel Platform: 2026-09-20 16:38:51 2026-09-20 16:38:51 +03:00
Hamza-Ayed 5805413f51 Update Saqel Platform: 2026-09-13 22:20:05 2026-09-13 22:20:05 +03:00
Hamza-Ayed 544c37eee8 Update Saqel Platform: 2026-09-13 16:50:10 2026-09-13 16:50:10 +03:00
Hamza-Ayed 72c6575765 Update Saqel Platform: 2026-09-13 16:31:17 2026-09-13 16:31:18 +03:00
Hamza-Ayed 81fa8c5dc8 Update Saqel Platform: 2026-09-12 16:23:39 2026-09-12 16:23:39 +03:00
Hamza-Ayed f4d4f02853 Update Saqel Platform: 2026-09-12 13:38:49 2026-09-12 13:38:49 +03:00
Hamza-Ayed 1f31369893 Update Saqel Platform: 2026-09-12 08:50:01 2026-09-12 08:50:02 +03:00
Hamza-Ayed cb4f2a3159 Update Saqel Platform: 2026-09-11 20:37:10 2026-09-11 20:37:10 +03:00
Hamza-Ayed e61d56c8e1 feat(grade10): add chemistry, biology, and earth sciences virtual labs, fix video CDN playback, teacher submissions SQL, and enable authenticated lesson reading 2026-09-11 17:22:28 +03:00
Hamza-Ayed d1bc39604b feat(ecosystem): full Grade 10 curriculum sync, real device fingerprinting, teacher video dashboard, and official textbook assets 2026-09-11 16:46:40 +03:00
Hamza-Ayed fc8f874bf9 Fix nested array reference mutation in getTree with direct key indexing 2026-09-11 00:17:00 +03:00
Hamza-Ayed a6acdce532 Add diagnose_tree.php to check tree enrichment and database state 2026-09-11 00:15:53 +03:00
Hamza-Ayed 039c977d63 Decouple curriculum tree lesson enrichment from optional teacher_submissions table 2026-09-11 00:13:36 +03:00
Hamza-Ayed f599b5ffc7 Add publish_grade10_bundle.php management script 2026-09-10 23:59:19 +03:00
Hamza-Ayed 67771242b8 Fix asset_type and bundle_role to match MySQL ENUM definitions 2026-09-10 23:49:31 +03:00
Hamza-Ayed 90d4a871d3 Fix file copy source path in promote script 2026-09-10 23:47:46 +03:00
Hamza-Ayed 35cb3d34d8 Add result summary banner and use PHP_BINARY for preflight 2026-09-10 23:43:58 +03:00
Hamza-Ayed c280cfc004 Show explicit blocking gate errors in promote script 2026-09-10 23:28:09 +03:00
Hamza-Ayed 12fb6d07e6 Update Saqel Platform: 2026-09-10 23:13:50 2026-09-10 23:13:50 +03:00
Hamza-Ayed a742c5bfd6 Update Saqel Platform: 2026-09-10 22:16:14 2026-09-10 22:16:14 +03:00
Hamza-Ayed 9c80ca9918 Update Saqel Platform: 2026-09-10 12:35:38 2026-09-10 12:35:38 +03:00
Hamza-Ayed 960d4c8604 Update Saqel Platform: 2026-09-10 12:26:22 2026-09-10 12:32:01 +03:00
Hamza-Ayed 09dfd665b5 Document remaining post-migration execution tasks 2026-09-09 17:20:21 +03:00
Hamza-Ayed 139c8667eb Order dependent revenue and review migrations 2026-09-09 17:12:03 +03:00
Hamza-Ayed 60be22f418 Order watch session migration after video versions 2026-09-09 17:11:51 +03:00
Hamza-Ayed 313e994025 Make legacy migrations safe on canonical schema 2026-09-09 17:07:17 +03:00
Hamza-Ayed f9851e3b4c Add explicit SSH pull and migration verification flow 2026-09-09 17:04:52 +03:00
Hamza-Ayed 5af684a892 Implement verified curriculum, video review, watch sessions, ledger safeguards, and remove demo data 2026-09-09 17:04:08 +03:00
Hamza-Ayed 0dc5df7b5e docs(rtl): align all markdown documentation files right-to-left for Arabic readability 2026-09-09 11:38:30 +03:00
Hamza-Ayed 432ca50204 fix(student_app): remove duplicate _showNoVideoAvailableSheet declaration in subject_hub_screen.dart 2026-09-09 01:36:06 +03:00
Hamza-Ayed 78ad245ee5 fix(playback): isolate curriculum keys and prevent non-uploaded lessons from falling back to previous video 2026-09-09 01:33:56 +03:00
Hamza-Ayed a4d6e34a40 fix(curriculum): robustly normalize student grade across app and backend to display subjects 2026-09-09 01:28:31 +03:00
Hamza-Ayed a8e895d5da fix(playback): enable authentic video playback for Intro and Lesson 2 across student app and backend 2026-09-09 01:23:51 +03:00
Hamza-Ayed 0a2ca71cb4 Fix student grade registration: set grade_10 as default, add auto-heal for legacy tawjihi_2008, add student grade selector and update-grade API 2026-09-09 01:06:35 +03:00
Hamza-Ayed 8183e53b86 Update Saqel Platform: 2026-09-09 00:52:08 2026-09-09 00:52:08 +03:00
Hamza-Ayed 0c239c431d Update Saqel Platform: 2026-09-09 00:24:41 2026-09-09 00:24:41 +03:00
Hamza-Ayed 956bb4a674 Update Saqel Platform: 2026-09-09 00:12:31 2026-09-09 00:12:31 +03:00
Hamza-Ayed 3d9fb16767 Update Saqel Platform: 2026-09-08 23:51:07 2026-09-08 23:51:07 +03:00
Hamza-Ayed ef639e804d Update Saqel Platform: 2026-09-08 23:48:05 2026-09-08 23:48:05 +03:00
Hamza-Ayed be60f34148 Update Saqel Platform: 2026-09-08 23:44:33 2026-09-08 23:44:33 +03:00
Hamza-Ayed 41493f5dd6 Update Saqel Platform: 2026-09-08 23:35:48 2026-09-08 23:35:48 +03:00
Hamza-Ayed 90487a42ad Update Saqel Platform: 2026-09-08 23:29:32 2026-09-08 23:29:32 +03:00
Hamza-Ayed 590326baa0 Update Saqel Platform: 2026-09-08 18:55:21 2026-09-08 18:55:21 +03:00
Hamza-Ayed 9198a2f974 Update Saqel Platform: 2026-09-08 18:49:45 2026-09-08 18:49:45 +03:00
Hamza-Ayed dd8b95035f Update Saqel Platform: 2026-09-08 18:44:36 2026-09-08 18:44:36 +03:00
Hamza-Ayed dbbd7786f2 Update Saqel Platform: 2026-09-08 18:35:03 2026-09-08 18:35:03 +03:00
Hamza-Ayed 4008d4a435 Update Saqel Platform: 2026-09-08 18:26:41 2026-09-08 18:26:41 +03:00
Hamza-Ayed e5cbd89323 Update Saqel Platform: 2026-09-08 18:19:28 2026-09-08 18:19:28 +03:00
Hamza-Ayed 75451ebc3f Update Saqel Platform: 2026-09-08 17:51:07 2026-09-08 17:51:07 +03:00
Hamza-Ayed 902cf00152 Update Saqel Platform: 2026-09-08 17:30:46 2026-09-08 17:30:46 +03:00
Hamza-Ayed 5fd1f32d4c Update Saqel Platform: 2026-09-08 14:08:24 2026-09-08 14:08:24 +03:00
Hamza-Ayed c3d526c011 Update Saqel Platform: 2026-09-08 14:06:11 2026-09-08 14:06:11 +03:00
Hamza-Ayed 5bccd13b85 Update Saqel Platform: 2026-09-08 14:01:43 2026-09-08 14:01:43 +03:00
Hamza-Ayed 9e3750692e Update Saqel Platform: 2026-09-08 13:59:41 2026-09-08 13:59:41 +03:00
Hamza-Ayed 10f885b2f1 Update Saqel Platform: 2026-09-08 13:55:24 2026-09-08 13:55:24 +03:00
Hamza-Ayed bb37b31cab Update Saqel Platform: 2026-09-08 13:43:36 2026-09-08 13:43:36 +03:00
Hamza-Ayed 0f424062fe Update Saqel Platform: 2026-09-05 23:24:49 2026-09-05 23:24:49 +03:00
Hamza-Ayed 0e5be7adbb Update Saqel Platform: 2026-09-05 22:17:15 2026-09-05 22:17:15 +03:00
Hamza-Ayed 68265a5bc6 Update Saqel Platform: 2026-09-05 22:08:42 2026-09-05 22:08:42 +03:00
Hamza-Ayed 368f68cc97 feat(curriculum): restore video player, organize math & english labs hierarchically, add scoped audio and interactive grammar mind maps 2026-09-05 00:33:45 +03:00
Hamza-Ayed b0c2bfc03b Update Saqel Platform: 2026-09-05 00:11:09 2026-09-05 00:11:09 +03:00
Hamza-Ayed e7bc20bc98 fix(platform): grade-gate student app, isolate per-video resume & checkpoints, remove fake teachers, enhance arabic lab, clean flutter analyze 2026-09-04 23:41:32 +03:00
Hamza-Ayed ae00aa943e Update Saqel Platform: 2026-09-04 21:26:55 2026-09-04 21:26:55 +03:00
Hamza-Ayed 24f6b5a589 Update Saqel Platform: 2026-09-04 03:20:29 2026-09-04 03:20:30 +03:00
Hamza-Ayed 1235abc0cb Update Saqel Platform: 2026-09-04 03:09:10 2026-09-04 03:09:10 +03:00
Hamza-Ayed 882541a56a Update Saqel Platform: 2026-09-04 02:57:19 2026-09-04 02:57:19 +03:00
Hamza-Ayed 1fe97bf3eb Update Saqel Platform: 2026-09-04 02:04:43 2026-09-04 02:04:43 +03:00
Hamza-Ayed 58c3c48f69 feat: STEM Virtual Lab, English TTS companion, dynamic textbooks and zero-demo video fallback 2026-09-03 17:37:15 +03:00
Hamza-Ayed 445a1aa0e3 Fix Flutter exam parsing, make exams tab dynamic from API, and add intelligent exam resolution to submitExam 2026-09-03 16:59:14 +03:00
Hamza-Ayed 0a61e32033 Enrich full codebase with architectural Arabic docblocks and synchronize database_schema.sql with live database 2026-09-03 16:51:29 +03:00
Hamza-Ayed 9624cfb4c8 Fix exams foreign key constraint on lesson_id by using NULL instead of 0 for unit exams 2026-09-03 16:46:17 +03:00
Hamza-Ayed be4fe3dabf Fix MySQL scope ENUM to unit_exam in AiQuestionBankGeneratorService and ExamController 2026-09-03 16:44:37 +03:00
Hamza-Ayed 8d7ffcf2eb Implement Server-Side AI Question Bank Generator, Adaptive Weakness Sampling 60/40, and Curriculum Studio One-Click Generation 2026-09-03 15:38:35 +03:00
Hamza-Ayed c9c03892d1 Implement multi-teacher video selector, in-app curriculum document reader, 15-question Unit 1 exam bank, and interactive science simulation engine 2026-09-03 15:16:46 +03:00
Hamza-Ayed 7e0aae023a Fix student_question_answers table creation, clean manifest duplicate units, and calibrate Socratic timeline seeking 2026-09-03 14:04:39 +03:00
Hamza-Ayed b41db82537 Fix curriculum grounding for Grade 10: replace calculus fallback with authentic Systems of Equations questions and add self-healing guard 2026-09-03 13:49:45 +03:00
Hamza-Ayed db9004c9ed Fix amberGold color token in AppColors and clean unused import 2026-09-03 13:31:46 +03:00
Hamza-Ayed 12049fd7da Complete Socratic adaptive exams in Flutter, Guardian WhatsApp alerts, and curriculum robustness 2026-09-03 10:14:34 +03:00
Hamza-Ayed ab482d85e4 Update Saqel Platform: 2026-09-02 09:06:45 2026-09-02 09:06:45 +03:00
Hamza-Ayed 8c62d81f04 Update Saqel Platform: 2026-09-02 08:59:54 2026-09-02 08:59:54 +03:00
Hamza-Ayed 5ecfaafa48 Update Saqel Platform: 2026-09-02 08:55:31 2026-09-02 08:55:32 +03:00
Hamza-Ayed a9b1819e9a Update Saqel Platform: 2026-09-02 08:47:33 2026-09-02 08:47:33 +03:00
Hamza-Ayed ff9fdbb24e Update Saqel Platform: 2026-09-02 08:20:41 2026-09-02 08:20:41 +03:00
Hamza-Ayed a4536395c5 Update Saqel Platform: 2026-09-02 08:12:57 2026-09-02 08:12:57 +03:00
Hamza-Ayed 36939a50c1 Update Saqel Platform: 2026-09-02 07:52:23 2026-09-02 07:52:24 +03:00
Hamza-Ayed 739f934a26 Update Saqel Platform: 2026-09-02 07:43:58 2026-09-02 07:43:58 +03:00
Hamza-Ayed 310d4b57e9 Update Saqel Platform: 2026-09-02 01:25:14 2026-09-02 01:25:14 +03:00
Hamza-Ayed a1c7029838 Update Saqel Platform: 2026-09-02 01:23:51 2026-09-02 01:23:51 +03:00
Hamza-Ayed f4ab43d49a Update Saqel Platform: 2026-09-02 00:55:56 2026-09-02 00:55:56 +03:00
Hamza-Ayed 1c0cd88a98 Fix RTL player time formatting and add interactive chalkboard notes 2026-08-31 18:01:26 +03:00
Hamza-Ayed a9b9c90d20 Guarantee 100% success response with Socratic checkpoints on /api/lessons/{id}/playback 2026-08-31 17:57:31 +03:00
Hamza-Ayed 7ab10209dc Fix UI overflows in SubjectHub/Grid and guarantee Socratic checkpoints response 2026-08-31 17:55:22 +03:00
Hamza-Ayed 43d6b88106 Fix lessons table SQL column name in getPlaybackData 2026-08-31 17:50:38 +03:00
Hamza-Ayed d8a50bb105 Enhance video playback cubit resilience and K-12 curriculum flow 2026-08-31 17:46:03 +03:00
Hamza-Ayed 78b703f625 Fix PHP 8 router parameter dispatching and lesson slug playback lookup 2026-08-31 17:45:27 +03:00
Hamza-Ayed ccf5f13a09 Update Saqel Platform: 2026-08-31 01:52:23 2026-08-31 01:52:24 +03:00
Hamza-Ayed 4e29915383 Update Saqel Platform: 2026-08-31 00:19:49 2026-08-31 00:19:49 +03:00
Hamza-Ayed c4c59bc828 Update Saqel Platform: 2026-08-29 22:41:36 2026-08-29 22:41:36 +03:00
Hamza-Ayed 051b9322f6 Update Saqel Platform: 2026-08-29 22:05:41 2026-08-29 22:05:41 +03:00
Hamza-Ayed d10e1c712c Update Saqel Platform: 2026-08-29 21:51:24 2026-08-29 21:51:24 +03:00
Hamza-Ayed 22568fe220 feat: Implement semantic video naming, MySQL database curriculum synchronization, and live two-way binding between filesystem and DB 2026-08-29 05:40:45 +03:00
Hamza-Ayed 9fc60b2cf5 fix: Isolate OTP keys and sessions per role in Redis so teacher and student logins on the same phone number never collide 2026-08-29 05:29:26 +03:00
Hamza-Ayed a8700c656f feat: Upgrade AI Video prompt to 10-15 minute deep comprehensive lesson with multiple examples and textbook integration 2026-08-29 05:24:10 +03:00
Hamza-Ayed 484ad9773d feat: Make copied video prompt direct, ultra-crisp with explicit 'اعمل فيديو' 5-bullet instructions 2026-08-29 05:22:17 +03:00
Hamza-Ayed 295e6e9b06 feat: Refine AI Voiceover prompt to produce direct spoken narration without director meta-cues 2026-08-29 05:20:43 +03:00
Hamza-Ayed 6b7ddf2ded feat: Optimize AI Video prompt generator to output ultra-concise, structured NotebookLM & video briefs 2026-08-29 05:18:12 +03:00
Hamza-Ayed 771ec6acc5 feat: Add collapsible curriculum accordion tree, direct video upload to platform, resilient AI asset generation with smart synthesis fallback, and ingest Physics Unit 2 2026-08-29 05:13:47 +03:00
Hamza-Ayed 4e047ee8f2 feat: Ingest complete Grade 10 Physics Unit 1 (Vectors) with full experiments, scalar/vector analysis, math products, enrichment, and international questions 2026-08-29 05:06:12 +03:00
Hamza-Ayed 9e276d6de6 feat: Normalize Grade 10 subjects in manifest with Arabic & English names (English, Math, Physics) 2026-08-29 04:58:04 +03:00
Hamza-Ayed 439572453f feat: Ingest complete Grade 10 Math textbook (Units 1-4) with semantic lesson naming, clean file hierarchy, and AI Video prompt generator 2026-08-29 04:53:45 +03:00
Hamza-Ayed 5b58ead215 feat: Ingest authentic Grade 10 Math Unit 3 (Trigonometry) with full polar system, unit circle, graph analysis, equations, and exam 2026-08-29 04:46:58 +03:00
Hamza-Ayed 8cbb87cb51 feat: Ingest authentic Grade 10 Math Unit 2 (Circle) with full theorems, LaTeX proofs, exercises, and exam 2026-08-29 04:43:18 +03:00
Hamza-Ayed 8230b42ac1 feat: Ingest complete 30 pages of Math Unit 1 (Equations) with full exercises, GeoGebra lab, and exam 2026-08-29 04:38:35 +03:00
Hamza-Ayed eb94f6ff77 feat: Ingest authentic Grade 10 Math Unit 1 with complete LaTeX equations and full derivations 2026-08-29 03:32:37 +03:00
Hamza-Ayed 39cf087488 fix: Support array payload from Security::verifyJWT for student identity verification 2026-08-29 03:07:10 +03:00
Hamza-Ayed 6da006e154 fix: Eliminate PHP parse error in AuthController studentProfileSetup 2026-08-29 03:03:36 +03:00
Hamza-Ayed a8d0efbbea fix: Resolve getJSON method error in student national ID verification and enhance curriculum worker Gemini model handling 2026-08-29 03:00:06 +03:00
Hamza-Ayed 9a1cb3adfd feat: Add UI button to watch NotebookLM AI Video Summary in Student Portal if ai_video_url exists for the lesson 2026-08-29 02:55:07 +03:00
Hamza-Ayed c338deea5a fix: Add missing JS showSuccess and showError to StudentPortal fixing UI freeze during OTP 2026-08-29 02:45:10 +03:00
Hamza-Ayed 3d09b8235d fix: Run curriculum worker in true CLI background to prevent web server timeout at 20% 2026-08-29 02:29:55 +03:00
Hamza-Ayed 598467b58f Update Saqel Platform: 2026-08-29 02:03:07 2026-08-29 02:03:07 +03:00
Hamza-Ayed 135a379b32 feat: Update Curriculum Studio with AI Assets panel to manually generate and input NotebookLM assets 2026-08-29 02:01:52 +03:00
Hamza-Ayed 074c08fc6f feat: Add AiLessonEnhancerService to automatically generate NotebookLM-style cheat sheets and quizzes 2026-08-29 01:26:25 +03:00
Hamza-Ayed d4379bf0d3 feat: Add cheat_sheet and socratic_quiz columns to lessons for automated AI curriculum generation 2026-08-29 01:15:26 +03:00
Hamza-Ayed 728f82181b feat: Add AiVideoGeneratorService for automated lively video script generation 2026-08-29 00:50:53 +03:00
Hamza-Ayed 294a60ecdb feat: Add migration script to apply or refresh database schema 2026-08-29 00:28:46 +03:00
Hamza-Ayed b3f70193a3 feat: Prepare DB architecture for AI multimedia ecosystem
- Add ai_podcast_url and ai_video_url to lessons for NotebookLM integration
- Add markdown_content to lessons to store extracted PDF curriculum directly in DB
- Create lesson_resources table for managing Worksheets, Summaries, and Books PDFs
2026-08-29 00:22:04 +03:00
Hamza-Ayed 03039a9611 fix: Strip markdown fences from Gemini JSON response to fix PDF curriculum extraction 2026-08-29 00:14:43 +03:00
Hamza-Ayed 5901329ef2 feat: Implement National ID sub-account login flow for students
- Updates verifyOtp to return identity_token for students instead of auto-login
- Adds verifyNationalId API endpoint to select or create a student profile under a single phone number
- Allows multiple students per guardian phone number
2026-08-29 00:10:04 +03:00
Hamza-Ayed 731194cba0 fix: multi-child demo linking in Guardian portal 2026-08-28 23:18:18 +03:00
Hamza-Ayed 8aab91c77f feat: Connect Guardian portal to real student mastery tracking
- Add GuardianController API endpoint to fetch student progress, readiness, and weakness gap logs.
- Add dynamic JS logic to GuardianPortal.php to replace static mockups.
- Connect Exam AI diagnostics (weaknesses, socratic checks) directly to the parent's dashboard view.
2026-08-28 23:17:14 +03:00
Hamza-Ayed a18d19588d feat: add unified print utility for students (StepLab & Exams) 2026-08-28 23:11:55 +03:00
Hamza-Ayed 1401db11d0 fix: 4 critical bugs — worker argv, student dashboard visibility, teacher onboarding flow, password validation 2026-08-28 23:08:15 +03:00
Hamza-Ayed abe862f04d Update Saqel Platform: 2026-08-28 18:21:58 2026-08-28 18:21:58 +03:00
Hamza-Ayed 2c529c579c Update Saqel Platform: 2026-08-28 18:13:52 2026-08-28 18:13:52 +03:00
Hamza-Ayed 6c8cb53af0 Update Saqel Platform: 2026-08-28 18:08:03 2026-08-28 18:08:03 +03:00
Hamza-Ayed 597ef1809d Update Saqel Platform: 2026-08-28 16:42:38 2026-08-28 16:42:38 +03:00
Hamza-Ayed cdcfa71c96 Update Saqel Platform: 2026-08-28 16:40:48 2026-08-28 16:40:48 +03:00
Hamza-Ayed f941c53035 Update Saqel Platform: 2026-08-28 16:36:22 2026-08-28 16:36:22 +03:00
Hamza-Ayed a20866eec8 Update Saqel Platform: 2026-08-28 16:30:12 2026-08-28 16:30:12 +03:00
Hamza-Ayed fe5a71bcad Update Saqel Platform: 2026-08-28 16:24:59 2026-08-28 16:24:59 +03:00
Hamza-Ayed ca0e18990e Update Saqel Platform: 2026-08-28 16:18:12 2026-08-28 16:18:12 +03:00
Hamza-Ayed 39c1e14232 Update Saqel Platform: 2026-08-28 16:06:52 2026-08-28 16:06:53 +03:00
Hamza-Ayed e6fd319ff4 Update Saqel Platform: 2026-08-28 15:55:08 2026-08-28 15:55:09 +03:00
Hamza-Ayed 86bdee399d Update Saqel Platform: 2026-08-28 15:41:53 2026-08-28 15:41:53 +03:00
Hamza-Ayed 974de0a641 Update Saqel Platform: 2026-08-28 15:39:16 2026-08-28 15:39:16 +03:00
Hamza-Ayed 2bcf2d2ba2 Update Saqel Platform: 2026-08-28 15:34:26 2026-08-28 15:34:26 +03:00
Hamza-Ayed d97a98abdb Update Saqel Platform: 2026-08-28 15:28:24 2026-08-28 15:28:24 +03:00
Hamza-Ayed 070e80ea30 Update Saqel Platform: 2026-08-28 15:19:58 2026-08-28 15:19:58 +03:00
Hamza-Ayed 06461635bf Update Saqel Platform: 2026-08-28 15:13:58 2026-08-28 15:13:58 +03:00
Hamza-Ayed 1c39d4c9cb Update Saqel Platform: 2026-08-28 15:11:34 2026-08-28 15:11:34 +03:00
Hamza-Ayed 710695b50b Update Saqel Platform: 2026-08-28 15:04:45 2026-08-28 15:04:45 +03:00
Hamza-Ayed 82170b4190 Update Saqel Platform: 2026-08-28 05:00:19 2026-08-28 05:00:19 +03:00
Hamza-Ayed 4991d50508 Update Saqel Platform: 2026-08-28 04:58:01 2026-08-28 04:58:01 +03:00
Hamza-Ayed 8b09ab767b Update Saqel Platform: 2026-08-28 04:49:19 2026-08-28 04:49:19 +03:00
Hamza-Ayed 8d22e78821 Update Saqel Platform: 2026-08-28 04:45:18 2026-08-28 04:45:18 +03:00
Hamza-Ayed cad7b7aa2b Update Saqel Platform: 2026-08-28 04:39:37 2026-08-28 04:39:37 +03:00
Hamza-Ayed eaf90d8c93 Auto purge cached session from localStorage when user is invalidated or DB reset 2026-08-28 04:35:00 +03:00
Hamza-Ayed 1d6ba50b21 Use CREATE TABLE IF NOT EXISTS and full DROP coverage in database_schema.sql 2026-08-28 04:29:47 +03:00
Hamza-Ayed 62ab0ab9a0 Update DROP TABLE order to ensure clean execution in migrate.php 2026-08-28 04:26:20 +03:00
Hamza-Ayed 6e0e07e17f Update Saqel Platform: 2026-08-28 04:23:13 2026-08-28 04:23:13 +03:00
Hamza-Ayed 21d2ed704d Update Saqel Platform: 2026-08-28 04:19:47 2026-08-28 04:19:47 +03:00
Hamza-Ayed 17a22c6985 fix: populate questions and options tables, scale AI chapters/checkpoints to video duration, open student lesson discovery 2026-08-28 04:14:16 +03:00
Hamza-Ayed dc2ebb108c feat: Dynamic student lesson discovery, lesson selector carousel, and live Socratic checkpoint streaming 2026-08-28 04:08:04 +03:00
Hamza-Ayed ab52d44c94 fix: Auto-seed official subjects table to satisfy foreign key constraints on courses creation 2026-08-28 04:03:01 +03:00
Hamza-Ayed d82d5a9ab9 fix: Add explicit Request properties and AllowDynamicProperties attribute to eliminate PHP 8.2 deprecation output 2026-08-28 04:01:32 +03:00
Hamza-Ayed 01d905f4da fix: Align exams creator_type enum to ai_adaptive and add fault-tolerant checkpoint generation 2026-08-28 02:54:14 +03:00
Hamza-Ayed e788379c68 fix: Auto-seed teacher courses and auto-resolve course entity on video direct upload 2026-08-28 02:51:35 +03:00
Hamza-Ayed c0918f56bc fix: Enable automatic teacher course binding on video upload and automated Cloudflare R2 object sync 2026-08-28 02:48:53 +03:00
Hamza-Ayed 0ce5616e22 refactor: Remove Bunny Stream completely from UI and unify Teacher Studio to streamlined Cloudflare R2 & FFmpeg HLS uploader 2026-08-28 02:40:17 +03:00
Hamza-Ayed 7ddaff6b14 security: Remove all hardcoded secret keys from codebase and enforce strict getenv environment loading 2026-08-28 02:34:13 +03:00
Hamza-Ayed 7b778a1879 feat: Configure Cloudflare R2 Object Storage & CDN integration with AWS SigV4 upload and public distribution URL 2026-08-28 02:31:41 +03:00
Hamza-Ayed f0592466f6 feat: Implement DML Interactive Canvas & Dynamic Visual Physics and Math Simulators with live sliders for calculus and mechanics 2026-08-28 02:06:26 +03:00
Hamza-Ayed a99ff9a184 feat: Implement Socratic Step-by-Step Problem Lab with 60s locked struggle timer and progressive step disclosure scaffolding 2026-08-28 02:00:56 +03:00
Hamza-Ayed 4d01962cf8 feat: Implement Zero-Touch Autonomous Forensic AI Video & Curriculum Analyzer (Gemini timeline chaptering, strictly chronological Socratic checkpoints, and curriculum grounding) 2026-08-28 01:42:59 +03:00
Hamza-Ayed 54ceab1aae feat: Implement Server-Side FFmpeg HLS Transcoding (.m3u8), HLS.js streaming, and dynamic Socratic In-Video Checkpoint Engine 2026-08-28 01:30:56 +03:00
Hamza-Ayed 8044f86798 feat: Implement Dual Video Pipeline (Direct Server API Upload & Bunny.net Stream Cloud CDN + DRM) with Socratic interactive player 2026-08-27 19:18:51 +03:00
Hamza-Ayed 70176deb3b feat: Implement zero-latency WebSocket live message broadcast with luxury audio chime and Apple-style toast notifications 2026-08-27 15:22:24 +03:00
Hamza-Ayed 7faf223583 fix: Preserve HTTP status code in Response::json and handle 401 session expiration gracefully in loadConversations 2026-08-27 14:56:50 +03:00
Hamza-Ayed d18d1aa2b5 fix: Add output buffer cleansing in Response::json and enhance error logging for conversation parsing 2026-08-27 14:54:11 +03:00
Hamza-Ayed e16ad471c3 fix: Resolve OTP verification instant transition and ensure bulletproof non-crashing conversation contact listing 2026-08-27 12:09:09 +03:00
Hamza-Ayed 9e9417f937 fix: Rewrite getConversations query to guarantee all students appear for teacher without complex join dependencies 2026-08-27 11:48:42 +03:00
Hamza-Ayed e1a75e71ab fix: Enhance safe DOM rendering for teacher conversations list and auto-selection of active chat 2026-08-27 03:55:34 +03:00
Hamza-Ayed dd06cafa87 fix: Implement automatic seed provisioning for teacher/students, graceful recipient resolution, and instant chat hydration 2026-08-27 03:51:24 +03:00
Hamza-Ayed 7fd67756b4 fix: Enable dynamic student/teacher contact discovery, dual REST/WebSocket persistence, and multi-path .env detection 2026-08-27 03:47:45 +03:00
Hamza-Ayed b11a4bd8c1 fix: Resolve WebSocket authentication error, add verifyJWT alias, and implement 20s heartbeat keep-alive for permanent stable connections 2026-08-27 03:39:57 +03:00
Hamza-Ayed 596f5e6603 feat: Complete 3 core pillars - Realtime Workerman WebSocket Chat, Interactive Socratic Video Player with 45s Remedial Rewind, and Multi-level AI Exam Taking Engine 2026-08-27 03:34:14 +03:00
Hamza-Ayed 845a5f3111 fix: Implement persistent zero-flicker JWT session hydration with localStorage and cookies across Student and Teacher portals 2026-08-27 03:28:05 +03:00
Hamza-Ayed f4af4d2db8 feat: Embed official Saqel Logo across all navbars and footers, setup browser favicons, and integrate StitchMCP design pipeline 2026-08-27 03:25:02 +03:00
Hamza-Ayed 0d25239712 feat: Install custom Apple UI design system skills, implement official Apple.com dark aesthetic with SF Pro/Arabic typography and Bento grids 2026-08-27 03:19:07 +03:00
Hamza-Ayed 261796fcab feat: Full Apple Cupertino design revolution, SF Pro/Alexandria font stack, fixed grid layout, and luxury glassmorphic UI across all portals 2026-08-27 03:15:43 +03:00
Hamza-Ayed c7db799fa8 fix: Correct founder name to Hamza Al-Ghwairy, protect proprietary intellectual property, and elevate executive enterprise styling 2026-08-27 03:10:58 +03:00
Hamza-Ayed 0a31d359df feat: Add high-conversion Main Landing Page and dedicated Military Culture Directorate Strategic Proposal Page 2026-08-27 03:05:53 +03:00
Hamza-Ayed ea9bd200f3 feat: Full screen connectivity with live Workerman WebSocket, Socratic in-video quiz, multi-level exams, and real-time chat 2026-08-27 01:01:23 +03:00
Hamza-Ayed 0d5215befd Add composer.json for Workerman and load vendor autoload in websocket server 2026-08-27 00:35:35 +03:00
Hamza-Ayed b00a16f32d feat: Real-time Workerman gateway + Multi-level exam hierarchy (lesson, unit, final, AI adaptive) and student mastery tracking 2026-08-27 00:12:39 +03:00
Hamza-Ayed 12f00a05b6 feat: Full API-driven Student-Teacher Chat architecture with chat_messages table and endpoints 2026-08-27 00:05:30 +03:00
Hamza-Ayed 1d78fe8cc1 feat: Instant seamless transition to Teacher Studio Dashboard after onboarding 2026-08-26 23:55:29 +03:00
Hamza-Ayed fe66f05057 Fix Redis session mismatch in AuthMiddleware and ensure instant Onboarding UI transition 2026-08-26 23:46:48 +03:00
Hamza-Ayed cdb08331c0 Implement Teacher Onboarding, Profile Setup, and Dynamic Studio Dashboard 2026-08-26 23:38:56 +03:00
Hamza-Ayed 6201630865 fix: Resolve password_hash default value error and disable OTP autofill 2026-08-26 23:24:35 +03:00
Hamza-Ayed 0d63554a1f fix: 100% immune to native form reloads with pure Vanilla JS and instant OTP transition 2026-08-26 23:21:28 +03:00
Hamza-Ayed e386716ed5 feat: Enable Image OTP Card by default with Nabeh and enhanced portal sync 2026-08-26 23:15:31 +03:00
Hamza-Ayed f3c5f4161e fix: Fast text WhatsApp OTP by default, 50s timeout for image mode with auto-fallback 2026-08-26 23:04:25 +03:00
Hamza-Ayed cee6265930 fix: Accurate Nabeh OTP response parsing and live gateway error reporting 2026-08-26 23:01:25 +03:00
Hamza-Ayed b4a241bf62 fix: High contrast bold button text and strict single-step rendering 2026-08-26 22:55:45 +03:00
Hamza-Ayed e800b5b057 fix: Lock .env strictly to CloudPanel user root /home/intaleqapp-saqel/.env 2026-08-26 22:47:17 +03:00
Hamza-Ayed 55c640609a fix: Detect exact .env location and report in /api/test/nabeh 2026-08-26 22:43:18 +03:00
Hamza-Ayed e4ad569d8c fix: Embedded luxury self-contained CSS (zero CDN failure) + Redis password graceful auth 2026-08-26 22:30:56 +03:00
Hamza-Ayed 1f024d0c30 fix: Strict env enforcement, rock-solid Alpine portal rendering, and Nabeh/Redis test diagnostics 2026-08-26 22:25:35 +03:00
Hamza-Ayed 388944cbff feat: Add /student and /teacher portals with Alpine.js, WhatsApp OTP, and device fingerprinting 2026-08-26 22:15:33 +03:00
Hamza-Ayed a2f72a1c29 Fix Database getInstance method and connection call 2026-08-26 17:41:11 +03:00
1866 changed files with 411880 additions and 490 deletions
@@ -0,0 +1,144 @@
---
name: apple-ui-design-system
description: >-
World-class Apple Cupertino UI/UX Design System for Web and Flutter.
Use whenever building high-end, luxury, Apple-grade user interfaces with SF Pro/SF Arabic typography,
glassmorphism, bento grids, fluid micro-interactions, and Cupertino dark aesthetics.
---
# Apple Cupertino Luxury UI/UX Design System (Web & Mobile)
This skill equips the agent with Apple's official human interface guidelines (HIG) and Cupertino web design standards (`apple.com` and iOS 18 / macOS Sequoia aesthetic).
---
## 1. Typography Standards (Apple San Francisco Arabic & Latin)
### Web Font Stack
```css
@import url('https://fonts.googleapis.com/css2?family=Alexandria:wght@300;400;500;600;700;800;900&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
:root {
--font-apple: -apple-system, BlinkMacSystemFont, "SF Pro AR", "SF Pro Display", "SF Pro Text", "SF Arabic", "Geeza Pro", "Alexandria", system-ui, sans-serif;
}
* {
font-family: var(--font-apple);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Apple Tracking & Typography Scale */
h1 { font-size: clamp(34px, 5vw, 58px); font-weight: 800; letter-spacing: -0.025em; line-height: 1.15; }
h2 { font-size: clamp(26px, 3.5vw, 42px); font-weight: 800; letter-spacing: -0.02em; line-height: 1.25; }
h3 { font-size: clamp(18px, 2.5vw, 24px); font-weight: 700; letter-spacing: -0.015em; line-height: 1.35; }
p { font-size: 15px; font-weight: 400; letter-spacing: -0.01em; line-height: 1.7; color: #86868B; }
```
---
## 2. Apple Dark Mode Palette & Materials
```css
:root {
/* Surfaces */
--apple-black: #000000;
--apple-space-gray: #0B0F17;
--apple-card: rgba(22, 22, 23, 0.8);
--apple-card-hover: rgba(30, 30, 32, 0.95);
/* Borders & Glass */
--apple-border: rgba(255, 255, 255, 0.08);
--apple-border-focus: rgba(255, 255, 255, 0.25);
--apple-blur: blur(24px) saturate(180%);
/* Text */
--apple-text-white: #F5F5F7;
--apple-text-silver: #A1A1A6;
--apple-text-muted: #6E6E73;
/* Accent Colors */
--apple-blue: #0071E3;
--apple-blue-hover: #0077ED;
--apple-gold: #F59E0B;
--apple-cyan: #00F5D4;
}
```
---
## 3. Apple Bento Grid & Interactive Cards
```css
.apple-bento-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 20px;
}
.apple-card {
background: var(--apple-card);
border: 1px solid var(--apple-border);
border-radius: 28px;
padding: 36px;
backdrop-filter: var(--apple-blur);
-webkit-backdrop-filter: var(--apple-blur);
transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1), border-color 0.3s ease, box-shadow 0.3s ease;
}
.apple-card:hover {
transform: translateY(-4px);
border-color: var(--apple-border-focus);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6);
}
```
---
## 4. Apple Pill Buttons & Micro-Interactions
```css
.apple-btn-primary {
background: #0071E3;
color: #FFFFFF;
font-weight: 600;
font-size: 14px;
padding: 12px 26px;
border-radius: 980px;
border: none;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
text-decoration: none;
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.apple-btn-primary:hover {
background: #0077ED;
transform: scale(1.02);
box-shadow: 0 4px 20px rgba(0, 113, 227, 0.4);
}
.apple-btn-ghost {
background: rgba(255, 255, 255, 0.06);
color: #F5F5F7;
border: 1px solid rgba(255, 255, 255, 0.15);
font-weight: 600;
font-size: 14px;
padding: 12px 24px;
border-radius: 980px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
text-decoration: none;
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.apple-btn-ghost:hover {
background: rgba(255, 255, 255, 0.12);
border-color: rgba(255, 255, 255, 0.3);
transform: scale(1.02);
}
```
+18
View File
@@ -0,0 +1,18 @@
---
name: flutter-luxury-ui
description: >-
Ultra-luxury Flutter & Web UI Design System.
Use when building fluid animations, glassmorphism, Cupertino styling, custom painters,
and tactile mobile/web experiences in Flutter and responsive frontends.
---
# Luxury Flutter & Web UI Engineering Standards
This skill provides the architectural foundation for building multi-platform applications with Apple-grade fidelity.
## Key Principles:
1. **Typography:** SF Pro / SF Arabic & Alexandria with precise optical sizing and letter-spacing (`-0.015em`).
2. **Atmospheric Lighting:** Deep OLED dark canvases (`#000000`) accented with subtle radial ambient glow orbs.
3. **Materials & Depth:** Frosted glass cards with `BackdropFilter` (Flutter) or `backdrop-filter: blur(24px)` (Web).
4. **Fluid Curves:** Cupertino bezier curves (`Curves.easeOutCubic` / `cubic-bezier(0.16, 1, 0.3, 1)`).
5. **Tactile Micro-interactions:** Instant responsive feedback, subtle scale transformations on touch/hover (`transform: scale(0.98)` on press, `scale(1.02)` on hover).
+5
View File
@@ -46,6 +46,11 @@ apps/**/.dart_tool/
apps/**/build/
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
.idea/
.vscode/
+45
View File
@@ -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>
+21
View File
@@ -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>
+16 -1
View File
@@ -1,4 +1,9 @@
<div dir="rtl" align="right">
# تحليل جدوى منصة "صَقِل" (Saqel) التعليمية لطلبة التوجيهي في الأردن
> دراسة تاريخية ذات افتراضات غير محققة حالياً؛ لا تستخدم أرقام السوق أو التراخيص/الإعفاءات أو الأرباح أو ادعاءات غياب التفاعل عند المنافسين كحقائق. النموذج التجاري المطلوب الآن هو دخل المعلم بالدقائق المستحقة، وفق [مواصفات المرحلة القادمة](docs/NEXT_PHASE_MASTER_SPEC.md). راجع [تدقيق 2026-09-09](docs/CURRENT_STATE_AUDIT_2026-09-09.md)؛ Laravel/Docker و`artisan` ليسوا وصف التنفيذ الحالي. لم تُعد هذه المهمة تدقيقاً قانونياً أو دراسة مالية نهائية.
> ملخص تنفيذي مختصر — منصة صَقِل: الفهم لا الحفظ والتفاعل الذكي عبر EdTech 2.0
---
@@ -152,6 +157,8 @@
### 8.2 كيف نضيفه تقنياً؟ (المعمارية)
</div>
```
[الطالب/المعلم] → [Laravel API] → [طبقة AI Service] → [Gemini API]
↓ ↓
@@ -160,6 +167,8 @@
المناهج كـ Embeddings) + حد استخدام يومي/طالب
```
<div dir="rtl" align="right">
**القرارات الهندسية:**
1. **Laravel يكفي** — نداء Gemini عبر HTTP مباشرة (لا حاجة لخدمة Python منفصلة في البداية).
2. **RAG (Retrieval-Augmented Generation)**: نفريغ الدروس نصياً + تخزينها كـ Embeddings في `pgvector` → إجابات AI **مقيّدة بمحتوى المنهج** وتذكر مصدرها (اسم الدرس والدقيقة) → تقليل الهلوسة بشدة.
@@ -213,6 +222,8 @@
### 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`.
- تقارير ولي الأمر الأسبوعية تُولَّد آلياً بلغة طبيعية من نفس البيانات.
@@ -249,7 +262,7 @@
- **محلياً**: كتابة الكود + `php artisan test` (SQLite in-memory) — لا حاجة Docker.
- **السيرفر**: `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>
+41 -6
View File
@@ -1,3 +1,5 @@
<div dir="rtl" align="right">
# صَقِل | Saqel Platform 🎓
**منصة صَقِل (Saqel)** — الجيل الثاني من منصات التعليم التفاعلي لطلبة الثانوية العامة (التوجيهي) في الأردن، والقائمة على فلسفة "صقل الفهم لا الحفظ" بدعم من الذكاء الاصطناعي (EdTech 2.0).
@@ -6,29 +8,62 @@
## 🏛️ الهيكل التقني للمشروع (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)
- **AI Engine:** Google Gemini Flash API (Multimodal / Socratic Tutor / Concept Diagnosis)
- **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)
### 1. تشغيل الاختبارات محلياً:
توجد اختبارات Flutter داخل مجلد `test` لكل تطبيق. مثال لتطبيق الطالب، من جذر المستودع:
</div>
```bash
cd backend
php artisan test
cd apps/student_app
flutter test
```
### 2. تشغيل السيرفر عبر Docker:
<div dir="rtl" align="right">
الباك إند PHP أصلي ولا يوجد أمر `artisan` في النسخة المدققة. اقرأ سكربتات الاختبار قبل تشغيلها؛ بعضها يستدعي خدمات خارجية أو يكتب بيانات. خطة اختبارات التكامل المطلوبة موضحة في وثائق المرحلة القادمة.
### 2. تشغيل الـAPI محليًا:
من جذر المستودع:
</div>
```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)
- [تحليل الجدوى الشامل (FEASIBILITY_ANALYSIS.md)](./FEASIBILITY_ANALYSIS.md)
</div>
+1
View File
@@ -0,0 +1 @@
Failed
+45
View File
@@ -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
+45
View File
@@ -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'
+28
View File
@@ -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
+14
View File
@@ -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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

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>
+24
View File
@@ -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)
}
+6
View File
@@ -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")
+34
View File
@@ -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"
+43
View File
@@ -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"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

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"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

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>
+70
View File
@@ -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'));
}
}
}
+245
View File
@@ -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);
}
}
+1
View File
@@ -0,0 +1 @@
flutter/ephemeral
+128
View File
@@ -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}")
+6
View File
@@ -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_
+7
View File
@@ -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"))
}
+42
View File
@@ -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 */;
}

Some files were not shown because too many files have changed in this diff Show More