From 423aae59b1f9a2d98a1584f81bbf3596caaa3860 Mon Sep 17 00:00:00 2001 From: Nicolas Gruber Date: Thu, 11 May 2023 16:57:17 +0200 Subject: [PATCH 1/6] implemented making a pdf from DetailPage.dart and croping image to 1:1 format --- lib/AddPage.dart | 27 ++++++++++++++- lib/DetailPage.dart | 57 ++++++++++++++++++++++++++++++- pubspec.lock | 82 ++++++++++++++++++++++++++++++++++++++++++++- pubspec.yaml | 3 ++ 4 files changed, 166 insertions(+), 3 deletions(-) diff --git a/lib/AddPage.dart b/lib/AddPage.dart index cfe5410..5c3dd7a 100644 --- a/lib/AddPage.dart +++ b/lib/AddPage.dart @@ -1,8 +1,10 @@ import 'dart:io'; +import 'dart:math'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:image/image.dart' as img; class AddScreen extends StatefulWidget { @@ -35,6 +37,27 @@ class _AddScreenState extends State { return fileSize <= maxSizeInBytes; } + Future _cropImage(File imageFile) async { + final originalImage = img.decodeImage(await imageFile.readAsBytes()); + + if (originalImage != null) { + int originSize = min(originalImage.width, originalImage.height); + final croppedImage = img.copyCrop( + originalImage, + x: (originalImage.width - originSize)~/2, + y: (originalImage.height - originSize)~/2, + width: originSize, + height: originSize + ); + final newPath = imageFile.path.substring(0, imageFile.path.lastIndexOf('/')); + final newFile = File('$newPath/cropped.jpg'); + await newFile.writeAsBytes(img.encodePng(croppedImage)); + return newFile; + } else { + throw Exception('Unable to decode image file.'); + } + } + Future _getImage(ImageSource source) async { final pickedFile = await ImagePicker().pickImage(source: source); @@ -42,8 +65,9 @@ class _AddScreenState extends State { final tempFile = File(pickedFile.path); final bool isValid = await isFileSizeValid(tempFile, 10); if (isValid) { + File croppedFile = await _cropImage(tempFile); // Bild zuschneiden setState(() { - _imageFile = tempFile; + _imageFile = croppedFile; }); } else { ScaffoldMessenger.of(context).showSnackBar( @@ -53,6 +77,7 @@ class _AddScreenState extends State { } } + Future addNewElementToFirestore(String title, String description, File imageFile, {required Function(bool) onComplete}) async { String collectionName; if (_selectedIndex == 0) { diff --git a/lib/DetailPage.dart b/lib/DetailPage.dart index 74d97eb..7d0ae5a 100644 --- a/lib/DetailPage.dart +++ b/lib/DetailPage.dart @@ -1,5 +1,9 @@ -import 'package:cached_network_image/cached_network_image.dart'; +import 'dart:typed_data'; +import 'package:pdf/widgets.dart' as pdfWidgets; +import 'package:printing/printing.dart'; import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:cached_network_image/cached_network_image.dart'; class DetailPage extends StatelessWidget { final String title; @@ -15,11 +19,62 @@ class DetailPage extends StatelessWidget { required this.imageUrl, }) : super(key: key); + + Future _printPdf() async { + final pdf = pdfWidgets.Document(); + final image = await _loadNetworkImage(imageUrl); + + pdf.addPage( + pdfWidgets.MultiPage( + build: (context) => [ + pdfWidgets.Center( + child: pdfWidgets.SizedBox( + width: double.infinity, // Die Breite auf die maximal mögliche Breite setzen + child: pdfWidgets.AspectRatio( + aspectRatio: 1, // Sie können das Seitenverhältnis entsprechend Ihrem Bild anpassen + child: pdfWidgets.Image(image), + ), + ), + ), + pdfWidgets.Text( + title, + style: pdfWidgets.TextStyle( + fontSize: 20, + fontWeight: pdfWidgets.FontWeight.bold // Titel fett + ) + ), + pdfWidgets.Text(datum, style: const pdfWidgets.TextStyle(fontSize: 14, )), + pdfWidgets.Paragraph(text: description, style: const pdfWidgets.TextStyle(fontSize: 12)), + ], + ), + ); + await Printing.sharePdf(bytes: await pdf.save(), filename: '$title.pdf'); + } + + Future _loadNetworkImage(String url) async { + final response = await http.get(Uri.parse(url)); + + if (response.statusCode != 200) { + throw Exception('Failed to load network image.'); + } + + final Uint8List bytes = response.bodyBytes; + final imageProvider = pdfWidgets.MemoryImage(bytes); + + return imageProvider; + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), + actions: [ + IconButton( + icon: const Icon(Icons.picture_as_pdf), + onPressed: _printPdf, + ), + ], ), body: SingleChildScrollView( child: Column( diff --git a/pubspec.lock b/pubspec.lock index a63eef7..e53bb1f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,6 +25,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.10.0" + barcode: + dependency: transitive + description: + name: barcode + sha256: "52570564684bbb0240a9f1fdb6bad12adc5e0540103c1c96d6dd550bd928b1c9" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + bidi: + dependency: transitive + description: + name: bidi + sha256: dc00274c7edabae2ab30c676e736ea1eb0b1b7a1b436cb5fe372e431ccb39ab0 + url: "https://pub.dev" + source: hosted + version: "2.0.6" boolean_selector: dependency: transitive description: @@ -312,6 +328,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.16" + image_cropper: + dependency: "direct main" + description: + name: image_cropper + sha256: "710ab4b7953e9ce1d27d833f741e5f8f3afb0b0ba3556dc0b844741b5f55c2b3" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + image_cropper_for_web: + dependency: transitive + description: + name: image_cropper_for_web + sha256: "09e93a8ec0435adcaa23622ac090442872f18145d70b9ff605ffedcf97d56255" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + image_cropper_platform_interface: + dependency: transitive + description: + name: image_cropper_platform_interface + sha256: "62349e3aab63873ea9b9ab9f69d036ab8a0d74b3004beec4303981386cb9273f" + url: "https://pub.dev" + source: hosted + version: "3.0.3" image_picker: dependency: "direct main" description: @@ -416,6 +456,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.2" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" + source: hosted + version: "1.0.1" path_provider: dependency: transitive description: @@ -464,6 +512,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.6" + pdf: + dependency: "direct main" + description: + name: pdf + sha256: "586d3debf5432e5377044754032cfa53ab45e9abf371d4865e9ad5019570e246" + url: "https://pub.dev" + source: hosted + version: "3.10.1" + pdf_widget_wrapper: + dependency: transitive + description: + name: pdf_widget_wrapper + sha256: "754867bf32c4683da29ccc6bf31250905431fb0818a7258c153e21cd627b3212" + url: "https://pub.dev" + source: hosted + version: "1.0.0" pedantic: dependency: transitive description: @@ -504,6 +568,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.7.3" + printing: + dependency: "direct main" + description: + name: printing + sha256: "6aa86779d51f1c60608defee7b231e1133ab9b00f63b3b71abfa85cb39898571" + url: "https://pub.dev" + source: hosted + version: "5.10.4" process: dependency: transitive description: @@ -512,6 +584,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.2.4" + qr: + dependency: transitive + description: + name: qr + sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" rxdart: dependency: transitive description: @@ -647,4 +727,4 @@ packages: version: "6.2.2" sdks: dart: ">=2.19.0 <3.0.0" - flutter: ">=3.3.0" + flutter: ">=3.7.0" diff --git a/pubspec.yaml b/pubspec.yaml index 292470e..79d708c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,9 @@ dependencies: change_app_package_name: ^1.1.0 cached_network_image: ^3.2.3 image: ^4.0.16 + pdf: ^3.10.1 + printing: ^5.10.4 + image_cropper: ^3.0.3 dev_dependencies: From 5d1c30ce9fd8699c86106c02dbd30ce567195807 Mon Sep 17 00:00:00 2001 From: Nicolas Gruber Date: Sat, 13 May 2023 13:06:37 +0200 Subject: [PATCH 2/6] skipped image cropper funktion if it is already a 1:1 image, plugin for logging --- lib/AddPage.dart | 27 +++++++++++++++++++++++---- pubspec.lock | 8 ++++++++ pubspec.yaml | 1 + 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/lib/AddPage.dart b/lib/AddPage.dart index 5c3dd7a..b717bb4 100644 --- a/lib/AddPage.dart +++ b/lib/AddPage.dart @@ -65,10 +65,28 @@ class _AddScreenState extends State { final tempFile = File(pickedFile.path); final bool isValid = await isFileSizeValid(tempFile, 10); if (isValid) { - File croppedFile = await _cropImage(tempFile); // Bild zuschneiden - setState(() { - _imageFile = croppedFile; - }); + // Bildinformationen lesen + final originalImage = img.decodeImage(await tempFile.readAsBytes()); + + if (originalImage != null) { + if (originalImage.width == originalImage.height) { + // Wenn das Bild bereits im 1:1-Format ist, überspringen Sie das Zuschneiden + setState(() { + _imageFile = tempFile; + print("1:1"); + }); + + } else { + // Wenn das Bild nicht im 1:1-Format ist, schneiden Sie es zu + File croppedFile = await _cropImage(tempFile); + setState(() { + _imageFile = croppedFile; + }); + print("decode"); + } + } else { + throw Exception('Unable to decode image file.'); + } } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Die Dateigröße darf maximal 10 MB betragen.')), @@ -78,6 +96,7 @@ class _AddScreenState extends State { } + Future addNewElementToFirestore(String title, String description, File imageFile, {required Function(bool) onComplete}) async { String collectionName; if (_selectedIndex == 0) { diff --git a/pubspec.lock b/pubspec.lock index e53bb1f..c82e205 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -416,6 +416,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + logging: + dependency: "direct main" + description: + name: logging + sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + url: "https://pub.dev" + source: hosted + version: "1.1.1" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 79d708c..31a2529 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,6 +50,7 @@ dependencies: pdf: ^3.10.1 printing: ^5.10.4 image_cropper: ^3.0.3 + logging: ^1.1.1 dev_dependencies: From 5687670e361f0bc1ea01ae096df2f0140f17a294 Mon Sep 17 00:00:00 2001 From: Nicolas Gruber Date: Sat, 13 May 2023 19:39:10 +0200 Subject: [PATCH 3/6] implemented item selection + button to print a list of issues/news etc(Button has no functionality yet). --- lib/UserDefinedList.dart | 265 ++++++++++++++++++++++++++++----------- lib/home.dart | 1 + 2 files changed, 191 insertions(+), 75 deletions(-) diff --git a/lib/UserDefinedList.dart b/lib/UserDefinedList.dart index 357cb00..6658078 100644 --- a/lib/UserDefinedList.dart +++ b/lib/UserDefinedList.dart @@ -3,14 +3,19 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:printing/printing.dart'; import 'DetailPage.dart'; import 'package:intl/intl.dart'; +import 'package:pdf/widgets.dart' as pdfWidgets; +import 'package:http/http.dart' as http; class UserDefinedItem extends StatelessWidget { final String title; final String datum; final String description; final String imageUrl; + final Function onLongPress; + final bool isSelected; const UserDefinedItem({ Key? key, @@ -18,43 +23,62 @@ class UserDefinedItem extends StatelessWidget { required this.description, required this.datum, required this.imageUrl, + required this.onLongPress, + required this.isSelected, }) : super(key: key); + @override Widget build(BuildContext context) { - return Card( - child: InkWell( - onTap: () { - // Open a new screen when the user taps on the list item - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DetailPage( - title: title, - datum: datum, - description: description, - imageUrl: imageUrl, - ), + return InkWell( + onTap: () { + // Open a new screen when the user taps on the list item + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DetailPage( + title: title, + datum: datum, + description: description, + imageUrl: imageUrl, ), - ); - }, + ), + ); + }, + onLongPress: onLongPress as void Function()?, + child: Card( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - width: 120, - height: 120, - child: CachedNetworkImage( - imageUrl: imageUrl, - placeholder: (context, url) => const Center( - child: SizedBox( - width: 50, - height: 50, - child: CircularProgressIndicator(), + Stack( + children: [ + SizedBox( + width: 120, + height: 120, + child: CachedNetworkImage( + imageUrl: imageUrl, + placeholder: (context, url) => const Center( + child: SizedBox( + width: 50, + height: 50, + child: CircularProgressIndicator(), + ), + ), + errorWidget: (context, url, error) => const Icon(Icons.error), ), ), - errorWidget: (context, url, error) => const Icon(Icons.error), - ), + if (isSelected) // Wenn das Element ausgewählt ist, zeigen Sie die Checkbox an + Positioned( + top: 0, + left: 0, + child: Checkbox( + value: isSelected, + onChanged: (bool? value) { + onLongPress(); // Wenn die Checkbox angeklickt wird, rufen Sie die onLongPress-Funktion auf + }, + ), + ), + ], ), Flexible( flex: 1, @@ -104,6 +128,8 @@ class UserDefinedItem extends StatelessWidget { } } + + class GenericList extends StatefulWidget { final String collectionName; const GenericList({Key? key, required this.collectionName}) : super(key: key); @@ -114,6 +140,7 @@ class GenericList extends StatefulWidget { class _GenericListState extends State { final FirebaseFirestore firestore = FirebaseFirestore.instance; + Map selectedItems = {}; @override void initState() { @@ -122,7 +149,7 @@ class _GenericListState extends State { firestore.enablePersistence(); // Enable network firestore.settings = - const Settings(cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED); + const Settings(cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED); } Stream getDocumentStream() { @@ -139,7 +166,9 @@ class _GenericListState extends State { await FirebaseStorage.instance.refFromURL(imageUrl).delete(); } // Löschen Sie das Dokument von der Firestore-Kollektion - await firestore.collection(widget.collectionName).doc(documentId).delete(); + await firestore.collection(widget.collectionName) + .doc(documentId) + .delete(); } catch (e) { if (kDebugMode) { print('Error deleting document: $e'); @@ -149,53 +178,139 @@ class _GenericListState extends State { @override Widget build(BuildContext context) { - return StreamBuilder( - stream: getDocumentStream(), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasError) { - return Text('Error: ${snapshot.error}'); - } - switch (snapshot.connectionState) { - case ConnectionState.waiting: - return const Text('Loading...'); - default: - final List documents = snapshot.data!.docs; - return ListView.builder( - itemCount: documents.length, - itemBuilder: (BuildContext context, int index) { - final Map data = - documents[index].data()! as Map; - final String documentId = documents[index].id; - final DateTime createdDate = data['createdDate'].toDate(); - final formattedDate = - DateFormat('dd.MM.yyyy').format(createdDate); - return Dismissible( - key: UniqueKey(), - direction: DismissDirection.endToStart, - onDismissed: (direction) { - _deleteDocument(documentId, data['imageUrl']); + return Scaffold( + body: Stack( + children: [ + StreamBuilder( + stream: getDocumentStream(), + builder: (BuildContext context, + AsyncSnapshot snapshot) { + if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); + } + switch (snapshot.connectionState) { + case ConnectionState.waiting: + return const Text('Loading...'); + default: + final List documents = snapshot.data! + .docs; + return ListView.builder( + itemCount: documents.length, + itemBuilder: (BuildContext context, int index) { + final Map data = + documents[index].data()! as Map; + final String documentId = documents[index].id; + final DateTime createdDate = data['createdDate'].toDate(); + final formattedDate = + DateFormat('dd.MM.yyyy').format(createdDate); + return Dismissible( + key: UniqueKey(), + direction: DismissDirection.endToStart, + onDismissed: (direction) { + _deleteDocument(documentId, data['imageUrl']); + }, + background: Container( + color: Colors.red, + child: const Align( + alignment: Alignment.centerRight, + child: Padding( + padding: EdgeInsets.only(right: 16), + child: Icon(Icons.delete, color: Colors.white), + ), + ), + ), + child: UserDefinedItem( + title: data['title'] ?? '', + datum: formattedDate, + description: data['description'] ?? '', + imageUrl: data['imageUrl'] ?? '', + isSelected: selectedItems.containsKey(documentId) + ? selectedItems[documentId]! + : false, + onLongPress: () { + // Aktualisieren Sie den ausgewählten Zustand des Elements + setState(() { + selectedItems[documentId] = + !selectedItems.containsKey(documentId) + ? true + : !selectedItems[documentId]!; + }); + }, + ), + ); + }, + ); + } + }, + ), + if (selectedItems.containsValue(true)) + Align( + alignment: Alignment.bottomLeft, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: FloatingActionButton( + onPressed: () { + // Führen Sie hier die gewünschte Aktion aus }, - background: Container( - color: Colors.red, - child: const Align( - alignment: Alignment.centerRight, - child: Padding( - padding: EdgeInsets.only(right: 16), - child: Icon(Icons.delete, color: Colors.white), - ), - ), - ), - child: UserDefinedItem( - title: data['title'] ?? '', - datum: formattedDate, - description: data['description'] ?? '', - imageUrl: data['imageUrl'] ?? '', - ), - ); - }, - ); - } - }, + backgroundColor: Colors.red, + child: const Icon(Icons.picture_as_pdf), + ), + ), + ), + ], + ), ); } + /* + Future _loadNetworkImage(String url) async { + final response = await http.get(Uri.parse(url)); + + if (response.statusCode != 200) { + throw Exception('Failed to load network image.'); + } + + final Uint8List bytes = response.bodyBytes; + final imageProvider = pdfWidgets.MemoryImage(bytes); + + return imageProvider; + } + Future _printPdf() async { + final pdf = pdfWidgets.Document(); + + for (var docId in selectedItems.keys) { + final title = selectedItems[docId]!['title']; + final datum = selectedItems[docId]!['datum']; + final description = selectedItems[docId]!['description']; + final imageUrl = selectedItems[docId]!['imageUrl']; + + final image = await _loadNetworkImage(imageUrl); + + pdf.addPage( + pdfWidgets.MultiPage( + build: (context) => [ + pdfWidgets.Center( + child: pdfWidgets.SizedBox( + width: double.infinity, + child: pdfWidgets.AspectRatio( + aspectRatio: 1, + child: pdfWidgets.Image(image), + ), + ), + ), + pdfWidgets.Text( + title, + style: pdfWidgets.TextStyle( + fontSize: 20, + fontWeight: pdfWidgets.FontWeight.bold + ) + ), + pdfWidgets.Text(datum, style: const pdfWidgets.TextStyle(fontSize: 14, )), + pdfWidgets.Paragraph(text: description, style: const pdfWidgets.TextStyle(fontSize: 12)), + ], + ), + ); + } + + await Printing.sharePdf(bytes: await pdf.save(), filename: 'selectedItems.pdf'); + }*/ } diff --git a/lib/home.dart b/lib/home.dart index aa89715..552fdcb 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -30,6 +30,7 @@ class _MyHomePageState extends State { }); } + @override Widget build(BuildContext context) { final List widgetOptions = [ From 893d095f7562aebedb9c86f33bbc99dee93c2acd Mon Sep 17 00:00:00 2001 From: Nicolas Gruber Date: Sun, 14 May 2023 15:36:29 +0200 Subject: [PATCH 4/6] implemented that a list of issues/news can be exported as pdf. --- lib/UserDefinedList.dart | 178 +++++++++++++++++++++++++++------------ lib/home.dart | 6 +- 2 files changed, 128 insertions(+), 56 deletions(-) diff --git a/lib/UserDefinedList.dart b/lib/UserDefinedList.dart index 6658078..c213309 100644 --- a/lib/UserDefinedList.dart +++ b/lib/UserDefinedList.dart @@ -132,7 +132,9 @@ class UserDefinedItem extends StatelessWidget { class GenericList extends StatefulWidget { final String collectionName; - const GenericList({Key? key, required this.collectionName}) : super(key: key); + final int selectedIndex; + + const GenericList({required this.collectionName, required this.selectedIndex}); @override _GenericListState createState() => _GenericListState(); @@ -140,7 +142,7 @@ class GenericList extends StatefulWidget { class _GenericListState extends State { final FirebaseFirestore firestore = FirebaseFirestore.instance; - Map selectedItems = {}; + Map> selectedItems = {}; @override void initState() { @@ -224,16 +226,14 @@ class _GenericListState extends State { datum: formattedDate, description: data['description'] ?? '', imageUrl: data['imageUrl'] ?? '', - isSelected: selectedItems.containsKey(documentId) - ? selectedItems[documentId]! - : false, + isSelected: selectedItems.containsKey(documentId), onLongPress: () { - // Aktualisieren Sie den ausgewählten Zustand des Elements setState(() { - selectedItems[documentId] = - !selectedItems.containsKey(documentId) - ? true - : !selectedItems[documentId]!; + if (selectedItems.containsKey(documentId)) { + selectedItems.remove(documentId); + } else { + selectedItems[documentId] = data; + } }); }, ), @@ -243,25 +243,42 @@ class _GenericListState extends State { } }, ), - if (selectedItems.containsValue(true)) - Align( - alignment: Alignment.bottomLeft, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: FloatingActionButton( - onPressed: () { - // Führen Sie hier die gewünschte Aktion aus - }, - backgroundColor: Colors.red, - child: const Icon(Icons.picture_as_pdf), + if (selectedItems.isNotEmpty) + Stack( + children: [ + Align( + alignment: Alignment.topRight, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: FloatingActionButton( + backgroundColor: Colors.blue, + onPressed: () { + setState(() { + selectedItems.clear(); + }); + }, + child: const Icon(Icons.refresh), + ), + ), ), - ), + Align( + alignment: Alignment.bottomLeft, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: FloatingActionButton( + backgroundColor: Colors.red, + onPressed: () => _printSelectedItemsAsPdf(widget.selectedIndex), + child: const Icon(Icons.picture_as_pdf), + ), + ), + ), + ], ), ], ), ); } - /* + Future _loadNetworkImage(String url) async { final response = await http.get(Uri.parse(url)); @@ -274,43 +291,98 @@ class _GenericListState extends State { return imageProvider; } - Future _printPdf() async { - final pdf = pdfWidgets.Document(); - for (var docId in selectedItems.keys) { - final title = selectedItems[docId]!['title']; - final datum = selectedItems[docId]!['datum']; - final description = selectedItems[docId]!['description']; - final imageUrl = selectedItems[docId]!['imageUrl']; + Future _printSelectedItemsAsPdf(int selectedIndex) async { + final pdfWidgets.Document pdf = pdfWidgets.Document(); - final image = await _loadNetworkImage(imageUrl); + final List items = []; + for (final entry in selectedItems.entries) { + final String title = entry.value['title'] ?? ''; + final String description = (entry.value['description'] ?? '').length > 200 ? (entry.value['description'] ?? '').substring(0, 200) + '...' : (entry.value['description'] ?? ''); + final String imageUrl = entry.value['imageUrl'] ?? ''; + final DateTime date = entry.value['createdDate'].toDate(); + final String formattedDate = DateFormat('dd.MM.yyyy').format(date); + final pdfWidgets.ImageProvider imageProvider = await _loadNetworkImage(imageUrl); - pdf.addPage( - pdfWidgets.MultiPage( - build: (context) => [ - pdfWidgets.Center( - child: pdfWidgets.SizedBox( - width: double.infinity, - child: pdfWidgets.AspectRatio( - aspectRatio: 1, - child: pdfWidgets.Image(image), + items.add( + pdfWidgets.Container( + height: 150, // Begrenzt die Höhe des Containers + padding: const pdfWidgets.EdgeInsets.all(5.0), // Reduziert den Abstand + child: pdfWidgets.Row( + children: [ + pdfWidgets.Container( + width: 150, // Reduziert die Breite des Bildes + height: 150, // Reduziert die Höhe des Bildes + child: pdfWidgets.Image(imageProvider), + ), + pdfWidgets.Flexible( + child: pdfWidgets.Padding( + padding: const pdfWidgets.EdgeInsets.all(5.0), // Reduziert den Abstand + child: pdfWidgets.Column( + crossAxisAlignment: pdfWidgets.CrossAxisAlignment.start, + children: [ + pdfWidgets.Row( + mainAxisAlignment: pdfWidgets.MainAxisAlignment.spaceBetween, + children: [ + pdfWidgets.Flexible( + child: pdfWidgets.Text( + title, + style: pdfWidgets.TextStyle( + fontSize: 20, fontWeight: pdfWidgets.FontWeight.bold), + ), + ), + pdfWidgets.Text( + formattedDate, + style: const pdfWidgets.TextStyle( + fontSize: 15, + ), + ), + + ], + ), + pdfWidgets.Padding( + padding: const pdfWidgets.EdgeInsets.all(5.0), // Reduziert den Abstand + child: pdfWidgets.Text( + description, + style: const pdfWidgets.TextStyle( + fontSize: 15, + ), + ), + ), + ], + ), ), ), - ), - pdfWidgets.Text( - title, - style: pdfWidgets.TextStyle( - fontSize: 20, - fontWeight: pdfWidgets.FontWeight.bold - ) - ), - pdfWidgets.Text(datum, style: const pdfWidgets.TextStyle(fontSize: 14, )), - pdfWidgets.Paragraph(text: description, style: const pdfWidgets.TextStyle(fontSize: 12)), - ], + ], + ), ), ); } - await Printing.sharePdf(bytes: await pdf.save(), filename: 'selectedItems.pdf'); - }*/ + pdf.addPage( + pdfWidgets.MultiPage( + build: (pdfWidgets.Context context) => items, + ), + ); + + String filename; + switch(selectedIndex) { + case 0: + filename = 'Bürgerforum_items.pdf'; + break; + case 1: + filename = 'Neuigkeiten_items.pdf'; + break; + case 2: + filename = 'Mängel_items.pdf'; + break; + default: + filename = 'selected_items.pdf'; + } + + await Printing.sharePdf(bytes: await pdf.save(), filename: filename); + setState(() { + selectedItems.clear(); + }); + } } diff --git a/lib/home.dart b/lib/home.dart index 552fdcb..be36aa1 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -34,9 +34,9 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { final List widgetOptions = [ - const GenericList(collectionName: 'CitizensForum'), - const GenericList(collectionName: 'News'), - const GenericList(collectionName: 'Issues'), + const GenericList(collectionName: 'CitizensForum', selectedIndex: 0), + const GenericList(collectionName: 'News', selectedIndex: 1), + const GenericList(collectionName: 'Issues', selectedIndex: 2), Column(mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( child: Text(login ? 'Logout' : 'Login'), From 3a50d7f1bc50c6da1c287b33e698b5fe99da0948 Mon Sep 17 00:00:00 2001 From: Nicolas Gruber Date: Mon, 15 May 2023 09:21:43 +0200 Subject: [PATCH 5/6] call clear function when selected index is changed and reduced pdf size by decoded the images before adding it to the pdf --- lib/UserDefinedList.dart | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/UserDefinedList.dart b/lib/UserDefinedList.dart index c213309..5080766 100644 --- a/lib/UserDefinedList.dart +++ b/lib/UserDefinedList.dart @@ -8,6 +8,7 @@ import 'DetailPage.dart'; import 'package:intl/intl.dart'; import 'package:pdf/widgets.dart' as pdfWidgets; import 'package:http/http.dart' as http; +import 'package:image/image.dart' as img; class UserDefinedItem extends StatelessWidget { final String title; @@ -153,6 +154,13 @@ class _GenericListState extends State { firestore.settings = const Settings(cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED); } + @override + void didUpdateWidget(GenericList oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.selectedIndex != oldWidget.selectedIndex) { + selectedItems.clear(); + } + } Stream getDocumentStream() { return firestore @@ -287,7 +295,11 @@ class _GenericListState extends State { } final Uint8List bytes = response.bodyBytes; - final imageProvider = pdfWidgets.MemoryImage(bytes); + final img.Image? image = img.decodeImage(bytes); + final img.Image resizedImage = img.copyResize(image!, width: 500); // Beispielwert für die Breite + final Uint8List resizedBytes = Uint8List.fromList(img.encodeJpg(resizedImage)); + + final imageProvider = pdfWidgets.MemoryImage(resizedBytes); return imageProvider; } From 7062aef03bed3cda17cc3a56f28c3028be165ec4 Mon Sep 17 00:00:00 2001 From: Nicolas Gruber Date: Mon, 15 May 2023 11:24:20 +0200 Subject: [PATCH 6/6] call clear funktion everywhere where it is useful, moved add button from home to user defined list (helpful for using the clearSelectedItems function) --- lib/UserDefinedList.dart | 71 +++++++++++++++++++++++++--------------- lib/home.dart | 13 -------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/lib/UserDefinedList.dart b/lib/UserDefinedList.dart index 5080766..336a2a0 100644 --- a/lib/UserDefinedList.dart +++ b/lib/UserDefinedList.dart @@ -4,6 +4,7 @@ import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:printing/printing.dart'; +import 'AddPage.dart'; import 'DetailPage.dart'; import 'package:intl/intl.dart'; import 'package:pdf/widgets.dart' as pdfWidgets; @@ -17,6 +18,7 @@ class UserDefinedItem extends StatelessWidget { final String imageUrl; final Function onLongPress; final bool isSelected; + final VoidCallback onTap; const UserDefinedItem({ Key? key, @@ -26,26 +28,14 @@ class UserDefinedItem extends StatelessWidget { required this.imageUrl, required this.onLongPress, required this.isSelected, + required this.onTap, }) : super(key: key); @override Widget build(BuildContext context) { return InkWell( - onTap: () { - // Open a new screen when the user taps on the list item - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DetailPage( - title: title, - datum: datum, - description: description, - imageUrl: imageUrl, - ), - ), - ); - }, + onTap:onTap, onLongPress: onLongPress as void Function()?, child: Card( child: Row( @@ -161,6 +151,11 @@ class _GenericListState extends State { selectedItems.clear(); } } + void clearSelectedItems() { + setState(() { + selectedItems.clear(); + }); + } Stream getDocumentStream() { return firestore @@ -235,6 +230,21 @@ class _GenericListState extends State { description: data['description'] ?? '', imageUrl: data['imageUrl'] ?? '', isSelected: selectedItems.containsKey(documentId), + onTap: () { + clearSelectedItems(); + // Open a new screen when the user taps on the list item + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DetailPage( + title: data['title'], + datum: formattedDate, + description: data['description'], + imageUrl: data['imageUrl'], + ), + ), + ); + }, onLongPress: () { setState(() { if (selectedItems.containsKey(documentId)) { @@ -261,10 +271,9 @@ class _GenericListState extends State { child: FloatingActionButton( backgroundColor: Colors.blue, onPressed: () { - setState(() { - selectedItems.clear(); - }); + clearSelectedItems(); }, + tooltip: "Auswahl zurücksetzen", child: const Icon(Icons.refresh), ), ), @@ -276,6 +285,7 @@ class _GenericListState extends State { child: FloatingActionButton( backgroundColor: Colors.red, onPressed: () => _printSelectedItemsAsPdf(widget.selectedIndex), + tooltip: "Ausgewählte Elemente als pdf speichern", child: const Icon(Icons.picture_as_pdf), ), ), @@ -283,6 +293,18 @@ class _GenericListState extends State { ], ), ], + + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + clearSelectedItems(); + Navigator.push( + context, + MaterialPageRoute(builder: (context) => AddScreen(initialIndex: widget.selectedIndex)), + ); + }, + tooltip: "Neues Element hinzufügen", + child: const Icon(Icons.add), ), ); } @@ -293,14 +315,13 @@ class _GenericListState extends State { if (response.statusCode != 200) { throw Exception('Failed to load network image.'); } - final Uint8List bytes = response.bodyBytes; + final img.Image? image = img.decodeImage(bytes); final img.Image resizedImage = img.copyResize(image!, width: 500); // Beispielwert für die Breite final Uint8List resizedBytes = Uint8List.fromList(img.encodeJpg(resizedImage)); final imageProvider = pdfWidgets.MemoryImage(resizedBytes); - return imageProvider; } @@ -310,7 +331,7 @@ class _GenericListState extends State { final List items = []; for (final entry in selectedItems.entries) { final String title = entry.value['title'] ?? ''; - final String description = (entry.value['description'] ?? '').length > 200 ? (entry.value['description'] ?? '').substring(0, 200) + '...' : (entry.value['description'] ?? ''); + final String description = (entry.value['description'] ?? '').length > 165 ? (entry.value['description'] ?? '').substring(0, 165) + '...' : (entry.value['description'] ?? ''); final String imageUrl = entry.value['imageUrl'] ?? ''; final DateTime date = entry.value['createdDate'].toDate(); final String formattedDate = DateFormat('dd.MM.yyyy').format(date); @@ -318,13 +339,13 @@ class _GenericListState extends State { items.add( pdfWidgets.Container( - height: 150, // Begrenzt die Höhe des Containers + height: 145, // Begrenzt die Höhe des Containers padding: const pdfWidgets.EdgeInsets.all(5.0), // Reduziert den Abstand child: pdfWidgets.Row( children: [ pdfWidgets.Container( - width: 150, // Reduziert die Breite des Bildes - height: 150, // Reduziert die Höhe des Bildes + width: 145, // Reduziert die Breite des Bildes + height: 145, // Reduziert die Höhe des Bildes child: pdfWidgets.Image(imageProvider), ), pdfWidgets.Flexible( @@ -393,8 +414,6 @@ class _GenericListState extends State { } await Printing.sharePdf(bytes: await pdf.save(), filename: filename); - setState(() { - selectedItems.clear(); - }); + clearSelectedItems(); } } diff --git a/lib/home.dart b/lib/home.dart index be36aa1..9a9c1c3 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -58,19 +58,6 @@ class _MyHomePageState extends State { body: Center( child: widgetOptions.elementAt(_selectedIndex), ), - floatingActionButton: _selectedIndex == 3 // Wenn der ausgewählte Index 3 ist, also auf dem Profil-Widget - ? null // Zeigt keinen Button an - : FloatingActionButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AddScreen(initialIndex: _selectedIndex)), - ); - }, - tooltip: "Add", - child: const Icon(Icons.add), - ), bottomNavigationBar: BottomNavigationBar( items: const [ BottomNavigationBarItem(