part of '../settings_modal.dart'; class _MobileHeader extends StatelessWidget { final String title; final PyramidTheme pt; final VoidCallback? onBack; final VoidCallback onClose; const _MobileHeader({required this.title, required this.pt, this.onBack, required this.onClose}); @override Widget build(BuildContext context) { return Container( height: 52, padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( color: pt.bg2, border: Border(bottom: BorderSide(color: pt.border)), ), child: Row( children: [ if (onBack != null) IconButton( onPressed: onBack, icon: Icon(Icons.arrow_back_rounded, size: 20, color: pt.fg), tooltip: 'Zurück', ) else const SizedBox(width: 8), Expanded( child: Text( title, style: TextStyle(color: pt.fg, fontSize: 16, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis, ), ), IconButton( onPressed: onClose, icon: Icon(Icons.close_rounded, size: 20, color: pt.fgMuted), tooltip: 'Schließen', ), ], ), ); } } class _MobileCategoryTile extends StatelessWidget { final String label; final IconData icon; final PyramidTheme pt; final VoidCallback onTap; const _MobileCategoryTile({required this.label, required this.icon, required this.pt, required this.onTap}); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15), child: Row( children: [ Icon(icon, size: 21, color: pt.fgMuted), const SizedBox(width: 16), Expanded( child: Text(label, style: TextStyle(color: pt.fg, fontSize: 15)), ), Icon(Icons.chevron_right_rounded, size: 20, color: pt.fgDim), ], ), ), ); } } class _NavSection extends StatelessWidget { final String title; final PyramidTheme pt; const _NavSection(this.title, this.pt); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB(12, 0, 12, 4), child: Text( title.toUpperCase(), style: TextStyle( color: pt.fgDim, fontSize: 11, fontWeight: FontWeight.w600, letterSpacing: 0.04 * 11, ), ), ); } } class _NavItem extends StatefulWidget { final String id; final String label; final IconData icon; final String active; final PyramidTheme pt; final VoidCallback onTap; const _NavItem(this.id, this.label, this.icon, this.active, this.pt, this.onTap); @override State<_NavItem> createState() => _NavItemState(); } class _NavItemState extends State<_NavItem> { bool _hovered = false; @override Widget build(BuildContext context) { final isActive = widget.id == widget.active; final pt = widget.pt; return MouseRegion( cursor: SystemMouseCursors.click, onEnter: (_) => setState(() => _hovered = true), onExit: (_) => setState(() => _hovered = false), child: GestureDetector( onTap: widget.onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 150), margin: const EdgeInsets.fromLTRB(8, 1, 8, 1), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7), decoration: BoxDecoration( color: isActive ? pt.accentSoft : _hovered ? pt.bgHover : Colors.transparent, borderRadius: BorderRadius.circular(pt.rSm), ), child: Row( mainAxisAlignment: widget.label.isEmpty ? MainAxisAlignment.center : MainAxisAlignment.start, children: [ Icon( widget.icon, size: 14, color: isActive ? pt.accent : _hovered ? pt.fg : pt.fgMuted, ), if (widget.label.isNotEmpty) ...[ const SizedBox(width: 8), Text( widget.label, style: TextStyle( color: isActive ? pt.fg : _hovered ? pt.fg : pt.fgMuted, fontSize: 13, fontWeight: isActive ? FontWeight.w600 : FontWeight.w400, ), ), ], ], ), ), ), ); } } class _LogoutNavButton extends ConsumerStatefulWidget { final PyramidTheme pt; final bool isMobile; final VoidCallback onClose; const _LogoutNavButton({required this.pt, required this.isMobile, required this.onClose}); @override ConsumerState<_LogoutNavButton> createState() => _LogoutNavButtonState(); } class _LogoutNavButtonState extends ConsumerState<_LogoutNavButton> { bool _hovered = false; bool _loading = false; Future _logout() async { final pt = widget.pt; final client = await ref.read(matrixClientProvider.future); final backupMissing = await isKeyBackupMissing(client); if (!mounted) return; final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: pt.bg2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(pt.rXl), side: BorderSide(color: pt.border)), title: Text('Abmelden?', style: TextStyle(color: pt.fg, fontSize: 16, fontWeight: FontWeight.w700)), content: Text( backupMissing ? 'Du wirst von diesem Gerät abgemeldet. Es ist kein Schlüssel-Backup eingerichtet — ' 'ältere verschlüsselte Nachrichten sind danach auf keinem Gerät mehr lesbar.' : 'Du wirst von diesem Gerät abgemeldet.', style: TextStyle( color: backupMissing ? pt.danger : pt.fgMuted, fontSize: 13, height: 1.4), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: Text('Abbrechen', style: TextStyle(color: pt.fgMuted)), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: pt.danger, foregroundColor: Colors.white, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), onPressed: () => Navigator.pop(ctx, true), child: const Text('Abmelden'), ), ], ), ); if (confirmed != true || !mounted) return; setState(() => _loading = true); try { widget.onClose(); unawaited(AuthLog.write('User-initiated logout (Settings-Sidebar)')); await client.logout(); } catch (_) {} } @override Widget build(BuildContext context) { final pt = widget.pt; return MouseRegion( cursor: SystemMouseCursors.click, onEnter: (_) => setState(() => _hovered = true), onExit: (_) => setState(() => _hovered = false), child: GestureDetector( onTap: _loading ? null : _logout, child: AnimatedContainer( duration: const Duration(milliseconds: 150), margin: const EdgeInsets.fromLTRB(8, 1, 8, 1), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7), decoration: BoxDecoration( color: _hovered ? pt.danger.withAlpha(30) : Colors.transparent, borderRadius: BorderRadius.circular(pt.rSm), ), child: Row( mainAxisAlignment: widget.isMobile ? MainAxisAlignment.center : MainAxisAlignment.start, children: [ _loading ? SizedBox(width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2, color: pt.danger)) : Icon(Icons.logout_rounded, size: 14, color: pt.danger), if (!widget.isMobile) ...[ const SizedBox(width: 8), Text('Abmelden', style: TextStyle(color: pt.danger, fontSize: 13)), ], ], ), ), ), ); } } class _SectionTitle extends StatelessWidget { final String title; final String subtitle; final PyramidTheme pt; const _SectionTitle({required this.title, required this.subtitle, required this.pt}); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: TextStyle(color: pt.fg, fontSize: 20, fontWeight: FontWeight.w700)), const SizedBox(height: 4), Text(subtitle, style: TextStyle(color: pt.fgMuted, fontSize: 13)), const SizedBox(height: 24), ], ); } } class _SettingsGroup extends StatelessWidget { final String title; final PyramidTheme pt; final List children; const _SettingsGroup({required this.title, required this.pt, required this.children}); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title.toUpperCase(), style: TextStyle( color: pt.fgDim, fontSize: 11, fontWeight: FontWeight.w600, letterSpacing: 0.04 * 11), ), const SizedBox(height: 10), Container( decoration: BoxDecoration( color: pt.bg2, borderRadius: BorderRadius.circular(10), border: Border.all(color: pt.border), ), child: Column(children: children), ), ], ); } } class _SettingsRow extends StatelessWidget { final String title; final String? desc; final PyramidTheme pt; final Widget? control; final VoidCallback? onTap; final bool destructive; final bool showArrow; const _SettingsRow({ required this.title, required this.pt, this.desc, this.control, this.onTap, this.destructive = false, this.showArrow = false, }); @override Widget build(BuildContext context) { Widget content = Padding( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( color: destructive ? pt.danger : pt.fg, fontSize: 13, fontWeight: FontWeight.w500, ), ), if (desc != null) ...[ const SizedBox(height: 2), Text(desc!, style: TextStyle(color: pt.fgDim, fontSize: 12)), ], ], ), ), ?control, if (showArrow) Icon(Icons.chevron_right_rounded, size: 16, color: pt.fgDim), ], ), ); if (onTap != null) { return Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(10), child: content, ), ); } return content; } } class _Divider extends StatelessWidget { final PyramidTheme pt; const _Divider({required this.pt}); @override Widget build(BuildContext context) { return Container(height: 1, color: pt.border, margin: const EdgeInsets.only(left: 14)); } } class _Toggle extends StatelessWidget { final bool value; final ValueChanged onChanged; final PyramidTheme pt; const _Toggle({required this.value, required this.onChanged, required this.pt}); @override Widget build(BuildContext context) { return GestureDetector( onTap: () => onChanged(!value), child: AnimatedContainer( duration: const Duration(milliseconds: 200), width: 42, height: 24, decoration: BoxDecoration( color: value ? pt.accent : pt.bg3, borderRadius: BorderRadius.circular(12), border: Border.all(color: value ? pt.accent : pt.borderStrong), ), child: Padding( padding: const EdgeInsets.all(2), child: AnimatedAlign( duration: const Duration(milliseconds: 200), alignment: value ? Alignment.centerRight : Alignment.centerLeft, child: Container( width: 18, height: 18, decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, boxShadow: [BoxShadow(color: Colors.black.withAlpha(60), blurRadius: 4)], ), ), ), ), ), ); } } class _SegControl extends StatelessWidget { final List options; final int selected; final PyramidTheme pt; final ValueChanged onSelect; const _SegControl( {required this.options, required this.selected, required this.pt, required this.onSelect}); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: pt.bg3, borderRadius: BorderRadius.circular(pt.rSm), ), child: Row( mainAxisSize: MainAxisSize.min, children: options.asMap().entries.map((e) { final isActive = e.key == selected; return GestureDetector( onTap: () => onSelect(e.key), child: AnimatedContainer( duration: const Duration(milliseconds: 150), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), decoration: BoxDecoration( color: isActive ? pt.accent : Colors.transparent, borderRadius: BorderRadius.circular(pt.rSm - 2), ), child: Text( e.value, style: TextStyle( color: isActive ? pt.accentFg : pt.fgMuted, fontSize: 13, fontWeight: isActive ? FontWeight.w600 : FontWeight.w400, ), ), ), ); }).toList(), ), ); } } class _PrimaryBtn extends StatelessWidget { final String label; final IconData? icon; final VoidCallback? onPressed; final PyramidTheme pt; final bool loading; const _PrimaryBtn({ required this.label, required this.pt, this.icon, this.onPressed, this.loading = false, }); @override Widget build(BuildContext context) { final bg = pt.accent; final fg = pt.accentFg; return ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: bg, foregroundColor: fg, disabledBackgroundColor: bg.withAlpha(100), padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), elevation: 0, ), onPressed: loading ? null : onPressed, icon: loading ? SizedBox( width: 14, height: 14, child: CircularProgressIndicator(color: fg, strokeWidth: 2)) : (icon != null ? Icon(icon, size: 16) : const SizedBox.shrink()), label: Text(label), ); } } class _AvatarFallback extends StatelessWidget { final String name; final double size; final PyramidTheme pt; const _AvatarFallback({required this.name, required this.size, required this.pt}); @override Widget build(BuildContext context) { return Container( width: size, height: size, color: pt.accent, child: Center( child: Text( name.isNotEmpty ? name[0].toUpperCase() : '?', style: TextStyle(color: pt.accentFg, fontSize: size * 0.4, fontWeight: FontWeight.w700), ), ), ); } } class _PrefTextField extends StatelessWidget { final TextEditingController controller; final String label; final PyramidTheme pt; final int? maxLength; const _PrefTextField({ required this.controller, required this.label, required this.pt, this.maxLength, }); @override Widget build(BuildContext context) { return TextField( controller: controller, maxLength: maxLength, style: TextStyle(color: pt.fg, fontSize: 13), decoration: InputDecoration( labelText: label, labelStyle: TextStyle(color: pt.fgMuted, fontSize: 13), counterText: '', filled: true, fillColor: pt.bg3, isDense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), 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)), ), cursorColor: pt.accent, ); } } class _PwField extends StatelessWidget { final TextEditingController ctrl; final String label; final PyramidTheme pt; final bool visible; final VoidCallback? onToggle; final ValueChanged? onSubmitted; const _PwField({ required this.ctrl, required this.label, required this.pt, required this.visible, required this.onToggle, this.onSubmitted, }); @override Widget build(BuildContext context) => TextField( controller: ctrl, obscureText: !visible, onSubmitted: onSubmitted, style: TextStyle(color: pt.fg, fontSize: 13), decoration: InputDecoration( labelText: label, 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.accent)), suffixIcon: onToggle != null ? IconButton( icon: Icon(visible ? Icons.visibility_off_rounded : Icons.visibility_rounded, size: 16, color: pt.fgDim), onPressed: onToggle, ) : null, ), cursorColor: pt.accent, ); } class _StatusCard extends StatelessWidget { final PyramidTheme pt; final IconData icon; final Color color; final String text; const _StatusCard( {required this.pt, required this.icon, required this.color, required this.text}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: pt.bg2, borderRadius: BorderRadius.circular(8), border: Border.all(color: pt.border), ), child: Row( children: [ Icon(icon, color: color, size: 18), const SizedBox(width: 10), Expanded(child: Text(text, style: TextStyle(color: pt.fgMuted, fontSize: 13))), ], ), ); } }