157 Commits
Author SHA1 Message Date
marcandClaude Opus 5 b6e98463ee feat(web+android): Lyrics-Mitlaufen umschaltbar, echtes Mischen, Wiederholung am Queue-Ende
Lyrics: Schalter "Mitlaufen" im Lyrics-Reiter, Vorgabe AUS. Hervorgehoben wird die
laufende Zeile immer, gescrollt nur bei eingeschaltetem Mitlaufen. Nur bei synchronen
Texten sichtbar. Android speichert den Schluessel einzeln, damit das Speichern im
Einstellungsbildschirm ihn nicht auf die Vorgabe zuruecksetzt.

Mischen (Web): sort(() => Math.random() - 0.5) ist keine Mischung. Gemessen blieb der
erste Titel drei- bis fuenfmal zu oft vorn (18,8 % statt 10 % bei n=10, 0,23 % statt
0,05 % bei n=2000). Ersetzt durch Fisher-Yates in queueMath.mischeListe.

Queue-Ende (Web): ein beendetes <audio> meldet sich als pausiert, die Wiederaufnahme
nach unfreiwilligem Hintergrund-Stop rief play() und spielte den letzten Titel erneut.
Jetzt Abbruch bei audio.ended.

Shuffle (Android): das Mischen macht jetzt der Player (playSongs(liste, -1)), damit die
ungemischte Liste erhalten bleibt — vorher leuchtete das Symbol nicht und "Shuffle aus"
konnte die Albumreihenfolge nicht wiederherstellen.

Radio-Modus (beide): der laufende Titel ist aus der letzten Rueckfallebene ausgenommen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-01 17:13:38 +02:00
marcandClaude Opus 5 ae357b0eb9 change: Konzerte ohne Zeitfenster — Chips nur noch bei den Veroeffentlichungen
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 10:46:05 +02:00
marcandClaude Opus 5 17a677a5a5 feat(web+android+backend): Konzerte gefolgter Kuenstler als zweiter Reiter
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-25 19:23:00 +02:00
marcandClaude Opus 5 1432a960fb style: Honigtopf auf Feldbreite aufgezogen, wirkt jetzt so gross wie das Zahnrad
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 23:11:07 +02:00
marcandClaude Opus 5 37f8f8acac feat(web+android): Gesamtdauer mit Stunden, in Android neu
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 22:38:40 +02:00
marcandClaude Opus 5 e8582381ec feat(web+android): Honigtopf mit Brief statt Glocke fuer Benachrichtigungen
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 16:53:06 +02:00
marcandClaude Opus 5 899c98977a feat: Wischgeste wechselt Titel, Designs Violet und Crimson, Mischen-Anzeige gefixt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 21:04:37 +02:00
marcandClaude Opus 5 2258e5465f fix(android): Songzeilen halten rechts Platz fuer den Rollbalken
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 20:47:37 +02:00
marcandClaude Opus 5 2641a6df44 fix(android): Rollbalken verschluckte das Kontextmenue der Songzeilen
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 20:45:20 +02:00
marcandClaude Opus 5 d5b5c8cf84 feat(web+android): Warteschlange bearbeiten — ziehen und entfernen
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 20:31:08 +02:00
marcandClaude Opus 5 77a3bb33f0 fix(android): Rollbalken-Geste brach ab, weil pointerInput auf die Geometrie geschluesselt war
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 20:24:37 +02:00
marcandClaude Opus 5 f083fad986 fix(android): Rollbalken liess sich nicht greifen — Greifflaeche und Gestenvorrang
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 20:20:08 +02:00
marcandClaude Opus 5 1aa218f3e3 feat(android): Rollbalken zum Ziehen in den langen Listen
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 20:13:10 +02:00
marcandClaude Opus 5 eb72e5c27d feat(android): Album-Knopfleiste wie im Playlist-Detail; Changelog-Backlog aufgeraeumt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:06:30 +02:00
marcandClaude Opus 5 86810b6a51 feat(web+android): gefolgte Künstler begrenzt anzeigen, mit "Alle anzeigen"
Vorher landeten alle gefolgten Künstler in der waagerechten Reihe der
Favoriten — bei 200 Gefolgten 200 Karten samt Bildern, von denen man vier
sieht. Am Ende steht jetzt "Alle anzeigen (+n weitere)" und springt in die
Künstlerliste mit aktivem Gefolgt-Filter.

Im Web richtet sich die Anzahl nach der Fensterbreite statt nach einer
festen Zahl (passende Karten minus eine, damit der Knopf nicht
angeschnitten klebt; Untergrenze zwei), und bei Größenänderung wird neu
aufgeteilt. Android zeigt zehn; die LazyRow baut ohnehin nur Sichtbares,
dort ging es allein um Durchsehbarkeit.

