From ecd362928d9104c3160ddb4409b760c5fad5a986 Mon Sep 17 00:00:00 2001 From: joschy2002 Date: Thu, 15 May 2025 18:13:56 +0200 Subject: [PATCH] Login/Regestrierung mit DB --- trainerbox/lib/main.dart | 26 +++- trainerbox/lib/screens/home_screen.dart | 15 ++- trainerbox/lib/screens/login_screen.dart | 127 ++++++++++++++++++ trainerbox/lib/screens/profile_tab.dart | 34 ++--- .../Flutter/GeneratedPluginRegistrant.swift | 4 + trainerbox/pubspec.lock | 86 +++++++++++- trainerbox/pubspec.yaml | 4 +- .../flutter/generated_plugin_registrant.cc | 6 + .../windows/flutter/generated_plugins.cmake | 2 + 9 files changed, 271 insertions(+), 33 deletions(-) create mode 100644 trainerbox/lib/screens/login_screen.dart diff --git a/trainerbox/lib/main.dart b/trainerbox/lib/main.dart index 382a10d..0317838 100644 --- a/trainerbox/lib/main.dart +++ b/trainerbox/lib/main.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import 'firebase_options.dart'; import 'screens/home_screen.dart'; +import 'screens/login_screen.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -11,9 +12,28 @@ void main() async { runApp(const MyApp()); } -class MyApp extends StatelessWidget { +class MyApp extends StatefulWidget { const MyApp({super.key}); + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + bool _loggedIn = false; + + void _handleLoginSuccess() { + setState(() { + _loggedIn = true; + }); + } + + void _handleLogoutSuccess() { + setState(() { + _loggedIn = false; + }); + } + @override Widget build(BuildContext context) { return MaterialApp( @@ -23,7 +43,9 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.pink), useMaterial3: true, ), - home: const HomeScreen(), + home: _loggedIn + ? HomeScreen(onLogoutSuccess: _handleLogoutSuccess) + : LoginScreen(onLoginSuccess: _handleLoginSuccess), ); } } diff --git a/trainerbox/lib/screens/home_screen.dart b/trainerbox/lib/screens/home_screen.dart index 59ae37b..6ab8aa5 100644 --- a/trainerbox/lib/screens/home_screen.dart +++ b/trainerbox/lib/screens/home_screen.dart @@ -8,7 +8,8 @@ import 'calendar_tab.dart'; import 'profile_tab.dart'; class HomeScreen extends StatefulWidget { - const HomeScreen({super.key}); + final VoidCallback? onLogoutSuccess; + const HomeScreen({super.key, this.onLogoutSuccess}); @override State createState() => _HomeScreenState(); @@ -17,12 +18,12 @@ class HomeScreen extends StatefulWidget { class _HomeScreenState extends State { int _selectedIndex = 0; - final List _screens = const [ - HomeTab(), - SearchTab(), - FavoritesTab(), - CalendarTab(), - ProfileTab(), + List get _screens => [ + const HomeTab(), + const SearchTab(), + const FavoritesTab(), + const CalendarTab(), + ProfileTab(onLogoutSuccess: widget.onLogoutSuccess), ]; void _onItemTapped(int index) { diff --git a/trainerbox/lib/screens/login_screen.dart b/trainerbox/lib/screens/login_screen.dart new file mode 100644 index 0000000..8cd23d4 --- /dev/null +++ b/trainerbox/lib/screens/login_screen.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; + +class LoginScreen extends StatefulWidget { + final void Function() onLoginSuccess; + const LoginScreen({super.key, required this.onLoginSuccess}); + + @override + State createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State { + final _formKey = GlobalKey(); + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); + String? _error; + bool _loading = false; + bool _isLogin = true; + + Future _submit() async { + setState(() { _loading = true; _error = null; }); + try { + if (_isLogin) { + // Login + UserCredential cred = await FirebaseAuth.instance.signInWithEmailAndPassword( + email: _emailController.text.trim(), + password: _passwordController.text.trim(), + ); + // Firestore-Check + final uid = cred.user!.uid; + final userDoc = await FirebaseFirestore.instance.collection('User').doc(uid).get(); + if (userDoc.exists) { + widget.onLoginSuccess(); + } else { + setState(() { _error = 'Kein Benutzerprofil in der Datenbank gefunden!'; }); + await FirebaseAuth.instance.signOut(); + } + } else { + // Registrierung + UserCredential cred = await FirebaseAuth.instance.createUserWithEmailAndPassword( + email: _emailController.text.trim(), + password: _passwordController.text.trim(), + ); + // User-Datensatz in Firestore anlegen + final uid = cred.user!.uid; + await FirebaseFirestore.instance.collection('User').doc(uid).set({ + 'email': _emailController.text.trim(), + 'createdAt': FieldValue.serverTimestamp(), + }); + widget.onLoginSuccess(); + } + } on FirebaseAuthException catch (e) { + setState(() { _error = e.message ?? 'Fehler'; }); + } catch (e) { + setState(() { _error = 'Unbekannter Fehler'; }); + } finally { + setState(() { _loading = false; }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(_isLogin ? 'Login' : 'Registrieren', style: Theme.of(context).textTheme.headlineMedium), + const SizedBox(height: 32), + TextFormField( + controller: _emailController, + decoration: const InputDecoration(labelText: 'E-Mail'), + keyboardType: TextInputType.emailAddress, + validator: (v) => v != null && v.contains('@') ? null : 'Gib eine gültige E-Mail ein', + ), + const SizedBox(height: 16), + TextFormField( + controller: _passwordController, + decoration: const InputDecoration(labelText: 'Passwort'), + obscureText: true, + validator: (v) => v != null && v.length >= 6 ? null : 'Mind. 6 Zeichen', + ), + const SizedBox(height: 24), + if (_error != null) ...[ + Text(_error!, style: const TextStyle(color: Colors.red)), + const SizedBox(height: 12), + ], + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _loading + ? null + : () { + if (_formKey.currentState!.validate()) { + _submit(); + } + }, + child: _loading + ? const CircularProgressIndicator() + : Text(_isLogin ? 'Login' : 'Registrieren'), + ), + ), + const SizedBox(height: 16), + TextButton( + onPressed: _loading + ? null + : () { + setState(() { + _isLogin = !_isLogin; + _error = null; + }); + }, + child: Text(_isLogin ? 'Noch keinen Account? Jetzt registrieren!' : 'Schon registriert? Jetzt einloggen!'), + ), + ], + ), + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/trainerbox/lib/screens/profile_tab.dart b/trainerbox/lib/screens/profile_tab.dart index 6bd9bd0..7123c5c 100644 --- a/trainerbox/lib/screens/profile_tab.dart +++ b/trainerbox/lib/screens/profile_tab.dart @@ -1,14 +1,24 @@ import 'package:flutter/material.dart'; +import 'package:firebase_auth/firebase_auth.dart'; class ProfileTab extends StatelessWidget { - const ProfileTab({super.key}); + final VoidCallback? onLogoutSuccess; + const ProfileTab({super.key, this.onLogoutSuccess}); + + Future _logout(BuildContext context) async { + await FirebaseAuth.instance.signOut(); + if (onLogoutSuccess != null) { + onLogoutSuccess!(); + } + } @override Widget build(BuildContext context) { - // Beispiel-Benutzerdaten + final user = FirebaseAuth.instance.currentUser; + final email = user?.email ?? 'Keine E-Mail gefunden'; + // Beispiel-Benutzerdaten (kannst du später dynamisch machen) final Map userData = { 'name': 'Max Mustermann', - 'email': 'max.mustermann@example.com', 'level': 'Fortgeschritten', 'joinedDate': '01.01.2024', 'workoutsCompleted': 42, @@ -66,7 +76,7 @@ class ProfileTab extends StatelessWidget { title: 'Persönliche Informationen', child: Column( children: [ - _buildInfoRow('E-Mail', userData['email'], Icons.email), + _buildInfoRow('E-Mail', email, Icons.email), const Divider(), _buildInfoRow('Level', userData['level'], Icons.star), const Divider(), @@ -88,9 +98,7 @@ class ProfileTab extends StatelessWidget { title: const Text('Benachrichtigungen'), trailing: Switch( value: true, - onChanged: (value) { - // TODO: Implement notification settings - }, + onChanged: (value) {}, ), ), const Divider(), @@ -99,9 +107,7 @@ class ProfileTab extends StatelessWidget { title: const Text('Dark Mode'), trailing: Switch( value: false, - onChanged: (value) { - // TODO: Implement dark mode - }, + onChanged: (value) {}, ), ), const Divider(), @@ -109,9 +115,7 @@ class ProfileTab extends StatelessWidget { leading: const Icon(Icons.language), title: const Text('Sprache'), trailing: const Text('Deutsch'), - onTap: () { - // TODO: Implement language selection - }, + onTap: () {}, ), ], ), @@ -119,9 +123,7 @@ class ProfileTab extends StatelessWidget { const SizedBox(height: 16), Center( child: TextButton.icon( - onPressed: () { - // TODO: Implement logout - }, + onPressed: () => _logout(context), icon: const Icon(Icons.logout), label: const Text('Abmelden'), style: TextButton.styleFrom(foregroundColor: Colors.red), diff --git a/trainerbox/macos/Flutter/GeneratedPluginRegistrant.swift b/trainerbox/macos/Flutter/GeneratedPluginRegistrant.swift index e46c39f..804ef2f 100644 --- a/trainerbox/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/trainerbox/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,12 @@ import FlutterMacOS import Foundation +import cloud_firestore +import firebase_auth import firebase_core func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) + FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) } diff --git a/trainerbox/pubspec.lock b/trainerbox/pubspec.lock index 8a9c140..3f1f3e5 100644 --- a/trainerbox/pubspec.lock +++ b/trainerbox/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "37a42d06068e2fe3deddb2da079a8c4d105f241225ba27b7122b37e9865fd8f7" + url: "https://pub.dev" + source: hosted + version: "1.3.35" async: dependency: transitive description: @@ -33,6 +41,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + sha256: a0f161b92610e078b4962d7e6ebeb66dc9cce0ada3514aeee442f68165d78185 + url: "https://pub.dev" + source: hosted + version: "4.17.5" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + sha256: "6a55b319f8d33c307396b9104512e8130a61904528ab7bd8b5402678fca54b81" + url: "https://pub.dev" + source: hosted + version: "6.2.5" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + sha256: "89dfa1304d3da48b3039abbb2865e3d30896ef858e569a16804a99f4362283a9" + url: "https://pub.dev" + source: hosted + version: "3.12.5" collection: dependency: transitive description: @@ -57,14 +89,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: cfc2d970829202eca09e2896f0a5aa7c87302817ecc0bdfa954f026046bf10ba + url: "https://pub.dev" + source: hosted + version: "4.20.0" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: a0270e1db3b2098a14cb2a2342b3cd2e7e458e0c391b1f64f6f78b14296ec093 + url: "https://pub.dev" + source: hosted + version: "7.3.0" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: "64e067e763c6378b7e774e872f0f59f6812885e43020e25cde08f42e9459837b" + url: "https://pub.dev" + source: hosted + version: "5.12.0" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "017d17d9915670e6117497e640b2859e0b868026ea36bf3a57feb28c3b97debe" + sha256: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c" url: "https://pub.dev" source: hosted - version: "3.13.0" + version: "2.32.0" firebase_core_platform_interface: dependency: transitive description: @@ -77,10 +133,10 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: "129a34d1e0fb62e2b488d988a1fc26cc15636357e50944ffee2862efe8929b23" + sha256: "362e52457ed2b7b180964769c1e04d1e0ea0259fdf7025fdfedd019d4ae2bd88" url: "https://pub.dev" source: hosted - version: "2.22.0" + version: "2.17.5" flutter: dependency: "direct main" description: flutter @@ -104,6 +160,14 @@ packages: description: flutter source: sdk version: "0.0.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" intl: dependency: transitive description: @@ -253,6 +317,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -273,10 +345,10 @@ packages: dependency: transitive description: name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "0.5.1" sdks: dart: ">=3.7.2 <4.0.0" - flutter: ">=3.22.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/trainerbox/pubspec.yaml b/trainerbox/pubspec.yaml index 7cd8d89..88e1a1f 100644 --- a/trainerbox/pubspec.yaml +++ b/trainerbox/pubspec.yaml @@ -34,8 +34,10 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 - firebase_core: ^3.13.0 + firebase_core: ^2.32.0 table_calendar: ^3.0.9 + cloud_firestore: ^4.17.3 + firebase_auth: ^4.17.4 dev_dependencies: flutter_test: diff --git a/trainerbox/windows/flutter/generated_plugin_registrant.cc b/trainerbox/windows/flutter/generated_plugin_registrant.cc index 1a82e7d..bf6d21a 100644 --- a/trainerbox/windows/flutter/generated_plugin_registrant.cc +++ b/trainerbox/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,15 @@ #include "generated_plugin_registrant.h" +#include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + CloudFirestorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("CloudFirestorePluginCApi")); + FirebaseAuthPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi")); FirebaseCorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); } diff --git a/trainerbox/windows/flutter/generated_plugins.cmake b/trainerbox/windows/flutter/generated_plugins.cmake index fa8a39b..b83b40a 100644 --- a/trainerbox/windows/flutter/generated_plugins.cmake +++ b/trainerbox/windows/flutter/generated_plugins.cmake @@ -3,6 +3,8 @@ # list(APPEND FLUTTER_PLUGIN_LIST + cloud_firestore + firebase_auth firebase_core )