2062 lines
78 KiB
Dart
2062 lines
78 KiB
Dart
import 'package:Intaleq/views/widgets/my_textField.dart';
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:Intaleq/constant/box_name.dart';
|
|
import 'package:Intaleq/constant/style.dart';
|
|
import 'package:Intaleq/controller/home/map_passenger_controller.dart';
|
|
import 'package:Intaleq/main.dart';
|
|
import 'package:Intaleq/views/home/map_widget.dart/form_search_places_destenation.dart';
|
|
import 'package:Intaleq/views/widgets/elevated_btn.dart';
|
|
import 'package:maplibre_gl/maplibre_gl.dart';
|
|
import '../../../constant/colors.dart';
|
|
import '../../../constant/table_names.dart';
|
|
import '../../../controller/functions/toast.dart';
|
|
import '../../../controller/functions/tts.dart';
|
|
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<BoxShadow> 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<BoxShadow> 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<BoxShadow> 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 - Modern Redesign
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
class MainBottomMenuMap extends StatelessWidget {
|
|
const MainBottomMenuMap({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
Get.put(MapPassengerController());
|
|
|
|
return GetBuilder<MapPassengerController>(
|
|
builder: (controller) {
|
|
if (controller.isPickerShown) {
|
|
return _MapPickerOverlay(controller: controller);
|
|
}
|
|
|
|
return Positioned(
|
|
bottom: Get.height * .035,
|
|
left: 16,
|
|
right: 16,
|
|
child: AnimatedContainer(
|
|
duration: _D.medium,
|
|
curve: Curves.easeOutQuint,
|
|
height: controller.mainBottomMenuMapHeight,
|
|
decoration: BoxDecoration(
|
|
gradient: _D.cardGradient(),
|
|
borderRadius: BorderRadius.circular(_D.radiusCard),
|
|
boxShadow: _D.cardShadow,
|
|
border: Border.all(
|
|
color: Colors.white.withOpacity(0.65),
|
|
width: 1.2,
|
|
),
|
|
),
|
|
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),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// COLLAPSED VIEW - Modern & Elegant
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
class _CollapsedView extends StatelessWidget {
|
|
final MapPassengerController controller;
|
|
const _CollapsedView({required this.controller});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final String firstName = box.read(BoxName.name).toString().split(' ').first;
|
|
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// ── 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,
|
|
),
|
|
),
|
|
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,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// 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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// ── 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 - Dynamic Route Planner
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
class _ExpandedView extends StatelessWidget {
|
|
final MapPassengerController controller;
|
|
final BuildContext context;
|
|
const _ExpandedView({required this.controller, required this.context});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
// ── 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.w800,
|
|
fontSize: 18,
|
|
letterSpacing: -0.5,
|
|
height: 1.2,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
// 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,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// 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),
|
|
|
|
// ── 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,
|
|
iconColor: AppColor.primaryColor,
|
|
bgColor: AppColor.primaryColor,
|
|
label: controller.currentLocationString,
|
|
)
|
|
: Padding(
|
|
padding: const EdgeInsets.only(right: 16),
|
|
child: formSearchPlacesStart(),
|
|
),
|
|
),
|
|
|
|
// Dynamic waypoints with color coding
|
|
...List.generate(controller.activeMenuWaypointCount, (index) {
|
|
final wpName = controller.menuWaypointNames[index];
|
|
final isSet = controller.menuWaypoints[index] != null;
|
|
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: accent,
|
|
showTopLine: true,
|
|
showBottomLine: true,
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
|
decoration: BoxDecoration(
|
|
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: [
|
|
// Animated waypoint number badge
|
|
AnimatedContainer(
|
|
duration: _D.fast,
|
|
width: 26,
|
|
height: 26,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: index == 0
|
|
? [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: 11,
|
|
fontWeight: FontWeight.w800,
|
|
letterSpacing: -0.3,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
controller.changeMainBottomMenuMap();
|
|
controller.startPickingWaypointOnMap(index);
|
|
},
|
|
child: Text(
|
|
isSet ? wpName : '${'Stop'.tr} ${index + 1}',
|
|
style: TextStyle(
|
|
fontSize: 13.5,
|
|
color: isSet
|
|
? accent.withOpacity(0.9)
|
|
: Colors.grey.shade400,
|
|
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: 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: 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,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
|
|
// Add stop button - Modern CTA style
|
|
if (controller.activeMenuWaypointCount < 2)
|
|
_buildTimelineItem(
|
|
dotColor: Colors.orange.shade300,
|
|
isDotDashed: true,
|
|
showTopLine: true,
|
|
showBottomLine: true,
|
|
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,
|
|
),
|
|
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,
|
|
),
|
|
const SizedBox(width: 10),
|
|
Text(
|
|
'Add a Stop'.tr,
|
|
style: TextStyle(
|
|
color: Colors.orange.shade700,
|
|
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 elegant styling
|
|
_buildTimelineItem(
|
|
dotColor: Colors.red.shade500,
|
|
showTopLine: true,
|
|
showBottomLine: false,
|
|
isEnd: true,
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(right: 16),
|
|
child: formSearchPlacesDestenation(),
|
|
),
|
|
),
|
|
|
|
// ── Smart Surcharge banner ───────────────────────────────────────────
|
|
if (controller.activeMenuWaypointCount > 0)
|
|
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: 14),
|
|
|
|
// ── WhatsApp button - Modern card style ──────────────────────────────
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 18),
|
|
child: _WhatsAppLinkButton(controller: controller),
|
|
),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
// ── Order type button - Dynamic toggle style ─────────────────────────
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 18),
|
|
child: _OrderTypeButton(controller: controller),
|
|
),
|
|
|
|
const SizedBox(height: 18),
|
|
],
|
|
);
|
|
}
|
|
|
|
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: 20),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(
|
|
width: 24,
|
|
child: Column(
|
|
children: [
|
|
if (showTopLine)
|
|
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),
|
|
),
|
|
),
|
|
),
|
|
// 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.withOpacity(0.95),
|
|
shape: BoxShape.circle,
|
|
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,
|
|
),
|
|
if (showBottomLine)
|
|
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: 14),
|
|
Expanded(child: child),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 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 +
|
|
' ${controller.pickingWaypointIndex + 1}'.tr;
|
|
}
|
|
if (controller.passengerStartLocationFromMap) {
|
|
return 'Move map to your pickup point'.tr;
|
|
} else if (controller.startLocationFromMap) {
|
|
return 'Move map to set start location'.tr;
|
|
} else if (controller.workLocationFromMap) {
|
|
return 'Move map to your work location'.tr;
|
|
} else if (controller.homeLocationFromMap) {
|
|
return 'Move map to your home location'.tr;
|
|
}
|
|
return 'Move map to select destination'.tr;
|
|
}
|
|
|
|
String _getConfirmLabel(BuildContext context) {
|
|
if (controller.isPickingWaypoint) return 'Set as Stop'.tr;
|
|
if (controller.passengerStartLocationFromMap)
|
|
return 'Confirm Pickup Location'.tr;
|
|
if (controller.workLocationFromMap) return 'Set as Work'.tr;
|
|
if (controller.homeLocationFromMap) return 'Set as Home'.tr;
|
|
return 'Set Destination'.tr;
|
|
}
|
|
|
|
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;
|
|
if (controller.homeLocationFromMap) return Icons.home_rounded;
|
|
return Icons.location_on_rounded;
|
|
}
|
|
|
|
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;
|
|
return AppColor.primaryColor;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final modeColor = _getModeColor();
|
|
|
|
return Positioned(
|
|
bottom: Get.height * .035,
|
|
left: 16,
|
|
right: 16,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// ── Dynamic instruction banner with gradient ───────────────────────
|
|
AnimatedContainer(
|
|
duration: _D.medium,
|
|
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
|
|
decoration: BoxDecoration(
|
|
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: [
|
|
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.w700,
|
|
fontSize: 14,
|
|
height: 1.3,
|
|
letterSpacing: -0.2,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
// ── Glassmorphism coordinate card ─────────────────────────────────
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
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: 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: 16),
|
|
|
|
// Elegant separator
|
|
Container(
|
|
height: 1,
|
|
margin: const EdgeInsets.symmetric(horizontal: 20),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
Colors.transparent,
|
|
Colors.grey.shade200,
|
|
Colors.transparent,
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// 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),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// ── confirm logic (unchanged) ─────────────────────────────────────────────
|
|
Future<void> _onConfirmTap(
|
|
MapPassengerController controller, BuildContext context) async {
|
|
await Future.delayed(const Duration(milliseconds: 280));
|
|
|
|
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}');
|
|
|
|
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 = [];
|
|
|
|
if (controller.passengerStartLocationFromMap) {
|
|
final LatLng start = currentCameraPosition;
|
|
controller.newStartPointLocation = start;
|
|
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();
|
|
await controller.getDirectionMap(
|
|
'${start.latitude},${start.longitude}',
|
|
'${controller.myDestination.latitude},${controller.myDestination.longitude}',
|
|
);
|
|
controller.showBottomSheet1();
|
|
return;
|
|
}
|
|
|
|
if (controller.startLocationFromMap) {
|
|
final LatLng start = currentCameraPosition;
|
|
controller.newMyLocation = start;
|
|
controller.newStartPointLocation = start;
|
|
controller.hintTextStartPoint =
|
|
'${start.latitude.toStringAsFixed(4)} , ${start.longitude.toStringAsFixed(4)}';
|
|
controller.startLocationFromMap = false;
|
|
controller.isPickerShown = false;
|
|
controller.update();
|
|
return;
|
|
}
|
|
|
|
if (controller.workLocationFromMap) {
|
|
final LatLng work = currentCameraPosition;
|
|
box.write(BoxName.addWork,
|
|
'${work.latitude.toStringAsFixed(4)} , ${work.longitude.toStringAsFixed(4)}');
|
|
controller.hintTextDestinationPoint = 'To Work'.tr;
|
|
controller.workLocationFromMap = false;
|
|
controller.isPickerShown = false;
|
|
controller.update();
|
|
Get.snackbar('Work Saved'.tr, '',
|
|
backgroundColor: AppColor.greenColor,
|
|
colorText: Colors.white,
|
|
snackPosition: SnackPosition.BOTTOM);
|
|
return;
|
|
}
|
|
|
|
if (controller.homeLocationFromMap) {
|
|
final LatLng home = currentCameraPosition;
|
|
box.write(BoxName.addHome,
|
|
'${home.latitude.toStringAsFixed(4)} , ${home.longitude.toStringAsFixed(4)}');
|
|
controller.hintTextDestinationPoint = 'To Home'.tr;
|
|
controller.homeLocationFromMap = false;
|
|
controller.isPickerShown = false;
|
|
controller.update();
|
|
Get.snackbar('Home Saved'.tr, '',
|
|
backgroundColor: AppColor.greenColor,
|
|
colorText: Colors.white,
|
|
snackPosition: SnackPosition.BOTTOM);
|
|
return;
|
|
}
|
|
|
|
final LatLng confirmedDestination = currentCameraPosition;
|
|
controller.myDestination = confirmedDestination;
|
|
controller.hintTextDestinationPoint =
|
|
'${confirmedDestination.latitude.toStringAsFixed(4)} , ${confirmedDestination.longitude.toStringAsFixed(4)}';
|
|
controller.placesDestination = [];
|
|
controller.placeDestinationController.clear();
|
|
controller.passengerStartLocationFromMap = true;
|
|
controller.update();
|
|
|
|
try {
|
|
if (controller.isAnotherOreder) {
|
|
await controller.mapController?.animateCamera(
|
|
CameraUpdate.newLatLng(LatLng(
|
|
controller.newStartPointLocation.latitude,
|
|
controller.newStartPointLocation.longitude,
|
|
)),
|
|
);
|
|
} else {
|
|
await controller.mapController?.animateCamera(
|
|
CameraUpdate.newLatLng(LatLng(
|
|
controller.passengerLocation.latitude,
|
|
controller.passengerLocation.longitude,
|
|
)),
|
|
);
|
|
}
|
|
} catch (_) {}
|
|
|
|
Get.snackbar(
|
|
'Destination Set'.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,
|
|
duration: const Duration(seconds: 2),
|
|
margin: const EdgeInsets.all(12),
|
|
borderRadius: 12,
|
|
);
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// HELPER WIDGETS - Modern Redesign
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
class _LocationRow extends StatelessWidget {
|
|
final IconData icon;
|
|
final Color iconColor;
|
|
final String label;
|
|
final bool isStart;
|
|
const _LocationRow({
|
|
required this.icon,
|
|
required this.iconColor,
|
|
required this.label,
|
|
this.isStart = false,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AnimatedContainer(
|
|
duration: _D.fast,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
decoration: BoxDecoration(
|
|
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: [
|
|
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.5,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
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 AnimatedContainer(
|
|
duration: _D.fast,
|
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11),
|
|
decoration: BoxDecoration(
|
|
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: [
|
|
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: 13,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// شريحة الأماكن الأخيرة - تصميم عصري
|
|
class _RecentPlaceChip extends StatelessWidget {
|
|
final MapPassengerController controller;
|
|
final int index;
|
|
const _RecentPlaceChip({required this.controller, required this.index});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final place = controller.recentPlaces[index];
|
|
|
|
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 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: 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,
|
|
),
|
|
),
|
|
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) {
|
|
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),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
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,
|
|
),
|
|
),
|
|
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 - Modern Modal Design
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
class FaviouratePlacesDialog extends StatelessWidget {
|
|
const FaviouratePlacesDialog({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
Get.put(MapPassengerController());
|
|
|
|
return GetBuilder<MapPassengerController>(
|
|
builder: (controller) => Center(
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(14),
|
|
onTap: () async {
|
|
final List favoritePlaces =
|
|
await sql.getAllData(TableName.placesFavorite);
|
|
|
|
Get.defaultDialog(
|
|
title: 'Favorite Places'.tr,
|
|
titleStyle: AppStyle.title,
|
|
content: SizedBox(
|
|
width: Get.width * .85,
|
|
height: 300,
|
|
child: favoritePlaces.isEmpty
|
|
? Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
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: (_, __) =>
|
|
Divider(height: 1, color: Colors.grey.shade100),
|
|
itemBuilder: (context, index) => ListTile(
|
|
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,
|
|
),
|
|
onPressed: () async {
|
|
await sql.deleteData(TableName.placesFavorite,
|
|
favoritePlaces[index]['id']);
|
|
Get.back();
|
|
Toast.show(
|
|
context,
|
|
'${'Deleted'.tr} ${favoritePlaces[index]['name']}',
|
|
AppColor.redColor,
|
|
);
|
|
},
|
|
),
|
|
onTap: () async {
|
|
Get.back();
|
|
await controller.getLocation();
|
|
await controller.getDirectionMap(
|
|
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
|
|
'${favoritePlaces[index]['latitude']},${favoritePlaces[index]['longitude']}',
|
|
);
|
|
controller.showBottomSheet1();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
confirm: MyElevatedButton(
|
|
title: 'Back'.tr, onPressed: () => Get.back()),
|
|
);
|
|
},
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|