import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_event.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_state.dart'; import 'package:hl_lieferservice/feature/delivery/repository/tour_repository.dart'; import 'package:hl_lieferservice/feature/delivery/overview/service/distance_service.dart'; import 'package:hl_lieferservice/feature/delivery/overview/service/reorder_service.dart'; import 'package:hl_lieferservice/feature/delivery/util.dart'; import 'package:hl_lieferservice/model/tour.dart'; import 'package:hl_lieferservice/feature/authentication/bloc/auth_bloc.dart'; import 'package:hl_lieferservice/feature/authentication/bloc/auth_event.dart'; import 'package:hl_lieferservice/feature/authentication/exceptions.dart'; import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart'; import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart'; import 'package:rxdart/rxdart.dart'; class TourBloc extends Bloc { OperationBloc opBloc; AuthBloc authBloc; TourRepository tourRepository; StreamSubscription? _combinedSubscription; TourBloc({required this.opBloc, required this.authBloc, required this.tourRepository}) : super(TourInitial()) { _combinedSubscription = CombineLatestStream.combine2( tourRepository.tour, tourRepository.paymentOptions, (tour, payments) => {'tour': tour, 'payments': payments}, ).listen((combined) { final tour = combined['tour'] as Tour?; final payments = combined['payments'] as List; if (tour == null) { return; } add(TourUpdated(tour: tour, payments: payments)); }); on(_load); on(_assignCar); on(_increment); on(_scan); on(_scanComponent); on(_holdDelivery); on(_cancelDelivery); on(_reactivateDelivery); on(_unscan); on(_resetAmount); on(_addDiscount); on(_removeDiscount); on(_updateDiscount); on(_updateDeliveryOptions); on(_updatePayment); on(_finishDelivery); on(_updated); on(_calculateDistances); on(_requestSortingInformation); on(_reorderDelivery); on(_carsLoaded); on(_setArticleAmount); } @override Future close() { _combinedSubscription?.cancel(); return super.close(); } void _handleError(Object e, String fallbackMessage) { if (e is UserUnauthorized) { authBloc.add(SessionExpiredEvent()); } else { opBloc.add(FailOperation(message: fallbackMessage)); } } void _setArticleAmount( SetArticleAmountEvent event, Emitter emit, ) async { final currentState = state; if (currentState is TourLoaded) { opBloc.add(StartOperation()); try { await tourRepository.setArticleAmount( event.deliveryId, event.articleId, event.amount, event.reason, ); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("$e $st"); _handleError(e, "Fehler beim Ändern der Menge des Artikels"); } } } void _carsLoaded(CarsLoadedEvent event, Emitter emit) { final currentState = state; if (currentState is TourLoaded) { currentState.tour.driver.cars = event.cars; emit(currentState.copyWith()); } } void _reorderDelivery( ReorderDeliveryEvent event, Emitter emit, ) async { final currentState = state; if (currentState is TourLoaded) { Map> container = {...currentState.sortingInformation}; List reorderedList = reorderList( container[event.carId.toString()] ?? [], event.oldPosition, event.newPosition, ); container[event.carId.toString()] = reorderedList; await ReorderService().saveSortingInformation(container); emit(currentState.copyWith(sortingInformation: container)); } } void _calculateDistances( RequestDeliveryDistanceEvent event, Emitter emit, ) async { Map distances = {}; for (final delivery in event.tour.deliveries) { try { distances[delivery.id] = await DistanceService.getDistanceByRoad( delivery.customer.address.toString(), ); } catch (e, st) { debugPrint("Fehler beim Laden der Distanz: $e"); debugPrint("$st"); distances[delivery.id] = double.nan; } } final currentState = state; if (currentState is TourLoaded) { emit(currentState.copyWith(distances: distances)); } } void _requestSortingInformation( RequestSortingInformationEvent event, Emitter emit, ) async { Map> container = {}; try { ReorderService service = ReorderService(); // Create empty default value if it does not exist yet if (!service.orderInformationExist()) { await service.initializeTour(event.tour); } // Populate the container with information. If the file did not exist then it // now contains the standard values. container = await service.loadSortingInformation(); bool inconsistent = false; for (final delivery in event.tour.deliveries) { int info = container[delivery.carId.toString()]!.indexWhere( (id) => id == delivery.id, ); // not found, so add it to the list if (info == -1) { inconsistent = true; container[delivery.carId.toString()]!.add(delivery.id); } } // if new deliveries were added then save the information with the newly // populated container if (inconsistent) { await service.saveSortingInformation(container); } } catch (e, st) { debugPrint("Fehler beim Lesen der Datei: $e"); debugPrint("$st"); opBloc.add( FailOperation( message: "Fehler beim Laden der Sortierung. Es wird ohne Sortierung fortgefahren", ), ); // fill the container without sorting information for (final delivery in event.tour.deliveries) { container[delivery.carId.toString()]!.add(delivery.id); } } emit( TourLoaded( tour: event.tour, paymentOptions: event.payments, sortingInformation: container, ), ); add(RequestDeliveryDistanceEvent(tour: event.tour)); } void _updated(TourUpdated event, Emitter emit) { final currentState = state; final tour = event.tour.copyWith(); final payments = event.payments.map((payment) => payment.copyWith()).toList(); if (currentState is TourLoaded) { emit( TourLoaded( tour: tour, paymentOptions: payments, distances: Map.from(currentState.distances ?? {}), sortingInformation: currentState.sortingInformation, pendingScanRequests: currentState.pendingScanRequests, ), ); } if (currentState is TourLoading) { add( RequestSortingInformationEvent(tour: tour.copyWith(), payments: payments), ); } } void _reactivateDelivery( ReactivateDeliveryEvent event, Emitter emit, ) async { final currentState = state; if (currentState is TourLoaded) { opBloc.add(StartOperation()); try { await tourRepository.reactivateDelivery(event.deliveryId); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("$e $st"); _handleError(e, "Fehler beim Zurückstellen der Lieferung"); } } } void _holdDelivery(HoldDeliveryEvent event, Emitter emit) async { final currentState = state; if (currentState is TourLoaded) { opBloc.add(StartOperation()); try { await tourRepository.holdDelivery(event.deliveryId); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("$e $st"); _handleError(e, "Fehler beim Zurückstellen der Lieferung"); } } } void _cancelDelivery( CancelDeliveryEvent event, Emitter emit, ) async { final currentState = state; if (currentState is TourLoaded) { opBloc.add(StartOperation()); try { await tourRepository.cancelDelivery(event.deliveryId); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("$e $st"); _handleError(e, "Fehler beim Stornieren der Lieferung"); } } } void _bumpPendingScans(Emitter emit, int delta) { final currentState = state; if (currentState is TourLoaded) { final next = (currentState.pendingScanRequests + delta).clamp(0, 1 << 30); emit(currentState.copyWith(pendingScanRequests: next)); } } void _scanComponent( ScanComponentEvent event, Emitter emit, ) async { final currentState = state; if (currentState is TourLoaded) { _bumpPendingScans(emit, 1); try { switch (await tourRepository.scanComponent( event.deliveryId, event.carId, event.componentArticleNumber, )) { case ScanResult.scanned: opBloc.add(FinishOperation(message: 'Komponente gescannt')); break; case ScanResult.alreadyScanned: opBloc.add( FailOperation(message: 'Komponente wurde bereits gescannt'), ); break; case ScanResult.notFound: opBloc.add( FailOperation( message: 'Komponente ist für keine Lieferung vorgesehen', ), ); break; } } catch (e, st) { debugPrint("FEHLER beim Scannen einer Komponente: $e $st"); _handleError(e, "Fehler beim Scannen der Komponente"); } finally { _bumpPendingScans(emit, -1); } } } void _scan(ScanArticleEvent event, Emitter emit) async { final currentState = state; if (currentState is TourLoaded) { _bumpPendingScans(emit, 1); try { switch (await tourRepository.scanArticle( event.deliveryId, event.carId, event.articleNumber, )) { case ScanResult.scanned: opBloc.add(FinishOperation(message: 'Artikel gescannt')); break; case ScanResult.alreadyScanned: opBloc.add( FailOperation(message: 'Artikel wurde bereits gescannt'), ); break; case ScanResult.notFound: opBloc.add( FailOperation( message: 'Artikel ist für keine Lieferung vorgesehen', ), ); break; } } catch (e, st) { debugPrint("FEHLER beim Scannen eines Artikels: $e $st"); _handleError(e, "Fehler beim Scannen des Artikels"); } finally { _bumpPendingScans(emit, -1); } } } Future _increment( IncrementArticleScanAmount event, Emitter emit, ) async { final currentState = state; if (currentState is TourLoaded) { _bumpPendingScans(emit, 1); try { await tourRepository.scanArticle( event.deliveryId, event.carId, event.internalArticleId, ); } catch (e, st) { debugPrint("$e $st"); _handleError(e, "Fehler beim Scannen des Artikels"); } finally { _bumpPendingScans(emit, -1); } } } Future _assignCar(AssignCarEvent event, Emitter emit) async { final currentState = state; if (currentState is TourLoaded) { opBloc.add(StartOperation()); try { await tourRepository.assignCar(event.deliveryId, event.carId); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("$e $st"); _handleError(e, "Fehler beim Zuweisen des Fahrzeugs"); } } } Future _load(LoadTour event, Emitter emit) async { try { emit(TourLoading()); await tourRepository.loadTourOfToday(event.teamId); await tourRepository.loadPaymentOptions(); } catch (e) { if (e is UserUnauthorized) { authBloc.add(SessionExpiredEvent()); return; } emit(TourLoadingFailed()); opBloc.add(FailOperation(message: "Fehler beim Laden der heutigen Fahrten")); } } void _finishDelivery( FinishDeliveryEvent event, Emitter emit, ) async { final currentState = state; if (currentState is TourLoaded) { opBloc.add(StartOperation(message: "Lieferung wird abgeschlossen…")); try { await tourRepository.uploadDriverSignature( event.deliveryId, event.driverSignature, ); await tourRepository.uploadCustomerSignature( event.deliveryId, event.customerSignature, ); await tourRepository.finishDelivery(event.deliveryId); opBloc.add(FinishOperation(message: "Lieferung abgeschlossen")); } catch (e, st) { debugPrint("$e $st"); _handleError(e, "Fehler beim Abschließen der Lieferung"); } } } void _updatePayment( UpdateSelectedPaymentMethodEvent event, Emitter emit, ) async { opBloc.add(StartOperation()); try { await tourRepository.updatePayment(event.deliveryId, event.payment); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("$e $st"); _handleError(e, "Fehler beim Aktualisieren des Betrags"); } } void _updateDeliveryOptions( UpdateDeliveryOptionEvent event, Emitter emit, ) async { opBloc.add(StartOperation()); try { await tourRepository.updateOption( event.deliveryId, event.key, event.value, ); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("$e $st"); _handleError(e, "Fehler beim Aktualisieren der Optionen"); } } void _updateDiscount( UpdateDiscountEvent event, Emitter emit, ) async { opBloc.add(StartOperation()); try { await tourRepository.updateDiscount( event.deliveryId, event.reason, event.value, ); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("Fehler beim Aktualisieren des Discounts: $e $st"); _handleError(e, "Fehler beim Aktualisieren des Discounts"); } } void _removeDiscount( RemoveDiscountEvent event, Emitter emit, ) async { opBloc.add(StartOperation()); try { await tourRepository.removeDiscount(event.deliveryId); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("Fehler beim Löschen des Discounts: $e $st"); _handleError(e, "Fehler beim Löschen des Discounts"); } } void _addDiscount(AddDiscountEvent event, Emitter emit) async { opBloc.add(StartOperation()); try { await tourRepository.addDiscount( event.deliveryId, event.reason, event.value, ); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("Fehler beim Hinzufügen des Discounts: $e $st"); _handleError(e, "Fehler beim Hinzufügen des Discounts"); } } void _unscan(UnscanArticleEvent event, Emitter emit) async { opBloc.add(StartOperation()); try { await tourRepository.unscan( event.deliveryId, event.articleId, event.newAmount, event.reason, ); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("Fehler beim Unscan des Artikels ${event.articleId}: $e $st"); _handleError(e, "Fehler beim Unscan des Artikels"); } } void _resetAmount(ResetScanAmountEvent event, Emitter emit) async { opBloc.add(StartOperation()); try { await tourRepository.resetScan(event.articleId, event.deliveryId); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("Fehler beim Zurücksetzen Artikel ${event.articleId}: $e $st"); _handleError(e, "Fehler beim Zurücksetzen"); } } }