Files
Melo/lib/library/song_sort.dart
dustinandClaude Opus 5 2667613a2e 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
2026-08-20 18:00:53 +02:00

62 lines
2.1 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'database.dart';
/// Sortier-Kriterien der Liederlisten (Meine Musik + Favoriten).
enum SortMode { dateAdded, name, playCount }
extension SortModeLabel on SortMode {
String get label => switch (this) {
SortMode.dateAdded => 'Zuletzt hinzugefügt',
SortMode.name => 'Name (AZ#)',
SortMode.playCount => 'Wie oft abgespielt',
};
}
/// Richtung, in der ein Modus üblicherweise gelesen wird: neueste bzw.
/// meistgespielte Songs zuerst, Namen dagegen von A nach Z.
bool defaultAscending(SortMode mode) => mode == SortMode.name;
/// Sortiert eine Kopie von [songs]. Bei gleichem Sortierwert entscheidet der
/// Titel, damit die Reihenfolge zwischen Aufrufen stabil bleibt.
List<Song> sortSongs(List<Song> songs, SortMode mode, {required bool ascending}) {
final sorted = [...songs];
sorted.sort((a, b) {
final cmp = switch (mode) {
SortMode.dateAdded => a.dateAddedMs.compareTo(b.dateAddedMs),
SortMode.playCount => a.playCount.compareTo(b.playCount),
SortMode.name => _compareNames(a.title, b.title),
};
if (cmp != 0) return ascending ? cmp : -cmp;
// Tiebreak immer aufsteigend nach Titel — sonst wäre die Reihenfolge
// gleichwertiger Songs beim Umschalten der Richtung willkürlich.
return _compareNames(a.title, b.title);
});
return sorted;
}
/// AZ zuerst, alles was nicht mit einem Buchstaben beginnt (Ziffern,
/// Sonderzeichen) landet unter "#" am Ende.
int _compareNames(String a, String b) {
final keyA = _sortKey(a);
final keyB = _sortKey(b);
final letterA = _startsWithLetter(keyA);
final letterB = _startsWithLetter(keyB);
if (letterA != letterB) return letterA ? -1 : 1;
return keyA.compareTo(keyB);
}
final _letter = RegExp(r'^\p{L}', unicode: true);
bool _startsWithLetter(String s) => _letter.hasMatch(s);
/// Kleinschreibung + Umlaute auf ihren Grundbuchstaben, damit "Ärger"
/// zwischen "Anfang" und "Berg" steht und nicht hinter "Zug".
String _sortKey(String title) {
return title
.trim()
.toLowerCase()
.replaceAll('ä', 'a')
.replaceAll('ö', 'o')
.replaceAll('ü', 'u')
.replaceAll('ß', 'ss');
}