import 'package:flutter/material.dart'; import '../../../routing/app_routes.dart'; import '../login_controller.dart'; import 'login_error_banner.dart'; import 'login_form_parts.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 result = await widget.controller.submit( _usernameController.text, _passwordController.text, ); if (!mounted) return; switch (result) { case LoginResult.success: widget.onSuccess(); case LoginResult.nextcloudLoginRequired: // 2FA (oder abweichendes NC-Passwort): Anmeldung im Browser über den // Login Flow v2 abschließen; ohne Erfolg wird der Login zurückgerollt. final ok = await AppRoutes.openNextcloudLoginFlow(context); if (!mounted) return; if (ok) { widget.onSuccess(); } else { await widget.controller.abortNextcloudLogin(); } case LoginResult.failure: break; } } @override Widget build(BuildContext context) { final theme = Theme.of(context); final loading = widget.controller.loading; return Form( key: _formKey, child: LoginCardFrame( title: 'Anmelden', hint: 'Melde dich mit deinen Marianum-Zugangsdaten an.', children: [ TextFormField( key: const Key('login-username-field'), controller: _usernameController, enabled: !loading, validator: _required, autocorrect: false, textInputAction: TextInputAction.next, onFieldSubmitted: (_) => _passwordFocus.requestFocus(), decoration: loginInputDecoration( theme, 'Nutzername', Icons.person_outline, ), ), const SizedBox(height: 12), TextFormField( key: const Key('login-password-field'), controller: _passwordController, focusNode: _passwordFocus, enabled: !loading, validator: _required, obscureText: true, obscuringCharacter: '•', autocorrect: false, enableSuggestions: false, keyboardType: TextInputType.visiblePassword, textInputAction: TextInputAction.done, onFieldSubmitted: (_) => _submit(), decoration: loginInputDecoration( theme, 'Passwort', Icons.lock_outline, ), ), LoginErrorBanner( message: widget.controller.errorMessage, details: widget.controller.errorDetails, ), const SizedBox(height: 20), LoginSubmitButton( key: const Key('login-submit-button'), label: 'Anmelden', loading: loading, onPressed: _submit, ), ], ), ); } }