refactor: disable Mawasalati feature, wrap controller initialization in post-frame callbacks, and add documentation plan
This commit is contained in:
@@ -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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
Get.find<TransitAdminController>().fetchOrgAdmins(widget.orgId);
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
Get.find<TransitAdminController>().fetchOrgAdmins(widget.orgId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ class _TransitOrgDetailsPageState extends State<TransitOrgDetailsPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
Get.find<TransitAdminController>().loadOrgDetails(widget.orgId);
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
Get.find<TransitAdminController>().loadOrgDetails(widget.orgId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -56,11 +56,11 @@ class AppDrawer extends StatelessWidget {
|
|||||||
|
|
||||||
// 2. تعريف بيانات القائمة بشكل مركزي ومنظم
|
// 2. تعريف بيانات القائمة بشكل مركزي ومنظم
|
||||||
final List<DrawerItem> drawerItems = [
|
final List<DrawerItem> drawerItems = [
|
||||||
DrawerItem(
|
// DrawerItem(
|
||||||
title: 'Mawasalati'.tr,
|
// title: 'Mawasalati'.tr,
|
||||||
icon: Icons.directions_bus_filled_rounded,
|
// icon: Icons.directions_bus_filled_rounded,
|
||||||
color: Colors.teal,
|
// color: Colors.teal,
|
||||||
onTap: () => Get.to(() => const TransitDriverHomePage())),
|
// onTap: () => Get.to(() => const TransitDriverHomePage())),
|
||||||
DrawerItem(
|
DrawerItem(
|
||||||
title: 'Balance'.tr,
|
title: 'Balance'.tr,
|
||||||
icon: Icons.account_balance_wallet,
|
icon: Icons.account_balance_wallet,
|
||||||
|
|||||||
@@ -149,11 +149,11 @@ class MapMenuWidget extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 12, vertical: 4),
|
horizontal: 12, vertical: 4),
|
||||||
children: [
|
children: [
|
||||||
MenuListItem(
|
// MenuListItem(
|
||||||
title: 'Mawasalati'.tr,
|
// title: 'Mawasalati'.tr,
|
||||||
icon: Icons.directions_bus_filled_rounded,
|
// icon: Icons.directions_bus_filled_rounded,
|
||||||
onTap: () => Get.to(() => const TransitHomePage()),
|
// onTap: () => Get.to(() => const TransitHomePage()),
|
||||||
),
|
// ),
|
||||||
MenuListItem(
|
MenuListItem(
|
||||||
title: 'My Balance'.tr,
|
title: 'My Balance'.tr,
|
||||||
icon: Icons.account_balance_wallet_outlined,
|
icon: Icons.account_balance_wallet_outlined,
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ class _TransitLiveMapPageState extends State<TransitLiveMapPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
Get.find<TransitController>().openLiveRoute(widget.routeId);
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
Get.find<TransitController>().openLiveRoute(widget.routeId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -78,52 +80,56 @@ class _TransitLiveMapPageState extends State<TransitLiveMapPage> {
|
|||||||
title: widget.routeName,
|
title: widget.routeName,
|
||||||
isleading: true,
|
isleading: true,
|
||||||
body: [
|
body: [
|
||||||
Expanded(
|
Column(
|
||||||
child: Stack(
|
children: [
|
||||||
children: [
|
Expanded(
|
||||||
IntaleqMap(
|
child: Stack(
|
||||||
apiKey: Env.mapSaasKey,
|
children: [
|
||||||
initialCameraPosition: CameraPosition(target: initialTarget, zoom: 14),
|
IntaleqMap(
|
||||||
markers: markers,
|
apiKey: Env.mapSaasKey,
|
||||||
onMapCreated: (ctrl) => _mapController = ctrl,
|
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user