Implemented new custom sort and separated the sorted view for each car

This commit is contained in:
Dennis Nemec
2026-01-27 00:21:23 +01:00
parent 08322e6847
commit 366a3560dc
11 changed files with 181 additions and 141 deletions

View File

@ -8,6 +8,7 @@ import 'package:hl_lieferservice/feature/delivery/overview/model/sorting_informa
import 'package:hl_lieferservice/feature/delivery/repository/tour_repository.dart'; import 'package:hl_lieferservice/feature/delivery/repository/tour_repository.dart';
import 'package:hl_lieferservice/feature/delivery/overview/service/distance_service.dart'; import 'package:hl_lieferservice/feature/delivery/overview/service/distance_service.dart';
import 'package:hl_lieferservice/feature/delivery/overview/service/reorder_service.dart'; import 'package:hl_lieferservice/feature/delivery/overview/service/reorder_service.dart';
import 'package:hl_lieferservice/feature/delivery/util.dart';
import 'package:hl_lieferservice/model/tour.dart'; import 'package:hl_lieferservice/model/tour.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart'; import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart'; import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart';
@ -77,7 +78,7 @@ class TourBloc extends Bloc<TourEvent, TourState> {
event.deliveryId, event.deliveryId,
event.articleId, event.articleId,
event.amount, event.amount,
event.reason event.reason,
); );
opBloc.add(FinishOperation()); opBloc.add(FinishOperation());
@ -106,30 +107,18 @@ class TourBloc extends Bloc<TourEvent, TourState> {
) async { ) async {
final currentState = state; final currentState = state;
if (currentState is TourLoaded) { if (currentState is TourLoaded) {
int newPosition = Map<String, List<String>> container = {...currentState.sortingInformation};
event.newPosition == currentState.sortingInformation.sorting.length
? event.newPosition - 1
: event.newPosition;
SortingInformation informationOld = currentState
.sortingInformation
.sorting
.firstWhere((info) => info.position == event.oldPosition);
SortingInformation information = currentState.sortingInformation.sorting List<String> reorderedList = reorderList(
.firstWhere((info) => info.position == newPosition); container[event.carId.toString()] ?? [],
event.oldPosition,
information.position = event.oldPosition; event.newPosition,
informationOld.position = newPosition;
await ReorderService().saveSortingInformation(
currentState.sortingInformation,
); );
emit( container[event.carId.toString()] = reorderedList;
currentState.copyWith( await ReorderService().saveSortingInformation(container);
sortingInformation: currentState.sortingInformation.copyWith(),
), emit(currentState.copyWith(sortingInformation: container));
);
} }
} }
@ -171,9 +160,7 @@ class TourBloc extends Bloc<TourEvent, TourState> {
) async { ) async {
try { try {
ReorderService service = ReorderService(); ReorderService service = ReorderService();
SortingInformationContainer container = SortingInformationContainer( Map<String, List<String>> container = {};
sorting: [],
);
// Create empty default value if it does not exist yet // Create empty default value if it does not exist yet
if (!service.orderInformationExist()) { if (!service.orderInformationExist()) {
@ -186,25 +173,14 @@ class TourBloc extends Bloc<TourEvent, TourState> {
bool inconsistent = false; bool inconsistent = false;
for (final delivery in event.tour.deliveries) { for (final delivery in event.tour.deliveries) {
int info = container.sorting.indexWhere( int info = container[delivery.carId.toString()]!.indexWhere(
(info) => info.deliveryId == delivery.id, (id) => id == delivery.id,
); );
int max = container.sorting.fold(0, (acc, element) {
if (element.position > acc) {
return element.position;
}
return acc;
});
// not found, so add it to the list // not found, so add it to the list
if (info == -1) { if (info == -1) {
inconsistent = true; inconsistent = true;
container.sorting.add( container[delivery.carId.toString()]!.add(delivery.id);
SortingInformation(
deliveryId: delivery.id,
position: container.sorting.isEmpty ? 0 : max + 1,
),
);
} }
} }
@ -233,13 +209,9 @@ class TourBloc extends Bloc<TourEvent, TourState> {
), ),
); );
SortingInformationContainer container = SortingInformationContainer( Map<String, List<String>> container = {};
sorting: [], for (final delivery in event.tour.deliveries) {
); container[delivery.carId.toString()]!.add(delivery.id);
for (final (index, delivery) in event.tour.deliveries.indexed) {
container.sorting.add(
SortingInformation(deliveryId: delivery.id, position: index),
);
} }
emit( emit(
@ -365,7 +337,9 @@ class TourBloc extends Bloc<TourEvent, TourState> {
); );
break; break;
} }
} on TourNotFoundException { } catch (e, st) {
debugPrint("FEHLER beim Scannen eines Artikels: $e");
debugPrint("$st");
opBloc.add(FailOperation(message: "Fehler beim Scannen des Artikels")); opBloc.add(FailOperation(message: "Fehler beim Scannen des Artikels"));
} }
} }

