part of '../settings_modal.dart'; class _ChangePasswordDialog extends ConsumerStatefulWidget { final PyramidTheme pt; const _ChangePasswordDialog({required this.pt}); @override ConsumerState<_ChangePasswordDialog> createState() => _ChangePasswordDialogState(); } class _ChangePasswordDialogState extends ConsumerState<_ChangePasswordDialog> { final _oldCtrl = TextEditingController(); final _newCtrl = TextEditingController(); final _confirmCtrl = TextEditingController(); bool _oldVisible = false; bool _newVisible = false; bool _loading = false; String? _error; bool _done = false; @override void dispose() { _oldCtrl.dispose(); _newCtrl.dispose(); _confirmCtrl.dispose(); super.dispose(); } Future _submit() async { final oldPw = _oldCtrl.text; final newPw = _newCtrl.text; final confirm = _confirmCtrl.text; if (oldPw.isEmpty || newPw.isEmpty) { setState(() => _error = 'Bitte alle Felder ausfüllen.'); return; } if (newPw != confirm) { setState(() => _error = 'Die neuen Passwörter stimmen nicht überein.'); return; } if (newPw.length < 8) { setState(() => _error = 'Das neue Passwort muss mindestens 8 Zeichen lang sein.'); return; } setState(() { _loading = true; _error = null; }); try { final client = await ref.read(matrixClientProvider.future); await client.changePassword(newPw, oldPassword: oldPw); if (mounted) setState(() { _loading = false; _done = true; }); } on MatrixException catch (e) { if (mounted) setState(() { _loading = false; _error = e.errorMessage; }); } catch (e) { if (mounted) setState(() { _loading = false; _error = e.toString().split('\n').first; }); } } @override Widget build(BuildContext context) { final pt = widget.pt; return Dialog( backgroundColor: pt.bg2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(pt.rXl), side: BorderSide(color: pt.border)), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 420, minWidth: 300), child: Padding( padding: const EdgeInsets.all(24), child: _done ? _buildDone(pt) : _buildForm(pt), ), ), ); } Widget _buildDone(PyramidTheme pt) => Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.check_circle_outline_rounded, color: PyramidColors.success, size: 52), const SizedBox(height: 12), Text('Passwort geändert', style: TextStyle(color: pt.fg, fontSize: 15, fontWeight: FontWeight.w600)), const SizedBox(height: 6), Text('Dein Passwort wurde erfolgreich aktualisiert.', textAlign: TextAlign.center, style: TextStyle(color: pt.fgMuted, fontSize: 13)), const SizedBox(height: 20), SizedBox(width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: pt.accent, foregroundColor: pt.accentFg, elevation: 0, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), onPressed: () => Navigator.of(context).pop(), child: const Text('Schließen'), )), ], ); Widget _buildForm(PyramidTheme pt) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Icon(Icons.lock_outline_rounded, size: 18, color: pt.accent), const SizedBox(width: 8), Text('Passwort ändern', style: TextStyle(color: pt.fg, fontSize: 15, fontWeight: FontWeight.w600)), const Spacer(), IconButton( icon: Icon(Icons.close_rounded, color: pt.fgDim, size: 16), onPressed: () => Navigator.of(context).pop(), padding: EdgeInsets.zero, constraints: const BoxConstraints(maxWidth: 28, maxHeight: 28), ), ]), const SizedBox(height: 20), _PwField(ctrl: _oldCtrl, label: 'Aktuelles Passwort', pt: pt, visible: _oldVisible, onToggle: () => setState(() => _oldVisible = !_oldVisible)), const SizedBox(height: 12), _PwField(ctrl: _newCtrl, label: 'Neues Passwort', pt: pt, visible: _newVisible, onToggle: () => setState(() => _newVisible = !_newVisible)), const SizedBox(height: 12), _PwField(ctrl: _confirmCtrl, label: 'Neues Passwort bestätigen', pt: pt, visible: _newVisible, onToggle: null, onSubmitted: (_) => _submit()), if (_error != null) ...[ const SizedBox(height: 10), _StatusCard(pt: pt, color: pt.danger, icon: Icons.error_outline_rounded, text: _error!), ], const SizedBox(height: 20), SizedBox(width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: pt.accent, foregroundColor: pt.accentFg, elevation: 0, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), onPressed: _loading ? null : _submit, child: _loading ? const SizedBox(width: 18, height: 18, child: CircularProgressIndicator.adaptive(strokeWidth: 2)) : const Text('Passwort ändern'), )), ], ); } class _DeleteAccountDialog extends ConsumerStatefulWidget { final PyramidTheme pt; const _DeleteAccountDialog({required this.pt}); @override ConsumerState<_DeleteAccountDialog> createState() => _DeleteAccountDialogState(); } enum _DeleteStep { confirm, auth, deleting, error } class _DeleteAccountDialogState extends ConsumerState<_DeleteAccountDialog> { _DeleteStep _step = _DeleteStep.confirm; final _pwCtrl = TextEditingController(); bool _pwVisible = false; bool _eraseData = true; String? _error; @override void dispose() { _pwCtrl.dispose(); super.dispose(); } Future _proceed() async { setState(() { _step = _DeleteStep.deleting; _error = null; }); final client = await ref.read(matrixClientProvider.future); try { await client.deactivateAccount(erase: _eraseData); } on MatrixException catch (e) { if (e.requireAdditionalAuthentication && mounted) { // UIA required — try with password try { await client.deactivateAccount( erase: _eraseData, auth: AuthenticationData( type: AuthenticationTypes.password, session: e.session, additionalFields: { 'identifier': {'type': 'm.id.user', 'user': client.userID}, 'password': _pwCtrl.text, }, ), ); } on MatrixException catch (e2) { if (mounted) setState(() { _step = _DeleteStep.error; _error = e2.errorMessage; }); return; } catch (e2) { if (mounted) setState(() { _step = _DeleteStep.error; _error = e2.toString().split('\n').first; }); return; } } else { if (mounted) setState(() { _step = _DeleteStep.error; _error = e.errorMessage; }); return; } } catch (e) { if (mounted) setState(() { _step = _DeleteStep.error; _error = e.toString().split('\n').first; }); return; } // Konto wurde gelöscht — ausloggen unawaited(AuthLog.write('User-initiated logout (Konto gelöscht)')); try { await client.logout(); } catch (_) {} } @override Widget build(BuildContext context) { final pt = widget.pt; return Dialog( backgroundColor: pt.bg2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(pt.rXl), side: BorderSide(color: pt.border)), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 440, minWidth: 300), child: Padding( padding: const EdgeInsets.all(24), child: switch (_step) { _DeleteStep.confirm => _buildConfirm(pt), _DeleteStep.auth => _buildAuth(pt), _DeleteStep.deleting => const Center( child: Padding(padding: EdgeInsets.all(32), child: CircularProgressIndicator.adaptive()), ), _DeleteStep.error => _buildError(pt), }, ), ), ); } Widget _buildConfirm(PyramidTheme pt) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Icon(Icons.warning_rounded, size: 20, color: pt.danger), const SizedBox(width: 8), Text('Konto löschen', style: TextStyle(color: pt.danger, fontSize: 15, fontWeight: FontWeight.w700)), const Spacer(), IconButton( icon: Icon(Icons.close_rounded, color: pt.fgDim, size: 16), onPressed: () => Navigator.of(context).pop(), padding: EdgeInsets.zero, constraints: const BoxConstraints(maxWidth: 28, maxHeight: 28), ), ]), const SizedBox(height: 16), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: pt.danger.withAlpha(20), borderRadius: BorderRadius.circular(8), border: Border.all(color: pt.danger.withAlpha(60)), ), child: Text( 'Diese Aktion ist unwiderruflich. Dein Konto, alle Sitzungen und ' 'deine Mitgliedschaft in Räumen werden dauerhaft gelöscht.', style: TextStyle(color: pt.fg, fontSize: 13, height: 1.5), ), ), const SizedBox(height: 16), GestureDetector( onTap: () => setState(() => _eraseData = !_eraseData), child: Row(children: [ SizedBox( width: 18, height: 18, child: Checkbox( value: _eraseData, onChanged: (v) => setState(() => _eraseData = v ?? true), activeColor: pt.accent, side: BorderSide(color: pt.border), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), ), const SizedBox(width: 8), Expanded(child: Text('Nachrichten und Daten vom Server löschen', style: TextStyle(color: pt.fg, fontSize: 13))), ]), ), const SizedBox(height: 20), Row(children: [ Expanded(child: OutlinedButton( style: OutlinedButton.styleFrom(foregroundColor: pt.fg, side: BorderSide(color: pt.border), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), onPressed: () => Navigator.of(context).pop(), child: const Text('Abbrechen'), )), const SizedBox(width: 12), Expanded(child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: pt.danger, foregroundColor: Colors.white, elevation: 0, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), onPressed: () => setState(() => _step = _DeleteStep.auth), child: const Text('Weiter'), )), ]), ], ); Widget _buildAuth(PyramidTheme pt) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Icon(Icons.lock_outline_rounded, size: 18, color: pt.danger), const SizedBox(width: 8), Text('Konto löschen bestätigen', style: TextStyle(color: pt.fg, fontSize: 15, fontWeight: FontWeight.w600)), ]), const SizedBox(height: 16), Text('Gib dein Passwort ein, um die Löschung zu bestätigen.', style: TextStyle(color: pt.fgMuted, fontSize: 13)), const SizedBox(height: 16), TextField( controller: _pwCtrl, autofocus: true, obscureText: !_pwVisible, style: TextStyle(color: pt.fg, fontSize: 13), decoration: InputDecoration( labelText: 'Passwort', labelStyle: TextStyle(color: pt.fgMuted, fontSize: 13), filled: true, fillColor: pt.bg3, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: pt.border)), focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: pt.danger)), suffixIcon: IconButton( icon: Icon(_pwVisible ? Icons.visibility_off_rounded : Icons.visibility_rounded, size: 16, color: pt.fgDim), onPressed: () => setState(() => _pwVisible = !_pwVisible), ), ), cursorColor: pt.danger, onSubmitted: (_) => _proceed(), ), const SizedBox(height: 20), Row(children: [ Expanded(child: OutlinedButton( style: OutlinedButton.styleFrom(foregroundColor: pt.fg, side: BorderSide(color: pt.border), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), onPressed: () => setState(() => _step = _DeleteStep.confirm), child: const Text('Zurück'), )), const SizedBox(width: 12), Expanded(child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: pt.danger, foregroundColor: Colors.white, elevation: 0, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), onPressed: _proceed, child: const Text('Konto endgültig löschen'), )), ]), ], ); Widget _buildError(PyramidTheme pt) => Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.error_outline_rounded, color: pt.danger, size: 48), const SizedBox(height: 12), Text(_error ?? 'Ein Fehler ist aufgetreten.', textAlign: TextAlign.center, style: TextStyle(color: pt.fgMuted, fontSize: 13)), const SizedBox(height: 20), SizedBox(width: double.infinity, child: OutlinedButton( style: OutlinedButton.styleFrom(foregroundColor: pt.fg, side: BorderSide(color: pt.border), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), onPressed: () => Navigator.of(context).pop(), child: const Text('Schließen'), )), ], ); }