LibraryIntent reicht die Absicht einmalig durch, weil das
LibraryViewModel am Navigationseintrag hängt und einen Zielwechsel nicht
überlebt.

Dazu: die Zeitfenster-Chips der Android-Zeitleiste beginnen mit 90 Tagen,
der Vorgabe — Web war schon so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 18:37:47 +02:00
marcandClaude Opus 5 cb43c3375e feat(android): Gefolgt-Filter im Künstler-Reiter und Zeitleiste "Neu von deinen Künstlern"
Filter statt eigenem Reiter: die Bibliotheksleiste trägt schon Playlists,
Alben, Künstler und Offline. Die markierten IDs kommen bei jedem
Einschalten frisch, damit ein eben entfolgter Künstler nicht dasteht.

Die Zeitleiste ist über "✨ Neu" in den Favoriten erreichbar — dort stehen
die gefolgten Künstler, also liegt die Frage nach Neuem dort nahe.
Gleiche Darstellung wie im Web: nach Monat gruppiert, neueste zuerst,
Vorhandenes mit Haken statt Download-Knopf, Künstlername anklickbar.
Download läuft über den bekannten Weg samt Fortschrittsmeldung.

Neu: api/ReleasesClient.kt, api/model/ReleaseModels.kt,
viewmodel/ArtistNewsViewModel.kt, ui/screen/ArtistNewsScreen.kt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 14:10:37 +02:00
marcandClaude Opus 5 19f241e493 feat(web+android): Folgen-Herz ins Künstler-Banner, Favoriten zeigen Künstler und Alben
Das Herz sitzt jetzt im Banner neben dem Pin-Zahnrad statt in der
Aktionsleiste: die Leiste ist "was ich jetzt tue" (abspielen, mischen,
Konzerte), Folgen ist ein Zustand und gehört an den Künstler. Auf beiden
Plattformen gleich. Beim Album bleibt das Herz in der Aktionsleiste, dort
gibt es kein Banner.

Dazu die Übersicht, wem man folgt: die Favoritenseite bekommt Abschnitte
Künstler / Alben / Songs. Ohne zusätzlichen Aufruf — getStarred2 liefert
Künstler und Alben im selben Paket wie die Songs, wir haben sie bisher
verworfen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:11:18 +02:00
marcandClaude Opus 5 5f043beb5d feat(web+android): Künstlern folgen, Alben merken, Top Songs per Künstler-ID
Folgen ist kein eigener Speicher, sondern Navidromes Stern auf dem
Künstler (star&id=<ndArtistId>) — Navidrome wirft id, albumId und
artistId beim Markieren in eine Liste und behandelt sie gleich, der
vorhandene Aufruf trug also ohne Änderung. Zustand aus `starred` in
getArtist/getAlbum, beide Plattformen schalten optimistisch um.

Android nutzt den vorhandenen StarState, der ohnehin über IDs arbeitet.

Dazu: getTopSongs bekommt seit Navidrome 0.64 zusätzlich `id`. Der
Namensweg sucht mit LIKE und Max:1, bei gleichnamigen Künstlern gewann
der erste Treffer. Jetzt id + artist zusammen; in Erkunden bleibt es beim
Namen, weil die artistId dort Deezers ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 17:51:53 +02:00
marcandClaude Opus 5 de674dbe3f fix(android): Offline-Downloads vollständig löschen, Fortschritt selbst begradigen
Löschen ließ Dateien zurück: Cover wurden nie entfernt (nur die
Audiodatei), unfertige Titel behielten ihre Datei, weil der Pfad aus der
Karte der FERTIGEN Titel kam, und laufende Downloadarbeit lief nach dem
Löschen noch minutenlang ins Leere. Jetzt kommt der Pfad aus der
Datenbankzeile, .tmp wird mitgenommen, Cover gehen nur weg wenn kein
anderer Titel sie noch benutzt, und die Arbeit der Gruppe wird
abgebrochen.

Dazu: ein zu hoch gezählter Fortschritt aus der Zeit vor 1.10 konnte sich
nie mehr korrigieren — die Gruppe galt als vollständig, der Knopf war
unantastbar und das Fortsetzen sucht nur Gruppen unter dem Sollwert. Beim
App-Start wird der Fortschritt aller Gruppen einmal aus dem echten
Zustand neu berechnet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 12:00:08 +02:00
marcandClaude Opus 5 ffedc7532b feat(android): Schalter "Nur über WLAN herunterladen" (Standard an)
Die Downloadarbeit lief mit NetworkType.CONNECTED, also über jede
Verbindung. Das war selbstbegrenzend, solange nach zehn Minuten Schluss
war — mit der automatischen Wiederholung aus 1.9 hätte sich eine große
Playlist unterwegs ununterbrochen weitergeladen (1900 Titel ≈ 9-10 GB).

