diff --git a/lib/AddPage.dart b/lib/AddPage.dart index b717bb4..1ab9519 100644 --- a/lib/AddPage.dart +++ b/lib/AddPage.dart @@ -1,19 +1,19 @@ import 'dart:io'; import 'dart:math'; +import 'package:firebase_auth/firebase_auth.dart'; 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 { final int initialIndex; - const AddScreen({super.key, required this.initialIndex}); + const AddScreen({Key? key, required this.initialIndex}) : super(key: key); + @override _AddScreenState createState() => _AddScreenState(); - } class _AddScreenState extends State { @@ -24,7 +24,6 @@ class _AddScreenState extends State { bool _uploading = false; int _selectedIndex = 0; - @override void initState() { super.initState(); @@ -42,14 +41,13 @@ class _AddScreenState extends State { 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, + 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('/')); + 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; @@ -71,33 +69,37 @@ class _AddScreenState extends State { 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"); - }); - + if (mounted) { + 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"); + if (mounted) { + 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.')), + const SnackBar( + content: Text('Die Dateigröße darf maximal 10 MB betragen.')), ); } } } - - - Future addNewElementToFirestore(String title, String description, File imageFile, {required Function(bool) onComplete}) async { + Future addNewElementToFirestore( + String title, String description, File imageFile, + {required Function(bool) onComplete}) async { String collectionName; if (_selectedIndex == 0) { collectionName = "CitizensForum"; @@ -107,36 +109,45 @@ class _AddScreenState extends State { collectionName = "Issues"; } - String imageUrl = await uploadImageToFirebaseStorage(imageFile, collectionName); - try - { + String imageUrl = + await uploadImageToFirebaseStorage(imageFile, collectionName); + String? userId = FirebaseAuth.instance.currentUser?.uid; // get the user ID + + try { await FirebaseFirestore.instance.collection(collectionName).add({ "title": title, "description": description, "imageUrl": imageUrl, "createdDate": DateTime.now(), + "author_uid": userId, // add the user ID to the document }); onComplete(true); - } - catch(e) - { + } catch (e) { onComplete(false); } - } - Future uploadImageToFirebaseStorage(File imageFile, String collectionName) async { - Reference ref = FirebaseStorage.instance.ref().child("$collectionName/${DateTime.now().toString()}"); - UploadTask uploadTask = ref.putFile(imageFile); - return await (await uploadTask).ref.getDownloadURL(); + Future uploadImageToFirebaseStorage( + File imageFile, String collectionName) async { + String? userId = FirebaseAuth.instance.currentUser?.uid; + if (userId != null) { + Reference ref = FirebaseStorage.instance + .ref() + .child("$collectionName/$userId/${DateTime.now().toString()}"); + UploadTask uploadTask = ref.putFile(imageFile); + return await (await uploadTask).ref.getDownloadURL(); + } else { + throw Exception('Cannot upload without user ID'); + } } - @override Widget build(BuildContext context) { String appBarTitle = ''; + double keyboardHeight = MediaQuery.of(context).viewInsets.bottom; + if (_selectedIndex == 0) { appBarTitle = 'Neue BürgerForum Information'; } else if (_selectedIndex == 1) { @@ -166,20 +177,30 @@ class _AddScreenState extends State { child: IconButton( icon: const Icon(Icons.check), onPressed: () async { - if (_formKey.currentState!.validate() && _imageFile != null && !_uploading) { + if (_formKey.currentState!.validate() && + _imageFile != null && + !_uploading) { setState(() { _uploading = true; }); - Navigator.pop(context); // Fenster direkt schließen, nachdem der Upload-Button gedrückt wurde. - await addNewElementToFirestore(_titleController.text, _descriptionController.text, _imageFile!, onComplete: (bool success) { - setState(() { - _uploading = false; - }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(success ? 'Upload erfolgreich.' : 'Upload fehlgeschlagen.'), - ), - ); + Navigator.pop( + context); // Fenster direkt schließen, nachdem der Upload-Button gedrückt wurde. + await addNewElementToFirestore( + _titleController.text, + _descriptionController.text, + _imageFile!, onComplete: (bool success) { + if (mounted) { + setState(() { + _uploading = false; + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(success + ? 'Upload erfolgreich.' + : 'Upload fehlgeschlagen.'), + ), + ); + } }); } }, @@ -230,96 +251,104 @@ class _AddScreenState extends State { ), const SizedBox(height: 16.0), _imageFile != null - ? Image.file(_imageFile!) + ? Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + double imageHeight = + max(0, constraints.maxHeight - keyboardHeight); + return Container( + height: imageHeight, + child: Image.file(_imageFile!), + ); + }, + ), + ) : Container( - height: 200.0, - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: BorderRadius.circular(16.0), - ), - child: Center( - child: TextButton( - onPressed: () { - showModalBottomSheet( - context: context, - builder: (context) => - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - height: 60, - padding: const EdgeInsets.symmetric( - horizontal: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment - .spaceBetween, - children: [ - const Text( - 'Bild hinzufügen', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, + height: 200.0, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(16.0), + ), + child: Center( + child: TextButton( + onPressed: () { + showModalBottomSheet( + context: context, + builder: (context) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 60, + padding: const EdgeInsets.symmetric( + horizontal: 16), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Bild hinzufügen', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), ), - ), - IconButton( - icon: const Icon(Icons.close), - onPressed: () { + IconButton( + icon: const Icon(Icons.close), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + ), + Center( + child: SizedBox( + height: 80, + child: ListTile( + leading: const Icon(Icons.photo_library, + size: 40), + title: const Text( + 'Galerie auswählen', + style: TextStyle(fontSize: 20), + ), + onTap: () { Navigator.pop(context); + _getImage(ImageSource.gallery); }, ), - ], - ), - ), - Center( - child: SizedBox( - height: 80, - child: ListTile( - leading: const Icon( - Icons.photo_library, size: 40), - title: const Text( - 'Galerie auswählen', - style: TextStyle(fontSize: 20), - ), - onTap: () { - Navigator.pop(context); - _getImage(ImageSource.gallery); - }, ), ), - ), - Center( - child: SizedBox( - height: 80, - child: ListTile( - leading: const Icon( - Icons.camera_alt, size: 40), - title: const Text( - 'Kamera verwenden', - style: TextStyle(fontSize: 20), + Center( + child: SizedBox( + height: 80, + child: ListTile( + leading: const Icon(Icons.camera_alt, + size: 40), + title: const Text( + 'Kamera verwenden', + style: TextStyle(fontSize: 20), + ), + onTap: () { + Navigator.pop(context); + _getImage(ImageSource.camera); + }, ), - onTap: () { - Navigator.pop(context); - _getImage(ImageSource.camera); - }, ), ), - ), - ], + ], + ), + ); + }, + child: Text( + 'Bild hinzufügen', + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, ), - ); - }, - child: Text( - 'Bild hinzufügen', - style: TextStyle( - color: Theme - .of(context) - .primaryColor, - fontWeight: FontWeight.bold, + ), + ), ), ), - ), - ), - ), ], ), ), diff --git a/lib/LoginPage.dart b/lib/LoginPage.dart index fdca8a1..4f60ad4 100644 --- a/lib/LoginPage.dart +++ b/lib/LoginPage.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:oggauergemeindetrommler/register.dart'; class LoginPage extends StatefulWidget { LoginPage({Key? key}) : super(key: key); @@ -79,6 +80,11 @@ class _LoginPageState extends State { TextButton( onPressed: () { // Navigiere zur Registrierungsseite + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const RegisterPage()), + ); }, child: const Text('Registrieren'), ), @@ -92,4 +98,3 @@ class _LoginPageState extends State { ); } } - diff --git a/lib/UserDefinedList.dart b/lib/UserDefinedList.dart index 336a2a0..5ae02e9 100644 --- a/lib/UserDefinedList.dart +++ b/lib/UserDefinedList.dart @@ -1,9 +1,11 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:printing/printing.dart'; +import 'package:provider/provider.dart'; import 'AddPage.dart'; import 'DetailPage.dart'; import 'package:intl/intl.dart'; @@ -11,6 +13,8 @@ import 'package:pdf/widgets.dart' as pdfWidgets; import 'package:http/http.dart' as http; import 'package:image/image.dart' as img; +import 'anonym_aut.dart'; + class UserDefinedItem extends StatelessWidget { final String title; final String datum; @@ -208,23 +212,59 @@ class _GenericListState extends State { 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), + final currentUser = FirebaseAuth.instance.currentUser; + + if (currentUser != null && data['author_uid'] == currentUser.uid) { + 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( + child: UserDefinedItem( + title: data['title'] ?? '', + datum: formattedDate, + 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)) { + selectedItems.remove(documentId); + } else { + selectedItems[documentId] = data; + } + }); + }, + ), + ); + } else { + return UserDefinedItem( title: data['title'] ?? '', datum: formattedDate, description: data['description'] ?? '', @@ -254,8 +294,9 @@ class _GenericListState extends State { } }); }, - ), - ); + ); + } + }, ); } @@ -295,17 +336,26 @@ class _GenericListState extends State { ], ), - floatingActionButton: FloatingActionButton( - onPressed: () { - clearSelectedItems(); - Navigator.push( - context, - MaterialPageRoute(builder: (context) => AddScreen(initialIndex: widget.selectedIndex)), - ); + floatingActionButton: Consumer( + builder: (context, authState, child) { + if (authState.user != null) { + return FloatingActionButton( + onPressed: () { + clearSelectedItems(); + Navigator.push( + context, + MaterialPageRoute(builder: (context) => AddScreen(initialIndex: widget.selectedIndex)), + ); + }, + tooltip: "Neues Element hinzufügen", + child: const Icon(Icons.add), + ); + } else { + return Container(); // Return an empty container when the user is not signed in + } }, - tooltip: "Neues Element hinzufügen", - child: const Icon(Icons.add), ), + ); } @@ -318,7 +368,7 @@ class _GenericListState extends State { 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 img.Image resizedImage = img.copyResize(image!, width: 290); // Beispielwert für die Breite final Uint8List resizedBytes = Uint8List.fromList(img.encodeJpg(resizedImage)); final imageProvider = pdfWidgets.MemoryImage(resizedBytes); diff --git a/lib/anonym_aut.dart b/lib/anonym_aut.dart new file mode 100644 index 0000000..1d764cd --- /dev/null +++ b/lib/anonym_aut.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:provider/provider.dart'; + +class AuthState extends ChangeNotifier { + FirebaseAuth auth = FirebaseAuth.instance; + User? _user; + + User? get user => _user; + + AuthState() { + auth.authStateChanges().listen((User? user) { + if (user == null) { + print('User is currently signed out!'); + _user = null; + } else { + print('User is signed in!'); + _user = user; + } + notifyListeners(); + }); + } + + Future signInAnonymously() async { + try { + await auth.signInAnonymously(); + } catch (e) { + print(e); + } + } + + Future signOut() async { + await auth.signOut(); + } +} + +class AnonymAuthScreen extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Consumer( + builder: (context, authState, child) { + return ElevatedButton( + onPressed: authState.user == null + ? authState.signInAnonymously + : authState.signOut, + child: Text(authState.user == null ? 'Anonym anmelden' : 'Abmelden'), + ); + }, + ), + ), + ); + } +} diff --git a/lib/home.dart b/lib/home.dart index 44367e4..f4997e8 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:oggauergemeindetrommler/LoginPage.dart'; import 'UserDefinedList.dart'; +import 'anonym_aut.dart'; class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, required this.title}); @@ -23,7 +24,7 @@ class _MyHomePageState extends State { const GenericList(collectionName: 'CitizensForum', selectedIndex: 0,), const GenericList(collectionName: 'News', selectedIndex: 1,), const GenericList(collectionName: 'Issues', selectedIndex: 2,), - LoginPage() + AnonymAuthScreen() ]; return Scaffold( diff --git a/lib/main.dart b/lib/main.dart index 5374ad6..b7b7e22 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'anonym_aut.dart'; import 'home.dart'; import 'package:firebase_core/firebase_core.dart'; import 'firebase_options.dart'; @@ -8,10 +10,15 @@ void main() async { await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); - runApp(const MyApp()); - + runApp( + ChangeNotifierProvider( + create: (context) => AuthState(), + child: const MyApp(), + ), + ); } + class MyApp extends StatelessWidget { const MyApp({super.key}); diff --git a/lib/register.dart b/lib/register.dart new file mode 100644 index 0000000..2c6e122 --- /dev/null +++ b/lib/register.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; + +class RegisterPage extends StatefulWidget { + const RegisterPage({Key? key}) : super(key: key); + + @override + _RegisterPageState createState() => _RegisterPageState(); +} + +class _RegisterPageState extends State { + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); + final _confirmPasswordController = TextEditingController(); + + @override + void dispose() { + _emailController.dispose(); + _passwordController.dispose(); + _confirmPasswordController.dispose(); + super.dispose(); + } + + bool isValidEmail(String email) { + // Die gleiche Validierung wie in Ihrer LoginPage + return RegExp(r'^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$').hasMatch(email); + } + + bool arePasswordsMatching(String password, String confirmPassword) { + return password == confirmPassword; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Column( + children: [ + TextFormField( + controller: _emailController, + decoration: const InputDecoration( + labelText: 'E-Mail', + ), + validator: (value) { + if (value!.isEmpty) { + return 'Bitte geben Sie eine E-Mail-Adresse ein.'; + } else if (!isValidEmail(value)) { + return 'Bitte geben Sie eine gültige E-Mail-Adresse ein.'; + } + return null; + }, + ), + const SizedBox(height: 16), + TextFormField( + controller: _passwordController, + obscureText: true, + decoration: const InputDecoration( + labelText: 'Passwort', + ), + ), + const SizedBox(height: 16), + TextFormField( + controller: _confirmPasswordController, + obscureText: true, + decoration: const InputDecoration( + labelText: 'Passwort bestätigen', + ), + ), + ElevatedButton( + onPressed: () { + String email = _emailController.text; + String password = _passwordController.text; + String confirmPassword = _confirmPasswordController.text; + if (isValidEmail(email) && arePasswordsMatching(password, confirmPassword)) { + // E-Mail und Passwörter sind gültig, führe die entsprechende Aktion aus + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Ungültige Eingaben'), + duration: Duration(seconds: 2), + ), + ); + } + }, + child: const Text('Registrieren'), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 70c1984..11f15e6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,6 +51,7 @@ dependencies: printing: ^5.10.4 image_cropper: ^8.0.1 logging: ^1.1.1 + provider: ^6.0.5 dev_dependencies: