Initial draft
This commit is contained in:
@ -0,0 +1,151 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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/model/delivery.dart';
|
||||
|
||||
class NoteAddDialog extends StatefulWidget {
|
||||
final String delivery;
|
||||
final List<NoteTemplate> templates;
|
||||
|
||||
const NoteAddDialog({
|
||||
super.key,
|
||||
required this.delivery,
|
||||
required this.templates,
|
||||
});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _NoteAddDialogState();
|
||||
}
|
||||
|
||||
class _NoteAddDialogState extends State<NoteAddDialog> {
|
||||
final _noteController = TextEditingController();
|
||||
final _noteSelectionController = TextEditingController();
|
||||
late FocusNode _noteFieldFocusNode;
|
||||
bool _isCustomNotesEmpty = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_noteFieldFocusNode = FocusNode();
|
||||
|
||||
_noteController.addListener(() {
|
||||
setState(() {
|
||||
_isCustomNotesEmpty = _noteController.text.isEmpty;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _onSave() {
|
||||
String content = _noteController.text;
|
||||
|
||||
if (_noteSelectionController.text.isNotEmpty) {
|
||||
NoteTemplate template = widget.templates.firstWhere(
|
||||
(note) => note.title == _noteSelectionController.text,
|
||||
);
|
||||
|
||||
content = template.content;
|
||||
}
|
||||
|
||||
context.read<NoteBloc>().add(
|
||||
AddNote(note: content, deliveryId: widget.delivery),
|
||||
);
|
||||
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.75,
|
||||
height: MediaQuery.of(context).size.height * 0.45,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Notiz hinzufügen",
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: Icon(Icons.close),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10.0, top: 20),
|
||||
child: DropdownMenu(
|
||||
controller: _noteSelectionController,
|
||||
onSelected: (int? value) {
|
||||
setState(() {
|
||||
_noteSelectionController.text =
|
||||
widget.templates[value!].title;
|
||||
});
|
||||
},
|
||||
enabled: _isCustomNotesEmpty,
|
||||
width: double.infinity,
|
||||
label: const Text("Notiz auswählen"),
|
||||
dropdownMenuEntries:
|
||||
widget.templates
|
||||
.mapIndexed(
|
||||
(i, note) =>
|
||||
DropdownMenuEntry(value: i, label: note.title),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 0.0, bottom: 0.0),
|
||||
child: Text("oder"),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 20.0),
|
||||
child: TextFormField(
|
||||
controller: _noteController,
|
||||
enabled: _noteSelectionController.text.isEmpty,
|
||||
focusNode: _noteFieldFocusNode,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Eigene Notiz",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
minLines: 3,
|
||||
maxLines: 5,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
FilledButton(
|
||||
onPressed:
|
||||
_noteSelectionController.text.isNotEmpty ||
|
||||
_noteController.text.isNotEmpty
|
||||
? _onSave
|
||||
: null,
|
||||
child: const Text("Hinzufügen"),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10.0),
|
||||
child: OutlinedButton(
|
||||
onPressed: null,
|
||||
child: const Text("Zurücksetzen"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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/model/delivery.dart';
|
||||
|
||||
class NoteEditDialog extends StatefulWidget {
|
||||
final Note note;
|
||||
|
||||
const NoteEditDialog({super.key, required this.note});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _NoteEditDialogState();
|
||||
}
|
||||
|
||||
class _NoteEditDialogState extends State<NoteEditDialog> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
late TextEditingController _editController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_editController = TextEditingController(text: widget.note.content);
|
||||
}
|
||||
|
||||
void _onEdit(BuildContext context) {
|
||||
context.read<NoteBloc>().add(
|
||||
EditNote(
|
||||
content: _editController.text,
|
||||
noteId: widget.note.id.toString(),
|
||||
),
|
||||
);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.75,
|
||||
height: MediaQuery.of(context).size.height * 0.32,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
"Notiz bearbeiten",
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 15),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextFormField(
|
||||
onTapOutside: (event) {
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
decoration: InputDecoration(label: const Text("Notiz")),
|
||||
controller: _editController,
|
||||
minLines: 4,
|
||||
maxLines: 8,
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
_onEdit(context);
|
||||
},
|
||||
child: const Text("Bearbeiten"),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text("Abbrechen"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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/model/delivery.dart';
|
||||
|
||||
class NoteImageOverview extends StatefulWidget {
|
||||
final List<(ImageNote, Uint8List)> images;
|
||||
final String deliveryId;
|
||||
|
||||
const NoteImageOverview({
|
||||
super.key,
|
||||
required this.images,
|
||||
required this.deliveryId,
|
||||
});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _NoteImageOverviewState();
|
||||
}
|
||||
|
||||
class _NoteImageOverviewState extends State<NoteImageOverview> {
|
||||
int? _imageDeleting;
|
||||
|
||||
void _onRemoveImage(int index) {
|
||||
ImageNote note = widget.images[index].$1;
|
||||
|
||||
context.read<NoteBloc>().add(
|
||||
RemoveImageNote(objectId: note.objectId, deliveryId: widget.deliveryId),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildImageCarousel() {
|
||||
return CarouselSlider(
|
||||
options: CarouselOptions(
|
||||
height: 300.0,
|
||||
aspectRatio: 2.0,
|
||||
enableInfiniteScroll: false,
|
||||
),
|
||||
items:
|
||||
widget.images.mapIndexed((index, data) {
|
||||
ImageNote note = data.$1;
|
||||
Uint8List bytes = data.$2;
|
||||
|
||||
return Builder(
|
||||
builder: (BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(15.0),
|
||||
child: Image.memory(
|
||||
bytes,
|
||||
fit: BoxFit.fill,
|
||||
width: 1920.0,
|
||||
height: 1090.0,
|
||||
),
|
||||
),
|
||||
_imageDeleting == index
|
||||
? Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(15.0),
|
||||
child: Container(
|
||||
color: Colors.black.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: CircularProgressIndicator(
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Container(),
|
||||
Positioned(
|
||||
right: 0.0,
|
||||
top: 0.0,
|
||||
child: CircleAvatar(
|
||||
radius: 20,
|
||||
child: IconButton.filled(
|
||||
onPressed:
|
||||
!(_imageDeleting == index)
|
||||
? () {
|
||||
_onRemoveImage(index);
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.delete, color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.images.isEmpty
|
||||
? const Center(child: Text("Noch keine Bilder hochgeladen"))
|
||||
: _buildImageCarousel();
|
||||
}
|
||||
}
|
||||
30
lib/feature/delivery/detail/presentation/note/note_list.dart
Normal file
30
lib/feature/delivery/detail/presentation/note/note_list.dart
Normal file
@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/detail/model/note.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/detail/presentation/note/note_list_item.dart';
|
||||
|
||||
class NoteList extends StatelessWidget {
|
||||
final List<NoteInformation> notes;
|
||||
final String deliveryId;
|
||||
|
||||
const NoteList({super.key, required this.notes, required this.deliveryId});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (notes.isEmpty) {
|
||||
return const Center(child: Text("keine Notizen vorhanden"));
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemBuilder:
|
||||
(context, index) => NoteListItem(
|
||||
note: notes[index],
|
||||
deliveryId: deliveryId,
|
||||
index: index,
|
||||
),
|
||||
separatorBuilder: (context, index) => const Divider(height: 0),
|
||||
itemCount: notes.length,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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/model/note.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/detail/presentation/note/note_edit_dialog.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_bloc.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_state.dart';
|
||||
|
||||
import '../../../../../model/delivery.dart';
|
||||
|
||||
class NoteListItem extends StatelessWidget {
|
||||
final NoteInformation note;
|
||||
final String deliveryId;
|
||||
final int index;
|
||||
|
||||
const NoteListItem({
|
||||
super.key,
|
||||
required this.note,
|
||||
required this.deliveryId,
|
||||
required this.index,
|
||||
});
|
||||
|
||||
void _onDelete(BuildContext context) {
|
||||
context.read<NoteBloc>().add(RemoveNote(noteId: note.note.id.toString()));
|
||||
}
|
||||
|
||||
Widget? _subtitle(BuildContext context) {
|
||||
String discountArticleId =
|
||||
(context.read<TourBloc>().state as TourLoaded)
|
||||
.tour
|
||||
.discountArticleNumber;
|
||||
|
||||
if (note.article != null && note.article?.articleNumber == discountArticleId) {
|
||||
return const Text("Begründung der Gutschrift");
|
||||
}
|
||||
|
||||
return note.article != null ? Text(note.article!.name) : null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: ListTile(
|
||||
title: Text(note.note.content),
|
||||
subtitle: _subtitle(context),
|
||||
tileColor: Theme.of(context).colorScheme.surfaceContainerLowest,
|
||||
leading: CircleAvatar(child: Text("${index + 1}")),
|
||||
trailing: PopupMenuButton(
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => NoteEditDialog(note: note.note),
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.edit, color: Colors.blueAccent),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 5),
|
||||
child: const Text("Editieren"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
_onDelete(context);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.delete, color: Colors.redAccent),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 5),
|
||||
child: const Text("Löschen"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
162
lib/feature/delivery/detail/presentation/note/note_overview.dart
Normal file
162
lib/feature/delivery/detail/presentation/note/note_overview.dart
Normal file
@ -0,0 +1,162 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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/model/note.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/detail/presentation/note/note_add_dialog.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/detail/presentation/note/note_image_overview.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/detail/presentation/note/note_list.dart';
|
||||
import 'package:hl_lieferservice/model/delivery.dart';
|
||||
import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart';
|
||||
import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
class NoteOverview extends StatefulWidget {
|
||||
final List<NoteInformation> notes;
|
||||
final List<NoteTemplate> templates;
|
||||
final List<(ImageNote, Uint8List)> images;
|
||||
final String deliveryId;
|
||||
|
||||
const NoteOverview({
|
||||
super.key,
|
||||
required this.notes,
|
||||
required this.deliveryId,
|
||||
required this.templates,
|
||||
required this.images,
|
||||
});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _NoteOverviewState();
|
||||
}
|
||||
|
||||
class _NoteOverviewState extends State<NoteOverview> {
|
||||
final _imagePicker = ImagePicker();
|
||||
|
||||
Widget _notes() {
|
||||
return NoteList(notes: widget.notes, deliveryId: widget.deliveryId);
|
||||
}
|
||||
|
||||
Widget _images() {
|
||||
return NoteImageOverview(
|
||||
images: widget.images,
|
||||
deliveryId: widget.deliveryId,
|
||||
);
|
||||
}
|
||||
|
||||
void _onAddNote(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return NoteAddDialog(
|
||||
delivery: widget.deliveryId,
|
||||
templates: widget.templates,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _onAddImage(BuildContext context) async {
|
||||
XFile? file = await _imagePicker.pickImage(source: ImageSource.camera);
|
||||
if (file == null) {
|
||||
context.read<OperationBloc>().add(
|
||||
FailOperation(message: "Fehler beim Aufnehmen des Bildes"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
context.read<NoteBloc>().add(
|
||||
AddImageNote(file: file, deliveryId: widget.deliveryId),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Text(
|
||||
"Notizen",
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
_notes(),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10, top: 10),
|
||||
child: Text(
|
||||
"Bilder",
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
|
||||
_images(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 25),
|
||||
child: PopupMenuButton(
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.note_add_rounded,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: const Text("Notiz hinzufügen"),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () => _onAddNote(context),
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.image,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: const Text("Bild hochladen"),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () => _onAddImage(context),
|
||||
),
|
||||
];
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
child: CircleAvatar(
|
||||
radius: 32,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
color: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user