1173 lines
46 KiB
Dart
1173 lines
46 KiB
Dart
import 'package:Intaleq/print.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/map_engine_controller.dart';
|
|
import 'package:Intaleq/controller/home/map/location_search_controller.dart';
|
|
import 'package:Intaleq/controller/home/map/ride_lifecycle_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:intaleq_maps/intaleq_maps.dart';
|
|
import '../../../constant/colors.dart';
|
|
import '../../../constant/table_names.dart';
|
|
import '../../widgets/error_snakbar.dart';
|
|
import '../../widgets/mydialoug.dart';
|
|
import 'form_search_start.dart';
|
|
|
|
// ─── Design Tokens (Modern & Dynamic) ────────────────────────────────────────
|
|
|
|
class _D {
|
|
static const double radiusCard = 28;
|
|
static const double radiusChip = 20;
|
|
static const double radiusInner = 14;
|
|
static const double radiusPill = 50;
|
|
|
|
static List<BoxShadow> get cardShadow => [
|
|
BoxShadow(
|
|
color: Colors.black.withValues(alpha: 0.08),
|
|
blurRadius: 40,
|
|
spreadRadius: -8,
|
|
offset: const Offset(0, 12),
|
|
),
|
|
BoxShadow(
|
|
color: Colors.black.withValues(alpha: 0.04),
|
|
blurRadius: 16,
|
|
spreadRadius: -4,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
];
|
|
|
|
static List<BoxShadow> glowShadow(Color c, {double intensity = 0.4}) => [
|
|
BoxShadow(
|
|
color: c.withValues(alpha: intensity),
|
|
blurRadius: 24,
|
|
spreadRadius: -4,
|
|
offset: const Offset(0, 8),
|
|
),
|
|
BoxShadow(
|
|
color: c.withValues(alpha: intensity * 0.5),
|
|
blurRadius: 12,
|
|
spreadRadius: -2,
|
|
offset: const Offset(0, 3),
|
|
),
|
|
];
|
|
|
|
static const Duration fast = Duration(milliseconds: 180);
|
|
static const Duration medium = Duration(milliseconds: 420);
|
|
|
|
static LinearGradient primaryGradient({
|
|
Alignment begin = Alignment.topLeft,
|
|
Alignment end = Alignment.bottomRight,
|
|
}) =>
|
|
LinearGradient(
|
|
begin: begin,
|
|
end: end,
|
|
colors: [
|
|
AppColor.primaryColor,
|
|
AppColor.primaryColor.withValues(alpha: 0.85),
|
|
AppColor.primaryColor.withValues(alpha: 0.7),
|
|
],
|
|
stops: const [0.0, 0.5, 1.0],
|
|
);
|
|
|
|
static LinearGradient cardGradient() => LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
colors: [
|
|
AppColor.secondaryColor.withValues(alpha: 0.98),
|
|
AppColor.secondaryColor
|
|
.withBlue(
|
|
((AppColor.secondaryColor.b * 255.0).round() + 12).clamp(0, 255),
|
|
)
|
|
.withValues(alpha: 0.95),
|
|
],
|
|
);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// MAIN BOTTOM MENU MAP - Scrollable Redesign
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
class MainBottomMenuMap extends StatelessWidget {
|
|
const MainBottomMenuMap({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GetBuilder<MapEngineController>(
|
|
builder: (controller) {
|
|
if (controller.isPickerShown) {
|
|
return const _MapPickerOverlay();
|
|
}
|
|
|
|
return Positioned(
|
|
bottom: Get.height * .035,
|
|
left: 16,
|
|
right: 16,
|
|
child: AnimatedContainer(
|
|
duration: _D.medium,
|
|
curve: Curves.easeOutQuint,
|
|
constraints: BoxConstraints(
|
|
maxHeight: controller.isMainBottomMenuMap
|
|
? Get.height * 0.4
|
|
: Get.height * 0.75, // الحد الأقصى للشاشة المفتوحة
|
|
),
|
|
decoration: BoxDecoration(
|
|
gradient: _D.cardGradient(),
|
|
borderRadius: BorderRadius.circular(_D.radiusCard),
|
|
boxShadow: _D.cardShadow,
|
|
border: Border.all(
|
|
color: Get.isDarkMode
|
|
? Colors.white.withValues(alpha: 0.15)
|
|
: Colors.white.withValues(alpha: 0.65),
|
|
width: 1.2,
|
|
),
|
|
),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(_D.radiusCard),
|
|
child: SingleChildScrollView(
|
|
physics: const BouncingScrollPhysics(),
|
|
child: controller.isMainBottomMenuMap
|
|
? const _CollapsedView()
|
|
: const _ExpandedView(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// COLLAPSED VIEW
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
class _CollapsedView extends StatelessWidget {
|
|
const _CollapsedView();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final String firstName = box.read(BoxName.name).toString().split(' ').first;
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
final rideLifecycle = Get.find<RideLifecycleController>();
|
|
|
|
return GetBuilder<LocationSearchController>(
|
|
builder: (locationSearch) {
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const SizedBox(height: 14),
|
|
AnimatedContainer(
|
|
duration: _D.fast,
|
|
width: 44,
|
|
height: 5,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
Colors.grey.shade400.withValues(alpha: 0.6),
|
|
Colors.grey.shade300,
|
|
Colors.grey.shade400.withValues(alpha: 0.6),
|
|
],
|
|
),
|
|
borderRadius: BorderRadius.circular(3),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Semantics(
|
|
button: true,
|
|
label: 'Open destination search'.tr,
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
onTap: mapEngine.changeMainBottomMenuMap,
|
|
borderRadius: BorderRadius.circular(_D.radiusInner),
|
|
child: Padding(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 18, vertical: 8),
|
|
child: Row(
|
|
children: [
|
|
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),
|
|
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 (!rideLifecycle.noCarString)
|
|
Text(
|
|
'Tap to search your destination'.tr,
|
|
style: AppStyle.subtitle.copyWith(
|
|
fontSize: 12,
|
|
color: Colors.grey.shade500,
|
|
fontWeight: FontWeight.w400,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
if (locationSearch.recentPlaces.isNotEmpty) ...[
|
|
const SizedBox(height: 12),
|
|
Container(
|
|
height: 40,
|
|
padding: const EdgeInsets.symmetric(horizontal: 18),
|
|
child: ListView.separated(
|
|
scrollDirection: Axis.horizontal,
|
|
itemCount: locationSearch.recentPlaces.length,
|
|
separatorBuilder: (_, __) => const SizedBox(width: 10),
|
|
itemBuilder: (context, index) =>
|
|
_RecentPlaceChip(locationSearch: locationSearch, index: index),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
] else
|
|
const SizedBox(height: 20),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// EXPANDED VIEW - Grouped Layout
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
class _ExpandedView extends StatelessWidget {
|
|
const _ExpandedView();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
final rideLifecycle = Get.find<RideLifecycleController>();
|
|
|
|
return GetBuilder<LocationSearchController>(
|
|
builder: (locationSearch) {
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
const SizedBox(height: 14),
|
|
Center(
|
|
child: AnimatedContainer(
|
|
duration: _D.fast,
|
|
width: 44,
|
|
height: 5,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
Colors.grey.shade400.withValues(alpha: 0.6),
|
|
Colors.grey.shade300,
|
|
Colors.grey.shade400.withValues(alpha: 0.6),
|
|
],
|
|
),
|
|
borderRadius: BorderRadius.circular(3),
|
|
),
|
|
),
|
|
),
|
|
|
|
// ── Header ──
|
|
Container(
|
|
padding: const EdgeInsets.fromLTRB(20, 18, 16, 14),
|
|
child: Row(
|
|
children: [
|
|
Text(
|
|
'Plan Your Route'.tr,
|
|
style: AppStyle.title.copyWith(
|
|
fontWeight: FontWeight.w800,
|
|
fontSize: 18,
|
|
letterSpacing: -0.5,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
Semantics(
|
|
button: true,
|
|
label: 'Close panel'.tr,
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
onTap: mapEngine.changeMainBottomMenuMap,
|
|
borderRadius: BorderRadius.circular(_D.radiusPill),
|
|
child: Container(
|
|
width: 38,
|
|
height: 38,
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade100,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(Icons.keyboard_arrow_down_rounded,
|
|
size: 24, color: Colors.grey.shade600),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// ── Group 1: Core Routing ──
|
|
_buildSectionTitle('Route'.tr),
|
|
|
|
_buildTimelineItem(
|
|
dotColor: AppColor.primaryColor,
|
|
showTopLine: false,
|
|
showBottomLine: true,
|
|
isStart: true,
|
|
child: !rideLifecycle.isAnotherOreder
|
|
? _TimelineRow(
|
|
icon: Icons.my_location_rounded,
|
|
iconColor: AppColor.primaryColor,
|
|
bgColor: AppColor.primaryColor,
|
|
label: locationSearch.currentLocationString,
|
|
)
|
|
: Padding(
|
|
padding: const EdgeInsets.only(right: 16),
|
|
child: formSearchPlacesStart(),
|
|
),
|
|
),
|
|
|
|
...List.generate(locationSearch.activeMenuWaypointCount, (index) {
|
|
final wpName = locationSearch.menuWaypointNames[index];
|
|
final isSet = locationSearch.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.withValues(alpha: 0.9), soft.withValues(alpha: 0.6)]),
|
|
borderRadius: BorderRadius.circular(_D.radiusInner),
|
|
border: Border.all(
|
|
color: isSet
|
|
? accent.withValues(alpha: 0.35)
|
|
: Colors.grey.shade200),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 26,
|
|
height: 26,
|
|
decoration:
|
|
BoxDecoration(color: accent, shape: BoxShape.circle),
|
|
child: Center(
|
|
child: Text('${index + 1}',
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w800))),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
mapEngine.changeMainBottomMenuMap();
|
|
locationSearch.startPickingWaypointOnMap(index);
|
|
},
|
|
child: Text(
|
|
isSet ? wpName : '${'Stop'.tr} ${index + 1}',
|
|
style: TextStyle(
|
|
fontSize: 13.5,
|
|
color: isSet
|
|
? accent.withValues(alpha: 0.9)
|
|
: Colors.grey.shade400,
|
|
fontWeight: isSet ? FontWeight.w600 : FontWeight.w400,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
)),
|
|
GestureDetector(
|
|
onTap: () => locationSearch.removeMenuWaypoint(index),
|
|
child: Container(
|
|
width: 28,
|
|
height: 28,
|
|
decoration: BoxDecoration(
|
|
color: Colors.red.shade50, shape: BoxShape.circle),
|
|
child: Icon(Icons.close_rounded,
|
|
color: Colors.red.shade400, size: 15),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
|
|
if (locationSearch.activeMenuWaypointCount < 2)
|
|
_buildTimelineItem(
|
|
dotColor: Colors.orange.shade300,
|
|
isDotDashed: true,
|
|
showTopLine: true,
|
|
showBottomLine: true,
|
|
child: InkWell(
|
|
onTap: () => locationSearch.addMenuWaypoint(),
|
|
borderRadius: BorderRadius.circular(_D.radiusInner),
|
|
child: Container(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(_D.radiusInner),
|
|
border: Border.all(color: Colors.orange.shade200, width: 1.5),
|
|
color: Colors.orange.shade50.withValues(alpha: 0.6),
|
|
),
|
|
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)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
_buildTimelineItem(
|
|
dotColor: Colors.red.shade500,
|
|
showTopLine: true,
|
|
showBottomLine: false,
|
|
isEnd: true,
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(right: 16),
|
|
child: formSearchPlacesDestenation(),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// ── Group 2: Quick Access ──
|
|
_buildSectionDivider(),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
_buildSectionTitle('Quick Access'.tr),
|
|
const FaviouratePlacesDialog(), // تم نقلها هنا لتكون جزء من الوصول السريع
|
|
],
|
|
),
|
|
|
|
if (locationSearch.recentPlaces.isNotEmpty)
|
|
Container(
|
|
height: 40,
|
|
margin: const EdgeInsets.only(bottom: 16),
|
|
child: ListView.separated(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
scrollDirection: Axis.horizontal,
|
|
itemCount: locationSearch.recentPlaces.length,
|
|
separatorBuilder: (_, __) => const SizedBox(width: 10),
|
|
itemBuilder: (context, index) =>
|
|
_RecentPlaceChip(locationSearch: locationSearch, index: index),
|
|
),
|
|
),
|
|
|
|
// ── Group 3: Advanced Tools ──
|
|
_buildSectionDivider(),
|
|
_buildSectionTitle('Advanced Tools'.tr),
|
|
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 18),
|
|
child: _WhatsAppLinkButton(locationSearch: locationSearch),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 18),
|
|
child: _OrderTypeButton(mapEngine: mapEngine),
|
|
),
|
|
|
|
const SizedBox(height: 24), // مساحة سفلية لضمان راحة السحب
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildSectionTitle(String title) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(left: 20, right: 20, bottom: 12),
|
|
child: Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.grey.shade400,
|
|
letterSpacing: 0.5,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSectionDivider() {
|
|
return Container(
|
|
height: 1,
|
|
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
|
color: Colors.grey.shade200,
|
|
);
|
|
}
|
|
|
|
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)
|
|
Container(
|
|
width: 2.5, height: 12, color: Colors.grey.shade300),
|
|
Container(
|
|
width: 15,
|
|
height: 15,
|
|
decoration: BoxDecoration(
|
|
color: isDotDashed ? Colors.transparent : dotColor,
|
|
shape: BoxShape.circle,
|
|
border:
|
|
Border.all(color: dotColor, width: isDotDashed ? 2 : 3),
|
|
),
|
|
),
|
|
if (showBottomLine)
|
|
Container(
|
|
width: 2.5, height: 12, color: Colors.grey.shade300),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 14),
|
|
Expanded(child: child),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// MAP PICKER OVERLAY
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
class _MapPickerOverlay extends StatelessWidget {
|
|
const _MapPickerOverlay();
|
|
|
|
String _getModeTitle(LocationSearchController locationSearch, BuildContext context) {
|
|
if (locationSearch.isPickingWaypoint) {
|
|
return 'Move map to set stop'.tr +
|
|
' ${locationSearch.pickingWaypointIndex + 1}'.tr;
|
|
}
|
|
if (locationSearch.passengerStartLocationFromMap) {
|
|
final rideLifecycle = Get.find<RideLifecycleController>();
|
|
return rideLifecycle.isAnotherOreder
|
|
? 'Now set the pickup point for the other person'.tr
|
|
: 'Move map to your pickup point'.tr;
|
|
}
|
|
if (locationSearch.startLocationFromMap) {
|
|
return 'Move map to set start location'.tr;
|
|
}
|
|
if (locationSearch.workLocationFromMap) {
|
|
return 'Move map to your work location'.tr;
|
|
}
|
|
if (locationSearch.homeLocationFromMap) {
|
|
return 'Move map to your home location'.tr;
|
|
}
|
|
return 'Move map to select destination'.tr;
|
|
}
|
|
|
|
String _getConfirmLabel(LocationSearchController locationSearch, BuildContext context) {
|
|
if (locationSearch.isPickingWaypoint) return 'Set as Stop'.tr;
|
|
if (locationSearch.passengerStartLocationFromMap) {
|
|
return 'Confirm Pickup Location'.tr;
|
|
}
|
|
if (locationSearch.workLocationFromMap) return 'Set as Work'.tr;
|
|
if (locationSearch.homeLocationFromMap) return 'Set as Home'.tr;
|
|
return 'Set Destination'.tr;
|
|
}
|
|
|
|
IconData _getModeIcon(LocationSearchController locationSearch) {
|
|
if (locationSearch.isPickingWaypoint) return Icons.add_location_alt_rounded;
|
|
if (locationSearch.passengerStartLocationFromMap) {
|
|
return Icons.person_pin_circle_rounded;
|
|
}
|
|
if (locationSearch.workLocationFromMap) return Icons.work_rounded;
|
|
if (locationSearch.homeLocationFromMap) return Icons.home_rounded;
|
|
return Icons.location_on_rounded;
|
|
}
|
|
|
|
Color _getModeColor(LocationSearchController locationSearch) {
|
|
if (locationSearch.isPickingWaypoint) return Colors.orange.shade600;
|
|
if (locationSearch.passengerStartLocationFromMap) return Colors.green.shade600;
|
|
if (locationSearch.workLocationFromMap) return Colors.blue.shade600;
|
|
if (locationSearch.homeLocationFromMap) return Colors.orange.shade600;
|
|
return AppColor.primaryColor;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
|
|
return GetBuilder<LocationSearchController>(
|
|
builder: (locationSearch) {
|
|
final modeColor = _getModeColor(locationSearch);
|
|
|
|
return Positioned(
|
|
bottom: Get.height * .035,
|
|
left: 16,
|
|
right: 16,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
|
|
decoration: BoxDecoration(
|
|
color: modeColor,
|
|
borderRadius: BorderRadius.circular(_D.radiusCard),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(_getModeIcon(locationSearch), color: Colors.white, size: 19),
|
|
const SizedBox(width: 14),
|
|
Expanded(
|
|
child: Text(
|
|
_getModeTitle(locationSearch, context),
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w700,
|
|
fontSize: 14),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: AppColor.secondaryColor,
|
|
borderRadius: BorderRadius.circular(_D.radiusCard),
|
|
boxShadow: _D.cardShadow,
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(20, 18, 20, 2),
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.gps_fixed_rounded, color: modeColor, size: 16),
|
|
const SizedBox(width: 14),
|
|
Expanded(
|
|
child: Text(
|
|
'${locationSearch.newMyLocation.latitude.toStringAsFixed(5)}, ${locationSearch.newMyLocation.longitude.toStringAsFixed(5)}',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Colors.grey.shade700,
|
|
fontWeight: FontWeight.w500),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 14, 16, 18),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
flex: 2,
|
|
child: OutlinedButton(
|
|
onPressed: () {
|
|
mapEngine.isPickerShown = false;
|
|
locationSearch.passengerStartLocationFromMap = false;
|
|
locationSearch.startLocationFromMap = false;
|
|
locationSearch.workLocationFromMap = false;
|
|
locationSearch.homeLocationFromMap = false;
|
|
locationSearch.isPickingWaypoint = false;
|
|
locationSearch.pickingWaypointIndex = -1;
|
|
if (!mapEngine.isMainBottomMenuMap) {
|
|
mapEngine.isMainBottomMenuMap = true;
|
|
mapEngine.mainBottomMenuMapHeight =
|
|
Get.height * .22;
|
|
}
|
|
mapEngine.update();
|
|
locationSearch.update();
|
|
},
|
|
child: Text('Cancel'.tr),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
flex: 3,
|
|
child: ElevatedButton(
|
|
onPressed: () => _onConfirmTap(mapEngine, locationSearch, context),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: modeColor),
|
|
child: Text(_getConfirmLabel(locationSearch, context),
|
|
style: const TextStyle(color: Colors.white)),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> _onConfirmTap(
|
|
MapEngineController mapEngine, LocationSearchController locationSearch, BuildContext context) async {
|
|
final rideLifecycle = Get.find<RideLifecycleController>();
|
|
Log.print(
|
|
'🔘 _onConfirmTap: isPickingWaypoint=${locationSearch.isPickingWaypoint}, newMyLocation=${locationSearch.newMyLocation}');
|
|
await Future.delayed(const Duration(milliseconds: 280));
|
|
final LatLng currentCameraPosition = LatLng(
|
|
locationSearch.newMyLocation.latitude, locationSearch.newMyLocation.longitude);
|
|
|
|
if (locationSearch.isPickingWaypoint && locationSearch.pickingWaypointIndex >= 0) {
|
|
locationSearch.setMenuWaypointFromMap(
|
|
locationSearch.pickingWaypointIndex, currentCameraPosition);
|
|
mySnackbarSuccess('Waypoint has been set successfully'.tr);
|
|
return;
|
|
}
|
|
|
|
mapEngine.clearPolyline();
|
|
rideLifecycle.data = [];
|
|
|
|
if (locationSearch.passengerStartLocationFromMap) {
|
|
final LatLng start = currentCameraPosition;
|
|
locationSearch.newStartPointLocation = start;
|
|
locationSearch.passengerStartLocationFromMap = false;
|
|
mapEngine.isPickerShown = false;
|
|
locationSearch.currentLocationToFormPlaces = false;
|
|
locationSearch.placesDestination = [];
|
|
locationSearch.clearPlacesStart();
|
|
locationSearch.clearPlacesDestination();
|
|
mapEngine.isMainBottomMenuMap = true;
|
|
mapEngine.mainBottomMenuMapHeight = Get.height * .22;
|
|
mapEngine.update();
|
|
locationSearch.update();
|
|
await rideLifecycle.getDirectionMap('${start.latitude},${start.longitude}',
|
|
'${locationSearch.myDestination.latitude},${locationSearch.myDestination.longitude}');
|
|
rideLifecycle.showBottomSheet1();
|
|
return;
|
|
}
|
|
|
|
if (locationSearch.startLocationFromMap) {
|
|
final LatLng start = currentCameraPosition;
|
|
locationSearch.newMyLocation = start;
|
|
locationSearch.newStartPointLocation = start;
|
|
locationSearch.hintTextStartPoint =
|
|
'${start.latitude.toStringAsFixed(4)} , ${start.longitude.toStringAsFixed(4)}';
|
|
locationSearch.startLocationFromMap = false;
|
|
mapEngine.isPickerShown = false;
|
|
locationSearch.update();
|
|
mapEngine.update();
|
|
return;
|
|
}
|
|
|
|
if (locationSearch.workLocationFromMap) {
|
|
box.write(BoxName.addWork,
|
|
'${currentCameraPosition.latitude.toStringAsFixed(4)} , ${currentCameraPosition.longitude.toStringAsFixed(4)}');
|
|
locationSearch.hintTextDestinationPoint = 'To Work'.tr;
|
|
locationSearch.workLocationFromMap = false;
|
|
mapEngine.isPickerShown = false;
|
|
locationSearch.update();
|
|
mapEngine.update();
|
|
mySnackbarSuccess('Work Saved'.tr);
|
|
return;
|
|
}
|
|
|
|
if (locationSearch.homeLocationFromMap) {
|
|
box.write(BoxName.addHome,
|
|
'${currentCameraPosition.latitude.toStringAsFixed(4)} , ${currentCameraPosition.longitude.toStringAsFixed(4)}');
|
|
locationSearch.hintTextDestinationPoint = 'To Home'.tr;
|
|
locationSearch.homeLocationFromMap = false;
|
|
mapEngine.isPickerShown = false;
|
|
locationSearch.update();
|
|
mapEngine.update();
|
|
mySnackbarSuccess('Home Saved'.tr);
|
|
return;
|
|
}
|
|
|
|
locationSearch.myDestination = currentCameraPosition;
|
|
locationSearch.hintTextDestinationPoint =
|
|
'${currentCameraPosition.latitude.toStringAsFixed(4)} , ${currentCameraPosition.longitude.toStringAsFixed(4)}';
|
|
locationSearch.placesDestination = [];
|
|
locationSearch.placeDestinationController.clear();
|
|
locationSearch.passengerStartLocationFromMap = true;
|
|
mapEngine.isPickerShown = true;
|
|
locationSearch.update();
|
|
mapEngine.update();
|
|
|
|
try {
|
|
if (rideLifecycle.isAnotherOreder) {
|
|
await mapEngine.mapController?.animateCamera(CameraUpdate.newLatLng(
|
|
LatLng(locationSearch.newStartPointLocation.latitude,
|
|
locationSearch.newStartPointLocation.longitude)));
|
|
} else {
|
|
await mapEngine.mapController?.animateCamera(CameraUpdate.newLatLng(
|
|
LatLng(locationSearch.passengerLocation.latitude,
|
|
locationSearch.passengerLocation.longitude)));
|
|
}
|
|
} catch (e) {
|
|
Log.print("Error occurred: $e");
|
|
}
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// HELPER WIDGETS
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
class _TimelineRow extends StatelessWidget {
|
|
final IconData icon;
|
|
final Color iconColor;
|
|
final Color bgColor;
|
|
final String label;
|
|
const _TimelineRow(
|
|
{required this.icon,
|
|
required this.iconColor,
|
|
required this.bgColor,
|
|
required this.label});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11),
|
|
decoration: BoxDecoration(
|
|
color: bgColor.withValues(alpha: 0.06),
|
|
borderRadius: BorderRadius.circular(_D.radiusInner)),
|
|
child: Row(
|
|
children: [
|
|
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 LocationSearchController locationSearch;
|
|
final int index;
|
|
const _RecentPlaceChip({required this.locationSearch, required this.index});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final place = locationSearch.recentPlaces[index];
|
|
final rideLifecycle = Get.find<RideLifecycleController>();
|
|
return Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
onTap: () {
|
|
MyDialog().getDialog(
|
|
'Are you want to go this site'.tr,
|
|
' ',
|
|
() async {
|
|
await locationSearch.getLocation();
|
|
await rideLifecycle.getDirectionMap(
|
|
'${locationSearch.passengerLocation.latitude},${locationSearch.passengerLocation.longitude}',
|
|
'${place['latitude']},${place['longitude']}');
|
|
rideLifecycle.showBottomSheet1();
|
|
},
|
|
);
|
|
},
|
|
borderRadius: BorderRadius.circular(_D.radiusChip),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 7),
|
|
decoration: BoxDecoration(
|
|
color: AppColor.primaryColor.withValues(alpha: 0.08),
|
|
borderRadius: BorderRadius.circular(_D.radiusChip),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.history_rounded,
|
|
size: 14, color: AppColor.primaryColor.withValues(alpha: 0.7)),
|
|
const SizedBox(width: 7),
|
|
Text(place['name'] ?? '',
|
|
style: TextStyle(
|
|
fontSize: 12.5,
|
|
color: AppColor.primaryColor.withValues(alpha: 0.9),
|
|
fontWeight: FontWeight.w600)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _WhatsAppLinkButton extends StatelessWidget {
|
|
final LocationSearchController locationSearch;
|
|
const _WhatsAppLinkButton({required this.locationSearch});
|
|
|
|
@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: locationSearch.sosFormKey,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
MyTextForm(
|
|
controller: locationSearch.whatsAppLocationText,
|
|
label: 'Location Link'.tr,
|
|
type: TextInputType.url,
|
|
hint: 'https://maps.app.goo.gl/...'),
|
|
const SizedBox(height: 16),
|
|
MyElevatedButton(
|
|
title: 'Go to this location'.tr,
|
|
onPressed: () => locationSearch.goToWhatappLocation()),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
borderRadius: BorderRadius.circular(_D.radiusInner),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
decoration: BoxDecoration(
|
|
color: Colors.green.shade50,
|
|
borderRadius: BorderRadius.circular(_D.radiusInner)),
|
|
child: Row(
|
|
children: [
|
|
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))),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _OrderTypeButton extends StatelessWidget {
|
|
final MapEngineController mapEngine;
|
|
const _OrderTypeButton({required this.mapEngine});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final rideLifecycle = Get.find<RideLifecycleController>();
|
|
final bool isOther = mapEngine.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),
|
|
actions: [
|
|
CupertinoActionSheetAction(
|
|
child: Text('I want to order for myself'.tr),
|
|
onPressed: () {
|
|
mapEngine.changeisAnotherOreder(false);
|
|
rideLifecycle.isAnotherOreder = false;
|
|
Navigator.pop(ctx);
|
|
},
|
|
),
|
|
CupertinoActionSheetAction(
|
|
child: Text('I want to order for someone else'.tr),
|
|
onPressed: () {
|
|
mapEngine.changeisAnotherOreder(true);
|
|
rideLifecycle.isAnotherOreder = true;
|
|
Navigator.pop(ctx);
|
|
},
|
|
),
|
|
],
|
|
cancelButton: CupertinoActionSheetAction(
|
|
isDefaultAction: true,
|
|
onPressed: () => Navigator.pop(ctx),
|
|
child: Text('Cancel'.tr)),
|
|
),
|
|
);
|
|
},
|
|
borderRadius: BorderRadius.circular(_D.radiusInner),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
decoration: BoxDecoration(
|
|
color: accent.withValues(alpha: 0.08),
|
|
borderRadius: BorderRadius.circular(_D.radiusInner)),
|
|
child: Row(
|
|
children: [
|
|
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))),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class FaviouratePlacesDialog extends StatelessWidget {
|
|
const FaviouratePlacesDialog({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GetBuilder<LocationSearchController>(
|
|
builder: (locationSearch) {
|
|
final rideLifecycle = Get.find<RideLifecycleController>();
|
|
return InkWell(
|
|
borderRadius: BorderRadius.circular(14),
|
|
onTap: () async {
|
|
final List favoritePlaces =
|
|
await sql.getAllData(TableName.placesFavorite);
|
|
Get.defaultDialog(
|
|
title: 'Favorite Places'.tr,
|
|
content: SizedBox(
|
|
width: Get.width * .85,
|
|
height: 300,
|
|
child: favoritePlaces.isEmpty
|
|
? Center(child: Text('No favorite places yet!'.tr))
|
|
: ListView.separated(
|
|
itemCount: favoritePlaces.length,
|
|
separatorBuilder: (_, __) =>
|
|
Divider(height: 1, color: Colors.grey.shade100),
|
|
itemBuilder: (context, index) => ListTile(
|
|
leading: const Icon(Icons.star,
|
|
color: Colors.amber, size: 19),
|
|
title: Text(favoritePlaces[index]['name']),
|
|
trailing: IconButton(
|
|
icon: const Icon(Icons.delete_outline,
|
|
color: Colors.redAccent),
|
|
onPressed: () async {
|
|
await sql.deleteData(TableName.placesFavorite,
|
|
favoritePlaces[index]['id']);
|
|
Get.back();
|
|
},
|
|
),
|
|
onTap: () async {
|
|
Get.back();
|
|
await locationSearch.getLocation();
|
|
await rideLifecycle.getDirectionMap(
|
|
'${locationSearch.passengerLocation.latitude},${locationSearch.passengerLocation.longitude}',
|
|
'${favoritePlaces[index]['latitude']},${favoritePlaces[index]['longitude']}');
|
|
rideLifecycle.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: [
|
|
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)),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|