Redesign ArtistScreen (#123, #172)

This commit is contained in:
vfsfitvnm
2022-09-30 19:27:34 +02:00
parent cfd369266e
commit 4bc3671be1
25 changed files with 2107 additions and 381 deletions

View File

@@ -20,4 +20,4 @@ dependencies {
implementation(libs.ktor.serialization.json)
testImplementation(testLibs.junit)
}
}

View File

@@ -70,6 +70,7 @@ object YouTube {
data class BrowseBody(
val context: Context,
val browseId: String,
val params: String? = null,
)
@Serializable
@@ -560,7 +561,7 @@ object YouTube {
} else {
response.body<ContinuationResponse>()
.continuationContents
.musicShelfContinuation
?.musicShelfContinuation
}
}
SearchResult(
@@ -580,7 +581,7 @@ object YouTube {
continuation = musicShelfRenderer
?.continuations
?.firstOrNull()
?.nextRadioContinuationData
?.nextContinuationData
?.continuation
)
}.recoverIfCancelled()
@@ -785,7 +786,7 @@ object YouTube {
?.playlistPanelRenderer
?.continuations
?.getOrNull(0)
?.nextRadioContinuationData
?.nextContinuationData
?.continuation,
items = (tabs
.getOrNull(0)
@@ -868,9 +869,9 @@ object YouTube {
} else {
browse(lyricsBrowseId)?.map { body ->
body.contents
.sectionListRenderer
?.sectionListRenderer
?.contents
?.first()
?.firstOrNull()
?.musicDescriptionShelfRenderer
?.description
?.text
@@ -895,6 +896,105 @@ object YouTube {
}.recoverIfCancelled()
}
data class ItemsResult<T : Item>(
val items: List<T>?,
val continuation: String?
)
suspend fun <T : Item> items(
browseId: String,
continuation: String?,
block: (MusicResponsiveListItemRenderer) -> T?
): Result<ItemsResult<T>?>? {
return runCatching {
val response = client.post("/youtubei/v1/browse") {
contentType(ContentType.Application.Json)
setBody(
BrowseBody(
browseId = browseId,
context = Context.DefaultWeb
)
)
parameter("key", Key)
parameter("prettyPrint", false)
parameter("continuation", continuation)
}
if (continuation == null) {
response
.body<BrowseResponse>()
.contents
?.singleColumnBrowseResultsRenderer
?.tabs
?.firstOrNull()
?.tabRenderer
?.content
?.sectionListRenderer
?.contents
?.firstOrNull()
?.musicShelfRenderer
} else {
response
.body<ContinuationResponse>()
.continuationContents
?.musicShelfContinuation
}?.let { musicShelfRenderer ->
ItemsResult(
items = musicShelfRenderer
.contents
.mapNotNull(MusicShelfRenderer.Content::musicResponsiveListItemRenderer)
.mapNotNull(block),
continuation = musicShelfRenderer
.continuations
?.firstOrNull()
?.nextContinuationData
?.continuation
)
}
}.recoverIfCancelled()
}
suspend fun <T : Item> items2(
browseId: String,
params: String?,
block: (MusicTwoRowItemRenderer) -> T?
): Result<ItemsResult<T>?>? {
return runCatching {
client.post("/youtubei/v1/browse") {
contentType(ContentType.Application.Json)
setBody(
BrowseBody(
browseId = browseId,
context = Context.DefaultWeb,
params = params
)
)
parameter("key", Key)
parameter("prettyPrint", false)
}
.body<BrowseResponse>()
.contents
?.singleColumnBrowseResultsRenderer
?.tabs
?.firstOrNull()
?.tabRenderer
?.content
?.sectionListRenderer
?.contents
?.firstOrNull()
?.gridRenderer
?.let { gridRenderer ->
ItemsResult(
items = gridRenderer
.items
?.mapNotNull(SectionListRenderer.Content.GridRenderer.Item::musicTwoRowItemRenderer)
?.mapNotNull(block),
continuation = null
)
}
}.recoverIfCancelled()
}
data class PlaylistOrAlbum(
val title: String?,
val authors: List<Info<NavigationEndpoint.Endpoint.Browse>>?,
@@ -918,17 +1018,17 @@ object YouTube {
songs = songs?.plus(
continuationResponse
.continuationContents
.musicShelfContinuation
?.musicShelfContinuation
?.contents
?.map(MusicShelfRenderer.Content::musicResponsiveListItemRenderer)
?.mapNotNull(Item.Song.Companion::from) ?: emptyList()
),
continuation = continuationResponse
.continuationContents
.musicShelfContinuation
?.musicShelfContinuation
?.continuations
?.firstOrNull()
?.nextRadioContinuationData
?.nextContinuationData
?.continuation
).next()
}
@@ -1003,7 +1103,7 @@ object YouTube {
?.text,
songs = body
.contents
.singleColumnBrowseResultsRenderer
?.singleColumnBrowseResultsRenderer
?.tabs
?.firstOrNull()
?.tabRenderer
@@ -1021,7 +1121,7 @@ object YouTube {
?.urlCanonical,
continuation = body
.contents
.singleColumnBrowseResultsRenderer
?.singleColumnBrowseResultsRenderer
?.tabs
?.firstOrNull()
?.tabRenderer
@@ -1032,7 +1132,7 @@ object YouTube {
?.musicShelfRenderer
?.continuations
?.firstOrNull()
?.nextRadioContinuationData
?.nextContinuationData
?.continuation
)
}
@@ -1043,24 +1143,61 @@ object YouTube {
val description: String?,
val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail?,
val shuffleEndpoint: NavigationEndpoint.Endpoint.Watch?,
val radioEndpoint: NavigationEndpoint.Endpoint.Watch?
val radioEndpoint: NavigationEndpoint.Endpoint.Watch?,
val songs: List<Item.Song>?,
val songsEndpoint: NavigationEndpoint.Endpoint.Browse?,
val albums: List<Item.Album>?,
val albumsEndpoint: NavigationEndpoint.Endpoint.Browse?,
val singles: List<Item.Album>?,
val singlesEndpoint: NavigationEndpoint.Endpoint.Browse?,
)
suspend fun artist(browseId: String): Result<Artist>? {
return browse(browseId)?.map { body ->
return browse(browseId)?.map { response ->
fun findSectionByTitle(text: String): SectionListRenderer.Content? {
return response
.contents
?.singleColumnBrowseResultsRenderer
?.tabs
?.get(0)
?.tabRenderer
?.content
?.sectionListRenderer
?.contents
?.find { content ->
val title = content
.musicCarouselShelfRenderer
?.header
?.musicCarouselShelfBasicHeaderRenderer
?.title
?: content
.musicShelfRenderer
?.title
title
?.runs
?.firstOrNull()
?.text == text
}
}
val songsSection = findSectionByTitle("Songs")?.musicShelfRenderer
val albumsSection = findSectionByTitle("Albums")?.musicCarouselShelfRenderer
val singlesSection = findSectionByTitle("Singles")?.musicCarouselShelfRenderer
Artist(
name = body
name = response
.header
?.musicImmersiveHeaderRenderer
?.title
?.text,
description = body
description = response
.header
?.musicImmersiveHeaderRenderer
?.description
?.text
?.substringBeforeLast("\n\nFrom Wikipedia"),
thumbnail = body
thumbnail = response
.header
?.musicImmersiveHeaderRenderer
?.thumbnail
@@ -1068,20 +1205,49 @@ object YouTube {
?.thumbnail
?.thumbnails
?.getOrNull(0),
shuffleEndpoint = body
shuffleEndpoint = response
.header
?.musicImmersiveHeaderRenderer
?.playButton
?.buttonRenderer
?.navigationEndpoint
?.watchEndpoint,
radioEndpoint = body
radioEndpoint = response
.header
?.musicImmersiveHeaderRenderer
?.startRadioButton
?.buttonRenderer
?.navigationEndpoint
?.watchEndpoint
?.watchEndpoint,
songs = songsSection
?.contents
?.mapNotNull(MusicShelfRenderer.Content::musicResponsiveListItemRenderer)
?.mapNotNull(Item.Song::from),
songsEndpoint = songsSection
?.bottomEndpoint
?.browseEndpoint,
albums = albumsSection
?.contents
?.mapNotNull(MusicCarouselShelfRenderer.Content::musicTwoRowItemRenderer)
?.mapNotNull(Item.Album::from),
albumsEndpoint = albumsSection
?.header
?.musicCarouselShelfBasicHeaderRenderer
?.moreContentButton
?.buttonRenderer
?.navigationEndpoint
?.browseEndpoint,
singles = singlesSection
?.contents
?.mapNotNull(MusicCarouselShelfRenderer.Content::musicTwoRowItemRenderer)
?.mapNotNull(Item.Album::from),
singlesEndpoint = singlesSection
?.header
?.musicCarouselShelfBasicHeaderRenderer
?.moreContentButton
?.buttonRenderer
?.navigationEndpoint
?.browseEndpoint,
)
}
}
@@ -1132,7 +1298,7 @@ object YouTube {
browse(browseId)?.getOrThrow()?.let { browseResponse ->
browseResponse
.contents
.sectionListRenderer
?.sectionListRenderer
?.contents
?.mapNotNull(SectionListRenderer.Content::musicCarouselShelfRenderer)
?.map(MusicCarouselShelfRenderer::contents)

View File

@@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable
@OptIn(ExperimentalSerializationApi::class)
@Serializable
data class BrowseResponse(
val contents: Contents,
val contents: Contents?,
val header: Header?,
val microformat: Microformat?
) {

View File

@@ -8,7 +8,7 @@ import kotlinx.serialization.json.JsonNames
@Serializable
data class Continuation(
@JsonNames("nextContinuationData", "nextRadioContinuationData")
val nextRadioContinuationData: Data
val nextContinuationData: Data
) {
@Serializable
data class Data(

View File

@@ -7,7 +7,7 @@ import kotlinx.serialization.json.JsonNames
@OptIn(ExperimentalSerializationApi::class)
@Serializable
data class ContinuationResponse(
val continuationContents: ContinuationContents,
val continuationContents: ContinuationContents?,
) {
@Serializable
data class ContinuationContents(

View File

@@ -1,9 +1,7 @@
package it.vfsfitvnm.youtubemusic.models
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
@OptIn(ExperimentalSerializationApi::class)
@Serializable
data class MusicShelfRenderer(
val bottomEndpoint: NavigationEndpoint?,

View File

@@ -44,11 +44,12 @@ data class SectionListRenderer(
) {
@Serializable
data class GridRenderer(
val items: List<Item>,
val items: List<Item>?,
) {
@Serializable
data class Item(
val musicNavigationButtonRenderer: MusicNavigationButtonRenderer
val musicNavigationButtonRenderer: MusicNavigationButtonRenderer?,
val musicTwoRowItemRenderer: MusicTwoRowItemRenderer?
)
}