import 'dart:developer'; import 'package:wanisclient/config/custom_loader.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:sliding_up_panel/sliding_up_panel.dart'; import 'package:url_launcher/url_launcher.dart'; import '../config/AppSnackbar.dart'; import '../config/CustomPoup.dart'; import '../config/custom_text.dart'; import '../config/enum/ride_enum.dart'; import '../config/enum/ride_type.dart'; import '../config/language/language_enum.dart'; import '../models/ride_model.dart'; import '../providers/language_provider.dart'; import '../providers/ride_providers.dart'; import 'package:latlong2/latlong.dart' as ll; import '../services/map_service.dart'; import '../utils/colors.dart'; import '../utils/sized.dart'; import '../widgets/RideCard.dart'; import '../widgets/TermsCard.dart'; import '../widgets/custom_button.dart'; import 'home_screnn.dart'; class ScheduleRides extends StatefulWidget { const ScheduleRides({super.key}); @override State createState() => _ScheduleRidesState(); } class _ScheduleRidesState extends State with SingleTickerProviderStateMixin { late RidesProvider rideProvider; int? _cancellingRideId; late TabController _tabController; @override void initState() { super.initState(); rideProvider = Provider.of(context, listen: false); _tabController = TabController(length: 2, vsync: this); _tabController.addListener(_handleTabSelection); WidgetsBinding.instance.addPostFrameCallback((_) { rideProvider.fetchSCHEDULEMyRidesStatus(history:false,status: ["ONGOING", "REQUESTED"]); // Provider.of(context, listen: false).fetchSCHEDULEMyRides(); if (rideProvider.currentstate != RideEnum.idle) { rideProvider.updateCurrentstate(RideEnum.idle); } }); } void _handleTabSelection() { // Ensure the listener only acts on user tab changes if (_tabController.indexIsChanging) { switch (_tabController.index) { case 0: // "Scheduled" tab rideProvider.fetchSCHEDULEMyRidesStatus(history:false, status: ["ONGOING", "REQUESTED"]); break; case 1: // "History" tab rideProvider.fetchSCHEDULEMyRides(); break; } } } Future _refreshScheduledRides() async { if (_tabController.index == 0) { await rideProvider.fetchSCHEDULEMyRidesStatus(history:false, status: ["ONGOING", "REQUESTED"]); } else { await rideProvider.fetchSCHEDULEMyRides(); } } @override void dispose() { _tabController.removeListener(_handleTabSelection); _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { rideProvider = context.watch(); final languageProvider = context.watch(); bool isArabic = languageProvider.currentLangCode == Language.ar; return Scaffold( backgroundColor: Colors.white, appBar: AppBar( backgroundColor: Colors.white, automaticallyImplyLeading: false, elevation: 1, title: const Txt( text: "My Rides", style: TextStyle(fontWeight: FontWeight.bold)), centerTitle: true, bottom: TabBar( controller: _tabController, labelColor: AppNewColors.primaryBtnColor, unselectedLabelColor: Colors.grey, indicatorColor: AppNewColors.primaryBtnColor, tabs: [ Tab(text: languageSingleton.translate('Scheduled')), Tab(text: languageSingleton.translate('History')), ], ), ), body: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox(height: getResponsiveScreenHeight(context) * .05), if (rideProvider.isLoading) const Center(child: CustomLoader()) else if (rideProvider.scheduledrides.isEmpty) const Txt(text: "No scheduled rides") else Expanded( child: RefreshIndicator( onRefresh: _refreshScheduledRides, child: ListView.builder( physics: const AlwaysScrollableScrollPhysics(), itemCount: rideProvider.scheduledrides.length, itemBuilder: (context, index) { print( "rideProvider.scheduledrides -------******1111111111111111111111***------- ${rideProvider.scheduledrides}"); if (index < rideProvider.scheduledrides.length) { RideDto ride = rideProvider.scheduledrides[index]; return Padding( padding: const EdgeInsets.only(bottom: 8.0), child: RideCard( cancellingRideId:_cancellingRideId == ride.id, ride: ride, takaction: ride.rideStatus != "CANCELLED" && ride.rideStartTime!.isAfter(DateTime.now()), onupdate:(){ rideProvider.clearRideProvider("Cancel ride schedule"); rideProvider.updateCoupon(""); rideProvider.fillProvider(ride); rideProvider.updateRideType(RideType.SCHEDULE); rideProvider.updateCurrentstate(RideEnum.fill_places); Get.offAll(() => const HomeScreen()); }, oncancel: ()=>_showCancelDialog(ride), ), ); // return _buildRideCard( // rideProvider.scheduledrides[index], isArabic); } else { // This is the "+1" index - render a placeholder or loading indicator return Padding( padding: const EdgeInsets.symmetric(vertical: 24), child: Center( child: rideProvider.hasMoreschudle ? const CircularProgressIndicator() : const Txt(text: "No more scheduled rides"), ), ); } }, ), ), ), if (!rideProvider.isLoading) _buildSlidingPanelContent(rideProvider), ], ), ), ); } Widget _buildRideCard(RideDto ride, bool isArabic) { final bool isCancelling = _cancellingRideId == ride.id; final bool isAnyCancelling = _cancellingRideId != null; print("ide.rideStartTim ==== ${ride.rideStartTime}"); return Card( color: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), elevation: 4, margin: const EdgeInsets.symmetric(vertical: 8), child: Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header with date and edit button Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( ride.rideStartTime != null ? DateFormat('EEE, MMM d, yyyy') .format(ride.rideStartTime!) : 'No Date', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold), ), Column( children: [ buildStatusTag(ride.rideStatus), if (ride.driverId != null) const SizedBox( height: 10, ), if (ride.driverId != null) buildDriverTag(ride.driverId, ride.driverphonenumber), ], ), ], ), const SizedBox(height: 12), Row( children: [ Text( ride.drivername != null ? (ride.drivername == 'not_exist' ? "Driver not assign" : ride.drivername!) : "Driver not assign", overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(width: 8), Expanded( child: Container( height: 1, color: Colors.grey[400], // Light grey line ), ), const SizedBox(width: 8), Image.asset( "assets/images/wanis.png", height: 35.0, ), ], ), Text( DateFormat('hh:mm a').format(ride.rideStartTime!), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), const Txt( text: "You'll be notified when your driver confirms", style: TextStyle(color: Colors.grey), ), const SizedBox(height: 12), _buildLocationRow( title: ride.startLocationName, subtitle: ride.startLocation, color: Colors.green, ), const SizedBox(height: 8), _buildLocationRow( title: ride.endLocationName, subtitle: ride.endLocation, color: Colors.purple, ), const SizedBox(height: 16), (ride.rideStatus != "CANCELLED" ) && ( ride.rideStartTime!.isAfter(DateTime.now())) ? Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ CustomButton( onClick: () => _showCancelDialog(ride), width: MediaQuery.of(context).size.width * .4, fontSize: 15, height: 40, borderRadius: 10, txtColor: AppColors.secondaryColor, title: "Cancel ride", backgroundColor: AppColors.white, borderColor: AppColors.secondaryColor, isloading: isCancelling, loadercolor: AppColors.secondaryColor, ), CustomButton( onClick: () async { rideProvider .clearRideProvider("Cancel ride schedule"); rideProvider.updateCoupon(""); rideProvider.fillProvider(ride); rideProvider.updateRideType(RideType.SCHEDULE); rideProvider.updateCurrentstate(RideEnum.fill_places); Get.offAll(() => const HomeScreen()); }, width: MediaQuery.of(context).size.width * .4, fontSize: 15, height: 40, borderRadius: 10, title: 'Update', backgroundColor: AppNewColors.primaryBtnColor, borderColor: AppNewColors.primaryBtnColor, isloading: null, loadercolor: AppNewColors.primaryBtnColor, ), ], ) : const SizedBox(), const SizedBox(height: 10), ], ), ), ); } void _showCancelDialog(RideDto ride) { showDialog( context: context, builder: (context) => CustomPopup( imagePath: 'assets/images/wanis.png', loader: rideProvider.isLoadingCreateride, twoButton: false, message: 'Are you sure you want to cancel the trip?', onOk: () async { //Navigator.of(context).pop(); if (!mounted) return; setState(() { _cancellingRideId = ride.id; }); try { await rideProvider.cancelRideScheduled(ride.id); //await rideProvider.fetchMyScheduledRides(type: "SCHEDULE",load :false); } catch (e) { log("Error cancelling ride: $e"); } finally { if (!mounted) return; setState(() { _cancellingRideId = null; }); } }, ), ); } Widget _buildLocationRow({ required String title, required String subtitle, required Color color, }) { return Row( children: [ Icon(Icons.circle, color: color, size: 16), const SizedBox(width: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: MediaQuery.sizeOf(context).width * 0.7, child: Text( getPreviewText(title), overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold), ), ), SizedBox( width: MediaQuery.sizeOf(context).width * 0.7, child: Text( getPreviewText(subtitle), overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12, color: Colors.grey), )), ], ), ], ); } Widget _buildSlidingPanelContent(RidesProvider rideProvider) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AnimatedContainer( duration: const Duration(milliseconds: 300), padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ Expanded( child: Padding( padding: const EdgeInsets.all(10.0), child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: AppNewColors.primaryBtnColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.symmetric( vertical: 12, horizontal: 32), elevation: 4, ), onPressed: () { rideProvider.updateRideType(RideType.SCHEDULE); rideProvider.updateCurrentstate(RideEnum.fill_places); Get.offAll(() => const HomeScreen()); }, child: const Txt( text: "Schedule Ride", style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white), ), ), ), ), ], ), ), ], ), ); } Widget buildStatusTag(String status) { Color bgColor; Icon icon; switch (status) { case 'REQUESTED': bgColor = Colors.yellow.shade700; icon = const Icon( Icons.timelapse, size: 16, color: AppColors.white, ); break; case 'CANCELLED': bgColor = Colors.red.shade400; icon = const Icon( Icons.cancel_outlined, size: 16, color: AppColors.white, ); break; case 'ONGOING': bgColor = Colors.orange.shade400; icon = const Icon( Icons.update_outlined, size: 16, color: AppColors.white, ); break; case 'COMPLETED': bgColor = Colors.green.shade500; icon = const Icon( Icons.done_outline_sharp, size: 16, color: AppColors.white, ); break; default: bgColor = Colors.grey; icon = const Icon( Icons.timelapse, size: 16, color: AppColors.white, ); } return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5), decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ icon, const SizedBox( width: 5, ), Text( status, style: const TextStyle( fontSize: 10, fontWeight: FontWeight.w600, color: AppColors.white, ), ), ], ), ); } Widget buildDriverTag(int? status, String? phonenumber) { Color bgColor; Icon icon; bgColor = Colors.green; icon = const Icon( Icons.call, size: 16, color: AppColors.white, ); return GestureDetector( onTap: () { if (phonenumber == null || phonenumber == 'not_exist') return; //TODO moving to Payment Historique _showContactOptions(context, phonenumber); }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5), decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ icon, const SizedBox( width: 5, ), const Txt( text: "Driver Assigned", textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 10, fontWeight: FontWeight.w600, color: AppColors.white, ), ), ], ), ), ); } void _showContactOptions(BuildContext context, String number) { showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20)), ), builder: (ctx) { return Container( height: 200, padding: const EdgeInsets.symmetric(vertical: 8), child: Column( children: [ ListTile( leading: const Icon(Icons.phone, color: Colors.green), title: const Txt( text: "Call", style: TextStyle(), ), onTap: () async { Navigator.pop(ctx); final Uri phoneUri = Uri.parse("tel:$number"); if (await canLaunchUrl(phoneUri)) { await launchUrl(phoneUri); } else { AppSnackbar.showError("Error", "Cannot make a call"); } }, ), ListTile( leading: SvgPicture.asset( 'assets/images/icons8-whatsapp.svg', width: 25, color: Colors.green, ), title: const Txt( text: "Open in WhatsApp", style: TextStyle(), ), onTap: () async { Navigator.pop(ctx); String formattedNumber = number.replaceAll(RegExp(r'\D'), ''); if (!formattedNumber.startsWith('+')) { formattedNumber = '+' + formattedNumber; } if (formattedNumber.isEmpty || formattedNumber.length < 8) { AppSnackbar.showError("Error", "Phone number is too short"); return; } final Uri whatsappUri = Uri.parse("https://wa.me/$formattedNumber"); if (await canLaunchUrl(whatsappUri)) { await launchUrl(whatsappUri, mode: LaunchMode.externalApplication); } else { AppSnackbar.showError("Error", "Cannot open WhatsApp"); } }, ), ], ), ); }, ); } }