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