Start removing Outcome class in favor of Result

This commit is contained in:
vfsfitvnm
2022-07-01 18:22:53 +02:00
parent 6ccbf7759c
commit c4fea8835a
5 changed files with 155 additions and 157 deletions

View File

@@ -17,17 +17,16 @@ import androidx.compose.ui.unit.dp
import com.valentinilk.shimmer.shimmer import com.valentinilk.shimmer.shimmer
import it.vfsfitvnm.vimusic.R import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.ui.components.Message import it.vfsfitvnm.vimusic.ui.components.Message
import it.vfsfitvnm.vimusic.ui.components.OutcomeItem
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
import it.vfsfitvnm.vimusic.utils.center import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.secondary import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.youtubemusic.Outcome
@Composable @Composable
fun LyricsView( fun LyricsView(
lyricsOutcome: Outcome<String>, lyrics: String?,
onInitialize: () -> Unit, onInitialize: () -> Unit,
onSearchOnline: () -> Unit, onSearchOnline: () -> Unit,
onLyricsUpdate: (String) -> Unit, onLyricsUpdate: (String) -> Unit,
@@ -42,7 +41,7 @@ fun LyricsView(
if (isEditingLyrics) { if (isEditingLyrics) {
TextFieldDialog( TextFieldDialog(
hintText = "Enter the lyrics", hintText = "Enter the lyrics",
initialTextInput = lyricsOutcome.valueOrNull ?: "", initialTextInput = lyrics ?: "",
singleLine = false, singleLine = false,
maxLines = 10, maxLines = 10,
isTextInputValid = { true }, isTextInputValid = { true },
@@ -53,26 +52,7 @@ fun LyricsView(
) )
} }
OutcomeItem( if (lyrics != null ) {
outcome = lyricsOutcome,
onInitialize = onInitialize,
onLoading = {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.shimmer()
) {
repeat(16) { index ->
TextPlaceholder(
modifier = Modifier
.alpha(1f - index * 0.05f)
)
}
}
}
) { lyrics ->
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier modifier = Modifier
@@ -125,5 +105,22 @@ fun LyricsView(
) )
} }
} }
} else {
SideEffect(onInitialize)
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.shimmer()
) {
repeat(16) { index ->
TextPlaceholder(
modifier = Modifier
.alpha(1f - index * 0.05f)
)
}
}
} }
} }

View File

