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>
This commit is contained in:
1 parent
fca7cc79c1
commit
ec75fdef7b
7 files changed
+90
-10
No files matched your search
@@ -14,8 +14,8 @@ android {
|
||||
applicationId = "com.novasonic.app"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 10
|
||||
versionName = "1.7"
|
||||
versionCode = 11
|
||||
versionName = "1.8"
|
||||
// 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.
|
||||
|
||||
@@ -126,6 +126,7 @@ fun ExploreScreen(
|
||||
previewingId = state.previewingId,
|
||||
onPreview = vm::togglePreview,
|
||||
onDownload = vm::showDownloadSheet,
|
||||
onArtistClick = vm::openArtistOfAlbum,
|
||||
onBack = vm::dismissAlbum,
|
||||
)
|
||||
}
|
||||
@@ -407,6 +408,7 @@ private fun GlobalExploreContent(
|
||||
previewingId = state.previewingId,
|
||||
onPreview = vm::togglePreview,
|
||||
onDownload = onDownload,
|
||||
onArtistClick = vm::openArtistOfAlbum,
|
||||
onBack = vm::dismissAlbum,
|
||||
)
|
||||
return
|
||||
@@ -524,6 +526,7 @@ internal fun ItunesAlbumDetail(
|
||||
previewingId: Long?,
|
||||
onPreview: (ItunesItem) -> Unit,
|
||||
onDownload: (ItunesItem) -> Unit = {},
|
||||
onArtistClick: ((ItunesItem) -> Unit)? = null,
|
||||
onBack: () -> Unit,
|
||||
) {
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
@@ -535,9 +538,25 @@ internal fun ItunesAlbumDetail(
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Zurück",
|
||||
tint = MaterialTheme.colorScheme.onSurface)
|
||||
}
|
||||
Text(title, fontFamily = Outfit, fontWeight = FontWeight.Bold,
|
||||
fontSize = 16.sp, maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||
color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.weight(1f))
|
||||
// Anklickbar nur, wenn Deezer eine Künstler-id mitgeliefert hat — /artist/{id}/albums
|
||||
// lässt das artist-Objekt weg, dort bleibt der Name einfach Text.
|
||||
val artistName = albumItem?.artistName
|
||||
val artistTippbar = onArtistClick != null && albumItem?.artistId != null &&
|
||||
!artistName.isNullOrBlank()
|
||||
Column(
|
||||
Modifier.weight(1f).then(
|
||||
if (artistTippbar) Modifier.clickable { onArtistClick!!(albumItem!!) } else Modifier
|
||||
)
|
||||
) {
|
||||
Text(title, fontFamily = Outfit, fontWeight = FontWeight.Bold,
|
||||
fontSize = 16.sp, maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||
color = MaterialTheme.colorScheme.onSurface)
|
||||
if (!artistName.isNullOrBlank()) {
|
||||
Text(artistName, fontFamily = Outfit, fontSize = 12.sp,
|
||||
maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||
color = if (artistTippbar) NsAccent else NsOnSurface2)
|
||||
}
|
||||
}
|
||||
if (albumItem != null) {
|
||||
IconButton(onClick = { onDownload(albumItem) }) {
|
||||
Icon(Icons.Default.Download, "Album herunterladen",
|
||||
|
||||
@@ -180,6 +180,16 @@ class ExploreViewModel(app: Application) : AndroidViewModel(app) {
|
||||
_state.value = _state.value.copy(albumTracks = null, albumItem = null)
|
||||
}
|
||||
|
||||
/** Vom geöffneten Album zu dessen Künstler springen. Deezer legt das artist-Objekt samt id
|
||||
* jedem Albumtreffer bei, der Sprung kostet also keinen zusätzlichen Suchaufruf. Die
|
||||
* Albumansicht wird geschlossen, sonst läge sie weiter über der Künstlerseite. */
|
||||
fun openArtistOfAlbum(album: ItunesItem) {
|
||||
val id = album.artistId ?: return
|
||||
val name = album.artistName ?: return
|
||||
_state.value = _state.value.copy(albumTracks = null, albumItem = null, partialAlbum = null)
|
||||
openArtistDetail(ItunesItem(wrapperType = "artist", artistId = id, artistName = name))
|
||||
}
|
||||
|
||||
fun openArtistAllAlbums(title: String) { _state.value = _state.value.copy(artistAllAlbums = title) }
|
||||
fun closeArtistAllAlbums() { _state.value = _state.value.copy(artistAllAlbums = null) }
|
||||
|
||||
@@ -199,9 +209,14 @@ class ExploreViewModel(app: Application) : AndroidViewModel(app) {
|
||||
|
||||
val ndAlbums = ndAlbumsD.await()
|
||||
val iAlbums = iAlbumsD.await()
|
||||
// Aus einem Album konstruierte Künstler tragen weder Bild noch Fanzahl — einmal
|
||||
// nachschlagen, damit die Kopfzeile genauso aussieht wie aus der Künstlersuche.
|
||||
val voll = if (artistId != null && artist.artworkUrl100 == null && artist.fanCount == null)
|
||||
runCatching { ItunesClient.lookupArtist(artistId) }.getOrNull() ?: artist
|
||||
else artist
|
||||
_state.value = _state.value.copy(
|
||||
artistDetail = ItunesArtistState(
|
||||
artist = artist,
|
||||
artist = voll,
|
||||
isLoading = false,
|
||||
ndAlbums = ndAlbums,
|
||||
iAlbums = iAlbums,
|
||||
|
||||
Reference in new issue
Block a user