289 lines
8.3 KiB
Dart
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'),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|