@@ -34,11 +34,11 @@ import it.vfsfitvnm.vimusic.ui.components.BottomSheetState
import it.vfsfitvnm.vimusic.ui.screens.rememberLyricsRoute import it.vfsfitvnm.vimusic.ui.screens.rememberLyricsRoute
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.utils.* import it.vfsfitvnm.vimusic.utils.PlayerState
import it.vfsfitvnm.youtubemusic.Outcome import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.color
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.youtubemusic.YouTube import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.isEvaluable
import it.vfsfitvnm.youtubemusic.toNotNull
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -62,8 +62,8 @@ fun PlayerBottomSheet(
var route by rememberRoute() var route by rememberRoute()
var nextOutcome by remember(playerState?.mediaItem?.mediaId) { var nextResult by remember(playerState?.mediaItem?.mediaId) {
mutableStateOf<Outcome<YouTube.NextResult>>(Outcome.Initial) mutableStateOf<Result<YouTube.NextResult>?>(null)
} }
BottomSheet( BottomSheet(
@@ -150,8 +150,8 @@ fun PlayerBottomSheet(
} }
} }
) { ) {
var lyricsOutcome by remember(song) { var lyricsResult by remember(song) {
mutableStateOf(song?.lyrics?.let { Outcome.Success(it) } ?: Outcome.Initial) mutableStateOf(song?.lyrics?.let { Result.success(it) })
} }
RouteHandler( RouteHandler(
@@ -181,21 +181,16 @@ fun PlayerBottomSheet(
val context = LocalContext.current val context = LocalContext.current
LyricsView( LyricsView(
lyricsOutcome = lyricsOutcome, lyrics = lyricsResult?.getOrNull(),
nestedScrollConnectionProvider = layoutState::nestedScrollConnection, nestedScrollConnectionProvider = layoutState::nestedScrollConnection,
onInitialize = { onInitialize = {
coroutineScope.launch(Dispatchers.Main) { coroutineScope.launch(Dispatchers.Main) {
lyricsOutcome = Outcome.Loading
val mediaItem = player?.currentMediaItem!! val mediaItem = player?.currentMediaItem!!
if (nextOutcome.isEvaluable) { if (nextResult == null) {
nextOutcome = Outcome.Loading
val mediaItemIndex = player.currentMediaItemIndex val mediaItemIndex = player.currentMediaItemIndex
nextOutcome = withContext(Dispatchers.IO) { nextResult = withContext(Dispatchers.IO) {
YouTube.next( YouTube.next(
mediaItem.mediaId, mediaItem.mediaId,
mediaItem.mediaMetadata.extras?.getString("playlistId"), mediaItem.mediaMetadata.extras?.getString("playlistId"),
@@ -204,11 +199,9 @@ fun PlayerBottomSheet(
} }
} }
lyricsOutcome = nextOutcome.flatMap { lyricsResult = nextResult?.map { nextResult ->
it.lyrics?.text().toNotNull() nextResult.lyrics?.text()?.getOrNull() ?: ""
}.map { lyrics -> }?.map { lyrics ->
lyrics ?: ""
}.map { lyrics ->
query { query {
song?.let { song?.let {
Database.update(song.copy(lyrics = lyrics)) Database.update(song.copy(lyrics = lyrics))

View File

@@ -5,6 +5,7 @@ import it.vfsfitvnm.youtubemusic.YouTube
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
data class YouTubeRadio( data class YouTubeRadio(
private val videoId: String? = null, private val videoId: String? = null,
private val playlistId: String? = null, private val playlistId: String? = null,
@@ -23,11 +24,11 @@ data class YouTubeRadio(
params = parameters, params = parameters,
playlistSetVideoId = playlistSetVideoId, playlistSetVideoId = playlistSetVideoId,
continuation = nextContinuation continuation = nextContinuation
) )?.getOrNull()?.let { nextResult ->
}.map { nextResult -> mediaItems = nextResult.items?.map(YouTube.Item.Song::asMediaItem)
mediaItems = nextResult.items?.map(YouTube.Item.Song::asMediaItem) nextResult.continuation?.takeUnless { nextContinuation == nextResult.continuation }
nextResult.continuation?.takeUnless { nextContinuation == nextResult.continuation } }
}.recoverWith(nextContinuation).valueOrNull }
return mediaItems ?: emptyList() return mediaItems ?: emptyList()
} }

View File

@@ -8,6 +8,13 @@ import io.ktor.util.network.*
import io.ktor.utils.io.* import io.ktor.utils.io.*
fun <T> Result<T>.recoverIfCancelled(): Result<T>? {
return when (exceptionOrNull()) {
is CancellationException -> null
else -> this
}
}
suspend inline fun <reified T> Outcome<HttpResponse>.bodyCatching(): Outcome<T> { suspend inline fun <reified T> Outcome<HttpResponse>.bodyCatching(): Outcome<T> {
return when (this) { return when (this) {
is Outcome.Success -> value.bodyCatching() is Outcome.Success -> value.bodyCatching()
@@ -39,4 +46,4 @@ suspend inline fun <reified T> HttpResponse.bodyCatching(): Outcome<T> {
}.getOrElse { throwable -> }.getOrElse { throwable ->
Outcome.Error.Unhandled(throwable) Outcome.Error.Unhandled(throwable)
} }
} }

View File

@@ -448,7 +448,7 @@ object YouTube {
.searchEndpoint .searchEndpoint
?.query ?.query
} }
} }
} }
} }
@@ -539,109 +539,109 @@ object YouTube {
params: String? = null, params: String? = null,
playlistSetVideoId: String? = null, playlistSetVideoId: String? = null,
continuation: String? = null, continuation: String? = null,
): Outcome<NextResult> { ): Result<NextResult>? {
return client.postCatching("/youtubei/v1/next") { return runCatching {
contentType(ContentType.Application.Json) val body = client.post("/youtubei/v1/next") {
setBody( contentType(ContentType.Application.Json)
NextBody( setBody(
context = Context.DefaultWeb, NextBody(
videoId = videoId, context = Context.DefaultWeb,
playlistId = playlistId, videoId = videoId,
isAudioOnly = true, playlistId = playlistId,
tunerSettingValue = "AUTOMIX_SETTING_NORMAL", isAudioOnly = true,
watchEndpointMusicSupportedConfigs = NextBody.WatchEndpointMusicSupportedConfigs( tunerSettingValue = "AUTOMIX_SETTING_NORMAL",
musicVideoType = "MUSIC_VIDEO_TYPE_ATV" watchEndpointMusicSupportedConfigs = NextBody.WatchEndpointMusicSupportedConfigs(
), musicVideoType = "MUSIC_VIDEO_TYPE_ATV"
index = index, ),
playlistSetVideoId = playlistSetVideoId, index = index,
params = params, playlistSetVideoId = playlistSetVideoId,
continuation = continuation params = params,
) continuation = continuation
)
parameter("key", Key)
parameter("prettyPrint", false)
}
.bodyCatching<NextResponse>()
.map { body ->
val tabs = body
.contents
.singleColumnMusicWatchNextResultsRenderer
.tabbedRenderer
.watchNextTabbedResultsRenderer
.tabs
NextResult(
continuation = (tabs
.getOrNull(0)
?.tabRenderer
?.content
?.musicQueueRenderer
?.content
?: body.continuationContents)
?.playlistPanelRenderer
?.continuations
?.getOrNull(0)
?.nextRadioContinuationData
?.continuation,
items = (tabs
.getOrNull(0)
?.tabRenderer
?.content
?.musicQueueRenderer
?.content
?: body.continuationContents)
?.playlistPanelRenderer
?.contents
?.mapNotNull { it.playlistPanelVideoRenderer }
?.mapNotNull { renderer ->
Item.Song(
info = Info(
name = renderer
.title
?.text ?: return@mapNotNull null,
endpoint = renderer
.navigationEndpoint
.watchEndpoint
),
authors = renderer
.longBylineText
?.splitBySeparator()
?.getOrNull(0)
?.map { run -> Info.from(run) }
?: emptyList(),
album = renderer
.longBylineText
?.splitBySeparator()
?.getOrNull(1)
?.getOrNull(0)
?.let { run -> Info.from(run) },
thumbnail = renderer
.thumbnail
.thumbnails
.firstOrNull(),
durationText = renderer
.lengthText
?.text
)
},
lyrics = NextResult.Lyrics(
browseId = tabs
.getOrNull(1)
?.tabRenderer
?.endpoint
?.browseEndpoint
?.browseId
),
related = NextResult.Related(
browseId = tabs
.getOrNull(2)
?.tabRenderer
?.endpoint
?.browseEndpoint
?.browseId
) )
) )
} parameter("key", Key)
parameter("prettyPrint", false)
}.body<NextResponse>()
val tabs = body
.contents
.singleColumnMusicWatchNextResultsRenderer
.tabbedRenderer
.watchNextTabbedResultsRenderer
.tabs
NextResult(
continuation = (tabs
.getOrNull(0)
?.tabRenderer
?.content
?.musicQueueRenderer
?.content
?: body.continuationContents)
?.playlistPanelRenderer
?.continuations
?.getOrNull(0)
?.nextRadioContinuationData
?.continuation,
items = (tabs
.getOrNull(0)
?.tabRenderer
?.content
?.musicQueueRenderer
?.content
?: body.continuationContents)
?.playlistPanelRenderer
?.contents
?.mapNotNull { it.playlistPanelVideoRenderer }
?.mapNotNull { renderer ->
Item.Song(
info = Info(
name = renderer
.title
?.text ?: return@mapNotNull null,
endpoint = renderer
.navigationEndpoint
.watchEndpoint
),
authors = renderer
.longBylineText
?.splitBySeparator()
?.getOrNull(0)
?.map { run -> Info.from(run) }
?: emptyList(),
album = renderer
.longBylineText
?.splitBySeparator()
?.getOrNull(1)
?.getOrNull(0)
?.let { run -> Info.from(run) },
thumbnail = renderer
.thumbnail
.thumbnails
.firstOrNull(),
durationText = renderer
.lengthText
?.text
)
},
lyrics = NextResult.Lyrics(
browseId = tabs
.getOrNull(1)
?.tabRenderer
?.endpoint
?.browseEndpoint
?.browseId
),
related = NextResult.Related(
browseId = tabs
.getOrNull(2)
?.tabRenderer
?.endpoint
?.browseEndpoint
?.browseId
)
)
}.recoverIfCancelled()
} }
data class NextResult( data class NextResult(
@@ -653,11 +653,11 @@ object YouTube {
class Lyrics( class Lyrics(
val browseId: String?, val browseId: String?,
) { ) {
suspend fun text(): Outcome<String?> { suspend fun text(): Result<String?> {
return if (browseId == null) { return if (browseId == null) {
Outcome.Success(null) Result.success(null)
} else { } else {
browse(browseId).map { body -> browse2(browseId).map { body ->
body.contents body.contents
.sectionListRenderer .sectionListRenderer
?.contents ?.contents
@@ -690,7 +690,7 @@ object YouTube {
} }
suspend fun browse2(browseId: String): Result<BrowseResponse> { suspend fun browse2(browseId: String): Result<BrowseResponse> {
return runCatching<YouTube, BrowseResponse> { return runCatching {
client.post("/youtubei/v1/browse") { client.post("/youtubei/v1/browse") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody( setBody(