import 'package:appwrite/appwrite.dart'; import 'package:flutter/material.dart'; import '../../appwrite_config.dart'; import '../../theme/app_theme.dart'; /// Screen 1: Login & Registrierung (E-Mail / Passwort) über Appwrite. class AuthScreen extends StatefulWidget { const AuthScreen({super.key, required this.onLoggedIn}); final VoidCallback onLoggedIn; @override State createState() => _AuthScreenState(); } class _AuthScreenState extends State with SingleTickerProviderStateMixin { late final TabController _tabController; final _account = Account(appwriteClient); final _loginEmail = TextEditingController(); final _loginPassword = TextEditingController(); final _regName = TextEditingController(); final _regEmail = TextEditingController(); final _regPassword = TextEditingController(); bool _loading = false; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); } @override void dispose() { _tabController.dispose(); _loginEmail.dispose(); _loginPassword.dispose(); _regName.dispose(); _regEmail.dispose(); _regPassword.dispose(); super.dispose(); } void _snack(String text) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(text))); } String _authMessage(AppwriteException e) { final t = e.type ?? ''; final m = (e.message ?? '').toLowerCase(); if (t == 'user_invalid_credentials' || m.contains('invalid credentials') || m.contains('wrong password')) { return 'E-Mail oder Passwort ist falsch.'; } if (t == 'user_already_exists' || m.contains('already exists')) { return 'Diese E-Mail ist bereits registriert.'; } if (m.contains('password') && m.contains('short')) { return 'Passwort ist zu schwach (mindestens 8 Zeichen in Appwrite).'; } if (t == 'general_argument_invalid' && m.contains('email')) { return 'Ungültige E-Mail-Adresse.'; } if (e.code == 0 && m.contains('network')) { return 'Netzwerkfehler. Internet prüfen.'; } return e.message?.isNotEmpty == true ? e.message! : 'Fehler: $t'; } Future _login() async { final email = _loginEmail.text.trim(); final password = _loginPassword.text; if (email.isEmpty || password.isEmpty) { _snack('E-Mail und Passwort eingeben.'); return; } setState(() => _loading = true); try { await _account.createEmailPasswordSession( email: email, password: password, ); if (mounted) widget.onLoggedIn(); } on AppwriteException catch (e) { _snack(_authMessage(e)); } catch (e) { _snack('Unerwarteter Fehler: $e'); } finally { if (mounted) setState(() => _loading = false); } } Future _register() async { final email = _regEmail.text.trim(); final password = _regPassword.text; final name = _regName.text.trim(); if (email.isEmpty || password.isEmpty) { _snack('E-Mail und Passwort eingeben.'); return; } if (password.length < 8) { _snack('Passwort mindestens 8 Zeichen (Appwrite-Standard).'); return; } setState(() => _loading = true); try { await _account.create( userId: ID.unique(), email: email, password: password, name: name.isEmpty ? null : name, ); await _account.createEmailPasswordSession( email: email, password: password, ); if (name.isNotEmpty) { try { await _account.updateName(name: name); } catch (_) {} } if (mounted) widget.onLoggedIn(); } on AppwriteException catch (e) { _snack(_authMessage(e)); } catch (e) { _snack('Unerwarteter Fehler: $e'); } finally { if (mounted) setState(() => _loading = false); } } @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; return Scaffold( backgroundColor: AppTheme.background, body: SafeArea( child: ListView( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), children: [ const SizedBox(height: 24), Icon(Icons.handyman_rounded, size: 48, color: scheme.primary), const SizedBox(height: 16), Text( 'HandwerkPro', textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, color: Colors.white, ), ), const SizedBox(height: 8), Text( 'Aufträge, Fotos, Unterschrift, PDF – alles in einer App. ' 'Anmelden oder registrieren (Appwrite).', textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: const Color(0xFFB0B0B0), ), ), const SizedBox(height: 32), TabBar( controller: _tabController, tabs: const [ Tab(text: 'Anmelden'), Tab(text: 'Registrieren'), ], ), const SizedBox(height: 16), SizedBox( height: 320, child: TabBarView( controller: _tabController, children: [ _LoginForm( email: _loginEmail, password: _loginPassword, loading: _loading, onSubmit: _login, ), _RegisterForm( name: _regName, email: _regEmail, password: _regPassword, loading: _loading, onSubmit: _register, ), ], ), ), ], ), ), ); } } class _LoginForm extends StatelessWidget { const _LoginForm({ required this.email, required this.password, required this.loading, required this.onSubmit, }); final TextEditingController email; final TextEditingController password; final bool loading; final VoidCallback onSubmit; @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextField( controller: email, keyboardType: TextInputType.emailAddress, autocorrect: false, autofillHints: const [AutofillHints.email], decoration: const InputDecoration(labelText: 'E-Mail'), ), const SizedBox(height: 12), TextField( controller: password, obscureText: true, autofillHints: const [AutofillHints.password], decoration: const InputDecoration(labelText: 'Passwort'), onSubmitted: (_) => onSubmit(), ), const Spacer(), FilledButton( onPressed: loading ? null : onSubmit, child: loading ? const SizedBox( height: 22, width: 22, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Anmelden'), ), ], ); } } class _RegisterForm extends StatelessWidget { const _RegisterForm({ required this.name, required this.email, required this.password, required this.loading, required this.onSubmit, }); final TextEditingController name; final TextEditingController email; final TextEditingController password; final bool loading; final VoidCallback onSubmit; @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextField( controller: name, textCapitalization: TextCapitalization.words, decoration: const InputDecoration( labelText: 'Firmen- oder Anzeigename', ), ), const SizedBox(height: 12), TextField( controller: email, keyboardType: TextInputType.emailAddress, autocorrect: false, autofillHints: const [AutofillHints.email], decoration: const InputDecoration(labelText: 'E-Mail'), ), const SizedBox(height: 12), TextField( controller: password, obscureText: true, autofillHints: const [AutofillHints.newPassword], decoration: const InputDecoration( labelText: 'Passwort (min. 8 Zeichen)', ), ), const Spacer(), FilledButton( onPressed: loading ? null : onSubmit, child: loading ? const SizedBox( height: 22, width: 22, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Konto erstellen'), ), ], ); } }