import 'dart:developer'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_lucide/flutter_lucide.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:geolocator/geolocator.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:flutter_svg/svg.dart'; import '../config/constants/app_constant.dart'; import '../config/custom_text.dart'; import '../config/language/language_enum.dart'; import '../config/AppSnackbar.dart'; import '../models/ride_model.dart'; import '../providers/language_provider.dart'; import '../services/SystemInsetsService.dart'; import '../utils/colors.dart'; import '../widgets/custom_button.dart'; class RideDetailClientScreen extends StatefulWidget { final RideDto ride; const RideDetailClientScreen({super.key, required this.ride}); @override State createState() => _RideDetailClientScreenState(); } class _RideDetailClientScreenState extends State { double _systembottompadding = 0; Future calculateDistanceFromPolyline(String encodedPolyline) async { PolylinePoints polylinePoints = PolylinePoints(); List points = polylinePoints.decodePolyline(encodedPolyline); double totalDistance = 0.0; for (int i = 0; i < points.length - 1; i++) { final start = points[i]; final end = points[i + 1]; totalDistance += Geolocator.distanceBetween( start.latitude, start.longitude, end.latitude, end.longitude, ); } // Convert meters to kilometers return totalDistance / 1000; } @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _loadSystemPadding(); }); } Future _loadSystemPadding() async { final pad = await SystemInsetsService.instance.bottomPadding(context); log("pad :initState() $pad"); if (mounted) setState(() => _systembottompadding = pad); } @override Widget build(BuildContext context) { final languageProvider = context.watch(); bool isArabic = languageProvider.currentLangCode == Language.ar; return Scaffold( backgroundColor: Colors.white, body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ GestureDetector( onTap: () { Navigator.pop(context); }, child: Container( width: 32, height: 32, margin: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: const Color(0xffF5F5F5), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: AppNewColors.detailsColors.withOpacity(.1), blurRadius: 1, offset: const Offset(2, 1), ), ], ), child: const Icon( Icons.arrow_back, color: AppNewColors.detailsColors, size: 16, ), ), ), Expanded( child: Center( child: Txt( text: 'Ride details', style: TextStyle( color: const Color(0xFF0A0C11), fontSize: 18, fontFamily: isArabic ? "Readex_Pro" : 'Inter', fontWeight: FontWeight.w600, height: 1.23, ), ), ), ), const SizedBox(width: 32), ], ), ), // Departure Section Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all( color: Theme.of(context).brightness == Brightness.dark ? AppNewColors.white : AppNewColors.detailsColors, width: 0.5, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Txt( text: "Departure", style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: Theme.of(context).brightness == Brightness.dark ? Colors.white : Colors.black), ), const SizedBox(height: 20), // Pick up and Drop off locations in containers _locationItem( "Pick up", widget.ride.startLocationName ?? 'El Mourouj, Ben Arous'), const SizedBox(height: 12), _locationItem("Drop off", widget.ride.endLocationName ?? 'Centre Ville, Tunis'), const SizedBox(height: 14), const Divider( thickness: 0.3, ), const SizedBox(height: 14), // Date, Depart, Arrival Section _timeInfoRow( LucideIcons.calendar, "Date", _formatDate(widget.ride.rideStartTime), ), const SizedBox(height: 16), _timeInfoRow( Icons.access_time, "Depart", _formatTime(widget.ride.rideStartTime), ), // const SizedBox(height: 16), // _timeInfoRow( // Icons.access_time, // "Arrival", // _formatTime(widget.ride.rideStartTime?.add( // Duration( // minutes: // widget.ride.durationInMinutes?.round() ?? 0), // )), // ), ], ), ), const SizedBox(height: 30), // Driver and Vehicle details Section if (widget.ride.drivername != null) ...[ const SizedBox(height: 16), Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[800] : const Color(0xffF9F9FA), borderRadius: BorderRadius.circular(12), border: Border.all( color: Theme.of(context).brightness == Brightness.dark ? AppNewColors.white : AppNewColors.detailsColors, width: 0.5, ), ), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Txt( text: "Driver and Vehicle details", style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: Theme.of(context).brightness == Brightness.dark ? Colors.white : Colors.black), ), const SizedBox(height: 16), Row( children: [ // Driver Avatar ClipOval( child: CachedNetworkImage( imageUrl: widget.ride.driverimageurl ?? '', width: 50, height: 50, fit: BoxFit.cover, placeholder: (context, url) => Container( width: 50, height: 50, decoration: BoxDecoration( color: Colors.grey[300], shape: BoxShape.circle, ), child: const Icon(Icons.person, color: Colors.grey), ), errorWidget: (context, url, error) => Container( width: 50, height: 50, decoration: BoxDecoration( color: Colors.grey[300], shape: BoxShape.circle, ), child: const Icon(Icons.person, color: Colors.grey), ), ), ), const SizedBox(width: 12), // Driver Info Expanded( child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Txt( text: (widget.ride.drivername!=null && widget.ride.drivername!="not_exist") ?widget.ride.drivername!: 'Driver', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Theme.of(context).brightness == Brightness.dark ? Colors.white : Colors.black, ), ), const SizedBox(height: 4), const Txt( text: "Verified driver", style: TextStyle( fontSize: 12, color: AppNewColors.detailsColors, ), ), ], ), Row( children: [ Txt( text: widget.ride.averagedriverrating .toStringAsFixed(2) ?? '0.0', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: Theme.of(context).brightness == Brightness.dark ? Colors.white : Colors.black, ), ), const SizedBox(width: 4), const Icon(Icons.star, color: Colors.amber, size: 16), ], ), ], ), ), ], ), if (widget.ride.driverphonenumber != null && widget.ride.driverphonenumber != "not_exist") const Divider( thickness: .9, ), const SizedBox(height: 12), // Call him button if (widget.ride.driverphonenumber != null && widget.ride.driverphonenumber != "not_exist") CustomButton( onClick: () => _showContactOptions( context, widget.ride.driverphonenumber!), fontSize: 16, width: double.infinity, height: 50, borderRadius: 12, txtColor: Colors.white, title: 'Call him', backgroundColor: AppNewColors.primaryBtnColor, borderColor: AppNewColors.primaryBtnColor, ), ], ), ), ], const SizedBox(height: 30), // Pricing Section Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all( color: Theme.of(context).brightness == Brightness.dark ? AppNewColors.white : AppNewColors.detailsColors, width: 0.5, ), ), child: Column( children: [ _priceRow( "${widget.ride.totalPrice?.toStringAsFixed(0) ?? '24'} TND ${languageSingleton.translate('(Booking fee) + meter rate')}", "", isMainPrice: true, ), const SizedBox(height: 8), Align( alignment: isArabic ?Alignment.centerRight : Alignment.centerLeft, child: const Txt( text: "The reservation fees is not the trip total", style: TextStyle( color: Color(0xff8C929C), fontSize: 13, ), ), ), const SizedBox(height: 16), _priceRow( "Coupon", "${widget.ride.couponvalue?.toStringAsFixed(2)} TND", isMainPrice: false, ), const SizedBox(height: 8), Row( children: [ const Txt( text: "Including taxes and fees.", style: TextStyle( color: Color(0xff8C929C), fontSize: 13, ), ), const SizedBox(width: 5,), GestureDetector( onTap: () { // View details action }, child: const Txt( text: "View details", style: TextStyle( color: AppNewColors.primaryColor, fontSize: 13, decoration: TextDecoration.underline, ), ), ), ], ), ], ), ), SizedBox(height: 20 + _systembottompadding), // Additional ride info if needed // if (widget.ride.ridername != null) ...[ // const SizedBox(height: 20), // _infoRow('Rider Name', widget.ride.ridername ?? '-'), // const SizedBox(height: 12), // _infoRow('Phone', widget.ride.riderphonenumber ?? '-'), // const SizedBox(height: 12), // _infoRow('Payment Method', widget.ride.paymenttype ?? 'N/A'), // ], ], ), ), ); } Widget _locationItem(String label, String location) { return Row( children: [ SizedBox( width: 55, child: Txt( text: label, style: const TextStyle( color: AppNewColors.detailsColors, fontSize: 14, ), ), ), const SizedBox(width: 12), Expanded( child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), decoration: BoxDecoration( color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[800] : const Color(0xffF9F9FA), borderRadius: BorderRadius.circular(8), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ const Icon( LucideIcons.map_pin, size: 20, ), const SizedBox(width: 8), Flexible( child: Txt( text: location, maxLines: 1, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), ), ], ), ), ), ], ); } Widget _timeInfoRow(IconData icon, String label, String value) { return Row( children: [ Icon( icon, color: AppNewColors.detailsColors, size: 20, ), const SizedBox(width: 12), Expanded( child: Txt( text: label, style: const TextStyle( color: AppNewColors.detailsColors, fontSize: 16, ), ), ), Txt( text: value, style: TextStyle( fontSize: 16, color: Theme.of(context).brightness == Brightness.dark ? AppNewColors.white : AppNewColors.black, fontWeight: FontWeight.w600, ), ), ], ); } Widget _priceRow(String label, String value, {bool isMainPrice = false}) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Txt( text: label, style: TextStyle( fontSize: isMainPrice ? 16 : 14, fontWeight: isMainPrice ? FontWeight.w600 : FontWeight.normal, color: AppNewColors.detailsSColors, ), ), Txt( text: value, style: TextStyle( fontSize: isMainPrice ? 18 : 14, fontWeight: FontWeight.w600, color: AppNewColors.detailsSColors, ), ), ], ); } Widget _infoRow(String title, String value) { return GestureDetector( onTap: () { if (title != 'Phone' || value == null || value == "not_exist") { return; } _showContactOptions(context, value); }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Txt( text: title, style: const TextStyle(fontWeight: FontWeight.bold)), Text(value), ], ), ), ); } 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"); } }, ), ], ), ); }, ); } String _formatDate(DateTime? dateTime) { if (dateTime == null) return 'Fri 18 Aug'; return DateFormat('E d MMM').format(dateTime); } String _formatTime(DateTime? dateTime) { if (dateTime == null) return '06:00 AM'; return DateFormat('hh:mm a').format(dateTime); } String _formatDateTime(DateTime? dateTime) { if (dateTime == null) return '-'; final now = DateTime.now(); final isToday = now.year == dateTime.year && now.month == dateTime.month && now.day == dateTime.day; final time = DateFormat('h:mm a').format(dateTime); return isToday ? 'Today $time' : DateFormat.yMMMd().add_jm().format(dateTime); } }