Neuer Schalter unter "Offline-Downloads", standardmäßig an: dann gilt
UNMETERED, der Download pausiert ohne WLAN und läuft von selbst weiter.
Abschalten nur über eine Rückfrage, die das Datenvolumen benennt.

Beim Speichern werden laufende Gruppen mit REPLACE neu eingereiht —
WorkManager ändert die Bedingungen eingereihter Arbeit nicht mehr, der
Schalter hätte sonst erst beim nächsten App-Start gewirkt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 22:34:44 +02:00
marcandClaude Opus 5 e2e7b2490f fix(android): Zählfehler im Downloader — halb geladene Gruppen galten als vollständig
Der Worker startete done bei group.downloadedTracks und zählte dieselben
fertigen Titel beim Überspringen nochmal: 412 fertige wurden zu 824.
Übersteigt der Wert totalTracks, gilt die Gruppe als vollständig, der
Fortschrittsbalken verschwindet und der Herunterladen-Knopf wird
unantastbar, obwohl Titel fehlen.

Der Fehler ist älter, fiel aber kaum auf, solange Fortsetzen nur von Hand
passierte — mit der automatischen Wiederholung aus 1.9 hätte er bei jedem
Lauf zugeschlagen. Zähler beginnt jetzt bei 0.

Dazu: beim Überspringen wurde der unveränderte Fortschritt geschrieben,
1900 überflüssige DB-Writes je Lauf. Jetzt nur nach echtem Versuch und
einmal beim Abbruch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 20:22:18 +02:00
marcandClaude Opus 5 8bc03da88e fix(android): große Offline-Downloads laufen jetzt bis zum Ende
WorkManager stoppt einen Hintergrund-Worker nach ~10 Minuten (ein
Vordergrunddienst scheidet aus, FOREGROUND_SERVICE_TYPE_DATA_SYNC stürzt
unter Android 15 ab). Der Worker prüfte nie isStopped, lief blind weiter
und meldete Result.failure() ohne Backoff — WorkManager startete nichts
neu, bei 1900 Titeln musste man wiederholt von Hand antippen.

Jetzt: sauberer Ausstieg bei isStopped mit Result.retry() und linearem
Backoff, Selbst-Fortsetzung solange Titel offen sind (max. 20 Versuche,
damit dauerhaft kaputte Titel keine Endlosschleife auslösen), Retry statt
Failure im äußeren catch, und beim App-Start werden unvollständige
Gruppen weitergeführt.

Dazu: die Zahl offener Titel kam aus einer Abfrage pro Titel am Ende
jedes Laufs — jetzt ein einzelnes COUNT mit JOIN.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:17:50 +02:00
marcandClaude Opus 5 35e36067af fix(android): große Offline-Playlists ließen sich nicht abspielen
Die Warteschlange wurde aus allen Titeln einer Gruppe gebaut, ohne den
Download-Zustand zu prüfen. Die Map für lokale Dateien kennt aber nur
state = 'DONE' — für wartende oder fehlgeschlagene Titel lieferte
streamUrl() die Netz-Adresse, die offline nichts tut. Bei 100 Titeln ist
der Download durch, bei 1900 nicht; steht so ein Titel vorn, scheitert
die Wiedergabe sofort.

Play und Shuffle nehmen jetzt nur fertige Titel, ein Toast nennt die Zahl
der übersprungenen. Die Liste zeigt weiter alle, damit sichtbar bleibt,
was fehlt; Tippen auf einen unfertigen sagt es ebenfalls.

Nicht die Ursache: Media3 reicht große Listen als BundleListRetriever
durch, die 1-MB-Binder-Grenze greift dort nicht.

Dazu Backlog-Notiz: Künstler/Alben liken als Basis für "Künstler folgen".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:50:27 +02:00
marcandClaude Opus 5 568bb5002c fix(web): Zurück-Knopf tat oft nichts + Besitzer geteilter Playlists (2.6)
navigate() ließ den Stack-Eintrag weg, wenn sich die Ansicht nicht
geändert hatte, rief history.pushState aber trotzdem. Zweimal auf
denselben Tab geklickt = zwei Browser-Einträge auf dieselbe Ansicht,
jeder davon ein Zurück-Druck ohne sichtbare Wirkung. Im Test: 3x "Alben"
-> 3 Browser-Einträge bei 1 Stack-Eintrag, danach bewegten 4 Zurück-
Drücke die Ansicht einmal. Jetzt pusht nur eine wirklich neue Ansicht,
sonst replaceState. Vorwärts mitgetestet, unverändert.

