refactor: disable Mawasalati feature, wrap controller initialization in post-frame callbacks, and add documentation plan

This commit is contained in:
Hamza-Ayed
2026-07-12 06:27:36 +03:00
parent 08340493c0
commit 8fa2f153c1
6 changed files with 114 additions and 58 deletions
+46
View File
@@ -0,0 +1,46 @@
# خطة مواصلاتي (Siro Transit) - ما تم إنجازه وما لم يتم
تُوثق هذه الوثيقة حالة ميزة "مواصلاتي" (النقل المؤسسي للجامعات والفنادق) في منصة سيرو، وتفصل ما تم بناءه حتى الآن وما يتبقى إكماله قبل الإطلاق النهائي.
## ✅ أولاً: ما تم إنجازه
### 1. البنية التحتية وقواعد البيانات (Backend)
- تصميم قواعد البيانات وإضافة الجداول اللازمة (مؤسسات، خطوط، محطات، جداول زمنية، رحلات، واشتراكات الركاب).
- بناء مسارات الـ API بلغة PHP لخدمة الراكب والسائق (جلب المؤسسات، المسارات، المحطات، الرحلات اليومية).
### 2. تطبيق الراكب (Rider App)
- شاشة **تصفح المؤسسات** (`TransitOrgBrowsePage`) لعرض الجامعات والفنادق المتاحة.
- شاشة **مسارات المؤسسة** (`TransitRoutesPage`) لعرض الخطوط والمحطات على الخريطة.
- شاشة **الصفحة الرئيسية للمواصلاتي** (`TransitHomePage`) لعرض اشتراكات الراكب الحالية.
- شاشة **التتبع الحي** (`TransitLiveMapPage`) المهيأة مبدئياً لتتبع الباص.
- إصلاح مشاكل الـ UI (ParentDataWidget و initState).
### 3. تطبيق السائق (Driver App)
- شاشة **الرحلات المجدولة** (`TransitDriverHomePage`) لعرض الرحلات المطلوبة من السائق اليوم، مع أزرار "بدء الرحلة" و"إنهاء الرحلة".
- تحسين خريطة السائق الأساسية (`home_captin.dart`) وتفعيل ماركر السيارة الصحيح (`car_icon`) ليكون جاهزاً لبث الموقع الفعلي أثناء الرحلة، وإصلاح مشاكل الـ UI (Overflow).
---
## ⏳ ثانياً: ما لم يتم إنجازه (الخطوات القادمة والأولويات)
> [!IMPORTANT]
> تم تعليق (إخفاء) أزرار الوصول لخدمة "مواصلاتي" مؤقتاً من تطبيقي الراكب والسائق حتى يتم الانتهاء من هذه الخطوات واختبارها فعلياً على سيرفرات الإنتاج.
### أولوية 1: الاختبار الميداني الحقيقي
- **بث واستقبال الموقع الحي (WebSockets):** تشغيل واختبار `driver_socket.php` و `passenger_socket.php` على سيرفر الإنتاج الفعلي.
- التأكد من إعدادات الـ Redis (`REDIS_LOCATION_HOST`) في الـ `.env`.
- التأكد من أن "ماركر" الباص يتحرك بسلاسة على خريطة الراكب عندما يبدأ السائق الرحلة.
### أولوية 2: دورة حياة الرحلة للسائق والتذاكر
- **وضع التوجيه للسائق (Active Trip Navigation):** شاشة تظهر للسائق عند الضغط على "بدء الرحلة" لترشده للمحطات بالترتيب وتتبع تقدمه.
- **الاشتراكات والتذاكر (Ticketing):** شراء الراكب للاشتراك وخصم الرصيد من المحفظة.
- **مسح كود الصعود (QR Boarding):** آلية لكي يثبت الراكب صعوده للباص (إما السائق يمسح كود الراكب، أو يعلّم عليه في قائمة الركاب).
### أولوية 3: لوحة تحكم المؤسسات والتنبيهات
- **واجهة الويب للمؤسسات (B2B Admin Dashboard):** واجهة ويب تتيح لمدير الحركة في الجامعة إضافة المسارات، الباصات، وتتبع الأسطول.
- **استيراد كشف الركاب:** واجهة لرفع ملفات CSV (`import_roster.php`) لتعريف طلاب الجامعة المسموح لهم بالركوب.
- **التنبيهات الجغرافية (Geofence Push Notifications):** تفعيل إشعارات FCM ("الباص على بُعد 5 دقائق من محطتك").
- **إدارة العقود:** زر في تطبيق `siro_admin` لتفعيل/تعليق عقود المؤسسات (`contract_status`).
---
سيتم الرجوع لهذه الخطة واستئناف العمل عليها في المرحلة القادمة بعد الانتهاء من الأولويات الحالية للنظام.
@@ -28,7 +28,9 @@ class _TransitOrgAdminsPageState extends State<TransitOrgAdminsPage> {
@override
void initState() {
super.initState();
Get.find<TransitAdminController>().fetchOrgAdmins(widget.orgId);
WidgetsBinding.instance.addPostFrameCallback((_) {
Get.find<TransitAdminController>().fetchOrgAdmins(widget.orgId);
});
}
@override
@@ -19,7 +19,9 @@ class _TransitOrgDetailsPageState extends State<TransitOrgDetailsPage> {
@override
void initState() {
super.initState();
Get.find<TransitAdminController>().loadOrgDetails(widget.orgId);
WidgetsBinding.instance.addPostFrameCallback((_) {
Get.find<TransitAdminController>().loadOrgDetails(widget.orgId);
});
}
@override
@@ -56,11 +56,11 @@ class AppDrawer extends StatelessWidget {
// 2. تعريف بيانات القائمة بشكل مركزي ومنظم
final List<DrawerItem> drawerItems = [
DrawerItem(
title: 'Mawasalati'.tr,
icon: Icons.directions_bus_filled_rounded,
color: Colors.teal,
onTap: () => Get.to(() => const TransitDriverHomePage())),
// DrawerItem(
// title: 'Mawasalati'.tr,
// icon: Icons.directions_bus_filled_rounded,
// color: Colors.teal,
// onTap: () => Get.to(() => const TransitDriverHomePage())),
DrawerItem(
title: 'Balance'.tr,
icon: Icons.account_balance_wallet,
@@ -149,11 +149,11 @@ class MapMenuWidget extends StatelessWidget {
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 4),
children: [
MenuListItem(
title: 'Mawasalati'.tr,
icon: Icons.directions_bus_filled_rounded,
onTap: () => Get.to(() => const TransitHomePage()),
),
// MenuListItem(
// title: 'Mawasalati'.tr,
// icon: Icons.directions_bus_filled_rounded,
// onTap: () => Get.to(() => const TransitHomePage()),
// ),
MenuListItem(
title: 'My Balance'.tr,
icon: Icons.account_balance_wallet_outlined,
@@ -24,7 +24,9 @@ class _TransitLiveMapPageState extends State<TransitLiveMapPage> {
@override
void initState() {
super.initState();
Get.find<TransitController>().openLiveRoute(widget.routeId);
WidgetsBinding.instance.addPostFrameCallback((_) {
Get.find<TransitController>().openLiveRoute(widget.routeId);
});
}
@override
@@ -78,52 +80,56 @@ class _TransitLiveMapPageState extends State<TransitLiveMapPage> {
title: widget.routeName,
isleading: true,
body: [
Expanded(
child: Stack(
children: [
IntaleqMap(
apiKey: Env.mapSaasKey,
initialCameraPosition: CameraPosition(target: initialTarget, zoom: 14),
markers: markers,
onMapCreated: (ctrl) => _mapController = ctrl,
Column(
children: [
Expanded(
child: Stack(
children: [
IntaleqMap(
apiKey: Env.mapSaasKey,
initialCameraPosition: CameraPosition(target: initialTarget, zoom: 14),
markers: markers,
onMapCreated: (ctrl) => _mapController = ctrl,
),
if (c.isLoadingLiveTrip)
const Positioned.fill(
child: ColoredBox(
color: Colors.black12,
child: Center(child: CircularProgressIndicator()),
),
),
if (!c.isLoadingLiveTrip && c.liveError.isNotEmpty)
Positioned(
top: 16,
left: 16,
right: 16,
child: _statusBanner(c.liveError, AppColor.redColor),
),
if (!c.isLoadingLiveTrip &&
c.liveError.isEmpty &&
c.liveBusPosition == null)
Positioned(
top: 16,
left: 16,
right: 16,
child: _statusBanner(
'No bus running on this route right now'.tr, AppColor.yellowColor),
),
if (c.liveTripData?['trip']?['delay_minutes'] != null &&
(int.tryParse(c.liveTripData!['trip']['delay_minutes'].toString()) ?? 0) > 0)
Positioned(
bottom: 24,
left: 16,
right: 16,
child: _statusBanner(
'${'Bus is delayed'.tr} ${c.liveTripData!['trip']['delay_minutes']} ${'min'.tr}',
AppColor.yellowColor,
),
),
],
),
if (c.isLoadingLiveTrip)
const Positioned.fill(
child: ColoredBox(
color: Colors.black12,
child: Center(child: CircularProgressIndicator()),
),
),
if (!c.isLoadingLiveTrip && c.liveError.isNotEmpty)
Positioned(
top: 16,
left: 16,
right: 16,
child: _statusBanner(c.liveError, AppColor.redColor),
),
if (!c.isLoadingLiveTrip &&
c.liveError.isEmpty &&
c.liveBusPosition == null)
Positioned(
top: 16,
left: 16,
right: 16,
child: _statusBanner(
'No bus running on this route right now'.tr, AppColor.yellowColor),
),
if (c.liveTripData?['trip']?['delay_minutes'] != null &&
(int.tryParse(c.liveTripData!['trip']['delay_minutes'].toString()) ?? 0) > 0)
Positioned(
bottom: 24,
left: 16,
right: 16,
child: _statusBanner(
'${'Bus is delayed'.tr} ${c.liveTripData!['trip']['delay_minutes']} ${'min'.tr}',
AppColor.yellowColor,
),
),
],
),
),
],
),
],
);