import 'dart:math' as math; import 'package:wanisclient/config/custom_text.dart'; import 'package:wanisclient/utils/colors.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:beemwidget/text_styles/beem_text_styles.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import '../config/AppSnackbar.dart'; import '../config/constants/app_constant.dart'; import '../config/language/language_enum.dart'; import '../providers/language_provider.dart'; import '../providers/utils_singleton.dart'; import '_DashedProgressPainter.dart'; import 'custom_button.dart'; class startRideCardInfo extends StatefulWidget { final String driverName; final String driverImage; final String tripCost; final String distance; final String carModel; final String licensePlate; final String starLocation; final String endLocation; final double systembottompadding; final int rating; final String driverCurrentLocation; //Lat,Lang final String endLocationLatLang;//Lat,Lang final String paymentMethod; final String estimatedArrival; final VoidCallback onCall; final VoidCallback onCancel; final VoidCallback? onTap; // For expanding/collapsing startRideCardInfo({ required this.driverName, required this.driverImage, required this.starLocation, required this.tripCost, required this.distance, required this.carModel, required this.licensePlate, required this.endLocation, required this.rating, required this.driverCurrentLocation, required this.endLocationLatLang, required this.paymentMethod, required this.estimatedArrival, required this.onCall, required this.onCancel, required this.systembottompadding, this.onTap, }); @override State createState() => _startRideCardInfoState(); } class _startRideCardInfoState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _taxiPositionAnimation; double? _initialDistance; // Store initial distance to destination @override void initState() { super.initState(); _animationController = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); // Store initial distance on first load _storeInitialDistance(); _updateTaxiPosition(); _animationController.repeat(reverse: true); } @override void didUpdateWidget(startRideCardInfo oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.driverCurrentLocation != widget.driverCurrentLocation) { _updateTaxiPosition(); } } void _storeInitialDistance() { final driverCoords = widget.driverCurrentLocation.split(','); final endCoords = widget.endLocationLatLang.split(','); if (driverCoords.length == 2 && endCoords.length == 2) { final driverLat = double.tryParse(driverCoords[0]) ?? 0.0; final driverLng = double.tryParse(driverCoords[1]) ?? 0.0; final endLat = double.tryParse(endCoords[0]) ?? 0.0; final endLng = double.tryParse(endCoords[1]) ?? 0.0; _initialDistance = _calculateDistance(driverLat, driverLng, endLat, endLng); } } void _updateTaxiPosition() { if (_initialDistance == null || _initialDistance == 0) { // Fallback to a default progress if we can't calculate _taxiPositionAnimation = Tween( begin: 0.3, end: 0.4, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); return; } final driverCoords = widget.driverCurrentLocation.split(','); final endCoords = widget.endLocationLatLang.split(','); if (driverCoords.length == 2 && endCoords.length == 2) { final driverLat = double.tryParse(driverCoords[0]) ?? 0.0; final driverLng = double.tryParse(driverCoords[1]) ?? 0.0; final endLat = double.tryParse(endCoords[0]) ?? 0.0; final endLng = double.tryParse(endCoords[1]) ?? 0.0; // Current distance to destination final currentDistance = _calculateDistance(driverLat, driverLng, endLat, endLng); // Calculate progress based on initial distance final progress = _initialDistance! > 0 ? (1.0 - (currentDistance / _initialDistance!)).clamp(0.0, 1.0) : 0.5; // Default to middle if calculation fails _taxiPositionAnimation = Tween( begin: (progress - 0.02).clamp(0.0, 1.0), end: (progress + 0.02).clamp(0.0, 1.0), ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); } } // Distance calculation using Haversine formula double _calculateDistance(double lat1, double lon1, double lat2, double lon2) { const double earthRadius = 6371; // km final double dLat = _toRadians(lat2 - lat1); final double dLon = _toRadians(lon2 - lon1); final double a = math.sin(dLat / 2) * math.sin(dLat / 2) + math.cos(_toRadians(lat1)) * math.cos(_toRadians(lat2)) * math.sin(dLon / 2) * math.sin(dLon / 2); final double c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)); return earthRadius * c; } double _toRadians(double degrees) { return degrees * (math.pi / 180); } @override void dispose() { _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final languageProvider = context.watch(); bool isArabic = languageProvider.currentLangCode == Language.ar; final screenHeight = MediaQuery.of(context).size.height; final maxHeight = screenHeight * 0.65; // Max 60% of screen height final minHeight = 300.h; // Minimum height to ensure content fits return Container( width: MediaQuery.of(context).size.width, decoration: const BoxDecoration( color: AppNewColors.primaryBtnColor, borderRadius: BorderRadius.only( topLeft: Radius.circular(25), topRight: Radius.circular(25), ), boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 10, offset: Offset(0, -2), ), ], ), child: Stack( children: [ ConstrainedBox( constraints: BoxConstraints( minHeight: minHeight, maxHeight: maxHeight, ), child: IntrinsicHeight( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ // Header with purple background Container( width: double.infinity, padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 12.h), decoration: const BoxDecoration( color: AppNewColors.primaryBtnColor, borderRadius: BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30), ), ), child: Row( children: [ Container( padding: EdgeInsets.all(6.w), decoration: const BoxDecoration( color: Colors.white, shape: BoxShape.circle, ), child: SvgPicture.asset( 'assets/icons/location_icon.svg', width: 20.w, height: 20.w, ), ), SizedBox(width: 8.w), Expanded( flex: 2, child: Txt( text: "Estimate to destination", style: GoogleFonts.poppins( fontSize: 13.sp, fontWeight: FontWeight.w500, color: Colors.white, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), SizedBox(width: 8.w), Flexible( child: Container( padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20.r), ), child: Txt( text: widget.estimatedArrival, style: GoogleFonts.poppins( fontSize: 14.sp, fontWeight: FontWeight.w500, color: Colors.black, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), ), SizedBox(width: 10.w), GestureDetector( onTap: widget.onCall, child: Container( padding: EdgeInsets.all(8.w), decoration: const BoxDecoration( color: Colors.white, shape: BoxShape.circle, ), child: Icon( Icons.call, color: Colors.black, size: 16.sp, ), ), ), ], ), ), Flexible( child: Container( width: double.infinity, padding: EdgeInsets.only( top: 12.h, bottom: 80.h + widget.systembottompadding, // Space for button left: 0, right: 0, ), decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30), ), ), child: Column( children: [ // Driver info section Padding( padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 5), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Driver avatar ClipOval( child: CachedNetworkImage( imageUrl: '$mediaUrl${widget.driverImage}', width: 41.w, height: 41.w, fit: BoxFit.cover, placeholder: (_, __) => SizedBox( width: 20.w, height: 20.w, child: const CircularProgressIndicator(strokeWidth: 2), ), errorWidget: (_, __, ___) => Image.asset( 'assets/images/wanislogo.png', width: 41.w, height: 41.w, fit: BoxFit.cover, ), ), ), SizedBox(width: 16.w), // Driver details Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.driverName, style: GoogleFonts.poppins( fontSize: 16.sp, fontWeight: FontWeight.w600, color: Colors.black, ), ), Row( children: [ Text( widget.licensePlate, style: GoogleFonts.poppins( fontSize: 15.sp, fontWeight: FontWeight.w500, color: Colors.black87, ), ), const Padding( padding: EdgeInsets.symmetric(horizontal: 8.0), child: Icon(Icons.circle, color: Colors.black, size: 6), ), Row( mainAxisSize: MainAxisSize.min, children: [ Text( widget.rating.toStringAsFixed(1), style: GoogleFonts.poppins( fontSize: 15.sp, fontWeight: FontWeight.w500, color: Colors.black87, ), ), SizedBox(width: 2.w), SvgPicture.asset( 'assets/icons/rating_star.svg', width: 24, height: 24, colorFilter: const ColorFilter.mode( AppNewColors.yellowColor, BlendMode.srcIn, ), ), ], ), ], ), ], ), ), Image.asset( 'assets/images/wanis.png', width: 77, ), ], ), ), const Padding( padding: EdgeInsets.symmetric(horizontal: 1.0, vertical: 5), child: Divider(thickness: 0.5, color: Colors.grey), ), // --- dashed progress route --- Padding( padding: EdgeInsets.symmetric(horizontal: 20.w), child: SizedBox( height: 24.h, width: double.infinity, child: AnimatedBuilder( animation: _animationController, builder: (context, _) { final w = MediaQuery.of(context).size.width - 40.w; // minus padding final progress = (_taxiPositionAnimation.value).clamp(0.0, 1.0); return Stack( clipBehavior: Clip.none, children: [ // dashed route (colored up to taxi) CustomPaint( size: Size(w, 24.h), painter: DashedProgressPainter( progress: progress, thickness: 4.h, dash: 28.w, gap: 10.w, baseColor: const Color(0xFFEAEAEA), progressColor: const Color(0xFF8B5CF6), radius: 2.r, ), ), // taxi bubble positioned on current progress Positioned( left: progress * w - 10.w, top: 0.h, child: SvgPicture.asset( 'assets/icons/small_traject_taxi.svg', width: 24, ), ), ], ); }, ), ), ), SizedBox(height: 30.h), // Trip locations Padding( padding: EdgeInsets.symmetric(horizontal: 20.w), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Txt( text: "PICK UP", style: GoogleFonts.poppins( fontSize: 15.sp, fontWeight: FontWeight.w600, color: const Color(0xff8D8D8D), letterSpacing: 1.2, ), ), Txt( text: "DROP OFF", style: GoogleFonts.poppins( fontSize: 15.sp, fontWeight: FontWeight.w600, color: const Color(0xff8D8D8D), letterSpacing: 1.2, ), ), ], ), SizedBox(height: 8.h), Row( children: [ Expanded( child: Text( widget.starLocation, style: GoogleFonts.poppins( fontSize: 14.sp, fontWeight: FontWeight.w500, color: Colors.black, ), overflow: TextOverflow.ellipsis, ), ), Expanded( child: Text( widget.endLocation, style: GoogleFonts.poppins( fontSize: 14.sp, fontWeight: FontWeight.w500, color: Colors.black, ), overflow: TextOverflow.ellipsis, textAlign: TextAlign.end, ), ), ], ), ], ), ), SizedBox(height: 20.h), // Payment section Padding( padding: EdgeInsets.symmetric(horizontal: 20.w), child: Container( padding: EdgeInsets.symmetric(vertical: 12.h), decoration: const BoxDecoration( border: Border( top: BorderSide(color: Color(0xffE3E3E3), width: 1), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Row( children: [ SvgPicture.asset( 'assets/icons/wallet_payment_method.svg', width: 20.w, height: 20.w, ), SizedBox(width: 8.w), Flexible( child: Text( widget.paymentMethod, style: GoogleFonts.poppins( fontSize: 14.sp, color: Colors.black, ), overflow: TextOverflow.ellipsis, ), ), SizedBox(width: 4.w), Icon( Icons.arrow_forward_ios, size: 12.sp, color: Colors.black, ), ], ), ), Row( children: [ Text( widget.tripCost, style: GoogleFonts.poppins( fontSize: 14.sp, fontWeight: FontWeight.w500, color: AppNewColors.primaryBtnColor, ), ), Text( languageProvider.translate('+ tarif compteur'), style: TextStyle( fontFamily: isArabic ? "Readex_Pro" : 'Inter', fontSize: 12.sp, fontWeight: FontWeight.w500, color: AppNewColors.detailsSColors, ), ), ], ), ], ), ), ), ], ), ), ), ], ), ), ), _buildCancelButton(context) ], ), ); } Widget _buildCancelButton(BuildContext context) { return Positioned( left: 0, right: 0, bottom: 0, child: Container( padding: EdgeInsets.fromLTRB( 20, 12, 20, widget.systembottompadding + 12, ), decoration: BoxDecoration( color: Colors.white, boxShadow: const [ BoxShadow( color: Color(0x0F000000), blurRadius: 10, offset: Offset(0, 2), ), ], border: Border( top: BorderSide(color: Colors.grey.shade200, width: 1.0), ), ), // ⬇️ no Expanded here child: SizedBox( width: double.infinity, child: CustomButton( onClick: widget.onCancel, width: double.infinity, fontSize: 14, height: 50, borderRadius: 12, txtColor: AppNewColors.white, title: "Cancel Ride", backgroundColor: AppNewColors.primaryBtnColor, borderColor: AppNewColors.primaryBtnColor, loadercolor: AppNewColors.primaryBtnColor, ), ), ), ); } }