import 'dart:async'; import 'dart:ui'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import '../../../di/service_locator.dart'; import '../../event/active_ride_event.dart'; import '../../state/active_ride_state.dart'; import '../../viewmodel/active_ride_bloc.dart'; import '../notification_toast.dart'; class ActiveRideSheet extends StatefulWidget { final int orderId; final String scooterNumber; final Duration initialElapsedTime; const ActiveRideSheet({ super.key, required this.orderId, required this.scooterNumber, this.initialElapsedTime = Duration.zero, }); @override State createState() => _ActiveRideSheetState(); } class _ActiveRideSheetState extends State { late final ActiveRideBloc _bloc; Timer? _localTimer; @override void initState() { super.initState(); _bloc = getIt(); _bloc.add(LoadScooterOrder(widget.orderId)); _localTimer = Timer.periodic(const Duration(seconds: 1), (timer) { if (mounted && !_bloc.state.isPaused) { setState(() {}); } }); } @override void dispose() { _bloc.close(); _localTimer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return BlocProvider.value( value: _bloc, child: BlocConsumer( listenWhen: (previous, current) => previous.inZone != current.inZone, listener: (context, state) { if (!state.inZone) { BotToast.showCustomNotification( toastBuilder: (_) { return NotificationToast( title: "Вы покинули зону разрешенную для езды", onClose: () { BotToast.cleanAll(); }, ); }, ); } }, builder: (context, state) { // Логика отображения загрузки и ошибок остается прежней if (state.status == ActiveRideStatus.loading && state.order == null) { return _buildLoading(); } if (state.status == ActiveRideStatus.failure && state.order == null) { return _buildError(state.errorMessage); } return _buildContent(state); }, ), ); } Widget _buildLoading() { return Align( alignment: Alignment.bottomCenter, child: Container( padding: const EdgeInsets.all(40), decoration: BoxDecoration( color: const Color(0x00000032).withOpacity(0.6), borderRadius: const BorderRadius.vertical(top: Radius.circular(30)), ), child: const CircularProgressIndicator(color: Colors.white), ), ); } Widget _buildError(String? message) { return Align( alignment: Alignment.bottomCenter, child: Container( padding: const EdgeInsets.all(40), decoration: BoxDecoration( color: const Color(0x00000032).withOpacity(0.6), borderRadius: const BorderRadius.vertical(top: Radius.circular(30)), ), child: Text( message ?? 'Ошибка загрузки', style: const TextStyle(color: Colors.white), ), ), ); } Widget _buildContent(ActiveRideState state) { final displayTime = state.isPaused ? state.elapsedTime : state.elapsedTime + (DateTime.now().difference(DateTime.now().subtract(const Duration(seconds: 1)))); final effectiveElapsedTime = state.isPaused ? state.elapsedTime : DateTime.now().difference(state.order?.startAt ?? state.order?.createdAt ?? DateTime.now()); return Align( alignment: Alignment.bottomCenter, child: ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(30)), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), child: Container( padding: const EdgeInsets.only(top: 20, bottom: 10), decoration: BoxDecoration( color: const Color(0x00000032).withOpacity(0.6), borderRadius: const BorderRadius.vertical(top: Radius.circular(30)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Row( children: [ GestureDetector( onTap: () => Navigator.pop(context), child: Row( children: [ Icon( Icons.arrow_back_ios_sharp, color: const Color(0x99FFFFFF), size: 20, ), Icon( Icons.arrow_back_ios_sharp, color: const Color(0x66FFFFFF), size: 20, ), Icon( Icons.arrow_back_ios_sharp, color: const Color(0x22FFFFFF), size: 20, ), ], ), ), const SizedBox(width: 12), Expanded( child: Text( 'Самокат ${widget.scooterNumber}', style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600, ), overflow: TextOverflow.ellipsis, ), ), ], ), ), const SizedBox(height: 20), Container( width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(vertical: 16), decoration: BoxDecoration( color: Colors.white.withOpacity(0.15), borderRadius: BorderRadius.circular(16), ), child: Column( children: [ Text( _formatDuration(effectiveElapsedTime), style: const TextStyle( color: Colors.white, fontSize: 32, fontWeight: FontWeight.bold, fontFeatures: [FontFeature.tabularFigures()], fontFamily: 'Digital Numbers', ), ), const SizedBox(height: 4), Text( state.isPaused ? 'на паузе' : 'время в пути', style: TextStyle( color: Colors.white.withOpacity(0.6), fontSize: 13, ), ), ], ), ), const SizedBox(height: 20), // 🔹 КНОПКИ УПРАВЛЕНИЯ Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Row( children: [ Expanded( child: Container( height: 56, decoration: BoxDecoration( gradient: state.isPaused ? null : const LinearGradient( colors: [Color(0xFF66E3C4), Color(0xFF4CD1B5)], ), color: state.isPaused ? Colors.white.withOpacity(0.15) : null, borderRadius: BorderRadius.circular(16), ), child: Material( color: Colors.transparent, child: InkWell( onTap: state.status == ActiveRideStatus.loading ? null : () { if (state.isPaused) { _bloc.add(ResumeRide(widget.orderId)); } else { _bloc.add(PauseRide(widget.orderId)); } }, borderRadius: BorderRadius.circular(16), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( state.isPaused ? Icons.play_arrow : Icons.pause, color: state.isPaused ? Colors.white.withOpacity(0.8) : const Color(0xFF0A0F2E), size: 24, ), const SizedBox(height: 4), Text( state.isPaused ? 'ПРОДОЛЖИТЬ' : 'ПАУЗА', style: TextStyle( color: state.isPaused ? Colors.white.withOpacity(0.8) : const Color(0xFF0A0F2E), fontSize: 12, fontWeight: FontWeight.w600, ), ), ], ), ), ), ), ), const SizedBox(width: 12), Expanded( child: Container( height: 56, decoration: BoxDecoration( color: const Color(0xFFB84949), borderRadius: BorderRadius.circular(16), ), child: Material( color: Colors.transparent, child: InkWell( onTap: state.status == ActiveRideStatus.loading ? null : () { _bloc.add(FinishRide(widget.orderId)); Navigator.pop(context); context.go("/home/order-photos/${widget.orderId}"); }, borderRadius: BorderRadius.circular(16), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.stop, color: Colors.white, size: 24, ), const SizedBox(height: 4), const Text( 'ЗАВЕРШИТЬ', style: TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w600, ), ), ], ), ), ), ), ), const SizedBox(width: 12), Expanded( child: Container( height: 56, decoration: BoxDecoration( color: Colors.white.withOpacity(0.1), borderRadius: BorderRadius.circular(16), border: Border.all( color: Colors.white.withOpacity(0.2), width: 1, ), ), child: Material( color: Colors.transparent, child: InkWell( onTap: () { // TODO: Поддержка }, borderRadius: BorderRadius.circular(16), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.phone_in_talk, color: Colors.white.withOpacity(0.8), size: 24, ), const SizedBox(height: 4), Text( 'Поддержка', style: TextStyle( color: Colors.white.withOpacity(0.8), fontSize: 12, fontWeight: FontWeight.w600, ), ), ], ), ), ), ), ), ], ), ), const SizedBox(height: 16), // 🔹 СТАТИСТИКА (2 равных столбца, правый на всю высоту) Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: IntrinsicHeight( child: Row( children: [ // 🔹 ЛЕВЫЙ СТОЛБЕЦ: Скорость + Расстояние Expanded( child: Column( children: [ // Скорость Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white.withOpacity(0.15), borderRadius: BorderRadius.circular(16), ), child: Column( children: [ Text( state.speed.toStringAsFixed(1), style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( 'скорость', style: TextStyle( color: Colors.white.withOpacity(0.6), fontSize: 12, ), ), ], ), ), const SizedBox(height: 12), // Расстояние Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white.withOpacity(0.15), borderRadius: BorderRadius.circular(16), ), child: Column( children: [ Text( state.distance.toStringAsFixed(1), style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( 'расстояние', style: TextStyle( color: Colors.white.withOpacity(0.6), fontSize: 12, ), ), ], ), ), ], ), ), const SizedBox(width: 12), // 🔹 ПРАВЫЙ СТОЛБЕЦ: Стоимость (на всю высоту) Expanded( child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white.withOpacity(0.15), borderRadius: BorderRadius.circular(16), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Стоимость', style: TextStyle( color: Colors.white.withOpacity(0.6), fontSize: 12, ), ), const SizedBox(height: 8), RichText( text: TextSpan( children: [ TextSpan( text: state.cost.toStringAsFixed(1), style: const TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, fontFamily: 'Digital Numbers', fontFeatures: [FontFeature.tabularFigures()], ), ), const TextSpan( text: ' BYN', style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600, ), ), ], ), ), ], ), ), ), ], ), ), ), const SizedBox(height: 24), ], ), ), ), ), ); } String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, '0'); final hours = twoDigits(duration.inHours); final minutes = twoDigits(duration.inMinutes.remainder(60)); final seconds = twoDigits(duration.inSeconds.remainder(60)); return '$hours:$minutes:$seconds'; } }