Playlists: Navidrome liefert owner mit, das Web nutzte es nie. Übersicht
und Detail zeigen "· von <Name>", nur bei fremden Listen. Android zeigte
ihn bisher bei jeder Liste und unbeschriftet; beide Plattformen jetzt
gleich, Bibliotheks-Übersicht ebenfalls.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 14:12:07 +02:00
marcandClaude Opus 5 ec75fdef7b feat(web+android): vom Album in der globalen Suche zum Künstler springen (2.5 / Android 1.8)
Der Künstlername unter einem Deezer-Album war reiner Text — von dort zum
Künstler ging nur über Zurück und neu suchen. Er ist jetzt anklickbar und
öffnet dieselbe Deezer-Künstlerseite wie eine Künstler-Kachel.

Kostet keinen zusätzlichen API-Aufruf: Deezer legt /search/album und
/album/{id} das artist-Objekt samt id bei (120/120 Treffer über zwölf
Suchen geprüft), beide Mappings übernahmen die id längst.

/artist/{id}/albums lässt das artist-Objekt weg — Alben von dort bleiben
bewusst reiner Text, statt ins Leere zu zeigen.

Android zeigte in der Album-Detailansicht bisher gar keinen Künstler und
lädt Bild + Fanzahl nach, wenn der Künstler aus einem Album stammt.

.album-artist-name trug cursor:pointer auch ohne Ziel — jetzt nur noch,
wo wirklich etwas dahintersteht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 22:22:37 +02:00
marcandClaude Opus 5 fca7cc79c1 feat(android): Cache nach einem Update einmalig leeren (1.7)
Im cacheDir liegen Coils Bildspeicher und heruntergeladene APK-Updates, beides
jederzeit neu beschaffbar. Blieb dort nach einer neuen Version etwas Altes
liegen, war "Cache leeren" von Hand in den Systemeinstellungen die einzige
Abhilfe — nichts, das sich einem normalen Nutzer erklaeren laesst.

Geleert wird jetzt beim ersten Start nach einem Versionswechsel, verglichen wird
der gespeicherte versionCode. Im laufenden Betrieb passiert nichts, sonst waeren
die Cover bei jedem Start neu zu laden. Beim allerersten Start nach der
Installation ebenfalls nicht, da gibt es nichts aufzuraeumen.

Offline-Downloads bleiben unberuehrt: die liegen unter filesDir/offline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 08:42:27 +02:00
marcandClaude Opus 5 651f979401 perf(android): Warteschlange sofort wiederherstellen, SW-Cache automatisch versionieren
Android speicherte nur die Song-Ids und lud beim Start jeden Titel EINZELN und
nacheinander ueber das Netz nach. Das dauerte Sekunden, war unzuverlaessig und
ohne Verbindung unmoeglich. Gespeichert werden jetzt die vollstaendigen Songs als
JSON — Wiederherstellen ist ein lokaler Lesevorgang, wie im Web. Der Preis ist
ein veralteter Titel oder Cover bis zum naechsten Abspielen, was die deutlich
kleinere Unschoenheit ist.

Dazu sichert MusicService.onTaskRemoved die Warteschlange beim Wegwischen der
App: dort feuert onIsPlayingChanged nicht zuverlaessig, genau dieser Fall ging
vorher verloren.

Service Worker: die Cache-Version stand handgepflegt als ns-v78 in sw.js und
wurde beim Ausliefern vergessen — die Aktivierung loescht nur Caches mit
abweichendem Namen, also blieb der alte bestehen und Nutzer mussten den
Browsercache von Hand leeren. Ein Vite-Plugin setzt sie jetzt aus der Bauzeit,
die Quelldatei bleibt unveraendert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 00:01:30 +02:00
marcandClaude Opus 5 0b23a07039 fix(android): Warteschlangen-Wiederherstellung robuster und nachvollziehbar (1.5)
Das Wiederherstellen schlug still fehl. Mehrere runCatching verschluckten jeden
Grund, sodass von aussen nur "passiert nichts" zu sehen war.

Zwei wahrscheinliche Ursachen adressiert:
- connect() laeuft in Application.onCreate(), wo der DataStore mit den
  Navidrome-Zugangsdaten unter Umstaenden noch nicht gelesen ist. Dann scheitert
  das Aufloesen ALLER Song-Ids und die Warteschlange bleibt leer. Es wird jetzt
  bis zu fuenfmal mit wachsendem Abstand versucht.
- Wischt man die App aus den letzten Apps, wurde nichts gesichert:
  onIsPlayingChanged feuert dabei nicht zuverlaessig. MusicService.onTaskRemoved
  sichert jetzt vor dem Beenden.

