5d772389d6
Reine Verschiebung per Dart part/part-of (keine Umbenennung, keine Logikänderung) - settings_modal.dart schrumpft von 5541 auf 270 Zeilen. Verifiziert per automatisiertem Blockvergleich gegen das Original und flutter analyze (Baseline unverändert: 1 bekannter Hinweis). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
365 lines
14 KiB
Dart
365 lines
14 KiB
Dart
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<void> _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<void> _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'),
|
|
)),
|
|
],
|
|
);
|
|
}
|