feat: استيراد كود سيرو إلى تريبز (سيرو @ecfe7568) — بلا تعديل

قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة
ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ.

الخريطة:
  backend · payment_server · loction_server · ride_server ·
  passenger_server · docker · dashboard · stress_test  → الجذر
  siro_rider  → apps/rider          siro_driver  → apps/driver
  siro_admin  → dashboards/admin    siro_service → dashboards/service
  android_bot → apps/android_bot    socialBot    → apps/socialBot

نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب)
لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً:
كل ما يلي يصير فرقاً مقروءاً مقابل المصدر.

لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز،
سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh
(ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في
مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و
dashboards/transit-web).

⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة:
1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر):
   كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner.
2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist)
   يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً.
3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع →
   يجب ضمّ الحزم داخله أسوة بـ apps/rider.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-27 05:14:13 +03:00
co-authored by Claude Opus 5
parent 9909d9b4c1
commit 4d8414c96b
3198 changed files with 766859 additions and 0 deletions
@@ -0,0 +1,151 @@
// transit_home_page.dart — الصفحة الرئيسية لتبويب "مواصلاتي"
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/colors.dart';
import '../../constant/style.dart';
import '../../controller/transit/transit_controller.dart';
import '../../controller/transit/transit_models.dart';
import '../widgets/my_scafold.dart';
import 'transit_org_browse_page.dart';
import 'transit_routes_page.dart';
class TransitHomePage extends StatelessWidget {
const TransitHomePage({super.key});
@override
Widget build(BuildContext context) {
Get.put(TransitController());
return GetBuilder<TransitController>(
builder: (c) => MyScafolld(
title: 'Mawasalati'.tr,
isleading: true,
body: [
Column(
children: [
Expanded(
child: RefreshIndicator(
onRefresh: c.fetchMyEnrollments,
child: c.isLoadingEnrollments
? const Center(child: CircularProgressIndicator())
: c.myEnrollments.isEmpty
? _emptyState(c)
: ListView(
padding: const EdgeInsets.all(16),
children: [
Text('My Memberships'.tr,
style: AppStyle.headTitle2.copyWith(fontSize: 18)),
const SizedBox(height: 12),
...c.myEnrollments.map((e) => _enrollmentCard(e, c)),
const SizedBox(height: 20),
_addOrgTile(),
],
),
),
),
],
),
],
),
);
}
Widget _emptyState(TransitController c) {
return ListView(
padding: const EdgeInsets.all(24),
children: [
const SizedBox(height: 60),
Icon(Icons.directions_bus_filled_outlined,
size: 72, color: AppColor.grayColor),
const SizedBox(height: 16),
Text(
'Not enrolled in any institution yet'.tr,
textAlign: TextAlign.center,
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(
'Activate your membership in your university or institution to track your bus live'.tr,
textAlign: TextAlign.center,
style: AppStyle.subtitle.copyWith(color: AppColor.grayColor),
),
const SizedBox(height: 20),
_addOrgTile(),
],
);
}
Widget _addOrgTile() {
return Card(
color: AppColor.cardColor,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
side: BorderSide(color: AppColor.borderColor),
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: CircleAvatar(
backgroundColor: AppColor.accentColor.withOpacity(0.15),
child: Icon(Icons.add, color: AppColor.accentColor),
),
title: Text('Join a new institution'.tr, style: AppStyle.title),
subtitle: Text('University, school, or hotel'.tr, style: AppStyle.subtitle),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () => Get.to(() => const TransitOrgBrowsePage()),
),
);
}
Widget _enrollmentCard(TransitEnrollment e, TransitController c) {
Color statusColor;
String statusText;
switch (e.status) {
case 'active':
statusColor = AppColor.greenColor;
statusText = 'Active'.tr;
break;
case 'pending':
statusColor = AppColor.yellowColor;
statusText = 'Pending Review'.tr;
break;
default:
statusColor = AppColor.redColor;
statusText = 'Suspended'.tr;
}
return Card(
margin: const EdgeInsets.only(bottom: 12),
color: AppColor.cardColor,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
side: BorderSide(color: AppColor.borderColor),
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: CircleAvatar(
backgroundColor: AppColor.primaryColor.withOpacity(0.1),
child: Icon(Icons.school_outlined, color: AppColor.primaryColor),
),
title: Text(e.orgName, style: AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
subtitle: Row(
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(color: statusColor, shape: BoxShape.circle),
),
const SizedBox(width: 6),
Text(statusText, style: AppStyle.subtitle.copyWith(color: statusColor)),
],
),
trailing: e.status == 'active' ? const Icon(Icons.arrow_forward_ios, size: 16) : null,
onTap: e.status == 'active'
? () => Get.to(() => TransitRoutesPage(orgId: e.orgId, orgName: e.orgName))
: null,
),
);
}
}
@@ -0,0 +1,3 @@
// transit_live_map_page.dart — محوّل إلى transit_map_page الجديدة
// احتُفظ بهذا الملف للتوافق مع أي مرجع موجود
export 'transit_map_page.dart';
@@ -0,0 +1,527 @@
// transit_map_page.dart — خريطة الباص الحي (مواصلاتي)
// خريطة مستقلة داخل مجلد transit — لا تمس home_captain ولا passenger map
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../constant/colors.dart';
import '../../constant/style.dart';
import '../../env/env.dart';
import '../../controller/transit/transit_controller.dart';
import 'transit_qr_code_page.dart';
import '../widgets/elevated_btn.dart';
// ── ألوان الصفحة ──────────────────────────────────────────────────────────────
Color get _bg =>
Get.isDarkMode ? const Color(0xFF0A0F1E) : const Color(0xFFF0F4FF);
Color get _card =>
Get.isDarkMode ? const Color(0xFF151C2E) : Colors.white;
Color get _text =>
Get.isDarkMode ? const Color(0xFFEEF2FF) : const Color(0xFF1E293B);
Color get _sub =>
Get.isDarkMode ? const Color(0xFF94A3B8) : const Color(0xFF64748B);
const Color _accent = Color(0xFF2563EB);
class TransitMapPage extends StatefulWidget {
final int routeId;
final String routeName;
const TransitMapPage({super.key, required this.routeId, required this.routeName});
@override
State<TransitMapPage> createState() => _TransitMapPageState();
}
class _TransitMapPageState extends State<TransitMapPage> {
IntaleqMapController? _mapCtrl;
InlqBitmap _busIcon = InlqBitmap.defaultMarkerWithHue(120);
bool _busIconLoaded = false;
bool _cameraLocked = true;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
// تحميل أيقونة الباص من الأصول (bus.png: 499×499، size يضبط الحجم المرئي ~80dp)
try {
_busIcon = InlqBitmap.fromAsset(
'assets/images/bus.png',
size: 0.40,
);
_busIconLoaded = true;
} catch (_) {
_busIcon = InlqBitmap.defaultMarkerWithHue(120);
}
// تحميل بيانات الرحلة + اشتراك السوكيت
await Get.find<TransitController>().openLiveRoute(widget.routeId);
// اشتراك FCM لتنبيهات الخط
Get.find<TransitController>().subscribeToRouteTopic(widget.routeId);
});
}
@override
void dispose() {
Get.find<TransitController>().closeLiveRoute();
Get.find<TransitController>().unsubscribeFromRouteTopic(widget.routeId);
super.dispose();
}
@override
Widget build(BuildContext context) {
final bottomPad = MediaQuery.of(context).padding.bottom;
return Scaffold(
backgroundColor: _bg,
body: GetBuilder<TransitController>(
builder: (c) {
// ── بناء ماركرات المحطات والباص ─────────────────────────
final stops = (c.liveTripData?['stops'] is List)
? List<Map>.from(c.liveTripData!['stops'])
: <Map>[];
final markers = <Marker>{};
// ماركر المحطات
for (final s in stops) {
final lat = double.tryParse(s['latitude']?.toString() ?? '') ?? 0;
final lng = double.tryParse(s['longitude']?.toString() ?? '') ?? 0;
if (lat == 0 && lng == 0) continue;
final isMajor = (s['is_major']?.toString() ?? '0') == '1';
markers.add(Marker(
markerId: MarkerId('stop_${s['id']}'),
position: LatLng(lat, lng),
infoWindow: InfoWindow(title: s['name_ar']?.toString() ?? ''),
icon: InlqBitmap.defaultMarkerWithHue(isMajor ? 30 : 200),
zIndex: 1,
));
}
// ماركر الباص — أيقونة مخصصة 80×80 بدون خلفية
LatLng? busPos;
if (c.liveBusPosition != null) {
busPos = LatLng(c.liveBusPosition!.lat, c.liveBusPosition!.lng);
markers.add(Marker(
markerId: const MarkerId('transit_bus'),
position: busPos,
rotation: c.liveBusPosition!.heading,
anchor: const Offset(0.5, 0.5),
flat: true,
zIndex: 3,
icon: _busIconLoaded ? _busIcon : InlqBitmap.defaultMarkerWithHue(120),
));
// تتبع الكاميرا للباص
if (_cameraLocked && _mapCtrl != null) {
_mapCtrl!.animateCamera(CameraUpdate.newLatLng(busPos));
}
}
// ── polyline الخط ──────────────────────────────────────
final polylines = <Polyline>{};
final encodedPolyline =
c.liveTripData?['trip']?['route_polyline']?.toString() ?? '';
if (encodedPolyline.isNotEmpty) {
final coords = PolylineUtils.decode(encodedPolyline);
if (coords.isNotEmpty) {
polylines.add(Polyline(
polylineId: const PolylineId('transit_route'),
points: coords,
color: _accent.withOpacity(0.7),
width: 4,
));
}
}
final initialTarget = busPos ??
(markers.isNotEmpty
? markers.first.position
: const LatLng(31.95, 35.93));
// ── معلومات البطاقة السفلية ────────────────────────────
final trip = c.liveTripData?['trip'];
final driverName = trip?['driver_name']?.toString() ?? '';
final departure = trip?['departure_time']?.toString() ?? '';
final delayMin =
int.tryParse(trip?['delay_minutes']?.toString() ?? '0') ?? 0;
final currentStopSeq = c.liveBusPosition?.currentStopSeq;
final currentStopName = currentStopSeq != null && stops.isNotEmpty
? stops
.where((s) =>
int.tryParse(s['sequence']?.toString() ?? '') ==
currentStopSeq)
.map((s) => s['name_ar']?.toString() ?? '')
.firstOrNull
: null;
return Stack(
children: [
// ── 1. الخريطة ─────────────────────────────────────
Listener(
onPointerDown: (_) {
if (_cameraLocked) setState(() => _cameraLocked = false);
},
child: IntaleqMap(
apiKey: Env.mapSaasKey,
initialCameraPosition:
CameraPosition(target: initialTarget, zoom: 14.5),
markers: markers,
polylines: polylines,
onMapCreated: (ctrl) => _mapCtrl = ctrl,
mapType: Get.isDarkMode
? IntaleqMapType.normal
: IntaleqMapType.light,
myLocationEnabled: true,
),
),
// ── 2. شريط العنوان ────────────────────────────────
Positioned(
top: MediaQuery.of(context).padding.top + 8,
left: 12,
right: 12,
child: Row(
children: [
// زر الرجوع
GestureDetector(
onTap: () => Get.back(),
child: Container(
width: 42,
height: 42,
decoration: BoxDecoration(
color: _card,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.12),
blurRadius: 10,
offset: const Offset(0, 3))
],
),
child: Icon(Icons.arrow_back_ios_rounded,
color: _text, size: 18),
),
),
const SizedBox(width: 10),
// اسم الخط
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: _card,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.08),
blurRadius: 10,
offset: const Offset(0, 3))
],
),
child: Row(
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: c.liveBusPosition != null
? Colors.greenAccent.shade400
: Colors.grey,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
widget.routeName,
style: AppStyle.title.copyWith(
fontSize: 14,
color: _text,
fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
),
const SizedBox(width: 10),
// زر إعادة تثبيت الكاميرا
if (!_cameraLocked)
GestureDetector(
onTap: () {
setState(() => _cameraLocked = true);
if (busPos != null && _mapCtrl != null) {
_mapCtrl!.animateCamera(
CameraUpdate.newLatLngZoom(busPos!, 15));
}
},
child: Container(
width: 42,
height: 42,
decoration: BoxDecoration(
color: _accent,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: _accent.withValues(alpha: 0.4),
blurRadius: 10,
offset: const Offset(0, 3))
],
),
child: const Icon(Icons.my_location_rounded,
color: Colors.white, size: 20),
),
),
],
),
),
// ── 3. بانر التأخير ────────────────────────────────
if (delayMin > 0)
Positioned(
top: MediaQuery.of(context).padding.top + 66,
left: 12,
right: 12,
child: _statusBanner(
'🕐 ${'Bus is delayed'.tr} $delayMin ${'min'.tr}',
const Color(0xFFF59E0B),
),
),
// ── 4. حالة التحميل أو الخطأ ─────────────────────
if (c.isLoadingLiveTrip)
Positioned.fill(
child: Container(
color: Colors.black26,
child: const Center(child: CircularProgressIndicator()),
),
),
if (!c.isLoadingLiveTrip && c.liveError.isNotEmpty)
Positioned(
top: MediaQuery.of(context).padding.top + 70,
left: 16,
right: 16,
child: _statusBanner(c.liveError, Colors.redAccent),
),
if (!c.isLoadingLiveTrip &&
c.liveError.isEmpty &&
c.liveBusPosition == null &&
!c.isLoadingLiveTrip)
Positioned(
top: MediaQuery.of(context).padding.top + 70,
left: 16,
right: 16,
child: _statusBanner(
'No bus running on this route right now'.tr,
const Color(0xFFF59E0B),
),
),
// ── 5. بطاقة المعلومات السفلية ─────────────────────
if (!c.isLoadingLiveTrip && trip != null)
Positioned(
bottom: bottomPad + 12,
left: 12,
right: 12,
child: _InfoCard(
driverName: driverName,
departure: departure,
currentStopName: currentStopName,
totalStops: stops.length,
currentStopSeq: currentStopSeq,
onMissedBus: () {
// CTA: فاتك الباص؟ اطلب رحلة
Get.back();
Get.toNamed('/home');
},
),
),
],
);
},
),
);
}
Widget _statusBanner(String text, Color color) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: _card,
borderRadius: BorderRadius.circular(14),
border: Border(right: BorderSide(color: color, width: 4)),
boxShadow: [
BoxShadow(color: Colors.black.withValues(alpha: 0.08), blurRadius: 8)
],
),
child: Text(text,
style: AppStyle.subtitle.copyWith(
color: color, fontWeight: FontWeight.bold, fontSize: 13)),
);
}
}
// ── بطاقة معلومات الرحلة ────────────────────────────────────────────────────
class _InfoCard extends StatelessWidget {
final String driverName;
final String departure;
final String? currentStopName;
final int totalStops;
final int? currentStopSeq;
final VoidCallback onMissedBus;
const _InfoCard({
required this.driverName,
required this.departure,
required this.currentStopName,
required this.totalStops,
required this.currentStopSeq,
required this.onMissedBus,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: _card,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.12),
blurRadius: 20,
offset: const Offset(0, -4))
],
),
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// ── صف المعلومات ─────────────────────────────
Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: _accent.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(Icons.directions_bus_rounded,
color: _accent, size: 26),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (driverName.isNotEmpty)
Text(driverName,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: _text)),
const SizedBox(height: 2),
Row(
children: [
if (departure.isNotEmpty) ...[
Icon(Icons.schedule_rounded, size: 13, color: _sub),
const SizedBox(width: 3),
Text(departure,
style: TextStyle(fontSize: 12, color: _sub)),
const SizedBox(width: 12),
],
if (currentStopSeq != null) ...[
Icon(Icons.location_on_rounded,
size: 13, color: Colors.greenAccent.shade400),
const SizedBox(width: 3),
Text(
currentStopName ??
'${'Stop'.tr} $currentStopSeq',
style:
TextStyle(fontSize: 12, color: _sub),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
],
),
],
),
),
// عداد المحطات
if (totalStops > 0 && currentStopSeq != null)
Column(
children: [
Text(
'$currentStopSeq/$totalStops',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: _accent),
),
Text('Stops'.tr,
style: TextStyle(fontSize: 10, color: _sub)),
],
),
],
),
// ── شريط التقدم بين المحطات ─────────────────
if (totalStops > 0 && currentStopSeq != null) ...[
const SizedBox(height: 12),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: currentStopSeq! / totalStops,
backgroundColor: _accent.withValues(alpha: 0.12),
valueColor: const AlwaysStoppedAnimation<Color>(_accent),
minHeight: 6,
),
),
],
const SizedBox(height: 12),
// ── زر "بطاقة الصعود" (Boarding Pass) ─────────────────
SizedBox(
width: double.infinity,
height: 55,
child: MyElevatedButton(
onPressed: () {
Get.to(() => const TransitQRCodePage());
},
kolor: _accent,
title: 'بطاقة الصعود (Boarding Pass)'.tr,
),
),
const SizedBox(height: 8),
// ── زر "فاتك الباص؟" ─────────────────────────
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: onMissedBus,
style: OutlinedButton.styleFrom(
side: BorderSide(color: _accent.withValues(alpha: 0.5)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 10),
),
icon: const Icon(Icons.local_taxi_rounded, size: 18),
label: Text(
'Missed the bus? Book a ride now'.tr,
style: const TextStyle(fontSize: 13),
),
),
),
],
),
);
}
}
@@ -0,0 +1,160 @@
// transit_org_browse_page.dart — تصفح المؤسسات + تفعيل عضوية بالرقم الجامعي
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/colors.dart';
import '../../constant/style.dart';
import '../../controller/transit/transit_controller.dart';
import '../../controller/transit/transit_models.dart';
import '../widgets/elevated_btn.dart';
import '../widgets/my_scafold.dart';
class TransitOrgBrowsePage extends StatefulWidget {
const TransitOrgBrowsePage({super.key});
@override
State<TransitOrgBrowsePage> createState() => _TransitOrgBrowsePageState();
}
class _TransitOrgBrowsePageState extends State<TransitOrgBrowsePage> {
final _searchCtrl = TextEditingController();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
Get.find<TransitController>().browseOrgs();
});
}
@override
Widget build(BuildContext context) {
return GetBuilder<TransitController>(
builder: (c) => MyScafolld(
title: 'Choose your institution'.tr,
isleading: true,
body: [
Column(
children: [
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: TextField(
controller: _searchCtrl,
onSubmitted: (v) => c.browseOrgs(search: v),
decoration: InputDecoration(
hintText: 'Search for a university or institution...'.tr,
prefixIcon: const Icon(Icons.search),
filled: true,
fillColor: AppColor.cardColor,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: AppColor.borderColor),
),
),
),
),
Expanded(
child: c.isLoadingOrgs
? const Center(child: CircularProgressIndicator())
: c.browsableOrgs.isEmpty
? Center(
child: Text('No results'.tr, style: AppStyle.title),
)
: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: c.browsableOrgs.length,
itemBuilder: (_, i) {
final org = c.browsableOrgs[i];
return Card(
margin: const EdgeInsets.only(bottom: 10),
color: AppColor.cardColor,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
side: BorderSide(color: AppColor.borderColor),
),
child: ListTile(
title:
Text(org.nameAr, style: AppStyle.title),
subtitle:
Text(org.city, style: AppStyle.subtitle),
trailing: const Icon(Icons.arrow_forward_ios,
size: 16),
onTap: () =>
_showActivateSheet(context, c, org),
),
);
},
),
),
],
),
],
),
);
}
void _showActivateSheet(
BuildContext context, TransitController c, TransitOrg org) {
final idCtrl = TextEditingController();
bool loading = false;
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: AppColor.cardColor,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (ctx) => StatefulBuilder(
builder: (ctx, setState) => Padding(
padding: EdgeInsets.only(
left: 20,
right: 20,
top: 20,
bottom: MediaQuery.of(ctx).viewInsets.bottom + 20,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(org.nameAr,
style: AppStyle.headTitle2.copyWith(fontSize: 18)),
const SizedBox(height: 6),
Text('Enter your student/employee ID to activate membership'.tr,
style: AppStyle.subtitle),
const SizedBox(height: 16),
TextField(
controller: idCtrl,
decoration: InputDecoration(
hintText: 'Student ID'.tr,
filled: true,
fillColor: AppColor.secondaryColor,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12)),
),
),
const SizedBox(height: 16),
MyElevatedButton(
title: 'Activate'.tr,
isLoading: loading,
onPressed: () async {
if (idCtrl.text.trim().isEmpty) return;
setState(() => loading = true);
final ok =
await c.activateEnrollment(org.id, idCtrl.text.trim());
setState(() => loading = false);
if (ok && ctx.mounted) {
Navigator.pop(ctx);
Get.back(); // العودة للصفحة الرئيسية لمواصلاتي
}
},
),
],
),
),
),
);
}
}
@@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../constant/colors.dart';
import '../../constant/style.dart';
import '../../constant/box_name.dart';
import '../../main.dart';
class TransitQRCodePage extends StatelessWidget {
const TransitQRCodePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final String passengerId = box.read(BoxName.passengerID) ?? '';
final String name = box.read(BoxName.name) ?? '';
// The payload for the QR Code
// You can later encrypt this or make it dynamic for security.
final String qrPayload = 'transit_board:$passengerId';
return Scaffold(
backgroundColor: AppColor.secondaryColor,
appBar: AppBar(
title: Text('بطاقة الصعود'.tr),
backgroundColor: AppColor.secondaryColor,
elevation: 0,
foregroundColor: AppColor.writeColor,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'يرجى إبراز هذا الرمز للسائق عند الصعود للحافلة'.tr,
textAlign: TextAlign.center,
style: AppStyle.title.copyWith(color: AppColor.grayColor),
),
const SizedBox(height: 30),
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
spreadRadius: 2,
),
],
),
child: Column(
children: [
QrImageView(
data: qrPayload,
version: QrVersions.auto,
size: 250.0,
foregroundColor: Colors.black,
),
const SizedBox(height: 20),
Text(
name,
style: AppStyle.headTitle2,
),
const SizedBox(height: 5),
Text(
'مسافر موثق',
style: AppStyle.title.copyWith(
color: AppColor.greenColor,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
),
);
}
}
@@ -0,0 +1,80 @@
// transit_routes_page.dart — خطوط مؤسسة الراكب
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/colors.dart';
import '../../constant/style.dart';
import '../../controller/transit/transit_controller.dart';
import '../widgets/my_scafold.dart';
import 'transit_map_page.dart';
class TransitRoutesPage extends StatefulWidget {
final int orgId;
final String orgName;
const TransitRoutesPage({super.key, required this.orgId, required this.orgName});
@override
State<TransitRoutesPage> createState() => _TransitRoutesPageState();
}
class _TransitRoutesPageState extends State<TransitRoutesPage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
Get.find<TransitController>().openOrgRoutes(widget.orgId);
});
}
@override
Widget build(BuildContext context) {
return GetBuilder<TransitController>(
builder: (c) => MyScafolld(
title: widget.orgName,
isleading: true,
body: [
Column(
children: [
Expanded(
child: c.isLoadingRoutes
? const Center(child: CircularProgressIndicator())
: c.currentOrgRoutes.isEmpty
? Center(
child: Text('No active routes currently'.tr, style: AppStyle.title),
)
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: c.currentOrgRoutes.length,
itemBuilder: (_, i) {
final r = c.currentOrgRoutes[i];
return Card(
margin: const EdgeInsets.only(bottom: 10),
color: AppColor.cardColor,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
side: BorderSide(color: AppColor.borderColor),
),
child: ListTile(
leading: CircleAvatar(
backgroundColor: AppColor.accentColor.withOpacity(0.15),
child: Icon(Icons.directions_bus, color: AppColor.accentColor),
),
title: Text(r.nameAr, style: AppStyle.title),
subtitle: Text('${r.stopCount} ${'Stops'.tr}', style: AppStyle.subtitle),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () => Get.to(
() => TransitMapPage(routeId: r.id, routeName: r.nameAr),
),
),
);
},
),
),
],
),
],
),
);
}
}