Dazu Protokollausgaben unter dem Tag NsQueue an jeder Stufe (gesichert,
gefunden, Id nicht aufloesbar, wiederhergestellt, aufgegeben), damit ein
weiterer Fehlschlag per logcat zu klaeren ist statt per Vermutung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 23:26:17 +02:00
marcandClaude Opus 5 8da67e0975 chore(android): versionCode 7, versionName 1.4
Erste Android-Version mit Warteschlangen-Wiederherstellung und Streitpunkten.
Das zuvor hochgeladene 1.3 enthielt restoreQueue noch nicht — daher blieb die
Now-Playing-Leiste nach dem Oeffnen leer. Release-Beschreibung mit angehoben,
sonst meldet der In-App-Updater kein Update.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 22:44:25 +02:00
marcandClaude Opus 5 a2211a62d2 refactor(web+android): nur Warteschlange und Titel sichern, keine Sekunden
Das Mitschreiben der Abspielposition war zu viel des Guten: es bedeutete
staendiges Schreiben waehrend der Wiedergabe fuer einen Gewinn, den man kaum
bemerkt. Gesichert werden jetzt nur Warteschlange und aktueller Titel, der dann
von vorn beginnt.

Der eigentliche Gewinn bleibt und war der Ausloeser: unter Android war die
Now-Playing-Leiste nach dem Oeffnen leer, bis man einmal auf Play drueckte —
gesichert war nur der letzte Song, nicht die Warteschlange. Das Wiederherstellen
fuellt den Zustand jetzt ueber syncState(), also ohne Abspielen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 23:25:06 +02:00
marcandClaude Opus 5 65fa950043 feat(web+android): Wiedergabe fortsetzen und Streitpunkte im Duell (2.4)
Wiedergabe fortsetzen:
Im Web ueberlebten Warteschlange und letzter Titel bereits, die POSITION im
Titel aber nicht — beim Neuladen begann der Song von vorn. Android sicherte nur
den letzten Song ohne Warteschlange. Beide merken sich jetzt Warteschlange,
Listenposition und Sekunde.

Web schreibt gedrosselt (alle 5 s) waehrend der Wiedergabe und sicher beim
Pausieren, Wegwischen und Tabwechsel — pagehide/visibilitychange statt
beforeunload, das am Handy unzuverlaessig feuert. Android sichert bei jedem
Titelwechsel und beim Pausieren und stellt nach dem Verbinden wieder her:
vorbereitet, aber NICHT abgespielt, wer die App oeffnet will nicht ueberfallen
werden. Laeuft schon etwas (Android Auto), passiert nichts.

Zwei Feinheiten: lag die Position im letzten Rest des Titels, faengt er von vorn
an, statt zwei Sekunden Abspann zu spielen; und gespeichert werden nur Song-Ids,
weil Titel und Cover sich durch Metadaten-Bearbeitung aendern koennen —
verschwundene Titel werden beim Wiederherstellen uebersprungen.

Streitpunkte im Duell:
Gemeinsame Kuenstler nach groesstem Ungleichgewicht statt nach Summe, mit
Kraefteverhaeltnis-Balken. Mindestens vier gemeinsame Plays und 50 % Unwucht,
sonst gilt ein einzelner Play gegen null schon als Streit. Web und Android mit
identischen Schwellen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 23:20:40 +02:00
marcandClaude Opus 5 dfae34c706 feat(android): Navidrome-Adresse als Vorgabe, Login vorbelegt (1.3)
Ergaenzt Backend und Scrobble um die Navidrome-Adresse: alle drei kommen als
buildConfigField aus dem Build und greifen, wenn im Geraetespeicher nichts steht.
Der Anmeldebildschirm ist damit vorbelegt — beim ersten Start bleiben nur noch
Benutzer und Passwort. Die Felder in den Einstellungen bleiben sichtbar und
ueberschreibbar, ein Umzug ist also ohne neues APK zu korrigieren.

Zugangsdaten bleiben aussen vor. Das Publish-Skript reicht die Werte aus
scripts/.env.release durch, damit kuenftige Builds sie nicht verlieren.
Am erzeugten BuildConfig geprueft, alle vier Werte stehen darin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 20:37:13 +02:00
marcandClaude Opus 5 334aa51a09 feat(android): Backend- und Scrobble-Adresse als Vorgabe aus dem Build
Beide Adressen stehen ohnehin in der docker-compose.yml — sie zusaetzlich auf
jedem Geraet einzutippen ist Arbeit ohne Nutzen. Sie kommen jetzt als
buildConfigField aus -PnovasonicBackend= / -PnovasonicScrobble= und greifen,
wenn im Geraetespeicher nichts steht.

Die Felder in den Einstellungen bleiben sichtbar und ueberschreibbar: zieht ein
Dienst um oder aendert sich ein Port, laesst sich das am Geraet korrigieren,
statt ein APK bauen, hochladen und installieren zu muessen. Ohne gesetzte
Eigenschaften verhaelt sich die App exakt wie bisher.

