Initial draft

This commit is contained in:
Dennis Nemec
2025-09-20 16:14:06 +02:00
commit b19a6e1cd4
219 changed files with 10317 additions and 0 deletions

View File

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:hl_lieferservice/feature/delivery/detail/presentation/article/article_list_item.dart';
import 'package:hl_lieferservice/model/article.dart';
class ArticleList extends StatefulWidget {
const ArticleList({
super.key,
required this.articles,
required this.deliveryId,
});
final List<Article> articles;
final String deliveryId;
@override
State<StatefulWidget> createState() => _ArticleListState();
}
class _ArticleListState extends State<ArticleList> {
@override
Widget build(BuildContext context) {
return ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder:
(context, index) => ArticleListItem(
article: widget.articles[index],
deliveryId: widget.deliveryId,
),
separatorBuilder: (context, index) => const Divider(height: 0),
itemCount: widget.articles.length,
);
}
}

View File

@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:hl_lieferservice/feature/delivery/detail/presentation/article/article_reset_scan_dialog.dart';
import 'package:hl_lieferservice/feature/delivery/detail/presentation/article/article_unscan_dialog.dart';
import 'package:hl_lieferservice/model/article.dart';
class ArticleListItem extends StatefulWidget {
const ArticleListItem({
super.key,
required this.article,
required this.deliveryId,
});
final Article article;
final String deliveryId;
@override
State<StatefulWidget> createState() => _ArticleListItem();
}
class _ArticleListItem extends State<ArticleListItem> {
Widget _leading() {
int amount = widget.article.getScannedAmount();
Color? color;
Color? textColor;
if (amount == 0) {
color = Colors.redAccent;
textColor = Theme.of(context).colorScheme.onSecondary;
}
return CircleAvatar(
backgroundColor: color,
child: Text("${amount}x", style: TextStyle(color: textColor)),
);
}
@override
Widget build(BuildContext context) {
Widget actionButton = IconButton.outlined(
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(Colors.redAccent),
),
onPressed: () {
showDialog(
context: context,
builder: (context) => ArticleUnscanDialog(article: widget.article),
);
},
icon: Icon(
Icons.delete,
color: Theme.of(context).colorScheme.onSecondary,
),
);
if (widget.article.unscanned()) {
actionButton = IconButton.outlined(
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(Colors.blueAccent),
),
onPressed: () {
showDialog(
context: context,
builder:
(context) => ResetArticleAmountDialog(article: widget.article),
);
},
icon: Icon(
Icons.refresh,
color: Theme.of(context).colorScheme.onSecondary,
),
);
}
return Padding(
padding: const EdgeInsets.all(0),
child: ListTile(
tileColor: Theme.of(context).colorScheme.surfaceContainerLowest,
title: Text(widget.article.name),
leading: _leading(),
subtitle: Text("Artikelnr. ${widget.article.articleNumber}"),
trailing: actionButton,
),
);
}
}

View File

@ -0,0 +1,56 @@
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 '../../../../../model/article.dart';
class ResetArticleAmountDialog extends StatefulWidget {
const ResetArticleAmountDialog({super.key, required this.article});
final Article article;
@override
State<StatefulWidget> createState() => _ResetArticleAmountDialogState();
}
class _ResetArticleAmountDialogState extends State<ResetArticleAmountDialog> {
void _reset() {
context.read<DeliveryBloc>().add(
ResetScanAmountEvent(articleId: widget.article.internalId.toString()),
);
Navigator.pop(context);
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text("Anzahl Artikel zurücksetzen?"),
content: SizedBox(
height: 120,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text("Wollen Sie die entfernten Artikel wieder hinzufügen?"),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FilledButton(
onPressed: _reset,
child: const Text("Zurücksetzen"),
),
OutlinedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text("Abbrechen"),
),
],
),
],
),
),
);
}
}

View File

@ -0,0 +1,153 @@
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 '../../../../../model/article.dart';
class ArticleUnscanDialog extends StatefulWidget {
const ArticleUnscanDialog({super.key, required this.article});
final Article article;
@override
State<StatefulWidget> createState() => _ArticleUnscanDialogState();
}
class _ArticleUnscanDialogState extends State<ArticleUnscanDialog> {
late TextEditingController unscanAmountController;
late TextEditingController unscanNoteController;
bool isValidText = false;
final _formKey = GlobalKey<FormState>();
void _unscan() {
context.read<DeliveryBloc>().add(
UnscanArticleEvent(
articleId: widget.article.internalId.toString(),
newAmount: int.parse(unscanAmountController.text),
reason: unscanNoteController.text,
),
);
Navigator.pop(context);
}
@override
void initState() {
super.initState();
unscanAmountController = TextEditingController(text: "1");
unscanNoteController = TextEditingController(text: "");
unscanNoteController.addListener(() {
setState(() {
isValidText = _isValid();
});
});
unscanAmountController.addListener(() {
setState(() {
isValidText = _isValid();
});
});
}
@override
void dispose() {
unscanAmountController.dispose();
unscanNoteController.dispose();
super.dispose();
}
bool _isValid() {
return _isAmountValid() && unscanNoteController.text.isNotEmpty;
}
bool _isAmountValid() {
final amount = int.tryParse(unscanAmountController.text);
return amount != null && amount > 0 && amount <= widget.article.amount;
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text("Scan rückgängig machen"),
content: SizedBox(
width: double.infinity,
height: 350,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
"Wollen Sie den Scanvorgang des Artikel '${widget.article.name}' rückgängig machen und den Artikel aus der Bestellung entfernen?",
),
Form(
key: _formKey,
autovalidateMode: AutovalidateMode.always,
child: Column(
children: [
TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly,
],
validator: (text) {
if (text == null || text.isEmpty) {
return "Geben Sie eine Zahl ein";
}
final amount = int.tryParse(text);
if (amount == null || amount <= 0) {
return "Geben Sie eine gültige Zahl ein";
}
if (amount > widget.article.amount) {
return "Maximal ${widget.article.amount} möglich.";
}
return null;
},
controller: unscanAmountController,
decoration: const InputDecoration(
labelText: "Menge zu löschender Artikel",
),
),
TextFormField(
controller: unscanNoteController,
keyboardType: TextInputType.text,
decoration: const InputDecoration(
labelText: "Grund für die Entfernung",
),
validator: (text) {
if (text == null || text.isEmpty) {
return "Geben Sie einen Grund an.";
}
return null;
},
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
FilledButton(
onPressed: isValidText ? _unscan : null,
child: const Text("Entfernen"),
),
OutlinedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text("Abbrechen"),
),
],
),
],
),
),
);
}
}