Continue removing Outcome class in favor of Result (search)
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user