Update: 2026-07-10 16:09:37
This commit is contained in:
@@ -0,0 +1,722 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../constant/box_name.dart';
|
||||
import '../../../controller/home/map/location_search_controller.dart';
|
||||
import '../../../controller/home/map/map_engine_controller.dart';
|
||||
import '../../../controller/home/map/ride_lifecycle_controller.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../widgets/elevated_btn.dart';
|
||||
import '../../widgets/my_textField.dart';
|
||||
import 'rp_actions.dart';
|
||||
import 'rp_favorites.dart';
|
||||
import 'rp_location_field.dart';
|
||||
import 'rp_map_pick_overlay.dart';
|
||||
import 'rp_theme.dart';
|
||||
|
||||
/// The route-planning surface anchored to the bottom of the map.
|
||||
///
|
||||
/// • While the user drags the map to place a point → [RpMapPickOverlay].
|
||||
/// • Otherwise → a collapsed "Where to?" card or the full planner.
|
||||
///
|
||||
/// Drop-in replacement for the old `MainBottomMenuMap`. Uses only the
|
||||
/// existing controller APIs; nothing in the controllers changed.
|
||||
class RoutePlannerSheet extends StatelessWidget {
|
||||
const RoutePlannerSheet({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<MapEngineController>(
|
||||
builder: (mapEngine) {
|
||||
if (mapEngine.isPickerShown) return const RpMapPickOverlay();
|
||||
|
||||
return Positioned(
|
||||
left: 14,
|
||||
right: 14,
|
||||
bottom: Get.height * .03,
|
||||
child: AnimatedContainer(
|
||||
duration: RP.medium,
|
||||
curve: RP.ease,
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: mapEngine.isMainBottomMenuMap
|
||||
? Get.height * 0.42
|
||||
: Get.height * 0.72,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: RP.sheet,
|
||||
borderRadius: BorderRadius.circular(RP.rSheet),
|
||||
boxShadow: RP.sheetShadow,
|
||||
border: Border.all(color: RP.divider),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: mapEngine.isMainBottomMenuMap
|
||||
? const _Collapsed()
|
||||
: const _Expanded(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// COLLAPSED
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
class _Collapsed extends StatelessWidget {
|
||||
const _Collapsed();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final String firstName =
|
||||
(box.read(BoxName.name)?.toString().split(' ').first ?? '').trim();
|
||||
final mapEngine = Get.find<MapEngineController>();
|
||||
final rideLifecycle = Get.find<RideLifecycleController>();
|
||||
|
||||
return GetBuilder<LocationSearchController>(
|
||||
builder: (locationSearch) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
RP.grabber(),
|
||||
const SizedBox(height: 14),
|
||||
InkWell(
|
||||
onTap: mapEngine.changeMainBottomMenuMap,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: RP.brand,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: RP.glow(RP.brand),
|
||||
),
|
||||
child: const Icon(Icons.search_rounded,
|
||||
color: Colors.white, size: 24),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(children: [
|
||||
TextSpan(
|
||||
text: '${'Where to'.tr} ',
|
||||
style: TextStyle(
|
||||
fontSize: 16.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: RP.textStrong),
|
||||
),
|
||||
if (firstName.isNotEmpty)
|
||||
TextSpan(
|
||||
text: firstName,
|
||||
style: TextStyle(
|
||||
fontSize: 16.5,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: RP.brand),
|
||||
),
|
||||
const TextSpan(text: '؟'),
|
||||
]),
|
||||
),
|
||||
if (!rideLifecycle.noCarString) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text('Tap to search your destination'.tr,
|
||||
style:
|
||||
TextStyle(fontSize: 12, color: RP.textMuted)),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(Icons.keyboard_arrow_up_rounded,
|
||||
color: RP.textMuted, size: 26),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
RpRecentsRow(locationSearch: locationSearch),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// EXPANDED
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
class _Expanded extends StatelessWidget {
|
||||
const _Expanded();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final mapEngine = Get.find<MapEngineController>();
|
||||
|
||||
return GetBuilder<LocationSearchController>(
|
||||
builder: (s) {
|
||||
final rideLifecycle = Get.find<RideLifecycleController>();
|
||||
final bool isOther = rideLifecycle.isAnotherOreder;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
Center(child: RP.grabber()),
|
||||
// Header
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 14, 14, 6),
|
||||
child: Row(
|
||||
children: [
|
||||
Text('Plan Your Route'.tr, style: RP.title),
|
||||
const Spacer(),
|
||||
_CircleIconButton(
|
||||
icon: Icons.keyboard_arrow_down_rounded,
|
||||
onTap: mapEngine.changeMainBottomMenuMap,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Order-type toggle
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 6, 16, 4),
|
||||
child: _OrderTypeToggle(isOther: isOther),
|
||||
),
|
||||
|
||||
// Route timeline
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 10, 16, 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_originField(s, isOther),
|
||||
const SizedBox(height: 10),
|
||||
...List.generate(
|
||||
s.activeMenuWaypointCount,
|
||||
(i) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: _StopRow(index: i, s: s),
|
||||
),
|
||||
),
|
||||
if (s.activeMenuWaypointCount < 2) ...[
|
||||
_AddStopButton(onTap: RpActions.addStop),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
_destinationField(s),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 6),
|
||||
_Divider(),
|
||||
|
||||
// Quick access
|
||||
_SectionHeader(
|
||||
title: 'Quick Access'.tr,
|
||||
trailing: const RpFavoritesButton(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: _homeWorkButton(isWork: false)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: _homeWorkButton(isWork: true)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
RpRecentsRow(locationSearch: s),
|
||||
|
||||
const SizedBox(height: 6),
|
||||
_Divider(),
|
||||
|
||||
// Advanced
|
||||
_SectionHeader(title: 'Advanced Tools'.tr),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: _WhatsAppLinkButton(locationSearch: s),
|
||||
),
|
||||
const SizedBox(height: 22),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// ── Origin ──────────────────────────────────────────────────────────────
|
||||
Widget _originField(LocationSearchController s, bool isOther) {
|
||||
if (isOther) {
|
||||
return RpLocationField(
|
||||
accent: RP.origin,
|
||||
icon: Icons.person_pin_circle_rounded,
|
||||
controller: s.placeStartController,
|
||||
hintText: 'Search for a starting point'.tr,
|
||||
results: s.placesStart,
|
||||
onQuery: (_) => s.getPlacesStart(),
|
||||
onEmpty: s.clearPlacesStart,
|
||||
onPickOnMap: RpActions.pickOtherPickupOnMap,
|
||||
onResultTap: (i) => RpActions.selectStart(s.placesStart[i]),
|
||||
);
|
||||
}
|
||||
// Regular order: origin = current location, changeable on the map.
|
||||
return RpLocationField(
|
||||
accent: RP.origin,
|
||||
icon: Icons.my_location_rounded,
|
||||
controller: s.placeStartController,
|
||||
hintText: s.currentLocationString,
|
||||
results: const [],
|
||||
searchable: false,
|
||||
onQuery: (_) {},
|
||||
onEmpty: () {},
|
||||
onPickOnMap: RpActions.pickMyStartOnMap,
|
||||
onResultTap: (_) {},
|
||||
);
|
||||
}
|
||||
|
||||
// ── Destination ─────────────────────────────────────────────────────────
|
||||
Widget _destinationField(LocationSearchController s) {
|
||||
final mapEngine = Get.find<MapEngineController>();
|
||||
return RpLocationField(
|
||||
accent: RP.destination,
|
||||
icon: Icons.flag_rounded,
|
||||
controller: s.placeDestinationController,
|
||||
hintText: s.hintTextDestinationPoint,
|
||||
results: s.placesDestination,
|
||||
onQuery: (_) {
|
||||
s.getPlaces();
|
||||
mapEngine.changeHeightPlaces();
|
||||
},
|
||||
onEmpty: () {
|
||||
s.clearPlacesDestination();
|
||||
mapEngine.changeHeightPlaces();
|
||||
},
|
||||
onPickOnMap: RpActions.pickDestinationOnMap,
|
||||
onResultTap: (i) => RpActions.selectDestination(i, s.placesDestination[i]),
|
||||
onResultFavorite: (i) {
|
||||
final res = s.placesDestination[i];
|
||||
RpActions.addFavorite(
|
||||
Get.context!,
|
||||
res['latitude'],
|
||||
res['longitude'],
|
||||
(res['name_ar'] ?? res['name'] ?? 'Unknown Place').toString(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _homeWorkButton({required bool isWork}) {
|
||||
final String saved = isWork
|
||||
? (box.read(BoxName.addWork)?.toString() ?? 'addWork')
|
||||
: (box.read(BoxName.addHome)?.toString() ?? 'addHome');
|
||||
final bool isSet = isWork ? saved != 'addWork' : saved != 'addHome';
|
||||
final Color accent =
|
||||
isWork ? const Color(0xFF2563EB) : const Color(0xFFF59E0B);
|
||||
|
||||
return _QuickTile(
|
||||
icon: isWork ? Icons.work_rounded : Icons.home_rounded,
|
||||
accent: accent,
|
||||
label: isWork
|
||||
? (isSet ? 'To Work'.tr : 'Add Work'.tr)
|
||||
: (isSet ? 'To Home'.tr : 'Add Home'.tr),
|
||||
onTap: () {
|
||||
if (isSet) {
|
||||
RpActions.goToSaved(
|
||||
isWork ? BoxName.addWork : BoxName.addHome,
|
||||
isWork ? 'To Work' : 'To Home');
|
||||
} else {
|
||||
RpActions.pickHomeOrWork(isWork: isWork);
|
||||
}
|
||||
},
|
||||
onLongPress: () => RpActions.changeHomeOrWork(isWork: isWork),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Stop row ────────────────────────────────────────────────────────────────
|
||||
class _StopRow extends StatelessWidget {
|
||||
final int index;
|
||||
final LocationSearchController s;
|
||||
const _StopRow({required this.index, required this.s});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color accent = RP.stopColor(index);
|
||||
final bool isSet = s.menuWaypoints[index] != null;
|
||||
final String name =
|
||||
isSet ? s.menuWaypointNames[index] : '${'Stop'.tr} ${index + 1}';
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: RP.fieldFill,
|
||||
borderRadius: BorderRadius.circular(RP.rField),
|
||||
border: Border.all(
|
||||
color: isSet ? accent.withValues(alpha: 0.45) : Colors.transparent,
|
||||
width: 1.4,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 34,
|
||||
height: 34,
|
||||
decoration: BoxDecoration(
|
||||
color: accent,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: RP.glow(accent),
|
||||
),
|
||||
child: Center(
|
||||
child: Text('${index + 1}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w800)),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => RpActions.pickStopOnMap(index),
|
||||
child: Text(
|
||||
name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: isSet
|
||||
? RP.fieldValue
|
||||
: RP.fieldHint,
|
||||
),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => RpActions.removeStop(index),
|
||||
borderRadius: BorderRadius.circular(RP.rChip),
|
||||
child: Container(
|
||||
width: 34,
|
||||
height: 34,
|
||||
alignment: Alignment.center,
|
||||
child: Icon(Icons.delete_outline_rounded,
|
||||
color: RP.destination, size: 19),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => RpActions.pickStopOnMap(index),
|
||||
borderRadius: BorderRadius.circular(RP.rChip),
|
||||
child: Container(
|
||||
width: 34,
|
||||
height: 34,
|
||||
decoration: BoxDecoration(
|
||||
color: accent.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(RP.rChip),
|
||||
),
|
||||
child: Icon(Icons.map_rounded, color: accent, size: 19),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AddStopButton extends StatelessWidget {
|
||||
final VoidCallback onTap;
|
||||
const _AddStopButton({required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(RP.rField),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 13),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(RP.rField),
|
||||
border: Border.all(
|
||||
color: RP.stop1.withValues(alpha: 0.5),
|
||||
width: 1.4,
|
||||
style: BorderStyle.solid),
|
||||
color: RP.stop1.withValues(alpha: 0.06),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.add_location_alt_outlined,
|
||||
color: RP.stop1, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
Text('Add a Stop'.tr,
|
||||
style: TextStyle(
|
||||
color: RP.stop1,
|
||||
fontSize: 13.5,
|
||||
fontWeight: FontWeight.w700)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Order-type toggle ────────────────────────────────────────────────────────
|
||||
class _OrderTypeToggle extends StatelessWidget {
|
||||
final bool isOther;
|
||||
const _OrderTypeToggle({required this.isOther});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final mapEngine = Get.find<MapEngineController>();
|
||||
final rideLifecycle = Get.find<RideLifecycleController>();
|
||||
|
||||
void select(bool other) {
|
||||
if (mapEngine.isAnotherOreder == other) return;
|
||||
mapEngine.changeisAnotherOreder(other);
|
||||
rideLifecycle.isAnotherOreder = other;
|
||||
// The expanded planner rebuilds on LocationSearchController updates, so
|
||||
// refresh it here to swap the origin field (self ⇄ other) instantly.
|
||||
Get.find<LocationSearchController>().update();
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: RP.chipFill,
|
||||
borderRadius: BorderRadius.circular(RP.rField),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_seg(
|
||||
selected: !isOther,
|
||||
icon: Icons.person_rounded,
|
||||
label: 'Order for myself'.tr,
|
||||
onTap: () => select(false),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
_seg(
|
||||
selected: isOther,
|
||||
icon: Icons.group_rounded,
|
||||
label: 'Order for someone else'.tr,
|
||||
onTap: () => select(true),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _seg({
|
||||
required bool selected,
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return Expanded(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(RP.rChip),
|
||||
child: AnimatedContainer(
|
||||
duration: RP.fast,
|
||||
padding: const EdgeInsets.symmetric(vertical: 11),
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? const Color(0xFF4F46E5) : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(RP.rChip),
|
||||
boxShadow: selected
|
||||
? RP.glow(const Color(0xFF4F46E5), intensity: 0.35)
|
||||
: null,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon,
|
||||
size: 16,
|
||||
color: selected ? Colors.white : RP.textMuted),
|
||||
const SizedBox(width: 7),
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 12.5,
|
||||
fontWeight:
|
||||
selected ? FontWeight.w700 : FontWeight.w600,
|
||||
color: selected ? Colors.white : RP.textBody,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Quick tile (home/work) ───────────────────────────────────────────────────
|
||||
class _QuickTile extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final Color accent;
|
||||
final String label;
|
||||
final VoidCallback onTap;
|
||||
final VoidCallback? onLongPress;
|
||||
const _QuickTile({
|
||||
required this.icon,
|
||||
required this.accent,
|
||||
required this.label,
|
||||
required this.onTap,
|
||||
this.onLongPress,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
borderRadius: BorderRadius.circular(RP.rField),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 13),
|
||||
decoration: BoxDecoration(
|
||||
color: accent.withValues(alpha: 0.10),
|
||||
borderRadius: BorderRadius.circular(RP.rField),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, color: accent, size: 20),
|
||||
const SizedBox(width: 10),
|
||||
Flexible(
|
||||
child: Text(label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: accent,
|
||||
fontSize: 13.5,
|
||||
fontWeight: FontWeight.w700)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── WhatsApp link ─────────────────────────────────────────────────────────────
|
||||
class _WhatsAppLinkButton extends StatelessWidget {
|
||||
final LocationSearchController locationSearch;
|
||||
const _WhatsAppLinkButton({required this.locationSearch});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(RP.rField),
|
||||
onTap: () {
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
backgroundColor: RP.sheetElevated,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(RP.rCard)),
|
||||
title: Text('WhatsApp Location Extractor'.tr, style: RP.title),
|
||||
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()),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF25D366).withValues(alpha: 0.10),
|
||||
borderRadius: BorderRadius.circular(RP.rField),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.link_rounded,
|
||||
color: Color(0xFF1FA855), size: 20),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text('Paste WhatsApp location link'.tr,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF1FA855),
|
||||
fontSize: 13.5,
|
||||
fontWeight: FontWeight.w700)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Small shared bits ─────────────────────────────────────────────────────────
|
||||
class _SectionHeader extends StatelessWidget {
|
||||
final String title;
|
||||
final Widget? trailing;
|
||||
const _SectionHeader({required this.title, this.trailing});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 4, 12, 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(title.toUpperCase(), style: RP.sectionLabel),
|
||||
if (trailing != null) trailing!,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Divider extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 1,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
color: RP.divider,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CircleIconButton extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final VoidCallback onTap;
|
||||
const _CircleIconButton({required this.icon, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(RP.rPill),
|
||||
child: Container(
|
||||
width: 38,
|
||||
height: 38,
|
||||
decoration: BoxDecoration(color: RP.fieldFill, shape: BoxShape.circle),
|
||||
child: Icon(icon, size: 24, color: RP.textBody),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user