From 3f03f2514205204cc967ecd6694c25ca52b39980 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Tue, 7 Apr 2026 20:08:46 +0300 Subject: [PATCH] 2026-04-07 redesignmain bottom menu map --- .../map_widget.dart/main_bottom_Menu_map.dart | 1983 ++++++++++++----- 1 file changed, 1379 insertions(+), 604 deletions(-) diff --git a/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart b/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart index 316e3c1..750d57f 100644 --- a/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart +++ b/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart @@ -17,8 +17,109 @@ import '../../widgets/error_snakbar.dart'; import '../../widgets/mydialoug.dart'; import 'form_search_start.dart'; +// ─── Design Tokens (Modern & Dynamic) ──────────────────────────────────────── + +class _D { + // Radii - More rounded for modern feel + static const double radiusCard = 28; + static const double radiusChip = 20; + static const double radiusBtn = 16; + static const double radiusInner = 14; + static const double radiusPill = 50; + + // Shadows - Layered depth with blur + static List get cardShadow => [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 40, + spreadRadius: -8, + offset: const Offset(0, 12), + ), + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 16, + spreadRadius: -4, + offset: const Offset(0, 4), + ), + ]; + + static List glowShadow(Color c, {double intensity = 0.4}) => [ + BoxShadow( + color: c.withOpacity(intensity), + blurRadius: 24, + spreadRadius: -4, + offset: const Offset(0, 8), + ), + BoxShadow( + color: c.withOpacity(intensity * 0.5), + blurRadius: 12, + spreadRadius: -2, + offset: const Offset(0, 3), + ), + ]; + + static List innerGlow(Color c) => [ + BoxShadow( + color: c.withOpacity(0.15), + blurRadius: 20, + spreadRadius: -10, + offset: const Offset(0, 0), + ), + ]; + + // Durations - Smoother animations + static const Duration fast = Duration(milliseconds: 180); + static const Duration medium = Duration(milliseconds: 420); + static const Duration slow = Duration(milliseconds: 600); + + // Gradients + static LinearGradient primaryGradient({ + Alignment begin = Alignment.topLeft, + Alignment end = Alignment.bottomRight, + }) => + LinearGradient( + begin: begin, + end: end, + colors: [ + AppColor.primaryColor, + AppColor.primaryColor.withOpacity(0.85), + AppColor.primaryColor.withOpacity(0.7), + ], + stops: const [0.0, 0.5, 1.0], + ); + + static LinearGradient cardGradient() => LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColor.secondaryColor.withOpacity(0.98), + AppColor.secondaryColor + .withBlue( + (AppColor.secondaryColor.blue + 12).clamp(0, 255), + ) + .withOpacity(0.95), + ], + ); + + // Glassmorphism effect + static BoxDecoration glassEffect({ + required Color color, + double opacity = 0.1, + double borderOpacity = 0.2, + }) => + BoxDecoration( + color: color.withOpacity(opacity), + borderRadius: BorderRadius.circular(radiusInner), + border: Border.all( + color: color.withOpacity(borderOpacity), + width: 1, + ), + backgroundBlendMode: BlendMode.overlay, + ); +} + // ───────────────────────────────────────────────────────────────────────────── -// MAIN BOTTOM MENU MAP +// MAIN BOTTOM MENU MAP - Modern Redesign // ───────────────────────────────────────────────────────────────────────────── class MainBottomMenuMap extends StatelessWidget { @@ -30,38 +131,47 @@ class MainBottomMenuMap extends StatelessWidget { return GetBuilder( builder: (controller) { - // ─── حالة: يتم تحديد موقع على الخريطة (وضع الـ Picker) ─────────────── if (controller.isPickerShown) { return _MapPickerOverlay(controller: controller); } - // ─── الحالة العادية: القائمة السفلية ──────────────────────────────── return Positioned( - bottom: Get.height * .03, - left: 12, - right: 12, + bottom: Get.height * .035, + left: 16, + right: 16, child: AnimatedContainer( - duration: const Duration(milliseconds: 350), - curve: Curves.easeOutCubic, + duration: _D.medium, + curve: Curves.easeOutQuint, height: controller.mainBottomMenuMapHeight, decoration: BoxDecoration( - color: AppColor.secondaryColor, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.15), - blurRadius: 24, - offset: const Offset(0, 8), - ), - ], + gradient: _D.cardGradient(), + borderRadius: BorderRadius.circular(_D.radiusCard), + boxShadow: _D.cardShadow, + border: Border.all( + color: Colors.white.withOpacity(0.65), + width: 1.2, + ), ), - child: ClipRRect( - borderRadius: BorderRadius.circular(20), - child: SingleChildScrollView( - physics: const NeverScrollableScrollPhysics(), - child: controller.isMainBottomMenuMap - ? _CollapsedView(controller: controller) - : _ExpandedView(controller: controller, context: context), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(_D.radiusCard), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.white.withOpacity(0.08), + Colors.transparent, + ], + ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(_D.radiusCard), + child: SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + child: controller.isMainBottomMenuMap + ? _CollapsedView(controller: controller) + : _ExpandedView(controller: controller, context: context), + ), ), ), ), @@ -72,7 +182,7 @@ class MainBottomMenuMap extends StatelessWidget { } // ───────────────────────────────────────────────────────────────────────────── -// COLLAPSED VIEW (isMainBottomMenuMap = true) +// COLLAPSED VIEW - Modern & Elegant // ───────────────────────────────────────────────────────────────────────────── class _CollapsedView extends StatelessWidget { @@ -86,98 +196,172 @@ class _CollapsedView extends StatelessWidget { return Column( mainAxisSize: MainAxisSize.min, children: [ - // ── شريط العنوان ────────────────────────────────────────────────── - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: controller.changeMainBottomMenuMap, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14), - child: Row( - children: [ - // أيقونة الموقع بدائرة ملونة - Container( - width: 38, - height: 38, - decoration: BoxDecoration( - color: AppColor.primaryColor.withOpacity(0.12), - shape: BoxShape.circle, + // ── Animated drag handle ───────────────────────────────────────────── + const SizedBox(height: 14), + AnimatedContainer( + duration: _D.fast, + width: 44, + height: 5, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.grey.shade400.withOpacity(0.6), + Colors.grey.shade300, + Colors.grey.shade400.withOpacity(0.6), + ], + ), + borderRadius: BorderRadius.circular(3), + ), + ), + const SizedBox(height: 16), + + // ── Main interactive search card ───────────────────────────────────── + Material( + color: Colors.transparent, + child: InkWell( + onTap: controller.changeMainBottomMenuMap, + borderRadius: BorderRadius.circular(_D.radiusInner), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 8), + child: Row( + children: [ + // Animated search icon with glow + AnimatedContainer( + duration: _D.medium, + width: 48, + height: 48, + decoration: BoxDecoration( + gradient: _D.primaryGradient(), + borderRadius: BorderRadius.circular(_D.radiusPill), + boxShadow: _D.glowShadow(AppColor.primaryColor), + ), + child: const Icon( + Icons.search_rounded, + color: Colors.white, + size: 22, + ), ), - child: Icon(Icons.search_rounded, - color: AppColor.primaryColor, size: 20), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '${'Where to'.tr} $firstName؟', - style: AppStyle.title.copyWith( - fontWeight: FontWeight.w700, - fontSize: 15, - ), - ), - if (!controller.noCarString) - Text( - 'Tap to search your destination'.tr, - style: AppStyle.subtitle.copyWith( - fontSize: 11, - color: Colors.grey.shade500, + const SizedBox(width: 16), + + // Dynamic text content + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text.rich( + TextSpan( + children: [ + TextSpan( + text: '${'Where to'.tr} ', + style: AppStyle.title.copyWith( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.grey.shade700, + ), + ), + TextSpan( + text: firstName, + style: AppStyle.title.copyWith( + fontWeight: FontWeight.w800, + fontSize: 16.5, + color: AppColor.primaryColor, + letterSpacing: -0.3, + ), + ), + const TextSpan(text: '؟'), + ], ), ), - ], + const SizedBox(height: 2), + if (!controller.noCarString) + AnimatedOpacity( + duration: _D.fast, + opacity: 1, + child: Text( + 'Tap to search your destination'.tr, + style: AppStyle.subtitle.copyWith( + fontSize: 12, + color: Colors.grey.shade500, + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ), ), - ), - Container( - padding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 5), - decoration: BoxDecoration( - color: AppColor.primaryColor, - borderRadius: BorderRadius.circular(20), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.keyboard_arrow_up_rounded, - color: Colors.white, size: 18), - Text( - 'Open'.tr, - style: const TextStyle( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.w600), + + // Elegant expand indicator + Container( + padding: + const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.primaryColor.withOpacity(0.12), + AppColor.primaryColor.withOpacity(0.06), + ], ), - ], + borderRadius: BorderRadius.circular(_D.radiusPill), + border: Border.all( + color: AppColor.primaryColor.withOpacity(0.25), + width: 1, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + AnimatedRotation( + duration: _D.fast, + turns: 0, + child: Icon( + Icons.keyboard_arrow_up_rounded, + color: AppColor.primaryColor, + size: 20, + ), + ), + const SizedBox(width: 4), + Text( + 'Open'.tr, + style: TextStyle( + color: AppColor.primaryColor, + fontSize: 12.5, + fontWeight: FontWeight.w700, + letterSpacing: 0.3, + ), + ), + ], + ), ), - ), - ], + ], + ), ), ), ), - // ── الأماكن الأخيرة ─────────────────────────────────────────────── - if (controller.recentPlaces.isNotEmpty) - Padding( - padding: const EdgeInsets.only(bottom: 10, left: 8, right: 8), - child: SizedBox( - height: 32, - child: ListView.separated( - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: 4), - itemCount: controller.recentPlaces.length, - separatorBuilder: (_, __) => const SizedBox(width: 6), - itemBuilder: (context, index) => - _RecentPlaceChip(controller: controller, index: index), - ), + // ── Recent places - Modern horizontal chips ─────────────────────────── + if (controller.recentPlaces.isNotEmpty) ...[ + const SizedBox(height: 12), + Container( + height: 40, + padding: const EdgeInsets.symmetric(horizontal: 18), + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: controller.recentPlaces.length, + separatorBuilder: (_, __) => const SizedBox(width: 10), + itemBuilder: (context, index) => + _RecentPlaceChip(controller: controller, index: index), ), ), + const SizedBox(height: 16), + ] else + const SizedBox(height: 20), ], ); } } // ───────────────────────────────────────────────────────────────────────────── -// EXPANDED VIEW (isMainBottomMenuMap = false) +// EXPANDED VIEW - Dynamic Route Planner // ───────────────────────────────────────────────────────────────────────────── class _ExpandedView extends StatelessWidget { @@ -191,44 +375,125 @@ class _ExpandedView extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - // ── Header ──────────────────────────────────────────────────────── - Padding( - padding: const EdgeInsets.fromLTRB(18, 14, 12, 8), + // ── Animated drag handle ───────────────────────────────────────────── + const SizedBox(height: 14), + Center( + child: AnimatedContainer( + duration: _D.fast, + width: 44, + height: 5, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.grey.shade400.withOpacity(0.6), + Colors.grey.shade300, + Colors.grey.shade400.withOpacity(0.6), + ], + ), + borderRadius: BorderRadius.circular(3), + ), + ), + ), + + // ── Modern Header with gradient ────────────────────────────────────── + Container( + padding: const EdgeInsets.fromLTRB(20, 18, 16, 14), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.transparent, + Colors.grey.shade50.withOpacity(0.3), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), child: Row( children: [ + // Animated icon container + AnimatedContainer( + duration: _D.medium, + width: 42, + height: 42, + decoration: BoxDecoration( + gradient: _D.primaryGradient(), + borderRadius: BorderRadius.circular(_D.radiusInner), + boxShadow: _D.glowShadow(AppColor.primaryColor), + ), + child: const Icon( + Icons.alt_route_rounded, + color: Colors.white, + size: 20, + ), + ), + const SizedBox(width: 14), Text( 'Plan Your Route'.tr, style: AppStyle.title.copyWith( - fontWeight: FontWeight.w700, - fontSize: 16, + fontWeight: FontWeight.w800, + fontSize: 18, + letterSpacing: -0.5, + height: 1.2, ), ), const Spacer(), - GestureDetector( - onTap: controller.changeMainBottomMenuMap, - child: Container( - padding: const EdgeInsets.all(6), - decoration: BoxDecoration( - color: Colors.grey.shade200, - shape: BoxShape.circle, + // Elegant close button + Material( + color: Colors.transparent, + child: InkWell( + onTap: controller.changeMainBottomMenuMap, + borderRadius: BorderRadius.circular(_D.radiusPill), + child: AnimatedContainer( + duration: _D.fast, + width: 38, + height: 38, + decoration: BoxDecoration( + color: Colors.grey.shade100, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.grey.shade200.withOpacity(0.5), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Icon( + Icons.keyboard_arrow_down_rounded, + size: 24, + color: Colors.grey.shade600, + ), ), - child: Icon(Icons.keyboard_arrow_down_rounded, - size: 22, color: Colors.grey.shade700), ), ), ], ), ), - const Divider(height: 1, thickness: 0.5), - const SizedBox(height: 12), + // Subtle separator with gradient + Container( + height: 1, + margin: const EdgeInsets.symmetric(horizontal: 20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.transparent, + Colors.grey.shade200, + Colors.transparent, + ], + ), + ), + ), + const SizedBox(height: 16), - // ── Route Timeline (no IntrinsicHeight) ───────────────────────── - // Start location row with green dot + // ── Dynamic Route Timeline ─────────────────────────────────────────── + + // Start location row _buildTimelineItem( dotColor: AppColor.primaryColor, showTopLine: false, showBottomLine: true, + isStart: true, child: !controller.isAnotherOreder ? _TimelineRow( icon: Icons.my_location_rounded, @@ -242,61 +507,84 @@ class _ExpandedView extends StatelessWidget { ), ), - // Waypoint stop rows + // Dynamic waypoints with color coding ...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; + final Color accent = + index == 0 ? Colors.amber.shade600 : Colors.deepPurple.shade400; + final Color soft = + index == 0 ? Colors.amber.shade50 : Colors.deepPurple.shade50; + return _buildTimelineItem( - dotColor: dotColor, + dotColor: accent, showTopLine: true, showBottomLine: true, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), 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, + gradient: LinearGradient( + colors: [ + soft.withOpacity(0.9), + soft.withOpacity(0.6), + ], ), + borderRadius: BorderRadius.circular(_D.radiusInner), + border: Border.all( + color: + isSet ? accent.withOpacity(0.35) : Colors.grey.shade200, + width: 1, + ), + boxShadow: isSet + ? [ + BoxShadow( + color: accent.withOpacity(0.08), + blurRadius: 12, + offset: const Offset(0, 3), + ) + ] + : null, ), child: Row( children: [ - // Numbered badge - Container( - width: 20, - height: 20, + // Animated waypoint number badge + AnimatedContainer( + duration: _D.fast, + width: 26, + height: 26, decoration: BoxDecoration( gradient: LinearGradient( colors: index == 0 - ? [Colors.amber.shade500, Colors.amber.shade700] + ? [Colors.amber.shade400, Colors.amber.shade700] : [ Colors.deepPurple.shade300, Colors.deepPurple.shade500 ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, ), shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: accent.withOpacity(0.35), + blurRadius: 8, + offset: const Offset(0, 3), + ), + ], ), child: Center( child: Text( '${index + 1}', style: const TextStyle( color: Colors.white, - fontSize: 10, - fontWeight: FontWeight.w700, + fontSize: 11, + fontWeight: FontWeight.w800, + letterSpacing: -0.3, ), ), ), ), - const SizedBox(width: 10), + const SizedBox(width: 12), Expanded( child: GestureDetector( onTap: () { @@ -306,46 +594,66 @@ class _ExpandedView extends StatelessWidget { child: Text( isSet ? wpName : '${'Stop'.tr} ${index + 1}', style: TextStyle( - fontSize: 13, + fontSize: 13.5, color: isSet - ? (index == 0 - ? Colors.amber.shade900 - : Colors.deepPurple.shade700) + ? accent.withOpacity(0.9) : Colors.grey.shade400, - fontWeight: isSet ? FontWeight.w500 : FontWeight.w400, + fontWeight: isSet ? FontWeight.w600 : FontWeight.w400, fontStyle: isSet ? FontStyle.normal : FontStyle.italic, + height: 1.3, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ), + // Map button with hover effect simulation 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), + child: AnimatedContainer( + duration: _D.fast, + width: 34, + height: 34, + margin: const EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + color: accent.withOpacity(0.12), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: accent.withOpacity(0.25), + width: 1, + ), + ), + child: Icon( + Icons.map_outlined, + color: accent, + size: 17, + ), ), ), + // Remove button with subtle animation GestureDetector( onTap: () => controller.removeMenuWaypoint(index), - child: Container( - width: 20, - height: 20, + child: AnimatedContainer( + duration: _D.fast, + width: 28, + height: 28, decoration: BoxDecoration( color: Colors.red.shade50, shape: BoxShape.circle, + border: Border.all( + color: Colors.red.shade100, + width: 1, + ), + ), + child: Icon( + Icons.close_rounded, + color: Colors.red.shade400, + size: 15, ), - child: Icon(Icons.close_rounded, - color: Colors.red.shade400, size: 13), ), ), ], @@ -354,168 +662,313 @@ class _ExpandedView extends StatelessWidget { ); }), - // Add Stop button + // Add stop button - Modern CTA style if (controller.activeMenuWaypointCount < 2) _buildTimelineItem( - dotColor: Colors.grey.shade300, + dotColor: Colors.orange.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, - ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => controller.addMenuWaypoint(), + borderRadius: BorderRadius.circular(_D.radiusInner), + child: AnimatedContainer( + duration: _D.fast, + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(_D.radiusInner), + border: Border.all( + color: Colors.orange.shade200, + style: BorderStyle.solid, + width: 1.5, ), - const SizedBox(width: 6), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 5, vertical: 1), - decoration: BoxDecoration( - color: Colors.orange.shade100, - borderRadius: BorderRadius.circular(6), + gradient: LinearGradient( + colors: [ + Colors.orange.shade50.withOpacity(0.6), + Colors.orange.shade50.withOpacity(0.3), + ], + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.add_location_alt_outlined, + color: Colors.orange.shade500, + size: 18, ), - child: Text( - '+5 ${'min'.tr}', + const SizedBox(width: 10), + Text( + 'Add a Stop'.tr, style: TextStyle( color: Colors.orange.shade700, - fontSize: 9, + fontSize: 13.5, fontWeight: FontWeight.w600, + letterSpacing: 0.2, ), ), - ), - ], + const SizedBox(width: 10), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: Colors.orange.shade100, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: Colors.orange.shade200, + width: 1, + ), + ), + child: Text( + '+5 ${'min'.tr}', + style: TextStyle( + color: Colors.orange.shade700, + fontSize: 10.5, + fontWeight: FontWeight.w700, + ), + ), + ), + ], + ), ), ), ), ), - // Destination row with red dot + // Destination row with elegant styling _buildTimelineItem( dotColor: Colors.red.shade500, showTopLine: true, showBottomLine: false, + isEnd: true, child: Padding( padding: const EdgeInsets.only(right: 16), child: formSearchPlacesDestenation(), ), ), - // ── Surcharge info ─────────────────────────────────────────────── + // ── Smart Surcharge banner ─────────────────────────────────────────── 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, - ), - ), + AnimatedContainer( + duration: _D.medium, + margin: const EdgeInsets.fromLTRB(20, 8, 20, 0), + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.orange.shade50.withOpacity(0.95), + Colors.amber.shade50.withOpacity(0.85), ], ), + borderRadius: BorderRadius.circular(_D.radiusInner), + border: Border.all( + color: Colors.orange.shade100.withOpacity(0.8), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.orange.shade100.withOpacity(0.3), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], + ), + child: Row( + children: [ + AnimatedContainer( + duration: _D.fast, + width: 30, + height: 30, + decoration: BoxDecoration( + color: Colors.orange.shade100, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Colors.orange.shade200.withOpacity(0.4), + blurRadius: 6, + offset: const Offset(0, 2), + ), + ], + ), + child: Icon( + Icons.schedule_rounded, + size: 15, + color: Colors.orange.shade700, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: '${controller.activeMenuWaypointCount} ', + style: TextStyle( + fontSize: 13, + color: Colors.orange.shade800, + fontWeight: FontWeight.w700, + ), + ), + TextSpan( + text: '${'stop(s)'.tr} · +', + style: TextStyle( + fontSize: 12.5, + color: Colors.orange.shade600, + fontWeight: FontWeight.w500, + ), + ), + TextSpan( + text: '${controller.activeMenuWaypointCount * 5} ', + style: TextStyle( + fontSize: 13, + color: Colors.orange.shade800, + fontWeight: FontWeight.w700, + ), + ), + TextSpan( + text: 'min added to fare'.tr, + style: TextStyle( + fontSize: 12.5, + color: Colors.orange.shade600, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ], ), ), - const SizedBox(height: 10), + const SizedBox(height: 14), - // ── WhatsApp ───────────────────────────────────────────────────── + // ── WhatsApp button - Modern card style ────────────────────────────── Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.symmetric(horizontal: 18), child: _WhatsAppLinkButton(controller: controller), ), - const SizedBox(height: 10), + const SizedBox(height: 12), - // ── Order type ─────────────────────────────────────────────────── + // ── Order type button - Dynamic toggle style ───────────────────────── Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.symmetric(horizontal: 18), child: _OrderTypeButton(controller: controller), ), - const SizedBox(height: 14), + const SizedBox(height: 18), ], ); } - /// 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, + bool isStart = false, + bool isEnd = false, }) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.symmetric(horizontal: 20), 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, + AnimatedContainer( + duration: _D.fast, + width: 2.5, + height: 12, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.grey.shade200.withOpacity(0.4), + Colors.grey.shade300, + ], + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(2), + ), + ), ), - // Dot - Container( - width: 12, - height: 12, + // Animated dot with pulse effect simulation + AnimatedContainer( + duration: _D.medium, + width: isDotDashed ? 15 : 15, + height: isDotDashed ? 15 : 15, decoration: BoxDecoration( - color: isDotDashed ? Colors.transparent : dotColor, + color: isDotDashed + ? Colors.transparent + : dotColor.withOpacity(0.95), shape: BoxShape.circle, - border: Border.all(color: dotColor, width: 2), + border: Border.all( + color: isDotDashed ? dotColor : dotColor.withOpacity(0.3), + width: isDotDashed ? 2 : 3, + style: + isDotDashed ? BorderStyle.solid : BorderStyle.solid, + ), + boxShadow: isDotDashed + ? [] + : [ + BoxShadow( + color: dotColor.withOpacity(0.35), + blurRadius: 10, + spreadRadius: 2, + ), + BoxShadow( + color: dotColor.withOpacity(0.15), + blurRadius: 20, + spreadRadius: -5, + ), + ], ), + child: isDotDashed + ? Center( + child: Container( + width: 5, + height: 5, + decoration: BoxDecoration( + color: dotColor, + shape: BoxShape.circle, + ), + ), + ) + : null, ), - // Bottom connecting line if (showBottomLine) - Container( - width: 2, - height: 8, - color: Colors.grey.shade300, + AnimatedContainer( + duration: _D.fast, + width: 2.5, + height: 12, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.grey.shade300, + Colors.grey.shade200.withOpacity(0.4), + ], + ), + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(2), + ), + ), ), ], ), ), - const SizedBox(width: 10), - // Content + const SizedBox(width: 14), Expanded(child: child), ], ), @@ -524,15 +977,13 @@ class _ExpandedView extends StatelessWidget { } // ───────────────────────────────────────────────────────────────────────────── -// MAP PICKER OVERLAY (isPickerShown = true) -// الواجهة التي تظهر عندما يختار المستخدم موقعاً على الخريطة +// MAP PICKER OVERLAY - Glassmorphism Design // ───────────────────────────────────────────────────────────────────────────── class _MapPickerOverlay extends StatelessWidget { final MapPassengerController controller; const _MapPickerOverlay({required this.controller}); - // ── الحصول على نص الحالة الحالية ───────────────────────────────────────── String _getModeTitle(BuildContext context) { if (controller.isPickingWaypoint) { return 'Move map to set stop'.tr + @@ -551,16 +1002,11 @@ class _MapPickerOverlay extends StatelessWidget { } String _getConfirmLabel(BuildContext context) { - if (controller.isPickingWaypoint) { - return 'Set as Stop'.tr; - } - if (controller.passengerStartLocationFromMap) { + if (controller.isPickingWaypoint) return 'Set as Stop'.tr; + if (controller.passengerStartLocationFromMap) return 'Confirm Pickup Location'.tr; - } else if (controller.workLocationFromMap) { - return 'Set as Work'.tr; - } else if (controller.homeLocationFromMap) { - return 'Set as Home'.tr; - } + if (controller.workLocationFromMap) return 'Set as Work'.tr; + if (controller.homeLocationFromMap) return 'Set as Home'.tr; return 'Set Destination'.tr; } @@ -586,37 +1032,63 @@ class _MapPickerOverlay extends StatelessWidget { final modeColor = _getModeColor(); return Positioned( - bottom: Get.height * .03, - left: 12, - right: 12, + bottom: Get.height * .035, + left: 16, + right: 16, child: Column( mainAxisSize: MainAxisSize.min, children: [ - // ── بانر التعليمات ─────────────────────────────────────────────── - Container( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + // ── Dynamic instruction banner with gradient ─────────────────────── + AnimatedContainer( + duration: _D.medium, + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14), decoration: BoxDecoration( - color: modeColor, - borderRadius: BorderRadius.circular(14), - boxShadow: [ - BoxShadow( - color: modeColor.withOpacity(0.4), - blurRadius: 12, - offset: const Offset(0, 4), - ) - ], + gradient: LinearGradient( + colors: [ + modeColor, + modeColor.withOpacity(0.88), + modeColor.withOpacity(0.75), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(_D.radiusCard), + boxShadow: _D.glowShadow(modeColor, intensity: 0.5), + border: Border.all( + color: Colors.white.withOpacity(0.35), + width: 1, + ), ), child: Row( children: [ - Icon(_getModeIcon(), color: Colors.white, size: 20), - const SizedBox(width: 10), + AnimatedContainer( + duration: _D.fast, + width: 38, + height: 38, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.22), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.white.withOpacity(0.3), + width: 1, + ), + ), + child: Icon( + _getModeIcon(), + color: Colors.white, + size: 19, + ), + ), + const SizedBox(width: 14), Expanded( child: Text( _getModeTitle(context), style: const TextStyle( color: Colors.white, - fontWeight: FontWeight.w600, - fontSize: 13, + fontWeight: FontWeight.w700, + fontSize: 14, + height: 1.3, + letterSpacing: -0.2, ), ), ), @@ -624,119 +1096,233 @@ class _MapPickerOverlay extends StatelessWidget { ), ), - const SizedBox(height: 8), + const SizedBox(height: 12), - // ── بطاقة الإحداثيات + زر التأكيد ────────────────────────────── + // ── Glassmorphism coordinate card ───────────────────────────────── Container( decoration: BoxDecoration( - color: AppColor.secondaryColor, - borderRadius: BorderRadius.circular(18), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.12), - blurRadius: 20, - offset: const Offset(0, 6), - ), - ], + color: AppColor.secondaryColor.withOpacity(0.98), + borderRadius: BorderRadius.circular(_D.radiusCard), + boxShadow: _D.cardShadow, + border: Border.all( + color: Colors.white.withOpacity(0.7), + width: 1.3, + ), ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // ── الإحداثيات الحالية ──────────────────────────────────── - Padding( - padding: const EdgeInsets.fromLTRB(16, 14, 16, 0), - child: Row( - children: [ - Container( - width: 8, - height: 8, - decoration: BoxDecoration( - color: modeColor, - shape: BoxShape.circle, - ), - ), - const SizedBox(width: 10), - Expanded( - child: Text( - '${controller.newMyLocation.latitude.toStringAsFixed(5)}' - ', ${controller.newMyLocation.longitude.toStringAsFixed(5)}', - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade600, - fontFeatures: const [FontFeature.tabularFigures()], + child: ClipRRect( + borderRadius: BorderRadius.circular(_D.radiusCard), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Coordinate display with modern styling + Padding( + padding: const EdgeInsets.fromLTRB(20, 18, 20, 2), + child: Row( + children: [ + AnimatedContainer( + duration: _D.fast, + width: 34, + height: 34, + decoration: BoxDecoration( + color: modeColor.withOpacity(0.12), + borderRadius: BorderRadius.circular(11), + border: Border.all( + color: modeColor.withOpacity(0.25), + width: 1, + ), + ), + child: Icon( + Icons.gps_fixed_rounded, + color: modeColor, + size: 16, ), ), - ), - ], + const SizedBox(width: 14), + Expanded( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: Colors.grey.shade50, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: Colors.grey.shade200, + width: 1, + ), + ), + child: Text( + '${controller.newMyLocation.latitude.toStringAsFixed(5)}, ' + '${controller.newMyLocation.longitude.toStringAsFixed(5)}', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade700, + fontFeatures: const [ + FontFeature.tabularFigures() + ], + letterSpacing: 0.3, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + const SizedBox(width: 10), + // Live indicator badge + Container( + padding: const EdgeInsets.symmetric( + horizontal: 9, vertical: 4), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.green.shade100, + Colors.green.shade50, + ], + ), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: Colors.green.shade200, + width: 1, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 500), + width: 7, + height: 7, + decoration: BoxDecoration( + color: Colors.green.shade500, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.green.shade300 + .withOpacity(0.6), + blurRadius: 6, + spreadRadius: 1, + ), + ], + ), + ), + const SizedBox(width: 5), + Text( + 'Live', + style: TextStyle( + fontSize: 10.5, + color: Colors.green.shade700, + fontWeight: FontWeight.w700, + letterSpacing: 0.5, + ), + ), + ], + ), + ), + ], + ), ), - ), - const SizedBox(height: 10), - const Divider(height: 1, thickness: 0.5), + const SizedBox(height: 16), - // ── الأزرار ─────────────────────────────────────────────── - Padding( - padding: const EdgeInsets.fromLTRB(12, 10, 12, 14), - child: Row( - children: [ - // زر إلغاء - Expanded( - flex: 2, - child: OutlinedButton.icon( - onPressed: () { - controller.isPickerShown = false; - controller.passengerStartLocationFromMap = false; - controller.startLocationFromMap = false; - controller.workLocationFromMap = false; - controller.homeLocationFromMap = false; - controller.isPickingWaypoint = false; - controller.pickingWaypointIndex = -1; - // أعد الخريطة لحالتها المنهارة - if (!controller.isMainBottomMenuMap) { - controller.isMainBottomMenuMap = true; - controller.mainBottomMenuMapHeight = - Get.height * .22; - } - controller.update(); - }, - style: OutlinedButton.styleFrom( - foregroundColor: Colors.grey.shade600, - side: BorderSide(color: Colors.grey.shade300), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12)), - padding: const EdgeInsets.symmetric(vertical: 12), - ), - icon: const Icon(Icons.close_rounded, size: 16), - label: Text('Cancel'.tr, - style: const TextStyle(fontSize: 13)), - ), + // Elegant separator + Container( + height: 1, + margin: const EdgeInsets.symmetric(horizontal: 20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.transparent, + Colors.grey.shade200, + Colors.transparent, + ], ), - const SizedBox(width: 10), - // زر التأكيد - Expanded( - flex: 3, - child: ElevatedButton.icon( - onPressed: () => _onConfirmTap(controller, context), - style: ElevatedButton.styleFrom( - backgroundColor: modeColor, - foregroundColor: Colors.white, - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12)), - padding: const EdgeInsets.symmetric(vertical: 13), - ), - icon: Icon(_getModeIcon(), size: 18), - label: Text( - _getConfirmLabel(context), - style: const TextStyle( - fontWeight: FontWeight.w600, fontSize: 13), - ), - ), - ), - ], + ), ), - ), - ], + + // Action buttons with modern styling + Padding( + padding: const EdgeInsets.fromLTRB(16, 14, 16, 18), + child: Row( + children: [ + // Cancel button - Subtle outline style + Expanded( + flex: 2, + child: AnimatedContainer( + duration: _D.fast, + child: OutlinedButton.icon( + onPressed: () { + controller.isPickerShown = false; + controller.passengerStartLocationFromMap = + false; + controller.startLocationFromMap = false; + controller.workLocationFromMap = false; + controller.homeLocationFromMap = false; + controller.isPickingWaypoint = false; + controller.pickingWaypointIndex = -1; + if (!controller.isMainBottomMenuMap) { + controller.isMainBottomMenuMap = true; + controller.mainBottomMenuMapHeight = + Get.height * .22; + } + controller.update(); + }, + style: OutlinedButton.styleFrom( + foregroundColor: Colors.grey.shade600, + side: BorderSide( + color: Colors.grey.shade200, width: 1.5), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(_D.radiusBtn), + ), + padding: + const EdgeInsets.symmetric(vertical: 14), + elevation: 0, + ), + icon: const Icon(Icons.close_rounded, size: 17), + label: Text( + 'Cancel'.tr, + style: const TextStyle( + fontSize: 13.5, + fontWeight: FontWeight.w600), + ), + ), + ), + ), + const SizedBox(width: 12), + // Confirm button - Gradient with glow + Expanded( + flex: 3, + child: AnimatedContainer( + duration: _D.fast, + child: ElevatedButton.icon( + onPressed: () => + _onConfirmTap(controller, context), + style: ElevatedButton.styleFrom( + backgroundColor: modeColor, + foregroundColor: Colors.white, + elevation: 0, + shadowColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(_D.radiusBtn), + ), + padding: + const EdgeInsets.symmetric(vertical: 14), + ), + icon: Icon(_getModeIcon(), size: 18), + label: Text( + _getConfirmLabel(context), + style: const TextStyle( + fontWeight: FontWeight.w700, + fontSize: 13.5, + letterSpacing: 0.2), + ), + ), + ), + ), + ], + ), + ), + ], + ), ), ), ], @@ -744,30 +1330,23 @@ class _MapPickerOverlay extends StatelessWidget { ); } - // ────────────────────────────────────────────────────────────────────────── - // منطق التأكيد - هنا يتم إصلاح البق الأساسي - // ────────────────────────────────────────────────────────────────────────── + // ── confirm logic (unchanged) ───────────────────────────────────────────── Future _onConfirmTap( MapPassengerController controller, BuildContext context) async { - // ⚠️ ROOT FIX: onCameraMoveThrottled يعمل بنظام Throttle (يحفظ أول موقع) - // لكن نحتاج Debounce (يحفظ آخر موقع بعد توقف الكاميرا). - // نضيف delay 280ms لضمان أن الكاميرا توقفت وأن الـ timer (160ms) انتهى. - // هذا يضمن أن newMyLocation = الموقع الفعلي الأخير للكاميرا. ✅ await Future.delayed(const Duration(milliseconds: 280)); - // التقاط snapshot آمن بعد انتهاء الـ debounce timer final LatLng currentCameraPosition = LatLng( controller.newMyLocation.latitude, controller.newMyLocation.longitude, ); - print('🌐 MAP PICKER CENTER: ${currentCameraPosition.latitude}, ${currentCameraPosition.longitude}'); - print('✅ _onConfirmTap confirmed coordinates: ${currentCameraPosition.latitude}, ${currentCameraPosition.longitude}'); + print( + '🌐 MAP PICKER CENTER: ${currentCameraPosition.latitude}, ${currentCameraPosition.longitude}'); + print( + '✅ _onConfirmTap confirmed coordinates: ${currentCameraPosition.latitude}, ${currentCameraPosition.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, @@ -784,36 +1363,26 @@ class _MapPickerOverlay extends StatelessWidget { controller.clearPolyline(); controller.data = []; - // ── CASE 1: تأكيد نقطة الانطلاق (بعد تحديد الهدف سابقاً) ───────────── if (controller.passengerStartLocationFromMap) { - // حفظ نقطة الانطلاق من موقع الكاميرا الحالي (snapshot آمن) final LatLng start = currentCameraPosition; controller.newStartPointLocation = start; - - // تنظيف الحالة قبل استدعاء getDirectionMap controller.passengerStartLocationFromMap = false; controller.isPickerShown = false; controller.currentLocationToFormPlaces = false; controller.placesDestination = []; controller.clearPlacesStart(); controller.clearPlacesDestination(); - - // العودة لحالة القائمة المنهارة controller.isMainBottomMenuMap = true; controller.mainBottomMenuMapHeight = Get.height * .22; controller.update(); - - // 🔑 الـ destination محفوظة مسبقاً في myDestination - لا تلمسها هنا await controller.getDirectionMap( '${start.latitude},${start.longitude}', '${controller.myDestination.latitude},${controller.myDestination.longitude}', ); - controller.showBottomSheet1(); return; } - // ── CASE 2: تأكيد نقطة الانطلاق العادية (startLocationFromMap) ───────── if (controller.startLocationFromMap) { final LatLng start = currentCameraPosition; controller.newMyLocation = start; @@ -826,7 +1395,6 @@ class _MapPickerOverlay extends StatelessWidget { return; } - // ── CASE 3: حفظ موقع العمل ─────────────────────────────────────────── if (controller.workLocationFromMap) { final LatLng work = currentCameraPosition; box.write(BoxName.addWork, @@ -842,7 +1410,6 @@ class _MapPickerOverlay extends StatelessWidget { return; } - // ── CASE 4: حفظ موقع المنزل ───────────────────────────────────────── if (controller.homeLocationFromMap) { final LatLng home = currentCameraPosition; box.write(BoxName.addHome, @@ -858,28 +1425,17 @@ class _MapPickerOverlay extends StatelessWidget { return; } - // ── CASE 5 (DEFAULT): تحديد الوجهة - المرحلة الأولى ────────────────── - // currentCameraPosition محفوظ بأمان في بداية الدالة بعد الـ delay ✅ final LatLng confirmedDestination = currentCameraPosition; - - // ─── 1. حفظ الوجهة بأمان ───────────────────────────────────────────── controller.myDestination = confirmedDestination; controller.hintTextDestinationPoint = '${confirmedDestination.latitude.toStringAsFixed(4)} , ${confirmedDestination.longitude.toStringAsFixed(4)}'; controller.placesDestination = []; controller.placeDestinationController.clear(); - - // ─── 2. الانتقال لمرحلة تحديد نقطة الانطلاق ───────────────────────── controller.passengerStartLocationFromMap = true; - // isPickerShown يبقى true لأننا لا زلنا في وضع الـ picker - controller.update(); - // ─── 3. العمليات الـ async بعد حفظ الوجهة بأمان ────────────────────── 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, @@ -887,7 +1443,6 @@ class _MapPickerOverlay extends StatelessWidget { )), ); } else { - // Normal flow: move camera to the passenger's own location await controller.mapController?.animateCamera( CameraUpdate.newLatLng(LatLng( controller.passengerLocation.latitude, @@ -895,11 +1450,8 @@ class _MapPickerOverlay extends StatelessWidget { )), ); } - } catch (_) { - // Guard against disposed GoogleMapController - } + } catch (_) {} - // ─── 4. إشعار المستخدم ────────────────────────────────────────────── Get.snackbar( 'Destination Set'.tr, controller.isAnotherOreder @@ -916,10 +1468,9 @@ class _MapPickerOverlay extends StatelessWidget { } // ───────────────────────────────────────────────────────────────────────────── -// WIDGETS المساعدة +// HELPER WIDGETS - Modern Redesign // ───────────────────────────────────────────────────────────────────────────── -/// صف موقع البداية (للعرض فقط) class _LocationRow extends StatelessWidget { final IconData icon; final Color iconColor; @@ -934,23 +1485,46 @@ class _LocationRow extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11), + return AnimatedContainer( + duration: _D.fast, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( - color: isStart - ? AppColor.primaryColor.withOpacity(0.05) - : Colors.transparent, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.grey.shade200), + gradient: isStart + ? LinearGradient( + colors: [ + AppColor.primaryColor.withOpacity(0.06), + AppColor.primaryColor.withOpacity(0.02), + ], + ) + : null, + color: isStart ? null : Colors.transparent, + borderRadius: BorderRadius.circular(_D.radiusInner), + border: Border.all(color: Colors.grey.shade100, width: 1), ), child: Row( children: [ - Icon(icon, color: iconColor, size: 18), - const SizedBox(width: 10), + AnimatedContainer( + duration: _D.fast, + width: 32, + height: 32, + decoration: BoxDecoration( + color: iconColor.withOpacity(0.12), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: iconColor.withOpacity(0.2), + width: 1, + ), + ), + child: Icon(icon, color: iconColor, size: 16), + ), + const SizedBox(width: 12), Expanded( child: Text( label, - style: AppStyle.subtitle.copyWith(fontSize: 13), + style: AppStyle.subtitle.copyWith( + fontSize: 13.5, + fontWeight: FontWeight.w500, + ), maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -961,7 +1535,6 @@ class _LocationRow extends StatelessWidget { } } -/// صف في التايملاين (للعرض داخل عمود المسار) class _TimelineRow extends StatelessWidget { final IconData icon; final Color iconColor; @@ -976,21 +1549,46 @@ class _TimelineRow extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + return AnimatedContainer( + duration: _D.fast, + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11), decoration: BoxDecoration( - color: bgColor.withAlpha(15), - borderRadius: BorderRadius.circular(10), - border: Border.all(color: bgColor.withAlpha(40)), + gradient: LinearGradient( + colors: [ + bgColor.withAlpha(15), + bgColor.withAlpha(8), + ], + ), + borderRadius: BorderRadius.circular(_D.radiusInner), + border: Border.all( + color: bgColor.withAlpha(35), + width: 1, + ), ), child: Row( children: [ - Icon(icon, color: iconColor, size: 16), - const SizedBox(width: 8), + AnimatedContainer( + duration: _D.fast, + width: 30, + height: 30, + decoration: BoxDecoration( + color: bgColor.withAlpha(25), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: bgColor.withAlpha(40), + width: 1, + ), + ), + child: Icon(icon, color: iconColor, size: 15), + ), + const SizedBox(width: 12), Expanded( child: Text( label, - style: AppStyle.subtitle.copyWith(fontSize: 12.5), + style: AppStyle.subtitle.copyWith( + fontSize: 13, + fontWeight: FontWeight.w500, + ), maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -1001,7 +1599,7 @@ class _TimelineRow extends StatelessWidget { } } -/// شريحة الأماكن الأخيرة +/// شريحة الأماكن الأخيرة - تصميم عصري class _RecentPlaceChip extends StatelessWidget { final MapPassengerController controller; final int index; @@ -1011,189 +1609,291 @@ class _RecentPlaceChip extends StatelessWidget { Widget build(BuildContext context) { final place = controller.recentPlaces[index]; - return GestureDetector( - onTap: () { - MyDialog().getDialog( - 'Are you want to go this site'.tr, - ' ', - () async { - Get.back(); - await controller.getLocation(); - await controller.getDirectionMap( - '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', - '${place['latitude']},${place['longitude']}', - ); - controller.showBottomSheet1(); - }, - ); - }, - onLongPress: () { - MyDialog().getDialog( - 'Are you sure to delete this location?'.tr, - '', - () { - sql.deleteData(TableName.recentLocations, place['id']); - controller.getFavioratePlaces(); - controller.update(); - Get.back(); - mySnackbarSuccess('deleted'.tr); - }, - ); - }, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: AppColor.primaryColor.withOpacity(0.07), - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: AppColor.primaryColor.withOpacity(0.2), width: 1), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.history_rounded, - size: 13, color: AppColor.primaryColor.withOpacity(0.7)), - const SizedBox(width: 5), - Text( - place['name'] ?? '', - style: TextStyle( - fontSize: 12, - color: AppColor.primaryColor.withOpacity(0.85), - fontWeight: FontWeight.w500, - ), + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + MyDialog().getDialog( + 'Are you want to go this site'.tr, + ' ', + () async { + Get.back(); + await controller.getLocation(); + await controller.getDirectionMap( + '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', + '${place['latitude']},${place['longitude']}', + ); + controller.showBottomSheet1(); + }, + ); + }, + onLongPress: () { + MyDialog().getDialog( + 'Are you sure to delete this location?'.tr, + '', + () { + sql.deleteData(TableName.recentLocations, place['id']); + controller.getFavioratePlaces(); + controller.update(); + Get.back(); + mySnackbarSuccess('deleted'.tr); + }, + ); + }, + borderRadius: BorderRadius.circular(_D.radiusChip), + child: AnimatedContainer( + duration: _D.fast, + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 7), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.primaryColor.withOpacity(0.08), + AppColor.primaryColor.withOpacity(0.04), + ], ), - ], + borderRadius: BorderRadius.circular(_D.radiusChip), + border: Border.all( + color: AppColor.primaryColor.withOpacity(0.18), + width: 1, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.history_rounded, + size: 14, + color: AppColor.primaryColor.withOpacity(0.7), + ), + const SizedBox(width: 7), + Text( + place['name'] ?? '', + style: TextStyle( + fontSize: 12.5, + color: AppColor.primaryColor.withOpacity(0.9), + fontWeight: FontWeight.w600, + letterSpacing: -0.2, + ), + ), + ], + ), ), ), ); } } -/// زر رابط الواتس أب +/// زر رابط الواتساب - تصميم بطاقات حديث class _WhatsAppLinkButton extends StatelessWidget { final MapPassengerController controller; const _WhatsAppLinkButton({required this.controller}); @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () { - Get.dialog( - AlertDialog( - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - title: Text('WhatsApp Location Extractor'.tr), - content: Form( - key: controller.sosFormKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - MyTextForm( - controller: controller.whatsAppLocationText, - label: 'Location Link'.tr, - hint: 'Paste location link here'.tr, - type: TextInputType.url, - ), - const SizedBox(height: 16), - MyElevatedButton( - title: 'Go to this location'.tr, - onPressed: () => controller.goToWhatappLocation(), - ), - ], + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Get.dialog( + AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(22)), + title: Text('WhatsApp Location Extractor'.tr), + content: Form( + key: controller.sosFormKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + MyTextForm( + controller: controller.whatsAppLocationText, + label: 'Location Link'.tr, + hint: 'Paste location link here'.tr, + type: TextInputType.url, + ), + const SizedBox(height: 16), + MyElevatedButton( + title: 'Go to this location'.tr, + onPressed: () => controller.goToWhatappLocation(), + ), + ], + ), ), ), + ); + }, + borderRadius: BorderRadius.circular(_D.radiusInner), + child: AnimatedContainer( + duration: _D.fast, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.green.shade50.withOpacity(0.9), + Colors.green.shade50.withOpacity(0.6), + ], + ), + borderRadius: BorderRadius.circular(_D.radiusInner), + border: Border.all( + color: Colors.green.shade100.withOpacity(0.9), + width: 1, + ), ), - ); - }, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), - decoration: BoxDecoration( - color: Colors.green.shade50, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.green.shade200), - ), - child: Row( - children: [ - Icon(Icons.link_rounded, color: Colors.green.shade700, size: 18), - const SizedBox(width: 10), - Expanded( - child: Text( - 'Paste WhatsApp location link'.tr, - style: TextStyle( - color: Colors.green.shade700, - fontSize: 13, - fontWeight: FontWeight.w500), + child: Row( + children: [ + AnimatedContainer( + duration: _D.fast, + width: 34, + height: 34, + decoration: BoxDecoration( + color: Colors.green.shade100, + borderRadius: BorderRadius.circular(11), + border: Border.all( + color: Colors.green.shade200, + width: 1, + ), + ), + child: Icon( + Icons.link_rounded, + color: Colors.green.shade700, + size: 18, + ), ), - ), - Icon(Icons.arrow_forward_ios_rounded, - size: 13, color: Colors.green.shade400), - ], + const SizedBox(width: 14), + Expanded( + child: Text( + 'Paste WhatsApp location link'.tr, + style: TextStyle( + color: Colors.green.shade800, + fontSize: 13.5, + fontWeight: FontWeight.w600, + ), + ), + ), + Icon( + Icons.arrow_forward_ios_rounded, + size: 13, + color: Colors.green.shade400, + ), + ], + ), ), ), ); } } -/// زر نوع الطلب +/// زر نوع الطلب - تصميم تفاعلي حديث class _OrderTypeButton extends StatelessWidget { final MapPassengerController controller; const _OrderTypeButton({required this.controller}); @override Widget build(BuildContext context) { - return OutlinedButton.icon( - onPressed: () { - showCupertinoModalPopup( - context: context, - builder: (ctx) => CupertinoActionSheet( - title: Text('Select Order Type'.tr), - message: Text('Choose who this order is for'.tr), - actions: [ - CupertinoActionSheetAction( - child: Text('I want to order for myself'.tr), - onPressed: () { - controller.changeisAnotherOreder(false); - Navigator.pop(ctx); - }, + final bool isOther = controller.isAnotherOreder; + final Color accent = + isOther ? Colors.indigo.shade500 : AppColor.primaryColor; + + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + showCupertinoModalPopup( + context: context, + builder: (ctx) => CupertinoActionSheet( + title: Text('Select Order Type'.tr), + message: Text('Choose who this order is for'.tr), + actions: [ + CupertinoActionSheetAction( + child: Text('I want to order for myself'.tr), + onPressed: () { + controller.changeisAnotherOreder(false); + Navigator.pop(ctx); + }, + ), + CupertinoActionSheetAction( + child: Text('I want to order for someone else'.tr), + onPressed: () { + controller.changeisAnotherOreder(true); + Navigator.pop(ctx); + }, + ), + ], + cancelButton: CupertinoActionSheetAction( + isDefaultAction: true, + onPressed: () => Navigator.pop(ctx), + child: Text('Cancel'.tr), ), - CupertinoActionSheetAction( - child: Text('I want to order for someone else'.tr), - onPressed: () { - controller.changeisAnotherOreder(true); - Navigator.pop(ctx); - }, - ), - ], - cancelButton: CupertinoActionSheetAction( - isDefaultAction: true, - onPressed: () => Navigator.pop(ctx), - child: Text('Cancel'.tr), + ), + ); + }, + borderRadius: BorderRadius.circular(_D.radiusInner), + child: AnimatedContainer( + duration: _D.fast, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + accent.withOpacity(0.08), + accent.withOpacity(0.04), + ], + ), + borderRadius: BorderRadius.circular(_D.radiusInner), + border: Border.all( + color: accent.withOpacity(0.22), + width: 1, ), ), - ); - }, - style: OutlinedButton.styleFrom( - foregroundColor: AppColor.primaryColor, - side: BorderSide(color: AppColor.primaryColor.withOpacity(0.4)), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 11), - ), - icon: Icon( - controller.isAnotherOreder ? Icons.person_rounded : Icons.group_rounded, - size: 17, - ), - label: Text( - controller.isAnotherOreder - ? 'Order for myself'.tr - : 'Order for someone else'.tr, - style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500), + child: Row( + children: [ + AnimatedContainer( + duration: _D.fast, + width: 34, + height: 34, + decoration: BoxDecoration( + color: accent.withOpacity(0.14), + borderRadius: BorderRadius.circular(11), + border: Border.all( + color: accent.withOpacity(0.25), + width: 1, + ), + ), + child: Icon( + isOther ? Icons.person_rounded : Icons.group_rounded, + color: accent, + size: 17, + ), + ), + const SizedBox(width: 14), + Expanded( + child: Text( + isOther ? 'Order for myself'.tr : 'Order for someone else'.tr, + style: TextStyle( + color: accent, + fontSize: 13.5, + fontWeight: FontWeight.w600, + letterSpacing: -0.2, + ), + ), + ), + AnimatedRotation( + duration: _D.fast, + turns: isOther ? 0.5 : 0, + child: Icon( + Icons.unfold_more_rounded, + color: accent.withOpacity(0.55), + size: 19, + ), + ), + ], + ), + ), ), ); } } // ───────────────────────────────────────────────────────────────────────────── -// FAVOURITE PLACES DIALOG +// FAVOURITE PLACES DIALOG - Modern Modal Design // ───────────────────────────────────────────────────────────────────────────── class FaviouratePlacesDialog extends StatelessWidget { @@ -1206,7 +1906,7 @@ class FaviouratePlacesDialog extends StatelessWidget { return GetBuilder( builder: (controller) => Center( child: InkWell( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(14), onTap: () async { final List favoritePlaces = await sql.getAllData(TableName.placesFavorite); @@ -1222,24 +1922,82 @@ class FaviouratePlacesDialog extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.star_border_rounded, - size: 64, color: AppColor.accentColor), - const SizedBox(height: 12), - Text('No favorite places yet!'.tr, - style: AppStyle.title), + AnimatedContainer( + duration: _D.medium, + width: 76, + height: 76, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColor.accentColor.withOpacity(0.15), + AppColor.accentColor.withOpacity(0.05), + ], + ), + shape: BoxShape.circle, + border: Border.all( + color: AppColor.accentColor.withOpacity(0.2), + width: 1, + ), + ), + child: const Icon( + Icons.star_border_rounded, + size: 38, + color: AppColor.accentColor, + ), + ), + const SizedBox(height: 16), + Text( + 'No favorite places yet!'.tr, + style: AppStyle.title.copyWith( + fontWeight: FontWeight.w600, + fontSize: 15, + ), + ), ], ), ) : ListView.separated( itemCount: favoritePlaces.length, - separatorBuilder: (_, __) => const Divider(height: 1), + separatorBuilder: (_, __) => + Divider(height: 1, color: Colors.grey.shade100), itemBuilder: (context, index) => ListTile( - leading: const Icon(Icons.star, color: Colors.amber), - title: Text(favoritePlaces[index]['name'], - style: AppStyle.title), + contentPadding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + leading: AnimatedContainer( + duration: _D.fast, + width: 38, + height: 38, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.amber.shade50, + Colors.amber.shade100.withOpacity(0.5), + ], + ), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.amber.shade100, + width: 1, + ), + ), + child: const Icon( + Icons.star, + color: Colors.amber, + size: 19, + ), + ), + title: Text( + favoritePlaces[index]['name'], + style: AppStyle.title.copyWith( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), trailing: IconButton( - icon: const Icon(Icons.delete_outline, - color: Colors.redAccent), + icon: const Icon( + Icons.delete_outline, + color: Colors.redAccent, + ), onPressed: () async { await sql.deleteData(TableName.placesFavorite, favoritePlaces[index]['id']); @@ -1268,14 +2026,31 @@ class FaviouratePlacesDialog extends StatelessWidget { ); }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.star_border_rounded, - color: AppColor.accentColor, size: 20), - const SizedBox(width: 8), - Text('Favorite Places'.tr, style: AppStyle.title), + AnimatedContainer( + duration: _D.fast, + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: AppColor.accentColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: const Icon( + Icons.star_border_rounded, + color: AppColor.accentColor, + size: 21, + ), + ), + const SizedBox(width: 10), + Text( + 'Favorite Places'.tr, + style: AppStyle.title.copyWith( + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), ], ), ),