Zugangsdaten bleiben bewusst aussen vor — die gehoeren pro Person, nicht in ein
APK, das im Release liegt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 20:35:37 +02:00
marcandClaude Opus 5 83570b22b5 refactor(android): Web-Adresse aus BuildConfig statt als vierte Einstellung
Der Host stand ohnehin schon beim Bauen fest, weil die App-Links ihn im Manifest
brauchen. Ihn zusaetzlich als Einstellung zu fuehren hiess: eine vierte Adresse
pflegen, die vom Manifest abweichen kann, ohne dass es jemand merkt — und der
Teilen-Eintrag versteckte sich, solange sie leer war, was beim Suchen schlicht
wie ein fehlendes Feature aussah.

Jetzt kommt beides aus -PnovasonicHost=…: das Manifest fuer eingehende Links,
BuildConfig.SHARE_HOST fuer ausgehende. Die Einstellung samt Speicherfeld ist
entfernt, Teilen ist immer sichtbar. versionCode 5 / 1.2, APK hochgeladen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 20:27:27 +02:00
marcandClaude Opus 5 4323d68edc chore(android): versionCode 4, versionName 1.1
Der In-App-Updater vergleicht versionCode aus der Gitea-Release-Beschreibung mit
BuildConfig.VERSION_CODE. Beide standen auf 3, das hochgeladene APK meldete sich
also korrekt als "kein Update" — trotz Teilen-per-Link, Songloeschen,
Playlist-aus-Import und Duell-Anzeigetafel. Version angehoben und die
Release-Beschreibung auf versionCode=4 gesetzt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 19:03:17 +02:00
marcandClaude Opus 5 91d0d5fd0b feat(web+android): Musik-Match wird zum Duell mit Anzeigetafel
Statt eines Prozentrings ("wie aehnlich seid ihr") zeigt die Ansicht jetzt, wer
worin gewinnt: fuenf Kategorien mit je einem Sieger und ein Gesamtstand.
Hoerzeit, Vielfalt, Tiefe (Plays je Kuenstler), Nachteule (Anteil vor 5 Uhr) und
Entdeckerdrang (Anteil exklusiver Kuenstler) — bewusst Kennzahlen zur Hoerweise
statt zur Menge, sonst gewinnt immer, wer laenger dabei ist.

Am laufenden Dienst mit zwei gegensaetzlichen Nutzern geprueft: 60 Plays a 300 s
ergeben 5,0 h und Tiefe 12,0, jeder dritte Play nachts ergibt 33 %, 60 von 62
exklusiven Kuenstlern ergeben 97 % — Endstand 3:2. Das JSON-Feldschema wurde
gegen das Kotlin-Modell abgeglichen.

Dazu drei Fixes im selben Bereich:
- Die Web-Ansicht lebte nur von Inline-Stilen mit festen Flex-Verhaeltnissen und
  zerfiel am Desktop. Jetzt ein Raster mit eigenen Klassen, eine Spalte am Handy,
  zwei ab 780 px; das Fenster musste dafuer von 720 auf 940 px, sonst haette die
  zweite Spalte nie ausgeloest.
- Gemeinsame Songs wurden ueber song_id abgeglichen und fielen auseinander,
  sobald eine Datei umbenannt wurde. Jetzt normalisierter Titel+Kuenstler.
- Media-Session: die Metadaten wurden feldweise mutiert (gegen Kachel-Flackern
  unter Windows), ueber MPRIS kam davon nichts an — Plasma-Widgets zeigten
  dauerhaft den zuerst gespielten Titel. Bei echtem Titelwechsel jetzt ein neues
  MediaMetadata, sonst weiter in-place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 13:55:16 +02:00
marcandClaude Opus 5 a89e2974bc feat(android): zweiter App-Link-Host fuer einen Domainwechsel
Der Host eingehender Links steht fest im Manifest, ein Domainwechsel brauchte
also zwingend ein neues APK und liess alte Links sofort ins Leere laufen. Mit
-PnovasonicHost2= laesst sich waehrend des Umzugs eine zweite Adresse angeben;
ohne Angabe wird der erste Host wiederholt. Bewusst kein Platzhalter-Host als
Vorgabe: auf Android 11 und aelter verifiziert das System nur, wenn ALLE Hosts
eine gueltige assetlinks.json liefern — ein toter Eintrag wuerde alle blockieren.
Am zusammengefuehrten Manifest geprueft, beide Hosts landen darin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 03:47:35 +02:00
marcandClaude Opus 5 430ecdc737 feat(web+android): Teilen per Link
Ein Song oder Album laesst sich per https://<host>/?song=<id> an jemanden auf
derselben Instanz schicken. Der Link traegt nur die Navidrome-Id, keine
Zugangsdaten — der Empfaenger sieht den Inhalt mit seinem eigenen Konto.

