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
This commit is contained in:
Hermes (Server)
2026-08-25 14:17:13 +02:00
co-authored by Claude Sonnet 5
parent 54cb523cf4
commit da012a3dca
12 changed files with 364 additions and 73 deletions
+36
View File
@@ -8,6 +8,20 @@ class AppSettings extends ChangeNotifier {
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.
@@ -20,6 +34,7 @@ class AppSettings extends ChangeNotifier {
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.
@@ -39,12 +54,19 @@ class AppSettings extends ChangeNotifier {
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();
}
@@ -79,4 +101,18 @@ class AppSettings extends ChangeNotifier {
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());
}
}
+40
View File
@@ -160,6 +160,22 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
),
const Divider(height: 1),
const _SectionLabel('Meine Musik'),
Consumer<AppSettings>(
builder: (context, settings, _) => Column(
children: [
for (final sektion in AppSettings.alleMeineMusikSektionen)
CheckboxListTile(
secondary: Icon(_sektionIcon(sektion)),
title: Text(_sektionLabel(sektion)),
value: settings.meineMusikSektionen.contains(sektion),
onChanged: (aktiv) => settings.setMeineMusikSektionAktiv(
sektion, aktiv ?? false),
),
],
),
),
const Divider(height: 1),
const _SectionLabel('Darstellung'),
Consumer<AppSettings>(
builder: (context, settings, _) => SwitchListTile(
@@ -301,6 +317,15 @@ class _SettingsScreenState extends State<SettingsScreen> {
child: const Text('Einstellungen öffnen'),
),
),
ListTile(
leading: const Icon(Icons.notifications_outlined),
title: const Text('Benachrichtigungs-Berechtigung'),
subtitle: const Text('Für die Steuerung im Sperrbildschirm'),
trailing: TextButton(
onPressed: openMusicPermissionSettings,
child: const Text('Einstellungen öffnen'),
),
),
const Divider(height: 1),
const _SectionLabel('Über Melo'),
ListTile(
@@ -577,6 +602,21 @@ class _SettingsScreenState extends State<SettingsScreen> {
}
}
/// Icon und Beschriftung der Schnellzugriff-Sektionen aus [AppSettings].
IconData _sektionIcon(String sektion) => switch (sektion) {
AppSettings.sektionFavoriten => Icons.favorite,
AppSettings.sektionWiedergabelisten => Icons.queue_music,
AppSettings.sektionZuletzt => Icons.schedule,
_ => Icons.circle,
};
String _sektionLabel(String sektion) => switch (sektion) {
AppSettings.sektionFavoriten => 'Favoriten',
AppSettings.sektionWiedergabelisten => 'Wiedergabelisten',
AppSettings.sektionZuletzt => 'Zuletzt hinzugefügt',
_ => sektion,
};
/// Kleine graue Überschrift über einer Einstellungs-Gruppe.
class _SectionLabel extends StatelessWidget {
const _SectionLabel(this.label);