diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistItemsScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistItemsScreen.kt index 4f7e01105..57b5ad4cf 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistItemsScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistItemsScreen.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Checkbox @@ -56,6 +57,7 @@ import com.zionhuang.music.ui.component.IconButton import com.zionhuang.music.ui.component.LocalMenuState import com.zionhuang.music.ui.component.YouTubeGridItem import com.zionhuang.music.ui.component.YouTubeListItem +import com.zionhuang.music.ui.component.shimmer.GridItemPlaceHolder import com.zionhuang.music.ui.component.shimmer.ListItemPlaceHolder import com.zionhuang.music.ui.component.shimmer.ShimmerHost import com.zionhuang.music.ui.menu.YouTubeAlbumMenu @@ -80,6 +82,7 @@ fun ArtistItemsScreen( val mediaMetadata by playerConnection.mediaMetadata.collectAsState() val lazyListState = rememberLazyListState() + val lazyGridState = rememberLazyGridState() val coroutineScope = rememberCoroutineScope() val title by viewModel.title.collectAsState() @@ -117,6 +120,15 @@ fun ArtistItemsScreen( } } + LaunchedEffect(lazyGridState) { + snapshotFlow { + lazyGridState.layoutInfo.visibleItemsInfo.any { it.key == "loading" } + }.collect { shouldLoadMore -> + if (!shouldLoadMore) return@collect + viewModel.loadMore() + } + } + if (itemsPage == null) { ShimmerHost( modifier = Modifier.windowInsetsPadding(LocalPlayerAwareWindowInsets.current) @@ -208,6 +220,7 @@ fun ArtistItemsScreen( } } else { LazyVerticalGrid( + state = lazyGridState, columns = GridCells.Adaptive(minSize = GridThumbnailHeight + 24.dp), contentPadding = LocalPlayerAwareWindowInsets.current.asPaddingValues() ) { @@ -268,6 +281,14 @@ fun ArtistItemsScreen( .animateItem() ) } + + if (itemsPage?.continuation != null) { + item(key = "loading") { + ShimmerHost(Modifier.animateItem()) { + GridItemPlaceHolder(fillMaxWidth = true) + } + } + } } } diff --git a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt index 4b386cfe0..cd2e8aef3 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt @@ -179,7 +179,7 @@ object YouTube { } suspend fun albumSongs(playlistId: String): Result> = runCatching { - var response = innerTube.browse(WEB_REMIX, "VL$playlistId").body() + var response = innerTube.browse(WEB_REMIX, "VL$playlistId", setLogin = true).body() val songs = response.contents?.twoColumnBrowseResultsRenderer ?.secondaryContents?.sectionListRenderer ?.contents?.firstOrNull() @@ -205,7 +205,7 @@ object YouTube { } suspend fun artist(browseId: String): Result = runCatching { - val response = innerTube.browse(WEB_REMIX, browseId).body() + val response = innerTube.browse(WEB_REMIX, browseId, setLogin = true).body() ArtistPage( artist = ArtistItem( id = browseId, @@ -224,7 +224,7 @@ object YouTube { } suspend fun artistItems(endpoint: BrowseEndpoint): Result = runCatching { - val response = innerTube.browse(WEB_REMIX, endpoint.browseId, endpoint.params).body() + val response = innerTube.browse(WEB_REMIX, endpoint.browseId, endpoint.params, setLogin = true).body() val gridRenderer = response.contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull() ?.tabRenderer?.content?.sectionListRenderer?.contents?.firstOrNull() ?.gridRenderer @@ -236,7 +236,7 @@ object YouTube { ArtistItemsPage.fromMusicTwoRowItemRenderer(renderer) } }, - continuation = null + continuation = gridRenderer.continuations?.getContinuation() ) } else { ArtistItemsPage( @@ -254,13 +254,25 @@ object YouTube { } suspend fun artistItemsContinuation(continuation: String): Result = runCatching { - val response = innerTube.browse(WEB_REMIX, continuation = continuation).body() - ArtistItemsContinuationPage( - items = response.continuationContents?.musicPlaylistShelfContinuation?.contents?.mapNotNull { - ArtistItemsContinuationPage.fromMusicResponsiveListItemRenderer(it.musicResponsiveListItemRenderer) - }!!, - continuation = response.continuationContents.musicPlaylistShelfContinuation.continuations?.getContinuation() - ) + val response = innerTube.browse(WEB_REMIX, continuation = continuation, setLogin = true).body() + val gridContinuation = response.continuationContents?.gridContinuation + if (gridContinuation != null) { + ArtistItemsContinuationPage( + items = gridContinuation.items.mapNotNull { + it.musicTwoRowItemRenderer?.let { renderer -> + ArtistItemsPage.fromMusicTwoRowItemRenderer(renderer) + } + }, + continuation = gridContinuation.continuations?.getContinuation() + ) + } else { + ArtistItemsContinuationPage( + items = response.continuationContents?.musicPlaylistShelfContinuation?.contents?.mapNotNull { + ArtistItemsContinuationPage.fromMusicResponsiveListItemRenderer(it.musicResponsiveListItemRenderer) + }!!, + continuation = response.continuationContents.musicPlaylistShelfContinuation.continuations?.getContinuation() + ) + } } suspend fun playlist(playlistId: String): Result = runCatching { diff --git a/innertube/src/main/java/com/zionhuang/innertube/models/GridRenderer.kt b/innertube/src/main/java/com/zionhuang/innertube/models/GridRenderer.kt index 795e8b277..384928c57 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/models/GridRenderer.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/models/GridRenderer.kt @@ -6,6 +6,7 @@ import kotlinx.serialization.Serializable data class GridRenderer( val header: Header?, val items: List, + val continuations: List?, ) { @Serializable data class Header( diff --git a/innertube/src/main/java/com/zionhuang/innertube/models/NavigationEndpoint.kt b/innertube/src/main/java/com/zionhuang/innertube/models/NavigationEndpoint.kt index 83e4f3351..9229f3e5e 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/models/NavigationEndpoint.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/models/NavigationEndpoint.kt @@ -18,4 +18,8 @@ data class NavigationEndpoint( ?: searchEndpoint ?: queueAddEndpoint ?: shareEntityEndpoint + + val anyWatchEndpoint: WatchEndpoint? + get() = watchEndpoint + ?: watchPlaylistEndpoint } diff --git a/innertube/src/main/java/com/zionhuang/innertube/models/response/BrowseResponse.kt b/innertube/src/main/java/com/zionhuang/innertube/models/response/BrowseResponse.kt index 2273fee40..16fd87cec 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/models/response/BrowseResponse.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/models/response/BrowseResponse.kt @@ -2,6 +2,7 @@ package com.zionhuang.innertube.models.response import com.zionhuang.innertube.models.Button import com.zionhuang.innertube.models.Continuation +import com.zionhuang.innertube.models.GridRenderer import com.zionhuang.innertube.models.Menu import com.zionhuang.innertube.models.MusicShelfRenderer import com.zionhuang.innertube.models.ResponseContext @@ -49,6 +50,7 @@ data class BrowseResponse( data class ContinuationContents( val sectionListContinuation: SectionListContinuation?, val musicPlaylistShelfContinuation: MusicPlaylistShelfContinuation?, + val gridContinuation: GridContinuation?, ) { @Serializable data class SectionListContinuation( @@ -61,6 +63,12 @@ data class BrowseResponse( val contents: List, val continuations: List?, ) + + @Serializable + data class GridContinuation( + val items: List, + val continuations: List?, + ) } @Serializable diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistItemsPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistItemsPage.kt index 61cc4f4ed..109ec5d04 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistItemsPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistItemsPage.kt @@ -54,7 +54,7 @@ data class ArtistItemsPage( browseId = renderer.navigationEndpoint.browseEndpoint?.browseId ?: return null, playlistId = renderer.thumbnailOverlay?.musicItemThumbnailOverlayRenderer ?.content?.musicPlayButtonRenderer?.playNavigationEndpoint - ?.watchPlaylistEndpoint?.playlistId ?: return null, + ?.anyWatchEndpoint?.playlistId ?: return null, title = renderer.title.runs?.firstOrNull()?.text ?: return null, artists = null, year = renderer.subtitle?.runs?.lastOrNull()?.text?.toIntOrNull(), diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistPage.kt index 8e5fffbaa..0b65e2686 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistPage.kt @@ -123,7 +123,7 @@ data class ArtistPage( browseId = renderer.navigationEndpoint.browseEndpoint?.browseId ?: return null, playlistId = renderer.thumbnailOverlay?.musicItemThumbnailOverlayRenderer?.content ?.musicPlayButtonRenderer?.playNavigationEndpoint - ?.watchPlaylistEndpoint?.playlistId ?: return null, + ?.anyWatchEndpoint?.playlistId ?: return null, title = renderer.title.runs?.firstOrNull()?.text ?: return null, artists = null, year = renderer.subtitle?.runs?.lastOrNull()?.text?.toIntOrNull(),