feat(android): Album-Knopfleiste wie im Playlist-Detail; Changelog-Backlog aufgeraeumt

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-09-20 20:06:30 +02:00
1 parent 66fcdbba05
commit eb72e5c27d
3 files changed
+39 -39

No files matched your search

+20 -18
View File
@@ -79,8 +79,8 @@ Die Android-App zählt eigenständig weiter (mehrere APKs pro Release). Welche d
## [3.0] — 2026-09-19
**Android:** 1.13 (versionCode 16) → 1.14 (versionCode 17). 1.13 brachte Folgen, Zeitleiste und
den Gefolgt-Filter, 1.14 die begrenzte Reihe in den Favoriten.
**Android:** 1.13 (versionCode 16) → 1.15 (versionCode 18). 1.13 brachte Folgen, Zeitleiste und
den Gefolgt-Filter, 1.14 die begrenzte Reihe in den Favoriten, 1.15 die Knopfleiste im Album.
Erster Sprung auf eine neue Hauptversion seit 2.0: „Künstlern folgen" ist kein weiteres Fenster,
sondern ein neuer Umgang mit der Bibliothek — die App erzählt jetzt, was **draußen** neu ist, statt
@@ -124,6 +124,13 @@ nur zu zeigen, was drinsteht.
- Android zeigt zehn und dann den Knopf; die `LazyRow` baut ohnehin nur Sichtbares, dort ging es
allein um die Durchsehbarkeit. Weil das `LibraryViewModel` am Navigationseintrag hängt und einen
Zielwechsel nicht überlebt, reicht `LibraryIntent` die Absicht einmalig durch.
- **Die Knopfleiste im Album sieht aus wie die der Playlist (Android)** — im Album trugen *Play*
und *Shuffle* noch Beschriftungen und teilten sich die Breite, während Herz und Download auf feste
46 dp gesetzt waren. Vier Knöpfe in einer Reihe lassen dafür keinen Platz: die Schrift wurde
abgeschnitten, und die Reihe war ungleich geteilt. Jetzt vier gleich breite reine Symbolknöpfe wie
im Playlist-Detail — Play gefüllt als Hauptaktion, der Rest umrandet und erst accent gefärbt, wenn
er aktiv ist (gemerkt, vollständig geladen). Die Symbole tragen jetzt Beschriftungen für die
Vorlesefunktion, vorher stand dort bei Play und Shuffle `null`.
- **Zeitfenster-Chips der Zeitleiste beginnen mit 90 Tagen (Android)** — das ist die Vorgabe, also
steht der aktive Chip jetzt vorn statt in der Mitte. Web war schon so.
- **Filter „❤ Gefolgt" im Künstler-Reiter (Android)** — zwei Chips über der Liste, gleiche Stelle
@@ -1030,12 +1037,15 @@ Polish release: Android navigation and offline resilience, download accuracy for
- **Keep what you like:** per-track (or whole-mix) option to **promote into the permanent library** instead of letting it expire.
- Goal: completely random/unfamiliar music you don't already know — like Spotify's curated/discovery playlists, but self-hosted.
- Open questions: where downloaded-but-temporary tracks live vs. the main library (separate folder + retention job), how Navidrome should/shouldn't index them, and dedupe against tracks you already own.
- **Web + Android** — **Artist following + new-release & concert notifications** — follow artists and get notified when they drop something new or play nearby. Idea:
- A **follow list** of artists (stored per user, e.g. in app settings / a small backend table).
- A **"Following" view** listing all artists you follow (grid of artist photos), as an entry point to their pages — a dedicated overview so you can see and manage who you follow, plus a follow/unfollow toggle on the artist page.
- **New-release alerts:** periodic check for new albums/singles by followed artists (sources: iTunes lookup / MusicBrainz / Last.fm), surfaced in the existing in-app notification bell (and optionally push).
- **Concert alerts:** reuse the existing **Ticketmaster** events integration to notify when a followed artist has nearby/upcoming shows.
- Ties into the importer: a "download this new release" action straight from the notification.
- **Web + Android** — **Meldungen zu gefolgten Künstlern** — der Rest des Künstler-folgen-Vorhabens.
Folgen selbst, die Übersicht „wem folge ich" und die Zeitleiste neuer Veröffentlichungen sind
**seit 3.0 gebaut** (siehe dort); offen sind nur noch die beiden Meldewege:
- **Glocke für neue Veröffentlichungen:** die Zeitleiste muss man heute selbst aufschlagen. Die
Daten liegen schon vor (`POST /api/artist-releases`), es fehlt der Vergleich „was ist seit
deinem letzten Besuch dazugekommen" und der Eintrag in der vorhandenen Benachrichtigungsglocke
(wahlweise Push).
- **Konzert-Meldungen:** die vorhandene **Ticketmaster**-Anbindung wiederverwenden und melden,
wenn ein gefolgter Künstler in der Nähe spielt. Bewusst zurückgestellt — erst die Neuheiten.
- **Android** — Playlist drag-and-drop / sort order
- **Android** — Artist page iTunes fallback (for artists not in Navidrome)
- **Android** — Romaji / Japanese sort support
@@ -1044,16 +1054,8 @@ Polish release: Android navigation and offline resilience, download accuracy for
- **Android** - Andorid Auto Sync last played and queue too with Phone then, rn its not really syncing or safin what last played on Android Auto, also check if selecting a song to play on Android Auto works that song is getting played and in a playlist it will play the queue a this point then like it works already in Android and Web
### Ideen / Backlog
- **Künstler und Alben liken, daraus „folgen" bauen** — anders als Playlists sind Künstler und Alben
in der Subsonic-API wirklich markierbar. Navidrome wirft in `media_annotation.go` sogar alle drei
Parameter in eine Liste (`id`, `albumId`, `artistId`) und behandelt sie gleich, unser bestehender
Aufruf `api('star', '&id=' + id)` würde für Künstler also **ohne Änderung** funktionieren; den
Zustand liefert `getStarred2` mit Künstlern und Alben zurück. Es fehlt nur die Oberfläche: ein Herz
auf der Künstler- und Albumseite. Darauf ließe sich das länger angedachte *Künstler folgen* setzen
— neue Veröffentlichungen markierter Künstler über Deezer erkennen und melden, ohne dafür eine
eigene Liste pflegen zu müssen. Playlists gehen dafür ausdrücklich **nicht**: `getStarred`/
`getStarred2` geben nur Künstler, Alben und Songs zurück, ein in Navidromes Oberfläche geliktes
Playlist ist über die API unsichtbar.
- (Der Punkt „Künstler und Alben liken, daraus ‚folgen' bauen" ist mit 3.0 umgesetzt und dort
beschrieben.)
### Known Bugs
- **Android (lyrics)** — Synced-lyrics auto-scroll is **not smooth**: it follows playback and highlights the right line, but the list jumps to it rather than gliding. Web autoscroll is smooth. Not yet fixed.
+2 -2
View File
@@ -14,8 +14,8 @@ android {
applicationId = "com.novasonic.app"
minSdk = 26
targetSdk = 35
versionCode = 17
versionName = "1.14"
versionCode = 18
versionName = "1.15"
// Host der eigenen NovaSonic-Web-App — Ziel der geteilten Links. Einmal in
// gradle.properties setzen (novasonicHost=music.example.com); ohne Angabe bleibt der
// Platzhalter stehen und der App-Link greift schlicht nicht.
@@ -155,7 +155,12 @@ fun AlbumScreen(
}
}
// ── Play / Shuffle row ────────────────────────────────────────────
// ── Play / Shuffle / Merken / Download ────────────────────────────
// Gleiche Leiste wie beim Playlist-Detail: vier gleich breite, reine Symbolknoepfe.
// Play und Shuffle trugen hier noch Beschriftungen, waehrend Herz und Download fest
// 46 dp breit waren — die Reihe war dadurch ungleich geteilt und die Schrift wurde auf
// schmalen Geraeten abgeschnitten. Play bleibt gefuellt als Hauptaktion; die uebrigen
// sind umrandet und faerben sich erst accent, wenn sie aktiv sind.
Row(
Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
@@ -165,20 +170,18 @@ fun AlbumScreen(
modifier = Modifier.weight(1f).height(46.dp),
colors = ButtonDefaults.buttonColors(containerColor = NsAccent, contentColor = Color.White),
shape = RoundedCornerShape(14.dp),
contentPadding = PaddingValues(0.dp),
) {
Icon(Icons.Default.PlayArrow, null, modifier = Modifier.size(20.dp))
Spacer(Modifier.width(6.dp))
Text("Play", fontFamily = Outfit, fontWeight = FontWeight.Bold, fontSize = 15.sp)
Icon(Icons.Default.PlayArrow, "Abspielen", modifier = Modifier.size(22.dp))
}
OutlinedButton(
onClick = { onPlay(album.song.shuffled(), 0) },
modifier = Modifier.weight(1f).height(46.dp),
border = ButtonDefaults.outlinedButtonBorder(enabled = true).copy(brush = SolidColor(NsAccent.copy(alpha = 0.55f))),
shape = RoundedCornerShape(14.dp),
contentPadding = PaddingValues(0.dp),
) {
Icon(Icons.Default.Shuffle, null, modifier = Modifier.size(20.dp), tint = NsAccent)
Spacer(Modifier.width(6.dp))
Text("Shuffle", fontFamily = Outfit, fontWeight = FontWeight.Bold, fontSize = 15.sp, color = NsAccent)
Icon(Icons.Default.Shuffle, "Zufällig abspielen", modifier = Modifier.size(20.dp), tint = NsAccent)
}
// Album merken — derselbe Weg wie beim Song: Navidrome behandelt Song-, Album- und
// Kuenstler-IDs beim Markieren gleich.
@@ -186,9 +189,9 @@ fun AlbumScreen(
val gemerkt = overrides[album.id] ?: (album.starred != null)
OutlinedButton(
onClick = { vm.merkenUmschalten(album.id, album.starred != null) },
modifier = Modifier.height(46.dp).width(46.dp),
modifier = Modifier.weight(1f).height(46.dp),
border = ButtonDefaults.outlinedButtonBorder(enabled = true).copy(
brush = SolidColor(if (gemerkt) NsAccent else NsAccent.copy(alpha = 0.55f))
brush = SolidColor(if (gemerkt) NsAccent else NsOnSurface3)
),
shape = RoundedCornerShape(14.dp),
contentPadding = PaddingValues(0.dp),
@@ -196,7 +199,7 @@ fun AlbumScreen(
Icon(
if (gemerkt) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder,
if (gemerkt) "Gemerkt" else "Album merken",
Modifier.size(20.dp), tint = NsAccent,
Modifier.size(20.dp), tint = if (gemerkt) NsAccent else NsOnSurface2,
)
}
val isFullyDownloaded = downloadGroup != null &&
@@ -205,22 +208,17 @@ fun AlbumScreen(
OutlinedButton(
// Tappable unless fully done — resumes / re-downloads any missing songs.
onClick = { if (!isFullyDownloaded) vm.downloadAlbum() },
modifier = Modifier.height(46.dp).width(46.dp),
modifier = Modifier.weight(1f).height(46.dp),
border = ButtonDefaults.outlinedButtonBorder(enabled = true).copy(
brush = SolidColor(
when {
isFullyDownloaded -> MaterialTheme.colorScheme.primary.copy(alpha = 0.6f)
else -> NsAccent.copy(alpha = 0.55f)
}
)
brush = SolidColor(if (isFullyDownloaded) NsAccent else NsOnSurface3)
),
shape = RoundedCornerShape(14.dp),
contentPadding = PaddingValues(0.dp),
) {
when {
isFullyDownloaded -> Icon(Icons.Default.CheckCircle, "Downloaded", Modifier.size(20.dp), tint = MaterialTheme.colorScheme.primary)
isFullyDownloaded -> Icon(Icons.Default.CheckCircle, "Offline verfügbar", Modifier.size(20.dp), tint = NsAccent)
isDownloading -> CircularProgressIndicator(Modifier.size(18.dp), color = NsAccent, strokeWidth = 2.dp)
else -> Icon(Icons.Default.Download, "Herunterladen", Modifier.size(20.dp), tint = NsAccent)
else -> Icon(Icons.Default.Download, "Herunterladen", Modifier.size(20.dp), tint = NsOnSurface2)
}
}
}