Update: 2026-07-12 05:40:28
This commit is contained in:
@@ -46,6 +46,19 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
||||
bool isSocketConnected = false;
|
||||
Timer? _socketHeartbeat;
|
||||
|
||||
// 🚌 مواصلاتي — وضع الباص: بينما نشط، الموقع يُبَث فقط عبر update_bus_location
|
||||
// ولا يدخل حوض الرحلات العادي (geo:drivers:*)
|
||||
bool isBusMode = false;
|
||||
int? activeBusTripId;
|
||||
int? activeBusRouteId;
|
||||
|
||||
void setBusMode({required bool enabled, int? tripId, int? routeId}) {
|
||||
isBusMode = enabled;
|
||||
activeBusTripId = enabled ? tripId : null;
|
||||
activeBusRouteId = enabled ? routeId : null;
|
||||
Log.print('🚌 Bus mode ${enabled ? "enabled" : "disabled"} (trip: $tripId, route: $routeId)');
|
||||
}
|
||||
|
||||
StreamSubscription<LocationData>? _locSub;
|
||||
StreamSubscription<BatteryState>? _batterySub;
|
||||
|
||||
@@ -431,7 +444,11 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
||||
// الـ _locSub يرسل update_location عند كل تحرك (كل 5-10 ثوانٍ) تلقائياً.
|
||||
// الـ heartbeat يكون مفيداً فقط عندما يتوقف الـ stream (الجهاز ثابت أو أوقف الخدمة).
|
||||
if (_locSub != null) return;
|
||||
if (socket != null && isSocketConnected && myLocation.latitude != 0) {
|
||||
if (socket == null || !isSocketConnected || myLocation.latitude == 0) return;
|
||||
|
||||
if (isBusMode) {
|
||||
emitBusLocationToSocket(myLocation, heading, speed);
|
||||
} else {
|
||||
emitLocationToSocket(myLocation, heading, speed);
|
||||
}
|
||||
});
|
||||
@@ -477,6 +494,22 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
||||
socket!.emit('update_location', payload);
|
||||
}
|
||||
}
|
||||
|
||||
// 🚌 مواصلاتي — بثّ موقع الباص (بديل عن update_location أثناء وضع الباص)
|
||||
void emitBusLocationToSocket(LatLng pos, double head, double spd, {int? currentStopSeq}) {
|
||||
if (activeBusTripId == null || activeBusRouteId == null) return;
|
||||
if (socket == null || !socket!.connected) return;
|
||||
|
||||
socket!.emit('update_bus_location', {
|
||||
'trip_id': activeBusTripId,
|
||||
'route_id': activeBusRouteId,
|
||||
'lat': pos.latitude,
|
||||
'lng': pos.longitude,
|
||||
'heading': head,
|
||||
'speed': spd * 3.6,
|
||||
if (currentStopSeq != null) 'current_stop_seq': currentStopSeq,
|
||||
});
|
||||
}
|
||||
// ===================================================================
|
||||
// ====== Tracking Logic ======
|
||||
// ===================================================================
|
||||
@@ -535,6 +568,14 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
||||
_lastPosForDistance = pos;
|
||||
|
||||
update();
|
||||
|
||||
// 🚌 وضع الباص: بثّ موقع الباص فقط — لا يدخل حوض الرحلات العادي
|
||||
if (isBusMode) {
|
||||
emitBusLocationToSocket(pos, heading, speed);
|
||||
await _saveBehaviorIfMoved(pos, now, currentSpeed: speed);
|
||||
return;
|
||||
}
|
||||
|
||||
emitLocationToSocket(pos, heading, speed);
|
||||
|
||||
if (Get.isRegistered<HomeCaptainController>()) {
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
final Map<String, String> ar_eg = {
|
||||
// ── مواصلاتي (Mawasalati) ──
|
||||
"Mawasalati": "مواصلاتي",
|
||||
"This account is not registered as a bus driver in any institution":
|
||||
"هذا الحساب غير مسجّل كسائق باص في أي مؤسسة",
|
||||
"No trips today": "لا توجد رحلات اليوم",
|
||||
"Stops": "محطات",
|
||||
"Delayed by": "متأخر",
|
||||
"Trip completed": "اكتملت الرحلة",
|
||||
"Report Delay": "الإبلاغ عن تأخير",
|
||||
"Number of minutes": "عدد الدقائق",
|
||||
"Send": "إرسال",
|
||||
" \${durationController.jsonData1['message'][0]['day'].toString().split('-')[1]}": " \${durationController.jsonData1['message'][0]['day'].toString().split('-')[1]}",
|
||||
" \\\${durationController.jsonData1['message'][0]['day'].toString().split('-')[1]}": " \\\${durationController.jsonData1['message'][0]['day'].toString().split('-')[1]}",
|
||||
" and acknowledge our Privacy Policy.": "وأوافق على سياسة الخصوصية.",
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
final Map<String, String> ar_jo = {
|
||||
// ── مواصلاتي (Mawasalati) ──
|
||||
"Mawasalati": "مواصلاتي",
|
||||
"This account is not registered as a bus driver in any institution":
|
||||
"هذا الحساب غير مسجّل كسائق باص في أي مؤسسة",
|
||||
"No trips today": "لا توجد رحلات اليوم",
|
||||
"Stops": "محطات",
|
||||
"Delayed by": "متأخر",
|
||||
"Trip completed": "اكتملت الرحلة",
|
||||
"Report Delay": "الإبلاغ عن تأخير",
|
||||
"Number of minutes": "عدد الدقائق",
|
||||
"Send": "إرسال",
|
||||
" \${durationController.jsonData1['message'][0]['day'].toString().split('-')[1]}": " \${durationController.jsonData1['message'][0]['day'].toString().split('-')[1]}",
|
||||
" \\\${durationController.jsonData1['message'][0]['day'].toString().split('-')[1]}": " \\\${durationController.jsonData1['message'][0]['day'].toString().split('-')[1]}",
|
||||
" and acknowledge our Privacy Policy.": "وأوافق على سياسة الخصوصية الخاصة بنا.",
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
final Map<String, String> ar_sy = {
|
||||
// ── مواصلاتي (Mawasalati) ──
|
||||
"Mawasalati": "مواصلاتي",
|
||||
"This account is not registered as a bus driver in any institution":
|
||||
"هذا الحساب غير مسجّل كسائق باص في أي مؤسسة",
|
||||
"No trips today": "لا توجد رحلات اليوم",
|
||||
"Stops": "محطات",
|
||||
"Delayed by": "متأخر",
|
||||
"Trip completed": "اكتملت الرحلة",
|
||||
"Report Delay": "الإبلاغ عن تأخير",
|
||||
"Number of minutes": "عدد الدقائق",
|
||||
"Send": "إرسال",
|
||||
" \${durationController.jsonData1['message'][0]['day'].toString().split('-')[1]}": " \${durationController.jsonData1['message'][0]['day'].toString().split('-')[1]}",
|
||||
" \\\${durationController.jsonData1['message'][0]['day'].toString().split('-')[1]}": " \\\${durationController.jsonData1['message'][0]['day'].toString().split('-')[1]}",
|
||||
" and acknowledge our Privacy Policy.": "وبوافق على سياسة الخصوصية.",
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
// transit_driver_controller.dart — تحكم وضع الباص (جهة السائق)
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart' show LatLng;
|
||||
|
||||
import '../functions/location_controller.dart';
|
||||
import 'transit_driver_models.dart';
|
||||
import 'transit_driver_service.dart';
|
||||
|
||||
class TransitDriverController extends GetxController {
|
||||
bool isCheckingBusDriver = true;
|
||||
bool isBusDriver = false;
|
||||
int? driverTransitId;
|
||||
String orgName = '';
|
||||
|
||||
bool isLoadingTrips = false;
|
||||
List<TransitDriverTrip> todayTrips = [];
|
||||
|
||||
TransitDriverTrip? activeTrip;
|
||||
bool isActionInProgress = false;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
checkBusDriverStatus();
|
||||
}
|
||||
|
||||
Future<void> checkBusDriverStatus() async {
|
||||
isCheckingBusDriver = true;
|
||||
update();
|
||||
|
||||
final res = await TransitDriverService.checkIsBusDriver();
|
||||
if (res.success && res.data != null) {
|
||||
isBusDriver = res.data!['is_bus_driver'] == true;
|
||||
if (isBusDriver) {
|
||||
driverTransitId = int.tryParse(res.data!['driver_transit_id'].toString());
|
||||
orgName = res.data!['org_name']?.toString() ?? '';
|
||||
await fetchTodayTrips();
|
||||
}
|
||||
}
|
||||
isCheckingBusDriver = false;
|
||||
update();
|
||||
}
|
||||
|
||||
Future<void> fetchTodayTrips() async {
|
||||
if (driverTransitId == null) return;
|
||||
isLoadingTrips = true;
|
||||
update();
|
||||
|
||||
final res = await TransitDriverService.getTodayTrips(driverTransitId!);
|
||||
if (res.success) {
|
||||
todayTrips = res.data ?? [];
|
||||
final started = todayTrips.where((t) => t.status == 'started');
|
||||
activeTrip = started.isNotEmpty ? started.first : null;
|
||||
|
||||
// إن كانت هناك رحلة قيد التشغيل بالفعل (مثلاً بعد إعادة فتح التطبيق)، فعّل وضع الباص
|
||||
if (activeTrip != null && Get.isRegistered<LocationController>()) {
|
||||
Get.find<LocationController>().setBusMode(
|
||||
enabled: true,
|
||||
tripId: activeTrip!.id,
|
||||
routeId: activeTrip!.routeId,
|
||||
);
|
||||
}
|
||||
}
|
||||
isLoadingTrips = false;
|
||||
update();
|
||||
}
|
||||
|
||||
Future<bool> startTrip(TransitDriverTrip trip) async {
|
||||
if (driverTransitId == null || isActionInProgress) return false;
|
||||
isActionInProgress = true;
|
||||
update();
|
||||
|
||||
LatLng pos = const LatLng(0, 0);
|
||||
if (Get.isRegistered<LocationController>()) {
|
||||
pos = Get.find<LocationController>().myLocation;
|
||||
}
|
||||
|
||||
final res = await TransitDriverService.startTrip(
|
||||
tripId: trip.id,
|
||||
driverTransitId: driverTransitId!,
|
||||
lat: pos.latitude,
|
||||
lng: pos.longitude,
|
||||
);
|
||||
|
||||
if (res.success) {
|
||||
if (Get.isRegistered<LocationController>()) {
|
||||
Get.find<LocationController>().setBusMode(
|
||||
enabled: true,
|
||||
tripId: trip.id,
|
||||
routeId: trip.routeId,
|
||||
);
|
||||
}
|
||||
await fetchTodayTrips();
|
||||
} else {
|
||||
Get.snackbar('مواصلاتي', res.message);
|
||||
}
|
||||
|
||||
isActionInProgress = false;
|
||||
update();
|
||||
return res.success;
|
||||
}
|
||||
|
||||
Future<bool> endTrip(TransitDriverTrip trip) async {
|
||||
if (driverTransitId == null || isActionInProgress) return false;
|
||||
isActionInProgress = true;
|
||||
update();
|
||||
|
||||
final res = await TransitDriverService.endTrip(
|
||||
tripId: trip.id,
|
||||
driverTransitId: driverTransitId!,
|
||||
);
|
||||
|
||||
if (res.success) {
|
||||
if (Get.isRegistered<LocationController>()) {
|
||||
Get.find<LocationController>().setBusMode(enabled: false);
|
||||
}
|
||||
await fetchTodayTrips();
|
||||
} else {
|
||||
Get.snackbar('مواصلاتي', res.message);
|
||||
}
|
||||
|
||||
isActionInProgress = false;
|
||||
update();
|
||||
return res.success;
|
||||
}
|
||||
|
||||
Future<bool> reportDelay(TransitDriverTrip trip, int minutes, {String? reason}) async {
|
||||
if (driverTransitId == null) return false;
|
||||
final res = await TransitDriverService.reportDelay(
|
||||
tripId: trip.id,
|
||||
driverTransitId: driverTransitId!,
|
||||
delayMinutes: minutes,
|
||||
reason: reason,
|
||||
);
|
||||
if (!res.success) Get.snackbar('مواصلاتي', res.message);
|
||||
return res.success;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// transit_driver_models.dart — نماذج بيانات مواصلاتي (جهة سائق الباص)
|
||||
|
||||
class TransitStopInfo {
|
||||
final int sequence;
|
||||
final String nameAr;
|
||||
final double lat;
|
||||
final double lng;
|
||||
final int? etaOffsetMin;
|
||||
|
||||
TransitStopInfo({
|
||||
required this.sequence,
|
||||
required this.nameAr,
|
||||
required this.lat,
|
||||
required this.lng,
|
||||
this.etaOffsetMin,
|
||||
});
|
||||
|
||||
factory TransitStopInfo.fromJson(Map<String, dynamic> j) => TransitStopInfo(
|
||||
sequence: int.tryParse(j['sequence'].toString()) ?? 0,
|
||||
nameAr: j['name_ar']?.toString() ?? '',
|
||||
lat: double.tryParse(j['latitude']?.toString() ?? '0') ?? 0,
|
||||
lng: double.tryParse(j['longitude']?.toString() ?? '0') ?? 0,
|
||||
etaOffsetMin: j['eta_offset_min'] == null
|
||||
? null
|
||||
: int.tryParse(j['eta_offset_min'].toString()),
|
||||
);
|
||||
}
|
||||
|
||||
class TransitDriverTrip {
|
||||
final int id;
|
||||
final int routeId;
|
||||
final String status; // scheduled | started | completed | cancelled | no_show
|
||||
final int delayMinutes;
|
||||
final int? currentStopSeq;
|
||||
final String routeName;
|
||||
final String? departureTime;
|
||||
final String? vehiclePlate;
|
||||
final int? capacity;
|
||||
final List<TransitStopInfo> stops;
|
||||
|
||||
TransitDriverTrip({
|
||||
required this.id,
|
||||
required this.routeId,
|
||||
required this.status,
|
||||
required this.delayMinutes,
|
||||
required this.routeName,
|
||||
this.currentStopSeq,
|
||||
this.departureTime,
|
||||
this.vehiclePlate,
|
||||
this.capacity,
|
||||
this.stops = const [],
|
||||
});
|
||||
|
||||
factory TransitDriverTrip.fromJson(Map<String, dynamic> j) => TransitDriverTrip(
|
||||
id: int.tryParse(j['id'].toString()) ?? 0,
|
||||
routeId: int.tryParse(j['route_id']?.toString() ?? '0') ?? 0,
|
||||
status: j['status']?.toString() ?? '',
|
||||
delayMinutes: int.tryParse(j['delay_minutes']?.toString() ?? '0') ?? 0,
|
||||
currentStopSeq: j['current_stop_seq'] == null
|
||||
? null
|
||||
: int.tryParse(j['current_stop_seq'].toString()),
|
||||
routeName: j['route_name']?.toString() ?? '',
|
||||
departureTime: j['departure_time']?.toString(),
|
||||
vehiclePlate: j['vehicle_plate']?.toString(),
|
||||
capacity: j['capacity'] == null ? null : int.tryParse(j['capacity'].toString()),
|
||||
stops: (j['stops'] is List)
|
||||
? (j['stops'] as List)
|
||||
.map((s) => TransitStopInfo.fromJson(Map<String, dynamic>.from(s)))
|
||||
.toList()
|
||||
: const [],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// transit_driver_service.dart — طبقة الاتصال بـ backend/transit (جهة سائق الباص)
|
||||
import '../functions/crud.dart';
|
||||
import '../../constant/links.dart';
|
||||
import 'transit_driver_models.dart';
|
||||
|
||||
class TransitApiResult<T> {
|
||||
final bool success;
|
||||
final T? data;
|
||||
final String message;
|
||||
TransitApiResult(this.success, this.data, this.message);
|
||||
}
|
||||
|
||||
class TransitDriverService {
|
||||
static String get _base => '${AppLink.server}/transit';
|
||||
|
||||
static String _errMsg(dynamic res) {
|
||||
if (res == 'no_internet') return 'تحقق من اتصالك بالإنترنت';
|
||||
if (res == 'token_expired') return 'انتهت الجلسة، حاول مجدداً';
|
||||
if (res is Map && res['message'] is String) return res['message'];
|
||||
return 'حدث خطأ، حاول مجدداً';
|
||||
}
|
||||
|
||||
/// هل هذا الحساب سائق باص، وما معرّفه في مواصلاتي؟
|
||||
static Future<TransitApiResult<Map<String, dynamic>>> checkIsBusDriver() async {
|
||||
final res = await CRUD().post(link: '$_base/driver/me.php');
|
||||
if (res is Map && res['status'] == 'success' && res['message'] is Map) {
|
||||
return TransitApiResult(true, Map<String, dynamic>.from(res['message']), 'ok');
|
||||
}
|
||||
return TransitApiResult(false, null, _errMsg(res));
|
||||
}
|
||||
|
||||
/// رحلات اليوم (تُنشأ تلقائياً من الجداول عند أول استدعاء)
|
||||
static Future<TransitApiResult<List<TransitDriverTrip>>> getTodayTrips(
|
||||
int driverTransitId) async {
|
||||
final res = await CRUD().post(
|
||||
link: '$_base/trip/today.php',
|
||||
payload: {'driver_transit_id': driverTransitId.toString()},
|
||||
);
|
||||
if (res is Map && res['status'] == 'success' && res['message'] is Map) {
|
||||
final msg = res['message'] as Map;
|
||||
final list = (msg['trips'] is List)
|
||||
? (msg['trips'] as List)
|
||||
.map((t) => TransitDriverTrip.fromJson(Map<String, dynamic>.from(t)))
|
||||
.toList()
|
||||
: <TransitDriverTrip>[];
|
||||
return TransitApiResult(true, list, 'ok');
|
||||
}
|
||||
return TransitApiResult(false, null, _errMsg(res));
|
||||
}
|
||||
|
||||
static Future<TransitApiResult<Map<String, dynamic>>> startTrip({
|
||||
required int tripId,
|
||||
required int driverTransitId,
|
||||
required double lat,
|
||||
required double lng,
|
||||
}) async {
|
||||
final res = await CRUD().post(
|
||||
link: '$_base/trip/start.php',
|
||||
payload: {
|
||||
'trip_id': tripId.toString(),
|
||||
'driver_transit_id': driverTransitId.toString(),
|
||||
'lat': lat.toString(),
|
||||
'lng': lng.toString(),
|
||||
},
|
||||
);
|
||||
if (res is Map && res['status'] == 'success') {
|
||||
return TransitApiResult(true, Map<String, dynamic>.from(res['message'] ?? {}), 'ok');
|
||||
}
|
||||
return TransitApiResult(false, null, _errMsg(res));
|
||||
}
|
||||
|
||||
static Future<TransitApiResult<Map<String, dynamic>>> endTrip({
|
||||
required int tripId,
|
||||
required int driverTransitId,
|
||||
}) async {
|
||||
final res = await CRUD().post(
|
||||
link: '$_base/trip/end.php',
|
||||
payload: {
|
||||
'trip_id': tripId.toString(),
|
||||
'driver_transit_id': driverTransitId.toString(),
|
||||
},
|
||||
);
|
||||
if (res is Map && res['status'] == 'success') {
|
||||
return TransitApiResult(true, Map<String, dynamic>.from(res['message'] ?? {}), 'ok');
|
||||
}
|
||||
return TransitApiResult(false, null, _errMsg(res));
|
||||
}
|
||||
|
||||
static Future<TransitApiResult<void>> reportDelay({
|
||||
required int tripId,
|
||||
required int driverTransitId,
|
||||
required int delayMinutes,
|
||||
String? reason,
|
||||
}) async {
|
||||
final res = await CRUD().post(
|
||||
link: '$_base/trip/delay.php',
|
||||
payload: {
|
||||
'trip_id': tripId.toString(),
|
||||
'driver_transit_id': driverTransitId.toString(),
|
||||
'delay_minutes': delayMinutes.toString(),
|
||||
if (reason != null) 'reason': reason,
|
||||
},
|
||||
);
|
||||
if (res is Map && res['status'] == 'success') {
|
||||
return TransitApiResult(true, null, 'ok');
|
||||
}
|
||||
return TransitApiResult(false, null, _errMsg(res));
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import '../../../../constant/colors.dart';
|
||||
import '../About Us/video_page.dart';
|
||||
import '../assurance_health_page.dart';
|
||||
import '../maintain_center_page.dart';
|
||||
import '../../../transit/transit_driver_home_page.dart';
|
||||
|
||||
// 1. إنشاء Class لتعريف بيانات كل عنصر في القائمة
|
||||
class DrawerItem {
|
||||
@@ -55,6 +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: 'Balance'.tr,
|
||||
icon: Icons.account_balance_wallet,
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
// transit_driver_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_driver_controller.dart';
|
||||
import '../../controller/transit/transit_driver_models.dart';
|
||||
import '../widgets/elevated_btn.dart';
|
||||
import '../widgets/my_scafold.dart';
|
||||
|
||||
class TransitDriverHomePage extends StatelessWidget {
|
||||
const TransitDriverHomePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(TransitDriverController());
|
||||
|
||||
return GetBuilder<TransitDriverController>(
|
||||
builder: (c) {
|
||||
if (c.isCheckingBusDriver) {
|
||||
return MyScafolld(
|
||||
title: 'Mawasalati'.tr,
|
||||
isleading: true,
|
||||
body: const [Expanded(child: Center(child: CircularProgressIndicator()))],
|
||||
);
|
||||
}
|
||||
|
||||
if (!c.isBusDriver) {
|
||||
return MyScafolld(
|
||||
title: 'Mawasalati'.tr,
|
||||
isleading: true,
|
||||
body: [
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Text(
|
||||
'This account is not registered as a bus driver in any institution'.tr,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return MyScafolld(
|
||||
title: c.orgName,
|
||||
isleading: true,
|
||||
body: [
|
||||
Expanded(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: c.fetchTodayTrips,
|
||||
child: c.isLoadingTrips
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: c.todayTrips.isEmpty
|
||||
? ListView(
|
||||
children: [
|
||||
const SizedBox(height: 80),
|
||||
Center(
|
||||
child: Text('No trips today'.tr, style: AppStyle.title),
|
||||
),
|
||||
],
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: c.todayTrips.length,
|
||||
itemBuilder: (_, i) => _tripCard(c, c.todayTrips[i]),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _tripCard(TransitDriverController c, TransitDriverTrip trip) {
|
||||
final isStarted = trip.status == 'started';
|
||||
final isCompleted = trip.status == 'completed';
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 14),
|
||||
color: AppColor.cardColor,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
side: BorderSide(color: isStarted ? AppColor.greenColor : AppColor.borderColor,
|
||||
width: isStarted ? 1.5 : 1),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.directions_bus, color: AppColor.accentColor),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(trip.routeName,
|
||||
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
|
||||
),
|
||||
if (trip.departureTime != null)
|
||||
Text(trip.departureTime!, style: AppStyle.subtitle),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text('${trip.stops.length} ${'Stops'.tr}', style: AppStyle.subtitle),
|
||||
if (trip.delayMinutes > 0)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: Text('${'Delayed by'.tr} ${trip.delayMinutes} ${'min'.tr}',
|
||||
style: AppStyle.subtitle.copyWith(color: AppColor.yellowColor)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (!isCompleted)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MyElevatedButton(
|
||||
title: isStarted ? 'End Trip'.tr : 'Start Trip'.tr,
|
||||
kolor: isStarted ? AppColor.redColor : AppColor.greenColor,
|
||||
onPressed: c.isActionInProgress
|
||||
? () {}
|
||||
: () => isStarted ? c.endTrip(trip) : c.startTrip(trip),
|
||||
),
|
||||
),
|
||||
if (isStarted) ...[
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
icon: Icon(Icons.report_gmailerrorred, color: AppColor.yellowColor),
|
||||
onPressed: () => _showDelayDialog(c, trip),
|
||||
),
|
||||
],
|
||||
],
|
||||
)
|
||||
else
|
||||
Text('Trip completed'.tr,
|
||||
style: AppStyle.subtitle.copyWith(color: AppColor.greenColor)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showDelayDialog(TransitDriverController c, TransitDriverTrip trip) {
|
||||
final ctrl = TextEditingController(text: '5');
|
||||
Get.defaultDialog(
|
||||
title: 'Report Delay'.tr,
|
||||
content: Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: ctrl,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(labelText: 'Number of minutes'.tr),
|
||||
),
|
||||
],
|
||||
),
|
||||
textConfirm: 'Send'.tr,
|
||||
textCancel: 'Cancel'.tr,
|
||||
onConfirm: () {
|
||||
final minutes = int.tryParse(ctrl.text.trim()) ?? 5;
|
||||
c.reportDelay(trip, minutes);
|
||||
Get.back();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user