From 14f46429ef6616081d52e732cac0eac6e5b37152 Mon Sep 17 00:00:00 2001 From: vfsfitvnm Date: Fri, 1 Jul 2022 19:32:58 +0200 Subject: [PATCH] Continue removing Outcome class in favor of Result (search) --- .../vimusic/ui/screens/IntentUriScreen.kt | 3 - .../vimusic/ui/screens/SearchResultScreen.kt | 147 +++++++++--------- .../vimusic/ui/views/CurrentPlaylistView.kt | 12 +- .../it/vfsfitvnm/youtubemusic/YouTube.kt | 84 ++++------ 4 files changed, 111 insertions(+), 135 deletions(-) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/IntentUriScreen.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/IntentUriScreen.kt index e42bbe7..48f5ca1 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/IntentUriScreen.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/IntentUriScreen.kt @@ -69,8 +69,6 @@ fun IntentUriScreen(uri: Uri) { val density = LocalDensity.current val binder = LocalPlayerServiceBinder.current - val shimmer = rememberShimmer(shimmerBounds = ShimmerBounds.Window) - var items by remember(uri) { mutableStateOf>>(Outcome.Loading) } @@ -206,7 +204,6 @@ fun IntentUriScreen(uri: Uri) { } is Outcome.Loading, is Outcome.Initial -> items(count = 5) { index -> SmallSongItemShimmer( - shimmer = shimmer, thumbnailSizeDp = 54.dp, modifier = Modifier .alpha(1f - index * 0.175f) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/SearchResultScreen.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/SearchResultScreen.kt index e2138db..b65ee97 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/SearchResultScreen.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/SearchResultScreen.kt @@ -26,22 +26,21 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import coil.compose.AsyncImage -import com.valentinilk.shimmer.Shimmer -import com.valentinilk.shimmer.ShimmerBounds -import com.valentinilk.shimmer.rememberShimmer -import com.valentinilk.shimmer.shimmer import it.vfsfitvnm.route.RouteHandler import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder import it.vfsfitvnm.vimusic.R import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness -import it.vfsfitvnm.vimusic.ui.components.* +import it.vfsfitvnm.vimusic.ui.components.ChipGroup +import it.vfsfitvnm.vimusic.ui.components.ChipItem +import it.vfsfitvnm.vimusic.ui.components.TopAppBar +import it.vfsfitvnm.vimusic.ui.components.themed.LoadingOrError import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu +import it.vfsfitvnm.vimusic.ui.components.themed.TextCard import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette import it.vfsfitvnm.vimusic.ui.styling.LocalTypography import it.vfsfitvnm.vimusic.ui.views.SongItem import it.vfsfitvnm.vimusic.utils.* -import it.vfsfitvnm.youtubemusic.Outcome import it.vfsfitvnm.youtubemusic.YouTube import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -61,26 +60,26 @@ fun SearchResultScreen( val lazyListState = rememberLazyListState() - var continuation by remember(preferences.searchFilter) { - mutableStateOf>(Outcome.Initial) - } - val items = remember(preferences.searchFilter) { mutableStateListOf() } + var continuationResult by remember(preferences.searchFilter) { + mutableStateOf?>(null) + } + val onLoad = relaunchableEffect(preferences.searchFilter) { withContext(Dispatchers.Main) { - val token = continuation.valueOrNull + val token = continuationResult?.getOrNull() - continuation = Outcome.Loading + continuationResult = null - continuation = withContext(Dispatchers.IO) { + continuationResult = withContext(Dispatchers.IO) { YouTube.search(query, preferences.searchFilter, token) - }.map { searchResult -> + }?.map { searchResult -> items.addAll(searchResult.items) searchResult.continuation - }.recoverWith(token) + } } } @@ -116,8 +115,6 @@ fun SearchResultScreen( } host { - val shimmer = rememberShimmer(shimmerBounds = ShimmerBounds.Window) - LazyColumn( state = lazyListState, horizontalAlignment = Alignment.CenterHorizontally, @@ -237,63 +234,35 @@ fun SearchResultScreen( ) } - when (val currentResult = continuation) { - is Outcome.Error -> item { - Error( - error = currentResult, - onRetry = onLoad, - modifier = Modifier - .padding(vertical = 16.dp) - ) - } - is Outcome.Recovered -> item { - Error( - error = currentResult.error, - onRetry = onLoad, - modifier = Modifier - .padding(vertical = 16.dp) - ) - } - is Outcome.Success -> { - if (items.isEmpty()) { - item { - Message( - text = "No results found", - modifier = Modifier - ) - } + continuationResult?.getOrNull()?.let { + if (items.isNotEmpty()) { + item { + SideEffect(onLoad) } - - if (currentResult.value != null) { - item { - SideEffect(onLoad) + } + } ?: continuationResult?.exceptionOrNull()?.let { throwable -> + item { + LoadingOrError( + errorMessage = throwable.javaClass.canonicalName, + onRetry = onLoad + ) + } + } ?: continuationResult?.let { + if (items.isEmpty()) { + item { + TextCard( + icon = R.drawable.sad + ) { + Title(text = "No results found") + Text(text = "Please try a different query or category.") } } } - else -> {} - } - - if (continuation is Outcome.Loading || (continuation is Outcome.Success && continuation.valueOrNull != null)) { - items(count = if (items.isEmpty()) 8 else 3, key = { it }) { index -> - when (preferences.searchFilter) { - YouTube.Item.Artist.Filter.value -> SmallArtistItemShimmer( - shimmer = shimmer, - thumbnailSizeDp = 54.dp, - modifier = Modifier - .alpha(1f - index * 0.125f) - .fillMaxWidth() - .padding(vertical = 4.dp, horizontal = 16.dp) - ) - else -> SmallSongItemShimmer( - shimmer = shimmer, - thumbnailSizeDp = 54.dp, - modifier = Modifier - .alpha(1f - index * 0.125f) - .fillMaxWidth() - .padding(vertical = 4.dp, horizontal = 16.dp) - ) - } - } + } ?: item(key = "loading") { + LoadingOrError( + itemCount = if (items.isEmpty()) 8 else 3, + isLoadingArtists = preferences.searchFilter == YouTube.Item.Artist.Filter.value + ) } } } @@ -302,7 +271,6 @@ fun SearchResultScreen( @Composable fun SmallSongItemShimmer( - shimmer: Shimmer, thumbnailSizeDp: Dp, modifier: Modifier = Modifier ) { @@ -312,7 +280,6 @@ fun SmallSongItemShimmer( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = modifier - .shimmer(shimmer) ) { Spacer( modifier = Modifier @@ -329,7 +296,6 @@ fun SmallSongItemShimmer( @Composable fun SmallArtistItemShimmer( - shimmer: Shimmer, thumbnailSizeDp: Dp, modifier: Modifier = Modifier ) { @@ -339,7 +305,6 @@ fun SmallArtistItemShimmer( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = modifier - .shimmer(shimmer) ) { Spacer( modifier = Modifier @@ -579,3 +544,37 @@ fun SmallArtistItem( ) } } + +@Composable +private fun LoadingOrError( + itemCount: Int = 0, + isLoadingArtists: Boolean = false, + errorMessage: String? = null, + onRetry: (() -> Unit)? = null +) { + LoadingOrError( + errorMessage = errorMessage, + onRetry = onRetry, + horizontalAlignment = Alignment.CenterHorizontally + ) { + repeat(itemCount) { index -> + if (isLoadingArtists) { + SmallArtistItemShimmer( + thumbnailSizeDp = 54.dp, + modifier = Modifier + .alpha(1f - index * 0.125f) + .fillMaxWidth() + .padding(vertical = 4.dp, horizontal = 16.dp) + ) + } else { + SmallSongItemShimmer( + thumbnailSizeDp = 54.dp, + modifier = Modifier + .alpha(1f - index * 0.125f) + .fillMaxWidth() + .padding(vertical = 4.dp, horizontal = 16.dp) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/views/CurrentPlaylistView.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/views/CurrentPlaylistView.kt index dfd9b99..023a9b6 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/views/CurrentPlaylistView.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/views/CurrentPlaylistView.kt @@ -26,8 +26,7 @@ import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.media3.common.Player -import com.valentinilk.shimmer.ShimmerBounds -import com.valentinilk.shimmer.rememberShimmer +import com.valentinilk.shimmer.shimmer import it.vfsfitvnm.reordering.rememberReorderingState import it.vfsfitvnm.reordering.verticalDragAfterLongPressToReorder import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder @@ -132,7 +131,6 @@ fun CurrentPlaylistView( } else { MusicBars( color = LightColorPalette.background, -// shape = RectangleShape, modifier = Modifier .height(24.dp) ) @@ -159,12 +157,12 @@ fun CurrentPlaylistView( item { if (binder?.isLoadingRadio == true) { - val shimmer = rememberShimmer(shimmerBounds = ShimmerBounds.Window) - - Column { + Column( + modifier = Modifier + .shimmer() + ) { repeat(3) { index -> SmallSongItemShimmer( - shimmer = shimmer, thumbnailSizeDp = 54.dp, modifier = Modifier .alpha(1f - index * 0.125f) diff --git a/youtube-music/src/main/kotlin/it/vfsfitvnm/youtubemusic/YouTube.kt b/youtube-music/src/main/kotlin/it/vfsfitvnm/youtubemusic/YouTube.kt index b1cd3aa..1630390 100644 --- a/youtube-music/src/main/kotlin/it/vfsfitvnm/youtubemusic/YouTube.kt +++ b/youtube-music/src/main/kotlin/it/vfsfitvnm/youtubemusic/YouTube.kt @@ -364,43 +364,39 @@ object YouTube { query: String, filter: String, continuation: String? - ): Outcome { - return client.postCatching("/youtubei/v1/search") { - contentType(ContentType.Application.Json) - setBody( - SearchBody( - context = Context.DefaultWeb, - query = query, - params = filter + ): Result? { + return runCatching { + val musicShelfRenderer = client.post("/youtubei/v1/search") { + contentType(ContentType.Application.Json) + setBody( + SearchBody( + context = Context.DefaultWeb, + query = query, + params = filter + ) ) - ) - parameter("key", Key) - parameter("prettyPrint", false) - parameter("continuation", continuation) - }.flatMap { response -> - if (continuation == null) { - response.bodyCatching() - .map { body -> - body - .contents - .tabbedSearchResultsRenderer - .tabs - .firstOrNull() - ?.tabRenderer - ?.content - ?.sectionListRenderer - ?.contents - ?.lastOrNull() - ?.musicShelfRenderer - } - } else { - response.bodyCatching().map { body -> - body + parameter("key", Key) + parameter("prettyPrint", false) + parameter("continuation", continuation) + }.let { response -> + if (continuation == null) { + response.body() + .contents + .tabbedSearchResultsRenderer + .tabs + .firstOrNull() + ?.tabRenderer + ?.content + ?.sectionListRenderer + ?.contents + ?.lastOrNull() + ?.musicShelfRenderer + } else { + response.body() .continuationContents .musicShelfContinuation } } - }.map { musicShelfRenderer -> SearchResult( items = musicShelfRenderer ?.contents @@ -421,7 +417,7 @@ object YouTube { ?.nextRadioContinuationData ?.continuation ) - } + }.recoverIfCancelled() } suspend fun getSearchSuggestions(input: String): Outcome?> { @@ -657,7 +653,7 @@ object YouTube { return if (browseId == null) { Result.success(null) } else { - browse2(browseId)?.map { body -> + browse(browseId)?.map { body -> body.contents .sectionListRenderer ?.contents @@ -675,21 +671,7 @@ object YouTube { ) } - suspend fun browse(browseId: String): Outcome { - return client.postCatching("/youtubei/v1/browse") { - contentType(ContentType.Application.Json) - setBody( - BrowseBody( - browseId = browseId, - context = Context.DefaultWeb - ) - ) - parameter("key", Key) - parameter("prettyPrint", false) - }.bodyCatching() - } - - suspend fun browse2(browseId: String): Result? { + suspend fun browse(browseId: String): Result? { return runCatching { client.post("/youtubei/v1/browse") { contentType(ContentType.Application.Json) @@ -724,7 +706,7 @@ object YouTube { } suspend fun playlistOrAlbum(browseId: String): Result? { - return browse2(browseId)?.map { body -> + return browse(browseId)?.map { body -> PlaylistOrAlbum( title = body .header @@ -839,7 +821,7 @@ object YouTube { ) suspend fun artist(browseId: String): Result? { - return browse2(browseId)?.map { body -> + return browse(browseId)?.map { body -> Artist( name = body .header