Neue Melo-UI Phase 1: 4 Tabs (Meine Musik/Suche/Favoriten/Download) + Sortierung

- Meine Musik als Startbildschirm ohne Topbar: Kopfzeile (Einstellungen,
  Suche, Musikerkennung), Schnellzugriffe, Shuffle + Sortieren, Liederliste
- Sortierung nach Zuletzt hinzugefuegt / Name (A-Z-#) / Wie oft abgespielt,
  auf- und absteigend, pro Liste gespeichert (SortStore)
- Favoriten als eigener Tab mit gleicher Shuffle-/Sortier-Leiste
- Download-Tab uebernimmt den Navidrome-Server-Browser
- DB-Schema 3: playCount, gezaehlt beim Titelstart
- Theme auf #0B0B10 / #c0392b
- Scan-Aktionen aus der entfallenen Topbar jetzt in den Einstellungen

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011snPXPafPC5i87o68H14W7
This commit is contained in:
2026-08-20 18:00:53 +02:00
co-authored by Claude Opus 5
parent 5d08da1086
commit 2667613a2e
24 changed files with 1413 additions and 372 deletions
+15
View File
@@ -20,6 +20,12 @@ bool shouldResumeAt(int lastPositionMs, Duration? trackDuration) {
return true;
}
/// Entscheidet, ob für den Titel an [newIndex] eine Wiedergabe gezählt wird.
/// Gezählt wird jeder Titelwechsel; eine Wiederholung desselben Titels
/// (LoopMode.one) zählt bewusst nicht erneut.
bool shouldCountPlay(int? newIndex, int? lastCountedIndex) =>
newIndex != null && newIndex != lastCountedIndex;
/// Kern der Wiedergabe: kapselt just_audio hinter audio_service,
/// damit Hintergrund-Wiedergabe + Lockscreen/Notification funktionieren.
class MeloAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
@@ -29,6 +35,7 @@ class MeloAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
late final Timer _positionRecordTimer;
late final CacheManager _cache;
final NavidromeService _nav = NavidromeService();
int? _lastCountedIndex;
MeloAudioHandler({required this.db}) {
_cache = CacheManager();
@@ -56,6 +63,11 @@ class MeloAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
final q = queue.value;
if (index != null && index < q.length) {
mediaItem.add(q[index].copyWith(duration: _player.duration));
if (shouldCountPlay(index, _lastCountedIndex)) {
_lastCountedIndex = index;
final songId = q[index].extras?['songId'] as String?;
if (songId != null) db.incrementPlayCount(songId);
}
}
});
@@ -72,6 +84,9 @@ class MeloAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
/// Auto-Caching: Streame von Server + speichere lokal gleichzeitig.
Future<void> loadPlaylist(List<MediaItem> items, {int startIndex = 0}) async {
queue.add(items);
// Neue Warteschlange: der erste Titel soll wieder zählen, auch wenn er
// denselben Index wie der zuletzt gezählte hat.
_lastCountedIndex = null;
await _nav.ladeGespeicherteZugangsdaten();
final sources = <AudioSource>[];