import 'package:flutter/material.dart'; import '../login_controller.dart'; import 'login_error_banner.dart'; /// White Card hosting the login form (heading, two text fields, error /// banner, submit button). Submitting calls [controller.submit] and signals /// success via [onSuccess]. class LoginCard extends StatefulWidget { final LoginController controller; final VoidCallback onSuccess; const LoginCard({required this.controller, required this.onSuccess, super.key}); @override State createState() => _LoginCardState(); } class _LoginCardState extends State { final _formKey = GlobalKey(); final _usernameController = TextEditingController(); final _passwordController = TextEditingController(); final _passwordFocus = FocusNode(); @override void initState() { super.initState(); widget.controller.addListener(_onControllerChange); } @override void dispose() { widget.controller.removeListener(_onControllerChange); _usernameController.dispose(); _passwordController.dispose(); _passwordFocus.dispose(); super.dispose(); } void _onControllerChange() { if (mounted) setState(() {}); } String? _required(String? value) => (value ?? '').trim().isEmpty ? 'Eingabe erforderlich' : null; Future _submit() async { if (widget.controller.loading) return; if (!(_formKey.currentState?.validate() ?? false)) return; final ok = await widget.controller.submit( _usernameController.text, _passwordController.text, ); if (ok && mounted) widget.onSuccess(); } InputDecoration _decoration(ThemeData theme, String label, IconData icon) => InputDecoration( labelText: label, prefixIcon: Icon(icon), filled: true, fillColor: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.4), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: theme.colorScheme.primary, width: 1.5), ), ); @override Widget build(BuildContext context) { final theme = Theme.of(context); final loading = widget.controller.loading; return Card( elevation: 8, shadowColor: Colors.black.withValues(alpha: 0.35), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), color: theme.colorScheme.surface, child: Padding( padding: const EdgeInsets.fromLTRB(24, 24, 24, 20), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( 'Anmelden', style: theme.textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w600), ), const SizedBox(height: 6), Text( 'Melde dich mit deinen Marianum-Zugangsdaten an.', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 20), TextFormField( controller: _usernameController, enabled: !loading, validator: _required, autocorrect: false, textInputAction: TextInputAction.next, onFieldSubmitted: (_) => _passwordFocus.requestFocus(), decoration: _decoration(theme, 'Nutzername', Icons.person_outline), ), const SizedBox(height: 12), TextFormField( controller: _passwordController, focusNode: _passwordFocus, enabled: !loading, validator: _required, obscureText: true, obscuringCharacter: '•', autocorrect: false, enableSuggestions: false, keyboardType: TextInputType.visiblePassword, textInputAction: TextInputAction.done, onFieldSubmitted: (_) => _submit(), decoration: _decoration(theme, 'Passwort', Icons.lock_outline), ), LoginErrorBanner( message: widget.controller.errorMessage, details: widget.controller.errorDetails, ), const SizedBox(height: 20), SizedBox( height: 50, child: FilledButton( onPressed: loading ? null : _submit, style: FilledButton.styleFrom( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600), ), child: loading ? const SizedBox( height: 22, width: 22, child: CircularProgressIndicator(strokeWidth: 2.5, color: Colors.white), ) : const Text('Anmelden'), ), ), ], ), ), ), ); } }