From 591265a7fcb25c7c85df89c90d259f16854271ae Mon Sep 17 00:00:00 2001 From: Polyanka1 Date: Thu, 21 May 2026 18:00:26 +0300 Subject: [PATCH] fix order history, add CheckUser check before starting the trip, fix dialog for cancel the order, add dialog for finish the order --- .../dialog/cancel_booking_dialog.dart | 24 ++- .../finish_ride_confirmation_dialog.dart | 96 ++++++++++++ .../components/sheet/active_ride_sheet.dart | 44 +++--- lib/presentation/screens/map_screen.dart | 143 ++++++++++-------- .../screens/scooter_detail_screen.dart | 1 + .../viewmodel/order_history_bloc.dart | 19 +-- 6 files changed, 217 insertions(+), 110 deletions(-) create mode 100644 lib/presentation/components/dialog/finish_ride_confirmation_dialog.dart diff --git a/lib/presentation/components/dialog/cancel_booking_dialog.dart b/lib/presentation/components/dialog/cancel_booking_dialog.dart index d980296..c7a7047 100644 --- a/lib/presentation/components/dialog/cancel_booking_dialog.dart +++ b/lib/presentation/components/dialog/cancel_booking_dialog.dart @@ -3,11 +3,8 @@ import 'package:flutter/material.dart'; class CancelBookingDialog extends StatelessWidget { const CancelBookingDialog({super.key}); - @override Widget build(BuildContext context) { - bool result = false; - return Dialog( backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.symmetric(horizontal: 40), @@ -42,6 +39,7 @@ class CancelBookingDialog extends StatelessWidget { ), const SizedBox(height: 24), + // 🔹 Кнопка "Отменить" — ПЕРВАЯ, зелёная (градиент), возвращает true Container( width: double.infinity, height: 52, @@ -52,10 +50,7 @@ class CancelBookingDialog extends StatelessWidget { ), ), child: ElevatedButton( - onPressed: () => { - result = false, - Navigator.pop(context, result) - }, + onPressed: () => Navigator.pop(context, true), // ✅ true = отменить style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, @@ -64,9 +59,9 @@ class CancelBookingDialog extends StatelessWidget { ), ), child: const Text( - "Оставить", + "Отменить", style: TextStyle( - color: Color(0xFF1D273A), + color: Color(0xFF1D273A), // тёмный текст на светлом градиенте fontSize: 16, fontWeight: FontWeight.bold, ), @@ -74,14 +69,13 @@ class CancelBookingDialog extends StatelessWidget { ), ), const SizedBox(height: 12), + + // 🔹 Кнопка "Оставить" — ВТОРАЯ, тёмная, возвращает false SizedBox( width: double.infinity, height: 52, child: ElevatedButton( - onPressed: () { - result = true; - Navigator.pop(context, result); - }, + onPressed: () => Navigator.pop(context, false), // ❌ false = оставить style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF0D1024), shape: RoundedRectangleBorder( @@ -89,7 +83,7 @@ class CancelBookingDialog extends StatelessWidget { ), ), child: const Text( - "Отменить", + "Оставить", style: TextStyle(color: Colors.white, fontSize: 16), ), ), @@ -99,4 +93,4 @@ class CancelBookingDialog extends StatelessWidget { ), ); } -} +} \ No newline at end of file diff --git a/lib/presentation/components/dialog/finish_ride_confirmation_dialog.dart b/lib/presentation/components/dialog/finish_ride_confirmation_dialog.dart new file mode 100644 index 0000000..8850254 --- /dev/null +++ b/lib/presentation/components/dialog/finish_ride_confirmation_dialog.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; + +class FinishRideConfirmationDialog extends StatelessWidget { + const FinishRideConfirmationDialog({super.key}); + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: Colors.transparent, + insetPadding: const EdgeInsets.symmetric(horizontal: 40), + child: Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: const Color(0xFF2E3253).withOpacity(0.95), + borderRadius: BorderRadius.circular(28), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + "Завершить поездку?", + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 22, + fontWeight: FontWeight.w600, + height: 1.2, + ), + ), + const SizedBox(height: 16), + const Text( + "Вы действительно хотите завершить поездку?", + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white70, + fontSize: 14, + height: 1.4, + ), + ), + const SizedBox(height: 24), + + // 🔹 Кнопка "Завершить" — ПЕРВАЯ, зелёная (градиент), возвращает true + Container( + width: double.infinity, + height: 52, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(26), + gradient: const LinearGradient( + colors: [Color(0xFF8EFEB5), Color(0xFF86FEF1)], + ), + ), + child: ElevatedButton( + onPressed: () => Navigator.pop(context, true), // ✅ true = завершить + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26), + ), + ), + child: const Text( + "Завершить", + style: TextStyle( + color: Color(0xFF1D273A), // тёмный текст на светлом градиенте + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + const SizedBox(height: 12), + + // 🔹 Кнопка "Отмена" — ВТОРАЯ, тёмная, возвращает false + SizedBox( + width: double.infinity, + height: 52, + child: ElevatedButton( + onPressed: () => Navigator.pop(context, false), // ❌ false = отмена + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF0D1024), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26), + ), + ), + child: const Text( + "Отмена", + style: TextStyle(color: Colors.white, fontSize: 16), + ), + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/components/sheet/active_ride_sheet.dart b/lib/presentation/components/sheet/active_ride_sheet.dart index 416317e..a564221 100644 --- a/lib/presentation/components/sheet/active_ride_sheet.dart +++ b/lib/presentation/components/sheet/active_ride_sheet.dart @@ -8,6 +8,7 @@ import '../../../di/service_locator.dart'; import '../../event/active_ride_event.dart'; import '../../state/active_ride_state.dart'; import '../../viewmodel/active_ride_bloc.dart'; +import '../dialog/finish_ride_confirmation_dialog.dart'; import '../notification_toast.dart'; class ActiveRideSheet extends StatefulWidget { @@ -290,32 +291,25 @@ class _ActiveRideSheetState extends State { 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, - ), - ), - ], - ), + : () async { + // 🔹 Показываем диалог подтверждения + final result = await showDialog( + context: context, + builder: (context) => const FinishRideConfirmationDialog(), + ); + + // 🔹 Если пользователь подтвердил — завершаем и переходим + if (result == true) { + _bloc.add(FinishRide(widget.orderId)); + Navigator.pop(context); // закрываем ActiveRideSheet + context.go("/home/order-photos/${widget.orderId}"); + } + // 🔹 Если отменил — ничего не делаем, диалог уже закрылся + }, ), + + + ), ), ), diff --git a/lib/presentation/screens/map_screen.dart b/lib/presentation/screens/map_screen.dart index 0871cf3..01a99d4 100644 --- a/lib/presentation/screens/map_screen.dart +++ b/lib/presentation/screens/map_screen.dart @@ -378,70 +378,41 @@ class _MapScreenState extends State { } void _onMarkerTap(List scooters) async { + final flags = context.read().state.flags; + + if (!flags.hasCard) { + _showExistingNotification( + child: PaymentNotificationCard( + onBindCard: () { + BotToast.cleanAll(); + context.push("/home/payment-methods"); + }, + onClose: () => BotToast.cleanAll(), + ), + ); + return; + } + if (flags.hasUnpaidOrder) { + _showExistingNotification( + child: UnpaidOrderNotificationCard( + onClose: () => BotToast.cleanAll(), + ), + ); + return; + } + if (flags.hasFine) { + _showExistingNotification( + child: FineNotificationCard( + onClose: () => BotToast.cleanAll(), + ), + ); + return; + } + context.push( "/home/scooter-sheet", extra: {'scooters': scooters, 'currentLocation': _currentPosition}, ); - - /*final scoot = await showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - isDismissible: true, - builder: (context) { - return BlocProvider( - create: (context) => - ScooterDetailModalBloc( - getIt(), - getIt(), - getIt(), - )..add( - ScooterDetailModalStarted( - scooters, - _currentPosition!.latitude, - _currentPosition!.longitude, - ), - ), - child: ScooterBottomSheet(), - ); - }, - );*/ - /*bool? isBooking = false; - if (scoot != null) { - final result = await context.push('/home/scooter/${scoot.id}'); - - if (result == true) { - // Даем небольшую задержку, чтобы навигация завершилась корректно - await Future.delayed(Duration(milliseconds: 300), () async { - isBooking = await showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - isDismissible: true, - builder: (context) => BlocProvider( - create: (context) => TariffSheetBloc( - getIt(), - getIt(), - getIt(), - ), - child: TariffSheet(scooter: scoot), - ), - ); - }); - } - } - - if (isBooking ?? false) { - showModalBottomSheet( - context: context, - builder: (context) => BlocProvider( - create: (context) => - CurrentRidesBloc(getIt()) - ..add(LoadClientOrders(1)), - child: CurrentRidesSheet(clientId: 1), - ), - ); - }*/ } void _onMapSettingsTap() { @@ -717,7 +688,42 @@ class _MapScreenState extends State { right: 0, child: Center( child: GestureDetector( - onTap: () => context.push("/home/qr-info"), + onTap: () { + final flags = context.read().state.flags; + + // 🔹 Проверка флагов — показываем те же карточки, что и в listener + if (!flags.hasCard) { + _showExistingNotification( + child: PaymentNotificationCard( + onBindCard: () { + BotToast.cleanAll(); + context.push("/home/payment-methods"); + }, + onClose: () => BotToast.cleanAll(), + ), + ); + return; + } + if (flags.hasUnpaidOrder) { + _showExistingNotification( + child: UnpaidOrderNotificationCard( + onClose: () => BotToast.cleanAll(), + ), + ); + return; + } + if (flags.hasFine) { + _showExistingNotification( + child: FineNotificationCard( + onClose: () => BotToast.cleanAll(), + ), + ); + return; + } + + // ✅ Все проверки пройдены — переход на сканирование + context.push("/home/qr-info"); + }, child: Container( width: 64, height: 64, @@ -866,3 +872,18 @@ class _CircleIconButton extends StatelessWidget { ); } } + +void _showExistingNotification({required Widget child}) { + BotToast.showCustomNotification( + duration: null, // не исчезает, пока пользователь не закроет + toastBuilder: (_) { + return Container( + margin: const EdgeInsets.only(top: 120), // тот же отступ, что в listener + child: Material( + color: Colors.transparent, + child: child, // PaymentNotificationCard / UnpaidOrderNotificationCard / FineNotificationCard + ), + ); + }, + ); +} diff --git a/lib/presentation/screens/scooter_detail_screen.dart b/lib/presentation/screens/scooter_detail_screen.dart index ea286eb..096e93d 100644 --- a/lib/presentation/screens/scooter_detail_screen.dart +++ b/lib/presentation/screens/scooter_detail_screen.dart @@ -102,6 +102,7 @@ class ScooterDetailScreen extends StatelessWidget { context.pushReplacement('/home/tarif-sheet', extra: scooter); }, ), + const SizedBox(height: 30) ], ), ), diff --git a/lib/presentation/viewmodel/order_history_bloc.dart b/lib/presentation/viewmodel/order_history_bloc.dart index 5956994..ff690f4 100644 --- a/lib/presentation/viewmodel/order_history_bloc.dart +++ b/lib/presentation/viewmodel/order_history_bloc.dart @@ -21,14 +21,14 @@ class OrderHistoryState { final List orders; final String? errorMessage; final int currentPage; - final bool hasMore; + final bool hasMore; // можно оставить, но всегда будет false OrderHistoryState({ required this.status, this.orders = const [], this.errorMessage, this.currentPage = 1, - this.hasMore = true, + this.hasMore = false, // ✅ По умолчанию — нет больше данных }); OrderHistoryState copyWith({ @@ -51,7 +51,8 @@ class OrderHistoryState { class OrderHistoryBloc extends Bloc { final GetScooterOrderHistoryUsecase _usecase; - OrderHistoryBloc(this._usecase) : super(OrderHistoryState(status: OrderHistoryStatus.initial)) { + OrderHistoryBloc(this._usecase) + : super(OrderHistoryState(status: OrderHistoryStatus.initial)) { on(_onFetchRequested); on(_onRefreshRequested); } @@ -64,9 +65,11 @@ class OrderHistoryBloc extends Bloc { emit(state.copyWith(status: OrderHistoryStatus.loading)); } - final result = await _usecase.call(page: event.page); + final result = await _usecase.call( + page: event.page, + pageSize: 1000, + ); - // ✅ Явная проверка с правильным типом if (result is Success>) { final orders = result.data; @@ -84,12 +87,10 @@ class OrderHistoryBloc extends Bloc { status: OrderHistoryStatus.success, orders: newOrders, currentPage: event.page, - hasMore: orders.length == 20, + hasMore: false, )); } - } - // ✅ Явно указываем тип Failure - else if (result is Failure>) { + } else if (result is Failure>) { emit(state.copyWith( status: OrderHistoryStatus.failure, errorMessage: result.failure.message ?? 'Неизвестная ошибка',