Start removing Outcome class in favor of Result
This commit is contained in:
parent
6ccbf7759c
commit
c4fea8835a
5 changed files with 155 additions and 157 deletions
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue