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/overview/model/sorting_information.dart'; import 'package:hl_lieferservice/feature/delivery/overview/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/model/tour.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; TourRepository tourRepository; StreamSubscription? _combinedSubscription; TourBloc({required this.opBloc, 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(_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); } @override Future close() { _combinedSubscription?.cancel(); return super.close(); } 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) { int newPosition = event.newPosition == currentState.sortingInformation.sorting.length ? event.newPosition - 1 : event.newPosition; SortingInformation informationOld = currentState.sortingInformation.sorting .firstWhere((info) => info.position == event.oldPosition); SortingInformation information = currentState .sortingInformation .sorting .firstWhere((info) => info.position == newPosition); information.position = event.oldPosition; informationOld.position = newPosition; await ReorderService().saveSortingInformation( currentState.sortingInformation, ); emit( currentState.copyWith( sortingInformation: currentState.sortingInformation.copyWith(), ), ); } } void _calculateDistances( RequestDeliveryDistanceEvent event, Emitter emit, ) async { Map distances = {}; opBloc.add(LoadOperation()); emit(TourRequestingDistances(tour: event.tour, payments: event.payments)); try { for (final delivery in event.tour.deliveries) { distances[delivery.id] = await DistanceService.getDistanceByRoad( delivery.customer.address.toString(), ); } opBloc.add(FinishOperation()); } catch (e) { debugPrint("Fehler beim Berechnen der Distanzen: $e"); opBloc.add(FailOperation(message: "Fehler beim Berechnen der Distanzen")); return; } finally { // Independent of error state fetch the sorting information add( RequestSortingInformationEvent( tour: event.tour, payments: event.payments, distances: distances, ), ); } } void _requestSortingInformation( RequestSortingInformationEvent event, Emitter emit, ) async { try { ReorderService service = ReorderService(); SortingInformationContainer container = SortingInformationContainer( sorting: [], ); // 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.sorting.indexWhere( (info) => info.deliveryId == delivery.id, ); int max = container.sorting.fold(0, (acc, element) { if (element.position > acc) { return element.position; } return acc; }); // not found, so add it to the list if (info == -1) { inconsistent = true; container.sorting.add( SortingInformation( deliveryId: delivery.id, position: container.sorting.isEmpty ? 0 : max + 1, ), ); } } // if new deliveries were added then save the information with the newly // populated container if (inconsistent) { await service.saveSortingInformation(container); } emit( TourLoaded( tour: event.tour, paymentOptions: event.payments, sortingInformation: container, distances: event.distances, ), ); } 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", ), ); SortingInformationContainer container = SortingInformationContainer( sorting: [], ); for (final (index, delivery) in event.tour.deliveries.indexed) { container.sorting.add( SortingInformation(deliveryId: delivery.id, position: index), ); } emit( TourLoaded( tour: event.tour, paymentOptions: event.payments, sortingInformation: container, distances: event.distances, ), ); } } 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, ), ); } // Download distances if tour has previously fetched by API if (currentState is TourLoading) { add( RequestDeliveryDistanceEvent(tour: tour.copyWith(), payments: payments), ); } } void _reactivateDelivery( ReactivateDeliveryEvent event, Emitter emit, ) async { final currentState = state; if (currentState is TourLoaded) { opBloc.add(LoadOperation()); try { await tourRepository.reactivateDelivery(event.deliveryId); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("$e"); debugPrint("$st"); opBloc.add( FailOperation(message: "Fehler beim Zurückstellen der Lieferung"), ); } } } void _holdDelivery(HoldDeliveryEvent event, Emitter emit) async { final currentState = state; if (currentState is TourLoaded) { opBloc.add(LoadOperation()); try { await tourRepository.holdDelivery(event.deliveryId); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("$e"); debugPrint("$st"); opBloc.add( FailOperation(message: "Fehler beim Zurückstellen der Lieferung"), ); } } } void _cancelDelivery( CancelDeliveryEvent event, Emitter emit, ) async { final currentState = state; if (currentState is TourLoaded) { opBloc.add(LoadOperation()); try { await tourRepository.cancelDelivery(event.deliveryId); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("$e"); debugPrint("$st"); opBloc.add( FailOperation(message: "Fehler beim Zurückstellen der Lieferung"), ); } } } void _scan(ScanArticleEvent event, Emitter emit) async { final currentState = state; opBloc.add(LoadOperation()); if (currentState is TourLoaded) { 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; } } on TourNotFoundException { opBloc.add(FailOperation(message: "Fehler beim Scannen des Artikels")); } } } Future _increment( IncrementArticleScanAmount event, Emitter emit, ) async { final currentState = state; if (currentState is TourLoaded) { opBloc.add(LoadOperation()); try { await tourRepository.scanArticle( event.deliveryId, event.carId, event.internalArticleId, ); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint(st.toString()); opBloc.add(FailOperation(message: "Fehler beim Scannen des Artikels")); } } } Future _assignCar(AssignCarEvent event, Emitter emit) async { final currentState = state; if (currentState is TourLoaded) { opBloc.add(LoadOperation()); try { await tourRepository.assignCar(event.deliveryId, event.carId); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint(st.toString()); opBloc.add( FailOperation(message: "Fehler beim Zuweisen des Fahrzeugs"), ); } } } Future _load(LoadTour event, Emitter emit) async { opBloc.add(LoadOperation()); try { emit(TourLoading()); await tourRepository.loadTourOfToday(event.teamId); await tourRepository.loadPaymentOptions(); opBloc.add(FinishOperation()); } catch (e) { opBloc.add( FailOperation(message: "Fehler beim Laden der heutigen Fahrten"), ); } } void _finishDelivery( FinishDeliveryEvent event, Emitter emit, ) async { final currentState = state; opBloc.add(LoadOperation()); if (currentState is TourLoaded) { try { await tourRepository.uploadDriverSignature( event.deliveryId, event.driverSignature, ); await tourRepository.uploadCustomerSignature( event.deliveryId, event.customerSignature, ); await tourRepository.finishDelivery(event.deliveryId); opBloc.add(FinishOperation()); } catch (e, st) { opBloc.add(FailOperation(message: "Failed to update delivery")); debugPrint(st.toString()); } } } void _updatePayment( UpdateSelectedPaymentMethodEvent event, Emitter emit, ) async { try { opBloc.add(LoadOperation()); await tourRepository.updatePayment(event.deliveryId, event.payment); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint(st.toString()); opBloc.add( FailOperation(message: "Fehler beim Aktualisieren des Betrags"), ); } } void _updateDeliveryOptions( UpdateDeliveryOptionEvent event, Emitter emit, ) async { try { opBloc.add(LoadOperation()); await tourRepository.updateOption( event.deliveryId, event.key, event.value, ); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("$st"); opBloc.add( FailOperation(message: "Fehler beim Aktualisieren der Optionen"), ); } } void _updateDiscount( UpdateDiscountEvent event, Emitter emit, ) async { opBloc.add(LoadOperation()); try { opBloc.add(FinishOperation()); await tourRepository.updateDiscount( event.deliveryId, event.reason, event.value, ); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint( "Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event.deliveryId}:", ); debugPrint("$e"); debugPrint("$st"); opBloc.add( FailOperation(message: "Fehler beim Hinzufügen des Discounts: $e"), ); } } void _removeDiscount( RemoveDiscountEvent event, Emitter emit, ) async { opBloc.add(LoadOperation()); try { await tourRepository.removeDiscount(event.deliveryId); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint( "Fehler beim Löschen des Discounts der Lieferung: ${event.deliveryId}:", ); debugPrint("$e"); debugPrint("$st"); opBloc.add( FailOperation(message: "Fehler beim Löschen des Discounts: $e"), ); } } void _addDiscount(AddDiscountEvent event, Emitter emit) async { opBloc.add(LoadOperation()); try { await tourRepository.addDiscount( event.deliveryId, event.reason, event.value, ); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint( "Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event.deliveryId}:", ); debugPrint("$e"); debugPrint("$st"); opBloc.add( FailOperation(message: "Fehler beim Hinzufügen des Discounts: $e"), ); } } void _unscan(UnscanArticleEvent event, Emitter emit) async { opBloc.add(LoadOperation()); 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}:"); debugPrint("$e"); debugPrint("$st"); opBloc.add(FailOperation(message: "Fehler beim Unscan des Artikels: $e")); } } void _resetAmount(ResetScanAmountEvent event, Emitter emit) async { opBloc.add(LoadOperation()); try { await tourRepository.resetScan(event.articleId, event.deliveryId); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("Fehler beim Unscan des Artikels: ${event.articleId}:"); debugPrint("$e"); debugPrint("$st"); opBloc.add(FailOperation(message: "Fehler beim Zurücksetzen: $e")); } } }