diff --git a/lib/feature/settings/presentation/settings_page.dart b/lib/feature/settings/presentation/settings_page.dart index 13491b5..e00bed8 100644 --- a/lib/feature/settings/presentation/settings_page.dart +++ b/lib/feature/settings/presentation/settings_page.dart @@ -1,5 +1,10 @@ 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'; @@ -13,9 +18,63 @@ class SettingsPage extends StatefulWidget { } class _SettingsPage extends State { - void _logout() {} + /// 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()); + } - void _changePassword() {} + // ─── 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( @@ -97,15 +156,16 @@ class _SettingsPage extends State { ), ListTile( - title: const Text("Passwort öndern"), + 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: IconButton( - onPressed: _changePassword, - icon: FilledButton( - onPressed: _changePassword, - child: const Text("Ändern"), - ), + child: FilledButton.icon( + onPressed: _openAccountConsole, + icon: const Icon(Icons.open_in_new), + label: const Text("Öffnen"), ), ), tileColor: Theme.of(context).colorScheme.onSecondary, @@ -113,10 +173,10 @@ class _SettingsPage extends State { ListTile( title: const Text("Ausloggen"), - trailing: IconButton( - onPressed: _logout, - icon: Icon(Icons.logout, color: Colors.redAccent), - ), + // 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, ), ],