- 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
62 lines
2.1 KiB
Dart
62 lines
2.1 KiB
Dart
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 (A–Z–#)',
|
||
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;
|
||
}
|
||
|
||
/// A–Z 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');
|
||
}
|