View File

@ -35,8 +35,9 @@ class RequestSortingInformationEvent extends TourEvent {
class ReorderDeliveryEvent extends TourEvent { class ReorderDeliveryEvent extends TourEvent {
int newPosition; int newPosition;
int oldPosition; int oldPosition;
String carId;
ReorderDeliveryEvent({required this.newPosition, required this.oldPosition}); ReorderDeliveryEvent({required this.newPosition, required this.oldPosition, required this.carId});
} }
class TourUpdated extends TourEvent { class TourUpdated extends TourEvent {

View File

@ -31,7 +31,7 @@ class TourLoaded extends TourState {
Tour tour; Tour tour;
Map<String, double>? distances; Map<String, double>? distances;
List<Payment> paymentOptions; List<Payment> paymentOptions;
SortingInformationContainer sortingInformation; Map<String, List<String>> sortingInformation;
TourLoaded({ TourLoaded({
required this.tour, required this.tour,
@ -44,7 +44,7 @@ class TourLoaded extends TourState {
Tour? tour, Tour? tour,
Map<String, double>? distances, Map<String, double>? distances,
List<Payment>? paymentOptions, List<Payment>? paymentOptions,
SortingInformationContainer? sortingInformation Map<String, List<String>>? sortingInformation
}) { }) {
return TourLoaded( return TourLoaded(
tour: tour ?? this.tour, tour: tour ?? this.tour,

View File

@ -1,3 +1,4 @@
import 'package:flutter/cupertino.dart';
class SortingInformation { class SortingInformation {
String deliveryId; String deliveryId;
@ -18,36 +19,38 @@ class SortingInformation {
} }
class SortingInformationContainer { class SortingInformationContainer {
List<SortingInformation> sorting; Map<String, List<String>> cars;
SortingInformationContainer({required this.sorting}); SortingInformationContainer({required this.cars});
void sort() {
sorting.sort((a, b) => a.position.compareTo(b.position));
}
static SortingInformationContainer fromJson(Map<String, dynamic> json) { static SortingInformationContainer fromJson(Map<String, dynamic> json) {
SortingInformationContainer container = SortingInformationContainer( SortingInformationContainer container = SortingInformationContainer(
sorting: [], cars: {},
); );
for (final info in json["sorting"]) { for (final car in json["cars"].entries) {
container.sorting.add(SortingInformation.fromJson(info)); List<String> values = [];
for (String value in car.value) {
values.add(value);
}
container.cars[car.key] = values;
} }
return container; return container;
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { Map<String, dynamic> cars = {};
"sorting":
sorting.map((info) => SortingInformation.toJson(info)).toList(), for (final car in this.cars.entries) {
}; cars[car.key] = car.value;
} }
SortingInformationContainer copyWith({List<SortingInformation>? sorting}) { return {"cars": cars};
return SortingInformationContainer( }
sorting: sorting ?? this.sorting,
); SortingInformationContainer copyWith({Map<String, List<String>>? sorting}) {
return SortingInformationContainer(cars: sorting ?? cars);
} }
} }

View File

@ -26,18 +26,19 @@ class _DeliveryListState extends State<DeliveryList> {
Widget _showCustomSortedList( Widget _showCustomSortedList(
List<Delivery> deliveries, List<Delivery> deliveries,
List<SortingInformation> sortingInformation, List<String> sortingInformation,
Map<String, double> distances, Map<String, double> distances,
) { ) {
List<SortingInformation> sorted = [...sortingInformation];
sorted.sort((a, b) => a.position.compareTo(b.position));
return ListView.separated( return ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => const Divider(height: 0), separatorBuilder: (context, index) => const Divider(height: 0),
itemBuilder: (context, index) { itemBuilder: (context, index) {
SortingInformation info = sorted[index]; String id = sortingInformation[index];
Delivery delivery = deliveries.firstWhere( Delivery delivery = deliveries.firstWhere(
(delivery) => info.deliveryId == delivery.id, (delivery) =>
id == delivery.id &&
delivery.carId == widget.selectedCarId,
); );
return DeliveryListItem( return DeliveryListItem(
@ -45,7 +46,7 @@ class _DeliveryListState extends State<DeliveryList> {
distance: distances[delivery.id] ?? 0.0, distance: distances[delivery.id] ?? 0.0,
); );
}, },
itemCount: sorted.length, itemCount: sortingInformation.length,
); );
} }
@ -60,12 +61,24 @@ class _DeliveryListState extends State<DeliveryList> {
.where( .where(
(delivery) => (delivery) =>
delivery.carId == widget.selectedCarId && delivery.carId == widget.selectedCarId &&
delivery.allArticlesScanned() || delivery.state == DeliveryState.finished, delivery.allArticlesScanned() &&
delivery.state != DeliveryState.finished,
)
.toList();
List<Delivery> finishedDeliveries =
currentState.tour.deliveries
.where(
(delivery) =>
delivery.state == DeliveryState.finished &&
delivery.carId == widget.selectedCarId,
) )
.toList(); .toList();
if (deliveries.isEmpty) { if (deliveries.isEmpty) {
return ListView( return ListView(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
children: [ children: [
Center(child: const Text("Keine Auslieferungen gefunden")), Center(child: const Text("Keine Auslieferungen gefunden")),
], ],
@ -75,8 +88,8 @@ class _DeliveryListState extends State<DeliveryList> {
switch (widget.sortType) { switch (widget.sortType) {
case SortType.custom: case SortType.custom:
return _showCustomSortedList( return _showCustomSortedList(
deliveries, currentState.tour.deliveries,
currentState.sortingInformation.sorting, currentState.sortingInformation[widget.selectedCarId.toString()] ?? [],
currentState.distances ?? {}, currentState.distances ?? {},
); );
@ -101,8 +114,12 @@ class _DeliveryListState extends State<DeliveryList> {
break; break;
} }
//deliveries.addAll(finishedDeliveries);
return ListView.separated( return ListView.separated(
separatorBuilder: (context, index) => const Divider(height: 0), separatorBuilder: (context, index) => const Divider(height: 0),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) { itemBuilder: (context, index) {
Delivery delivery = deliveries[index]; Delivery delivery = deliveries[index];

View File

@ -101,8 +101,8 @@ class _DeliveryOverviewState extends State<DeliveryOverview> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return RefreshIndicator( return RefreshIndicator(
onRefresh: _loadTour, onRefresh: _loadTour,
child: Column( child: ListView(
crossAxisAlignment: CrossAxisAlignment.start, //crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
DeliveryInfo(tour: widget.tour), DeliveryInfo(tour: widget.tour),
Padding( Padding(
@ -150,7 +150,7 @@ class _DeliveryOverviewState extends State<DeliveryOverview> {
showDialog( showDialog(
context: context, context: context,
fullscreenDialog: true, fullscreenDialog: true,
builder: (context) => CustomSortDialog(), builder: (context) => CustomSortDialog(selectedCarId: _selectedCarId,),
); );
break; break;
} }
@ -195,12 +195,10 @@ class _DeliveryOverviewState extends State<DeliveryOverview> {
padding: const EdgeInsets.only(left: 10, right: 10, bottom: 20), padding: const EdgeInsets.only(left: 10, right: 10, bottom: 20),
child: _carSelection(), child: _carSelection(),
), ),
Expanded( DeliveryList(
child: DeliveryList(
selectedCarId: _selectedCarId, selectedCarId: _selectedCarId,
sortType: _sortType, sortType: _sortType,
), ),
),
], ],
), ),
); );

View File

@ -3,33 +3,34 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/tour_bloc.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/tour_event.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_event.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/tour_state.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_state.dart';
import 'package:hl_lieferservice/feature/delivery/util.dart';
import 'package:hl_lieferservice/model/delivery.dart'; import 'package:hl_lieferservice/model/delivery.dart';
import '../model/sorting_information.dart';
class CustomSortDialog extends StatefulWidget { class CustomSortDialog extends StatefulWidget {
const CustomSortDialog({super.key}); const CustomSortDialog({super.key, this.selectedCarId});
final int? selectedCarId;
@override @override
State<StatefulWidget> createState() => _CustomSortDialogState(); State<StatefulWidget> createState() => _CustomSortDialogState();
} }
class _CustomSortDialogState extends State<CustomSortDialog> { class _CustomSortDialogState extends State<CustomSortDialog> {
late List<SortingInformation> _localSortedList; late List<String> _localSortedList;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
final state = context.read<TourBloc>().state; final state = context.read<TourBloc>().state;
if (state is TourLoaded) { if (state is TourLoaded) {
_localSortedList = [...state.sortingInformation.sorting]; _localSortedList = [
_localSortedList.sort((a, b) => a.position.compareTo(b.position)); ...state.sortingInformation[widget.selectedCarId.toString()] ?? [],
];
} else { } else {
_localSortedList = []; _localSortedList = [];
} }
} }
Widget _information() { Widget _information() {
return Padding( return Padding(
padding: EdgeInsets.only(top: 15), padding: EdgeInsets.only(top: 15),
@ -68,37 +69,36 @@ class _CustomSortDialogState extends State<CustomSortDialog> {
child: ReorderableListView( child: ReorderableListView(
onReorder: (oldIndex, newIndex) { onReorder: (oldIndex, newIndex) {
setState(() { setState(() {
if (oldIndex < newIndex) { _localSortedList = reorderList(
newIndex -= 1; _localSortedList,
} oldIndex,
final SortingInformation item = _localSortedList.removeAt(oldIndex); newIndex,
_localSortedList.insert(newIndex, item); );
}); });
context.read<TourBloc>().add( context.read<TourBloc>().add(
ReorderDeliveryEvent( ReorderDeliveryEvent(
newPosition: newIndex, newPosition: newIndex,
oldPosition: oldIndex, oldPosition: oldIndex,
carId: widget.selectedCarId.toString(),
), ),
); );
}, },
children: children:
_localSortedList.map((info) { _localSortedList
Delivery delivery = currentState.tour.deliveries.firstWhere( .map((id) {
(delivery) => delivery.id == info.deliveryId, Delivery delivery = currentState.tour.deliveries
); .firstWhere((delivery) => delivery.id == id);
SortingInformation information = currentState int pos = _localSortedList.indexOf(id) + 1;
.sortingInformation
.sorting
.firstWhere((info) => info.deliveryId == delivery.id);
return ListTile( return ListTile(
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
child: Text( child: Text(
"${information.position + 1}", "$pos",
style: TextStyle( style: TextStyle(
color: Theme.of(context).colorScheme.onSecondary, color:
Theme.of(context).colorScheme.onSecondary,
), ),
), ),
), ),
@ -110,7 +110,8 @@ class _CustomSortDialogState extends State<CustomSortDialog> {
trailing: Icon(Icons.drag_handle), trailing: Icon(Icons.drag_handle),
key: Key("reorder-item-${delivery.id}"), key: Key("reorder-item-${delivery.id}"),
); );
}).toList(), })
.toList(),
), ),
); );
} }

View File

@ -28,6 +28,8 @@ class _DeliveryOverviewPageState extends State<DeliveryOverviewPage> {
); );
} }
debugPrint(state.toString());
return Container(); return Container();
}, },
); );

View File

@ -1,17 +1,15 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:hl_lieferservice/model/tour.dart'; import 'package:hl_lieferservice/model/tour.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import '../model/sorting_information.dart';
class ReorderService { class ReorderService {
get _path async { get _path async {
final dir = await getApplicationDocumentsDirectory(); final dir = await getApplicationDocumentsDirectory();
final date = DateTime.now(); final date = DateTime.now();
final filename = final filename = "custom_sort_${date.year}_${date.month}_${date.day}.json";
"custom_sort_${date.year}_${date.month}_${date.day}.json";
final path = "${dir.path}/$filename"; final path = "${dir.path}/$filename";
return path; return path;
@ -24,30 +22,53 @@ class ReorderService {
return file; return file;
} }
Future<void> saveSortingInformation(SortingInformationContainer container) async { Future<void> saveSortingInformation(
(await _file).writeAsString(jsonEncode(container.toJson())); Map<String, List<String>> container,
) async {
debugPrint("CONTAINER: ${jsonEncode(container)}");
(await _file).writeAsString(jsonEncode(container));
} }
Future<void> initializeTour(Tour tour) async { Future<void> initializeTour(Tour tour) async {
(await _file).create(); (await _file).create();
SortingInformationContainer container = SortingInformationContainer(sorting: []); Map<String, List<String>> sorting = {};
for (final (index, delivery) in tour.deliveries.indexed) { for (final delivery in tour.deliveries) {
container.sorting.add( if (!sorting.containsKey(delivery.carId.toString())) {
SortingInformation(deliveryId: delivery.id, position: index), sorting[delivery.carId.toString()] = [delivery.id];
); } else {
sorting[delivery.carId.toString()]!.add(delivery.id);
}
} }
(await _file).writeAsString(jsonEncode(container.toJson())); (await _file).writeAsString(jsonEncode({"cars": sorting}));
} }
bool orderInformationExist() { bool orderInformationExist() {
return false; return false;
} }
Future<SortingInformationContainer> loadSortingInformation() async { Future<Map<String, List<String>>> loadSortingInformation() async {
return SortingInformationContainer.fromJson( debugPrint("FILE: ${await (await _file).readAsString()}");
jsonDecode(await (await _file).readAsString()), Map<String, List<String>> container = {};
); Map<String, dynamic> json = jsonDecode(await (await _file).readAsString());
if (!json.containsKey("cars")) {
throw Exception("No cars found in file");
}
for (final car in json["cars"].entries) {
List<String> values = [];
for (String value in car.value) {
values.add(value);
}
container[car.key] = values;
}
return container;
} }
} }

View File

@ -0,0 +1,22 @@
List<String> reorderList(List<String> old, int oldIndex, int newIndex) {
List<String> tmp = [...old];
int newIndexCalc = newIndex - 1;
if (newIndex < oldIndex) {
newIndexCalc = newIndex;
}
if (newIndex == old.length) {
newIndexCalc = old.length - 1;
}
if (newIndex == 0) {
newIndexCalc = 0;
}
String oldItem = tmp.removeAt(oldIndex);
tmp.insert(newIndexCalc, oldItem);
return tmp;
}

View File

@ -287,6 +287,7 @@ class _ArticleScanningScreenState extends State<ArticleScanningScreen> {
isExpanded: true, isExpanded: true,
items: items:
deliveries deliveries
.where((delivery) => delivery.state != DeliveryState.finished)
.mapIndexed( .mapIndexed(
(index, delivery) => DropdownMenuItem( (index, delivery) => DropdownMenuItem(
value: index, value: index,