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>
This commit is contained in:
1 parent
cb43c3375e
commit
86810b6a51
8 files changed
+112
-7
No files matched your search
@@ -14,8 +14,8 @@ android {
|
||||
applicationId = "com.novasonic.app"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 16
|
||||
versionName = "1.13"
|
||||
versionCode = 17
|
||||
versionName = "1.14"
|
||||
// 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.
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.novasonic.app.data
|
||||
|
||||
/**
|
||||
* Einmalige Absicht beim Öffnen der Bibliothek. Das LibraryViewModel hängt am Navigationseintrag,
|
||||
* überlebt also keinen Wechsel des Ziels — ein Zustand, den ein anderer Screen setzen will, muss
|
||||
* deshalb hier durch. Wird beim Verarbeiten sofort zurückgesetzt, damit er nicht nachwirkt.
|
||||
*/
|
||||
object LibraryIntent {
|
||||
/** Direkt in den Künstler-Reiter mit aktivem "Gefolgt"-Filter springen. */
|
||||
var oeffneGefolgte: Boolean = false
|
||||
}
|
||||
@@ -418,6 +418,10 @@ fun AppNavigation() {
|
||||
onArtistClick = { id -> navController.navigate("artist/$id") },
|
||||
onAlbumClick = { id -> navController.navigate("album/$id") },
|
||||
onOpenNews = { navController.navigate("artist-news") },
|
||||
onOpenFollowed = {
|
||||
com.novasonic.app.data.LibraryIntent.oeffneGefolgte = true
|
||||
navController.navigate(TopLevel.LIBRARY.route)
|
||||
},
|
||||
)
|
||||
}
|
||||
composable("artist-news") {
|
||||
|
||||
@@ -63,7 +63,8 @@ fun ArtistNewsScreen(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.padding(start = 16.dp, top = 4.dp, bottom = 10.dp),
|
||||
) {
|
||||
listOf(30 to "30 Tage", 90 to "90 Tage", 365 to "1 Jahr").forEach { (tage, label) ->
|
||||
// 90 Tage zuerst, weil es die Vorgabe ist — der aktive Chip steht dann vorn.
|
||||
listOf(90 to "90 Tage", 30 to "30 Tage", 365 to "1 Jahr").forEach { (tage, label) ->
|
||||
FilterChip(
|
||||
selected = state.tage == tage,
|
||||
onClick = { vm.laden(tage) },
|
||||
|
||||
@@ -64,6 +64,15 @@ fun LibraryScreen(
|
||||
val vm: LibraryViewModel = viewModel()
|
||||
val state by vm.state.collectAsStateWithLifecycle()
|
||||
val offlineVm: OfflineViewModel = viewModel()
|
||||
|
||||
// Absicht aus den Favoriten ("Alle anzeigen" bei den gefolgten Künstlern) einmalig einlösen.
|
||||
LaunchedEffect(Unit) {
|
||||
if (com.novasonic.app.data.LibraryIntent.oeffneGefolgte) {
|
||||
com.novasonic.app.data.LibraryIntent.oeffneGefolgte = false
|
||||
vm.selectTab(LibraryTab.ARTISTS)
|
||||
vm.nurGefolgteUmschalten(true)
|
||||
}
|
||||
}
|
||||
val offlineGroups by offlineVm.groups.collectAsStateWithLifecycle()
|
||||
|
||||
// Fully-downloaded source IDs per type — drives the "downloaded" badge on cards.
|
||||
|
||||
@@ -31,6 +31,9 @@ import com.novasonic.app.ui.theme.*
|
||||
|
||||
enum class StarredSort { NEWEST, OLDEST, AZ, BY_ARTIST }
|
||||
|
||||
/** So viele gefolgte Kuenstler stehen in der Reihe, der Rest hinter "Alle anzeigen". */
|
||||
private const val GEZEIGT = 10
|
||||
|
||||
@Composable
|
||||
fun StarredScreen(
|
||||
coverUrl: (String?) -> String?,
|
||||
@@ -41,6 +44,7 @@ fun StarredScreen(
|
||||
onArtistClick: (String) -> Unit = {},
|
||||
onAlbumClick: (String) -> Unit = {},
|
||||
onOpenNews: () -> Unit = {},
|
||||
onOpenFollowed: () -> Unit = {},
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val repo = context.app.repository
|
||||
@@ -146,9 +150,17 @@ fun StarredScreen(
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
items(gefolgteKuenstler, key = { it.id }) { a ->
|
||||
// Die LazyRow baut zwar nur Sichtbares, aber eine endlose Reihe ist
|
||||
// zum Durchsehen untauglich — der Rest gehoert in die gefilterte
|
||||
// Kuenstlerliste. GEZEIGT deckt zwei Bildschirmbreiten ab.
|
||||
items(gefolgteKuenstler.take(GEZEIGT), key = { it.id }) { a ->
|
||||
GefolgterKuenstler(a.name, coverUrl(a.coverArt), a.albumCount) { onArtistClick(a.id) }
|
||||
}
|
||||
if (gefolgteKuenstler.size > GEZEIGT) {
|
||||
item(key = "alle_kuenstler") {
|
||||
AlleAnzeigen(gefolgteKuenstler.size - GEZEIGT, onOpenFollowed)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
}
|
||||
@@ -213,3 +225,22 @@ private fun GefolgterKuenstler(name: String, bild: String?, albumCount: Int, onC
|
||||
color = NsOnSurface2, maxLines = 1)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AlleAnzeigen(weitere: Int, onClick: () -> Unit) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.width(110.dp).clickable(onClick = onClick),
|
||||
) {
|
||||
Box(
|
||||
Modifier.size(110.dp).clip(CircleShape).background(NsSurface2),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text("→", fontSize = 26.sp, color = NsAccent)
|
||||
}
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Text("Alle anzeigen", fontFamily = Outfit, fontWeight = FontWeight.SemiBold, fontSize = 13.sp,
|
||||
maxLines = 1, color = MaterialTheme.colorScheme.onSurface)
|
||||
Text("+$weitere weitere", fontFamily = Outfit, fontSize = 11.sp, color = NsOnSurface2, maxLines = 1)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user