import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../core/design/tokens.dart'; import '../../../core/design/tripz_colors.dart'; import '../../../core/di.dart'; import '../../../core/l10n/l10n.dart'; import '../../../core/ui/status_banner.dart'; import '../../../core/ui/app_drawer.dart'; import '../cubit/duty_cubit.dart'; import '../cubit/duty_state.dart'; import '../data/models/geo_point.dart'; import '../data/models/route_info.dart'; import 'widgets/active_duty_sheet.dart'; import 'widgets/duty_toggle.dart'; import 'widgets/offer_sheet.dart'; import 'widgets/ride_map.dart'; /// نصيب الورقة السفلية من ارتفاع الشاشة — مطابق لتطبيق الراكب. const double _sheetFraction = 0.33; /// ما تحتاجه الخريطة وحدها — تغيّرُ الرصيد أو الانشغال لا يعيد بناءها. class _MapData { const _MapData({this.me, this.pickup, this.dropoff, this.route, this.target}); final GeoPoint? me; final GeoPoint? pickup; final GeoPoint? dropoff; final RouteInfo? route; final GeoPoint? target; @override bool operator ==(Object other) => other is _MapData && other.me == me && other.pickup == pickup && other.dropoff == dropoff && other.route == route && other.target == target; @override int get hashCode => Object.hash(me, pickup, dropoff, route, target); } /// شاشة السائق: خريطة + مفتاح الاتصال أعلى + ورقة تتبدّل بالمرحلة. class DutyPage extends StatefulWidget { const DutyPage({super.key}); @override State createState() => _DutyPageState(); } class _DutyPageState extends State { late final DutyCubit _cubit = sl()..init(); @override void dispose() { _cubit.close(); super.dispose(); } @override Widget build(BuildContext context) { return BlocProvider.value( value: _cubit, child: Scaffold( drawer: const AppDrawer(), body: Stack( children: [ // الخريطة معزولة عن بقية الحالة: نبضة موقع كل 25 متراً يجب ألّا // تعيد بناء الورقة والمفتاح معها. // // **مرفوعة عن القاع بالثلث** كما في تطبيق الراكب — الورقة لها // مساحتها ولا تغطّي الخريطة. Positioned( top: 0, left: 0, right: 0, bottom: MediaQuery.sizeOf(context).height * _sheetFraction, child: BlocSelector( selector: (s) => _MapData( me: s.myLocation, pickup: s.trip?.origin, dropoff: s.trip?.destination, route: s.navRoute, target: s.navTarget, ), // السائق كان يرى **موقعه وحده**: بلا نقطة الراكب ولا الوجهة // ولا خط يقوده. الخريطة الآن تعرض ما تعرضه خريطة الراكب: // نقطتَي الرحلة والمسار الحيّ إلى الهدف الحالي. builder: (context, d) => RideMap( origin: d.pickup ?? d.me, destination: d.dropoff, driver: d.pickup == null ? null : d.me, route: d.route, // الكاميرا تتبع الهدف حين يوجد، وإلا فموقع السائق. cameraTarget: d.target ?? d.me, ), ), ), SafeArea( child: Padding( padding: Space.page, child: Column( children: [ Builder( builder: (context) => Align( alignment: AlignmentDirectional.topStart, child: Material( color: context.tripzColors.surfaceRaised, shape: const CircleBorder(), clipBehavior: Clip.antiAlias, child: InkWell( onTap: Scaffold.of(context).openDrawer, child: const Padding( padding: EdgeInsets.all(Space.sm), child: Icon(Icons.menu_rounded, size: Sizes.icon), ), ), ), ), ), const SizedBox(height: Space.xs), BlocSelector( selector: (s) => (online: s.isOnline, busy: s.busy), builder: (context, d) => DutyToggle( isOnline: d.online, busy: d.busy, onToggle: _cubit.toggleOnline, ), ), const SizedBox(height: Space.xs), BlocSelector( selector: (s) => s.creditBlocked, builder: (context, blocked) => blocked ? StatusBanner( message: context.l10n.creditBlocked, tone: BannerTone.danger, icon: Icons.account_balance_wallet_outlined, ) : const SizedBox.shrink(), ), ], ), ), ), Align( alignment: Alignment.bottomCenter, child: BlocBuilder( builder: (context, state) => _Sheet(state: state), ), ), ], ), ), ); } } class _Sheet extends StatelessWidget { const _Sheet({required this.state}); final DutyState state; @override Widget build(BuildContext context) { final l10n = context.l10n; final child = switch (state.phase) { DutyPhase.offline => const SizedBox.shrink(), DutyPhase.waiting => Padding( padding: const EdgeInsets.symmetric(vertical: Space.md), child: Text( state.error == DutyError.offerTaken ? l10n.driverOfferTaken : l10n.driverNoOffers, textAlign: TextAlign.center, style: context.texts.bodyMedium?.copyWith( color: context.colors.onSurfaceVariant, ), ), ), DutyPhase.offered => OfferSheet(offer: state.offer!), DutyPhase.active => ActiveDutySheet( trip: state.trip!, busy: state.busy, navRoute: state.navRoute, ), }; if (state.phase == DutyPhase.offline) return const SizedBox.shrink(); return Container( width: double.infinity, // الحدّ الأدنى = المساحة المحجوزة تحت الخريطة، وإلا ظهر فراغ بينهما. constraints: BoxConstraints( minHeight: MediaQuery.sizeOf(context).height * _sheetFraction, ), decoration: BoxDecoration( color: context.colors.surface, borderRadius: Radii.sheetTop, ), child: SafeArea( top: false, child: Padding( padding: Space.page, child: AnimatedSize( duration: Motion.inScreen, curve: Motion.curve, alignment: Alignment.bottomCenter, child: child, ), ), ), ); } }