Web: Teilen ueber navigator.share() mit Rueckfall auf die Zwischenablage. Beim
Start wird ?song= / ?album= aufgeloest (nach der Anmeldung, vorher fehlen die
Zugangsdaten), das Album geoeffnet, der Song gestartet und der Parameter aus der
Adresszeile entfernt, damit ein Neuladen nicht erneut losspielt. Das Manifest
bekommt launch_handler=navigate-existing, damit der Link in einem bereits
offenen PWA-Fenster landet statt in einem neuen.

Android: Teilen ueber ACTION_SEND, Empfang ueber einen App-Link-Intent-Filter.
Der Host ist eine Build-Eigenschaft (-PnovasonicHost=…, verifiziert am
zusammengefuehrten Manifest), die Web-Adresse fuer ausgehende Links eine neue
Einstellung. Weil der NavController in der Compose-Hierarchie lebt und die
Activity nicht selbst navigieren kann, reicht DeepLinkState den Link durch;
onNewIntent ist mitbedacht, sonst passierte beim zweiten Link nichts.

assetlinks.json liegt als Vorlage in public/ und wird vom Frontend-Image
ausgeliefert (im Build verifiziert); ohne den Release-Fingerabdruck oeffnet der
Link die Web-App im Browser statt die App. Anleitung: docs/share-links.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 03:30:32 +02:00
marcandClaude Opus 5 f62b248bb6 fix(backend): Metadaten-Edit trifft nicht mehr die falsche Datei
Findet das Backend die von Navidrome gemeldete Datei nicht am angegebenen Pfad
(Subsonic liefert oft einen aus Tags gebauten virtuellen Pfad), sucht es ueber
Dateiname bzw. Kuenstler+Titel weiter. Liegt derselbe Song mehrfach auf der
Platte, konnte das die falsche Kopie treffen: die wurde getaggt, der bearbeitete
Song blieb unveraendert — die Aenderung "sprang zurueck". Das erklaert, warum es
bei Songs ohne Dublette immer ging und bei anderen nie.

Der ueber Aehnlichkeit gefundene Kandidat wird jetzt per ffprobe gegen die Tags
geprueft, die Navidrome fuer diesen Song meldet, und bei Widerspruch mit 409 und
erklaerender Meldung abgebrochen statt geraten. Zusaetzlich melden uebersprungene
Umbenennungen und Verschiebungen sich als Warnung, statt stumm zu bleiben; die
Antwort nennt den tatsaechlich bearbeiteten Pfad, der Editor zeigt beides an.

Ausserdem: -dontwarn org.slf4j.** — der Release-APK-Build brach seit laengerem an
R8 ab (Ktors Logging referenziert den slf4j-1.x-Binder, den es auf Android nicht
gibt). Vorbestehend, mit HEAD~1 verifiziert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 23:46:44 +02:00
marcandClaude Opus 5 aae358c4c7 feat(android): Songs loeschen und Playlist aus dem Import, plus Web-Scrollfix
Android holt zwei Sachen nach, die es im Web laengst gibt:

- Song von der Festplatte loeschen, im Kontextmenue unten abgesetzt und rot, mit
  derselben Sicherheitsabfrage wie im Web. Laeuft ueber /api/delete-song und
  erscheint nur mit konfiguriertem Backend — Navidrome allein loescht nichts.
- Playlist direkt aus dem Import anlegen: Haekchen plus Namensfeld ab zwei
  Titeln, danach Scan anstossen, Titel suchen, Playlist aus den gefundenen.
  Nicht gefundene werden gezaehlt statt die Erstellung scheitern zu lassen.

Dazu der Web-Fix zur Album-Position: die gespeicherte Scrollposition wurde in
genau einem requestAnimationFrame gesetzt. Solange Cover und Eintraege noch
nachluden, war scrollHeight kleiner als das Ziel und der Browser kappte den Wert
stillschweigend — man landete wieder oben. Jetzt wird er ueber bis zu ~0,5 s
erneut gesetzt, bis er sitzt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 23:40:57 +02:00
marcandClaude Opus 5 e91708bc97 chore(android): versionCode 3 veroeffentlicht (Scroll-Fixes im Release)
APK mit dem Debug-Key vom alten Rechner gebaut (Signatur 1039a00e…, passend zur
installierten App) und als Asset im Release apk-latest ersetzt, Body auf
versionCode=3. Dazu das ausfuehrbare Bit auf gradlew, das im Clone fehlte, und
.idea/ ignoriert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 21:25:25 +02:00
marcandClaude Opus 5 0029b89de4 fix(android): Alben-Tab behaelt die Scrollposition beim Zurueck
LaunchedEffect(state.albumSort) { scrollToItem(0) } feuerte nicht nur beim
Sortierwechsel, sondern auch beim ersten Compose nach der Rueckkehr aus einem
Album — die im ViewModel gehaltene Position wurde also sofort ueberschrieben.
Das Hochscrollen sitzt jetzt in setAlbumSort() und laeuft nur bei echtem
Wechsel; dazu zwei ungenutzte remember*State-Importe entfernt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 21:21:38 +02:00
HoneyandClaude Opus 4.8 93ee958434 feat: artist-page scroll/search memory (android) + move APK button to bottom (web)
- Android: the artist page's main list and the "Alle anzeigen" album overlay
  (grid scroll + search query) are hoisted into the Artist/Explore ViewModels,
  so drilling into an album/artist and back keeps position + query. Covers the
  library artist and Deezer (Explore) artist screens.
