Continue removing Outcome class in favor of Result (search)

This commit is contained in:
vfsfitvnm
2022-07-01 19:32:58 +02:00
parent 21ef7e8d5e
commit 14f46429ef
4 changed files with 111 additions and 135 deletions

View File

@@ -69,8 +69,6 @@ fun IntentUriScreen(uri: Uri) {
val density = LocalDensity.current val density = LocalDensity.current
val binder = LocalPlayerServiceBinder.current val binder = LocalPlayerServiceBinder.current
val shimmer = rememberShimmer(shimmerBounds = ShimmerBounds.Window)
var items by remember(uri) { var items by remember(uri) {
mutableStateOf<Outcome<List<YouTube.Item.Song>>>(Outcome.Loading) mutableStateOf<Outcome<List<YouTube.Item.Song>>>(Outcome.Loading)
} }
@@ -206,7 +204,6 @@ fun IntentUriScreen(uri: Uri) {
} }
is Outcome.Loading, is Outcome.Initial -> items(count = 5) { index -> is Outcome.Loading, is Outcome.Initial -> items(count = 5) { index ->
SmallSongItemShimmer( SmallSongItemShimmer(
shimmer = shimmer,
thumbnailSizeDp = 54.dp, thumbnailSizeDp = 54.dp,
modifier = Modifier modifier = Modifier
.alpha(1f - index * 0.175f) .alpha(1f - index * 0.175f)

View File

@@ -26,22 +26,21 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage 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.route.RouteHandler
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness 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.NonQueuedMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.TextCard
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
import it.vfsfitvnm.vimusic.ui.views.SongItem import it.vfsfitvnm.vimusic.ui.views.SongItem
import it.vfsfitvnm.vimusic.utils.* import it.vfsfitvnm.vimusic.utils.*
import it.vfsfitvnm.youtubemusic.Outcome
import it.vfsfitvnm.youtubemusic.YouTube import it.vfsfitvnm.youtubemusic.YouTube
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -61,26 +60,26 @@ fun SearchResultScreen(
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
var continuation by remember(preferences.searchFilter) {
mutableStateOf<Outcome<String?>>(Outcome.Initial)
}
val items = remember(preferences.searchFilter) { val items = remember(preferences.searchFilter) {
mutableStateListOf<YouTube.Item>() mutableStateListOf<YouTube.Item>()
} }
var continuationResult by remember(preferences.searchFilter) {
mutableStateOf<Result<String?>?>(null)
}
val onLoad = relaunchableEffect(preferences.searchFilter) { val onLoad = relaunchableEffect(preferences.searchFilter) {
withContext(Dispatchers.Main) { 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) YouTube.search(query, preferences.searchFilter, token)
}.map { searchResult -> }?.map { searchResult ->
items.addAll(searchResult.items) items.addAll(searchResult.items)
searchResult.continuation searchResult.continuation
}.recoverWith(token) }
} }
} }
@@ -116,8 +115,6 @@ fun SearchResultScreen(
} }
host { host {
val shimmer = rememberShimmer(shimmerBounds = ShimmerBounds.Window)
LazyColumn( LazyColumn(
state = lazyListState, state = lazyListState,
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
@@ -237,63 +234,35 @@ fun SearchResultScreen(
) )
} }
when (val currentResult = continuation) { continuationResult?.getOrNull()?.let {
is Outcome.Error -> item { if (items.isNotEmpty()) {
Error( item {
error = currentResult, SideEffect(onLoad)
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
)
}
} }
}
if (currentResult.value != null) { } ?: continuationResult?.exceptionOrNull()?.let { throwable ->
item { item {
SideEffect(onLoad) 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 -> {} } ?: item(key = "loading") {
} LoadingOrError(
itemCount = if (items.isEmpty()) 8 else 3,
if (continuation is Outcome.Loading || (continuation is Outcome.Success && continuation.valueOrNull != null)) { isLoadingArtists = preferences.searchFilter == YouTube.Item.Artist.Filter.value
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)
)
}
}
} }
} }
} }
@@ -302,7 +271,6 @@ fun SearchResultScreen(
@Composable @Composable
fun SmallSongItemShimmer( fun SmallSongItemShimmer(
shimmer: Shimmer,
thumbnailSizeDp: Dp, thumbnailSizeDp: Dp,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
@@ -312,7 +280,6 @@ fun SmallSongItemShimmer(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier modifier = modifier
.shimmer(shimmer)
) { ) {
Spacer( Spacer(
modifier = Modifier modifier = Modifier
@@ -329,7 +296,6 @@ fun SmallSongItemShimmer(
@Composable @Composable
fun SmallArtistItemShimmer( fun SmallArtistItemShimmer(
shimmer: Shimmer,
thumbnailSizeDp: Dp, thumbnailSizeDp: Dp,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
@@ -339,7 +305,6 @@ fun SmallArtistItemShimmer(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier modifier = modifier
.shimmer(shimmer)
) { ) {
Spacer( Spacer(
modifier = Modifier 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)
)
}
}
}
}

View File

@@ -26,8 +26,7 @@ import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.media3.common.Player import androidx.media3.common.Player
import com.valentinilk.shimmer.ShimmerBounds import com.valentinilk.shimmer.shimmer
import com.valentinilk.shimmer.rememberShimmer
import it.vfsfitvnm.reordering.rememberReorderingState import it.vfsfitvnm.reordering.rememberReorderingState
import it.vfsfitvnm.reordering.verticalDragAfterLongPressToReorder import it.vfsfitvnm.reordering.verticalDragAfterLongPressToReorder
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
@@ -132,7 +131,6 @@ fun CurrentPlaylistView(
} else { } else {
MusicBars( MusicBars(
color = LightColorPalette.background, color = LightColorPalette.background,
// shape = RectangleShape,
modifier = Modifier modifier = Modifier
.height(24.dp) .height(24.dp)
) )
@@ -159,12 +157,12 @@ fun CurrentPlaylistView(
item { item {
if (binder?.isLoadingRadio == true) { if (binder?.isLoadingRadio == true) {
val shimmer = rememberShimmer(shimmerBounds = ShimmerBounds.Window) Column(
modifier = Modifier
Column { .shimmer()
) {
repeat(3) { index -> repeat(3) { index ->
SmallSongItemShimmer( SmallSongItemShimmer(
shimmer = shimmer,
thumbnailSizeDp = 54.dp, thumbnailSizeDp = 54.dp,
modifier = Modifier modifier = Modifier
.alpha(1f - index * 0.125f) .alpha(1f - index * 0.125f)

View File

@@ -364,43 +364,39 @@ object YouTube {
query: String, query: String,
filter: String, filter: String,
continuation: String? continuation: String?
): Outcome<SearchResult> { ): Result<SearchResult>? {
return client.postCatching("/youtubei/v1/search") { return runCatching {
contentType(ContentType.Application.Json) val musicShelfRenderer = client.post("/youtubei/v1/search") {
setBody( contentType(ContentType.Application.Json)
SearchBody( setBody(
context = Context.DefaultWeb, SearchBody(
query = query, context = Context.DefaultWeb,
params = filter query = query,
params = filter
)
) )
) parameter("key", Key)
parameter("key", Key) parameter("prettyPrint", false)
parameter("prettyPrint", false) parameter("continuation", continuation)
parameter("continuation", continuation) }.let { response ->
}.flatMap { response -> if (continuation == null) {
if (continuation == null) { response.body<SearchResponse>()
response.bodyCatching<SearchResponse>() .contents
.map { body -> .tabbedSearchResultsRenderer
body .tabs
.contents .firstOrNull()
.tabbedSearchResultsRenderer ?.tabRenderer
.tabs ?.content
.firstOrNull() ?.sectionListRenderer
?.tabRenderer ?.contents
?.content ?.lastOrNull()
?.sectionListRenderer ?.musicShelfRenderer
?.contents } else {
?.lastOrNull() response.body<ContinuationResponse>()
?.musicShelfRenderer
}
} else {
response.bodyCatching<ContinuationResponse>().map { body ->
body
.continuationContents .continuationContents
.musicShelfContinuation .musicShelfContinuation
} }
} }
}.map { musicShelfRenderer ->
SearchResult( SearchResult(
items = musicShelfRenderer items = musicShelfRenderer
?.contents ?.contents
@@ -421,7 +417,7 @@ object YouTube {
?.nextRadioContinuationData ?.nextRadioContinuationData
?.continuation ?.continuation
) )
} }.recoverIfCancelled()
} }
suspend fun getSearchSuggestions(input: String): Outcome<List<String>?> { suspend fun getSearchSuggestions(input: String): Outcome<List<String>?> {
@@ -657,7 +653,7 @@ object YouTube {
return if (browseId == null) { return if (browseId == null) {
Result.success(null) Result.success(null)
} else { } else {
browse2(browseId)?.map { body -> browse(browseId)?.map { body ->
body.contents body.contents
.sectionListRenderer .sectionListRenderer
?.contents ?.contents
@@ -675,21 +671,7 @@ object YouTube {
) )
} }
suspend fun browse(browseId: String): Outcome<BrowseResponse> { suspend fun browse(browseId: String): Result<BrowseResponse>? {
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<BrowseResponse>? {
return runCatching<YouTube, BrowseResponse> { return runCatching<YouTube, BrowseResponse> {
client.post("/youtubei/v1/browse") { client.post("/youtubei/v1/browse") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
@@ -724,7 +706,7 @@ object YouTube {
} }
suspend fun playlistOrAlbum(browseId: String): Result<PlaylistOrAlbum>? { suspend fun playlistOrAlbum(browseId: String): Result<PlaylistOrAlbum>? {
return browse2(browseId)?.map { body -> return browse(browseId)?.map { body ->
PlaylistOrAlbum( PlaylistOrAlbum(
title = body title = body
.header .header
@@ -839,7 +821,7 @@ object YouTube {
) )
suspend fun artist(browseId: String): Result<Artist>? { suspend fun artist(browseId: String): Result<Artist>? {
return browse2(browseId)?.map { body -> return browse(browseId)?.map { body ->
Artist( Artist(
name = body name = body
.header .header