Files
pyramid/lib/features/spaces/admin/space_admin_permissions.dart
Bernd Steckmeister 0c7d359076 refactor: Gott-Datei space_admin_dialog.dart in 6 Dateien aufgeteilt
Gleiche Technik wie bei den vorigen drei Umbauten: reine Verschiebung per
Dart part/part-of, keine Logikänderung. space_admin_dialog.dart schrumpft
von 2278 auf 19 Zeilen (nur noch Bibliothekskopf). Verifiziert per
automatisiertem Blockvergleich gegen das Original und flutter analyze.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 11:01:58 +02:00

202 lines
6.2 KiB
Dart

part of '../space_admin_dialog.dart';
class _PermissionsTab extends ConsumerStatefulWidget {
final Room space;
const _PermissionsTab({required this.space});
@override
ConsumerState<_PermissionsTab> createState() => _PermissionsTabState();
}
class _PermissionsTabState extends ConsumerState<_PermissionsTab> {
bool _saving = false;
String? _status;
// Current power level values (loaded from room state)
late Map<String, int> _levels;
static const _defaults = {
'events_default': 0,
'state_default': 50,
'invite': 0,
'kick': 50,
'ban': 50,
'redact': 50,
};
static const _labels = {
'events_default': 'Nachrichten senden',
'state_default': 'Raum-Einstellungen ändern',
'invite': 'Mitglieder einladen',
'kick': 'Mitglieder kicken',
'ban': 'Mitglieder bannen',
'redact': 'Nachrichten löschen',
};
@override
void initState() {
super.initState();
_load();
}
void _load() {
final content = widget.space
.getState(EventTypes.RoomPowerLevels)
?.content ??
{};
_levels = {
for (final key in _defaults.keys)
key: (content[key] as int?) ?? _defaults[key]!,
};
}
Future<void> _save() async {
setState(() { _saving = true; _status = null; });
try {
// Server-Stand holen und nur die geänderten Schlüssel mergen — der
// lokale Cache kann unvollständig sein und würde users/events wipen.
await updatePowerLevelsSafely(widget.space, (content) {
for (final entry in _levels.entries) {
content[entry.key] = entry.value;
}
});
setState(() => _status = 'Gespeichert');
} catch (e) {
final msg = e is MatrixException
? '${e.errcode}: ${e.errorMessage}'
: e.toString().split('\n').first;
setState(() => _status = 'Fehler: $msg');
} finally {
setState(() => _saving = false);
}
}
@override
Widget build(BuildContext context) {
final pt = PyramidTheme.of(context);
final ownPower = widget.space.ownPowerLevel;
final canEdit = ownPower >= _kPowerAdmin;
return SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Mindestberechtigungen',
style: TextStyle(
color: pt.fgDim,
fontSize: 11,
fontWeight: FontWeight.w700,
letterSpacing: 0.8)),
const SizedBox(height: 4),
Text(
'Lege fest, welche Stufe ein Mitglied mindestens haben muss, um diese Aktion auszuführen.',
style: TextStyle(color: pt.fgDim, fontSize: 12)),
const SizedBox(height: 16),
...(_labels.entries.map((e) => _PermissionRow(
label: e.value,
value: _levels[e.key]!,
enabled: canEdit,
pt: pt,
onChanged: (v) => setState(() => _levels[e.key] = v),
))),
if (canEdit) ...[
const SizedBox(height: 20),
if (_status != null)
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(_status!,
style: TextStyle(
color: _status!.startsWith('F')
? const Color(0xFFED4245)
: const Color(0xFF57F287),
fontSize: 12)),
),
SizedBox(
width: double.infinity,
child: FilledButton(
onPressed: _saving ? null : _save,
style: FilledButton.styleFrom(
backgroundColor: pt.accent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(pt.rBase)),
),
child: _saving
? SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 2, color: pt.bg0))
: const Text('Speichern',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600)),
),
),
],
],
),
);
}
}
class _PermissionRow extends StatelessWidget {
final String label;
final int value;
final bool enabled;
final PyramidTheme pt;
final ValueChanged<int> onChanged;
const _PermissionRow({
required this.label,
required this.value,
required this.enabled,
required this.pt,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
Expanded(
child: Text(label,
style: TextStyle(
color: pt.fg,
fontSize: 13,
fontWeight: FontWeight.w500)),
),
if (enabled)
DropdownButton<int>(
value: value,
dropdownColor: pt.bg2,
underline: const SizedBox.shrink(),
style: TextStyle(color: pt.fg, fontSize: 13),
items: const [
DropdownMenuItem(value: 0, child: Text('Mitglied (0)')),
DropdownMenuItem(value: 50, child: Text('Moderator (50)')),
DropdownMenuItem(value: 100, child: Text('Admin (100)')),
],
onChanged: (v) => v != null ? onChanged(v) : null,
)
else
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: _powerColor(value, pt).withAlpha(30),
borderRadius: BorderRadius.circular(6),
),
child: Text(_powerLabel(value),
style: TextStyle(
color: _powerColor(value, pt),
fontSize: 12,
fontWeight: FontWeight.w600)),
),
],
),
);
}
}