Files
Melo/lib/settings/app_settings.dart
Hermes (Server)andClaude Sonnet 5 da012a3dca Tab-Reihenfolge, konfigurierbare Meine-Musik-Sektionen, Berechtigungen beim Start, schärferes Sperrbildschirm-Cover
- Tabs: Meine Musik, Suchen, Download, Favoriten (vorher Download vor Suchen)
- "Meine Musik": Schnellzugriffe (Favoriten/Wiedergabelisten/Zuletzt) einzeln
  über die Einstellungen abschaltbar
- Audio- und Benachrichtigungs-Berechtigung werden jetzt beim ersten Start
  gemeinsam angefragt statt erst bei Bedarf; Hinweisbanner + Einstellungen-
  Button bei dauerhafter Verweigerung (auch für Benachrichtigungen neu)
- Cover-Extraktion beim Android-Scan von 512px auf 1024px erhöht, für ein
  schärferes Cover im Sperrbildschirm
- Fix: notifyListeners() in LibraryService nur noch bei echter Wertänderung
  (verhinderte einen Test-Hang durch den neuen Start-Berechtigungs-Aufruf)

557 Tests grün, flutter analyze ohne Befund.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VM2JK5mV7AL1g2Rt6H6h9w
2026-08-25 14:17:13 +02:00

119 lines
4.8 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.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';
static const _keyCacheGrenze = 'cache_grenze_mb';
static const _keyMeineMusikSektionen = 'meine_musik_sektionen';
/// Schnellzugriffe oben in „Meine Musik" — Schlüssel für die
/// [meineMusikSektionen]-Auswahl in den Einstellungen.
static const sektionFavoriten = 'favoriten';
static const sektionWiedergabelisten = 'wiedergabelisten';
static const sektionZuletzt = 'zuletzt';
/// Alle wählbaren Sektionen, in der Reihenfolge, in der sie oben erscheinen.
static const alleMeineMusikSektionen = [
sektionFavoriten,
sektionWiedergabelisten,
sektionZuletzt,
];
/// Voreinstellung für den Zwischenspeicher: 2 GB. Groß genug für einige
/// hundert Titel, klein genug, um ein Telefon nicht zuzumüllen.
static const cacheGrenzeStandardMb = 2048;
/// Zur Auswahl stehende Grenzen in MB. 0 heißt: gar nicht zwischenspeichern.
static const cacheGrenzenMb = [0, 512, 1024, 2048, 4096, 8192];
bool _groupCoversByCategory = true;
bool _autoPlayOnHeadphones = false;
bool _normalizeVolume = true;
int _cacheGrenzeMb = cacheGrenzeStandardMb;
Set<String> _meineMusikSektionen = Set.of(alleMeineMusikSektionen);
/// "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;
/// Obergrenze des Zwischenspeichers in MB. Gilt nur für das, was beim
/// Abspielen nebenbei anfällt — bewusst heruntergeladene Titel liegen in
/// einem eigenen Ordner und werden nie verdrängt.
int get cacheGrenzeMb => _cacheGrenzeMb;
int get cacheGrenzeBytes => _cacheGrenzeMb * 1024 * 1024;
/// Welche Schnellzugriffe oben in „Meine Musik" angezeigt werden.
Set<String> get meineMusikSektionen => _meineMusikSektionen;
Future<void> init() async {
final prefs = await SharedPreferences.getInstance();
_groupCoversByCategory = prefs.getBool(_keyGroupCovers) ?? true;
_autoPlayOnHeadphones = prefs.getBool(_keyAutoPlayHeadphones) ?? false;
_normalizeVolume = prefs.getBool(_keyNormalizeVolume) ?? true;
_cacheGrenzeMb = prefs.getInt(_keyCacheGrenze) ?? cacheGrenzeStandardMb;
final gespeicherteSektionen = prefs.getStringList(_keyMeineMusikSektionen);
_meineMusikSektionen = gespeicherteSektionen == null
? Set.of(alleMeineMusikSektionen)
: Set.of(gespeicherteSektionen);
notifyListeners();
}
Future<void> setCacheGrenzeMb(int mb) async {
if (_cacheGrenzeMb == mb) return;
_cacheGrenzeMb = mb;
notifyListeners();
final prefs = await SharedPreferences.getInstance();
await prefs.setInt(_keyCacheGrenze, mb);
}
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);
}
/// Blendet eine Schnellzugriff-Sektion oben in „Meine Musik" ein oder aus.
Future<void> setMeineMusikSektionAktiv(String sektion, bool aktiv) async {
final aktuell = aktiv == _meineMusikSektionen.contains(sektion);
if (aktuell) return;
_meineMusikSektionen = Set.of(_meineMusikSektionen);
aktiv
? _meineMusikSektionen.add(sektion)
: _meineMusikSektionen.remove(sektion);
notifyListeners();
final prefs = await SharedPreferences.getInstance();
await prefs.setStringList(
_keyMeineMusikSektionen, _meineMusikSektionen.toList());
}
}