import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:hl_lieferservice/data/network/backend_config.dart'; import 'package:hl_lieferservice/feature/authentication/bloc/auth_bloc.dart'; import 'package:hl_lieferservice/feature/authentication/bloc/auth_event.dart'; import 'package:hl_lieferservice/feature/settings/bloc/settings_bloc.dart'; import 'package:hl_lieferservice/feature/settings/bloc/settings_event.dart'; import 'package:hl_lieferservice/feature/settings/bloc/settings_state.dart'; import 'package:hl_lieferservice/feature/settings/model/settings.dart'; class SettingsPage extends StatefulWidget { const SettingsPage({super.key}); @override State createState() => _SettingsPage(); } class _SettingsPage extends State { /// Bestätigt das Abmelden und triggert dann `LogoutRequested` am /// [AuthBloc]. Die Navigation zur LoginPage übernimmt der globale /// `LoginEnforcer` automatisch beim Wechsel auf `Unauthenticated` — /// gleicher Pfad wie der Abmelden-Button im Drawer /// (`home_drawer._confirmLogout`). /// /// Wording bewusst identisch zum Drawer-Dialog, damit es egal ist, von /// welchem Einstiegspunkt der Fahrer kommt. Future _logout() async { final authBloc = context.read(); final confirmed = await showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text("Abmelden"), content: const Text( "Möchten Sie sich wirklich abmelden? " "Beim nächsten Start ist eine erneute Anmeldung erforderlich.", ), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(false), child: const Text("Abbrechen"), ), FilledButton( style: FilledButton.styleFrom(backgroundColor: Colors.red), onPressed: () => Navigator.of(dialogContext).pop(true), child: const Text("Abmelden"), ), ], ), ); if (confirmed != true) return; authBloc.add(const LogoutRequested()); } // ─── Konto / Keycloak ───────────────────────────────────────────────── /// Öffnet die Keycloak-Account-Konsole im externen Browser. Dort kann /// der Fahrer sein Passwort ändern, hinterlegte E-Mail aktualisieren /// und (sofern aktiviert) Zwei-Faktor-Authentifizierung verwalten. /// /// URL-Pfad `/account/` ist die Standard-Route der Keycloak-26-Account- /// Konsole; sie hängt sich an den Realm-Issuer aus [BackendConfig]. /// Externer Browser statt In-App-WebView, damit ggf. im Browser /// gespeicherte Credentials / Authenticator-Apps weiter funktionieren. Future _openAccountConsole() async { final issuer = BackendConfig.fromEnvironment.keycloakIssuerUrl; final uri = Uri.parse('$issuer/account/'); final ok = await launchUrl(uri, mode: LaunchMode.externalApplication); if (!ok && mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Kontoeinstellungen konnten nicht geöffnet werden: $uri'), ), ); } } Widget _scanSettings() { return BlocBuilder( builder: (context, state) { final currentState = state; if (currentState is AppSettingsLoaded) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(20), child: Text( "Scaneinstellungen", style: Theme.of(context).textTheme.headlineSmall, ), ), ListTile( title: const Text("Hardware-Scanner"), subtitle: const Text( "Schaltet die Kamera beim Scannen aus und nutzt den Hardware-Scanner", ), trailing: Switch( value: currentState.settings.useHardwareScanner, onChanged: (value) { Settings newSettings = currentState.settings.copyWith(); newSettings.useHardwareScanner = value; context.read().add( UpdateSettings(settings: newSettings), ); }, ), tileColor: Theme.of(context).colorScheme.onSecondary, ), ], ); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(20), child: Text( "Scaneinstellungen", style: Theme.of(context).textTheme.headlineSmall, ), ), Card( color: Theme.of(context).colorScheme.onSecondary, child: Padding( padding: const EdgeInsets.all(20), child: Center( child: Text("Fehler beim Lesen der Scan-Einstellungen"), ), ), ), ], ); }, ); } @override Widget build(BuildContext context) { return Scaffold( body: ListView( children: [ _scanSettings(), Padding( padding: const EdgeInsets.all(20), child: Text( "Kontoeinstellungen", style: Theme.of(context).textTheme.headlineSmall, ), ), ListTile( title: const Text("Konto verwalten"), subtitle: const Text( "Passwort, E-Mail und Sicherheitseinstellungen — öffnet die Konto-Seite im Browser", ), trailing: Padding( padding: const EdgeInsets.all(2), child: FilledButton.icon( onPressed: _openAccountConsole, icon: const Icon(Icons.open_in_new), label: const Text("Öffnen"), ), ), tileColor: Theme.of(context).colorScheme.onSecondary, ), ListTile( title: const Text("Ausloggen"), // Ganze Zeile tappbar machen — sonst muss der Fahrer das kleine // Icon präzise treffen. onTap: _logout, trailing: const Icon(Icons.logout, color: Colors.redAccent), tileColor: Theme.of(context).colorScheme.onSecondary, ), ], ), appBar: AppBar( title: const Text("Einstellungen"), backgroundColor: Theme.of(context).primaryColor, foregroundColor: Theme.of(context).colorScheme.onSecondary, ), ); } }