part of '../settings_modal.dart'; class _AppearanceSection extends StatelessWidget { final PyramidTheme pt; final ThemeVariant variant; final double? textScale; final MessageStyle messageStyle; final int accentIdx; final double radius; final double density; final double motion; final WidgetRef ref; const _AppearanceSection({ required this.pt, required this.variant, required this.textScale, required this.messageStyle, required this.accentIdx, required this.radius, required this.density, required this.motion, required this.ref, }); @override Widget build(BuildContext context) { final mobile = isMobileLayout(MediaQuery.sizeOf(context)); final autoScale = defaultTextScale(mobile: mobile); final effectiveScale = textScale ?? autoScale; final variants = ThemeVariant.values; final styles = MessageStyle.values; return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _SectionTitle( title: 'Erscheinungsbild', subtitle: 'Gestalte Pyramid nach deinem Geschmack.', pt: pt), _SettingsGroup(title: 'Theme', pt: pt, children: [ _AdaptiveSettingsRow( title: 'Farbmodus', desc: 'Dunkel = weiches Dunkelgrau, Schwarz = wie früher (spart ' 'auf OLED-Displays Akku)', pt: pt, control: _SegControl( options: [for (final v in variants) v.label], selected: variants.indexOf(variant), pt: pt, onSelect: (i) { ref.read(themeVariantProvider.notifier).state = variants[i]; saveThemePrefs(variant: variants[i]); }, ), ), _Divider(pt: pt), _SettingsRow( title: 'Akzentfarbe', desc: 'Farbe für Hervorhebungen und Buttons', pt: pt, control: Row( children: accentPresets.asMap().entries.map((e) { final i = e.key; final preset = e.value; final isActive = accentIdx == i; return GestureDetector( onTap: () { ref.read(accentProvider.notifier).state = i; saveThemePrefs(accentIdx: i); }, child: AnimatedContainer( duration: const Duration(milliseconds: 150), width: 22, height: 22, margin: const EdgeInsets.only(right: 6), decoration: BoxDecoration( color: preset.color, shape: BoxShape.circle, border: isActive ? Border.all(color: Colors.white, width: 2) : null, boxShadow: isActive ? [BoxShadow(color: preset.color.withAlpha(100), blurRadius: 8)] : null, ), ), ); }).toList(), ), ), ]), const SizedBox(height: 20), _SettingsGroup(title: 'Text & Chat', pt: pt, children: [ _AdaptiveSettingsRow( title: 'Schriftgröße', desc: textScale == null ? 'Automatisch (${(autoScale * 100).round()} %) – zusätzlich ' 'zur Schriftgröße des Geräts' : '${(effectiveScale * 100).round()} %', pt: pt, control: Row( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: mobile ? 250 : 170, child: SliderTheme( data: SliderThemeData( activeTrackColor: pt.accent, thumbColor: pt.accent, inactiveTrackColor: pt.border, trackHeight: 3, thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 7), ), child: Slider( value: effectiveScale.clamp(minTextScale, maxTextScale), min: minTextScale, max: maxTextScale, divisions: ((maxTextScale - minTextScale) / 0.05).round(), onChanged: (v) { final rounded = (v * 20).round() / 20; ref.read(textScaleProvider.notifier).state = rounded; saveThemePrefs(textScale: rounded); }, ), ), ), if (textScale != null) TextButton( onPressed: () { ref.read(textScaleProvider.notifier).state = null; saveThemePrefs(resetTextScale: true); }, child: Text('Standard', style: TextStyle(color: pt.accent, fontSize: 12)), ), ], ), ), Padding( padding: const EdgeInsets.fromLTRB(14, 0, 14, 12), child: Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: pt.bg3, borderRadius: BorderRadius.circular(pt.rBase), ), child: Text( 'Vorschau: So groß erscheinen Nachrichten im Chat.', style: TextStyle(color: pt.fg, fontSize: 14), ), ), ), _Divider(pt: pt), _AdaptiveSettingsRow( title: 'Nachrichten', desc: 'Automatisch: Handy Sprechblasen, PC Liste', pt: pt, control: _SegControl( options: [for (final m in styles) m.label], selected: styles.indexOf(messageStyle), pt: pt, onSelect: (i) { ref.read(messageStyleProvider.notifier).state = styles[i]; saveThemePrefs(messageStyle: styles[i]); }, ), ), ]), const SizedBox(height: 20), if (isMobileLayout(MediaQuery.sizeOf(context))) ...[ _SettingsGroup(title: 'Navigation', pt: pt, children: [ Consumer(builder: (context, ref, _) { final bottom = ref.watch(bottomNavProvider); return _AdaptiveSettingsRow( title: 'Hauptmenü', desc: 'Seitenmenü (☰) oder Leiste unten mit Chats · Server · Du', pt: pt, control: _SegControl( options: const ['Seitenmenü', 'Untere Leiste'], selected: bottom ? 1 : 0, pt: pt, onSelect: (i) => ref.read(bottomNavProvider.notifier).set(i == 1), ), ); }), ]), const SizedBox(height: 20), ], _SettingsGroup(title: 'Form & Bewegung', pt: pt, children: [ _SettingsRow( title: 'Eckenrundung', desc: '${radius.round()} px', pt: pt, control: SizedBox( width: 150, child: SliderTheme( data: SliderThemeData( activeTrackColor: pt.accent, thumbColor: pt.accent, inactiveTrackColor: pt.border, trackHeight: 3, thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 6), ), child: Slider( value: radius, min: 4, max: 20, onChanged: (v) { ref.read(radiusProvider.notifier).state = v; saveThemePrefs(radius: v); }, ), ), ), ), _Divider(pt: pt), _SettingsRow( title: 'Animationen', desc: '${(motion * 100).round()}%', pt: pt, control: SizedBox( width: 150, child: SliderTheme( data: SliderThemeData( activeTrackColor: pt.accent, thumbColor: pt.accent, inactiveTrackColor: pt.border, trackHeight: 3, thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 6), ), child: Slider( value: motion, min: 0, max: 1, onChanged: (v) { ref.read(motionProvider.notifier).state = v; saveThemePrefs(motion: v); }, ), ), ), ), ]), const SizedBox(height: 20), _SettingsGroup(title: 'Vorschau', pt: pt, children: [ Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Expanded( child: Column( children: [ Container( height: 100, decoration: BoxDecoration( color: pt.bg3, borderRadius: BorderRadius.circular(pt.rBase), border: Border.all(color: pt.border), ), child: const Center(child: PyramidLoader(size: 70, useLottie: true)), ), const SizedBox(height: 6), Text('Lottie', style: TextStyle(color: pt.fgMuted, fontSize: 11)), ], ), ), const SizedBox(width: 12), Expanded( child: Column( children: [ Container( height: 100, decoration: BoxDecoration( color: pt.bg3, borderRadius: BorderRadius.circular(pt.rBase), border: Border.all(color: pt.border), ), child: const Center(child: PyramidLoader(size: 70, useLottie: false)), ), const SizedBox(height: 6), Text('SVG', style: TextStyle(color: pt.fgMuted, fontSize: 11)), ], ), ), ], ), ), ]), ], ), ); } } /// Wie [_SettingsRow], aber auf schmalen Bildschirmen steht das Bedienelement /// UNTER dem Text statt daneben (sonst quetschen sich Schalter mit drei /// Optionen auf dem Handy). class _AdaptiveSettingsRow extends StatelessWidget { final String title; final String? desc; final PyramidTheme pt; final Widget control; const _AdaptiveSettingsRow({ required this.title, required this.pt, required this.control, this.desc, }); @override Widget build(BuildContext context) { return LayoutBuilder(builder: (context, constraints) { if (constraints.maxWidth >= 520) { return _SettingsRow(title: title, desc: desc, pt: pt, control: control); } // Volle Breite: _SettingsGroup zentriert seine Kinder sonst. return Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: TextStyle( color: pt.fg, fontSize: 13, fontWeight: FontWeight.w500)), if (desc != null) ...[ const SizedBox(height: 2), Text(desc!, style: TextStyle(color: pt.fgDim, fontSize: 12)), ], const SizedBox(height: 10), SingleChildScrollView( scrollDirection: Axis.horizontal, child: control, ), ], ), ); }); } }