Added components to article
This commit is contained in:
@ -43,6 +43,7 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
on<AssignCarEvent>(_assignCar);
|
||||
on<IncrementArticleScanAmount>(_increment);
|
||||
on<ScanArticleEvent>(_scan);
|
||||
on<ScanComponentEvent>(_scanComponent);
|
||||
on<HoldDeliveryEvent>(_holdDelivery);
|
||||
on<CancelDeliveryEvent>(_cancelDelivery);
|
||||
on<ReactivateDeliveryEvent>(_reactivateDelivery);
|
||||
@ -82,6 +83,7 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is TourLoaded) {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await tourRepository.setArticleAmount(
|
||||
event.deliveryId,
|
||||
@ -89,6 +91,7 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
event.amount,
|
||||
event.reason,
|
||||
);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("$e $st");
|
||||
_handleError(e, "Fehler beim Ändern der Menge des Artikels");
|
||||
@ -131,8 +134,6 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
) async {
|
||||
Map<String, double> distances = {};
|
||||
|
||||
emit(TourRequestingDistances(tour: event.tour, payments: event.payments));
|
||||
|
||||
for (final delivery in event.tour.deliveries) {
|
||||
try {
|
||||
distances[delivery.id] = await DistanceService.getDistanceByRoad(
|
||||
@ -141,22 +142,14 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
} catch (e, st) {
|
||||
debugPrint("Fehler beim Laden der Distanz: $e");
|
||||
debugPrint("$st");
|
||||
|
||||
// set the distance to none in order to handle the error case
|
||||
// afterwards for that specific delivery
|
||||
distances[delivery.id] = double.nan;
|
||||
}
|
||||
}
|
||||
|
||||
// If an error occurred, then the distances will be empty
|
||||
// If the distances are empty then they shouldn't be displayed
|
||||
add(
|
||||
RequestSortingInformationEvent(
|
||||
tour: event.tour,
|
||||
payments: event.payments,
|
||||
distances: distances,
|
||||
),
|
||||
);
|
||||
final currentState = state;
|
||||
if (currentState is TourLoaded) {
|
||||
emit(currentState.copyWith(distances: distances));
|
||||
}
|
||||
}
|
||||
|
||||
void _requestSortingInformation(
|
||||
@ -217,9 +210,10 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
tour: event.tour,
|
||||
paymentOptions: event.payments,
|
||||
sortingInformation: container,
|
||||
distances: event.distances,
|
||||
),
|
||||
);
|
||||
|
||||
add(RequestDeliveryDistanceEvent(tour: event.tour));
|
||||
}
|
||||
|
||||
void _updated(TourUpdated event, Emitter<TourState> emit) {
|
||||
@ -235,14 +229,14 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
paymentOptions: payments,
|
||||
distances: Map<String, double>.from(currentState.distances ?? {}),
|
||||
sortingInformation: currentState.sortingInformation,
|
||||
pendingScanRequests: currentState.pendingScanRequests,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Download distances if tour has previously fetched by API
|
||||
if (currentState is TourLoading) {
|
||||
add(
|
||||
RequestDeliveryDistanceEvent(tour: tour.copyWith(), payments: payments),
|
||||
RequestSortingInformationEvent(tour: tour.copyWith(), payments: payments),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -253,8 +247,10 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is TourLoaded) {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await tourRepository.reactivateDelivery(event.deliveryId);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("$e $st");
|
||||
_handleError(e, "Fehler beim Zurückstellen der Lieferung");
|
||||
@ -265,8 +261,10 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
void _holdDelivery(HoldDeliveryEvent event, Emitter<TourState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is TourLoaded) {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await tourRepository.holdDelivery(event.deliveryId);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("$e $st");
|
||||
_handleError(e, "Fehler beim Zurückstellen der Lieferung");
|
||||
@ -280,8 +278,10 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
) async {
|
||||
final currentState = state;
|
||||
if (currentState is TourLoaded) {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await tourRepository.cancelDelivery(event.deliveryId);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("$e $st");
|
||||
_handleError(e, "Fehler beim Stornieren der Lieferung");
|
||||
@ -289,10 +289,58 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
}
|
||||
}
|
||||
|
||||
void _bumpPendingScans(Emitter<TourState> emit, int delta) {
|
||||
final currentState = state;
|
||||
if (currentState is TourLoaded) {
|
||||
final next = (currentState.pendingScanRequests + delta).clamp(0, 1 << 30);
|
||||
emit(currentState.copyWith(pendingScanRequests: next));
|
||||
}
|
||||
}
|
||||
|
||||
void _scanComponent(
|
||||
ScanComponentEvent event,
|
||||
Emitter<TourState> emit,
|
||||
) async {
|
||||
final currentState = state;
|
||||
|
||||
if (currentState is TourLoaded) {
|
||||
_bumpPendingScans(emit, 1);
|
||||
try {
|
||||
switch (await tourRepository.scanComponent(
|
||||
event.deliveryId,
|
||||
event.carId,
|
||||
event.componentArticleNumber,
|
||||
)) {
|
||||
case ScanResult.scanned:
|
||||
opBloc.add(FinishOperation(message: 'Komponente gescannt'));
|
||||
break;
|
||||
case ScanResult.alreadyScanned:
|
||||
opBloc.add(
|
||||
FailOperation(message: 'Komponente wurde bereits gescannt'),
|
||||
);
|
||||
break;
|
||||
case ScanResult.notFound:
|
||||
opBloc.add(
|
||||
FailOperation(
|
||||
message: 'Komponente ist für keine Lieferung vorgesehen',
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
} catch (e, st) {
|
||||
debugPrint("FEHLER beim Scannen einer Komponente: $e $st");
|
||||
_handleError(e, "Fehler beim Scannen der Komponente");
|
||||
} finally {
|
||||
_bumpPendingScans(emit, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _scan(ScanArticleEvent event, Emitter<TourState> emit) async {
|
||||
final currentState = state;
|
||||
|
||||
if (currentState is TourLoaded) {
|
||||
_bumpPendingScans(emit, 1);
|
||||
try {
|
||||
switch (await tourRepository.scanArticle(
|
||||
event.deliveryId,
|
||||
@ -318,6 +366,8 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
} catch (e, st) {
|
||||
debugPrint("FEHLER beim Scannen eines Artikels: $e $st");
|
||||
_handleError(e, "Fehler beim Scannen des Artikels");
|
||||
} finally {
|
||||
_bumpPendingScans(emit, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -329,6 +379,7 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
final currentState = state;
|
||||
|
||||
if (currentState is TourLoaded) {
|
||||
_bumpPendingScans(emit, 1);
|
||||
try {
|
||||
await tourRepository.scanArticle(
|
||||
event.deliveryId,
|
||||
@ -338,6 +389,8 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
} catch (e, st) {
|
||||
debugPrint("$e $st");
|
||||
_handleError(e, "Fehler beim Scannen des Artikels");
|
||||
} finally {
|
||||
_bumpPendingScans(emit, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -345,8 +398,10 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
Future<void> _assignCar(AssignCarEvent event, Emitter<TourState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is TourLoaded) {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await tourRepository.assignCar(event.deliveryId, event.carId);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("$e $st");
|
||||
_handleError(e, "Fehler beim Zuweisen des Fahrzeugs");
|
||||
@ -376,6 +431,7 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
final currentState = state;
|
||||
|
||||
if (currentState is TourLoaded) {
|
||||
opBloc.add(StartOperation(message: "Lieferung wird abgeschlossen…"));
|
||||
try {
|
||||
await tourRepository.uploadDriverSignature(
|
||||
event.deliveryId,
|
||||
@ -387,6 +443,7 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
);
|
||||
|
||||
await tourRepository.finishDelivery(event.deliveryId);
|
||||
opBloc.add(FinishOperation(message: "Lieferung abgeschlossen"));
|
||||
} catch (e, st) {
|
||||
debugPrint("$e $st");
|
||||
_handleError(e, "Fehler beim Abschließen der Lieferung");
|
||||
@ -398,8 +455,10 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
UpdateSelectedPaymentMethodEvent event,
|
||||
Emitter<TourState> emit,
|
||||
) async {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await tourRepository.updatePayment(event.deliveryId, event.payment);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("$e $st");
|
||||
_handleError(e, "Fehler beim Aktualisieren des Betrags");
|
||||
@ -410,12 +469,14 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
UpdateDeliveryOptionEvent event,
|
||||
Emitter<TourState> emit,
|
||||
) async {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await tourRepository.updateOption(
|
||||
event.deliveryId,
|
||||
event.key,
|
||||
event.value,
|
||||
);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("$e $st");
|
||||
_handleError(e, "Fehler beim Aktualisieren der Optionen");
|
||||
@ -426,12 +487,14 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
UpdateDiscountEvent event,
|
||||
Emitter<TourState> emit,
|
||||
) async {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await tourRepository.updateDiscount(
|
||||
event.deliveryId,
|
||||
event.reason,
|
||||
event.value,
|
||||
);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("Fehler beim Aktualisieren des Discounts: $e $st");
|
||||
_handleError(e, "Fehler beim Aktualisieren des Discounts");
|
||||
@ -442,8 +505,10 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
RemoveDiscountEvent event,
|
||||
Emitter<TourState> emit,
|
||||
) async {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await tourRepository.removeDiscount(event.deliveryId);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("Fehler beim Löschen des Discounts: $e $st");
|
||||
_handleError(e, "Fehler beim Löschen des Discounts");
|
||||
@ -451,12 +516,14 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
}
|
||||
|
||||
void _addDiscount(AddDiscountEvent event, Emitter<TourState> emit) async {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await tourRepository.addDiscount(
|
||||
event.deliveryId,
|
||||
event.reason,
|
||||
event.value,
|
||||
);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("Fehler beim Hinzufügen des Discounts: $e $st");
|
||||
_handleError(e, "Fehler beim Hinzufügen des Discounts");
|
||||
@ -464,6 +531,7 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
}
|
||||
|
||||
void _unscan(UnscanArticleEvent event, Emitter<TourState> emit) async {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await tourRepository.unscan(
|
||||
event.deliveryId,
|
||||
@ -471,6 +539,7 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
event.newAmount,
|
||||
event.reason,
|
||||
);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("Fehler beim Unscan des Artikels ${event.articleId}: $e $st");
|
||||
_handleError(e, "Fehler beim Unscan des Artikels");
|
||||
@ -478,8 +547,10 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
||||
}
|
||||
|
||||
void _resetAmount(ResetScanAmountEvent event, Emitter<TourState> emit) async {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await tourRepository.resetScan(event.articleId, event.deliveryId);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("Fehler beim Zurücksetzen Artikel ${event.articleId}: $e $st");
|
||||
_handleError(e, "Fehler beim Zurücksetzen");
|
||||
|
||||
@ -15,20 +15,17 @@ class LoadTour extends TourEvent {
|
||||
|
||||
class RequestDeliveryDistanceEvent extends TourEvent {
|
||||
Tour tour;
|
||||
List<Payment> payments;
|
||||
|
||||
RequestDeliveryDistanceEvent({required this.tour, required this.payments});
|
||||
RequestDeliveryDistanceEvent({required this.tour});
|
||||
}
|
||||
|
||||
class RequestSortingInformationEvent extends TourEvent {
|
||||
Tour tour;
|
||||
List<Payment> payments;
|
||||
Map<String, double>? distances;
|
||||
|
||||
RequestSortingInformationEvent({
|
||||
required this.tour,
|
||||
required this.payments,
|
||||
this.distances,
|
||||
});
|
||||
}
|
||||
|
||||
@ -90,6 +87,20 @@ class ScanArticleEvent extends TourEvent {
|
||||
String carId;
|
||||
}
|
||||
|
||||
/// Scan a single BOM component. The server call for the parent article is
|
||||
/// deferred until *all* components are fully scanned.
|
||||
class ScanComponentEvent extends TourEvent {
|
||||
ScanComponentEvent({
|
||||
required this.componentArticleNumber,
|
||||
required this.carId,
|
||||
required this.deliveryId,
|
||||
});
|
||||
|
||||
String componentArticleNumber;
|
||||
String deliveryId;
|
||||
String carId;
|
||||
}
|
||||
|
||||
class CancelDeliveryEvent extends TourEvent {
|
||||
String deliveryId;
|
||||
|
||||
|
||||
@ -8,49 +8,38 @@ class TourLoading extends TourState {}
|
||||
|
||||
class TourLoadingFailed extends TourState {}
|
||||
|
||||
class TourRequestingDistances extends TourState {
|
||||
Tour tour;
|
||||
List<Payment> payments;
|
||||
|
||||
TourRequestingDistances({required this.tour, required this.payments});
|
||||
}
|
||||
|
||||
class TourRequestingSortingInformation extends TourState {
|
||||
Tour tour;
|
||||
Map<String, double>? distances;
|
||||
List<Payment> paymentOptions;
|
||||
|
||||
TourRequestingSortingInformation({
|
||||
required this.tour,
|
||||
this.distances,
|
||||
required this.paymentOptions,
|
||||
});
|
||||
}
|
||||
|
||||
class TourLoaded extends TourState {
|
||||
Tour tour;
|
||||
Map<String, double>? distances;
|
||||
List<Payment> paymentOptions;
|
||||
Map<String, List<String>> sortingInformation;
|
||||
|
||||
/// Number of scan-related server requests currently in flight. Drives the
|
||||
/// inline indicator on the scanner widget. Using a counter (not bool) lets
|
||||
/// rapid-fire scans coexist without one prematurely clearing the indicator.
|
||||
int pendingScanRequests;
|
||||
|
||||
TourLoaded({
|
||||
required this.tour,
|
||||
this.distances,
|
||||
required this.paymentOptions,
|
||||
required this.sortingInformation
|
||||
required this.sortingInformation,
|
||||
this.pendingScanRequests = 0,
|
||||
});
|
||||
|
||||
TourLoaded copyWith({
|
||||
Tour? tour,
|
||||
Map<String, double>? distances,
|
||||
List<Payment>? paymentOptions,
|
||||
Map<String, List<String>>? sortingInformation
|
||||
Map<String, List<String>>? sortingInformation,
|
||||
int? pendingScanRequests,
|
||||
}) {
|
||||
return TourLoaded(
|
||||
tour: tour ?? this.tour,
|
||||
distances: distances ?? this.distances,
|
||||
paymentOptions: paymentOptions ?? this.paymentOptions,
|
||||
sortingInformation: sortingInformation ?? this.sortingInformation
|
||||
sortingInformation: sortingInformation ?? this.sortingInformation,
|
||||
pendingScanRequests: pendingScanRequests ?? this.pendingScanRequests,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,8 +94,10 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
|
||||
RemoveImageNote event,
|
||||
Emitter<NoteState> emit,
|
||||
) async {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await repository.deleteImage(event.deliveryId, event.objectId);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("Fehler beim Löschen des Bildes: $e $st");
|
||||
_handleError(e, "Fehler beim Löschen des Bildes");
|
||||
@ -103,9 +105,11 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
|
||||
}
|
||||
|
||||
Future<void> _upload(AddImageNote event, Emitter<NoteState> emit) async {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
Uint8List imageBytes = await event.file.readAsBytes();
|
||||
await repository.addImage(event.deliveryId, imageBytes);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("Fehler beim Hinzufügen des Bildes: $e $st");
|
||||
_handleError(e, "Fehler beim Hinzufügen des Bildes");
|
||||
@ -113,6 +117,10 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
|
||||
}
|
||||
|
||||
Future<void> _load(LoadNote event, Emitter<NoteState> emit) async {
|
||||
if (state is NoteLoaded || state is NoteLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit.call(NoteLoading());
|
||||
|
||||
try {
|
||||
@ -130,8 +138,10 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
|
||||
}
|
||||
|
||||
Future<void> _add(AddNote event, Emitter<NoteState> emit) async {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await repository.addNote(event.deliveryId, event.note);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("Fehler beim Hinzufügen der Notiz: $e $st");
|
||||
_handleError(e, "Fehler beim Hinzufügen der Notiz");
|
||||
@ -139,8 +149,10 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
|
||||
}
|
||||
|
||||
Future<void> _edit(EditNote event, Emitter<NoteState> emit) async {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await repository.editNote(event.noteId, event.content);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("Fehler beim Editieren der Notiz: $e $st");
|
||||
_handleError(e, "Fehler beim Editieren der Notiz");
|
||||
@ -148,8 +160,10 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
|
||||
}
|
||||
|
||||
Future<void> _remove(RemoveNote event, Emitter<NoteState> emit) async {
|
||||
opBloc.add(StartOperation());
|
||||
try {
|
||||
await repository.deleteNote(event.noteId);
|
||||
opBloc.add(FinishOperation());
|
||||
} catch (e, st) {
|
||||
debugPrint("Fehler beim Löschen der Notiz: $e $st");
|
||||
_handleError(e, "Notiz konnte nicht gelöscht werden");
|
||||
|
||||
@ -91,8 +91,10 @@ class _ResetArticleAmountDialogState extends State<ResetArticleAmountDialog> {
|
||||
children: [
|
||||
const Text("Wollen Sie die entfernten Artikel wieder hinzufügen?"),
|
||||
!widget.article.scannable ? _amountSelection() : Container(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 8,
|
||||
alignment: WrapAlignment.spaceEvenly,
|
||||
children: [
|
||||
FilledButton(
|
||||
onPressed: _reset,
|
||||
|
||||
@ -154,8 +154,10 @@ class _ArticleUnscanDialogState extends State<ArticleUnscanDialog> {
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 8,
|
||||
alignment: WrapAlignment.spaceAround,
|
||||
children: [
|
||||
FilledButton(
|
||||
onPressed: isValidText ? _unscan : null,
|
||||
|
||||
@ -142,66 +142,70 @@ class _DeliveryDetailState extends State<DeliveryDetail> {
|
||||
}
|
||||
|
||||
Widget _stepsNavigation(Delivery delivery) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
height: 90,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: _step == 0 ? null : _clickBack,
|
||||
child: const Text("zurück"),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 20),
|
||||
child: FilledButton(
|
||||
onPressed: () {
|
||||
if (_step == _steps.length - 1) {
|
||||
_openSignatureView(delivery);
|
||||
} else {
|
||||
_clickForward();
|
||||
}
|
||||
},
|
||||
child:
|
||||
_step == _steps.length - 1
|
||||
? const Text("Unterschreiben")
|
||||
: const Text("weiter"),
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: 90,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: _step == 0 ? null : _clickBack,
|
||||
child: const Text("zurück"),
|
||||
),
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 20),
|
||||
child: FilledButton(
|
||||
onPressed: () {
|
||||
if (_step == _steps.length - 1) {
|
||||
_openSignatureView(delivery);
|
||||
} else {
|
||||
_clickForward();
|
||||
}
|
||||
},
|
||||
child:
|
||||
_step == _steps.length - 1
|
||||
? const Text("Unterschreiben")
|
||||
: const Text("weiter"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text("Auslieferungsdetails")),
|
||||
body: BlocBuilder<TourBloc, TourState>(
|
||||
builder: (context, state) {
|
||||
final currentState = state;
|
||||
return BlocBuilder<TourBloc, TourState>(
|
||||
builder: (context, state) {
|
||||
Delivery? delivery;
|
||||
if (state is TourLoaded) {
|
||||
delivery = state.tour.deliveries.firstWhere(
|
||||
(d) => d.id == widget.deliveryId,
|
||||
);
|
||||
}
|
||||
|
||||
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(),
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text("Auslieferungsdetails")),
|
||||
body: delivery == null
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Column(
|
||||
children: [
|
||||
_stepInfo(),
|
||||
const Divider(),
|
||||
Expanded(
|
||||
child:
|
||||
StepFactory().make(_step, delivery) ??
|
||||
_stepMissingWarning(),
|
||||
),
|
||||
],
|
||||
),
|
||||
_stepsNavigation(delivery),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
},
|
||||
),
|
||||
bottomNavigationBar:
|
||||
delivery == null ? null : _stepsNavigation(delivery),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,17 +195,16 @@ class _DeliveryDiscountState extends State<DeliveryDiscount> {
|
||||
},
|
||||
),
|
||||
),
|
||||
Row(
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: FilledButton(
|
||||
onPressed:
|
||||
!_isReasonEmpty && _discountValue > 0
|
||||
? _updateValues
|
||||
: null,
|
||||
child: const Text("Speichern"),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed:
|
||||
!_isReasonEmpty && _discountValue > 0
|
||||
? _updateValues
|
||||
: null,
|
||||
child: const Text("Speichern"),
|
||||
),
|
||||
OutlinedButton(
|
||||
onPressed: _discountValue > 0 && widget.discount != null ? _resetValues : null,
|
||||
|
||||
@ -9,6 +9,8 @@ import 'package:hl_lieferservice/model/delivery.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:signature/signature.dart';
|
||||
|
||||
enum _SigningPhase { customerAcceptance, customerSignature, driverSignature }
|
||||
|
||||
class SignatureView extends StatefulWidget {
|
||||
const SignatureView({
|
||||
super.key,
|
||||
@ -43,33 +45,11 @@ class _SignatureViewState extends State<SignatureView> {
|
||||
exportBackgroundColor: Colors.white,
|
||||
);
|
||||
|
||||
bool _isDriverSigning = false;
|
||||
bool _customerAccepted = false;
|
||||
bool _noteAccepted = false;
|
||||
bool _notesEmpty = true;
|
||||
bool _isCustomerSignatureEmpty = true;
|
||||
bool _isDriverSignatureEmpty = true;
|
||||
_SigningPhase _phase = _SigningPhase.customerAcceptance;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_customerController.addListener(() {
|
||||
if (_isCustomerSignatureEmpty != _customerController.isEmpty) {
|
||||
setState(() {
|
||||
_isCustomerSignatureEmpty = _customerController.isEmpty;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_driverController.addListener(() {
|
||||
if (_isDriverSignatureEmpty != _driverController.isEmpty) {
|
||||
setState(() {
|
||||
_isDriverSignatureEmpty = _driverController.isEmpty;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
context.read<NoteBloc>().add(LoadNote(delivery: widget.delivery));
|
||||
}
|
||||
|
||||
@ -80,14 +60,88 @@ class _SignatureViewState extends State<SignatureView> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget _signatureField() {
|
||||
return Signature(
|
||||
controller: _isDriverSigning ? _driverController : _customerController,
|
||||
backgroundColor: Colors.white,
|
||||
void _onAcceptanceDone() {
|
||||
setState(() => _phase = _SigningPhase.customerSignature);
|
||||
}
|
||||
|
||||
void _onCustomerSigned() {
|
||||
setState(() => _phase = _SigningPhase.driverSignature);
|
||||
}
|
||||
|
||||
Future<void> _onDriverSigned() async {
|
||||
widget.onSigned(
|
||||
(await _customerController.toPngBytes())!,
|
||||
(await _driverController.toPngBytes())!,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _notes() {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return switch (_phase) {
|
||||
_SigningPhase.customerAcceptance => _AcceptanceStep(
|
||||
onContinue: _onAcceptanceDone,
|
||||
),
|
||||
_SigningPhase.customerSignature => _SignaturePadStep(
|
||||
controller: _customerController,
|
||||
delivery: widget.delivery,
|
||||
appBarTitle: "Unterschrift des Kunden",
|
||||
buttonLabel: "Weiter",
|
||||
onContinue: _onCustomerSigned,
|
||||
),
|
||||
_SigningPhase.driverSignature => _SignaturePadStep(
|
||||
controller: _driverController,
|
||||
delivery: widget.delivery,
|
||||
appBarTitle: "Unterschrift des Fahrers",
|
||||
buttonLabel: "Absenden",
|
||||
onContinue: _onDriverSigned,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class _AcceptanceStep extends StatefulWidget {
|
||||
const _AcceptanceStep({required this.onContinue});
|
||||
|
||||
final VoidCallback onContinue;
|
||||
|
||||
@override
|
||||
State<_AcceptanceStep> createState() => _AcceptanceStepState();
|
||||
}
|
||||
|
||||
class _AcceptanceStepState extends State<_AcceptanceStep> {
|
||||
bool _customerAccepted = false;
|
||||
bool _noteAccepted = false;
|
||||
|
||||
Widget _notesContent(NoteState noteState) {
|
||||
if (noteState is! NoteLoaded) {
|
||||
return const SizedBox(
|
||||
width: double.infinity,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
if (noteState.notes.isEmpty) {
|
||||
return const SizedBox(
|
||||
width: double.infinity,
|
||||
child: Center(child: Text("Keine Notizen vorhanden")),
|
||||
);
|
||||
}
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.event_note_outlined),
|
||||
title: Text(noteState.notes[index].content),
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
tileColor: Theme.of(context).colorScheme.onSecondary,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => const Divider(height: 0),
|
||||
itemCount: noteState.notes.length,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _notes(NoteState noteState) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -98,163 +152,171 @@ class _SignatureViewState extends State<SignatureView> {
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
BlocConsumer<NoteBloc, NoteState>(
|
||||
listener: (context, state) {
|
||||
final current = state;
|
||||
if (current is NoteLoaded) {
|
||||
setState(() {
|
||||
_notesEmpty = current.notes.isEmpty;
|
||||
});
|
||||
}
|
||||
|
||||
if (current is NoteLoadedBase) {
|
||||
setState(() {
|
||||
_notesEmpty = current.notes.isEmpty;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
builder: (context, state) {
|
||||
final current = state;
|
||||
|
||||
if (current is NoteLoaded) {
|
||||
if (current.notes.isEmpty) {
|
||||
return const SizedBox(
|
||||
width: double.infinity,
|
||||
child: Center(child: Text("Keine Notizen vorhanden")),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.event_note_outlined),
|
||||
title: Text(current.notes[index].content),
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
tileColor: Theme.of(context).colorScheme.onSecondary,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => const Divider(height: 0),
|
||||
itemCount: current.notes.length,
|
||||
);
|
||||
}
|
||||
|
||||
return const SizedBox(
|
||||
width: double.infinity,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
_notesContent(noteState),
|
||||
const Padding(padding: EdgeInsets.only(top: 25), child: Divider()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _customerCheckboxes() {
|
||||
return !_isDriverSigning
|
||||
? Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 25, bottom: 0),
|
||||
child: _notes(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 25.0, bottom: 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: _noteAccepted,
|
||||
onChanged:
|
||||
_notesEmpty
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<NoteBloc, NoteState>(
|
||||
builder: (context, noteState) {
|
||||
final notesEmpty = switch (noteState) {
|
||||
NoteLoadedBase(notes: final ns) => ns.isEmpty,
|
||||
_ => true,
|
||||
};
|
||||
final isButtonEnabled =
|
||||
_customerAccepted && (_noteAccepted || notesEmpty);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text("Unterschrift des Kunden")),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 25, bottom: 0),
|
||||
child: _notes(noteState),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 25.0, bottom: 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: _noteAccepted,
|
||||
onChanged: notesEmpty
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
_noteAccepted = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
Flexible(
|
||||
child: InkWell(
|
||||
onTap: _notesEmpty ? null : () {
|
||||
setState(() {
|
||||
_noteAccepted = !_noteAccepted;
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
"Ich nehme die oben genannten Anmerkungen zur Lieferung zur Kenntnis.",
|
||||
overflow: TextOverflow.fade,
|
||||
setState(() {
|
||||
_noteAccepted = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: InkWell(
|
||||
onTap: notesEmpty
|
||||
? null
|
||||
: () {
|
||||
setState(() {
|
||||
_noteAccepted = !_noteAccepted;
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
"Ich nehme die oben genannten Anmerkungen zur Lieferung zur Kenntnis.",
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 25.0, bottom: 10.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: _customerAccepted,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_customerAccepted = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
Flexible(
|
||||
child: InkWell(
|
||||
child: Text(
|
||||
"Ware in ordnungsgemäßem Zustand erhalten. Aufstell- und Einbauarbeiten wurden korrekt durchgeführt",
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_customerAccepted = !_customerAccepted;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: SafeArea(
|
||||
top: false,
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: 90,
|
||||
child: Center(
|
||||
child: FilledButton(
|
||||
onPressed: isButtonEnabled ? widget.onContinue : null,
|
||||
child: const Text("Unterschreiben"),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 25.0, bottom: 10.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: _customerAccepted,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_customerAccepted = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
Flexible(
|
||||
child: InkWell(
|
||||
child: Text(
|
||||
"Ware in ordnungsgemäßem Zustand erhalten. Aufstell- und Einbauarbeiten wurden korrekt durchgeführt",
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_customerAccepted = !_customerAccepted;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Container();
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SignaturePadStep extends StatefulWidget {
|
||||
const _SignaturePadStep({
|
||||
required this.controller,
|
||||
required this.delivery,
|
||||
required this.appBarTitle,
|
||||
required this.buttonLabel,
|
||||
required this.onContinue,
|
||||
});
|
||||
|
||||
final SignatureController controller;
|
||||
final Delivery delivery;
|
||||
final String appBarTitle;
|
||||
final String buttonLabel;
|
||||
final VoidCallback onContinue;
|
||||
|
||||
@override
|
||||
State<_SignaturePadStep> createState() => _SignaturePadStepState();
|
||||
}
|
||||
|
||||
class _SignaturePadStepState extends State<_SignaturePadStep> {
|
||||
bool _isEmpty = true;
|
||||
late final VoidCallback _listener;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_isEmpty = widget.controller.isEmpty;
|
||||
_listener = () {
|
||||
if (_isEmpty != widget.controller.isEmpty) {
|
||||
setState(() {
|
||||
_isEmpty = widget.controller.isEmpty;
|
||||
});
|
||||
}
|
||||
};
|
||||
widget.controller.addListener(_listener);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.controller.removeListener(_listener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String formattedDate = DateFormat("dd.MM.yyyy").format(DateTime.now());
|
||||
|
||||
bool isButtonEnabled;
|
||||
if (!_isDriverSigning) {
|
||||
isButtonEnabled =
|
||||
_customerAccepted &&
|
||||
(_noteAccepted || _notesEmpty) &&
|
||||
!_isCustomerSignatureEmpty;
|
||||
} else {
|
||||
isButtonEnabled = !_isDriverSignatureEmpty;
|
||||
}
|
||||
final formattedDate = DateFormat("dd.MM.yyyy").format(DateTime.now());
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title:
|
||||
!_isDriverSigning
|
||||
? const Text("Unterschrift des Kunden")
|
||||
: const Text("Unterschrift des Fahrers"),
|
||||
),
|
||||
appBar: AppBar(title: Text(widget.appBarTitle)),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: ListView(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height:
|
||||
MediaQuery.of(context).size.height *
|
||||
(_isDriverSigning ? 0.75 : 0.5),
|
||||
height: MediaQuery.of(context).size.height * 0.75,
|
||||
child: DecoratedBox(
|
||||
decoration: const BoxDecoration(color: Colors.white),
|
||||
child: Padding(
|
||||
@ -272,7 +334,12 @@ class _SignatureViewState extends State<SignatureView> {
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Expanded(child: _signatureField()),
|
||||
Expanded(
|
||||
child: Signature(
|
||||
controller: widget.controller,
|
||||
backgroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -285,36 +352,22 @@ class _SignatureViewState extends State<SignatureView> {
|
||||
),
|
||||
),
|
||||
),
|
||||
_customerCheckboxes(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 25.0, bottom: 25.0),
|
||||
child: Center(
|
||||
child: FilledButton(
|
||||
onPressed:
|
||||
isButtonEnabled
|
||||
? () async {
|
||||
if (!_isDriverSigning) {
|
||||
setState(() {
|
||||
_isDriverSigning = true;
|
||||
});
|
||||
} else {
|
||||
widget.onSigned(
|
||||
(await _customerController.toPngBytes())!,
|
||||
(await _driverController.toPngBytes())!,
|
||||
);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
child:
|
||||
!_isDriverSigning
|
||||
? const Text("Weiter")
|
||||
: const Text("Absenden"),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: SafeArea(
|
||||
top: false,
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: 90,
|
||||
child: Center(
|
||||
child: FilledButton(
|
||||
onPressed: _isEmpty ? null : widget.onContinue,
|
||||
child: Text(widget.buttonLabel),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +51,10 @@ class _NoteAddDialogState extends State<NoteAddDialog> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
// Default Dialog.insetPadding eats 80 dp horizontally on phones, leaving
|
||||
// too little room for two side-by-side buttons on narrow devices like
|
||||
// the Samsung A16F. Shrinking the inset gives back ~64 dp.
|
||||
insetPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 24),
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height * 0.6,
|
||||
@ -115,8 +119,9 @@ class _NoteAddDialogState extends State<NoteAddDialog> {
|
||||
maxLines: 10,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
FilledButton(
|
||||
onPressed:
|
||||
@ -126,15 +131,12 @@ class _NoteAddDialogState extends State<NoteAddDialog> {
|
||||
: null,
|
||||
child: const Text("Hinzufügen"),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10.0),
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
_noteController.clear();
|
||||
_noteSelectionController.clear();
|
||||
},
|
||||
child: const Text("Zurücksetzen"),
|
||||
),
|
||||
OutlinedButton(
|
||||
onPressed: () {
|
||||
_noteController.clear();
|
||||
_noteSelectionController.clear();
|
||||
},
|
||||
child: const Text("Zurücksetzen"),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@ -11,12 +11,12 @@ import '../../detail/service/notes_service.dart';
|
||||
|
||||
class DeliveryListItem extends StatelessWidget {
|
||||
final Delivery delivery;
|
||||
final double distance;
|
||||
final double? distance;
|
||||
|
||||
const DeliveryListItem({
|
||||
super.key,
|
||||
required this.delivery,
|
||||
required this.distance,
|
||||
this.distance,
|
||||
});
|
||||
|
||||
void _goToDelivery(BuildContext context) {
|
||||
@ -59,11 +59,14 @@ class DeliveryListItem extends StatelessWidget {
|
||||
"Pausiert",
|
||||
);
|
||||
case DeliveryState.ongoing:
|
||||
final distanceLabel = distance != null && !distance!.isNaN
|
||||
? "${distance!.toStringAsFixed(1)} km"
|
||||
: "–";
|
||||
return (
|
||||
Theme.of(context).colorScheme.surfaceContainerLow,
|
||||
Colors.transparent,
|
||||
Icons.local_shipping_outlined,
|
||||
"${distance.toStringAsFixed(1)} km",
|
||||
distanceLabel,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ class _DeliveryListState extends State<DeliveryList> {
|
||||
|
||||
return DeliveryListItem(
|
||||
delivery: delivery,
|
||||
distance: distances[delivery.id] ?? 0.0,
|
||||
distance: distances[delivery.id],
|
||||
);
|
||||
},
|
||||
itemCount: sortingInformation.length,
|
||||
@ -114,7 +114,7 @@ class _DeliveryListState extends State<DeliveryList> {
|
||||
itemCount: sorted.length,
|
||||
itemBuilder: (context, index) => DeliveryListItem(
|
||||
delivery: sorted[index],
|
||||
distance: currentState.distances?[sorted[index].id] ?? 0.0,
|
||||
distance: currentState.distances?[sorted[index].id],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -18,11 +18,9 @@ class DeliveryOverview extends StatefulWidget {
|
||||
const DeliveryOverview({
|
||||
super.key,
|
||||
required this.tour,
|
||||
required this.distances,
|
||||
});
|
||||
|
||||
final Tour tour;
|
||||
final Map<String, double> distances;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _DeliveryOverviewState();
|
||||
|
||||
@ -4,7 +4,7 @@ import 'package:hl_lieferservice/feature/car_selection/bloc/bloc.dart';
|
||||
import 'package:hl_lieferservice/feature/car_selection/bloc/state.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_fail_page.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_overview.dart';
|
||||
|
||||
import 'package:hl_lieferservice/model/tour.dart';
|
||||
import '../../bloc/tour_bloc.dart';
|
||||
import '../../bloc/tour_state.dart';
|
||||
|
||||
@ -16,6 +16,36 @@ class DeliveryOverviewPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DeliveryOverviewPageState extends State<DeliveryOverviewPage> {
|
||||
Widget _buildOverviewWithBanner({
|
||||
required Tour tour,
|
||||
required String bannerText,
|
||||
}) {
|
||||
return Column(
|
||||
children: [
|
||||
Material(
|
||||
color: Colors.amber.shade100,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: Text(bannerText)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: DeliveryOverview(tour: tour),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final carState = context.watch<CarSelectBloc>().state;
|
||||
@ -54,10 +84,13 @@ class _DeliveryOverviewPageState extends State<DeliveryOverviewPage> {
|
||||
body: BlocBuilder<TourBloc, TourState>(
|
||||
builder: (context, state) {
|
||||
if (state is TourLoaded) {
|
||||
return DeliveryOverview(
|
||||
tour: state.tour,
|
||||
distances: state.distances ?? {},
|
||||
);
|
||||
if (state.distances == null) {
|
||||
return _buildOverviewWithBanner(
|
||||
tour: state.tour,
|
||||
bannerText: "Berechne Distanzen…",
|
||||
);
|
||||
}
|
||||
return DeliveryOverview(tour: state.tour);
|
||||
}
|
||||
|
||||
if (state is TourLoadingFailed) {
|
||||
|
||||
@ -92,6 +92,48 @@ class TourRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/// Scan a single BOM component locally. The server-side `scanArticle` call
|
||||
/// for the parent article is deferred until **every** component of the
|
||||
/// parent is fully scanned — only then does the parent count as loaded.
|
||||
Future<ScanResult> scanComponent(
|
||||
String deliveryId,
|
||||
String carId,
|
||||
String componentArticleNumber,
|
||||
) async {
|
||||
if (!_tourStream.hasValue) {
|
||||
throw TourNotFoundException();
|
||||
}
|
||||
|
||||
final tour = _tourStream.value!;
|
||||
final delivery = tour.deliveries.firstWhere(
|
||||
(d) => d.id == deliveryId,
|
||||
);
|
||||
|
||||
// Locate the parent article and the matching component.
|
||||
final parentArticle = delivery.findParentOfComponent(
|
||||
componentArticleNumber,
|
||||
);
|
||||
if (parentArticle == null) return ScanResult.notFound;
|
||||
|
||||
final component = parentArticle.findComponent(componentArticleNumber)!;
|
||||
|
||||
if (component.isFullyScanned) return ScanResult.alreadyScanned;
|
||||
|
||||
// ── Local-only increment ──
|
||||
component.scannedAmount += 1;
|
||||
|
||||
// ── When every component is done, sync the parent with the server ──
|
||||
if (parentArticle.isFullyScanned) {
|
||||
await service.scanArticle(parentArticle.internalId.toString());
|
||||
parentArticle.scannedAmount += 1;
|
||||
delivery.carId = int.tryParse(carId) ?? delivery.carId;
|
||||
await service.assignCar(deliveryId, carId);
|
||||
}
|
||||
|
||||
_tourStream.add(tour);
|
||||
return ScanResult.scanned;
|
||||
}
|
||||
|
||||
Future<void> unscan(
|
||||
String deliveryId,
|
||||
String articleId,
|
||||
|
||||
Reference in New Issue
Block a user