2026-03-13-2

This commit is contained in:
Hamza-Ayed
2026-03-13 22:43:46 +03:00
parent fdfea5582a
commit e2341b104f
4 changed files with 1007 additions and 330 deletions

View File

@@ -191,20 +191,19 @@ class _ExpandedView extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ── هيدر "Quick Actions" ─────────────────────────────────────────
// ── Header ────────────────────────────────────────────────────────
Padding(
padding: const EdgeInsets.fromLTRB(18, 14, 12, 8),
child: Row(
children: [
Text(
'Quick Actions'.tr,
'Plan Your Route'.tr,
style: AppStyle.title.copyWith(
fontWeight: FontWeight.w700,
fontSize: 16,
),
),
const Spacer(),
// زر إغلاق
GestureDetector(
onTap: controller.changeMainBottomMenuMap,
child: Container(
@@ -222,36 +221,236 @@ class _ExpandedView extends StatelessWidget {
),
const Divider(height: 1, thickness: 0.5),
const SizedBox(height: 10),
// ── موقع البداية ─────────────────────────────────────────────────
if (!controller.isAnotherOreder)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: _LocationRow(
icon: Icons.my_location_rounded,
iconColor: AppColor.primaryColor,
label: controller.currentLocationString,
isStart: true,
),
)
else
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: formSearchPlacesStart(),
),
const SizedBox(height: 6),
// ── حقل الوجهة ────────────────────────────────────────────────────
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: formSearchPlacesDestenation(),
),
const SizedBox(height: 12),
// ── زر WhatsApp ───────────────────────────────────────────────────
// ── Route Timeline (no IntrinsicHeight) ─────────────────────────
// Start location row with green dot
_buildTimelineItem(
dotColor: AppColor.primaryColor,
showTopLine: false,
showBottomLine: true,
child: !controller.isAnotherOreder
? _TimelineRow(
icon: Icons.my_location_rounded,
iconColor: AppColor.primaryColor,
bgColor: AppColor.primaryColor,
label: controller.currentLocationString,
)
: Padding(
padding: const EdgeInsets.only(right: 16),
child: formSearchPlacesStart(),
),
),
// Waypoint stop rows
...List.generate(controller.activeMenuWaypointCount, (index) {
final wpName = controller.menuWaypointNames[index];
final isSet = controller.menuWaypoints[index] != null;
// Stop 1 = amber/orange, Stop 2 = deep purple
final Color dotColor =
index == 0 ? Colors.amber.shade700 : Colors.deepPurple.shade400;
return _buildTimelineItem(
dotColor: dotColor,
showTopLine: true,
showBottomLine: true,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: index == 0
? Colors.amber.shade50
: Colors.deepPurple.shade50,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: isSet
? (index == 0
? Colors.amber.shade300
: Colors.deepPurple.shade200)
: Colors.grey.shade200,
),
),
child: Row(
children: [
// Numbered badge
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: index == 0
? [Colors.amber.shade500, Colors.amber.shade700]
: [
Colors.deepPurple.shade300,
Colors.deepPurple.shade500
],
),
shape: BoxShape.circle,
),
child: Center(
child: Text(
'${index + 1}',
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.w700,
),
),
),
),
const SizedBox(width: 10),
Expanded(
child: GestureDetector(
onTap: () {
controller.changeMainBottomMenuMap();
controller.startPickingWaypointOnMap(index);
},
child: Text(
isSet ? wpName : '${'Stop'.tr} ${index + 1}',
style: TextStyle(
fontSize: 13,
color: isSet
? (index == 0
? Colors.amber.shade900
: Colors.deepPurple.shade700)
: Colors.grey.shade400,
fontWeight: isSet ? FontWeight.w500 : FontWeight.w400,
fontStyle:
isSet ? FontStyle.normal : FontStyle.italic,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
GestureDetector(
onTap: () {
controller.changeMainBottomMenuMap();
controller.startPickingWaypointOnMap(index);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: Icon(Icons.map_outlined,
color: index == 0
? Colors.amber.shade600
: Colors.deepPurple.shade400,
size: 18),
),
),
GestureDetector(
onTap: () => controller.removeMenuWaypoint(index),
child: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: Colors.red.shade50,
shape: BoxShape.circle,
),
child: Icon(Icons.close_rounded,
color: Colors.red.shade400, size: 13),
),
),
],
),
),
);
}),
// Add Stop button
if (controller.activeMenuWaypointCount < 2)
_buildTimelineItem(
dotColor: Colors.grey.shade300,
isDotDashed: true,
showTopLine: true,
showBottomLine: true,
child: GestureDetector(
onTap: () => controller.addMenuWaypoint(),
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 9),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.orange.shade200),
color: Colors.orange.shade50.withAlpha(100),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.add_circle_outline_rounded,
color: Colors.orange.shade500, size: 16),
const SizedBox(width: 6),
Text(
'Add a Stop'.tr,
style: TextStyle(
color: Colors.orange.shade700,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 5, vertical: 1),
decoration: BoxDecoration(
color: Colors.orange.shade100,
borderRadius: BorderRadius.circular(6),
),
child: Text(
'+5 ${'min'.tr}',
style: TextStyle(
color: Colors.orange.shade700,
fontSize: 9,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
),
),
// Destination row with red dot
_buildTimelineItem(
dotColor: Colors.red.shade500,
showTopLine: true,
showBottomLine: false,
child: Padding(
padding: const EdgeInsets.only(right: 16),
child: formSearchPlacesDestenation(),
),
),
// ── Surcharge info ───────────────────────────────────────────────
if (controller.activeMenuWaypointCount > 0)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 6),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.schedule_rounded,
size: 14, color: Colors.orange.shade600),
const SizedBox(width: 6),
Text(
'${controller.activeMenuWaypointCount} ${'stop(s)'.tr} · +${controller.activeMenuWaypointCount * 5} ${'min added to fare'.tr}',
style: TextStyle(
fontSize: 11,
color: Colors.orange.shade700,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
const SizedBox(height: 10),
// ── WhatsApp ─────────────────────────────────────────────────────
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: _WhatsAppLinkButton(controller: controller),
@@ -259,7 +458,7 @@ class _ExpandedView extends StatelessWidget {
const SizedBox(height: 10),
// ── Order for someone else ────────────────────────────────────────
// ── Order type ───────────────────────────────────────────────────
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: _OrderTypeButton(controller: controller),
@@ -269,6 +468,59 @@ class _ExpandedView extends StatelessWidget {
],
);
}
/// Builds a single timeline row: [dot + line] | [child widget]
Widget _buildTimelineItem({
required Color dotColor,
required bool showTopLine,
required bool showBottomLine,
required Widget child,
bool isDotDashed = false,
}) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Timeline indicator column (dot + lines)
SizedBox(
width: 24,
child: Column(
children: [
// Top connecting line
if (showTopLine)
Container(
width: 2,
height: 8,
color: Colors.grey.shade300,
),
// Dot
Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: isDotDashed ? Colors.transparent : dotColor,
shape: BoxShape.circle,
border: Border.all(color: dotColor, width: 2),
),
),
// Bottom connecting line
if (showBottomLine)
Container(
width: 2,
height: 8,
color: Colors.grey.shade300,
),
],
),
),
const SizedBox(width: 10),
// Content
Expanded(child: child),
],
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
@@ -282,6 +534,10 @@ class _MapPickerOverlay extends StatelessWidget {
// ── الحصول على نص الحالة الحالية ─────────────────────────────────────────
String _getModeTitle(BuildContext context) {
if (controller.isPickingWaypoint) {
return 'Move map to set stop'.tr +
' ${controller.pickingWaypointIndex + 1}'.tr;
}
if (controller.passengerStartLocationFromMap) {
return 'Move map to your pickup point'.tr;
} else if (controller.startLocationFromMap) {
@@ -295,6 +551,9 @@ class _MapPickerOverlay extends StatelessWidget {
}
String _getConfirmLabel(BuildContext context) {
if (controller.isPickingWaypoint) {
return 'Set as Stop'.tr;
}
if (controller.passengerStartLocationFromMap) {
return 'Confirm Pickup Location'.tr;
} else if (controller.workLocationFromMap) {
@@ -306,6 +565,7 @@ class _MapPickerOverlay extends StatelessWidget {
}
IconData _getModeIcon() {
if (controller.isPickingWaypoint) return Icons.add_location_alt_rounded;
if (controller.passengerStartLocationFromMap)
return Icons.person_pin_circle_rounded;
if (controller.workLocationFromMap) return Icons.work_rounded;
@@ -314,6 +574,7 @@ class _MapPickerOverlay extends StatelessWidget {
}
Color _getModeColor() {
if (controller.isPickingWaypoint) return Colors.orange.shade600;
if (controller.passengerStartLocationFromMap) return Colors.green.shade600;
if (controller.workLocationFromMap) return Colors.blue.shade600;
if (controller.homeLocationFromMap) return Colors.orange.shade600;
@@ -428,6 +689,8 @@ class _MapPickerOverlay extends StatelessWidget {
controller.startLocationFromMap = false;
controller.workLocationFromMap = false;
controller.homeLocationFromMap = false;
controller.isPickingWaypoint = false;
controller.pickingWaypointIndex = -1;
// أعد الخريطة لحالتها المنهارة
if (!controller.isMainBottomMenuMap) {
controller.isMainBottomMenuMap = true;
@@ -498,6 +761,24 @@ class _MapPickerOverlay extends StatelessWidget {
controller.newMyLocation.longitude,
);
// ── CASE 0: Waypoint picker mode ──────────────────────────────────────
if (controller.isPickingWaypoint && controller.pickingWaypointIndex >= 0) {
final int wpIndex = controller.pickingWaypointIndex;
controller.setMenuWaypointFromMap(wpIndex, currentCameraPosition);
Get.snackbar(
'Stop ${wpIndex + 1} Set'.tr,
'Waypoint has been set successfully'.tr,
backgroundColor: Colors.orange.shade600,
colorText: Colors.white,
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 2),
margin: const EdgeInsets.all(12),
borderRadius: 12,
);
return;
}
controller.clearPolyline();
controller.data = [];
@@ -593,21 +874,35 @@ class _MapPickerOverlay extends StatelessWidget {
controller.update();
// ─── 3. العمليات الـ async بعد حفظ الوجهة بأمان ──────────────────────
if (!controller.isAnotherOreder) {
// تحريك الكاميرا لموقع الراكب ليختار منه نقطة الانطلاق
// ملاحظة: هذا يُغير newMyLocation - لكن myDestination محفوظ بأمان ✅
await controller.mapController?.animateCamera(
CameraUpdate.newLatLng(LatLng(
controller.passengerLocation.latitude,
controller.passengerLocation.longitude,
)),
);
try {
if (controller.isAnotherOreder) {
// ✅ "Order for someone else": move camera to the OTHER person's
// start location (set via the start search form)
await controller.mapController?.animateCamera(
CameraUpdate.newLatLng(LatLng(
controller.newStartPointLocation.latitude,
controller.newStartPointLocation.longitude,
)),
);
} else {
// Normal flow: move camera to the passenger's own location
await controller.mapController?.animateCamera(
CameraUpdate.newLatLng(LatLng(
controller.passengerLocation.latitude,
controller.passengerLocation.longitude,
)),
);
}
} catch (_) {
// Guard against disposed GoogleMapController
}
// ─── 4. إشعار المستخدم ──────────────────────────────────────────────
Get.snackbar(
'Destination Set'.tr,
'Now move the map to your pickup point'.tr,
controller.isAnotherOreder
? 'Now set the pickup point for the other person'.tr
: 'Now move the map to your pickup point'.tr,
backgroundColor: Colors.green.shade600,
colorText: Colors.white,
snackPosition: SnackPosition.TOP,
@@ -664,6 +959,46 @@ class _LocationRow extends StatelessWidget {
}
}
/// صف في التايملاين (للعرض داخل عمود المسار)
class _TimelineRow extends StatelessWidget {
final IconData icon;
final Color iconColor;
final Color bgColor;
final String label;
const _TimelineRow({
required this.icon,
required this.iconColor,
required this.bgColor,
required this.label,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: bgColor.withAlpha(15),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: bgColor.withAlpha(40)),
),
child: Row(
children: [
Icon(icon, color: iconColor, size: 16),
const SizedBox(width: 8),
Expanded(
child: Text(
label,
style: AppStyle.subtitle.copyWith(fontSize: 12.5),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
}
/// شريحة الأماكن الأخيرة
class _RecentPlaceChip extends StatelessWidget {
final MapPassengerController controller;