Added custom tour ordering

This commit is contained in:
Dennis Nemec
2025-12-20 21:00:33 +01:00
parent 0c61f65961
commit edb8676f5a
13 changed files with 502 additions and 166 deletions

View File

@ -60,7 +60,7 @@ class _LoginPageState extends State<LoginPage> {
debugPrint("🔵 Opening browser to: http://localhost:3000/login");
final loginUrl = Uri.parse('http://192.168.1.9:3000/login');
final loginUrl = Uri.parse('http://100.72.100.33:3000/login');
final launched = await launchUrl(
loginUrl,
mode: LaunchMode.externalApplication,

View File

@ -56,7 +56,7 @@ class _CarManagementPageState extends State<CarManagementPage> {
return Scaffold(
body: BlocConsumer<CarsBloc, CarsState>(
listener: (context, state) {
if (state is CarsLoaded) {
if (state is CarsLoaded && context.read<TourBloc>().state is TourLoaded) {
var tour = (context.read<TourBloc>().state as TourLoaded).tour.copyWith();
tour.driver.cars = state.cars;
context.read<TourBloc>().add(UpdateTour(tour: tour));

View File

@ -4,6 +4,8 @@ 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';
@ -15,6 +17,7 @@ import '../../../../model/delivery.dart' as model;
class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
OperationBloc opBloc;
NoteBloc noteBloc;
DeliveryRepository repository;
NoteRepository noteRepository;
@ -22,6 +25,7 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
required this.opBloc,
required this.repository,
required this.noteRepository,
required this.noteBloc
}) : super(DeliveryInitial()) {
on<UnscanArticleEvent>(_unscan);
on<ResetScanAmountEvent>(_resetAmount);
@ -34,10 +38,8 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
on<FinishDeliveryEvent>(_finishDelivery);
}
void _finishDelivery(
FinishDeliveryEvent event,
Emitter<DeliveryState> emit,
) async {
void _finishDelivery(FinishDeliveryEvent event,
Emitter<DeliveryState> emit,) async {
final currentState = state;
opBloc.add(LoadOperation());
@ -70,10 +72,8 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
}
}
void _updatePayment(
UpdateSelectedPaymentMethodEvent event,
Emitter<DeliveryState> emit,
) {
void _updatePayment(UpdateSelectedPaymentMethodEvent event,
Emitter<DeliveryState> emit,) {
final currentState = state;
if (currentState is DeliveryLoaded) {
@ -85,25 +85,23 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
}
}
void _updateDeliveryOptions(
UpdateDeliveryOptionEvent event,
Emitter<DeliveryState> emit,
) {
void _updateDeliveryOptions(UpdateDeliveryOptionEvent event,
Emitter<DeliveryState> emit,) {
final currentState = state;
if (currentState is DeliveryLoaded) {
List<model.DeliveryOption> 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");
}
}
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();
return option;
}).toList();
emit(
DeliveryLoaded(
@ -113,10 +111,8 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
}
}
void _updateDiscount(
UpdateDiscountEvent event,
Emitter<DeliveryState> emit,
) async {
void _updateDiscount(UpdateDiscountEvent event,
Emitter<DeliveryState> emit,) async {
opBloc.add(LoadOperation());
try {
@ -139,22 +135,22 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
String discountArticleNumber = delivery.discount!.article.articleNumber;
delivery.discount = model.Discount(
article:
response.values?.article != null
? Article.fromDTO(response.values!.article)
: delivery.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,
response.values?.note != null
? response.values!.note.noteDescription
: delivery.discount!.note,
noteId:
response.values?.note != null
? response.values!.note.rowId
: delivery.discount!.noteId,
response.values?.note != null
? response.values!.note.rowId
: delivery.discount!.noteId,
);
delivery.articles = [
...delivery.articles.where(
(article) => article.articleNumber != discountArticleNumber,
(article) => article.articleNumber != discountArticleNumber,
),
delivery.discount!.article,
];
@ -165,7 +161,8 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
}
} catch (e, st) {
debugPrint(
"Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event.deliveryId}:",
"Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event
.deliveryId}:",
);
debugPrint("$e");
debugPrint("$st");
@ -176,10 +173,8 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
}
}
void _removeDiscount(
RemoveDiscountEvent event,
Emitter<DeliveryState> emit,
) async {
void _removeDiscount(RemoveDiscountEvent event,
Emitter<DeliveryState> emit,) async {
opBloc.add(LoadOperation());
try {
@ -196,9 +191,9 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
delivery.articles
.where(
(article) =>
article.internalId !=
delivery.discount?.article.internalId,
)
article.internalId !=
delivery.discount?.article.internalId,
)
.toList();
delivery.discount = null;
@ -245,6 +240,10 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
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));
@ -253,7 +252,8 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
}
} catch (e, st) {
debugPrint(
"Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event.deliveryId}:",
"Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event
.deliveryId}:",
);
debugPrint("$e");
debugPrint("$st");
@ -284,7 +284,7 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
if (currentState is DeliveryLoaded) {
Article article = currentState.delivery.articles.firstWhere(
(article) => article.internalId == int.parse(event.articleId),
(article) => article.internalId == int.parse(event.articleId),
);
article.removeNoteId = noteId;
@ -293,7 +293,7 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
List<Article> articles = [
...currentState.delivery.articles.where(
(article) => article.internalId != int.parse(event.articleId),
(article) => article.internalId != int.parse(event.articleId),
),
article,
];
@ -313,10 +313,8 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
}
}
void _resetAmount(
ResetScanAmountEvent event,
Emitter<DeliveryState> emit,
) async {
void _resetAmount(ResetScanAmountEvent event,
Emitter<DeliveryState> emit,) async {
opBloc.add(LoadOperation());
try {
@ -325,7 +323,7 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
if (currentState is DeliveryLoaded) {
Article article = currentState.delivery.articles.firstWhere(
(article) => article.internalId == int.parse(event.articleId),
(article) => article.internalId == int.parse(event.articleId),
);
article.removeNoteId = null;
@ -334,7 +332,7 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
List<Article> articles = [
...currentState.delivery.articles.where(
(article) => article.internalId != int.parse(event.articleId),
(article) => article.internalId != int.parse(event.articleId),
),
article,
];

View File

@ -15,7 +15,7 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
final OperationBloc opBloc;
NoteBloc({required this.repository, required this.opBloc})
: super(NoteInitial()) {
: super(NoteInitial()) {
on<LoadNote>(_load);
on<AddNote>(_add);
on<EditNote>(_edit);
@ -23,16 +23,44 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
on<AddImageNote>(_upload);
on<RemoveImageNote>(_removeImage);
on<ResetNotes>(_reset);
on<AddNoteOffline>(_addOffline);
}
Future<void> _reset(ResetNotes event, Emitter<NoteState> emit) async {
emit.call(NoteInitial());
}
Future<void> _removeImage(
RemoveImageNote event,
Emitter<NoteState> emit,
) async {
Future<void> _addOffline(AddNoteOffline event,
Emitter<NoteState> 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<void> _removeImage(RemoveImageNote event,
Emitter<NoteState> emit,) async {
opBloc.add(LoadOperation());
try {
@ -43,9 +71,9 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
emit.call(
currentState.copyWith(
images:
currentState.images
.where((image) => image.$1.objectId != event.objectId)
.toList(),
currentState.images
.where((image) => image.$1.objectId != event.objectId)
.toList(),
),
);
}
@ -89,7 +117,7 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
try {
List<String> urls =
event.delivery.images.map((image) => image.url).toList();
event.delivery.images.map((image) => image.url).toList();
List<Note> notes = await repository.loadNotes(event.delivery.id);
List<NoteTemplate> templates = await repository.loadTemplates();
List<Uint8List> images = await repository.loadImages(urls);
@ -100,7 +128,7 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
templates: templates,
images: List.generate(
images.length,
(index) => (event.delivery.images[index], images[index]),
(index) => (event.delivery.images[index], images[index]),
),
),
);
@ -148,7 +176,7 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
if (currentState is NoteLoaded) {
List<Note> refreshedNotes = [
...currentState.notes.where(
(note) => note.id != int.parse(event.noteId),
(note) => note.id != int.parse(event.noteId),
),
Note(content: event.content, id: int.parse(event.noteId)),
];
@ -173,9 +201,9 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
if (currentState is NoteLoaded) {
List<Note> refreshedNotes =
currentState.notes
.where((note) => note.id != int.parse(event.noteId))
.toList();
currentState.notes
.where((note) => note.id != int.parse(event.noteId))
.toList();
emit.call(currentState.copyWith(notes: refreshedNotes));
}

View File

@ -18,6 +18,14 @@ class AddNote extends NoteEvent {
final String deliveryId;
}
class AddNoteOffline extends NoteEvent {
AddNoteOffline({required this.note, required this.deliveryId, required this.noteId});
final String note;
final String noteId;
final String deliveryId;
}
class RemoveNote extends NoteEvent {
RemoveNote({required this.noteId});

View File

@ -10,14 +10,21 @@ class NoteLoading extends NoteState {}
class NoteLoadingFailed extends NoteState {}
class NoteLoaded extends NoteState {
NoteLoaded({
class NoteLoadedBase extends NoteState {
NoteLoadedBase({
required this.notes,
required this.templates,
required this.images,
});
List<Note> notes;
}
class NoteLoaded extends NoteLoadedBase {
NoteLoaded({
required this.templates,
required this.images,
required super.notes,
});
List<NoteTemplate> templates;
List<(ImageNote, Uint8List)> images;

View File

@ -48,11 +48,29 @@ class _SignatureViewState extends State<SignatureView> {
bool _customerAccepted = false;
bool _noteAccepted = false;
bool _notesEmpty = true;
bool _isCustomerSignatureEmpty = true;
bool _isDriverSignatureEmpty = true;
@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;
});
}
});
// only load notes if they are not already loaded
final noteState = context.read<NoteBloc>().state;
if (noteState is NoteInitial) {
@ -63,6 +81,7 @@ class _SignatureViewState extends State<SignatureView> {
@override
void dispose() {
_customerController.dispose();
_driverController.dispose();
super.dispose();
}
@ -92,12 +111,18 @@ class _SignatureViewState extends State<SignatureView> {
_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 is NoteLoadedBase) {
if (current.notes.isEmpty) {
return const SizedBox(
width: double.infinity,
@ -107,12 +132,12 @@ class _SignatureViewState extends State<SignatureView> {
return ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return ListTile(
leading: const Icon(Icons.event_note_outlined),
title: Text(current.notes[index].content),
contentPadding: EdgeInsets.all(20),
contentPadding: const EdgeInsets.all(20),
tileColor: Theme.of(context).colorScheme.onSecondary,
);
},
@ -121,7 +146,7 @@ class _SignatureViewState extends State<SignatureView> {
);
}
return SizedBox(
return const SizedBox(
width: double.infinity,
child: Center(child: CircularProgressIndicator()),
);
@ -156,10 +181,17 @@ class _SignatureViewState extends State<SignatureView> {
});
},
),
const Flexible(
child: Text(
"Ich nehme die oben genannten Anmerkungen zur Lieferung zur Kenntnis.",
overflow: TextOverflow.fade,
Flexible(
child: InkWell(
onTap: _notesEmpty ? null : () {
setState(() {
_noteAccepted = !_noteAccepted;
});
},
child: Text(
"Ich nehme die oben genannten Anmerkungen zur Lieferung zur Kenntnis.",
overflow: TextOverflow.fade,
),
),
),
],
@ -177,10 +209,17 @@ class _SignatureViewState extends State<SignatureView> {
});
},
),
const Flexible(
child: Text(
"Ich bestätige, dass ich die Ware im ordnungsgemäßen Zustand erhalten habe und, dass die Aufstell- und Einbauarbeiten korrekt durchgeführt wurden.",
overflow: TextOverflow.fade,
Flexible(
child: InkWell(
child: Text(
"Ich bestätige, dass ich die Ware im ordnungsgemäßen Zustand erhalten habe und, dass die Aufstell- und Einbauarbeiten korrekt durchgeführt wurden.",
overflow: TextOverflow.fade,
),
onTap: () {
setState(() {
_customerAccepted = !_customerAccepted;
});
},
),
),
],
@ -195,6 +234,16 @@ class _SignatureViewState extends State<SignatureView> {
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;
}
return Scaffold(
appBar: AppBar(
title:
@ -247,9 +296,8 @@ class _SignatureViewState extends State<SignatureView> {
child: Center(
child: FilledButton(
onPressed:
!(_customerAccepted && (_noteAccepted || _notesEmpty))
? null
: () async {
isButtonEnabled
? () async {
if (!_isDriverSigning) {
setState(() {
_isDriverSigning = true;
@ -260,7 +308,8 @@ class _SignatureViewState extends State<SignatureView> {
(await _driverController.toPngBytes())!,
);
}
},
}
: null,
child:
!_isDriverSigning
? const Text("Weiter")

View File

@ -4,6 +4,7 @@ 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/presentation/delivery_info.dart';
import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_list.dart';
import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_overview_custom_sort.dart';
import 'package:hl_lieferservice/model/tour.dart';
import '../../../../model/delivery.dart';
@ -11,7 +12,11 @@ import '../../../authentication/bloc/auth_bloc.dart';
import '../../../authentication/bloc/auth_state.dart';
class DeliveryOverview extends StatefulWidget {
const DeliveryOverview({super.key, required this.tour, required this.distances});
const DeliveryOverview({
super.key,
required this.tour,
required this.distances,
});
final Tour tour;
final Map<String, double> distances;
@ -44,9 +49,7 @@ class _DeliveryOverviewState extends State<DeliveryOverview> {
}
Future<void> _loadTour() async {
Authenticated state = context
.read<AuthBloc>()
.state as Authenticated;
Authenticated state = context.read<AuthBloc>().state as Authenticated;
context.read<TourBloc>().add(LoadTour(teamId: state.user.number));
}
@ -57,58 +60,47 @@ class _DeliveryOverviewState extends State<DeliveryOverview> {
child: ListView(
scrollDirection: Axis.horizontal,
children:
widget.tour.driver.cars.map((car) {
Color? backgroundColor;
Color? iconColor = Theme
.of(context)
.primaryColor;
Color? textColor;
widget.tour.driver.cars.map((car) {
Color? backgroundColor;
Color? iconColor = Theme.of(context).primaryColor;
Color? textColor;
if (_selectedCarId == car.id) {
backgroundColor = Theme
.of(context)
.primaryColor;
textColor = Theme
.of(context)
.colorScheme
.onSecondary;
iconColor = Theme
.of(context)
.colorScheme
.onSecondary;
}
if (_selectedCarId == car.id) {
backgroundColor = Theme.of(context).primaryColor;
textColor = Theme.of(context).colorScheme.onSecondary;
iconColor = Theme.of(context).colorScheme.onSecondary;
}
return Padding(
padding: const EdgeInsets.only(right: 8),
child: GestureDetector(
onTap: () {
setState(() {
_selectedCarId = car.id;
});
},
child: Chip(
backgroundColor: backgroundColor,
label: Row(
children: [
Icon(Icons.local_shipping, color: iconColor, size: 20),
Padding(
padding: const EdgeInsets.only(left: 5),
child: Text(
car.plate,
style: TextStyle(color: textColor, fontSize: 12),
),
return Padding(
padding: const EdgeInsets.only(right: 8),
child: GestureDetector(
onTap: () {
setState(() {
_selectedCarId = car.id;
});
},
child: Chip(
backgroundColor: backgroundColor,
label: Row(
children: [
Icon(Icons.local_shipping, color: iconColor, size: 20),
Padding(
padding: const EdgeInsets.only(left: 5),
child: Text(
car.plate,
style: TextStyle(color: textColor, fontSize: 12),
),
),
],
),
],
),
),
),
),
);
}).toList(),
);
}).toList(),
),
);
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
@ -118,7 +110,12 @@ class _DeliveryOverviewState extends State<DeliveryOverview> {
children: [
DeliveryInfo(tour: widget.tour),
Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
padding: const EdgeInsets.only(
left: 10,
right: 10,
top: 15,
bottom: 10,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -126,10 +123,7 @@ class _DeliveryOverviewState extends State<DeliveryOverview> {
children: [
Text(
"Fahrten",
style: Theme
.of(context)
.textTheme
.headlineSmall,
style: Theme.of(context).textTheme.headlineSmall,
),
],
),
@ -147,25 +141,41 @@ class _DeliveryOverviewState extends State<DeliveryOverview> {
_deliveries = _deliveries.reversed.toList();
});
}
if (value == "custom") {
showDialog(
context: context,
fullscreenDialog: true,
builder: (context) => CustomSortDialog(),
);
}
if (value == "distance") {
// TODO: muss noch implementiert werden
}
});
},
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: 'name-asc',
child: Text('Name (A-Z)'),
),
PopupMenuItem<String>(
value: 'name-desc',
child: Text('Name (Z-A)'),
),
PopupMenuItem<String>(
value: 'distance',
child: Text('Entfernung'),
),
],
itemBuilder:
(BuildContext context) => <PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: 'name-asc',
child: Text('Name (A-Z)'),
),
PopupMenuItem<String>(
value: 'name-desc',
child: Text('Name (Z-A)'),
),
PopupMenuItem<String>(
value: 'distance',
child: Text('Entfernung'),
),
PopupMenuItem<String>(
value: 'custom',
child: Text('Eigene Sortierung'),
),
],
child: Icon(Icons.filter_list),
)
),
],
),
),
@ -177,13 +187,13 @@ class _DeliveryOverviewState extends State<DeliveryOverview> {
child: DeliveryList(
distances: widget.distances,
deliveries:
_deliveries
.where(
(delivery) =>
delivery.carId == _selectedCarId &&
delivery.allArticlesScanned(),
)
.toList(),
_deliveries
.where(
(delivery) =>
delivery.carId == _selectedCarId &&
delivery.allArticlesScanned(),
)
.toList(),
),
),
],

View File

@ -0,0 +1,113 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_state.dart';
class CustomSortDialog extends StatefulWidget {
const CustomSortDialog({super.key});
@override
State<StatefulWidget> createState() => _CustomSortDialogState();
}
class _CustomSortDialogState extends State<CustomSortDialog> {
Widget _information() {
return Padding(
padding: EdgeInsets.only(top: 15),
child: Column(
children: [
Padding(
padding: EdgeInsets.all(15),
child: Row(
children: [
Padding(
padding: EdgeInsets.only(right: 15),
child: Icon(Icons.info_outline, color: Colors.blueAccent),
),
Expanded(
child: Text(
"Ziehen Sie die einzelnen Lieferungen mit dem Finger in die gewünschte Position.",
),
),
],
),
),
Divider(),
_sortableList(),
],
),
);
}
Widget _sortableList() {
return BlocBuilder<TourBloc, TourState>(
builder: (context, state) {
final currentState = state;
if (currentState is TourLoaded) {
return Expanded(
child: ReorderableListView(
onReorder: (oldIndex, newIndex) {},
children: currentState.tour.deliveries.indexed.fold([], (
acc,
current,
) {
final delivery = current.$2;
final index = current.$1;
acc.add(
ListTile(
leading: CircleAvatar(
backgroundColor: Theme.of(context).primaryColor,
child: Text(
"${index + 1}",
style: TextStyle(
color: Theme.of(context).colorScheme.onSecondary,
),
),
),
title: Text(delivery.customer.name),
subtitle: Text(delivery.customer.address.toString(), style: TextStyle(fontSize: 11),),
trailing: Icon(Icons.drag_handle),
key: Key("reorder-item-${delivery.id}"),
),
);
return acc;
}),
),
);
}
return Center(child: CircularProgressIndicator());
},
);
}
@override
Widget build(BuildContext context) {
return Dialog(
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 20, right: 10, top: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Fahrten sortieren",
style: Theme.of(context).textTheme.headlineSmall,
),
IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.close),
),
],
),
),
Expanded(child: _information()),
],
),
);
}
}

View File

@ -20,7 +20,12 @@ class _DeliveryOverviewPageState extends State<DeliveryOverviewPage> {
if (state is TourLoaded) {
final currentState = state;
return Center(child: DeliveryOverview(tour: currentState.tour, distances: currentState.distances));
return Center(
child: DeliveryOverview(
tour: currentState.tour,
distances: currentState.distances,
),
);
}
return Container();