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 it.vfsfitvnm.vimusic.R
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.TextPlaceholder
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.youtubemusic.Outcome
@Composable
fun LyricsView(
lyricsOutcome: Outcome<String>,
lyrics: String?,
onInitialize: () -> Unit,
onSearchOnline: () -> Unit,
onLyricsUpdate: (String) -> Unit,
@ -42,7 +41,7 @@ fun LyricsView(
if (isEditingLyrics) {
TextFieldDialog(
hintText = "Enter the lyrics",
initialTextInput = lyricsOutcome.valueOrNull ?: "",
initialTextInput = lyrics ?: "",
singleLine = false,
maxLines = 10,
isTextInputValid = { true },
@ -53,26 +52,7 @@ fun LyricsView(
)
}
OutcomeItem(
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 ->
if (lyrics != null ) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
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.styling.LocalColorPalette
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
import it.vfsfitvnm.vimusic.utils.*
import it.vfsfitvnm.youtubemusic.Outcome
import it.vfsfitvnm.vimusic.utils.PlayerState
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.isEvaluable
import it.vfsfitvnm.youtubemusic.toNotNull
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -62,8 +62,8 @@ fun PlayerBottomSheet(
var route by rememberRoute()
var nextOutcome by remember(playerState?.mediaItem?.mediaId) {
mutableStateOf<Outcome<YouTube.NextResult>>(Outcome.Initial)
var nextResult by remember(playerState?.mediaItem?.mediaId) {
mutableStateOf<Result<YouTube.NextResult>?>(null)
}
BottomSheet(
@ -150,8 +150,8 @@ fun PlayerBottomSheet(
}
}
) {
var lyricsOutcome by remember(song) {
mutableStateOf(song?.lyrics?.let { Outcome.Success(it) } ?: Outcome.Initial)
var lyricsResult by remember(song) {
mutableStateOf(song?.lyrics?.let { Result.success(it) })
}
RouteHandler(
@ -181,21 +181,16 @@ fun PlayerBottomSheet(
val context = LocalContext.current
LyricsView(
lyricsOutcome = lyricsOutcome,
lyrics = lyricsResult?.getOrNull(),
nestedScrollConnectionProvider = layoutState::nestedScrollConnection,
onInitialize = {
coroutineScope.launch(Dispatchers.Main) {
lyricsOutcome = Outcome.Loading
val mediaItem = player?.currentMediaItem!!
if (nextOutcome.isEvaluable) {
nextOutcome = Outcome.Loading
if (nextResult == null) {
val mediaItemIndex = player.currentMediaItemIndex
nextOutcome = withContext(Dispatchers.IO) {
nextResult = withContext(Dispatchers.IO) {
YouTube.next(
mediaItem.mediaId,
mediaItem.mediaMetadata.extras?.getString("playlistId"),
@ -204,11 +199,9 @@ fun PlayerBottomSheet(
}
}
lyricsOutcome = nextOutcome.flatMap {
it.lyrics?.text().toNotNull()
}.map { lyrics ->
lyrics ?: ""
}.map { lyrics ->
lyricsResult = nextResult?.map { nextResult ->
nextResult.lyrics?.text()?.getOrNull() ?: ""
}?.map { lyrics ->
query {
song?.let {
Database.update(song.copy(lyrics = lyrics))

View file

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

View file

@ -8,6 +8,13 @@ import io.ktor.util.network.*
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> {
return when (this) {
is Outcome.Success -> value.bodyCatching()
@ -39,4 +46,4 @@ suspend inline fun <reified T> HttpResponse.bodyCatching(): Outcome<T> {
}.getOrElse { throwable ->
Outcome.Error.Unhandled(throwable)
}
}
}

View file

@ -448,7 +448,7 @@ object YouTube {
.searchEndpoint
?.query
}
}
}
}
}
@ -539,109 +539,109 @@ object YouTube {
params: String? = null,
playlistSetVideoId: String? = null,
continuation: String? = null,
): Outcome<NextResult> {
return client.postCatching("/youtubei/v1/next") {
contentType(ContentType.Application.Json)
setBody(
NextBody(
context = Context.DefaultWeb,
videoId = videoId,
playlistId = playlistId,
isAudioOnly = true,
tunerSettingValue = "AUTOMIX_SETTING_NORMAL",
watchEndpointMusicSupportedConfigs = NextBody.WatchEndpointMusicSupportedConfigs(
musicVideoType = "MUSIC_VIDEO_TYPE_ATV"
),
index = index,
playlistSetVideoId = playlistSetVideoId,
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
): Result<NextResult>? {
return runCatching {
val body = client.post("/youtubei/v1/next") {
contentType(ContentType.Application.Json)
setBody(
NextBody(
context = Context.DefaultWeb,
videoId = videoId,
playlistId = playlistId,
isAudioOnly = true,
tunerSettingValue = "AUTOMIX_SETTING_NORMAL",
watchEndpointMusicSupportedConfigs = NextBody.WatchEndpointMusicSupportedConfigs(
musicVideoType = "MUSIC_VIDEO_TYPE_ATV"
),
index = index,
playlistSetVideoId = playlistSetVideoId,
params = params,
continuation = continuation
)
)
}
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(
@ -653,11 +653,11 @@ object YouTube {
class Lyrics(
val browseId: String?,
) {
suspend fun text(): Outcome<String?> {
suspend fun text(): Result<String?> {
return if (browseId == null) {
Outcome.Success(null)
Result.success(null)
} else {
browse(browseId).map { body ->
browse2(browseId).map { body ->
body.contents
.sectionListRenderer
?.contents
@ -690,7 +690,7 @@ object YouTube {
}
suspend fun browse2(browseId: String): Result<BrowseResponse> {
return runCatching<YouTube, BrowseResponse> {
return runCatching {
client.post("/youtubei/v1/browse") {
contentType(ContentType.Application.Json)
setBody(