diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..4b8d8ee --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,68 @@ +PODS: + - app_links (6.4.1): + - Flutter + - Flutter (1.0.0) + - geolocator_apple (1.2.0): + - Flutter + - FlutterMacOS + - image_picker_ios (0.0.1): + - Flutter + - mobile_scanner (7.0.0): + - Flutter + - FlutterMacOS + - package_info_plus (0.4.5): + - Flutter + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - url_launcher_ios (0.0.1): + - Flutter + +DEPENDENCIES: + - app_links (from `.symlinks/plugins/app_links/ios`) + - Flutter (from `Flutter`) + - geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + +EXTERNAL SOURCES: + app_links: + :path: ".symlinks/plugins/app_links/ios" + Flutter: + :path: Flutter + geolocator_apple: + :path: ".symlinks/plugins/geolocator_apple/darwin" + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" + mobile_scanner: + :path: ".symlinks/plugins/mobile_scanner/darwin" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + +SPEC CHECKSUMS: + app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e + image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a + mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + +PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e + +COCOAPODS: 1.16.2 diff --git a/lib/dto/delivery.dart b/lib/dto/delivery.dart index dcfab01..f8b7df8 100644 --- a/lib/dto/delivery.dart +++ b/lib/dto/delivery.dart @@ -1,3 +1,5 @@ +import 'package:hl_lieferservice/dto/image_note_response.dart'; + import 'article.dart'; import 'contact_person.dart'; import 'customer.dart'; @@ -73,7 +75,7 @@ class DeliveryDTO { DiscountDTO? discount; PaymentMethodDTO payment; List notes; - List images; + List images; List options; factory DeliveryDTO.fromJson(Map json) => diff --git a/lib/dto/delivery.g.dart b/lib/dto/delivery.g.dart index 1d912a1..03a4c29 100644 --- a/lib/dto/delivery.g.dart +++ b/lib/dto/delivery.g.dart @@ -45,7 +45,7 @@ DeliveryDTO _$DeliveryDTOFromJson(Map json) => DeliveryDTO( totalGrossValue: json['total_gross_value'] as String, images: (json['images'] as List) - .map((e) => ImageDTO.fromJson(e as Map)) + .map((e) => ImageNoteDTO.fromJson(e as Map)) .toList(), customer: CustomerDTO.fromJson(json['customer'] as Map), finishedTime: json['finished_time'] as String, diff --git a/lib/dto/image_note_response.dart b/lib/dto/image_note_response.dart new file mode 100644 index 0000000..f417809 --- /dev/null +++ b/lib/dto/image_note_response.dart @@ -0,0 +1,18 @@ +import 'note.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'image_note_response.g.dart'; + +@JsonSerializable(fieldRename: FieldRename.snake) +class ImageNoteDTO { + final String url; + final String oid; + final String name; + + ImageNoteDTO({required this.url, required this.oid, required this.name}); + + factory ImageNoteDTO.fromJson(Map json) => + _$ImageNoteDTOFromJson(json); + + Map toJson() => _$ImageNoteDTOToJson(this); +} diff --git a/lib/dto/image_note_response.g.dart b/lib/dto/image_note_response.g.dart new file mode 100644 index 0000000..594c093 --- /dev/null +++ b/lib/dto/image_note_response.g.dart @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'image_note_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ImageNoteDTO _$ImageNoteDTOFromJson(Map json) => ImageNoteDTO( + url: json['url'] as String, + oid: json['oid'] as String, + name: json['name'] as String, +); + +Map _$ImageNoteDTOToJson(ImageNoteDTO instance) => + { + 'url': instance.url, + 'oid': instance.oid, + 'name': instance.name, + }; diff --git a/lib/dto/note_get_response.dart b/lib/dto/note_get_response.dart index a1c7ad4..6181b65 100644 --- a/lib/dto/note_get_response.dart +++ b/lib/dto/note_get_response.dart @@ -1,3 +1,5 @@ +import 'package:hl_lieferservice/dto/image_note_response.dart'; + import 'note.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -6,9 +8,10 @@ part 'note_get_response.g.dart'; @JsonSerializable(fieldRename: FieldRename.snake) class NoteGetResponseDTO { NoteGetResponseDTO( - {required this.notes, required this.succeeded, required this.message}); + {required this.notes, required this.succeeded, required this.message, required this.images}); final List notes; + final List images; final bool succeeded; final String message; diff --git a/lib/dto/note_get_response.g.dart b/lib/dto/note_get_response.g.dart index c956860..7e81213 100644 --- a/lib/dto/note_get_response.g.dart +++ b/lib/dto/note_get_response.g.dart @@ -14,11 +14,16 @@ NoteGetResponseDTO _$NoteGetResponseDTOFromJson(Map json) => .toList(), succeeded: json['succeeded'] as bool, message: json['message'] as String, + images: + (json['images'] as List) + .map((e) => ImageNoteDTO.fromJson(e as Map)) + .toList(), ); Map _$NoteGetResponseDTOToJson(NoteGetResponseDTO instance) => { 'notes': instance.notes, + 'images': instance.images, 'succeeded': instance.succeeded, 'message': instance.message, }; diff --git a/lib/feature/cars/presentation/car_management_page.dart b/lib/feature/cars/presentation/car_management_page.dart index fb6f0eb..fb6ed1e 100644 --- a/lib/feature/cars/presentation/car_management_page.dart +++ b/lib/feature/cars/presentation/car_management_page.dart @@ -56,10 +56,14 @@ class _CarManagementPageState extends State { return Scaffold( body: BlocConsumer( listener: (context, state) { - if (state is CarsLoaded && context.read().state is TourLoaded) { - var tour = (context.read().state as TourLoaded).tour.copyWith(); + if (state is CarsLoaded && + context.read().state is TourLoaded) { + var tour = + (context.read().state as TourLoaded).tour.copyWith(); tour.driver.cars = state.cars; - context.read().add(UpdateTour(tour: tour)); + context.read().add( + TourUpdated(tour: tour, payments: tour.paymentMethods), + ); } }, builder: (context, state) { diff --git a/lib/feature/delivery/detail/bloc/delivery_bloc.dart b/lib/feature/delivery/detail/bloc/delivery_bloc.dart deleted file mode 100644 index 8366063..0000000 --- a/lib/feature/delivery/detail/bloc/delivery_bloc.dart +++ /dev/null @@ -1,353 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:hl_lieferservice/dto/discount_add_response.dart'; -import 'package:hl_lieferservice/dto/discount_update_response.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_state.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_event.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/repository/delivery_repository.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/repository/note_repository.dart'; -import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart'; -import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart'; - -import '../../../../dto/discount_remove_response.dart'; -import '../../../../model/article.dart'; -import '../../../../model/delivery.dart' as model; - -class DeliveryBloc extends Bloc { - OperationBloc opBloc; - NoteBloc noteBloc; - DeliveryRepository repository; - NoteRepository noteRepository; - - DeliveryBloc({ - required this.opBloc, - required this.repository, - required this.noteRepository, - required this.noteBloc - }) : super(DeliveryInitial()) { - on(_unscan); - on(_resetAmount); - on(_load); - on(_addDiscount); - on(_removeDiscount); - on(_updateDiscount); - on(_updateDeliveryOptions); - on(_updatePayment); - on(_finishDelivery); - } - - void _finishDelivery(FinishDeliveryEvent event, - Emitter emit,) async { - final currentState = state; - opBloc.add(LoadOperation()); - - if (currentState is DeliveryLoaded) { - try { - model.Delivery newDelivery = event.delivery.copyWith(); - newDelivery.state = model.DeliveryState.finished; - - for (final option in event.delivery.options) { - debugPrint("VALUE=${option.value};KEY=${option.key}"); - } - - await repository.updateDelivery(newDelivery); - await noteRepository.addNamedImage( - event.delivery.id, - event.driverSignature, - "delivery_${event.delivery.id}_signature_driver.jpg", - ); - await noteRepository.addNamedImage( - event.delivery.id, - event.customerSignature, - "delivery_${event.delivery.id}_signature_customer.jpg", - ); - emit(DeliveryFinished(delivery: newDelivery)); - opBloc.add(FinishOperation()); - } catch (e, st) { - opBloc.add(FailOperation(message: "Failed to update delivery")); - debugPrint(st.toString()); - } - } - } - - void _updatePayment(UpdateSelectedPaymentMethodEvent event, - Emitter emit,) { - final currentState = state; - - if (currentState is DeliveryLoaded) { - emit( - DeliveryLoaded( - delivery: currentState.delivery.copyWith(payment: event.payment), - ), - ); - } - } - - void _updateDeliveryOptions(UpdateDeliveryOptionEvent event, - Emitter emit,) { - final currentState = state; - - if (currentState is DeliveryLoaded) { - List options = - currentState.delivery.options.map((option) { - if (option.key == event.key) { - if (option.numerical) { - return option.copyWith(value: event.value); - } else { - return option.copyWith(value: event.value == true ? "1" : "0"); - } - } - - return option; - }).toList(); - - emit( - DeliveryLoaded( - delivery: currentState.delivery.copyWith(options: options), - ), - ); - } - } - - void _updateDiscount(UpdateDiscountEvent event, - Emitter emit,) async { - opBloc.add(LoadOperation()); - - try { - final currentState = state; - - if (currentState is DeliveryLoaded) { - DiscountUpdateResponseDTO response = await repository.updateDiscount( - event.deliveryId, - event.reason, - event.value, - ); - - model.Delivery delivery = currentState.delivery; - - if (response.values?.receipt != null) { - delivery.totalNetValue = response.values!.receipt.net; - delivery.totalGrossValue = response.values!.receipt.gross; - } - - String discountArticleNumber = delivery.discount!.article.articleNumber; - delivery.discount = model.Discount( - article: - response.values?.article != null - ? Article.fromDTO(response.values!.article) - : delivery.discount!.article, - note: - response.values?.note != null - ? response.values!.note.noteDescription - : delivery.discount!.note, - noteId: - response.values?.note != null - ? response.values!.note.rowId - : delivery.discount!.noteId, - ); - - delivery.articles = [ - ...delivery.articles.where( - (article) => article.articleNumber != discountArticleNumber, - ), - delivery.discount!.article, - ]; - - emit(currentState.copyWith(delivery)); - - 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 { - final currentState = state; - - if (currentState is DeliveryLoaded) { - model.Delivery delivery = currentState.delivery; - - DiscountRemoveResponseDTO response = await repository.removeDiscount( - event.deliveryId, - ); - - delivery.articles = - delivery.articles - .where( - (article) => - article.internalId != - delivery.discount?.article.internalId, - ) - .toList(); - - delivery.discount = null; - delivery.totalGrossValue = response.receipt.gross; - delivery.totalNetValue = response.receipt.net; - - emit(currentState.copyWith(delivery)); - - 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 { - final currentState = state; - - if (currentState is DeliveryLoaded) { - DiscountAddResponseDTO response = await repository.addDiscount( - event.deliveryId, - event.reason, - event.value, - ); - - model.Delivery delivery = currentState.delivery; - delivery.totalNetValue = response.values.receipt.net; - delivery.totalGrossValue = response.values.receipt.gross; - - delivery.discount = model.Discount( - article: Article.fromDTO(response.values.article), - note: response.values.note.noteDescription, - noteId: response.values.note.rowId, - ); - - noteBloc.add(AddNoteOffline(note: response.values.note.noteDescription, - deliveryId: delivery.id, - noteId: response.values.note.rowId)); - - delivery.articles = [...delivery.articles, delivery.discount!.article]; - - emit(currentState.copyWith(delivery)); - - 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 _load(LoadDeliveryEvent event, Emitter emit) async { - debugPrint("Discount; ${event.delivery.discount?.note}"); - emit(DeliveryLoaded(delivery: event.delivery)); - } - - void _unscan(UnscanArticleEvent event, Emitter emit) async { - opBloc.add(LoadOperation()); - - try { - String? noteId = await repository.unscan( - event.articleId, - event.newAmount, - event.reason, - ); - - if (noteId != null) { - final currentState = state; - - if (currentState is DeliveryLoaded) { - Article article = currentState.delivery.articles.firstWhere( - (article) => article.internalId == int.parse(event.articleId), - ); - - article.removeNoteId = noteId; - article.scannedRemovedAmount += event.newAmount; - article.scannedAmount -= event.newAmount; - - List
articles = [ - ...currentState.delivery.articles.where( - (article) => article.internalId != int.parse(event.articleId), - ), - article, - ]; - currentState.delivery.articles = articles; - - emit.call(currentState.copyWith(currentState.delivery)); - } - } - - 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 { - final currentState = state; - await repository.resetScan(event.articleId); - - if (currentState is DeliveryLoaded) { - Article article = currentState.delivery.articles.firstWhere( - (article) => article.internalId == int.parse(event.articleId), - ); - - article.removeNoteId = null; - article.scannedRemovedAmount = 0; - article.scannedAmount = article.amount; - - List
articles = [ - ...currentState.delivery.articles.where( - (article) => article.internalId != int.parse(event.articleId), - ), - article, - ]; - currentState.delivery.articles = articles; - - emit.call(currentState.copyWith(currentState.delivery)); - } - - 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")); - } - } -} diff --git a/lib/feature/delivery/detail/bloc/delivery_event.dart b/lib/feature/delivery/detail/bloc/delivery_event.dart deleted file mode 100644 index 8b43caf..0000000 --- a/lib/feature/delivery/detail/bloc/delivery_event.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'dart:typed_data'; - -import 'package:hl_lieferservice/model/delivery.dart'; -import 'package:hl_lieferservice/model/tour.dart'; - -abstract class DeliveryEvent {} - -class LoadDeliveryEvent extends DeliveryEvent { - LoadDeliveryEvent({required this.delivery}); - - Delivery delivery; -} - -class UnscanArticleEvent extends DeliveryEvent { - UnscanArticleEvent({ - required this.articleId, - required this.newAmount, - required this.reason, - }); - - String articleId; - String reason; - int newAmount; -} - -class ResetScanAmountEvent extends DeliveryEvent { - ResetScanAmountEvent({required this.articleId}); - - String articleId; -} - -class AddDiscountEvent extends DeliveryEvent { - AddDiscountEvent({ - required this.deliveryId, - required this.value, - required this.reason, - }); - - String deliveryId; - String reason; - int value; -} - -class RemoveDiscountEvent extends DeliveryEvent { - RemoveDiscountEvent({required this.deliveryId}); - - String deliveryId; -} - -class UpdateDiscountEvent extends DeliveryEvent { - UpdateDiscountEvent({ - required this.deliveryId, - required this.value, - required this.reason, - }); - - String deliveryId; - String? reason; - int? value; -} - -class UpdateDeliveryOptionEvent extends DeliveryEvent { - UpdateDeliveryOptionEvent({required this.key, required this.value}); - - String key; - dynamic value; -} - -class UpdateSelectedPaymentMethodEvent extends DeliveryEvent { - UpdateSelectedPaymentMethodEvent({required this.payment}); - - Payment payment; -} - -class FinishDeliveryEvent extends DeliveryEvent { - FinishDeliveryEvent({ - required this.delivery, - required this.driverSignature, - required this.customerSignature, - }); - - Delivery delivery; - Uint8List customerSignature; - Uint8List driverSignature; -} \ No newline at end of file diff --git a/lib/feature/delivery/detail/bloc/delivery_state.dart b/lib/feature/delivery/detail/bloc/delivery_state.dart deleted file mode 100644 index 4d1f8f3..0000000 --- a/lib/feature/delivery/detail/bloc/delivery_state.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:hl_lieferservice/model/delivery.dart'; - -abstract class DeliveryState {} - -class DeliveryInitial extends DeliveryState {} - -class DeliveryLoaded extends DeliveryState { - DeliveryLoaded({required this.delivery}); - - Delivery delivery; - - DeliveryLoaded copyWith(Delivery? delivery) { - return DeliveryLoaded(delivery: delivery ?? this.delivery); - } -} - -class DeliveryFinished extends DeliveryState { - DeliveryFinished({required this.delivery}); - - Delivery delivery; - - DeliveryFinished copyWith(Delivery? delivery) { - return DeliveryFinished(delivery: delivery ?? this.delivery); - } -} diff --git a/lib/feature/delivery/detail/bloc/note_bloc.dart b/lib/feature/delivery/detail/bloc/note_bloc.dart index 6bb7783..36ff9d7 100644 --- a/lib/feature/delivery/detail/bloc/note_bloc.dart +++ b/lib/feature/delivery/detail/bloc/note_bloc.dart @@ -1,8 +1,8 @@ +import 'dart:async'; import 'dart:typed_data'; import 'package:flutter/cupertino.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:hl_lieferservice/model/delivery.dart'; import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart'; import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart'; @@ -13,9 +13,23 @@ import 'package:hl_lieferservice/feature/delivery/detail/repository/note_reposit class NoteBloc extends Bloc { final NoteRepository repository; final OperationBloc opBloc; + final String deliveryId; - NoteBloc({required this.repository, required this.opBloc}) + StreamSubscription? _noteSubscription; + StreamSubscription? _imageSubscription; + + NoteBloc({required this.repository, required this.opBloc, required this.deliveryId}) : super(NoteInitial()) { + repository.loadNotes(deliveryId); + + _noteSubscription = repository.notes.listen((notes) { + add(NotesUpdated(notes: notes)); + }); + + _imageSubscription = repository.images.listen((images) { + add(ImageUpdated(images: images)); + }); + on(_load); on(_add); on(_edit); @@ -23,61 +37,41 @@ class NoteBloc extends Bloc { on(_upload); on(_removeImage); on(_reset); - on(_addOffline); + on(_noteUpdated); + on(_imageUpdated); + } + + @override + Future close() { + _noteSubscription?.cancel(); + _imageSubscription?.cancel(); + + return super.close(); + } + + Future _imageUpdated(ImageUpdated event, Emitter emit) async { + final currentState = state; + + if (currentState is NoteLoaded) { + emit.call(currentState.copyWith(images: event.images)); + } + } + + Future _noteUpdated(NotesUpdated event, Emitter emit) async { + emit(NoteLoaded(notes: event.notes)); } Future _reset(ResetNotes event, Emitter emit) async { emit.call(NoteInitial()); } - Future _addOffline(AddNoteOffline event, - Emitter emit,) async { - if (state is NoteInitial) { - emit( - NoteLoadedBase( - notes: [Note(content: event.note, id: int.parse(event.noteId))], - ), - ); - } - - if (state is NoteLoadedBase) { - emit( - NoteLoadedBase( - notes: [ - ...(state as NoteLoadedBase).notes, - Note(content: event.note, id: int.parse(event.noteId)), - ], - ), - ); - } - - if (state is NoteLoaded) { - final current = state as NoteLoaded; - emit(NoteLoaded(notes: [...current.notes, Note(content: event.note, id: int.parse(event.noteId))], - templates: [...current.templates], - images: [...current.images])); - } - } - Future _removeImage(RemoveImageNote event, Emitter emit,) async { opBloc.add(LoadOperation()); try { - final currentState = state; await repository.deleteImage(event.deliveryId, event.objectId); - if (currentState is NoteLoaded) { - emit.call( - currentState.copyWith( - images: - currentState.images - .where((image) => image.$1.objectId != event.objectId) - .toList(), - ), - ); - } - opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("Fehler beim Löschen des Bildes: $e"); @@ -91,18 +85,8 @@ class NoteBloc extends Bloc { opBloc.add(LoadOperation()); try { - final currentState = state; Uint8List imageBytes = await event.file.readAsBytes(); - ImageNote note = await repository.addImage(event.deliveryId, imageBytes); - - if (currentState is NoteLoaded) { - emit.call( - currentState.copyWith( - images: [...currentState.images, (note, imageBytes)], - ), - ); - } - + await repository.addImage(event.deliveryId, imageBytes); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("Fehler beim Hinzufügen des Bildes: $e"); @@ -118,20 +102,12 @@ class NoteBloc extends Bloc { try { List urls = event.delivery.images.map((image) => image.url).toList(); - List notes = await repository.loadNotes(event.delivery.id); - List templates = await repository.loadTemplates(); - List images = await repository.loadImages(urls); - emit.call( - NoteLoaded( - notes: notes, - templates: templates, - images: List.generate( - images.length, - (index) => (event.delivery.images[index], images[index]), - ), - ), - ); + debugPrint("IMAGE URLS : $urls"); + + await repository.loadNotes(event.delivery.id); + await repository.loadTemplates(); + opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("Fehler beim Herunterladen der Notizen: $e"); @@ -149,14 +125,7 @@ class NoteBloc extends Bloc { opBloc.add(LoadOperation()); try { - final currentState = state; - Note note = await repository.addNote(event.deliveryId, event.note); - - if (currentState is NoteLoaded) { - List refreshedNotes = [...currentState.notes, note]; - emit.call(currentState.copyWith(notes: refreshedNotes)); - } - + await repository.addNote(event.deliveryId, event.note); opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("Fehler beim Hinzufügen der Notiz: $e"); @@ -170,19 +139,7 @@ class NoteBloc extends Bloc { opBloc.add(LoadOperation()); try { - final currentState = state; await repository.editNote(event.noteId, event.content); - - if (currentState is NoteLoaded) { - List refreshedNotes = [ - ...currentState.notes.where( - (note) => note.id != int.parse(event.noteId), - ), - Note(content: event.content, id: int.parse(event.noteId)), - ]; - emit.call(currentState.copyWith(notes: refreshedNotes)); - } - opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("Fehler beim Hinzufügen der Notiz: $e"); @@ -196,18 +153,7 @@ class NoteBloc extends Bloc { opBloc.add(LoadOperation()); try { - final currentState = state; await repository.deleteNote(event.noteId); - - if (currentState is NoteLoaded) { - List refreshedNotes = - currentState.notes - .where((note) => note.id != int.parse(event.noteId)) - .toList(); - - emit.call(currentState.copyWith(notes: refreshedNotes)); - } - opBloc.add(FinishOperation()); } catch (e, st) { debugPrint("Fehler beim Hinzufügen der Notiz: $e"); diff --git a/lib/feature/delivery/detail/bloc/note_event.dart b/lib/feature/delivery/detail/bloc/note_event.dart index a69bd5d..2e191dd 100644 --- a/lib/feature/delivery/detail/bloc/note_event.dart +++ b/lib/feature/delivery/detail/bloc/note_event.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:hl_lieferservice/model/delivery.dart'; import 'package:image_picker/image_picker.dart'; @@ -51,4 +53,16 @@ class RemoveImageNote extends NoteEvent { final String objectId; final String deliveryId; +} + +class NotesUpdated extends NoteEvent { + final List notes; + + NotesUpdated({required this.notes}); +} + +class ImageUpdated extends NoteEvent { + final List images; + + ImageUpdated({required this.images}); } \ No newline at end of file diff --git a/lib/feature/delivery/detail/bloc/note_state.dart b/lib/feature/delivery/detail/bloc/note_state.dart index ca9e7a5..f5a279c 100644 --- a/lib/feature/delivery/detail/bloc/note_state.dart +++ b/lib/feature/delivery/detail/bloc/note_state.dart @@ -20,18 +20,18 @@ class NoteLoadedBase extends NoteState { class NoteLoaded extends NoteLoadedBase { NoteLoaded({ - required this.templates, - required this.images, + this.templates, + this.images, required super.notes, }); - List templates; - List<(ImageNote, Uint8List)> images; + List? templates; + List? images; NoteLoaded copyWith({ List? notes, List? templates, - List<(ImageNote, Uint8List)>? images, + List? images, }) { return NoteLoaded( notes: notes ?? this.notes, diff --git a/lib/feature/delivery/detail/model/note.dart b/lib/feature/delivery/detail/model/note.dart index 229d276..b625c8a 100644 --- a/lib/feature/delivery/detail/model/note.dart +++ b/lib/feature/delivery/detail/model/note.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:hl_lieferservice/model/article.dart'; import 'package:hl_lieferservice/model/delivery.dart'; @@ -6,4 +8,4 @@ class NoteInformation { Note note; Article? article; -} +} \ No newline at end of file diff --git a/lib/feature/delivery/detail/presentation/article/article_list_item.dart b/lib/feature/delivery/detail/presentation/article/article_list_item.dart index d995b85..725873e 100644 --- a/lib/feature/delivery/detail/presentation/article/article_list_item.dart +++ b/lib/feature/delivery/detail/presentation/article/article_list_item.dart @@ -43,7 +43,11 @@ class _ArticleListItem extends State { onPressed: () { showDialog( context: context, - builder: (context) => ArticleUnscanDialog(article: widget.article), + builder: + (context) => ArticleUnscanDialog( + article: widget.article, + deliveryId: widget.deliveryId, + ), ); }, icon: Icon( @@ -61,7 +65,10 @@ class _ArticleListItem extends State { showDialog( context: context, builder: - (context) => ResetArticleAmountDialog(article: widget.article), + (context) => ResetArticleAmountDialog( + article: widget.article, + deliveryId: widget.deliveryId, + ), ); }, icon: Icon( diff --git a/lib/feature/delivery/detail/presentation/article/article_reset_scan_dialog.dart b/lib/feature/delivery/detail/presentation/article/article_reset_scan_dialog.dart index 57614d7..08bd15d 100644 --- a/lib/feature/delivery/detail/presentation/article/article_reset_scan_dialog.dart +++ b/lib/feature/delivery/detail/presentation/article/article_reset_scan_dialog.dart @@ -1,14 +1,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart'; +import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_bloc.dart'; +import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_event.dart'; import '../../../../../model/article.dart'; class ResetArticleAmountDialog extends StatefulWidget { - const ResetArticleAmountDialog({super.key, required this.article}); + const ResetArticleAmountDialog({ + super.key, + required this.article, + required this.deliveryId, + }); final Article article; + final String deliveryId; @override State createState() => _ResetArticleAmountDialogState(); @@ -16,8 +21,11 @@ class ResetArticleAmountDialog extends StatefulWidget { class _ResetArticleAmountDialogState extends State { void _reset() { - context.read().add( - ResetScanAmountEvent(articleId: widget.article.internalId.toString()), + context.read().add( + ResetScanAmountEvent( + articleId: widget.article.internalId.toString(), + deliveryId: widget.deliveryId, + ), ); Navigator.pop(context); diff --git a/lib/feature/delivery/detail/presentation/article/article_unscan_dialog.dart b/lib/feature/delivery/detail/presentation/article/article_unscan_dialog.dart index f496377..3d4852e 100644 --- a/lib/feature/delivery/detail/presentation/article/article_unscan_dialog.dart +++ b/lib/feature/delivery/detail/presentation/article/article_unscan_dialog.dart @@ -1,14 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart'; +import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_bloc.dart'; +import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_event.dart'; import '../../../../../model/article.dart'; class ArticleUnscanDialog extends StatefulWidget { - const ArticleUnscanDialog({super.key, required this.article}); + const ArticleUnscanDialog({super.key, required this.article, required this.deliveryId}); + final String deliveryId; final Article article; @override @@ -22,8 +23,9 @@ class _ArticleUnscanDialogState extends State { final _formKey = GlobalKey(); void _unscan() { - context.read().add( + context.read().add( UnscanArticleEvent( + deliveryId: widget.deliveryId, articleId: widget.article.internalId.toString(), newAmount: int.parse(unscanAmountController.text), reason: unscanNoteController.text, diff --git a/lib/feature/delivery/detail/presentation/delivery_detail_page.dart b/lib/feature/delivery/detail/presentation/delivery_detail_page.dart index c925ee9..435325c 100644 --- a/lib/feature/delivery/detail/presentation/delivery_detail_page.dart +++ b/lib/feature/delivery/detail/presentation/delivery_detail_page.dart @@ -3,23 +3,19 @@ import 'dart:typed_data'; import 'package:easy_stepper/easy_stepper.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_state.dart'; import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_bloc.dart'; import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_event.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_state.dart'; import 'package:hl_lieferservice/feature/delivery/detail/presentation/delivery_sign.dart'; import 'package:hl_lieferservice/feature/delivery/detail/presentation/steps/step.dart'; import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_bloc.dart'; import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_event.dart'; import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_state.dart'; -import 'package:hl_lieferservice/model/delivery.dart' as model; +import 'package:hl_lieferservice/model/delivery.dart'; class DeliveryDetail extends StatefulWidget { - final model.Delivery delivery; + final String deliveryId; - const DeliveryDetail({super.key, required this.delivery}); + const DeliveryDetail({super.key, required this.deliveryId}); @override State createState() => _DeliveryDetailState(); @@ -33,11 +29,6 @@ class _DeliveryDetailState extends State { void initState() { super.initState(); - // Initialize BLOC - context.read().add( - LoadDeliveryEvent(delivery: widget.delivery), - ); - // Reset Note BLOC // otherwise the notes of the previously // opened delivery would be loaded @@ -124,28 +115,27 @@ class _DeliveryDetailState extends State { } } - void _openSignatureView() { + void _openSignatureView(Delivery delivery) { Navigator.of(context).push( MaterialPageRoute( builder: (context) => - SignatureView(onSigned: _onSign, delivery: widget.delivery), + SignatureView(onSigned: _onSign, delivery: delivery), ), ); } void _onSign(Uint8List customer, Uint8List driver) async { - final currentState = context.read().state as DeliveryLoaded; - context.read().add( + context.read().add( FinishDeliveryEvent( - delivery: currentState.delivery, + deliveryId: widget.deliveryId, customerSignature: customer, driverSignature: driver, ), ); } - Widget _stepsNavigation() { + Widget _stepsNavigation(Delivery delivery) { return SizedBox( width: double.infinity, height: 90, @@ -160,9 +150,9 @@ class _DeliveryDetailState extends State { padding: const EdgeInsets.only(left: 20), child: FilledButton( onPressed: - _step == _steps.length - 1 - ? _openSignatureView - : _clickForward, + () => _step == _steps.length - 1 + ? _openSignatureView(delivery) + : _clickForward, child: _step == _steps.length - 1 ? const Text("Unterschreiben") @@ -178,48 +168,27 @@ class _DeliveryDetailState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("Auslieferungsdetails")), - body: BlocConsumer( - listener: (context, state) { - if (state is DeliveryFinished) { - final tourState = context.read().state as TourLoaded; - final newTour = tourState.tour.copyWith( - deliveries: - tourState.tour.deliveries.map((delivery) { - if (delivery.id == state.delivery.id) { - return state.delivery; - } + body: BlocBuilder(builder: (context, state) { + final currentState = state; - return delivery; - }).toList(), - ); + if (currentState is TourLoaded) { + Delivery delivery = currentState.tour.deliveries.firstWhere((delivery) => delivery.id == widget.deliveryId); + return Column( + children: [ + _stepInfo(), + const Divider(), + Expanded( + child: + StepFactory().make(_step, delivery) ?? + _stepMissingWarning(), + ), + _stepsNavigation(delivery), + ], + ); + } - context.read().add(UpdateTour(tour: newTour)); - - Navigator.pop(context); - Navigator.pop(context); - } - }, - builder: (context, state) { - final currentState = state; - - if (currentState is DeliveryLoaded) { - return Column( - children: [ - _stepInfo(), - const Divider(), - Expanded( - child: - StepFactory().make(_step, currentState.delivery) ?? - _stepMissingWarning(), - ), - _stepsNavigation(), - ], - ); - } - - return Container(); - }, - ), + return const Center(child: CircularProgressIndicator(),); + }), ); } } diff --git a/lib/feature/delivery/detail/presentation/delivery_discount.dart b/lib/feature/delivery/detail/presentation/delivery_discount.dart index b45d696..d969519 100644 --- a/lib/feature/delivery/detail/presentation/delivery_discount.dart +++ b/lib/feature/delivery/detail/presentation/delivery_discount.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart'; +import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_bloc.dart'; import 'package:hl_lieferservice/model/delivery.dart'; +import '../../overview/bloc/tour_event.dart'; + class DeliveryDiscount extends StatefulWidget { const DeliveryDiscount({ super.key, @@ -126,14 +127,14 @@ class _DeliveryDiscountState extends State { _isUpdated = false; }); - context.read().add( + context.read().add( RemoveDiscountEvent(deliveryId: widget.deliveryId), ); } void _updateValues() async { if (_isUpdated) { - context.read().add( + context.read().add( UpdateDiscountEvent( deliveryId: widget.deliveryId, value: _discountValue, @@ -141,7 +142,7 @@ class _DeliveryDiscountState extends State { ), ); } else { - context.read().add( + context.read().add( AddDiscountEvent( deliveryId: widget.deliveryId, value: _discountValue, diff --git a/lib/feature/delivery/detail/presentation/delivery_options.dart b/lib/feature/delivery/detail/presentation/delivery_options.dart index 2e0cb0b..fcec87e 100644 --- a/lib/feature/delivery/detail/presentation/delivery_options.dart +++ b/lib/feature/delivery/detail/presentation/delivery_options.dart @@ -1,13 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart'; +import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_bloc.dart'; import 'package:hl_lieferservice/model/delivery.dart' as model; +import '../../overview/bloc/tour_event.dart'; + class DeliveryOptionsView extends StatefulWidget { - const DeliveryOptionsView({super.key, required this.options}); + const DeliveryOptionsView({super.key, required this.options, required this.deliveryId}); final List options; + final String deliveryId; @override State createState() => _DeliveryOptionsViewState(); @@ -28,17 +30,16 @@ class _DeliveryOptionsViewState extends State { debugPrint(option.key); if (value is bool) { - context.read().add( - UpdateDeliveryOptionEvent(key: option.key, value: !value), + context.read().add( + UpdateDeliveryOptionEvent(key: option.key, value: !value, deliveryId: widget.deliveryId), ); return; } - context.read().add( - UpdateDeliveryOptionEvent(key: option.key, value: value), + context.read().add( + UpdateDeliveryOptionEvent(key: option.key, value: value, deliveryId: widget.deliveryId), ); - } List _options() { diff --git a/lib/feature/delivery/detail/presentation/delivery_summary.dart b/lib/feature/delivery/detail/presentation/delivery_summary.dart index e66b8d1..41b3d18 100644 --- a/lib/feature/delivery/detail/presentation/delivery_summary.dart +++ b/lib/feature/delivery/detail/presentation/delivery_summary.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart'; import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_bloc.dart'; +import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_event.dart'; import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_state.dart'; import 'package:hl_lieferservice/model/delivery.dart'; @@ -97,11 +96,10 @@ class _DeliverySummaryState extends State { dropdownMenuEntries: entries, initialSelection: widget.delivery.payment.id, onSelected: (id) { - context.read().add( + context.read().add( UpdateSelectedPaymentMethodEvent( - payment: _paymentMethods.firstWhere( - (payment) => payment.id == id, - ), + deliveryId: widget.delivery.id, + payment: _paymentMethods.firstWhere((payment) => payment.id == id), ), ); }, diff --git a/lib/feature/delivery/detail/presentation/note/note_image_overview.dart b/lib/feature/delivery/detail/presentation/note/note_image_overview.dart index 37d1eba..21f7577 100644 --- a/lib/feature/delivery/detail/presentation/note/note_image_overview.dart +++ b/lib/feature/delivery/detail/presentation/note/note_image_overview.dart @@ -9,7 +9,7 @@ import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_event.dart'; import 'package:hl_lieferservice/model/delivery.dart'; class NoteImageOverview extends StatefulWidget { - final List<(ImageNote, Uint8List)> images; + final List images; final String deliveryId; const NoteImageOverview({ @@ -26,7 +26,7 @@ class _NoteImageOverviewState extends State { int? _imageDeleting; void _onRemoveImage(int index) { - ImageNote note = widget.images[index].$1; + ImageNote note = widget.images[index]; context.read().add( RemoveImageNote(objectId: note.objectId, deliveryId: widget.deliveryId), @@ -42,8 +42,7 @@ class _NoteImageOverviewState extends State { ), items: widget.images.mapIndexed((index, data) { - ImageNote note = data.$1; - Uint8List bytes = data.$2; + Uint8List bytes = data.data!; return Builder( builder: (BuildContext context) { diff --git a/lib/feature/delivery/detail/presentation/note/note_overview.dart b/lib/feature/delivery/detail/presentation/note/note_overview.dart index 26b1589..6d54dc6 100644 --- a/lib/feature/delivery/detail/presentation/note/note_overview.dart +++ b/lib/feature/delivery/detail/presentation/note/note_overview.dart @@ -16,7 +16,7 @@ import 'package:image_picker/image_picker.dart'; class NoteOverview extends StatefulWidget { final List notes; final List templates; - final List<(ImageNote, Uint8List)> images; + final List images; final String deliveryId; const NoteOverview({ diff --git a/lib/feature/delivery/detail/presentation/steps/step_delivery_options.dart b/lib/feature/delivery/detail/presentation/steps/step_delivery_options.dart index 894d993..090fafd 100644 --- a/lib/feature/delivery/detail/presentation/steps/step_delivery_options.dart +++ b/lib/feature/delivery/detail/presentation/steps/step_delivery_options.dart @@ -17,6 +17,9 @@ class _DeliveryStepInfo extends State { debugPrint( "${widget.delivery.options.map((option) => "${option.display}, ${option.value}")}", ); - return DeliveryOptionsView(options: widget.delivery.options); + return DeliveryOptionsView( + options: widget.delivery.options, + deliveryId: widget.delivery.id, + ); } } diff --git a/lib/feature/delivery/detail/presentation/steps/step_note.dart b/lib/feature/delivery/detail/presentation/steps/step_note.dart index 61908ff..cfd5e57 100644 --- a/lib/feature/delivery/detail/presentation/steps/step_note.dart +++ b/lib/feature/delivery/detail/presentation/steps/step_note.dart @@ -41,7 +41,7 @@ class _DeliveryStepInfo extends State { BuildContext context, List notes, List templates, - List<(ImageNote, Uint8List)> images, + List images, ) { List hydratedNotes = notes @@ -75,8 +75,8 @@ class _DeliveryStepInfo extends State { return _notesOverview( context, state.notes, - state.templates, - state.images, + (state.templates ?? []), + (state.images ?? []), ); } diff --git a/lib/feature/delivery/detail/repository/delivery_repository.dart b/lib/feature/delivery/detail/repository/delivery_repository.dart index 9c0c162..a77c842 100644 --- a/lib/feature/delivery/detail/repository/delivery_repository.dart +++ b/lib/feature/delivery/detail/repository/delivery_repository.dart @@ -1,13 +1,17 @@ +import 'dart:typed_data'; + import 'package:hl_lieferservice/dto/discount_add_response.dart'; import 'package:hl_lieferservice/dto/discount_remove_response.dart'; import 'package:hl_lieferservice/dto/discount_update_response.dart'; +import 'package:hl_lieferservice/feature/delivery/detail/repository/note_repository.dart'; +import 'package:hl_lieferservice/feature/delivery/detail/service/notes_service.dart'; import 'package:hl_lieferservice/feature/delivery/overview/service/delivery_info_service.dart'; import 'package:hl_lieferservice/model/delivery.dart'; class DeliveryRepository { DeliveryRepository({required this.service}); - DeliveryInfoService service; + TourService service; Future unscan(String articleId, int newAmount, String reason) async { return await service.unscanArticle(articleId, newAmount, reason); @@ -17,6 +21,16 @@ class DeliveryRepository { return await service.resetScannedArticleAmount(articleId); } + Future uploadDriverSignature(String deliveryId, Uint8List signature) async { + NoteRepository noteRepository = NoteRepository(service: NoteService()); + await noteRepository.addNamedImage(deliveryId, signature, "delivery_${deliveryId}_signature_driver.jpg"); + } + + Future uploadCustomerSignature(String deliveryId, Uint8List signature) async { + NoteRepository noteRepository = NoteRepository(service: NoteService()); + await noteRepository.addNamedImage(deliveryId, signature, "delivery_${deliveryId}_signature_customer.jpg"); + } + Future addDiscount( String deliveryId, String reason, diff --git a/lib/feature/delivery/detail/repository/note_repository.dart b/lib/feature/delivery/detail/repository/note_repository.dart index 52b2860..2a281a1 100644 --- a/lib/feature/delivery/detail/repository/note_repository.dart +++ b/lib/feature/delivery/detail/repository/note_repository.dart @@ -2,43 +2,61 @@ import 'dart:typed_data'; import 'package:hl_lieferservice/model/delivery.dart'; import 'package:hl_lieferservice/feature/delivery/detail/service/notes_service.dart'; +import 'package:rxdart/rxdart.dart'; class NoteRepository { final NoteService service; + final _notesStream = BehaviorSubject>.seeded([]); + final _imageNoteStream = BehaviorSubject>.seeded([]); + final _noteTemplateStream = BehaviorSubject>.seeded([]); + + Stream> get notes => _notesStream.stream; + Stream> get images => _imageNoteStream.stream; + Stream> get templates => _noteTemplateStream.stream; + NoteRepository({required this.service}); - Future addNote(String deliveryId, String content) async { - return service.addNote(content, int.parse(deliveryId)); + Future addNote(String deliveryId, String content) async { + final note = await service.addNote(content, int.parse(deliveryId)); + final currentNotes = _notesStream.value; + currentNotes.add(note); + + _notesStream.add(currentNotes); } Future editNote(String noteId, String content) async { - return service.editNote(Note(content: content, id: int.parse(noteId))); + final newNote = Note(content: content, id: int.parse(noteId)); + await service.editNote(newNote); + + final currentNotes = _notesStream.value; + final index = currentNotes.indexWhere((note) => note.id == int.parse(noteId)); + currentNotes[index] = newNote; + + _notesStream.add(currentNotes); } Future deleteNote(String noteId) async { - return service.deleteNote(int.parse(noteId)); + await service.deleteNote(int.parse(noteId)); + final currentNotes = _notesStream.value; + final index = currentNotes.indexWhere((note) => note.id == int.parse(noteId)); + currentNotes.removeAt(index); + + _notesStream.add(currentNotes); } - Future> loadNotes(String deliveryId) async { - return service.getNotes(deliveryId); + Future loadNotes(String deliveryId) async { + var (notes, images) = await service.getNotes(deliveryId); + + _notesStream.add(notes); + _imageNoteStream.add(images); } - Future> loadImages(List urls) async { - List images = []; - - for (final image in await service.downloadImages(urls)) { - images.add(await image); - } - - return images; + Future loadTemplates() async { + _noteTemplateStream.add(await service.getNoteTemplates()); } - Future> loadTemplates() async { - return service.getNoteTemplates(); - } - - Future addImage(String deliveryId, Uint8List bytes) async { + Future addImage(String deliveryId, Uint8List bytes) async { final fileName = "delivery_note_${deliveryId}_${DateTime.timestamp().microsecondsSinceEpoch}.jpg"; @@ -48,11 +66,12 @@ class NoteRepository { bytes, "image/png", ); - - return ImageNote.make(objectId, fileName); + final imageNotes = _imageNoteStream.value; + imageNotes.add(ImageNote.make(objectId, fileName, bytes)); + _imageNoteStream.add(imageNotes); } - Future addNamedImage(String deliveryId, Uint8List bytes, String filename) async { + Future addNamedImage(String deliveryId, Uint8List bytes, String filename) async { String objectId = await service.uploadImage( deliveryId, filename, @@ -60,10 +79,18 @@ class NoteRepository { "image/png", ); - return ImageNote.make(objectId, filename); + final imageNotes = _imageNoteStream.value; + imageNotes.add(ImageNote.make(objectId, filename, bytes)); + _imageNoteStream.add(imageNotes); } Future deleteImage(String deliveryId, String objectId) async { await service.removeImage(objectId); + + final images = _imageNoteStream.value; + final index = images.indexWhere((imageNote) => imageNote.objectId == objectId); + images.removeAt(index); + + _imageNoteStream.add(images); } } diff --git a/lib/feature/delivery/detail/service/notes_service.dart b/lib/feature/delivery/detail/service/notes_service.dart index 13b3f7a..5c4498d 100644 --- a/lib/feature/delivery/detail/service/notes_service.dart +++ b/lib/feature/delivery/detail/service/notes_service.dart @@ -17,9 +17,7 @@ import '../../../../model/delivery.dart'; import '../../../../util.dart'; import '../../../authentication/exceptions.dart'; -class NoteService extends ErpFrameService { - NoteService({required super.backendUrl}); - +class NoteService { Future deleteNote(int noteId) async { try { var response = await http.post( @@ -110,7 +108,7 @@ class NoteService extends ErpFrameService { } } - Future> getNotes(String deliveryId) async { + Future<(List, List)> getNotes(String deliveryId) async { try { var response = await http.post( urlBuilder("_web_getNotes"), @@ -129,9 +127,22 @@ class NoteService extends ErpFrameService { ); if (responseDto.succeeded == true) { - return responseDto.notes - .map((noteDto) => Note.fromDto(noteDto)) - .toList(); + List imageNotes = + responseDto.images + .map((imageNoteDto) => ImageNote.fromDTO(imageNoteDto)) + .toList(); + + final images = await downloadImages(imageNotes.map((note) => note.url).toList()); + for (var (index, note) in imageNotes.indexed) { + note.data = await images[index]; + } + + return ( + responseDto.notes + .map((noteDto) => Note.fromDto(noteDto)) + .toList(), + imageNotes + ); } else { throw responseDto.message; } @@ -262,9 +273,11 @@ class NoteService extends ErpFrameService { Future>> downloadImages(List urls) async { try { + LocalDocuFrameConfiguration config = getConfig(); + return urls.map((url) async { return (await http.get( - Uri.parse("$backendUrl$url"), + Uri.parse("${config.backendUrl}$url"), headers: getSessionOrThrow(), )).bodyBytes; }).toList(); diff --git a/lib/feature/delivery/overview/bloc/tour_bloc.dart b/lib/feature/delivery/overview/bloc/tour_bloc.dart index 7399571..c43345d 100644 --- a/lib/feature/delivery/overview/bloc/tour_bloc.dart +++ b/lib/feature/delivery/overview/bloc/tour_bloc.dart @@ -1,49 +1,130 @@ -import 'package:flutter/cupertino.dart'; +import 'dart:async'; + +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_event.dart'; import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_state.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/model/delivery.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(_update); on(_assignCar); on(_increment); on(_scan); on(_holdDelivery); on(_cancelDelivery); - on(_reactiveateDelivery); + on(_reactivateDelivery); + on(_unscan); + on(_resetAmount); + on(_addDiscount); + on(_removeDiscount); + on(_updateDiscount); + on(_updateDeliveryOptions); + on(_updatePayment); + on(_finishDelivery); + on(_updated); + on(_calculateDistances); } - void _reactiveateDelivery( - ReactivateDeliveryEvent event, - Emitter emit, - ) async { + @override + Future close() { + _combinedSubscription?.cancel(); + + return super.close(); + } + + 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; + } + + emit( + TourLoaded( + tour: event.tour, + paymentOptions: event.payments, + distances: 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) { + debugPrint("TEST UPDATE"); + emit( + TourLoaded( + tour: tour, + paymentOptions: payments, + distances: Map.from(currentState.distances ?? {}), + ), + ); + } + + 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 { - Tour tourCopied = currentState.tour.copyWith(); - Delivery delivery = tourCopied.deliveries.firstWhere((delivery) => delivery.id == event.deliveryId); - delivery.state = DeliveryState.ongoing; - - await tourRepository.updateDelivery( - delivery, - ); + await tourRepository.reactivateDelivery(event.deliveryId); opBloc.add(FinishOperation()); - - emit(TourLoaded(tour: tourCopied, distances: currentState.distances)); } catch (e, st) { debugPrint("$e"); debugPrint("$st"); @@ -54,25 +135,14 @@ class TourBloc extends Bloc { } } - void _holdDelivery( - HoldDeliveryEvent event, - Emitter emit, - ) async { + void _holdDelivery(HoldDeliveryEvent event, Emitter emit) async { final currentState = state; if (currentState is TourLoaded) { opBloc.add(LoadOperation()); try { - Tour tourCopied = currentState.tour.copyWith(); - Delivery delivery = tourCopied.deliveries.firstWhere((delivery) => delivery.id == event.deliveryId); - delivery.state = DeliveryState.onhold; - - await tourRepository.updateDelivery( - delivery, - ); + await tourRepository.holdDelivery(event.deliveryId); opBloc.add(FinishOperation()); - - emit(TourLoaded(tour: tourCopied, distances: currentState.distances)); } catch (e, st) { debugPrint("$e"); debugPrint("$st"); @@ -83,22 +153,17 @@ class TourBloc extends Bloc { } } - void _cancelDelivery(CancelDeliveryEvent event, Emitter emit) async { + void _cancelDelivery( + CancelDeliveryEvent event, + Emitter emit, + ) async { final currentState = state; if (currentState is TourLoaded) { opBloc.add(LoadOperation()); try { - Tour tourCopied = currentState.tour.copyWith(); - Delivery delivery = tourCopied.deliveries.firstWhere((delivery) => delivery.id == event.deliveryId); - delivery.state = DeliveryState.canceled; - - await tourRepository.updateDelivery( - delivery, - ); + await tourRepository.cancelDelivery(event.deliveryId); opBloc.add(FinishOperation()); - - emit(TourLoaded(tour: tourCopied, distances: currentState.distances)); } catch (e, st) { debugPrint("$e"); debugPrint("$st"); @@ -115,54 +180,33 @@ class TourBloc extends Bloc { if (currentState is TourLoaded) { try { - if (currentState.tour.deliveries.any( - (delivery) => delivery.articles.any( - (article) => article.articleNumber == event.articleNumber, - ), + switch (await tourRepository.scanArticle( + event.deliveryId, + event.carId, + event.articleNumber, )) { - var tourCopied = currentState.tour.copyWith(); - var delivery = tourCopied.deliveries.firstWhere( - (delivery) => delivery.id == event.deliveryId, - ); - var article = delivery.articles.firstWhere( - (article) => article.articleNumber == event.articleNumber, - ); - - await tourRepository.scanArticle(article.internalId.toString()); - - if (article.scannedAmount < article.amount) { - article.scannedAmount += 1; - - emit(TourLoaded(tour: tourCopied, distances: currentState.distances)); - opBloc.add(FinishOperation(message: '${article.name} gescannt')); - } else { + 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: 'Alle ${article.name} wurden bereits gescannt', + message: 'Artikel ist für keine Lieferung vorgesehen', ), ); - } - } else { - opBloc.add( - FailOperation( - message: 'Fehler: Artikel ist für keine Lieferung vorgesehen', - ), - ); + break; } - } catch (e, st) { - debugPrint(st.toString()); - opBloc.add(FailOperation(message: "Fehler beim Scannnen des Artikels")); + } on TourNotFoundException catch (e) { + opBloc.add(FailOperation(message: "Fehler beim Scannen des Artikels")); } } } - Future _update(UpdateTour event, Emitter emit) async { - final currentState = state; - if (currentState is TourLoaded) { - emit(TourLoaded(tour: event.tour, distances: currentState.distances)); - } - } - Future _increment( IncrementArticleScanAmount event, Emitter emit, @@ -170,62 +214,27 @@ class TourBloc extends Bloc { final currentState = state; if (currentState is TourLoaded) { - var deliveryCopied = currentState.tour.deliveries.firstWhere( - (delivery) => delivery.id == event.deliveryId, - ); - var articleCopied = deliveryCopied.articles.firstWhere( - (article) => article.internalId == int.parse(event.internalArticleId), - ); - articleCopied.scannedAmount += 1; - - emit( - TourLoaded( - tour: currentState.tour.copyWith( - deliveries: - currentState.tour.deliveries.map((delivery) { - if (delivery.id == event.deliveryId) { - return deliveryCopied; - } - - return delivery; - }).toList(), - ), - distances: currentState.distances - ), - ); + 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()); - var copiedTour = currentState.tour.copyWith(); - var delivery = copiedTour.deliveries.firstWhere( - (delivery) => delivery.id == event.deliveryId, - ); - try { await tourRepository.assignCar(event.deliveryId, event.carId); - delivery.carId = int.parse(event.carId); - - emit( - TourLoaded( - tour: copiedTour.copyWith( - deliveries: - copiedTour.deliveries.map((d) { - if (d.id == delivery.id) { - return delivery; - } - - return d; - }).toList(), - ), - distances: currentState.distances - ), - ); - opBloc.add(FinishOperation()); } catch (e, st) { debugPrint(st.toString()); @@ -239,16 +248,10 @@ class TourBloc extends Bloc { Future _load(LoadTour event, Emitter emit) async { opBloc.add(LoadOperation()); try { - Tour tour = await tourRepository.loadAll(event.teamId); - List payments = await tourRepository.loadPaymentOptions(); - tour.paymentMethods = payments; - Map distances = {}; + emit(TourLoading()); + await tourRepository.loadTourOfToday(event.teamId); + await tourRepository.loadPaymentOptions(); - for (final delivery in tour.deliveries) { - distances[delivery.id] = await DistanceService.getDistanceByRoad(delivery.customer.address.toString()); - } - - emit(TourLoaded(tour: tour, distances: distances)); opBloc.add(FinishOperation()); } catch (e) { opBloc.add( @@ -256,4 +259,177 @@ class TourBloc extends Bloc { ); } } + + 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")); + } + } } diff --git a/lib/feature/delivery/overview/bloc/tour_event.dart b/lib/feature/delivery/overview/bloc/tour_event.dart index 97b3635..446c43e 100644 --- a/lib/feature/delivery/overview/bloc/tour_event.dart +++ b/lib/feature/delivery/overview/bloc/tour_event.dart @@ -1,5 +1,9 @@ +import 'dart:typed_data'; + import 'package:hl_lieferservice/model/tour.dart'; +import '../../../../model/delivery.dart'; + abstract class TourEvent {} class LoadTour extends TourEvent { @@ -8,6 +12,26 @@ class LoadTour extends TourEvent { LoadTour({required this.teamId}); } +class RequestDeliveryDistanceEvent extends TourEvent { + Tour tour; + List payments; + + RequestDeliveryDistanceEvent({required this.tour, required this.payments}); +} + +class TourUpdated extends TourEvent { + Tour tour; + List payments; + + TourUpdated({required this.tour, required this.payments}); +} + +class PaymentOptionsUpdated extends TourEvent { + List options; + + PaymentOptionsUpdated({required this.options}); +} + class UpdateTour extends TourEvent { Tour tour; @@ -24,8 +48,9 @@ class AssignCarEvent extends TourEvent { class IncrementArticleScanAmount extends TourEvent { String internalArticleId; String deliveryId; + String carId; - IncrementArticleScanAmount({required this.internalArticleId, required this.deliveryId}); + IncrementArticleScanAmount({required this.internalArticleId, required this.deliveryId, required this.carId}); } class ScanArticleEvent extends TourEvent { @@ -52,4 +77,88 @@ class ReactivateDeliveryEvent extends TourEvent { String deliveryId; ReactivateDeliveryEvent({required this.deliveryId}); +} + +class LoadDeliveryEvent extends TourEvent { + LoadDeliveryEvent({required this.delivery}); + + Delivery delivery; +} + +class UnscanArticleEvent extends TourEvent { + UnscanArticleEvent({ + required this.articleId, + required this.newAmount, + required this.reason, + required this.deliveryId + }); + + String articleId; + String deliveryId; + String reason; + int newAmount; +} + +class ResetScanAmountEvent extends TourEvent { + ResetScanAmountEvent({required this.articleId, required this.deliveryId}); + + String articleId; + String deliveryId; +} + +class AddDiscountEvent extends TourEvent { + AddDiscountEvent({ + required this.deliveryId, + required this.value, + required this.reason, + }); + + String deliveryId; + String reason; + int value; +} + +class RemoveDiscountEvent extends TourEvent { + RemoveDiscountEvent({required this.deliveryId}); + + String deliveryId; +} + +class UpdateDiscountEvent extends TourEvent { + UpdateDiscountEvent({ + required this.deliveryId, + required this.value, + required this.reason, + }); + + String deliveryId; + String? reason; + int? value; +} + +class UpdateDeliveryOptionEvent extends TourEvent { + UpdateDeliveryOptionEvent({required this.key, required this.value, required this.deliveryId}); + + String deliveryId; + String key; + dynamic value; +} + +class UpdateSelectedPaymentMethodEvent extends TourEvent { + UpdateSelectedPaymentMethodEvent({required this.payment, required this.deliveryId}); + + Payment payment; + String deliveryId; +} + +class FinishDeliveryEvent extends TourEvent { + FinishDeliveryEvent({ + required this.deliveryId, + required this.driverSignature, + required this.customerSignature, + }); + + String deliveryId; + Uint8List customerSignature; + Uint8List driverSignature; } \ No newline at end of file diff --git a/lib/feature/delivery/overview/bloc/tour_state.dart b/lib/feature/delivery/overview/bloc/tour_state.dart index 2983b53..dacbdd7 100644 --- a/lib/feature/delivery/overview/bloc/tour_state.dart +++ b/lib/feature/delivery/overview/bloc/tour_state.dart @@ -6,9 +6,33 @@ class TourInitial extends TourState {} class TourLoading extends TourState {} +class TourRequestingDistances extends TourState { + Tour tour; + List payments; + + TourRequestingDistances({required this.tour, required this.payments}); +} + class TourLoaded extends TourState { Tour tour; - Map distances; + Map? distances; + List paymentOptions; - TourLoaded({required this.tour, required this.distances}); -} \ No newline at end of file + TourLoaded({ + required this.tour, + this.distances, + required this.paymentOptions, + }); + + TourLoaded copyWith({ + Tour? tour, + Map? distances, + List? paymentOptions, + }) { + return TourLoaded( + tour: tour ?? this.tour, + distances: distances ?? this.distances, + paymentOptions: paymentOptions ?? this.paymentOptions, + ); + } +} diff --git a/lib/feature/delivery/overview/presentation/delivery_item.dart b/lib/feature/delivery/overview/presentation/delivery_item.dart index ce6bfc8..facf160 100644 --- a/lib/feature/delivery/overview/presentation/delivery_item.dart +++ b/lib/feature/delivery/overview/presentation/delivery_item.dart @@ -1,12 +1,24 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hl_lieferservice/model/delivery.dart'; import 'package:hl_lieferservice/feature/delivery/detail/presentation/delivery_detail_page.dart'; +import '../../../../bloc/app_bloc.dart'; +import '../../../../bloc/app_states.dart'; +import '../../../../widget/operations/bloc/operation_bloc.dart'; +import '../../detail/bloc/note_bloc.dart'; +import '../../detail/repository/note_repository.dart'; +import '../../detail/service/notes_service.dart'; + class DeliveryListItem extends StatelessWidget { final Delivery delivery; final double distance; - const DeliveryListItem({super.key, required this.delivery, required this.distance}); + const DeliveryListItem({ + super.key, + required this.delivery, + required this.distance, + }); Widget _leading(BuildContext context) { if (delivery.state == DeliveryState.finished) { @@ -17,6 +29,10 @@ class DeliveryListItem extends StatelessWidget { return Icon(Icons.cancel_rounded, color: Colors.red); } + if (delivery.state == DeliveryState.onhold) { + return Icon(Icons.pause_circle, color: Colors.orange); + } + return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -29,7 +45,19 @@ class DeliveryListItem extends StatelessWidget { void _goToDelivery(BuildContext context) { Navigator.of(context).push( MaterialPageRoute( - builder: (context) => DeliveryDetail(delivery: delivery), + builder: + (context) => BlocProvider( + create: + (context) => NoteBloc( + deliveryId: delivery.id, + opBloc: context.read(), + repository: NoteRepository( + service: NoteService(), + ), + ), + + child: DeliveryDetail(deliveryId: delivery.id), + ), ), ); } diff --git a/lib/feature/delivery/overview/presentation/delivery_list.dart b/lib/feature/delivery/overview/presentation/delivery_list.dart index fa4e537..cd5a6ad 100644 --- a/lib/feature/delivery/overview/presentation/delivery_list.dart +++ b/lib/feature/delivery/overview/presentation/delivery_list.dart @@ -32,7 +32,7 @@ class _DeliveryListState extends State { Delivery delivery = widget.deliveries[index]; return DeliveryListItem( delivery: delivery, - distance: widget.distances[delivery.id]!, + distance: widget.distances[delivery.id] ?? 0.0, ); }, itemCount: widget.deliveries.length, diff --git a/lib/feature/delivery/overview/presentation/delivery_overview_page.dart b/lib/feature/delivery/overview/presentation/delivery_overview_page.dart index c891c837..1c2bc6d 100644 --- a/lib/feature/delivery/overview/presentation/delivery_overview_page.dart +++ b/lib/feature/delivery/overview/presentation/delivery_overview_page.dart @@ -23,7 +23,7 @@ class _DeliveryOverviewPageState extends State { return Center( child: DeliveryOverview( tour: currentState.tour, - distances: currentState.distances, + distances: currentState.distances ?? {}, ), ); } diff --git a/lib/feature/delivery/overview/repository/tour_repository.dart b/lib/feature/delivery/overview/repository/tour_repository.dart index 0e314dd..4244c75 100644 --- a/lib/feature/delivery/overview/repository/tour_repository.dart +++ b/lib/feature/delivery/overview/repository/tour_repository.dart @@ -1,32 +1,351 @@ +import 'dart:typed_data'; + import 'package:hl_lieferservice/feature/delivery/overview/service/delivery_info_service.dart'; import 'package:hl_lieferservice/model/delivery.dart'; import 'package:hl_lieferservice/model/tour.dart'; +import 'package:rxdart/rxdart.dart'; + +import '../../../../dto/discount_add_response.dart'; +import '../../../../dto/discount_remove_response.dart'; +import '../../../../dto/discount_update_response.dart'; +import '../../../../model/article.dart'; +import '../../detail/repository/note_repository.dart'; +import '../../detail/service/notes_service.dart'; + +enum ScanResult { scanned, alreadyScanned, notFound } + +class TourNotFoundException implements Exception {} class TourRepository { - DeliveryInfoService service; + TourService service; + + final _tourStream = BehaviorSubject(); + final _paymentOptionsStream = BehaviorSubject>.seeded([]); + + Stream get tour => _tourStream.stream; + + Stream> get paymentOptions => _paymentOptionsStream.stream; TourRepository({required this.service}); - Future loadAll(String userId) async { - Tour? tour = await service.getTourOfToday(userId); - return tour!; + Future loadTourOfToday(String userId) async { + _tourStream.add(await service.getTourOfToday(userId)); } - Future> loadPaymentOptions() async { - return (await service.getPaymentMethods()) - .map((option) => Payment.fromDTO(option)) - .toList(); + Future loadPaymentOptions() async { + _paymentOptionsStream.add( + (await service.getPaymentMethods()) + .map((option) => Payment.fromDTO(option)) + .toList(), + ); } Future assignCar(String deliveryId, String carId) async { await service.assignCar(deliveryId, carId); + + final tour = _tourStream.value!; + final index = tour.deliveries.indexWhere( + (delivery) => delivery.id == deliveryId, + ); + tour.deliveries[index].carId = int.parse(carId); + + _tourStream.add(tour); } - Future scanArticle(String internalArticleId) async { - return await service.scanArticle(internalArticleId); + Future scanArticle( + String deliveryId, + String carId, + String articleNumber, + ) async { + if (!_tourStream.hasValue) { + throw TourNotFoundException(); + } + + final tour = _tourStream.value!; + + if (tour.deliveries.any( + (delivery) => delivery.articles.any( + (article) => article.articleNumber == articleNumber, + ), + )) { + var delivery = tour.deliveries.firstWhere( + (delivery) => delivery.id == deliveryId, + ); + var article = delivery.articles.firstWhere( + (article) => article.articleNumber == articleNumber, + ); + + await service.scanArticle(article.internalId.toString()); + + if (article.scannedAmount < article.amount) { + article.scannedAmount += 1; + _tourStream.add(tour); + return ScanResult.scanned; + } else { + return ScanResult.alreadyScanned; + } + } else { + return ScanResult.notFound; + } } - Future updateDelivery(Delivery delivery) { - return service.updateDelivery(delivery); + Future unscan( + String deliveryId, + String articleId, + int newAmount, + String reason, + ) async { + if (!_tourStream.hasValue) { + throw TourNotFoundException(); + } + + Tour tour = _tourStream.value!; + String? noteId = await service.unscanArticle(articleId, newAmount, reason); + Article article = tour.deliveries + .firstWhere((delivery) => delivery.id == deliveryId) + .articles + .firstWhere((article) => article.internalId == int.parse(articleId)); + + article.removeNoteId = noteId; + article.scannedRemovedAmount += newAmount; + article.scannedAmount -= newAmount; + + _tourStream.add(tour); + } + + Future resetScan(String articleId, String deliveryId) async { + if (!_tourStream.hasValue) { + throw TourNotFoundException(); + } + + Tour tour = _tourStream.value!; + await service.resetScannedArticleAmount(articleId); + + Article article = tour.deliveries + .firstWhere((delivery) => delivery.id == deliveryId) + .articles + .firstWhere((article) => article.internalId == int.parse(articleId)); + + article.removeNoteId = null; + article.scannedRemovedAmount = 0; + article.scannedAmount = article.amount; + + _tourStream.add(tour); + } + + Future uploadDriverSignature( + String deliveryId, + Uint8List signature, + ) async { + NoteRepository noteRepository = NoteRepository(service: NoteService()); + await noteRepository.addNamedImage( + deliveryId, + signature, + "delivery_${deliveryId}_signature_driver.jpg", + ); + } + + Future uploadCustomerSignature( + String deliveryId, + Uint8List signature, + ) async { + NoteRepository noteRepository = NoteRepository(service: NoteService()); + await noteRepository.addNamedImage( + deliveryId, + signature, + "delivery_${deliveryId}_signature_customer.jpg", + ); + } + + Future addDiscount(String deliveryId, String reason, int value) async { + if (!_tourStream.hasValue) { + throw TourNotFoundException(); + } + + Tour tour = _tourStream.value!; + DiscountAddResponseDTO response = await service.addDiscount( + deliveryId, + value, + reason, + ); + + Delivery delivery = tour.deliveries.firstWhere( + (delivery) => delivery.id == deliveryId, + ); + delivery.totalNetValue = response.values.receipt.net; + delivery.totalGrossValue = response.values.receipt.gross; + delivery.discount = Discount( + article: Article.fromDTO(response.values.article), + note: response.values.note.noteDescription, + noteId: response.values.note.rowId, + ); + + _tourStream.add(tour); + } + + Future removeDiscount(String deliveryId) async { + if (!_tourStream.hasValue) { + throw TourNotFoundException(); + } + + Tour tour = _tourStream.value!; + DiscountRemoveResponseDTO response = await service.removeDiscount( + deliveryId, + ); + + Delivery delivery = tour.deliveries.firstWhere( + (delivery) => delivery.id == deliveryId, + ); + delivery.articles = + delivery.articles + .where( + (article) => + article.internalId != delivery.discount?.article.internalId, + ) + .toList(); + + delivery.discount = null; + delivery.totalGrossValue = response.receipt.gross; + delivery.totalNetValue = response.receipt.net; + + _tourStream.add(tour); + } + + Future updateDiscount( + String deliveryId, + String? reason, + int? value, + ) async { + if (!_tourStream.hasValue) { + throw TourNotFoundException(); + } + + Tour tour = _tourStream.value!; + DiscountUpdateResponseDTO response = await service.updateDiscount( + deliveryId, + reason, + value, + ); + + Delivery delivery = tour.deliveries.firstWhere( + (delivery) => delivery.id == deliveryId, + ); + if (response.values?.receipt != null) { + delivery.totalNetValue = response.values!.receipt.net; + delivery.totalGrossValue = response.values!.receipt.gross; + } + + String discountArticleNumber = delivery.discount!.article.articleNumber; + delivery.discount = Discount( + article: + response.values?.article != null + ? Article.fromDTO(response.values!.article) + : delivery.discount!.article, + note: + response.values?.note != null + ? response.values!.note.noteDescription + : delivery.discount!.note, + noteId: + response.values?.note != null + ? response.values!.note.rowId + : delivery.discount!.noteId, + ); + + delivery.articles = [ + ...delivery.articles.where( + (article) => article.articleNumber != discountArticleNumber, + ), + delivery.discount!.article, + ]; + + _tourStream.add(tour); + } + + Future reactivateDelivery(String deliveryId) async { + await _changeState(deliveryId, DeliveryState.ongoing); + } + + Future holdDelivery(String deliveryId) async { + await _changeState(deliveryId, DeliveryState.onhold); + } + + Future cancelDelivery(String deliveryId) async { + await _changeState(deliveryId, DeliveryState.canceled); + } + + Future finishDelivery(String deliveryId) async { + await _changeState(deliveryId, DeliveryState.finished); + } + + Future _changeState(String deliveryId, DeliveryState state) async { + if (!_tourStream.hasValue) { + throw TourNotFoundException(); + } + + Tour tour = _tourStream.value!; + Delivery delivery = tour.deliveries.firstWhere( + (delivery) => delivery.id == deliveryId, + ); + + delivery.state = state; + await _updateDelivery(delivery); + + _tourStream.add(tour); + } + + Future _updateDelivery(Delivery newDelivery) async { + if (!_tourStream.hasValue) { + throw TourNotFoundException(); + } + + await service.updateDelivery(newDelivery); + } + + Future updatePayment(String deliveryId, Payment payment) async { + if (!_tourStream.hasValue) { + throw TourNotFoundException(); + } + + Tour tour = _tourStream.value!; + Delivery delivery = tour.deliveries.firstWhere( + (delivery) => delivery.id == deliveryId, + ); + + delivery.payment = payment; + await _updateDelivery(delivery); + + _tourStream.add(tour); + } + + Future updateOption( + String deliveryId, + String key, + dynamic value, + ) async { + if (!_tourStream.hasValue) { + throw TourNotFoundException(); + } + + Tour tour = _tourStream.value!; + Delivery delivery = tour.deliveries.firstWhere( + (delivery) => delivery.id == deliveryId, + ); + + delivery.options = + delivery.options.map((option) { + if (option.key == key) { + if (option.numerical) { + return option.copyWith(value: value); + } else { + return option.copyWith(value: value == true ? "1" : "0"); + } + } + + return option; + }).toList(); + + await _updateDelivery(delivery); + + _tourStream.add(tour); } } diff --git a/lib/feature/delivery/overview/service/delivery_info_service.dart b/lib/feature/delivery/overview/service/delivery_info_service.dart index aa5bd60..36d2fe1 100644 --- a/lib/feature/delivery/overview/service/delivery_info_service.dart +++ b/lib/feature/delivery/overview/service/delivery_info_service.dart @@ -20,8 +20,8 @@ import '../../../../dto/discount_update_response.dart'; import '../../../../dto/scan_response.dart'; import '../../../authentication/exceptions.dart'; -class DeliveryInfoService { - DeliveryInfoService(); +class TourService { + TourService(); Future updateDelivery(Delivery delivery) async { try { @@ -29,6 +29,7 @@ class DeliveryInfoService { headers.addAll(getSessionOrThrow()); debugPrint(getSessionOrThrow().toString()); + debugPrint(delivery.state.toString()); debugPrint(jsonEncode(DeliveryUpdateDTO.fromEntity(delivery).toJson())); var response = await post( @@ -97,7 +98,7 @@ class DeliveryInfoService { /// List all available deliveries for today. - Future getTourOfToday(String userId) async { + Future getTourOfToday(String userId) async { try { var response = await post( urlBuilder("_web_getDeliveries"), diff --git a/lib/model/delivery.dart b/lib/model/delivery.dart index 2b09230..aee0917 100644 --- a/lib/model/delivery.dart +++ b/lib/model/delivery.dart @@ -1,7 +1,10 @@ +import 'dart:typed_data'; + import 'package:flutter/cupertino.dart'; import 'package:hl_lieferservice/dto/contact_person.dart'; import 'package:hl_lieferservice/dto/delivery.dart'; import 'package:hl_lieferservice/dto/image.dart'; +import 'package:hl_lieferservice/dto/image_note_response.dart'; import 'package:hl_lieferservice/dto/note.dart'; import 'package:hl_lieferservice/dto/note_template.dart'; import 'package:hl_lieferservice/model/tour.dart'; @@ -87,24 +90,26 @@ class Discount { } class ImageNote { - const ImageNote({ + ImageNote({ required this.name, required this.url, required this.objectId, + this.data }); final String name; final String url; final String objectId; + Uint8List? data; - factory ImageNote.fromDTO(ImageDTO dto) { + factory ImageNote.fromDTO(ImageNoteDTO dto) { return ImageNote(name: dto.name, url: dto.url, objectId: dto.oid); } - factory ImageNote.make(String objectId, String name) { + factory ImageNote.make(String objectId, String name, Uint8List? bytes) { String url = "/v1/preview/1920_1080_100_png/$objectId"; - return ImageNote(name: name, url: url, objectId: objectId); + return ImageNote(name: name, url: url, objectId: objectId, data: bytes); } } diff --git a/lib/model/tour.dart b/lib/model/tour.dart index 86556af..769f305 100644 --- a/lib/model/tour.dart +++ b/lib/model/tour.dart @@ -21,6 +21,18 @@ class Payment { id: dto.id, ); } + + Payment copyWith({ + String? description, + String? shortcode, + String? id, + }) { + return Payment( + description: description ?? this.description, + shortcode: shortcode ?? this.shortcode, + id: id ?? this.id, + ); + } } class Tour { @@ -30,7 +42,7 @@ class Tour { required this.driver, required this.discountArticleNumber, required this.paymentMethods, - }); + }) : deliveriesPerCar = {}; final DateTime date; final String discountArticleNumber; @@ -38,6 +50,8 @@ class Tour { final List deliveries; List paymentMethods; + Map> deliveriesPerCar; + int getFinishedDeliveries(int carId) { return deliveries .where((delivery) => delivery.carId == carId) @@ -54,8 +68,9 @@ class Tour { List? paymentMethods, }) { return Tour( - date: date ?? this.date, - discountArticleNumber: discountArticleNumber ?? this.discountArticleNumber, + date: date ?? this.date.copyWith(), + discountArticleNumber: + discountArticleNumber ?? this.discountArticleNumber, driver: driver ?? this.driver, deliveries: deliveries ?? this.deliveries, paymentMethods: paymentMethods ?? this.paymentMethods, @@ -84,4 +99,18 @@ class Driver { return "$salutation, $name"; } + + Driver copyWith( + int? teamNumber, + String? name, + String? salutation, + List? cars, + ) { + return Driver( + teamNumber: teamNumber ?? this.teamNumber, + name: name ?? this.name, + salutation: salutation ?? this.salutation, + cars: cars ?? this.cars, + ); + } } diff --git a/lib/repository/note.dart b/lib/repository/note.dart deleted file mode 100644 index 007f292..0000000 --- a/lib/repository/note.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'dart:typed_data'; - -import 'package:hl_lieferservice/model/delivery.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/service/notes_service.dart'; - -class NoteRepository { - final NoteService service; - NoteRepository({required this.service}); - - Future addNote(String deliveryId, String content) async { - return service.addNote(content, int.parse(deliveryId)); - } - - Future deleteNote(String noteId) async { - return service.deleteNote(int.parse(noteId)); - } - - Future> loadNotes(String deliveryId) async { - return []; - } - - Future> loadImages(String deliveryId) async { - return []; - } - - Future addImage(String deliveryId, Uint8List imageBytes) async {} - Future deleteImage(String deliveryId, String imageIdentifier) async {} -} \ No newline at end of file diff --git a/lib/widget/app.dart b/lib/widget/app.dart index 5441479..41e2bdb 100644 --- a/lib/widget/app.dart +++ b/lib/widget/app.dart @@ -5,14 +5,8 @@ import 'package:hl_lieferservice/feature/authentication/bloc/auth_bloc.dart'; import 'package:hl_lieferservice/feature/authentication/presentation/login_enforcer.dart'; import 'package:hl_lieferservice/feature/authentication/service/userinfo.dart'; import 'package:hl_lieferservice/feature/cars/presentation/car_management_page.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/repository/delivery_repository.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/repository/note_repository.dart'; -import 'package:hl_lieferservice/feature/delivery/detail/service/notes_service.dart'; import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_bloc.dart'; import 'package:hl_lieferservice/feature/delivery/overview/repository/tour_repository.dart'; -import 'package:hl_lieferservice/feature/settings/bloc/settings_bloc.dart'; import 'package:hl_lieferservice/widget/home/bloc/navigation_bloc.dart'; import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart'; import 'package:hl_lieferservice/widget/operations/presentation/operation_view_enforcer.dart'; @@ -53,33 +47,7 @@ class _DeliveryAppState extends State { (context) => TourBloc( opBloc: context.read(), tourRepository: TourRepository( - service: DeliveryInfoService(), - ), - ), - ), - BlocProvider( - create: - (context) => NoteBloc( - opBloc: context.read(), - repository: NoteRepository( - service: NoteService( - backendUrl: currentAppState.config.backendUrl, - ), - ), - ), - ), - BlocProvider( - create: - (context) => DeliveryBloc( - noteBloc: context.read(), - opBloc: context.read(), - noteRepository: NoteRepository( - service: NoteService( - backendUrl: currentAppState.config.backendUrl, - ), - ), - repository: DeliveryRepository( - service: DeliveryInfoService(), + service: TourService(), ), ), ), diff --git a/pubspec.lock b/pubspec.lock index aa7bee3..4d898d2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -864,6 +864,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" + rxdart: + dependency: "direct main" + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 8c21789..dc94a61 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,6 +51,7 @@ dependencies: geolocator: ^14.0.2 mobile_scanner: ^7.1.3 shared_preferences: ^2.5.3 + rxdart: ^0.28.0 dev_dependencies: build_runner: ^2.5.4