import 'dart:developer' as dev; import 'dart:math' as math; import 'package:wanisclient/config/custom_text.dart'; import 'package:wanisclient/config/enum/ride_enum.dart'; import 'package:wanisclient/config/enum/ride_type.dart'; import 'package:wanisclient/providers/public_provider.dart'; import 'package:wanisclient/utils/colors.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_svg/svg.dart'; import 'package:geolocator/geolocator.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; import 'package:sidebarx/sidebarx.dart'; import 'package:sliding_up_panel/sliding_up_panel.dart'; import '../config/AppSnackbar.dart'; import '../config/enum/fill_data_ride.dart'; import '../config/language/language_enum.dart'; import '../models/userAddress_model.dart'; import '../providers/language_provider.dart'; import '../providers/ride_providers.dart'; import '../providers/utils_singleton.dart'; import '../widgets/LocationCard.dart'; import '../widgets/address_list.dart'; import '../widgets/custom_button.dart'; import '../widgets/custom_location_input.dart'; class TripsScreen extends StatefulWidget { final double andoridbuttonhight; final SidebarXController controllerSideBar; const TripsScreen( {Key? key, required this.andoridbuttonhight, required this.controllerSideBar}) : super(key: key); @override _TripsScreenState createState() => _TripsScreenState(); } class _TripsScreenState extends State { OverlayEntry? _backButtonOverlay; final List _stopControllers = []; final int _maxStops = 3; fill_data_ride stateinput = fill_data_ride.CHOOSE_START_LOCATION; // ―― route fields ―――――――――――――――――――――――――――――――――――――――― final TextEditingController pickupmlocation = TextEditingController(); LatLng? pickuplocationcordonation; final TextEditingController destinationlocation = TextEditingController(); LatLng? destinationlocationcordonation; List stopscordonation = []; late final PublicProvider _publicProvider; late final RidesProvider _rideprovider; bool _initialized = false; bool _isFetchingLocation = false; bool permissionsGranted = false; @override void initState() { super.initState(); _publicProvider = context.read(); _rideprovider = context.read(); // Post-frame callback to avoid build-phase mutation WidgetsBinding.instance.addPostFrameCallback((_) => _showBackButtonOverlay()); WidgetsBinding.instance.addPostFrameCallback((_) { _publicProvider.clear(); setState(() { stateinput = fill_data_ride.CHOOSE_START_LOCATION; }); _getCurrentLocation(pickupmlocation, _onPickupLocationSelected); setState(() { stateinput = fill_data_ride.CHOOSE_END_LOCATION; }); }); //_addStop(); // create first empty stop input //_onLoadStopLocationExist(); //_initializeLocation(); } @override void didChangeDependencies() { super.didChangeDependencies(); if (!_initialized) { _addStop(); // create first empty stop input _onLoadStopLocationExist(); _initializeLocation(); _initialized = true; // Ensure it runs only once } //context.read().clear(); } // ────────── helpers ──────────────────────────────────────────────────── void _addStop() { if (_stopControllers.length < _maxStops) { setState(() => _stopControllers.add(TextEditingController())); } else { AppSnackbar.showInfo( "Info", 'You reached the maximum stops'); } } void _removeStop(int index) { if (index == 0) return; // Ensure the provider is updated before changing the UI state context.read().removeStoplocationlocation(index); setState(() { // Remove from the local coordinates list if the index is valid if (index < stopscordonation.length) { stopscordonation.removeAt(index); } // Dispose the controller and remove it from the list _stopControllers[index].dispose(); _stopControllers.removeAt(index); }); } @override void dispose() { _backButtonOverlay?.remove(); // <-- ADD THIS LINE to clean up the button super.dispose(); } // ───── location callbacks ───────────────────────────────────────────── void _onPickupLocationSelected(LatLng pos, String name) { _rideprovider.updateStartlocation(pos); _rideprovider.updateStartlocationName(name); setState(() { pickuplocationcordonation = pos; pickupmlocation.text = name; }); } void _onPickupLocationClear() { _rideprovider.updateStartlocation(null); _rideprovider.updateStartlocationName(null); setState(() { pickuplocationcordonation = null; pickupmlocation.clear(); }); } void _onDestinationLocationSelected(LatLng pos, String name) { final rides = context.read(); rides.updateDestinationlocation(pos); rides.updatedestinationlocationName(name); setState(() { destinationlocationcordonation = pos; destinationlocation.text = name; }); } void _onDestinationLocationClear() { _rideprovider.updateDestinationlocation(null); _rideprovider.updatedestinationlocationName(null); setState(() { destinationlocationcordonation = null; destinationlocation.clear(); }); } void _initializeLocation() { // Bail out quickly when there’s literally nothing to copy. if (_rideprovider.destinationlocationname == null && _rideprovider.destinationlocation == null && _rideprovider.startlocationname == null && _rideprovider.startlocation == null) return; setState(() { // keep whatever was already there if the provider has nulls destinationlocation.text = _rideprovider.destinationlocationname ?? destinationlocation.text; destinationlocationcordonation = _rideprovider.destinationlocation ?? destinationlocationcordonation; pickupmlocation.text = _rideprovider.startlocationname ?? pickupmlocation.text; pickuplocationcordonation = _rideprovider.startlocation ?? pickuplocationcordonation; }); } void _onStopsLocationSelected(LatLng pos, String name, int index) { _rideprovider.addStoplocationlocation(pos); setState(() { if (index < stopscordonation.length) { stopscordonation[index] = pos; } else { stopscordonation.add(pos); } _stopControllers[index].text = name; }); } void _onStopsLocationClear(int index) { // This function now ONLY clears the data, it does not remove the UI element context.read().removeStoplocationlocation(index); _removeStop(index); setState(() { if (index < stopscordonation.length) { stopscordonation.removeAt(index); } // Correctly clear the controller's text _stopControllers[index].clear(); }); } void _onLoadStopLocationExist() { final stops = context.read().stoplocationlocation; if (stops != null) { for (var stop in stops) { // Parse the LatLng from the stored string final parts = stop.stopLocation.split(','); if (parts.length == 2) { final lat = double.tryParse(parts[0]); final lng = double.tryParse(parts[1]); if (lat != null && lng != null) { final pos = LatLng(lat, lng); stopscordonation.add(pos); // Add a controller and set its value to the raw string or a name if available final controller = TextEditingController( text: stop.stopLocation); // Or use a resolved name _stopControllers.add(controller); } } } // You must call setState to update the UI if this happens after build setState(() {}); } } void movenext() { if (pickupmlocation.text.isEmpty || _rideprovider.startlocation == null) { AppSnackbar.showError("Error", "Pickup location is required!"); return; } if (destinationlocation.text.isEmpty || _rideprovider.destinationlocation == null) { AppSnackbar.showError("Error", "Destination location is required!"); return; } _proceedIfValid(); // switch (stateinput) { // case (fill_data_ride.CHOOSE_START_LOCATION): // if (pickupmlocation.text.isEmpty || // _rideprovider.startlocation == null) { // AppSnackbar.showError("Error", "Pickup location is required!"); // return; // } // setState(() { // stateinput = fill_data_ride.CHOOSE_END_LOCATION; // }); // case (fill_data_ride.CHOOSE_END_LOCATION): // if (destinationlocation.text.isEmpty || // _rideprovider.destinationlocation == null) { // AppSnackbar.showError("Error", "Destination location is required!"); // return; // } // setState(() { // stateinput = fill_data_ride.CHOOSE_STOPS; // }); // default: // _proceedIfValid(); // } } // ───── validation & navigation ──────────────────────────────────────── bool _locationsAreValid() => pickupmlocation.text.isNotEmpty && destinationlocation.text.isNotEmpty; Future _proceedIfValid() async { if (!_locationsAreValid()) { AppSnackbar.showInfo("Info", 'Please complete pickup & drop locations'); return; } await _rideprovider.fetchRouteInfo( ridesprovidersingliton.startlocation!, ridesprovidersingliton.destinationlocation!, ); await _publicProvider.fetchRideOptions( ridesprovidersingliton.couponcode ?? "", ridesprovidersingliton.ridestartdatetime != null ? ridesprovidersingliton.ridestartdatetime!.toIso8601String() : "", "${ridesprovidersingliton.startlocation?.longitude},${ridesprovidersingliton.startlocation?.latitude}", ridesprovidersingliton.geohash ?? ""); switch (_rideprovider.oncreationridetype) { case RideType.INSTANT: _rideprovider.updateCurrentstate(RideEnum.choose_ride); break; case RideType.SCHEDULE: _rideprovider.updateCurrentstate(RideEnum.choose_ride_schedule); break; default: break; } } Future _getCurrentLocation( TextEditingController controller, void Function(LatLng coordinates, String locationName) onLocationSelected) async { try { setState(() => _isFetchingLocation = true); Position position = await Geolocator.getCurrentPosition( locationSettings: const LocationSettings( accuracy: LocationAccuracy.high, distanceFilter: 10, ), ); if (!mounted) return; final LatLng newLocation = LatLng(position.latitude, position.longitude); dev.log("newLocation iiiiiiiiiii $newLocation"); final address = await _rideprovider.getPlaceNameByGoogle(newLocation.latitude, newLocation.longitude); dev.log("********************************* $address"); setState(() { controller.text = address; onLocationSelected(LatLng(newLocation.latitude, newLocation.longitude), address); permissionsGranted = true; _isFetchingLocation = false; }); } catch (e) { if (!mounted) return; setState(() { _isFetchingLocation = false; permissionsGranted = false; }); print("Error fetching location: $e"); } finally {} } // ───── UI ───────────────────────────────────────────────────────────── @override Widget build(BuildContext context) { final languageProvider = context.watch(); bool isArabic = languageProvider.currentLangCode == Language.ar; bool isEnglish = languageProvider.currentLangCode == Language.en; final rideProvider = context.watch(); return choosestartlocation(context, rideProvider,isArabic,isEnglish); // switch (stateinput) { // case fill_data_ride.CHOOSE_END_LOCATION: // return chooseendlocation(context, rideProvider,isArabic,isEnglish); // case fill_data_ride.CHOOSE_STOPS: // return choosestops(context, rideProvider,isArabic,isEnglish); // default: // return choosestartlocation(context, rideProvider,isArabic,isEnglish); // } } void _goback() { print("Go back ..."); switch (stateinput) { case (fill_data_ride.CHOOSE_STOPS): setState(() { stateinput = fill_data_ride.CHOOSE_END_LOCATION; }); case (fill_data_ride.CHOOSE_END_LOCATION): setState(() { stateinput = fill_data_ride.CHOOSE_START_LOCATION; }); default: _rideprovider.updateCurrentstate(RideEnum.idle); _rideprovider.updateCoupon(""); } } // Add this new function anywhere inside your _TripsScreenState class void _showBackButtonOverlay() { // First, remove any existing overlay to be safe _backButtonOverlay?.remove(); _backButtonOverlay = OverlayEntry( builder: (context) { // Get the top safe area padding final topPadding = MediaQuery.of(context).viewPadding.top; final languageProvider = context.watch(); bool isArabic = languageProvider.currentLangCode == Language.ar; // The button is now built here, inside the Overlay return Positioned( top: topPadding + 16.0, left: isArabic ? null : 16.0, right: isArabic ? 16.0 : null, child: Transform.rotate( angle: isArabic ? math.pi : 0, child: FloatingActionButton.small( onPressed: _goback, backgroundColor: AppNewColors.backgroundPurpleColor2, elevation: 4.0, // Give it a slight shadow to pop shape: const CircleBorder(), child:Icon(isArabic? Icons.arrow_forward:Icons.arrow_back, color: Colors.black, size: 20,), ), ), ); }, ); // Insert the new overlay entry into the screen's overlay Overlay.of(context).insert(_backButtonOverlay!); } Widget _buildNextButton(String title) { return Positioned( left: 0, right: 0, bottom: 0, child: Container( padding: EdgeInsets.fromLTRB(20, 12, 20, widget.andoridbuttonhight+ 16), decoration: BoxDecoration( color: Colors.white, border: Border(top: BorderSide(color: Colors.grey.shade200, width: 1.0)), ), child: CustomButton( onClick: movenext, width: double.infinity, fontSize: 18, height: 45, borderRadius: 12, txtColor: Colors.white, title: title, backgroundColor: AppNewColors.primaryBtnColor, borderColor: AppNewColors.primaryBtnColor, loadercolor: Colors.white, isloading: _publicProvider.isLoadingRideOption, ), ), ); } Widget choosestartlocation(BuildContext context, RidesProvider rideProvider,isArabic,isEnglish) { return Stack( children: [ SafeArea( child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Column( // crossAxisAlignment: CrossAxisAlignment.center, // mainAxisAlignment: MainAxisAlignment.center, // mainAxisSize: MainAxisSize.min, children: [ SizedBox(height: MediaQuery.of(context).size.height*0.10), // main card --------------------------------------------------- Column( children: [ Align( // 1. Wrap the Txt widget with an Align widget alignment: isArabic ? Alignment.centerRight : Alignment.centerLeft, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 25.0), // Optional: add some padding child: Txt( // 2. Remove the 'const' keyword text: stateinput==fill_data_ride.CHOOSE_END_LOCATION ?'Where are you going?':'Where are you leaving from?', overflow: TextOverflow.ellipsis, maxLines: 2, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), ), // pickup ------------------------------------------------- Padding( padding: EdgeInsets.all(16.sp), child: CustomLocationInput( showcardicon: false, showcurrentlocationicon: false, showclearicon: true, isreadonly: false, controller: pickupmlocation, hint: isArabic? 'بحث': isEnglish ? 'Search' : 'Recherche', iconstart: SvgPicture.asset( 'assets/icons/my_location.svg', width: 15, ), isFirst: true, isLast: false, onLocationClear: _onPickupLocationClear, onLocationSelected: _onPickupLocationSelected, onFocus: () { dev.log("Field focused on pickup!"); // Your logic here setState(() { stateinput = fill_data_ride.CHOOSE_START_LOCATION; }); }, ), ), // Destination ------------------------------------------------- Padding( padding: EdgeInsets.only(left:16.sp,right:16.sp), child: CustomLocationInput( showcardicon: false, showcurrentlocationicon: false, showclearicon: true, disabled :pickupmlocation.text.isEmpty?true:false, isreadonly: false, controller: destinationlocation, hint: isArabic? 'بحث': isEnglish ? 'Search' : 'Recherche', iconstart: SvgPicture.asset( 'assets/icons/my_location.svg', width: 15, ), isFirst: false, isLast: true, onLocationClear: _onDestinationLocationClear, onLocationSelected: _onDestinationLocationSelected, onFocus: () { print("Field focused on destination!"); setState(() { stateinput = fill_data_ride.CHOOSE_END_LOCATION; }); // Your logic here }, ), ), ], ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: LocationCard( // Pass the path to your SVG asset svgIconPath: 'assets/icons/my_location.svg', text: 'Use current location', fetchinglocationload: _isFetchingLocation, onTap: () { if (_isFetchingLocation) { print("alread you have process ongoing waite"); AppSnackbar.showInfo( "Info", "Already ongoing process,Please wait"); return; } print('Card tapped!'); if(stateinput == fill_data_ride.CHOOSE_END_LOCATION){ _getCurrentLocation(destinationlocation, _onDestinationLocationSelected); }else{ _getCurrentLocation(pickupmlocation, _onPickupLocationSelected); } }, ), ), const SizedBox(height: 10), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: AddressList( onAddressTap: (UserAddress selected) { // do anything you want with `selected` final parts = selected.position.split(','); final dest = LatLng( double.parse(parts[0]), double.parse(parts[1]), ); if(stateinput == fill_data_ride.CHOOSE_END_LOCATION){ _onDestinationLocationSelected(dest, selected.name); }else{ _onPickupLocationSelected(dest, selected.name); } }, ), ), // NOTE: the 'Next' button has been moved out of the scroll view ], ), ), ), _buildNextButton('Next'), ], ); } Widget chooseendlocation(BuildContext context, RidesProvider rideProvider,isArabic,isEnglish) { return Stack( children: [ // ------------- scrollable content (with bottom padding) ---------- Padding( padding: const EdgeInsets.only(bottom: 80), child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Column( children: [ SizedBox(height: MediaQuery.of(context).size.height*0.13), // main card --------------------------------------------------- Column( children: [ Align( alignment: isArabic ? Alignment.centerRight : Alignment.centerLeft, child: const Padding( padding: EdgeInsets.symmetric(horizontal: 25.0), child: Txt( text: 'Where are you going?', overflow: TextOverflow.ellipsis, maxLines: 2, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), ), // pickup ------------------------------------------------- Padding( padding: EdgeInsets.all(16.sp), child: CustomLocationInput( showcardicon: false, showcurrentlocationicon: false, showclearicon: true, isreadonly: false, controller: destinationlocation, hint: isArabic? 'بحث': isEnglish ? 'Search' : 'Recherche', iconstart: SvgPicture.asset( 'assets/icons/my_location.svg', width: 15, ), isFirst: false, isLast: true, onLocationClear: _onDestinationLocationClear, onLocationSelected: _onDestinationLocationSelected, ), ), ], ), const SizedBox(height: 10), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: AddressList( onAddressTap: (UserAddress selected) { // do anything you want with `selected` final parts = selected.position.split(','); final dest = LatLng( double.parse(parts[0]), double.parse(parts[1]), ); _onDestinationLocationSelected(dest, selected.name); }, ), ), // NOTE: the 'Next' button has been moved out of the scroll view ], ), ), ), // _buildBackButton(), _buildNextButton('Next'), ], ); } Widget choosestops(BuildContext context, RidesProvider rideProvider,isArabic,isEnglish) { return Stack( children: [ Padding( padding: const EdgeInsets.only( bottom: 80), // height of the button + margin child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Column( // crossAxisAlignment: CrossAxisAlignment.center, // mainAxisAlignment: MainAxisAlignment.center, // mainAxisSize: MainAxisSize.min, children: [ SizedBox(height: MediaQuery.of(context).size.height*0.13), // main card --------------------------------------------------- Column( children: [ Align( // 1. Wrap the Txt widget with an Align widget alignment: isArabic ? Alignment.centerRight // Align RIGHT for Arabic : Alignment .centerLeft, // Align LEFT for other languages child: const Padding( padding: EdgeInsets.symmetric( horizontal: 25.0), // Optional: add some padding child: Txt( // 2. Remove the 'const' keyword text: 'Where do you want to stop?', overflow: TextOverflow.ellipsis, maxLines: 2, style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold), ), ), ), // pickup ------------------------------------------------- // Inside choosestops -> Column -> children: ..._stopControllers.asMap().entries.map((e) { final i = e.key; // The first stop should not be removable bool isRemovable = i > 0 || _stopControllers.length > 1; return Padding( padding: EdgeInsets.all(16.sp), // Adjust padding child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Make the input field take up the available space Expanded( child: CustomLocationInput( showcardicon: false, showcurrentlocationicon: false, showclearicon: true, isreadonly: false, controller: _stopControllers[i], hint: isArabic? 'بحث': isEnglish ? 'Search' : 'Recherche', iconstart: SvgPicture.asset( 'assets/icons/my_location.svg', ), isFirst: false, isLast: false, // The 'x' button now ONLY clears the location onLocationClear: () => _onStopsLocationClear(i), onLocationSelected: (pos, name) => _onStopsLocationSelected(pos, name, i), ), ), ], ), ); }), ], ), Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: LocationCard( // Pass the path to your SVG asset svgIconPath: 'assets/icons/circle-plus.svg', text: 'Now you can add multiple stops', fetchinglocationload: _isFetchingLocation, onTap: _addStop, ), ), const SizedBox(height: 6), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: AddressList( onAddressTap: (UserAddress selected) { // do anything you want with `selected` final parts = selected.position.split(','); final dest = LatLng( double.parse(parts[0]), double.parse(parts[1]), ); _onStopsLocationSelected( dest, selected.name, _stopControllers.length - 1); }, ), ), // NOTE: the 'Next' button has been moved out of the scroll view ], ), ), ), // _buildBackButton(), _buildNextButton('Skip'), ], ); } }