Files
Handwerks_app/screens/auth/auth_screen.dart
2026-04-03 20:42:47 +02:00

289 lines
8.3 KiB
Dart

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
/// Screen 1: Login & Registrierung (E-Mail / Passwort) über Firebase Auth.
class AuthScreen extends StatefulWidget {
const AuthScreen({super.key});
@override
State<AuthScreen> createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen>
with SingleTickerProviderStateMixin {
late final TabController _tabController;
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(FirebaseAuthException e) {
switch (e.code) {
case 'user-not-found':
case 'wrong-password':
case 'invalid-credential':
return 'E-Mail oder Passwort ist falsch.';
case 'email-already-in-use':
return 'Diese E-Mail ist bereits registriert.';
case 'weak-password':
return 'Passwort ist zu schwach (mindestens 6 Zeichen).';
case 'invalid-email':
return 'Ungültige E-Mail-Adresse.';
case 'network-request-failed':
return 'Netzwerkfehler. Internet prüfen.';
default:
return e.message ?? 'Fehler: ${e.code}';
}
}
Future<void> _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 FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
_snack(_authMessage(e));
} catch (e) {
_snack('Unerwarteter Fehler: $e');
} finally {
if (mounted) setState(() => _loading = false);
}
}
Future<void> _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 < 6) {
_snack('Passwort mindestens 6 Zeichen (Firebase).');
return;
}
setState(() => _loading = true);
try {
final cred = await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: email,
password: password,
);
if (name.isNotEmpty && cred.user != null) {
await cred.user!.updateDisplayName(name);
}
} on FirebaseAuthException 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(
body: SafeArea(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
children: [
const SizedBox(height: 24),
Icon(Icons.construction, size: 48, color: scheme.primary),
const SizedBox(height: 16),
Text(
'Handwerksapp',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'Nach dem Login: Aufträge anlegen, Fotos, Kunden-Unterschrift, PDF teilen.\n'
'Zuerst: Konto anlegen oder anmelden.',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: scheme.onSurfaceVariant,
),
),
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. 6 Zeichen)'),
),
const Spacer(),
FilledButton(
onPressed: loading ? null : onSubmit,
child: loading
? const SizedBox(
height: 22,
width: 22,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Konto erstellen'),
),
],
);
}
}