Merge Firebase_aut branch and resolve pubspec.lock conflict

This commit is contained in:
2026-09-13 15:09:31 +02:00
8 changed files with 401 additions and 155 deletions
+69 -40
View File
@@ -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<AddScreen> {
@@ -24,7 +24,6 @@ class _AddScreenState extends State<AddScreen> {
bool _uploading = false;
int _selectedIndex = 0;
@override
void initState() {
super.initState();
@@ -42,14 +41,13 @@ class _AddScreenState extends State<AddScreen> {
if (originalImage != null) {
int originSize = min(originalImage.width, originalImage.height);
final croppedImage = img.copyCrop(
originalImage,
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<AddScreen> {
if (originalImage != null) {
if (originalImage.width == originalImage.height) {
// Wenn das Bild bereits im 1:1-Format ist, überspringen Sie das Zuschneiden
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);
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<void> addNewElementToFirestore(String title, String description, File imageFile, {required Function(bool) onComplete}) async {
Future<void> 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<AddScreen> {
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<String> uploadImageToFirebaseStorage(File imageFile, String collectionName) async {
Reference ref = FirebaseStorage.instance.ref().child("$collectionName/${DateTime.now().toString()}");
Future<String> 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<AddScreen> {
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) {
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.'),
content: Text(success
? 'Upload erfolgreich.'
: 'Upload fehlgeschlagen.'),
),
);
}
});
}
},
@@ -230,7 +251,18 @@ class _AddScreenState extends State<AddScreen> {
),
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(
@@ -242,8 +274,7 @@ class _AddScreenState extends State<AddScreen> {
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) =>
Column(
builder: (context) => Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
@@ -251,8 +282,8 @@ class _AddScreenState extends State<AddScreen> {
padding: const EdgeInsets.symmetric(
horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment
.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(
'Bild hinzufügen',
@@ -274,8 +305,8 @@ class _AddScreenState extends State<AddScreen> {
child: SizedBox(
height: 80,
child: ListTile(
leading: const Icon(
Icons.photo_library, size: 40),
leading: const Icon(Icons.photo_library,
size: 40),
title: const Text(
'Galerie auswählen',
style: TextStyle(fontSize: 20),
@@ -291,8 +322,8 @@ class _AddScreenState extends State<AddScreen> {
child: SizedBox(
height: 80,
child: ListTile(
leading: const Icon(
Icons.camera_alt, size: 40),
leading: const Icon(Icons.camera_alt,
size: 40),
title: const Text(
'Kamera verwenden',
style: TextStyle(fontSize: 20),
@@ -311,9 +342,7 @@ class _AddScreenState extends State<AddScreen> {
child: Text(
'Bild hinzufügen',
style: TextStyle(
color: Theme
.of(context)
.primaryColor,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
),
+6 -1
View File
@@ -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<LoginPage> {
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<LoginPage> {
);
}
}
+52 -2
View File
@@ -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,6 +212,9 @@ class _GenericListState extends State<GenericList> {
final DateTime createdDate = data['createdDate'].toDate();
final formattedDate =
DateFormat('dd.MM.yyyy').format(createdDate);
final currentUser = FirebaseAuth.instance.currentUser;
if (currentUser != null && data['author_uid'] == currentUser.uid) {
return Dismissible(
key: UniqueKey(),
direction: DismissDirection.endToStart,
@@ -256,6 +263,40 @@ class _GenericListState extends State<GenericList> {
},
),
);
} else {
return 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;
}
});
},
);
}
},
);
}
@@ -295,7 +336,10 @@ class _GenericListState extends State<GenericList> {
],
),
floatingActionButton: FloatingActionButton(
floatingActionButton: Consumer<AuthState>(
builder: (context, authState, child) {
if (authState.user != null) {
return FloatingActionButton(
onPressed: () {
clearSelectedItems();
Navigator.push(
@@ -305,7 +349,13 @@ class _GenericListState extends State<GenericList> {
},
tooltip: "Neues Element hinzufügen",
child: const Icon(Icons.add),
);
} else {
return Container(); // Return an empty container when the user is not signed in
}
},
),
);
}
@@ -318,7 +368,7 @@ class _GenericListState extends State<GenericList> {
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);
+55
View File
@@ -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<void> signInAnonymously() async {
try {
await auth.signInAnonymously();
} catch (e) {
print(e);
}
}
Future<void> signOut() async {
await auth.signOut();
}
}
class AnonymAuthScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Consumer<AuthState>(
builder: (context, authState, child) {
return ElevatedButton(
onPressed: authState.user == null
? authState.signInAnonymously
: authState.signOut,
child: Text(authState.user == null ? 'Anonym anmelden' : 'Abmelden'),
);
},
),
),
);
}
}
+2 -1
View File
@@ -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<MyHomePage> {
const GenericList(collectionName: 'CitizensForum', selectedIndex: 0,),
const GenericList(collectionName: 'News', selectedIndex: 1,),
const GenericList(collectionName: 'Issues', selectedIndex: 2,),
LoginPage()
AnonymAuthScreen()
];
return Scaffold(
+9 -2
View File
@@ -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});
+98
View File
@@ -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<RegisterPage> {
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: <Widget>[
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'),
),
],
),
),
],
),
);
}
}
+1
View File
@@ -51,6 +51,7 @@ dependencies:
printing: ^5.10.4
image_cropper: ^8.0.1
logging: ^1.1.1
provider: ^6.0.5
dev_dependencies: