Initial draft
This commit is contained in:
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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"),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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"),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user