- Web: the "Android App" download section now sits at the very bottom of
  Settings, below the Navidrome-Server (account) section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 07:42:24 +02:00
HoneyandClaude Opus 4.8 a780f6622b chore(android): versionCode 2 (first apk-latest release upload)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 07:27:06 +02:00
HoneyandClaude Opus 4.8 2fd4d978d0 feat(android): in-app updater (backend/Gitea APK), auto-bump versionCode
The app checks {backend}/api/apk/info on launch and, if a newer versionCode
than BuildConfig.VERSION_CODE is offered, shows an update dialog that
downloads {backend}/api/apk into a single wiped-each-time cache file and
hands it to the system installer (FileProvider + REQUEST_INSTALL_PACKAGES).
No APK hoarding; in-place install preserves data (stable signing key needed).

- backend /api/apk/info now returns versionCode (parsed from the release body).
- publish-apk.mjs auto-increments versionCode, sets versionName, and writes
  versionCode/versionName into the Gitea release body.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 00:46:11 +02:00
HoneyandClaude Opus 4.8 845f125d76 feat: remember scroll + search position on Back (web + android)
Going into an album/artist and back now restores the scroll position and
keeps the typed query, instead of resetting to the top.

- Web: navigation.js snapshots each history entry's #main-content scroll and
  any open local (header) search, restoring them on Back/Forward; a genuine
  new navigation still starts at the top.
- Android: the Alben/Künstler lists (LibraryViewModel) and the global/local
  search-results lists (ExploreViewModel/SearchViewModel) hoist their
  LazyListState/LazyGridState, so scroll survives a drill-in/back for the
  tab's lifetime.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 00:06:46 +02:00
HoneyandClaude Opus 4.8 3ceba8a365 feat(android): partial-album detail gets Play/Shuffle + track 3-dot menu
You can still enjoy an incomplete album: the downloaded-tracks section now has
Abspielen + Zufall buttons (play/shuffle the downloaded songs) and each
downloaded row gets the 3-dot context menu, threaded from both artist screens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-01 23:58:24 +02:00
HoneyandClaude Opus 4.8 026b392657 fix: Japanese/CJK albums vanished from artist discography (web + android)
The album-name normaliser stripped everything but a-z0-9, so any JP/CJK
title collapsed to "". All of an artist's Japanese singles then normalised
identically, de-duplicated into one entry and "matched" each other — e.g.
inabakumori (稲葉曇) showed only 2 of 9 releases. Normalisers now keep
Unicode letters/numbers (\p{L}\p{N}) in:
- web _normAlbum (detail.js)
- android normAlbum (categorizeAlbums), normTrack (partial-album split),
  the enrichPartialCounts helpers, and the Deezer artist-name match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-22 18:49:59 +02:00
HoneyandClaude Opus 4.8 b7c96451d8 feat(android): download notifications everywhere + swipe tabs (NowPlaying, Library)
- Downloads started from global search / Bibliothek now poll via the
  app-lifetime NotificationStore, so they show success/failure like the
  importer and artist page did (was a silent toast only).
- Swipe between tabs, synced with the tab bar, matching Erkunden:
  * Now Playing: Player <-> Lyrics <-> Queue (cover's skip-track swipe
    still wins as a child gesture).
  * Bibliothek: Playlists <-> Alben <-> Künstler <-> Offline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 23:19:20 +02:00
HoneyandClaude Opus 4.8 d11de8a69e feat(android): importer 30s preview + partial-album detection
- Importer: each resolved track gets a play/stop button that plays its
  Deezer/Spotify preview via the app-level preview bar (pauses library
  playback, highlights the row). Parity with the web importer.
- Artist "Unvollständig" now works: Deezer's /artist/{id}/albums omits
  nb_tracks, so owned albums never registered as partial. The artist VM
  now resolves the real total for matched albums via a per-album lookup
  (like web), so the partial section + downloaded/missing split populate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 23:10:28 +02:00