Files
Melo/lib/settings/app_settings.dart
T
Hermes (Server)andClaude Opus 5 239aae3ea3 Datenschutz: Fehlerberichte lassen sich abschalten
Uebertragung eines Befunds aus dem Security-Audit der alten Melo-App
(21.08.2026) in die neue Codebasis.

Ausgangslage hier war eine ANDERE als dort: LoggerService lud jeden Fehler
unbedingt zu logs.baka-net.de hoch, es gab ueberhaupt keinen Schalter. In
der alten App existierte er zwar, wirkte aber nicht ueber einen Neustart
hinaus.

Neu:
- Schalter "Fehlerberichte senden" in den Einstellungen unter Datenschutz.
- Sperre an der einzigen Stelle, durch die jeder Upload laeuft. Lokales
  debugPrint bleibt, aber nichts verlaesst das Geraet; der Puffer wird
  geleert, damit er nicht unbegrenzt waechst.
- Die Entscheidung ueberlebt den Neustart: AppSettings speichert sie und
  reicht sie an den Logger durch, UND LoggerService liest sie beim Start
  zusaetzlich selbst - main() startet den Logger vor den Einstellungen, und
  schon dieses Zeitfenster darf bei Widerspruch nichts senden.
- LoggerService bekam einen injizierbaren http.Client (Muster wie BakaAuth),
  damit sich belegen laesst, dass bei Opt-out KEIN Request rausgeht.

Nicht uebernommen: der zweite Befund (Logout liess Zugangsdaten zurueck)
trifft hier nicht zu. BakaAuth.abmelden() loescht genau die Schluessel, die
anmelden() schreibt, und Navidrome hat mit loescheZugangsdaten() ein
eigenes, vollstaendiges Trennen. Der alte Fehler - Logout raeumt tote
Schluessel ab, waehrend die echten liegen bleiben - existiert in dieser
Codebasis nicht.

Standard bleibt bewusst "an", damit sich fuer bestehende Nutzer nichts
ungefragt aendert. Opt-in waere datenschutzfreundlicher, ist aber eine
Produktentscheidung.

Tests: 304 gruen (vorher 295), 9 neue. Alle drei tragenden Stellen per
Mutationsprobe geprueft. flutter analyze: keine Befunde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbuWT267W8rFDsaavANuEn
2026-08-21 12:24:11 +02:00

80 lines
3.1 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../services/logger_service.dart';
/// Nutzer-Einstellungen, die die ganze App betreffen. Werden beim Start
/// einmal geladen und bei jeder Änderung sofort gespeichert.
class AppSettings extends ChangeNotifier {
static const _keyGroupCovers = 'gleiche_kategorie_gleiches_cover';
static const _keyAutoPlayHeadphones = 'autoplay_kopfhoerer';
static const _keyNormalizeVolume = 'gleiche_lautstaerke';
// Schlüssel gehört dem Logger — der liest ihn beim Start selbst.
static const _keyDiagnose = LoggerService.diagnoseKey;
bool _groupCoversByCategory = true;
bool _autoPlayOnHeadphones = false;
bool _normalizeVolume = true;
bool _sendeDiagnosedaten = true;
/// "Gleiche Kategorie = gleiches Coverbild" — an: das Cover kommt von der
/// ersten Kategorie des Songs, aus: jeder Song zeigt sein eigenes Bild.
bool get groupCoversByCategory => _groupCoversByCategory;
/// Wiedergabe automatisch starten, sobald Kopfhörer verbunden werden.
bool get autoPlayOnHeadphones => _autoPlayOnHeadphones;
/// "Gleiche Lautstärke": laute Titel werden anhand ihres ReplayGain-Tags
/// auf das Niveau der leiseren abgesenkt.
bool get normalizeVolume => _normalizeVolume;
/// Fehler- und Absturzberichte an logs.baka-net.de senden.
/// Aus: die App protokolliert weiter lokal, aber nichts verlässt das Gerät.
bool get sendeDiagnosedaten => _sendeDiagnosedaten;
Future<void> init() async {
final prefs = await SharedPreferences.getInstance();
_groupCoversByCategory = prefs.getBool(_keyGroupCovers) ?? true;
_autoPlayOnHeadphones = prefs.getBool(_keyAutoPlayHeadphones) ?? false;
_normalizeVolume = prefs.getBool(_keyNormalizeVolume) ?? true;
_sendeDiagnosedaten = prefs.getBool(_keyDiagnose) ?? true;
// An den Logger durchreichen, damit Anzeige und tatsächliches
// Sendeverhalten nicht auseinanderlaufen können.
logger.sendeDiagnosedaten = _sendeDiagnosedaten;
notifyListeners();
}
Future<void> setGroupCoversByCategory(bool value) async {
if (_groupCoversByCategory == value) return;
_groupCoversByCategory = value;
notifyListeners();
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_keyGroupCovers, value);
}
Future<void> setNormalizeVolume(bool value) async {
if (_normalizeVolume == value) return;
_normalizeVolume = value;
notifyListeners();
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_keyNormalizeVolume, value);
}
Future<void> setAutoPlayOnHeadphones(bool value) async {
if (_autoPlayOnHeadphones == value) return;
_autoPlayOnHeadphones = value;
notifyListeners();
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_keyAutoPlayHeadphones, value);
}
Future<void> setSendeDiagnosedaten(bool value) async {
if (_sendeDiagnosedaten == value) return;
_sendeDiagnosedaten = value;
logger.sendeDiagnosedaten = value;
notifyListeners();
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_keyDiagnose, value);
}
}