import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../config/app_config.dart'; import '../utils/farb_theme.dart'; import '../utils/user_effekte.dart'; import '../services/auth_service.dart'; import '../services/cloud_service.dart'; import '../services/player_service.dart'; import '../services/sync_service.dart'; import '../services/realtime_sync_service.dart'; import '../database/db_helper.dart'; import 'cloud_screen.dart'; import 'recap_screen.dart'; import 'erweitert_screen.dart'; /// Einstellungen – 2-stufig: Standard + Expertenmodus (ab v2.56). /// Sprint D F4: alle Einstellungen funktionierend + persistiert. class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @override State createState() => _SettingsScreenState(); } class _SettingsScreenState extends State { // ─── Standard-Einstellungen ─── String _syncModus = 'realtime'; // 'realtime' | 'manual' | 'interval' int _cloudIntervallStunden = 48; int _sleepDefaultMin = 0; double _geschwindigkeit = 1.0; Color _akzent = MeloTheme.akzent; Map _speicherInfo = const {'anzahl': 0, 'bytes': 0}; // ─── Expertenmodus ─── bool _expertenModus = false; String? _letzterSync; String? _verbindungsStatus; /// Verfügbare Zeit-Intervalle für Modus 3 (in Stunden). static const _intervallOptionen = { 48: '2 Tage', 72: '3 Tage', 120: '5 Tage', 168: '1 Woche', 336: '2 Wochen', }; static const _akzentFarben = [ Color(0xFFCC0000), // Rot (Standard) Color(0xFF00B8A9), // Türkis Color(0xFF1E6FD9), // Blau Color(0xFF2E9E4F), // Grün Color(0xFF8E44AD), // Lila Color(0xFFE67E22), // Orange Color(0xFFD81B60), // Pink ]; @override void initState() { super.initState(); _ladeEinstellungen(); _ladeSpeicherInfo(); } Future _ladeEinstellungen() async { final p = await SharedPreferences.getInstance(); final speed = p.getDouble('playback_speed') ?? 1.0; final akzentHex = p.getString(UserEffekt.akzentKey(AuthService().benutzer)); final modus = p.getString('sync_modus') ?? 'realtime'; int intervallStunden = p.getInt('cloud_interval_stunden') ?? 0; if (intervallStunden <= 0) { final alt = p.getInt('cloud_interval') ?? 0; if (alt == 1 || alt == 3 || alt == 6 || alt == 12) { intervallStunden = 48; await p.setInt('cloud_interval_stunden', 48); } else { intervallStunden = 48; } } if (!['realtime', 'manual', 'interval'].contains(modus)) { await p.setString('sync_modus', 'realtime'); } // Expertenmodus laden (Key: 'experten_modus') final experte = p.getBool('experten_modus') ?? false; // Sync-Info für erweiterte Sektion final letzterSyncStr = p.getString('letzter_sync'); if (!mounted) return; setState(() { _syncModus = modus; _cloudIntervallStunden = intervallStunden; _sleepDefaultMin = p.getInt('sleep_timer_default_min') ?? 0; _geschwindigkeit = speed; _akzent = UserEffekt.farbeAusHex(akzentHex) ?? MeloTheme.akzent; _expertenModus = experte; _letzterSync = letzterSyncStr; _verbindungsStatus = '–'; // CloudService()-Instanz hat keinen echten Status }); await PlayerService().setGeschwindigkeit(speed); } Future _ladeSpeicherInfo() async { final info = await DbHelper().speicherInfo(); if (!mounted) return; setState(() => _speicherInfo = info); } // ─── Setter ──────────────────────────────────────── Future _syncModusSetzen(String modus) async { setState(() => _syncModus = modus); final p = await SharedPreferences.getInstance(); await p.setString('sync_modus', modus); final rs = RealtimeSyncService(); switch (modus) { case 'realtime': await rs.starteWennAktiviert(); break; case 'manual': rs.stoppe(); await p.setInt('cloud_interval', 0); await SyncService.starteAutoSyncTimer(); break; case 'interval': rs.stoppe(); await p.setInt('cloud_interval', _cloudIntervallStunden); await SyncService.starteAutoSyncTimer(); break; } } Future _intervallSetzen(int stunden) async { setState(() => _cloudIntervallStunden = stunden); final p = await SharedPreferences.getInstance(); await p.setInt('cloud_interval_stunden', stunden); await p.setInt('cloud_interval', stunden); await SyncService.starteAutoSyncTimer(); } Future _sleepDefaultSetzen(int min) async { setState(() => _sleepDefaultMin = min); final p = await SharedPreferences.getInstance(); await p.setInt('sleep_timer_default_min', min); } Future _geschwindigkeitSetzen(double wert) async { setState(() => _geschwindigkeit = wert); final p = await SharedPreferences.getInstance(); await p.setDouble('playback_speed', wert); await PlayerService().setGeschwindigkeit(wert); } Future _akzentSetzen(Color farbe) async { setState(() => _akzent = farbe); MeloTheme.akzent = farbe; final p = await SharedPreferences.getInstance(); await p.setString( UserEffekt.akzentKey(AuthService().benutzer), _hexVonFarbe(farbe)); } Future _expertenModusSetzen(bool wert) async { setState(() => _expertenModus = wert); final p = await SharedPreferences.getInstance(); await p.setBool('experten_modus', wert); } String _hexVonFarbe(Color c) { String hx(int w) => w.toRadixString(16).padLeft(2, '0').toUpperCase(); return '${hx((c.r * 255).round())}${hx((c.g * 255).round())}${hx((c.b * 255).round())}'; } String get _speicherText { final anzahl = _speicherInfo['anzahl'] ?? 0; final bytes = _speicherInfo['bytes'] ?? 0; if (bytes >= 1073741824) { return '$anzahl Songs · ${(bytes / 1073741824).toStringAsFixed(2)} GB'; } return '$anzahl Songs · ${(bytes / 1048576).toStringAsFixed(1)} MB'; } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: MeloTheme.schwarz, appBar: AppBar( backgroundColor: MeloTheme.oberflaeche, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white, size: 22), onPressed: () => Navigator.pop(context), ), title: Row( children: [ Icon(Icons.settings, color: MeloTheme.akzent, size: 20), const SizedBox(width: 10), const Text( 'Einstellungen', style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600), ), ], ), ), body: ListView( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), children: [ // ═══ Sektion: Standard ═══ _sektionHeader('⚙️ Standard', icon: Icons.tune), const SizedBox(height: 4), // ─── Verbindung ─── _einstellungsKachel( icon: Icons.cloud_outlined, titel: 'Cloud Sync', untertitel: 'Auto-Sync, Upload & Download', onTap: () { Navigator.push( context, MaterialPageRoute( builder: (_) => CloudScreen(cloud: CloudService()), ), ); }, ), _einstellungsKachel( icon: Icons.dns_outlined, titel: 'Musikserver', untertitel: 'Navidrome Musik-Server verbinden', onTap: () { Navigator.pop(context, 'server'); }, ), const SizedBox(height: 4), // ─── Sync & Wiedergabe ─── _einstellungsGruppe([ _gruppenZeile( label: 'Sync-Modus', wert: _syncModusText, inhalt: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _modusRadioTile('⚡ Echtzeit', 'realtime', 'Änderungen sofort synchronisieren (Push)'), _modusRadioTile('👆 Manuell', 'manual', 'Nur auf Knopfdruck synchronisieren'), _modusRadioTile('🗓️ Zeit-Intervall', 'interval', 'Automatisch nach Zeitplan synchronisieren'), ], ), ), if (_syncModus == 'interval') ...[ const Divider(color: MeloTheme.dunkel2, height: 1), _gruppenZeile( label: 'Intervall', wert: _intervallOptionen[_cloudIntervallStunden] ?? '${_cloudIntervallStunden}h', inhalt: DropdownButtonFormField( initialValue: _intervallOptionen.containsKey(_cloudIntervallStunden) ? _cloudIntervallStunden : 48, dropdownColor: MeloTheme.dunkel1, style: const TextStyle(color: Colors.white, fontSize: 13), decoration: const InputDecoration( contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), border: OutlineInputBorder(), ), items: _intervallOptionen.entries.map((e) { return DropdownMenuItem( value: e.key, child: Text(e.value), ); }).toList(), onChanged: (v) { if (v != null) _intervallSetzen(v); }, ), ), ], const Divider(color: MeloTheme.dunkel2, height: 1), _gruppenZeile( label: 'Sleep-Timer-Standard', wert: _sleepDefaultMin == 0 ? 'Aus' : '$_sleepDefaultMin Min', inhalt: Row(children: [ _wahlChip('Aus', _sleepDefaultMin == 0, () => _sleepDefaultSetzen(0)), _wahlChip('15', _sleepDefaultMin == 15, () => _sleepDefaultSetzen(15)), _wahlChip('30', _sleepDefaultMin == 30, () => _sleepDefaultSetzen(30)), _wahlChip('45', _sleepDefaultMin == 45, () => _sleepDefaultSetzen(45)), _wahlChip('60', _sleepDefaultMin == 60, () => _sleepDefaultSetzen(60)), ]), ), const Divider(color: MeloTheme.dunkel2, height: 1), _gruppenZeile( label: 'Geschwindigkeit', wert: '${_geschwindigkeit.toStringAsFixed(2)}x', inhalt: Slider( value: _geschwindigkeit, min: 0.5, max: 2.0, divisions: 6, activeColor: MeloTheme.akzent, onChanged: (v) => setState(() => _geschwindigkeit = v), onChangeEnd: _geschwindigkeitSetzen, ), ), ]), const SizedBox(height: 4), // ─── Aussehen ─── _einstellungsGruppe([ _gruppenZeile( label: 'Akzentfarbe', wert: 'Für ${AuthService().benutzer}', inhalt: Row(children: [ for (final farbe in _akzentFarben) ...[ _akzentPunkt(farbe, _akzent == farbe, () => _akzentSetzen(farbe)), ], ]), ), ]), const SizedBox(height: 4), // ─── Recap ─── _einstellungsKachel( icon: Icons.auto_graph, titel: 'Recap (Woche/Monat/Jahr)', untertitel: 'Deine Hörstatistiken – wie Spotify Wrapped', onTap: () { Navigator.push( context, MaterialPageRoute(builder: (_) => const RecapScreen()), ); }, ), const SizedBox(height: 4), // ─── Abmelden ─── SizedBox( width: double.infinity, height: 48, child: OutlinedButton.icon( onPressed: () { Navigator.pop(context, 'logout'); }, icon: const Icon(Icons.logout, size: 18), label: const Text('Abmelden', style: TextStyle(fontSize: 14)), style: OutlinedButton.styleFrom( foregroundColor: MeloTheme.akzent, side: BorderSide(color: MeloTheme.akzent), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(999)), ), ), ), const SizedBox(height: 20), // ─── Versions-Anzeige (sichtbar!) ─── Center( child: Text( 'Melo v2.56.1', style: TextStyle(fontSize: 12, color: MeloTheme.textSekundaer), ), ), const SizedBox(height: 8), // ═══ Expertenmodus ═══ Container( decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(20), border: Border.all(color: MeloTheme.dunkel2), ), child: SwitchListTile( key: const Key('experten_modus'), title: const Text( 'Expertenmodus', style: TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.w600), ), subtitle: const Text( 'Erweiterte Einstellungen & Diagnose', style: TextStyle(color: MeloTheme.textSekundaer, fontSize: 12), ), value: _expertenModus, activeTrackColor: MeloTheme.akzent, activeThumbColor: MeloTheme.akzent, onChanged: _expertenModusSetzen, secondary: Icon(Icons.science_outlined, color: MeloTheme.akzent, size: 22), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), ), ), // ═══ Erweiterte Sektion (nur wenn Expertenmodus an) ═══ if (_expertenModus) ...[ const SizedBox(height: 4), _sektionHeader('🔬 Erweitert', icon: Icons.science), // ─── Storage-Info ─── _infoKachel( icon: Icons.storage_outlined, titel: 'Lokale Musik', wert: _speicherText, ), // ─── Sync-Details ─── Container( margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(20), border: Border.all(color: MeloTheme.dunkel2), ), child: Column( children: [ _infoZeile('Letzter Sync', _letzterSync ?? 'Nie'), const Divider(color: MeloTheme.dunkel2, height: 1), _infoZeile('Modus', _syncModusText), const Divider(color: MeloTheme.dunkel2, height: 1), _infoZeile('Verbindung', _verbindungsStatus ?? '–'), ], ), ), // ─── Server-URL-Anzeige ─── Container( margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(20), border: Border.all(color: MeloTheme.dunkel2), ), child: Column( children: [ _infoZeile('Auth-URL', AppConfig.authUrl), const Divider(color: MeloTheme.dunkel2, height: 1), _infoZeile('Cloud-URL', AppConfig.cloudUrl), const Divider(color: MeloTheme.dunkel2, height: 1), _infoZeile('Musikserver', AppConfig.navidromeUrl), ], ), ), // ─── Logs & DB-Wartung ─── _einstellungsKachel( icon: Icons.article_outlined, titel: 'Logs', untertitel: 'App-Logbuch im Speicher ansehen', onTap: () { Navigator.push( context, MaterialPageRoute(builder: (_) => const LogViewerScreen()), ); }, ), _einstellungsKachel( icon: Icons.handyman_outlined, titel: 'DB-Wartung & Diagnose', untertitel: 'Datenbank-Optimierung, Scanner & mehr', onTap: () async { final result = await Navigator.push( context, MaterialPageRoute( builder: (_) => ErweitertScreen( onScan: () { Navigator.pop(context, 'scanner'); }, ), ), ); if (!mounted || result != 'scanner') return; Navigator.pop(this.context, 'scanner'); }, ), _einstellungsKachel( icon: Icons.bug_report_outlined, titel: 'Entwickler', untertitel: 'Version, URLs & technische Details', onTap: () { Navigator.push( context, MaterialPageRoute(builder: (_) => const EntwicklerScreen()), ); }, ), const SizedBox(height: 4), // ─── Info ─── Container( decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(20), border: Border.all(color: MeloTheme.dunkel2), ), child: Column( children: [ _infoZeile('Version', '2.56.0'), const Divider(color: MeloTheme.dunkel2, height: 1), _infoZeile('Theme', 'Melo Dark Fusion'), ], ), ), ], const SizedBox(height: 32), ], ), ); } // ─── ─── Hilfs-Widgets ─── ─── Widget _sektionHeader(String titel, {IconData? icon}) { return Padding( padding: const EdgeInsets.fromLTRB(4, 16, 4, 8), child: Row( children: [ if (icon != null) ...[ Icon(icon, size: 16, color: MeloTheme.akzent), const SizedBox(width: 6), ], Text( titel, style: const TextStyle( color: MeloTheme.textSekundaer, fontSize: 12, fontWeight: FontWeight.w600, letterSpacing: 1.0, ), ), ], ), ); } Widget _einstellungsKachel({ required IconData icon, required String titel, required String untertitel, required VoidCallback onTap, }) { return Padding( padding: const EdgeInsets.only(bottom: 8), child: Material( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(20), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(20), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), border: Border.all(color: MeloTheme.dunkel2), ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: MeloTheme.dunkel2, borderRadius: BorderRadius.circular(14), ), child: Icon(icon, color: MeloTheme.akzent, size: 20), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( titel, style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500), ), const SizedBox(height: 2), Text( untertitel, style: const TextStyle(color: MeloTheme.textSekundaer, fontSize: 12), ), ], ), ), const Icon(Icons.chevron_right, color: MeloTheme.textSekundaer, size: 20), ], ), ), ), ), ); } Widget _infoKachel({ required IconData icon, required String titel, required String wert, }) { return Padding( padding: const EdgeInsets.only(bottom: 8), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(20), border: Border.all(color: MeloTheme.dunkel2), ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: MeloTheme.dunkel2, borderRadius: BorderRadius.circular(14), ), child: Icon(icon, color: MeloTheme.akzent, size: 20), ), const SizedBox(width: 14), Expanded( child: Text( titel, style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500), ), ), Text( wert, style: const TextStyle(color: MeloTheme.textSekundaer, fontSize: 12), ), ], ), ), ); } Widget _einstellungsGruppe(List kinder) { return Container( margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(20), border: Border.all(color: MeloTheme.dunkel2), ), child: Column(children: kinder), ); } Widget _gruppenZeile({ required String label, String? wert, required Widget inhalt, }) { return Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( label, style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500), ), ), if (wert != null) Text( wert, style: TextStyle(color: MeloTheme.akzent, fontSize: 13, fontWeight: FontWeight.w600), ), ], ), const SizedBox(height: 10), inhalt, ], ), ); } Widget _wahlChip(String label, bool aktiv, VoidCallback onTap) { return GestureDetector( onTap: onTap, child: Container( margin: const EdgeInsets.only(right: 8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7), decoration: BoxDecoration( color: aktiv ? MeloTheme.akzent : MeloTheme.dunkel2, borderRadius: BorderRadius.circular(999), ), child: Text( label, style: TextStyle( fontSize: 12, color: aktiv ? Colors.white : MeloTheme.textSekundaer, ), ), ), ); } Widget _akzentPunkt(Color farbe, bool aktiv, VoidCallback onTap) { return GestureDetector( onTap: onTap, child: Container( margin: const EdgeInsets.only(right: 10), width: 34, height: 34, decoration: BoxDecoration( shape: BoxShape.circle, color: farbe, border: Border.all( color: aktiv ? Colors.white : Colors.transparent, width: 2.5, ), boxShadow: aktiv ? [ BoxShadow( color: farbe.withValues(alpha: 0.4), blurRadius: 12, ), ] : null, ), child: aktiv ? const Icon(Icons.check, size: 18, color: Colors.white) : null, ), ); } Widget _infoZeile(String label, String wert) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ Text( label, style: const TextStyle(color: MeloTheme.textSekundaer, fontSize: 13), ), const Spacer(), Text( wert, style: const TextStyle(color: Colors.white70, fontSize: 13), ), ], ), ); } String get _syncModusText { switch (_syncModus) { case 'realtime': return '⚡ Echtzeit'; case 'manual': return '👆 Manuell'; case 'interval': return '🗓️ Zeit'; default: return '⚡ Echtzeit'; } } Widget _modusRadioTile(String titel, String wert, String beschreibung) { final ausgewaehlt = _syncModus == wert; return Padding( padding: const EdgeInsets.only(bottom: 4), child: InkWell( onTap: () => _syncModusSetzen(wert), borderRadius: BorderRadius.circular(14), child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), decoration: BoxDecoration( color: ausgewaehlt ? MeloTheme.akzent.withAlpha(25) : Colors.transparent, borderRadius: BorderRadius.circular(14), border: Border.all( color: ausgewaehlt ? MeloTheme.akzent : MeloTheme.dunkel2, width: ausgewaehlt ? 1.5 : 1, ), ), child: Row( children: [ Icon( ausgewaehlt ? Icons.radio_button_checked : Icons.radio_button_unchecked, color: ausgewaehlt ? MeloTheme.akzent : MeloTheme.textSekundaer, size: 18, ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( titel, style: TextStyle( color: ausgewaehlt ? Colors.white : MeloTheme.textSekundaer, fontSize: 13, fontWeight: FontWeight.w500, ), ), Text( beschreibung, style: TextStyle( color: ausgewaehlt ? Colors.white70 : MeloTheme.textSekundaer, fontSize: 11, ), ), ], ), ), ], ), ), ), ); } }