import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:intaleq_maps/intaleq_maps.dart'; import '../../../core/l10n/app_localizations.dart'; import '../../../core/design/tokens.dart'; import '../../../core/ui/tripz_button.dart'; import '../../trip/bloc/trip_bloc.dart'; import '../../trip/bloc/trip_state.dart'; import '../../trip/bloc/trip_event.dart'; import '../cubit/quote_cubit.dart'; import '../data/models/place_model.dart'; import '../data/models/route_model.dart'; import '../data/maps_repository.dart'; import '../../../core/di.dart'; import 'widgets/ride_class_card.dart'; List _decodePolyline(String encoded) { return PolylineUtils.decode(encoded); } class RoutePreviewScreen extends StatefulWidget { final PlaceModel pickup; final PlaceModel destination; const RoutePreviewScreen({ super.key, required this.pickup, required this.destination, }); @override State createState() => _RoutePreviewScreenState(); } class _RoutePreviewScreenState extends State { IntaleqMapController? _mapController; RouteModel? _route; List _polylinePoints = []; bool _routeLoading = true; String? _routeError; final _promoController = TextEditingController(); @override void initState() { super.initState(); _fetchRoute(); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().fetchQuote( pickupLat: widget.pickup.lat, pickupLng: widget.pickup.lng, pickupAddress: widget.pickup.name, dropoffLat: widget.destination.lat, dropoffLng: widget.destination.lng, dropoffAddress: widget.destination.name, ); }); } @override void dispose() { _promoController.dispose(); super.dispose(); } Future _fetchRoute() async { setState(() { _routeLoading = true; _routeError = null; }); try { final repo = getIt(); final route = await repo.getRoute( fromLat: widget.pickup.lat, fromLng: widget.pickup.lng, toLat: widget.destination.lat, toLng: widget.destination.lng, ); final points = await compute(_decodePolyline, route.polyline ?? ''); if (!mounted) return; setState(() { _route = route; _polylinePoints = points; _routeLoading = false; }); _fitCamera(); } catch (e) { if (!mounted) return; setState(() { _routeError = e.toString(); _routeLoading = false; }); } } void _fitCamera() { if (_mapController == null) return; final bounds = _route?.getLatLngBounds(); if (bounds != null) { _mapController!.moveCamera(CameraUpdate.newLatLngBounds(bounds)); } else { _mapController!.moveCamera(CameraUpdate.newLatLngZoom( LatLng(widget.pickup.lat, widget.pickup.lng), 15, )); } } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final cs = Theme.of(context).colorScheme; final pickupLoc = LatLng(widget.pickup.lat, widget.pickup.lng); final destLoc = LatLng(widget.destination.lat, widget.destination.lng); return BlocListener( listenWhen: (prev, cur) => prev.requestStatus != cur.requestStatus && cur.requestStatus == TripRequestStatus.searching, listener: (context, state) { context.pushReplacement('/trip-tracking'); }, child: Scaffold( body: Stack( children: [ // الخريطة بكامل الشاشة if (_routeLoading) const Center(child: CircularProgressIndicator()) else if (_routeError != null) Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(l10n.errorGeneric), const SizedBox(height: T.s12), TripzButton( label: l10n.retry, onPressed: _fetchRoute, ), ], ), ) else IntaleqMap( apiKey: '', initialCameraPosition: CameraPosition( target: LatLng( (pickupLoc.latitude + destLoc.latitude) / 2, (pickupLoc.longitude + destLoc.longitude) / 2, ), zoom: 14, ), mapType: Theme.of(context).brightness == Brightness.dark ? IntaleqMapType.normal : IntaleqMapType.light, myLocationEnabled: false, zoomControlsEnabled: false, compassEnabled: false, onMapCreated: (c) { _mapController = c; _fitCamera(); }, markers: { Marker( markerId: const MarkerId('pickup'), position: pickupLoc, icon: InlqBitmap.defaultMarkerWithHue(210), anchor: const Offset(0.5, 1.0), ), Marker( markerId: const MarkerId('destination'), position: destLoc, icon: InlqBitmap.defaultMarkerWithHue(0), anchor: const Offset(0.5, 1.0), ), }, polylines: { // حماية محرك C++ من الانهيار إذا كانت النقاط أقل من 2 if (_polylinePoints.length >= 2) Polyline( polylineId: const PolylineId('route'), points: _polylinePoints, color: cs.primary, width: T.polylineWidth, ), }, ), // زر الرجوع العائم (Top Left) Positioned( top: MediaQuery.of(context).padding.top + T.s8, left: T.s12, child: FloatingActionButton.small( heroTag: 'back', backgroundColor: cs.surface.withValues(alpha: 0.9), elevation: 4, onPressed: () => context.pop(), child: Icon(Icons.arrow_back_rounded, color: cs.onSurface), ), ), // ملخص المسار العائم (Top Right) Positioned( top: MediaQuery.of(context).padding.top + T.s12, right: T.s12, child: BlocBuilder( builder: (context, state) { if (state.quote == null) return const SizedBox.shrink(); return Container( padding: const EdgeInsets.symmetric( horizontal: T.s16, vertical: T.s12), decoration: BoxDecoration( color: cs.surface.withValues(alpha: 0.95), borderRadius: BorderRadius.circular(T.r24), boxShadow: const [T.shadowSm], ), child: Text( l10n.routeSummary( state.quote!.distanceText ?? '', state.quote!.durationText ?? '', ), style: Theme.of(context).textTheme.labelLarge?.copyWith( fontWeight: FontWeight.w700, color: cs.onSurface, ), ), ); }, ), ), // Bottom Sheet: فئات الرحلات Align( alignment: Alignment.bottomCenter, child: Container( constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.55, ), decoration: BoxDecoration( color: cs.surface, borderRadius: const BorderRadius.vertical( top: Radius.circular(24), ), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), blurRadius: 20, offset: const Offset(0, -5), ) ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // شريط السحب (Grab handle) const SizedBox(height: T.s12), Container( width: 40, height: 5, decoration: BoxDecoration( color: cs.outlineVariant.withValues(alpha: 0.5), borderRadius: BorderRadius.circular(10), ), ), const SizedBox(height: T.s12), Expanded( child: BlocBuilder( builder: (context, state) { if (state.loading) { return const Center( child: CircularProgressIndicator()); } if (state.error != null) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(l10n.errorGeneric, style: Theme.of(context) .textTheme .bodyLarge ?.copyWith(color: cs.error)), const SizedBox(height: T.s12), TripzButton( label: l10n.retry, onPressed: () => context .read() .fetchQuote( pickupLat: widget.pickup.lat, pickupLng: widget.pickup.lng, pickupAddress: widget.pickup.name, dropoffLat: widget.destination.lat, dropoffLng: widget.destination.lng, dropoffAddress: widget.destination.name, ), ), ], ), ); } final classes = state.quote?.classes ?? []; if (classes.isEmpty) { return Center(child: Text(l10n.noResults)); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: const EdgeInsets.symmetric( horizontal: T.pagePadding), child: Text( l10n.rideClasses, style: Theme.of(context) .textTheme .titleLarge ?.copyWith( fontWeight: FontWeight.w800, letterSpacing: -0.5, ), ), ), const SizedBox(height: T.s12), Expanded( child: ListView.separated( padding: const EdgeInsets.symmetric( horizontal: T.pagePadding), itemCount: classes.length, separatorBuilder: (_, __) => const SizedBox(height: T.s12), itemBuilder: (context, index) { final cls = classes[index]; return RideClassCard( quoteClass: cls, selected: state.selectedClass == cls.vehicleClass, onTap: () => context .read() .selectClass(cls.vehicleClass), ); }, ), ), // شريط الدفع وكود الخصم Container( padding: const EdgeInsets.fromLTRB( T.pagePadding, T.s12, T.pagePadding, T.s8), decoration: BoxDecoration( color: cs.surface, border: Border( top: BorderSide( color: cs.outlineVariant .withValues(alpha: 0.3), ), ), ), child: Column( children: [ _PaymentRow( method: state.paymentMethod, onChanged: (m) => context .read() .setPaymentMethod(m), ), const SizedBox(height: T.s12), _PromoRow( controller: _promoController, onApply: (code) => context .read() .setCouponCode(code), ), ], ), ), // زر طلب الرحلة SafeArea( top: false, child: Padding( padding: const EdgeInsets.fromLTRB( T.pagePadding, 0, T.pagePadding, T.s12), child: BlocBuilder( builder: (context, tripState) { final isLoading = tripState.requestStatus == TripRequestStatus.loading; return TripzButton( label: isLoading ? l10n.loading : l10n.requestRide, onPressed: (state.selectedClass != null && !isLoading) ? () => _requestTrip(context, state) : null, ); }, ), ), ), ], ); }, ), ), ], ), ), ), ], ), ), ); } void _requestTrip(BuildContext context, QuoteState state) { if (state.selectedClass == null) return; context.read().add(TripRequested( pickupLat: widget.pickup.lat, pickupLng: widget.pickup.lng, pickupAddress: widget.pickup.name, dropoffLat: widget.destination.lat, dropoffLng: widget.destination.lng, dropoffAddress: widget.destination.name, vehicleClass: state.selectedClass!, paymentMethod: state.paymentMethod, couponCode: state.couponCode, )); } } class _PaymentRow extends StatelessWidget { final String method; final ValueChanged onChanged; const _PaymentRow({required this.method, required this.onChanged}); @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final cs = Theme.of(context).colorScheme; return Container( padding: const EdgeInsets.symmetric( horizontal: T.pagePadding, vertical: T.s12), decoration: BoxDecoration( color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(T.r12), ), child: Row( children: [ Icon( method == 'wallet' ? Icons.account_balance_wallet_outlined : Icons.money_rounded, color: cs.onSurfaceVariant, ), const SizedBox(width: T.s12), Text(l10n.paymentMethod, style: Theme.of(context).textTheme.bodyMedium), const Spacer(), DropdownButton( value: method, underline: const SizedBox.shrink(), isDense: true, items: [ DropdownMenuItem(value: 'cash', child: Text(l10n.cash)), DropdownMenuItem( value: 'wallet', child: Text(l10n.walletPayment)), ], onChanged: (v) { if (v != null) onChanged(v); }, ), ], ), ); } } class _PromoRow extends StatefulWidget { final TextEditingController controller; final ValueChanged onApply; const _PromoRow({required this.controller, required this.onApply}); @override State<_PromoRow> createState() => _PromoRowState(); } class _PromoRowState extends State<_PromoRow> { @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final cs = Theme.of(context).colorScheme; return Container( padding: const EdgeInsets.symmetric(horizontal: T.pagePadding, vertical: T.s8), decoration: BoxDecoration( color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(T.r12), ), child: Row( children: [ Icon( Icons.local_offer_outlined, color: cs.onSurfaceVariant, ), const SizedBox(width: T.s12), Expanded( child: TextField( controller: widget.controller, decoration: InputDecoration( hintText: l10n.couponCode, border: InputBorder.none, isDense: true, contentPadding: EdgeInsets.zero, ), style: Theme.of(context).textTheme.bodyMedium, ), ), TripzButton( label: l10n.confirm, style: TripzButtonStyle.text, fullWidth: false, onPressed: () { final code = widget.controller.text.trim(); widget.onApply(code.isEmpty ? null : code); }, ), ], ), ); } }