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 '../database/db_helper.dart'; import 'cloud_screen.dart'; import 'recap_screen.dart'; import 'erweitert_screen.dart'; /// Vollwertiger Einstellungen-Screen – kein Popup mehr. /// Sprint D F4: alle Einstellungen funktionierend + persistiert /// (Auto-Sync-Intervall, Sleep-Timer-Standard, Wiedergabegeschwindigkeit, /// Akzentfarbe pro User, Speicher-Info). class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @override State createState() => _SettingsScreenState(); } class _SettingsScreenState extends State { // ─── Einstellungen (aus SharedPreferences geladen) ─── int _syncIntervall = 0; // 0 = Aus, sonst Stunden int _sleepDefaultMin = 0; // 0 = Aus double _geschwindigkeit = 1.0; Color _akzent = MeloTheme.akzent; Map _speicherInfo = const {'anzahl': 0, 'bytes': 0}; 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)); if (!mounted) return; setState(() { _syncIntervall = p.getInt('cloud_interval') ?? 0; _sleepDefaultMin = p.getInt('sleep_timer_default_min') ?? 0; _geschwindigkeit = speed; _akzent = UserEffekt.farbeAusHex(akzentHex) ?? MeloTheme.akzent; }); // Speed-Default auch auf den Player anwenden (gilt fürs Abspielen) await PlayerService().setGeschwindigkeit(speed); } Future _ladeSpeicherInfo() async { final info = await DbHelper().speicherInfo(); if (!mounted) return; setState(() => _speicherInfo = info); } // ─── Setter (persistieren + anwenden) ─── Future _intervallSetzen(int stunden) async { setState(() => _syncIntervall = stunden); final p = await SharedPreferences.getInstance(); await p.setInt('cloud_interval', stunden); await p.setBool('cloud_auto', stunden > 0); // Kompatibilität (altes Flag) await SyncService.starteAutoSyncTimer(); // app-weiter Timer neu starten } 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; // live — main.dart rebuildet das Theme final p = await SharedPreferences.getInstance(); await p.setString( UserEffekt.akzentKey(AuthService().benutzer), _hexVonFarbe(farbe)); } 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.dunkel1, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white, size: 22), onPressed: () => Navigator.pop(context), ), title: const Row( children: [ Icon(Icons.settings, color: MeloTheme.rot, size: 20), SizedBox(width: 10), Text( 'Einstellungen', style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600), ), ], ), ), body: ListView( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), children: [ // ─── Sektion: Verbindung ─── _sektionHeader('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: () { // Signal zum Öffnen des Server-Browsers Navigator.pop(context, 'server'); }, ), const SizedBox(height: 8), // ─── Sektion: Wiedergabe & Sync ─── _sektionHeader('Wiedergabe & Sync'), _einstellungsGruppe([ _gruppenZeile( label: 'Auto-Sync-Intervall', wert: _syncIntervall == 0 ? 'Aus' : '${_syncIntervall}h', inhalt: Row(children: [ _wahlChip('Aus', _syncIntervall == 0, () => _intervallSetzen(0)), _wahlChip('1h', _syncIntervall == 1, () => _intervallSetzen(1)), _wahlChip('3h', _syncIntervall == 3, () => _intervallSetzen(3)), _wahlChip('6h', _syncIntervall == 6, () => _intervallSetzen(6)), _wahlChip('12h', _syncIntervall == 12, () => _intervallSetzen(12)), ]), ), 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: 'Wiedergabegeschwindigkeit', wert: '${_geschwindigkeit.toStringAsFixed(2)}x', inhalt: Slider( value: _geschwindigkeit, min: 0.5, max: 2.0, divisions: 6, activeColor: MeloTheme.akzent, // Live-Vorschau beim Ziehen, persistieren erst am Ende onChanged: (v) => setState(() => _geschwindigkeit = v), onChangeEnd: _geschwindigkeitSetzen, ), ), ]), const SizedBox(height: 8), // ─── Sektion: Aussehen ─── _sektionHeader('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: 8), // ─── Sektion: Daten & Privatsphäre ─── _sektionHeader('Daten & Privatsphäre'), _infoKachel( icon: Icons.storage_outlined, titel: 'Lokale Musik', wert: _speicherText, ), _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: 8), // ─── Sektion: Erweitert (Technik) ─── _sektionHeader('Erweitert'), _einstellungsKachel( icon: Icons.handyman_outlined, titel: 'Erweitert', untertitel: 'Logs, Diagnose, Entwickler & Scanner', onTap: () async { // Erweitert-Screen öffnen. „Musik scannen“ (onScan) schließt den // Erweitert-Screen mit dem Ergebnis 'scanner' → hier awaiten und // das Ergebnis an MeloHome weiterreichen, das den Scanner startet. 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.article_outlined, titel: 'Logs', untertitel: 'App-Logbuch im Speicher ansehen', onTap: () { Navigator.push( context, MaterialPageRoute(builder: (_) => const LogViewerScreen()), ); }, ), _einstellungsKachel( icon: Icons.bug_report_outlined, titel: 'Entwickler', untertitel: 'Version, URLs & technische Details', onTap: () { Navigator.push( context, MaterialPageRoute(builder: (_) => const EntwicklerScreen()), ); }, ), const SizedBox(height: 8), // ─── Sektion: Info ─── _sektionHeader('Info'), Container( decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(14), border: Border.all(color: MeloTheme.dunkel2), ), child: Column( children: [ _infoZeile('Version', '2.52.3'), const Divider(color: MeloTheme.dunkel2, height: 1), _infoZeile('Theme', 'Schwarz + Akzent'), const Divider(color: MeloTheme.dunkel2, height: 1), _infoZeile('Auth', AppConfig.authUrl), const Divider(color: MeloTheme.dunkel2, height: 1), _infoZeile('Cloud', AppConfig.cloudUrl), ], ), ), const SizedBox(height: 24), // ─── 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.rot, side: const BorderSide(color: MeloTheme.rot), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), ), ), ), const SizedBox(height: 32), ], ), ); } Widget _sektionHeader(String titel) { return Padding( padding: const EdgeInsets.fromLTRB(4, 16, 4, 8), child: Text( titel, style: const TextStyle( color: MeloTheme.textSekundaer, fontSize: 11, fontWeight: FontWeight.w600, letterSpacing: 1.2, ), ), ); } 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(14), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(14), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), border: Border.all(color: MeloTheme.dunkel2), ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: MeloTheme.dunkel2, borderRadius: BorderRadius.circular(10), ), child: Icon(icon, color: MeloTheme.rot, 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), ], ), ), ), ), ); } /// Informations-Kachel (nicht antippbar) — z.B. Speicher-Info. 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(14), border: Border.all(color: MeloTheme.dunkel2), ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: MeloTheme.dunkel2, borderRadius: BorderRadius.circular(10), ), child: Icon(icon, color: MeloTheme.rot, 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), ), ], ), ), ); } /// Gruppe aus mehreren Einstellungs-Zeilen in einer abgerundeten Box. Widget _einstellungsGruppe(List kinder) { return Container( margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(14), 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(16), ), 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, ), ), 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), ), ], ), ); } }