refactor: settings_encryption.dart (1470 Zeilen) in 4 thematische part-Dateien unter encryption/ geteilt (M2, zeichenidentisch verifiziert)
Vorbereitung fuer Schnitt-Doc Schritt 3 (verification-Modul), ohne den Verschluesselungs-heiligen Code anzufassen: megolm_crypto (Export/Import- Krypto-Helfer), encryption_section (Enums + _EncryptionSection), recovery_key_dialogs (_ChangeRecoveryKeyDialog/_ShowKeyBody/_ConfirmKeyBody), fingerprint_passphrase (_FingerprintGroup/_PassphraseDialog). Rekonstruktion zeichenidentisch (1468 Code-Zeilen, ordinaler Vergleich), analyze sauber, 29 Tests gruen, App-Start ok (auth_log 17:43:14 loggedIn). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
part of '../../settings_modal.dart';
|
||||
|
||||
class _FingerprintGroup extends ConsumerWidget {
|
||||
final PyramidTheme pt;
|
||||
const _FingerprintGroup({required this.pt});
|
||||
|
||||
static String _fmt(String key) {
|
||||
final clean = key.replaceAll(RegExp(r'\s'), '');
|
||||
final groups = <String>[];
|
||||
for (var i = 0; i < clean.length; i += 4) {
|
||||
groups.add(clean.substring(i, (i + 4).clamp(0, clean.length)));
|
||||
}
|
||||
return groups.join(' ');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final clientAsync = ref.watch(matrixClientProvider);
|
||||
final client = clientAsync.valueOrNull;
|
||||
if (client == null) return const SizedBox.shrink();
|
||||
|
||||
final fingerprint = _fmt(client.fingerprintKey);
|
||||
final deviceId = client.deviceID ?? '';
|
||||
|
||||
return _SettingsGroup(title: 'Dieses Gerät', pt: pt, children: [
|
||||
_SettingsRow(
|
||||
title: 'Geräte-ID',
|
||||
desc: deviceId,
|
||||
pt: pt,
|
||||
control: IconButton(
|
||||
icon: Icon(Icons.copy_rounded, size: 14, color: pt.fgDim),
|
||||
tooltip: 'Kopieren',
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(maxWidth: 28, maxHeight: 28),
|
||||
onPressed: () => Clipboard.setData(ClipboardData(text: deviceId)),
|
||||
),
|
||||
),
|
||||
_Divider(pt: pt),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 12, 14, 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text('Ed25519-Fingerabdruck',
|
||||
style: TextStyle(color: pt.fg, fontSize: 13, fontWeight: FontWeight.w500)),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: Icon(Icons.copy_rounded, size: 14, color: pt.fgDim),
|
||||
tooltip: 'Fingerabdruck kopieren',
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(maxWidth: 28, maxHeight: 28),
|
||||
onPressed: () => Clipboard.setData(ClipboardData(text: client.fingerprintKey)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
SelectableText(
|
||||
fingerprint,
|
||||
style: TextStyle(
|
||||
color: pt.fgMuted, fontSize: 11,
|
||||
fontFamily: 'monospace', letterSpacing: 0.5, height: 1.6,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Passphrase dialog (for key export/import)
|
||||
class _PassphraseDialog extends StatefulWidget {
|
||||
final PyramidTheme pt;
|
||||
final bool forExport;
|
||||
const _PassphraseDialog({required this.pt, required this.forExport});
|
||||
|
||||
@override
|
||||
State<_PassphraseDialog> createState() => _PassphraseDialogState();
|
||||
}
|
||||
|
||||
class _PassphraseDialogState extends State<_PassphraseDialog> {
|
||||
final _ctrl1 = TextEditingController();
|
||||
final _ctrl2 = TextEditingController();
|
||||
bool _obscure = true;
|
||||
String? _error;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ctrl1.dispose();
|
||||
_ctrl2.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _submit() {
|
||||
final pass = _ctrl1.text.trim();
|
||||
if (pass.isEmpty) {
|
||||
setState(() => _error = 'Bitte ein Passwort eingeben.');
|
||||
return;
|
||||
}
|
||||
if (widget.forExport && pass != _ctrl2.text.trim()) {
|
||||
setState(() => _error = 'Passwörter stimmen nicht überein.');
|
||||
return;
|
||||
}
|
||||
Navigator.pop(context, pass);
|
||||
}
|
||||
|
||||
InputDecoration _inputDeco(String label, PyramidTheme pt) => InputDecoration(
|
||||
labelText: label,
|
||||
labelStyle: TextStyle(color: pt.fgMuted, fontSize: 13),
|
||||
errorText: _error,
|
||||
filled: true,
|
||||
fillColor: pt.bg3,
|
||||
prefixIcon: Icon(Icons.lock_outline_rounded, color: pt.fgDim, size: 18),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: pt.border)),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: pt.border)),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: pt.accent)),
|
||||
isDense: true,
|
||||
errorMaxLines: 2,
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_obscure ? Icons.visibility_off_outlined : Icons.visibility_outlined,
|
||||
size: 18,
|
||||
color: pt.fgDim,
|
||||
),
|
||||
onPressed: () => setState(() => _obscure = !_obscure),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pt = widget.pt;
|
||||
return AlertDialog(
|
||||
backgroundColor: pt.bg2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(pt.rXl),
|
||||
side: BorderSide(color: pt.border),
|
||||
),
|
||||
title: Text(
|
||||
widget.forExport ? 'Schlüssel exportieren' : 'Schlüssel importieren',
|
||||
style: TextStyle(color: pt.fg, fontSize: 16, fontWeight: FontWeight.w700),
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 340,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.forExport
|
||||
? 'Wähle ein Passwort zum Verschlüsseln der Schlüsseldatei.'
|
||||
: 'Gib das Passwort ein, mit dem die Datei verschlüsselt wurde.',
|
||||
style: TextStyle(color: pt.fgMuted, fontSize: 13, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _ctrl1,
|
||||
obscureText: _obscure,
|
||||
autofocus: true,
|
||||
style: TextStyle(color: pt.fg, fontSize: 13),
|
||||
decoration: _inputDeco('Passwort', pt).copyWith(
|
||||
// Only show error on confirm field if export, else here
|
||||
errorText: widget.forExport ? null : _error,
|
||||
),
|
||||
cursorColor: pt.accent,
|
||||
onSubmitted: widget.forExport ? null : (_) => _submit(),
|
||||
),
|
||||
if (widget.forExport) ...[
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _ctrl2,
|
||||
obscureText: _obscure,
|
||||
style: TextStyle(color: pt.fg, fontSize: 13),
|
||||
decoration: _inputDeco('Passwort bestätigen', pt),
|
||||
cursorColor: pt.accent,
|
||||
onSubmitted: (_) => _submit(),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Abbrechen', style: TextStyle(color: pt.fgMuted)),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: pt.accent,
|
||||
foregroundColor: pt.accentFg,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
onPressed: _submit,
|
||||
child: Text(widget.forExport ? 'Exportieren' : 'Importieren'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user