Rename youtube-music module to innertube and rewrite it

This commit is contained in:
vfsfitvnm
2022-10-02 15:25:07 +02:00
parent 4bc3671be1
commit 917e194d63
126 changed files with 2210 additions and 2145 deletions

View File

@@ -81,7 +81,10 @@ import it.vfsfitvnm.vimusic.utils.intent
import it.vfsfitvnm.vimusic.utils.listener
import it.vfsfitvnm.vimusic.utils.preferences
import it.vfsfitvnm.vimusic.utils.thumbnailRoundnessKey
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
import it.vfsfitvnm.youtubemusic.requests.playlistPage
import it.vfsfitvnm.youtubemusic.requests.song
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
@@ -376,8 +379,8 @@ class MainActivity : ComponentActivity() {
val browseId = "VL$playlistId"
if (playlistId.startsWith("OLAK5uy_")) {
YouTube.playlist(browseId)?.getOrNull()?.let { playlist ->
playlist.songs?.firstOrNull()?.album?.endpoint?.browseId?.let { browseId ->
Innertube.playlistPage(BrowseBody(browseId = browseId))?.getOrNull()?.let { playlist ->
playlist.songsPage?.items?.firstOrNull()?.album?.endpoint?.browseId?.let { browseId ->
albumRoute.ensureGlobal(browseId)
}
}
@@ -385,7 +388,7 @@ class MainActivity : ComponentActivity() {
playlistRoute.ensureGlobal(browseId)
}
} ?: (uri.getQueryParameter("v") ?: uri.takeIf { uri.host == "youtu.be" }?.path?.drop(1))?.let { videoId ->
YouTube.song(videoId)?.getOrNull()?.let { song ->
Innertube.song(videoId)?.getOrNull()?.let { song ->
withContext(Dispatchers.Main) {
binder?.player?.forcePlay(song.asMediaItem)
}

View File

@@ -1,3 +0,0 @@
package it.vfsfitvnm.vimusic.savers
val AlbumListSaver = ListSaver.of(AlbumSaver)

View File

@@ -1,3 +0,0 @@
package it.vfsfitvnm.vimusic.savers
val AlbumResultSaver = resultSaver(AlbumSaver)

View File

@@ -27,3 +27,7 @@ object AlbumSaver : Saver<Album, List<Any?>> {
bookmarkedAt = value[7] as Long?,
)
}
val AlbumResultSaver = resultSaver(AlbumSaver)
val AlbumListSaver = listSaver(AlbumSaver)

View File

@@ -1,3 +0,0 @@
package it.vfsfitvnm.vimusic.savers
val ArtistListSaver = ListSaver.of(ArtistSaver)

View File

@@ -23,3 +23,5 @@ object ArtistSaver : Saver<Artist, List<Any?>> {
bookmarkedAt = value[5] as Long?,
)
}
val ArtistListSaver = listSaver(ArtistSaver)

View File

@@ -1,3 +0,0 @@
package it.vfsfitvnm.vimusic.savers
val DetailedSongListSaver = ListSaver.of(DetailedSongSaver)

View File

@@ -29,3 +29,5 @@ object DetailedSongSaver : Saver<DetailedSong, List<Any?>> {
artists = (value[7] as List<List<String>>?)?.let(InfoListSaver::restore)
)
}
val DetailedSongListSaver = listSaver(DetailedSongSaver)

View File

@@ -1,3 +0,0 @@
package it.vfsfitvnm.vimusic.savers
val InfoListSaver = ListSaver.of(InfoSaver)

View File

@@ -11,3 +11,5 @@ object InfoSaver : Saver<Info, List<String>> {
return if (value.size == 2) Info(id = value[0], name = value[1]) else null
}
}
val InfoListSaver = listSaver(InfoSaver)

View File

@@ -0,0 +1,24 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.Innertube
object InnertubeAlbumItemSaver : Saver<Innertube.AlbumItem, List<Any?>> {
override fun SaverScope.save(value: Innertube.AlbumItem): List<Any?> = listOf(
value.info?.let { with(InnertubeBrowseInfoSaver) { save(it) } },
value.authors?.let { with(InnertubeBrowseInfoListSaver) { save(it) } },
value.year,
value.thumbnail?.let { with(InnertubeThumbnailSaver) { save(it) } }
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = Innertube.AlbumItem(
info = (value[0] as List<Any?>?)?.let(InnertubeBrowseInfoSaver::restore),
authors = (value[1] as List<List<Any?>>?)?.let(InnertubeBrowseInfoListSaver::restore),
year = value[2] as String?,
thumbnail = (value[3] as List<Any?>?)?.let(InnertubeThumbnailSaver::restore)
)
}
val InnertubeAlbumItemListSaver = listSaver(InnertubeAlbumItemSaver)

View File

@@ -0,0 +1,21 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.Innertube
object InnertubeArtistItemSaver : Saver<Innertube.ArtistItem, List<Any?>> {
override fun SaverScope.save(value: Innertube.ArtistItem): List<Any?> = listOf(
value.info?.let { with(InnertubeBrowseInfoSaver) { save(it) } },
value.subscribersCountText,
value.thumbnail?.let { with(InnertubeThumbnailSaver) { save(it) } }
)
override fun restore(value: List<Any?>) = Innertube.ArtistItem(
info = (value[0] as List<Any?>?)?.let(InnertubeBrowseInfoSaver::restore),
subscribersCountText = value[1] as String?,
thumbnail = (value[2] as List<Any?>?)?.let(InnertubeThumbnailSaver::restore)
)
}
val InnertubeArtistItemListSaver = listSaver(InnertubeArtistItemSaver)

View File

@@ -0,0 +1,36 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.Innertube
object InnertubeArtistPageSaver : Saver<Innertube.ArtistPage, List<Any?>> {
override fun SaverScope.save(value: Innertube.ArtistPage) = listOf(
value.name,
value.description,
value.thumbnail?.let { with(InnertubeThumbnailSaver) { save(it) } },
value.shuffleEndpoint?.let { with(InnertubeWatchEndpointSaver) { save(it) } },
value.radioEndpoint?.let { with(InnertubeWatchEndpointSaver) { save(it) } },
value.songs?.let { with(InnertubeSongItemListSaver) { save(it) } },
value.songsEndpoint?.let { with(InnertubeBrowseEndpointSaver) { save(it) } },
value.albums?.let { with(InnertubeAlbumItemListSaver) { save(it) } },
value.albumsEndpoint?.let { with(InnertubeBrowseEndpointSaver) { save(it) } },
value.singles?.let { with(InnertubeAlbumItemListSaver) { save(it) } },
value.singlesEndpoint?.let { with(InnertubeBrowseEndpointSaver) { save(it) } },
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = Innertube.ArtistPage(
name = value[0] as String?,
description = value[1] as String?,
thumbnail = (value[2] as List<Any?>?)?.let(InnertubeThumbnailSaver::restore),
shuffleEndpoint = (value[3] as List<Any?>?)?.let(InnertubeWatchEndpointSaver::restore),
radioEndpoint = (value[4] as List<Any?>?)?.let(InnertubeWatchEndpointSaver::restore),
songs = (value[5] as List<List<Any?>>?)?.let(InnertubeSongItemListSaver::restore),
songsEndpoint = (value[6] as List<Any?>?)?.let(InnertubeBrowseEndpointSaver::restore),
albums = (value[7] as List<List<Any?>>?)?.let(InnertubeAlbumItemListSaver::restore),
albumsEndpoint = (value[8] as List<Any?>?)?.let(InnertubeBrowseEndpointSaver::restore),
singles = (value[9] as List<List<Any?>>?)?.let(InnertubeAlbumItemListSaver::restore),
singlesEndpoint = (value[10] as List<Any?>?)?.let(InnertubeBrowseEndpointSaver::restore),
)
}

View File

@@ -4,7 +4,7 @@ import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
object YouTubeBrowseEndpointSaver : Saver<NavigationEndpoint.Endpoint.Browse, List<Any?>> {
object InnertubeBrowseEndpointSaver : Saver<NavigationEndpoint.Endpoint.Browse, List<Any?>> {
override fun SaverScope.save(value: NavigationEndpoint.Endpoint.Browse) = listOf(
value.browseId,
value.params

View File

@@ -0,0 +1,20 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
object InnertubeBrowseInfoSaver : Saver<Innertube.Info<NavigationEndpoint.Endpoint.Browse>, List<Any?>> {
override fun SaverScope.save(value: Innertube.Info<NavigationEndpoint.Endpoint.Browse>) = listOf(
value.name,
value.endpoint?.let { with(InnertubeBrowseEndpointSaver) { save(it) } }
)
override fun restore(value: List<Any?>) = Innertube.Info(
name = value[0] as String?,
endpoint = (value[1] as List<Any?>?)?.let(InnertubeBrowseEndpointSaver::restore)
)
}
val InnertubeBrowseInfoListSaver = listSaver(InnertubeBrowseInfoSaver)

View File

@@ -0,0 +1,31 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.Innertube
object InnertubeSongsPageSaver : Saver<Innertube.ItemsPage<Innertube.SongItem>, List<Any?>> {
override fun SaverScope.save(value: Innertube.ItemsPage<Innertube.SongItem>) = listOf(
value.items?.let {with(InnertubeSongItemListSaver) { save(it) } },
value.continuation
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = Innertube.ItemsPage(
items = (value[0] as List<List<Any?>>?)?.let(InnertubeSongItemListSaver::restore),
continuation = value[1] as String?
)
}
object InnertubeAlbumsPageSaver : Saver<Innertube.ItemsPage<Innertube.AlbumItem>, List<Any?>> {
override fun SaverScope.save(value: Innertube.ItemsPage<Innertube.AlbumItem>) = listOf(
value.items?.let {with(InnertubeAlbumItemListSaver) { save(it) } },
value.continuation
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = Innertube.ItemsPage(
items = (value[0] as List<List<Any?>>?)?.let(InnertubeAlbumItemListSaver::restore),
continuation = value[1] as String?
)
}

View File

@@ -0,0 +1,23 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.Innertube
object InnertubePlaylistItemSaver : Saver<Innertube.PlaylistItem, List<Any?>> {
override fun SaverScope.save(value: Innertube.PlaylistItem): List<Any?> = listOf(
value.info?.let { with(InnertubeBrowseInfoSaver) { save(it) } },
value.channel?.let { with(InnertubeBrowseInfoSaver) { save(it) } },
value.songCount,
value.thumbnail?.let { with(InnertubeThumbnailSaver) { save(it) } }
)
override fun restore(value: List<Any?>) = Innertube.PlaylistItem(
info = (value[0] as List<Any?>?)?.let(InnertubeBrowseInfoSaver::restore),
channel = (value[1] as List<Any?>?)?.let(InnertubeBrowseInfoSaver::restore),
songCount = value[2] as Int?,
thumbnail = (value[3] as List<Any?>?)?.let(InnertubeThumbnailSaver::restore)
)
}
val InnertubePlaylistItemListSaver = listSaver(InnertubePlaylistItemSaver)

View File

@@ -0,0 +1,26 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.Innertube
object InnertubePlaylistOrAlbumPageSaver : Saver<Innertube.PlaylistOrAlbumPage, List<Any?>> {
override fun SaverScope.save(value: Innertube.PlaylistOrAlbumPage): List<Any?> = listOf(
value.title,
value.authors?.let { with(InnertubeBrowseInfoListSaver) { save(it) } } ,
value.year,
value.thumbnail?.let { with(InnertubeThumbnailSaver) { save(it) } } ,
value.url,
value.songsPage?.let { with(InnertubeSongsPageSaver) { save(it) } },
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = Innertube.PlaylistOrAlbumPage(
title = value[0] as String?,
authors = (value[1] as List<List<Any?>>?)?.let(InnertubeBrowseInfoListSaver::restore),
year = value[2] as String?,
thumbnail = (value[3] as List<Any?>?)?.let(InnertubeThumbnailSaver::restore),
url = value[4] as String?,
songsPage = (value[5] as List<Any?>?)?.let(InnertubeSongsPageSaver::restore),
)
}

View File

@@ -0,0 +1,22 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.Innertube
object InnertubeRelatedPageSaver : Saver<Innertube.RelatedPage, List<Any?>> {
override fun SaverScope.save(value: Innertube.RelatedPage): List<Any?> = listOf(
value.songs?.let { with(InnertubeSongItemListSaver) { save(it) } },
value.playlists?.let { with(InnertubePlaylistItemListSaver) { save(it) } },
value.albums?.let { with(InnertubeAlbumItemListSaver) { save(it) } },
value.artists?.let { with(InnertubeArtistItemListSaver) { save(it) } },
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = Innertube.RelatedPage(
songs = (value[0] as List<List<Any?>>?)?.let(InnertubeSongItemListSaver::restore),
playlists = (value[1] as List<List<Any?>>?)?.let(InnertubePlaylistItemListSaver::restore),
albums = (value[2] as List<List<Any?>>?)?.let(InnertubeAlbumItemListSaver::restore),
artists = (value[3] as List<List<Any?>>?)?.let(InnertubeArtistItemListSaver::restore),
)
}

View File

@@ -0,0 +1,26 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.Innertube
object InnertubeSongItemSaver : Saver<Innertube.SongItem, List<Any?>> {
override fun SaverScope.save(value: Innertube.SongItem): List<Any?> = listOf(
value.info?.let { with(InnertubeWatchInfoSaver) { save(it) } },
value.authors?.let { with(InnertubeBrowseInfoListSaver) { save(it) } },
value.album?.let { with(InnertubeBrowseInfoSaver) { save(it) } },
value.durationText,
value.thumbnail?.let { with(InnertubeThumbnailSaver) { save(it) } }
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = Innertube.SongItem(
info = (value[0] as List<Any?>?)?.let(InnertubeWatchInfoSaver::restore),
authors = (value[1] as List<List<Any?>>?)?.let(InnertubeBrowseInfoListSaver::restore),
album = (value[2] as List<Any?>?)?.let(InnertubeBrowseInfoSaver::restore),
durationText = value[3] as String?,
thumbnail = (value[4] as List<Any?>?)?.let(InnertubeThumbnailSaver::restore)
)
}
val InnertubeSongItemListSaver = listSaver(InnertubeSongItemSaver)

View File

@@ -0,0 +1,19 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.models.Thumbnail
object InnertubeThumbnailSaver : Saver<Thumbnail, List<Any?>> {
override fun SaverScope.save(value: Thumbnail) = listOf(
value.url,
value.width,
value.height
)
override fun restore(value: List<Any?>) = Thumbnail(
url = value[0] as String,
width = value[1] as Int,
height = value[2] as Int?,
)
}

View File

@@ -0,0 +1,26 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.Innertube
object InnertubeVideoItemSaver : Saver<Innertube.VideoItem, List<Any?>> {
override fun SaverScope.save(value: Innertube.VideoItem): List<Any?> = listOf(
value.info?.let { with(InnertubeWatchInfoSaver) { save(it) } },
value.authors?.let { with(InnertubeBrowseInfoListSaver) { save(it) } },
value.viewsText,
value.durationText,
value.thumbnail?.let { with(InnertubeThumbnailSaver) { save(it) } }
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = Innertube.VideoItem(
info = (value[0] as List<Any?>?)?.let(InnertubeWatchInfoSaver::restore),
authors = (value[1] as List<List<Any?>>?)?.let(InnertubeBrowseInfoListSaver::restore),
viewsText = value[2] as String?,
durationText = value[3] as String?,
thumbnail = (value[4] as List<Any?>?)?.let(InnertubeThumbnailSaver::restore)
)
}
val InnertubeVideoItemListSaver = listSaver(InnertubeVideoItemSaver)

View File

@@ -4,7 +4,7 @@ import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
object YouTubeWatchEndpointSaver : Saver<NavigationEndpoint.Endpoint.Watch, List<Any?>> {
object InnertubeWatchEndpointSaver : Saver<NavigationEndpoint.Endpoint.Watch, List<Any?>> {
override fun SaverScope.save(value: NavigationEndpoint.Endpoint.Watch) = listOf(
value.params,
value.playlistId,

View File

@@ -0,0 +1,18 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
object InnertubeWatchInfoSaver : Saver<Innertube.Info<NavigationEndpoint.Endpoint.Watch>, List<Any?>> {
override fun SaverScope.save(value: Innertube.Info<NavigationEndpoint.Endpoint.Watch>) = listOf(
value.name,
value.endpoint?.let { with(InnertubeWatchEndpointSaver) { save(it) } },
)
override fun restore(value: List<Any?>) = Innertube.Info(
name = value[0] as String?,
endpoint = (value[1] as List<Any?>?)?.let(InnertubeWatchEndpointSaver::restore)
)
}

View File

@@ -1,23 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
interface ListSaver<Original, Saveable : Any> : Saver<List<Original>, List<Saveable>> {
override fun SaverScope.save(value: List<Original>): List<Saveable>
override fun restore(value: List<Saveable>): List<Original>
companion object {
fun <Original, Saveable : Any> of(saver: Saver<Original, Saveable>): ListSaver<Original, Saveable> {
return object : ListSaver<Original, Saveable> {
override fun restore(value: List<Saveable>): List<Original> {
return value.mapNotNull(saver::restore)
}
override fun SaverScope.save(value: List<Original>): List<Saveable> {
return with(saver) { value.mapNotNull { save(it) } }
}
}
}
}
}

View File

@@ -1,13 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
fun <Original, Saveable : Any> nullableSaver(saver: Saver<Original, Saveable>) =
object : Saver<Original?, Saveable> {
override fun SaverScope.save(value: Original?): Saveable? =
value?.let { with(saver) { save(it) } }
override fun restore(value: Saveable): Original? =
saver.restore(value)
}

View File

@@ -1,3 +0,0 @@
package it.vfsfitvnm.vimusic.savers
val PlaylistPreviewListSaver = ListSaver.of(PlaylistPreviewSaver)

View File

@@ -4,18 +4,16 @@ import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.vimusic.models.PlaylistPreview
object PlaylistPreviewSaver : Saver<PlaylistPreview, List<Any?>> {
override fun SaverScope.save(value: PlaylistPreview): List<Any> {
return listOf(
with(PlaylistSaver) { save(value.playlist) },
value.songCount,
)
}
object PlaylistPreviewSaver : Saver<PlaylistPreview, List<Any>> {
override fun SaverScope.save(value: PlaylistPreview) = listOf(
with(PlaylistSaver) { save(value.playlist) },
value.songCount,
)
override fun restore(value: List<Any?>): PlaylistPreview? {
return if (value.size == 2) PlaylistPreview(
playlist = PlaylistSaver.restore(value[0] as List<Any?>),
songCount = value[1] as Int,
) else null
}
override fun restore(value: List<Any>) = PlaylistPreview(
playlist = PlaylistSaver.restore(value[0] as List<Any?>),
songCount = value[1] as Int,
)
}
val PlaylistPreviewListSaver = listSaver(PlaylistPreviewSaver)

View File

@@ -4,13 +4,11 @@ import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.vimusic.models.PlaylistWithSongs
object PlaylistWithSongsSaver : Saver<PlaylistWithSongs?, List<Any>> {
override fun SaverScope.save(value: PlaylistWithSongs?) = value?.let {
listOf(
with(PlaylistSaver) { save(value.playlist) },
with(DetailedSongListSaver) { save(value.songs) },
)
}
object PlaylistWithSongsSaver : Saver<PlaylistWithSongs, List<Any>> {
override fun SaverScope.save(value: PlaylistWithSongs) = listOf(
with(PlaylistSaver) { save(value.playlist) },
with(DetailedSongListSaver) { save(value.songs) },
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any>): PlaylistWithSongs = PlaylistWithSongs(

View File

@@ -1,14 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
fun <Original, Saveable : Any> resultSaver(saver: Saver<Original, Saveable>) =
object : Saver<Result<Original>?, Pair<Saveable?, Throwable?>> {
override fun restore(value: Pair<Saveable?, Throwable?>) =
value.first?.let(saver::restore)?.let(Result.Companion::success)
?: value.second?.let(Result.Companion::failure)
override fun SaverScope.save(value: Result<Original>?) =
with(saver) { value?.getOrNull()?.let { save(it) } } to value?.exceptionOrNull()
}

View File

@@ -0,0 +1,37 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
interface ListSaver<Original, Saveable : Any> : Saver<List<Original>, List<Saveable>> {
override fun SaverScope.save(value: List<Original>): List<Saveable>
override fun restore(value: List<Saveable>): List<Original>
}
fun <Original, Saveable : Any> resultSaver(saver: Saver<Original, Saveable>) =
object : Saver<Result<Original>?, Pair<Saveable?, Throwable?>> {
override fun restore(value: Pair<Saveable?, Throwable?>) =
value.first?.let(saver::restore)?.let(Result.Companion::success)
?: value.second?.let(Result.Companion::failure)
override fun SaverScope.save(value: Result<Original>?) =
with(saver) { value?.getOrNull()?.let { save(it) } } to value?.exceptionOrNull()
}
fun <Original, Saveable : Any> listSaver(saver: Saver<Original, Saveable>) =
object : ListSaver<Original, Saveable> {
override fun restore(value: List<Saveable>) =
value.mapNotNull(saver::restore)
override fun SaverScope.save(value: List<Original>) =
with(saver) { value.mapNotNull { save(it) } }
}
fun <Original, Saveable : Any> nullableSaver(saver: Saver<Original, Saveable>) =
object : Saver<Original?, Saveable> {
override fun SaverScope.save(value: Original?): Saveable? =
value?.let { with(saver) { save(it) } }
override fun restore(value: Saveable): Original? =
saver.restore(value)
}

View File

@@ -1,3 +0,0 @@
package it.vfsfitvnm.vimusic.savers
val SearchQueryListSaver = ListSaver.of(SearchQuerySaver)

View File

@@ -15,3 +15,5 @@ object SearchQuerySaver : Saver<SearchQuery, List<Any?>> {
query = value[1] as String
)
}
val SearchQueryListSaver = listSaver(SearchQuerySaver)

View File

@@ -1,5 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.autoSaver
val StringListResultSaver = resultSaver(autoSaver<List<String>?>())

View File

@@ -1,5 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.autoSaver
val StringResultSaver = resultSaver(autoSaver<String?>())

View File

@@ -1,3 +0,0 @@
package it.vfsfitvnm.vimusic.savers
val YouTubeAlbumListSaver = ListSaver.of(YouTubeAlbumSaver)

View File

@@ -1,22 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.YouTube
object YouTubeAlbumSaver : Saver<YouTube.Item.Album, List<Any?>> {
override fun SaverScope.save(value: YouTube.Item.Album): List<Any?> = listOf(
value.info?.let { with(YouTubeBrowseInfoSaver) { save(it) } },
value.authors?.let { with(YouTubeBrowseInfoListSaver) { save(it) } },
value.year,
value.thumbnail?.let { with(YouTubeThumbnailSaver) { save(it) } }
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = YouTube.Item.Album(
info = (value[0] as List<Any?>?)?.let(YouTubeBrowseInfoSaver::restore),
authors = (value[1] as List<List<Any?>>?)?.let(YouTubeBrowseInfoListSaver::restore),
year = value[2] as String?,
thumbnail = (value[3] as List<Any?>?)?.let(YouTubeThumbnailSaver::restore)
)
}

View File

@@ -1,3 +0,0 @@
package it.vfsfitvnm.vimusic.savers
val YouTubeArtistListSaver = ListSaver.of(YouTubeArtistSaver)

View File

@@ -1,36 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.YouTube
object YouTubeArtistPageSaver : Saver<YouTube.Artist, List<Any?>> {
override fun SaverScope.save(value: YouTube.Artist): List<Any?> = listOf(
value.name,
value.description,
value.thumbnail?.let { with(YouTubeThumbnailSaver) { save(it) } },
value.shuffleEndpoint?.let { with(YouTubeWatchEndpointSaver) { save(it) } },
value.radioEndpoint?.let { with(YouTubeWatchEndpointSaver) { save(it) } },
value.songs?.let { with(YouTubeSongListSaver) { save(it) } },
value.songsEndpoint?.let { with(YouTubeBrowseEndpointSaver) { save(it) } },
value.albums?.let { with(YouTubeAlbumListSaver) { save(it) } },
value.albumsEndpoint?.let { with(YouTubeBrowseEndpointSaver) { save(it) } },
value.singles?.let { with(YouTubeAlbumListSaver) { save(it) } },
value.singlesEndpoint?.let { with(YouTubeBrowseEndpointSaver) { save(it) } },
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = YouTube.Artist(
name = value[0] as String?,
description = value[1] as String?,
thumbnail = (value[2] as List<Any?>?)?.let(YouTubeThumbnailSaver::restore),
shuffleEndpoint = (value[3] as List<Any?>?)?.let(YouTubeWatchEndpointSaver::restore),
radioEndpoint = (value[4] as List<Any?>?)?.let(YouTubeWatchEndpointSaver::restore),
songs = (value[5] as List<List<Any?>>?)?.let(YouTubeSongListSaver::restore),
songsEndpoint = (value[6] as List<Any?>?)?.let(YouTubeBrowseEndpointSaver::restore),
albums = (value[7] as List<List<Any?>>?)?.let(YouTubeAlbumListSaver::restore),
albumsEndpoint = (value[8] as List<Any?>?)?.let(YouTubeBrowseEndpointSaver::restore),
singles = (value[9] as List<List<Any?>>?)?.let(YouTubeAlbumListSaver::restore),
singlesEndpoint = (value[10] as List<Any?>?)?.let(YouTubeBrowseEndpointSaver::restore),
)
}

View File

@@ -1,20 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.vimusic.savers.YouTubeThumbnailSaver.save
import it.vfsfitvnm.youtubemusic.YouTube
object YouTubeArtistSaver : Saver<YouTube.Item.Artist, List<Any?>> {
override fun SaverScope.save(value: YouTube.Item.Artist): List<Any?> = listOf(
value.info?.let { with(YouTubeBrowseInfoSaver) { save(it) } },
value.subscribersCountText,
value.thumbnail?.let { with(YouTubeThumbnailSaver) { save(it) } }
)
override fun restore(value: List<Any?>) = YouTube.Item.Artist(
info = (value[0] as List<Any?>?)?.let(YouTubeBrowseInfoSaver::restore),
subscribersCountText = value[1] as String?,
thumbnail = (value[2] as List<Any?>?)?.let(YouTubeThumbnailSaver::restore)
)
}

View File

@@ -1,3 +0,0 @@
package it.vfsfitvnm.vimusic.savers
val YouTubeBrowseInfoListSaver = ListSaver.of(YouTubeBrowseInfoSaver)

View File

@@ -1,18 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
object YouTubeBrowseInfoSaver : Saver<YouTube.Info<NavigationEndpoint.Endpoint.Browse>, List<Any?>> {
override fun SaverScope.save(value: YouTube.Info<NavigationEndpoint.Endpoint.Browse>) = listOf(
value.name,
value.endpoint?.let { with(YouTubeBrowseEndpointSaver) { save(it) } }
)
override fun restore(value: List<Any?>) = YouTube.Info(
name = value[0] as String?,
endpoint = (value[1] as List<Any?>?)?.let(YouTubeBrowseEndpointSaver::restore)
)
}

View File

@@ -1,3 +0,0 @@
package it.vfsfitvnm.vimusic.savers
val YouTubePlaylistListSaver = ListSaver.of(YouTubePlaylistSaver)

View File

@@ -1,27 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.YouTube
object YouTubePlaylistOrAlbumSaver : Saver<YouTube.PlaylistOrAlbum, List<Any?>> {
override fun SaverScope.save(value: YouTube.PlaylistOrAlbum): List<Any?> = listOf(
value.title,
value.authors?.let { with(YouTubeBrowseInfoListSaver) { save(it) } } ,
value.year,
value.thumbnail?.let { with(YouTubeThumbnailSaver) { save(it) } } ,
value.songs?.let { with(YouTubeSongListSaver) { save(it) } },
value.url
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = YouTube.PlaylistOrAlbum(
title = value[0] as String?,
authors = (value[1] as List<List<Any?>>?)?.let(YouTubeBrowseInfoListSaver::restore),
year = value[2] as String?,
thumbnail = (value[3] as List<Any?>?)?.let(YouTubeThumbnailSaver::restore),
songs = (value[4] as List<List<Any?>>?)?.let(YouTubeSongListSaver::restore),
url = value[5] as String?,
continuation = null
)
}

View File

@@ -1,21 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.YouTube
object YouTubePlaylistSaver : Saver<YouTube.Item.Playlist, List<Any?>> {
override fun SaverScope.save(value: YouTube.Item.Playlist): List<Any?> = listOf(
value.info?.let { with(YouTubeBrowseInfoSaver) { save(it) } },
value.channel?.let { with(YouTubeBrowseInfoSaver) { save(it) } },
value.songCount,
value.thumbnail?.let { with(YouTubeThumbnailSaver) { save(it) } }
)
override fun restore(value: List<Any?>) = YouTube.Item.Playlist(
info = (value[0] as List<Any?>?)?.let(YouTubeBrowseInfoSaver::restore),
channel = (value[1] as List<Any?>?)?.let(YouTubeBrowseInfoSaver::restore),
songCount = value[2] as Int?,
thumbnail = (value[3] as List<Any?>?)?.let(YouTubeThumbnailSaver::restore)
)
}

View File

@@ -1,22 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.YouTube
object YouTubeRelatedSaver : Saver<YouTube.Related, List<Any?>> {
override fun SaverScope.save(value: YouTube.Related): List<Any?> = listOf(
value.songs?.let { with(YouTubeSongListSaver) { save(it) } },
value.playlists?.let { with(YouTubePlaylistListSaver) { save(it) } },
value.albums?.let { with(YouTubeAlbumListSaver) { save(it) } },
value.artists?.let { with(YouTubeArtistListSaver) { save(it) } },
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = YouTube.Related(
songs = (value[0] as List<List<Any?>>?)?.let(YouTubeSongListSaver::restore),
playlists = (value[1] as List<List<Any?>>?)?.let(YouTubePlaylistListSaver::restore),
albums = (value[2] as List<List<Any?>>?)?.let(YouTubeAlbumListSaver::restore),
artists = (value[3] as List<List<Any?>>?)?.let(YouTubeArtistListSaver::restore),
)
}

View File

@@ -1,3 +0,0 @@
package it.vfsfitvnm.vimusic.savers
val YouTubeSongListSaver = ListSaver.of(YouTubeSongSaver)

View File

@@ -1,24 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.YouTube
object YouTubeSongSaver : Saver<YouTube.Item.Song, List<Any?>> {
override fun SaverScope.save(value: YouTube.Item.Song): List<Any?> = listOf(
value.info?.let { with(YouTubeWatchInfoSaver) { save(it) } },
value.authors?.let { with(YouTubeBrowseInfoListSaver) { save(it) } },
value.album?.let { with(YouTubeBrowseInfoSaver) { save(it) } },
value.durationText,
value.thumbnail?.let { with(YouTubeThumbnailSaver) { save(it) } }
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = YouTube.Item.Song(
info = (value[0] as List<Any?>?)?.let(YouTubeWatchInfoSaver::restore),
authors = (value[1] as List<List<Any?>>?)?.let(YouTubeBrowseInfoListSaver::restore),
album = (value[2] as List<Any?>?)?.let(YouTubeBrowseInfoSaver::restore),
durationText = value[3] as String?,
thumbnail = (value[4] as List<Any?>?)?.let(YouTubeThumbnailSaver::restore)
)
}

View File

@@ -1,19 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.models.ThumbnailRenderer
object YouTubeThumbnailSaver : Saver<ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail, List<Any?>> {
override fun SaverScope.save(value: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail) = listOf(
value.url,
value.width,
value.height
)
override fun restore(value: List<Any?>) = ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail(
url = value[0] as String,
width = value[1] as Int,
height = value[2] as Int?,
)
}

View File

@@ -1,3 +0,0 @@
package it.vfsfitvnm.vimusic.savers
val YouTubeVideoListSaver = ListSaver.of(YouTubeVideoSaver)

View File

@@ -1,24 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.YouTube
object YouTubeVideoSaver : Saver<YouTube.Item.Video, List<Any?>> {
override fun SaverScope.save(value: YouTube.Item.Video): List<Any?> = listOf(
value.info?.let { with(YouTubeWatchInfoSaver) { save(it) } },
value.authors?.let { with(YouTubeBrowseInfoListSaver) { save(it) } },
value.viewsText,
value.durationText,
value.thumbnail?.let { with(YouTubeThumbnailSaver) { save(it) } }
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = YouTube.Item.Video(
info = (value[0] as List<Any?>?)?.let(YouTubeWatchInfoSaver::restore),
authors = (value[1] as List<List<Any?>>?)?.let(YouTubeBrowseInfoListSaver::restore),
viewsText = value[2] as String?,
durationText = value[3] as String?,
thumbnail = (value[4] as List<Any?>?)?.let(YouTubeThumbnailSaver::restore)
)
}

View File

@@ -1,18 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
object YouTubeWatchInfoSaver : Saver<YouTube.Info<NavigationEndpoint.Endpoint.Watch>, List<Any?>> {
override fun SaverScope.save(value: YouTube.Info<NavigationEndpoint.Endpoint.Watch>) = listOf(
value.name,
value.endpoint?.let { with(YouTubeWatchEndpointSaver) { save(it) } },
)
override fun restore(value: List<Any?>) = YouTube.Info(
name = value[0] as String?,
endpoint = (value[1] as List<Any?>?)?.let(YouTubeWatchEndpointSaver::restore)
)
}

View File

@@ -92,8 +92,10 @@ import it.vfsfitvnm.vimusic.utils.shouldBePlaying
import it.vfsfitvnm.vimusic.utils.skipSilenceKey
import it.vfsfitvnm.vimusic.utils.timer
import it.vfsfitvnm.vimusic.utils.volumeNormalizationKey
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
import it.vfsfitvnm.youtubemusic.models.bodies.PlayerBody
import it.vfsfitvnm.youtubemusic.requests.player
import kotlin.math.roundToInt
import kotlin.system.exitProcess
import kotlinx.coroutines.CoroutineScope
@@ -642,9 +644,9 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
ringBuffer.getOrNull(1)?.first -> dataSpec.withUri(ringBuffer.getOrNull(1)!!.second)
else -> {
val urlResult = runBlocking(Dispatchers.IO) {
YouTube.player(videoId)
Innertube.player(PlayerBody(videoId = videoId))
}?.mapCatching { body ->
when (val status = body.playabilityStatus.status) {
when (val status = body.playabilityStatus?.status) {
"OK" -> body.streamingData?.adaptiveFormats?.findLast { format ->
format.itag == 251 || format.itag == 140
}?.let { format ->

View File

@@ -61,12 +61,13 @@ import it.vfsfitvnm.vimusic.utils.color
import it.vfsfitvnm.vimusic.utils.enqueue
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.vimusic.utils.thumbnail
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
import it.vfsfitvnm.youtubemusic.requests.albumPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
@@ -88,19 +89,21 @@ fun AlbumOverview(
withContext(Dispatchers.IO) {
Database.album(browseId).collect { album ->
if (album?.timestamp == null) {
YouTube.album(browseId)?.onSuccess { youtubeAlbum ->
Innertube.albumPage(BrowseBody(browseId = browseId))?.onSuccess { albumPage ->
Database.upsert(
Album(
id = browseId,
title = youtubeAlbum.title,
thumbnailUrl = youtubeAlbum.thumbnail?.url,
year = youtubeAlbum.year,
authorsText = youtubeAlbum.authors?.joinToString("") { it.name ?: "" },
shareUrl = youtubeAlbum.url,
title = albumPage.title,
thumbnailUrl = albumPage.thumbnail?.url,
year = albumPage.year,
authorsText = albumPage.authors?.joinToString("") { it.name ?: "" },
shareUrl = albumPage.url,
timestamp = System.currentTimeMillis()
),
youtubeAlbum.songs
?.map(YouTube.Item.Song::asMediaItem)
albumPage
.songsPage
?.items
?.map(Innertube.SongItem::asMediaItem)
?.onEach(Database::insert)
?.mapIndexed { position, mediaItem ->
SongAlbumMap(

View File

@@ -8,7 +8,6 @@ import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -26,19 +25,19 @@ import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.produceSaveableRelaunchableOneShotState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ExperimentalAnimationApi
@Composable
inline fun <T : YouTube.Item> ArtistContent(
inline fun <T : Innertube.Item> ArtistContent(
artist: Artist?,
youtubeArtist: YouTube.Artist?,
youtubeArtistPage: Innertube.ArtistPage?,
isLoading: Boolean,
isError: Boolean,
stateSaver: ListSaver<T, List<Any?>>,
crossinline itemsProvider: suspend (String?) -> Result<Pair<String?, List<T>?>>?,
crossinline itemsPageProvider: suspend (String?) -> Result<Innertube.ItemsPage<T>?>?,
crossinline bookmarkIconContent: @Composable () -> Unit,
crossinline shareIconContent: @Composable () -> Unit,
crossinline itemContent: @Composable LazyItemScope.(T) -> Unit,
@@ -61,18 +60,18 @@ inline fun <T : YouTube.Item> ArtistContent(
val (continuationState, fetch) = produceSaveableRelaunchableOneShotState(
initialValue = null,
stateSaver = autoSaver<String?>(),
youtubeArtist
youtubeArtistPage
) {
if (youtubeArtist == null) return@produceSaveableRelaunchableOneShotState
if (youtubeArtistPage == null) return@produceSaveableRelaunchableOneShotState
println("loading... $value")
isLoadingItems = true
withContext(Dispatchers.IO) {
itemsProvider(value)?.onSuccess { (continuation, newItems) ->
value = continuation
newItems?.let {
items = items.plus(it).distinctBy(YouTube.Item::key)
itemsPageProvider(value)?.onSuccess { itemsPage ->
value = itemsPage?.continuation
itemsPage?.items?.let {
items = items.plus(it).distinctBy(Innertube.Item::key)
}
isErrorItems = false
isLoadingItems = false
@@ -105,7 +104,7 @@ inline fun <T : YouTube.Item> ArtistContent(
items(
items = items,
key = YouTube.Item::key,
key = Innertube.Item::key,
itemContent = itemContent
)

View File

@@ -57,14 +57,14 @@ import it.vfsfitvnm.vimusic.utils.forcePlay
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.vimusic.utils.thumbnail
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
@ExperimentalAnimationApi
@Composable
fun ArtistOverview(
artist: Artist?,
youtubeArtist: YouTube.Artist?,
youtubeArtistPage: Innertube.ArtistPage?,
isLoading: Boolean,
isError: Boolean,
onViewAllSongsClick: () -> Unit,
@@ -100,7 +100,7 @@ fun ArtistOverview(
when {
artist != null -> {
Header(title = artist.name ?: "Unknown") {
youtubeArtist?.radioEndpoint?.let { radioEndpoint ->
youtubeArtistPage?.radioEndpoint?.let { radioEndpoint ->
SecondaryTextButton(
text = "Start radio",
onClick = {
@@ -130,8 +130,8 @@ fun ArtistOverview(
)
when {
youtubeArtist != null -> {
youtubeArtist.songs?.let { songs ->
youtubeArtistPage != null -> {
youtubeArtistPage.songs?.let { songs ->
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.SpaceBetween,
@@ -144,7 +144,7 @@ fun ArtistOverview(
modifier = sectionTextModifier
)
youtubeArtist.songsEndpoint?.let {
youtubeArtistPage.songsEndpoint?.let {
BasicText(
text = "View all",
style = typography.xs.secondary,
@@ -174,7 +174,7 @@ fun ArtistOverview(
}
}
youtubeArtist.albums?.let { albums ->
youtubeArtistPage.albums?.let { albums ->
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.SpaceBetween,
@@ -187,7 +187,7 @@ fun ArtistOverview(
modifier = sectionTextModifier
)
youtubeArtist.albumsEndpoint?.let {
youtubeArtistPage.albumsEndpoint?.let {
BasicText(
text = "View all",
style = typography.xs.secondary,
@@ -207,7 +207,7 @@ fun ArtistOverview(
) {
items(
items = albums,
key = YouTube.Item.Album::key
key = Innertube.AlbumItem::key
) { album ->
AlternativeAlbumItem(
album = album,
@@ -224,7 +224,7 @@ fun ArtistOverview(
}
}
youtubeArtist.singles?.let { singles ->
youtubeArtistPage.singles?.let { singles ->
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.SpaceBetween,
@@ -237,7 +237,7 @@ fun ArtistOverview(
modifier = sectionTextModifier
)
youtubeArtist.singlesEndpoint?.let {
youtubeArtistPage.singlesEndpoint?.let {
BasicText(
text = "View all",
style = typography.xs.secondary,
@@ -257,7 +257,7 @@ fun ArtistOverview(
) {
items(
items = singles,
key = YouTube.Item.Album::key
key = Innertube.AlbumItem::key
) { album ->
AlternativeAlbumItem(
album = album,
@@ -330,7 +330,7 @@ fun ArtistOverview(
}
}
youtubeArtist?.shuffleEndpoint?.let { shuffleEndpoint ->
youtubeArtistPage?.shuffleEndpoint?.let { shuffleEndpoint ->
PrimaryButton(
iconId = R.drawable.shuffle,
onClick = {

View File

@@ -26,14 +26,13 @@ import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.PartialArtist
import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.savers.ArtistSaver
import it.vfsfitvnm.vimusic.savers.YouTubeAlbumListSaver
import it.vfsfitvnm.vimusic.savers.YouTubeArtistPageSaver
import it.vfsfitvnm.vimusic.savers.YouTubeSongListSaver
import it.vfsfitvnm.vimusic.savers.InnertubeAlbumItemListSaver
import it.vfsfitvnm.vimusic.savers.InnertubeArtistPageSaver
import it.vfsfitvnm.vimusic.savers.InnertubeSongItemListSaver
import it.vfsfitvnm.vimusic.savers.nullableSaver
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
import it.vfsfitvnm.vimusic.ui.screens.albumRoute
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
import it.vfsfitvnm.vimusic.ui.screens.searchresult.SearchResult
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.ui.styling.px
@@ -47,7 +46,12 @@ import it.vfsfitvnm.vimusic.utils.forcePlay
import it.vfsfitvnm.vimusic.utils.produceSaveableLazyOneShotState
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
import it.vfsfitvnm.youtubemusic.models.bodies.ContinuationBody
import it.vfsfitvnm.youtubemusic.requests.artistPage
import it.vfsfitvnm.youtubemusic.requests.itemsPage
import it.vfsfitvnm.youtubemusic.utils.from
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
@@ -72,22 +76,22 @@ fun ArtistScreen(browseId: String) {
val youtubeArtist by produceSaveableLazyOneShotState(
initialValue = null,
stateSaver = nullableSaver(YouTubeArtistPageSaver)
stateSaver = nullableSaver(InnertubeArtistPageSaver)
) {
println("${System.currentTimeMillis()}, computing lazyEffect (youtubeArtistResult = ${value?.name})!")
isLoading = true
withContext(Dispatchers.IO) {
YouTube.artist(browseId)?.onSuccess { youtubeArtist ->
value = youtubeArtist
Innertube.artistPage(browseId)?.onSuccess { artistPage ->
value = artistPage
query {
Database.upsert(
PartialArtist(
id = browseId,
name = youtubeArtist.name,
thumbnailUrl = youtubeArtist.thumbnail?.url,
info = youtubeArtist.description,
name = artistPage.name,
thumbnailUrl = artistPage.thumbnail?.url,
info = artistPage.description,
timestamp = System.currentTimeMillis()
)
)
@@ -136,10 +140,13 @@ fun ArtistScreen(browseId: String) {
colorFilter = ColorFilter.tint(LocalAppearance.current.colorPalette.accent),
modifier = Modifier
.clickable {
val bookmarkedAt = if (artist?.bookmarkedAt == null) System.currentTimeMillis() else null
val bookmarkedAt =
if (artist?.bookmarkedAt == null) System.currentTimeMillis() else null
query {
artist?.copy(bookmarkedAt = bookmarkedAt)?.let(Database::update)
artist
?.copy(bookmarkedAt = bookmarkedAt)
?.let(Database::update)
}
}
.padding(all = 4.dp)
@@ -194,7 +201,7 @@ fun ArtistScreen(browseId: String) {
when (currentTabIndex) {
0 -> ArtistOverview(
artist = artist,
youtubeArtist = youtubeArtist,
youtubeArtistPage = youtubeArtist,
isLoading = isLoading,
isError = isError,
bookmarkIconContent = bookmarkIconContent,
@@ -204,6 +211,7 @@ fun ArtistScreen(browseId: String) {
onViewAllAlbumsClick = { onTabIndexChanged(2) },
onViewAllSinglesClick = { onTabIndexChanged(3) },
)
1 -> {
val binder = LocalPlayerServiceBinder.current
val thumbnailSizeDp = Dimensions.thumbnails.song
@@ -211,20 +219,26 @@ fun ArtistScreen(browseId: String) {
ArtistContent(
artist = artist,
youtubeArtist = youtubeArtist,
youtubeArtistPage = youtubeArtist,
isLoading = isLoading,
isError = isError,
stateSaver = YouTubeSongListSaver,
stateSaver = InnertubeSongItemListSaver,
bookmarkIconContent = bookmarkIconContent,
shareIconContent = shareIconContent,
itemsProvider = { continuation ->
youtubeArtist
itemsPageProvider = { continuation ->
continuation?.let {
Innertube.itemsPage(
body = ContinuationBody(continuation = continuation),
fromMusicResponsiveListItemRenderer = Innertube.SongItem::from,
)
} ?: youtubeArtist
?.songsEndpoint
?.browseId
?.let { browseId ->
YouTube.items(browseId, continuation, YouTube.Item.Song::from)?.map { result ->
result?.continuation to result?.items
}
Innertube.itemsPage(
body = BrowseBody(browseId = browseId),
fromMusicResponsiveListItemRenderer = Innertube.SongItem::from,
)
}
},
itemContent = { song ->
@@ -243,25 +257,33 @@ fun ArtistScreen(browseId: String) {
}
)
}
2 -> {
val thumbnailSizeDp = 108.dp
val thumbnailSizePx = thumbnailSizeDp.px
ArtistContent(
artist = artist,
youtubeArtist = youtubeArtist,
youtubeArtistPage = youtubeArtist,
isLoading = isLoading,
isError = isError,
stateSaver = YouTubeAlbumListSaver,
stateSaver = InnertubeAlbumItemListSaver,
bookmarkIconContent = bookmarkIconContent,
shareIconContent = shareIconContent,
itemsProvider = {
youtubeArtist
?.albumsEndpoint
?.let { endpoint ->
YouTube.items2(browseId, endpoint.params, YouTube.Item.Album::from)?.map { result ->
result?.continuation to result?.items
}
itemsPageProvider = { continuation ->
continuation?.let {
Innertube.itemsPage(
body = ContinuationBody(continuation = continuation),
fromMusicTwoRowItemRenderer = Innertube.AlbumItem::from,
)
} ?: youtubeArtist
?.songsEndpoint
?.browseId
?.let { browseId ->
Innertube.itemsPage(
body = BrowseBody(browseId = browseId),
fromMusicTwoRowItemRenderer = Innertube.AlbumItem::from,
)
}
},
itemContent = { album ->
@@ -282,25 +304,33 @@ fun ArtistScreen(browseId: String) {
}
)
}
3 -> {
val thumbnailSizeDp = 108.dp
val thumbnailSizePx = thumbnailSizeDp.px
ArtistContent(
artist = artist,
youtubeArtist = youtubeArtist,
youtubeArtistPage = youtubeArtist,
isLoading = isLoading,
isError = isError,
stateSaver = YouTubeAlbumListSaver,
stateSaver = InnertubeAlbumItemListSaver,
bookmarkIconContent = bookmarkIconContent,
shareIconContent = shareIconContent,
itemsProvider = {
youtubeArtist
?.singlesEndpoint
?.let { endpoint ->
YouTube.items2(browseId, endpoint.params, YouTube.Item.Album::from)?.map { result ->
result?.continuation to result?.items
}
itemsPageProvider = { continuation ->
continuation?.let {
Innertube.itemsPage(
body = ContinuationBody(continuation = continuation),
fromMusicTwoRowItemRenderer = Innertube.AlbumItem::from,
)
} ?: youtubeArtist
?.songsEndpoint
?.browseId
?.let { browseId ->
Innertube.itemsPage(
body = BrowseBody(browseId = browseId),
fromMusicTwoRowItemRenderer = Innertube.AlbumItem::from,
)
}
},
itemContent = { album ->
@@ -321,6 +351,7 @@ fun ArtistScreen(browseId: String) {
}
)
}
4 -> ArtistLocalSongsList(
browseId = browseId,
artist = artist,

View File

@@ -34,7 +34,7 @@ import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.savers.DetailedSongSaver
import it.vfsfitvnm.vimusic.savers.YouTubeRelatedSaver
import it.vfsfitvnm.vimusic.savers.InnertubeRelatedPageSaver
import it.vfsfitvnm.vimusic.savers.nullableSaver
import it.vfsfitvnm.vimusic.savers.resultSaver
import it.vfsfitvnm.vimusic.ui.components.themed.Header
@@ -60,8 +60,10 @@ import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.vimusic.utils.thumbnail
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
import it.vfsfitvnm.youtubemusic.models.bodies.NextBody
import it.vfsfitvnm.youtubemusic.requests.relatedPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
@@ -88,30 +90,13 @@ fun QuickPicks(
.collect { value = it }
}
val relatedResult by produceSaveableOneShotState(
val relatedPageResult by produceSaveableOneShotState(
initialValue = null,
stateSaver = resultSaver(nullableSaver(YouTubeRelatedSaver)),
stateSaver = resultSaver(nullableSaver(InnertubeRelatedPageSaver)),
trending?.id
) {
trending?.id?.let { trendingVideoId ->
value = YouTube.related(trendingVideoId)?.map { related ->
related?.copy(
albums = related.albums?.map { album ->
album.copy(
authors = trending?.artists?.map { info ->
YouTube.Info(
name = info.name,
endpoint = NavigationEndpoint.Endpoint.Browse(
browseId = info.id,
params = null,
browseEndpointContextSupportedConfigs = null
)
)
}
)
}
)
}
value = Innertube.relatedPage(NextBody(videoId = trendingVideoId))
}
}
@@ -140,7 +125,7 @@ fun QuickPicks(
) {
Header(title = "Quick picks")
relatedResult?.getOrNull()?.let { related ->
relatedPageResult?.getOrNull()?.let { related ->
LazyHorizontalGrid(
rows = GridCells.Fixed(4),
modifier = Modifier
@@ -171,7 +156,7 @@ fun QuickPicks(
items(
items = related.songs ?: emptyList(),
key = YouTube.Item.Song::key
key = Innertube.SongItem::key
) { song ->
SmallSongItem(
song = song,
@@ -204,7 +189,7 @@ fun QuickPicks(
) {
items(
items = related.albums ?: emptyList(),
key = YouTube.Item.Album::key
key = Innertube.AlbumItem::key
) { album ->
AlbumItem(
album = album,
@@ -235,7 +220,7 @@ fun QuickPicks(
) {
items(
items = related.artists ?: emptyList(),
key = YouTube.Item.Artist::key,
key = Innertube.ArtistItem::key,
) { artist ->
ArtistItem(
artist = artist,
@@ -268,7 +253,7 @@ fun QuickPicks(
) {
items(
items = related.playlists ?: emptyList(),
key = YouTube.Item.Playlist::key,
key = Innertube.PlaylistItem::key,
) { playlist ->
PlaylistItem(
playlist = playlist,
@@ -284,7 +269,7 @@ fun QuickPicks(
)
}
}
} ?: relatedResult?.exceptionOrNull()?.let {
} ?: relatedPageResult?.exceptionOrNull()?.let {
BasicText(
text = "An error has occurred",
style = typography.s.secondary.center,

View File

@@ -34,6 +34,7 @@ import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.models.SongPlaylistMap
import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.savers.PlaylistWithSongsSaver
import it.vfsfitvnm.vimusic.savers.nullableSaver
import it.vfsfitvnm.vimusic.transaction
import it.vfsfitvnm.vimusic.ui.components.themed.ConfirmationDialog
import it.vfsfitvnm.vimusic.ui.components.themed.Header
@@ -50,7 +51,9 @@ import it.vfsfitvnm.vimusic.utils.enqueue
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
import it.vfsfitvnm.youtubemusic.requests.playlistPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.runBlocking
@@ -68,7 +71,7 @@ fun LocalPlaylistSongList(
val playlistWithSongs by produceSaveableState(
initialValue = null,
stateSaver = PlaylistWithSongsSaver
stateSaver = nullableSaver(PlaylistWithSongsSaver)
) {
Database
.playlistWithSongs(playlistId)
@@ -165,13 +168,16 @@ fun LocalPlaylistSongList(
transaction {
runBlocking(Dispatchers.IO) {
withContext(Dispatchers.IO) {
YouTube.playlist(browseId)?.map { it.next() }
// TODO: fetch all songs!
Innertube.playlistPage(BrowseBody(browseId = browseId))
}
}?.getOrNull()?.let { remotePlaylist ->
Database.clearPlaylist(playlistId)
remotePlaylist.songs
?.map(YouTube.Item.Song::asMediaItem)
remotePlaylist.
songsPage
?.items
?.map(Innertube.SongItem::asMediaItem)
?.onEach(Database::insert)
?.mapIndexed { position, mediaItem ->
SongPlaylistMap(

View File

@@ -69,7 +69,9 @@ import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.relaunchableEffect
import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.vimusic.utils.verticalFadingEdge
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.NextBody
import it.vfsfitvnm.youtubemusic.requests.lyrics
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -134,8 +136,7 @@ fun Lyrics(
duration = duration / 1000
)?.map { it?.value }
} else {
YouTube.next(mediaId, null)
?.map { nextResult -> nextResult.lyrics()?.getOrNull() }
Innertube.lyrics(NextBody(videoId = mediaId))
}?.map { newLyrics ->
onLyricsUpdate(isShowingSynchronizedLyrics, mediaId, newLyrics ?: "")
state = state.copy(isLoading = false)

View File

@@ -40,7 +40,9 @@ import it.vfsfitvnm.vimusic.ui.styling.overlay
import it.vfsfitvnm.vimusic.utils.color
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.rememberVolume
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.PlayerBody
import it.vfsfitvnm.youtubemusic.requests.player
import kotlin.math.roundToInt
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -195,7 +197,7 @@ fun StatsForNerds(
onClick = {
query {
runBlocking(Dispatchers.IO) {
YouTube.player(mediaId)
Innertube.player(PlayerBody(videoId = mediaId))
?.map { response ->
response.streamingData?.adaptiveFormats
?.findLast { format ->

View File

@@ -27,7 +27,9 @@ fun PlaylistScreen(browseId: String) {
}
) { currentTabIndex ->
saveableStateHolder.SaveableStateProvider(key = currentTabIndex) {
PlaylistSongList(browseId = browseId)
when (currentTabIndex) {
0 -> PlaylistSongList(browseId = browseId)
}
}
}
}

View File

@@ -39,7 +39,7 @@ import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.Playlist
import it.vfsfitvnm.vimusic.models.SongPlaylistMap
import it.vfsfitvnm.vimusic.savers.YouTubePlaylistOrAlbumSaver
import it.vfsfitvnm.vimusic.savers.InnertubePlaylistOrAlbumPageSaver
import it.vfsfitvnm.vimusic.savers.resultSaver
import it.vfsfitvnm.vimusic.transaction
import it.vfsfitvnm.vimusic.ui.components.themed.Header
@@ -58,11 +58,12 @@ import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.enqueue
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.produceSaveableOneShotState
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
import it.vfsfitvnm.youtubemusic.requests.playlistPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
@@ -76,12 +77,13 @@ fun PlaylistSongList(
val binder = LocalPlayerServiceBinder.current
val context = LocalContext.current
val playlistResult by produceSaveableOneShotState(
val playlistPageResult by produceSaveableOneShotState(
initialValue = null,
stateSaver = resultSaver(YouTubePlaylistOrAlbumSaver),
stateSaver = resultSaver(InnertubePlaylistOrAlbumPageSaver),
) {
value = withContext(Dispatchers.IO) {
YouTube.playlist(browseId)?.map { it.next() }
// TODO: fetch all songs!
Innertube.playlistPage(BrowseBody(browseId = browseId))
}
}
@@ -102,7 +104,7 @@ fun PlaylistSongList(
val songThumbnailSizeDp = Dimensions.thumbnails.song
val songThumbnailSizePx = songThumbnailSizeDp.px
playlistResult?.getOrNull()?.let { playlist ->
playlistPageResult?.getOrNull()?.let { playlist ->
LazyColumn(
contentPadding = LocalPlayerAwarePaddingValues.current,
modifier = Modifier
@@ -117,9 +119,9 @@ fun PlaylistSongList(
Header(title = playlist.title ?: "Unknown") {
SecondaryTextButton(
text = "Enqueue",
isEnabled = playlist.songs?.isNotEmpty() == true,
isEnabled = playlist.songsPage?.items?.isNotEmpty() == true,
onClick = {
playlist.songs?.map(YouTube.Item.Song::asMediaItem)?.let { mediaItems ->
playlist.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
binder?.player?.enqueue(mediaItems)
}
}
@@ -147,8 +149,8 @@ fun PlaylistSongList(
)
)
playlist.songs
?.map(YouTube.Item.Song::asMediaItem)
playlist.songsPage?.items
?.map(Innertube.SongItem::asMediaItem)
?.onEach(Database::insert)
?.mapIndexed { index, mediaItem ->
SongPlaylistMap(
@@ -196,13 +198,13 @@ fun PlaylistSongList(
}
}
itemsIndexed(items = playlist.songs ?: emptyList()) { index, song ->
itemsIndexed(items = playlist.songsPage?.items ?: emptyList()) { index, song ->
SongItem(
title = song.info?.name,
authors = (song.authors ?: playlist.authors)?.joinToString("") { it.name ?: "" },
durationText = song.durationText,
onClick = {
playlist.songs?.map(YouTube.Item.Song::asMediaItem)?.let { mediaItems ->
playlist.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
binder?.stopRadio()
binder?.player?.forcePlayAtIndex(mediaItems, index)
}
@@ -226,15 +228,15 @@ fun PlaylistSongList(
PrimaryButton(
iconId = R.drawable.shuffle,
isEnabled = playlist.songs?.isNotEmpty() == true,
isEnabled = playlist.songsPage?.items?.isNotEmpty() == true,
onClick = {
playlist.songs?.map(YouTube.Item.Song::asMediaItem)?.let { mediaItems ->
playlist.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(mediaItems.shuffled())
}
}
)
} ?: playlistResult?.exceptionOrNull()?.let {
} ?: playlistPageResult?.exceptionOrNull()?.let {
Box(
modifier = Modifier
.align(Alignment.Center)

View File

@@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.autoSaver
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.paint
@@ -41,7 +42,7 @@ import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.SearchQuery
import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.savers.SearchQueryListSaver
import it.vfsfitvnm.vimusic.savers.StringListResultSaver
import it.vfsfitvnm.vimusic.savers.resultSaver
import it.vfsfitvnm.vimusic.ui.components.themed.Header
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
@@ -51,7 +52,9 @@ import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.produceSaveableOneShotState
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.SearchSuggestionsBody
import it.vfsfitvnm.youtubemusic.requests.searchSuggestions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -80,11 +83,11 @@ fun OnlineSearch(
val suggestionsResult by produceSaveableOneShotState(
initialValue = null,
stateSaver = StringListResultSaver,
stateSaver = resultSaver(autoSaver<List<String>?>()),
key1 = textFieldValue.text
) {
if (textFieldValue.text.isNotEmpty()) {
value = YouTube.getSearchSuggestions(textFieldValue.text)
value = Innertube.searchSuggestions(SearchSuggestionsBody(input = textFieldValue.text))
}
}

View File

@@ -14,6 +14,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.autoSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
@@ -23,23 +24,28 @@ import androidx.compose.ui.input.pointer.pointerInput
import com.valentinilk.shimmer.shimmer
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
import it.vfsfitvnm.vimusic.savers.ListSaver
import it.vfsfitvnm.vimusic.savers.StringResultSaver
import it.vfsfitvnm.vimusic.savers.resultSaver
import it.vfsfitvnm.vimusic.ui.components.themed.Header
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.produceSaveableRelaunchableOneShotState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.MusicShelfRenderer
import it.vfsfitvnm.youtubemusic.models.bodies.ContinuationBody
import it.vfsfitvnm.youtubemusic.models.bodies.SearchBody
import it.vfsfitvnm.youtubemusic.requests.searchPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ExperimentalAnimationApi
@Composable
inline fun <T : YouTube.Item> SearchResult(
inline fun <T : Innertube.Item> SearchResult(
query: String,
filter: String,
stateSaver: ListSaver<T, List<Any?>>,
noinline fromMusicShelfRendererContent: (MusicShelfRenderer.Content) -> T?,
crossinline onSearchAgain: () -> Unit,
crossinline itemContent: @Composable LazyItemScope.(T) -> Unit,
noinline itemShimmer: @Composable BoxScope.() -> Unit,
@@ -52,21 +58,30 @@ inline fun <T : YouTube.Item> SearchResult(
val (continuationResultState, fetch) = produceSaveableRelaunchableOneShotState(
initialValue = null,
stateSaver = StringResultSaver
stateSaver = resultSaver(autoSaver<String?>())
) {
val token = value?.getOrNull()
value = null
value = withContext(Dispatchers.IO) {
YouTube.search(query, filter, token)
}?.map { searchResult ->
@Suppress("UNCHECKED_CAST")
(searchResult.items as List<T>?)?.let {
items = items.plus(it).distinctBy(YouTube.Item::key)
if (token == null) {
Innertube.searchPage(
body = SearchBody(query = query, params = filter),
fromMusicShelfRendererContent = fromMusicShelfRendererContent
)
} else {
Innertube.searchPage(
body = ContinuationBody(continuation = token),
fromMusicShelfRendererContent = fromMusicShelfRendererContent
)
}
}?.map { itemsPage ->
itemsPage?.items?.let {
items = items.plus(it).distinctBy(Innertube.Item::key)
}
searchResult.continuation
itemsPage?.continuation
}
}
@@ -94,7 +109,7 @@ inline fun <T : YouTube.Item> SearchResult(
items(
items = items,
key = YouTube.Item::key,
key = Innertube.Item::key,
itemContent = itemContent
)

View File

@@ -13,16 +13,15 @@ import androidx.compose.ui.unit.dp
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.savers.YouTubeAlbumListSaver
import it.vfsfitvnm.vimusic.savers.YouTubeArtistListSaver
import it.vfsfitvnm.vimusic.savers.YouTubePlaylistListSaver
import it.vfsfitvnm.vimusic.savers.YouTubeSongListSaver
import it.vfsfitvnm.vimusic.savers.YouTubeVideoListSaver
import it.vfsfitvnm.vimusic.savers.InnertubeAlbumItemListSaver
import it.vfsfitvnm.vimusic.savers.InnertubeArtistItemListSaver
import it.vfsfitvnm.vimusic.savers.InnertubePlaylistItemListSaver
import it.vfsfitvnm.vimusic.savers.InnertubeSongItemListSaver
import it.vfsfitvnm.vimusic.savers.InnertubeVideoItemListSaver
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
import it.vfsfitvnm.vimusic.ui.screens.albumRoute
import it.vfsfitvnm.vimusic.ui.screens.artistRoute
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
import it.vfsfitvnm.vimusic.ui.screens.playlist.PlaylistScreen
import it.vfsfitvnm.vimusic.ui.screens.playlistRoute
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
import it.vfsfitvnm.vimusic.ui.styling.px
@@ -40,7 +39,8 @@ import it.vfsfitvnm.vimusic.utils.asMediaItem
import it.vfsfitvnm.vimusic.utils.forcePlay
import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.vimusic.utils.searchResultScreenTabIndexKey
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.utils.from
@ExperimentalFoundationApi
@ExperimentalAnimationApi
@@ -52,12 +52,6 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
RouteHandler(listenToGlobalEmitter = true) {
globalRoutes()
playlistRoute { browseId ->
PlaylistScreen(
browseId = browseId ?: "browseId cannot be null"
)
}
host {
Scaffold(
topIconButtonId = R.drawable.chevron_back,
@@ -74,12 +68,12 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
}
) { tabIndex ->
val searchFilter = when (tabIndex) {
0 -> YouTube.Item.Song.Filter
1 -> YouTube.Item.Album.Filter
2 -> YouTube.Item.Artist.Filter
3 -> YouTube.Item.Video.Filter
4 -> YouTube.Item.CommunityPlaylist.Filter
5 -> YouTube.Item.FeaturedPlaylist.Filter
0 -> Innertube.SearchFilter.Song
1 -> Innertube.SearchFilter.Album
2 -> Innertube.SearchFilter.Artist
3 -> Innertube.SearchFilter.Video
4 -> Innertube.SearchFilter.CommunityPlaylist
5 -> Innertube.SearchFilter.FeaturedPlaylist
else -> error("unreachable")
}.value
@@ -94,7 +88,8 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
query = query,
filter = searchFilter,
onSearchAgain = onSearchAgain,
stateSaver = YouTubeSongListSaver,
stateSaver = InnertubeSongItemListSaver,
fromMusicShelfRendererContent = Innertube.SongItem.Companion::from,
itemContent = { song ->
SmallSongItem(
song = song,
@@ -119,8 +114,9 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
SearchResult(
query = query,
filter = searchFilter,
stateSaver = YouTubeAlbumListSaver,
stateSaver = InnertubeAlbumItemListSaver,
onSearchAgain = onSearchAgain,
fromMusicShelfRendererContent = Innertube.AlbumItem.Companion::from,
itemContent = { album ->
AlbumItem(
album = album,
@@ -148,8 +144,9 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
SearchResult(
query = query,
filter = searchFilter,
stateSaver = YouTubeArtistListSaver,
stateSaver = InnertubeArtistItemListSaver,
onSearchAgain = onSearchAgain,
fromMusicShelfRendererContent = Innertube.ArtistItem.Companion::from,
itemContent = { artist ->
ArtistItem(
artist = artist,
@@ -176,8 +173,9 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
SearchResult(
query = query,
filter = searchFilter,
stateSaver = YouTubeVideoListSaver,
stateSaver = InnertubeVideoItemListSaver,
onSearchAgain = onSearchAgain,
fromMusicShelfRendererContent = Innertube.VideoItem.Companion::from,
itemContent = { video ->
VideoItem(
video = video,
@@ -206,8 +204,9 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
SearchResult(
query = query,
filter = searchFilter,
stateSaver = YouTubePlaylistListSaver,
stateSaver = InnertubePlaylistItemListSaver,
onSearchAgain = onSearchAgain,
fromMusicShelfRendererContent = Innertube.PlaylistItem.Companion::from,
itemContent = { playlist ->
PlaylistItem(
playlist = playlist,

View File

@@ -41,7 +41,7 @@ import it.vfsfitvnm.vimusic.utils.color
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
@Composable
fun SmallSongItemShimmer(
@@ -73,7 +73,7 @@ fun SmallSongItemShimmer(
@ExperimentalAnimationApi
@Composable
fun SmallSongItem(
song: YouTube.Item.Song,
song: Innertube.SongItem,
thumbnailSizePx: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier
@@ -95,7 +95,7 @@ fun SmallSongItem(
@ExperimentalAnimationApi
@Composable
fun VideoItem(
video: YouTube.Item.Video,
video: Innertube.VideoItem,
thumbnailHeightDp: Dp,
thumbnailWidthDp: Dp,
onClick: () -> Unit,
@@ -212,7 +212,7 @@ fun VideoItemShimmer(
@Composable
fun PlaylistItem(
playlist: YouTube.Item.Playlist,
playlist: Innertube.PlaylistItem,
thumbnailSizePx: Int,
thumbnailSizeDp: Dp,
modifier: Modifier = Modifier,
@@ -298,7 +298,7 @@ fun PlaylistItemShimmer(
@Composable
fun AlbumItem(
album: YouTube.Item.Album,
album: Innertube.AlbumItem,
thumbnailSizePx: Int,
thumbnailSizeDp: Dp,
modifier: Modifier = Modifier,
@@ -383,7 +383,7 @@ fun AlbumItemShimmer(
@Composable
fun AlternativeAlbumItem(
album: YouTube.Item.Album,
album: Innertube.AlbumItem,
thumbnailSizePx: Int,
thumbnailSizeDp: Dp,
modifier: Modifier = Modifier,
@@ -452,7 +452,7 @@ fun AlternativeAlbumItemPlaceholder(
@Composable
fun ArtistItem(
artist: YouTube.Item.Artist,
artist: Innertube.ArtistItem,
thumbnailSizePx: Int,
thumbnailSizeDp: Dp,
modifier: Modifier = Modifier,

View File

@@ -6,9 +6,9 @@ import androidx.core.os.bundleOf
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
val YouTube.Item.Song.asMediaItem: MediaItem
val Innertube.SongItem.asMediaItem: MediaItem
get() = MediaItem.Builder()
.setMediaId(key)
.setUri(key)
@@ -32,7 +32,7 @@ val YouTube.Item.Song.asMediaItem: MediaItem
)
.build()
val YouTube.Item.Video.asMediaItem: MediaItem
val Innertube.VideoItem.asMediaItem: MediaItem
get() = MediaItem.Builder()
.setMediaId(key)
.setUri(key)

View File

@@ -1,7 +1,10 @@
package it.vfsfitvnm.vimusic.utils
import androidx.media3.common.MediaItem
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.ContinuationBody
import it.vfsfitvnm.youtubemusic.models.bodies.NextBody
import it.vfsfitvnm.youtubemusic.requests.nextPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -17,20 +20,30 @@ data class YouTubeRadio(
var mediaItems: List<MediaItem>? = null
nextContinuation = withContext(Dispatchers.IO) {
YouTube.next(
videoId = videoId,
playlistId = playlistId,
params = parameters,
playlistSetVideoId = playlistSetVideoId,
continuation = nextContinuation
)?.getOrNull()?.let { nextResult ->
playlistId = nextResult.playlistId
parameters = nextResult.params
playlistSetVideoId = nextResult.playlistSetVideoId
val continuation = nextContinuation
mediaItems = nextResult.items?.map(YouTube.Item.Song::asMediaItem)
nextResult.continuation?.takeUnless { nextContinuation == nextResult.continuation }
if (continuation == null) {
Innertube.nextPage(
NextBody(
videoId = videoId,
playlistId = playlistId,
params = parameters,
playlistSetVideoId = playlistSetVideoId
)
)?.map { nextResult ->
playlistId = nextResult.playlistId
parameters = nextResult.params
playlistSetVideoId = nextResult.playlistSetVideoId
nextResult.itemsPage
}
} else {
Innertube.nextPage(ContinuationBody(continuation = continuation))
}?.getOrNull()?.let { songsPage ->
mediaItems = songsPage.items?.map(Innertube.SongItem::asMediaItem)
songsPage.continuation?.takeUnless { nextContinuation == it }
}
}
return mediaItems ?: emptyList()