Tweak code

This commit is contained in:
vfsfitvnm 2022-10-15 17:07:34 +02:00
parent 9a5ea69de4
commit df36075c3e
7 changed files with 132 additions and 160 deletions

View file

@ -153,9 +153,6 @@ interface Database {
@Query("SELECT * FROM Artist WHERE id = :id")
fun artist(id: String): Flow<Artist?>
@Query("SELECT timestamp FROM Artist WHERE id = :id")
fun artistTimestamp(id: String): Long?
@Query("SELECT * FROM Artist WHERE bookmarkedAt IS NOT NULL ORDER BY name DESC")
fun artistsByNameDesc(): Flow<List<Artist>>

View file

@ -6,10 +6,13 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Spacer
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
@ -38,12 +41,11 @@ import it.vfsfitvnm.vimusic.ui.screens.searchresult.ItemsPage
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.ui.styling.px
import it.vfsfitvnm.vimusic.utils.asMediaItem
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
import it.vfsfitvnm.youtubemusic.requests.albumPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.withContext
@ExperimentalFoundationApi
@ -52,63 +54,61 @@ import kotlinx.coroutines.withContext
fun AlbumScreen(browseId: String) {
val saveableStateHolder = rememberSaveableStateHolder()
val (tabIndex, onTabChanged) = rememberSaveable {
var tabIndex by rememberSaveable {
mutableStateOf(0)
}
val album by produceSaveableState(
initialValue = null,
stateSaver = nullableSaver(AlbumSaver),
) {
Database
.album(browseId)
.flowOn(Dispatchers.IO)
.collect { value = it }
var album by rememberSaveable(stateSaver = nullableSaver(AlbumSaver)) {
mutableStateOf(null)
}
val innertubeAlbum by produceSaveableState(
initialValue = null,
stateSaver = nullableSaver(InnertubePlaylistOrAlbumPageSaver),
tabIndex > 0
) {
if (value != null || (tabIndex == 0 && withContext(Dispatchers.IO) {
Database.albumTimestamp(
browseId
)
} != null)) return@produceSaveableState
var albumPage by rememberSaveable(stateSaver = nullableSaver(InnertubePlaylistOrAlbumPageSaver)) {
mutableStateOf(null)
}
withContext(Dispatchers.IO) {
Innertube.albumPage(BrowseBody(browseId = browseId))
}?.onSuccess { albumPage ->
value = albumPage
LaunchedEffect(Unit) {
Database
.album(browseId)
.combine(snapshotFlow { tabIndex }) { album, tabIndex -> album to tabIndex }
.collect { (currentAlbum, tabIndex) ->
album = currentAlbum
query {
Database.upsert(
Album(
id = browseId,
title = albumPage.title,
thumbnailUrl = albumPage.thumbnail?.url,
year = albumPage.year,
authorsText = albumPage.authors?.joinToString("") { it.name ?: "" },
shareUrl = albumPage.url,
timestamp = System.currentTimeMillis(),
bookmarkedAt = album?.bookmarkedAt
),
albumPage
.songsPage
?.items
?.map(Innertube.SongItem::asMediaItem)
?.onEach(Database::insert)
?.mapIndexed { position, mediaItem ->
SongAlbumMap(
songId = mediaItem.mediaId,
albumId = browseId,
position = position
)
} ?: emptyList()
)
if (albumPage == null && (currentAlbum?.timestamp == null || tabIndex == 1)) {
withContext(Dispatchers.IO) {
Innertube.albumPage(BrowseBody(browseId = browseId))
?.onSuccess { currentAlbumPage ->
albumPage = currentAlbumPage
Database.upsert(
Album(
id = browseId,
title = currentAlbumPage.title,
thumbnailUrl = currentAlbumPage.thumbnail?.url,
year = currentAlbumPage.year,
authorsText = currentAlbumPage.authors
?.joinToString("") { it.name ?: "" },
shareUrl = currentAlbumPage.url,
timestamp = System.currentTimeMillis(),
bookmarkedAt = album?.bookmarkedAt
),
currentAlbumPage
.songsPage
?.items
?.map(Innertube.SongItem::asMediaItem)
?.onEach(Database::insert)
?.mapIndexed { position, mediaItem ->
SongAlbumMap(
songId = mediaItem.mediaId,
albumId = browseId,
position = position
)
} ?: emptyList()
)
}
}
}
}
}
}
RouteHandler(listenToGlobalEmitter = true) {
@ -184,7 +184,7 @@ fun AlbumScreen(browseId: String) {
topIconButtonId = R.drawable.chevron_back,
onTopIconButtonClick = pop,
tabIndex = tabIndex,
onTabChanged = onTabChanged,
onTabChanged = { tabIndex = it },
tabColumnContent = { Item ->
Item(0, "Songs", R.drawable.musical_notes)
Item(1, "Other versions", R.drawable.disc)
@ -208,11 +208,11 @@ fun AlbumScreen(browseId: String) {
initialPlaceholderCount = 1,
continuationPlaceholderCount = 1,
emptyItemsText = "This album doesn't have any alternative version",
itemsPageProvider = innertubeAlbum?.let {
itemsPageProvider = albumPage?.let {
({
Result.success(
Innertube.ItemsPage(
items = innertubeAlbum?.otherVersions,
items = albumPage?.otherVersions,
continuation = null
)
)

View file

@ -8,8 +8,13 @@ import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
@ -45,7 +50,6 @@ import it.vfsfitvnm.vimusic.ui.styling.px
import it.vfsfitvnm.vimusic.utils.artistScreenTabIndexKey
import it.vfsfitvnm.vimusic.utils.asMediaItem
import it.vfsfitvnm.vimusic.utils.forcePlay
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
@ -54,7 +58,9 @@ import it.vfsfitvnm.youtubemusic.requests.artistPage
import it.vfsfitvnm.youtubemusic.requests.itemsPage
import it.vfsfitvnm.youtubemusic.utils.from
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
@ExperimentalFoundationApi
@ -63,49 +69,43 @@ import kotlinx.coroutines.withContext
fun ArtistScreen(browseId: String) {
val saveableStateHolder = rememberSaveableStateHolder()
val (tabIndex, onTabIndexChanged) = rememberPreference(
artistScreenTabIndexKey,
defaultValue = 0
)
var tabIndex by rememberPreference(artistScreenTabIndexKey, defaultValue = 0)
val artist by produceSaveableState(
initialValue = null,
stateSaver = nullableSaver(ArtistSaver),
) {
Database
.artist(browseId)
.flowOn(Dispatchers.IO)
.collect { value = it }
var artist by rememberSaveable(stateSaver = nullableSaver(ArtistSaver)) {
mutableStateOf(null)
}
val youtubeArtist by produceSaveableState(
initialValue = null,
stateSaver = nullableSaver(InnertubeArtistPageSaver),
tabIndex < 4
) {
if (value != null || (tabIndex == 4 && withContext(Dispatchers.IO) {
Database.artistTimestamp(
browseId
)
} != null)) return@produceSaveableState
var artistPage by rememberSaveable(stateSaver = nullableSaver(InnertubeArtistPageSaver)) {
mutableStateOf(null)
}
withContext(Dispatchers.IO) {
Innertube.artistPage(BrowseBody(browseId = browseId))
}?.onSuccess { artistPage ->
value = artistPage
LaunchedEffect(Unit) {
Database
.artist(browseId)
.combine(snapshotFlow { tabIndex }.map { it != 4 }) { artist, mustFetch -> artist to mustFetch }
.distinctUntilChanged()
.collect { (currentArtist, mustFetch) ->
artist = currentArtist
query {
Database.upsert(
Artist(
id = browseId,
name = artistPage.name,
thumbnailUrl = artistPage.thumbnail?.url,
timestamp = System.currentTimeMillis(),
bookmarkedAt = artist?.bookmarkedAt
)
)
if (artistPage == null && (currentArtist?.timestamp == null || mustFetch)) {
withContext(Dispatchers.IO) {
Innertube.artistPage(BrowseBody(browseId = browseId))
?.onSuccess { currentArtistPage ->
artistPage = currentArtistPage
Database.upsert(
Artist(
id = browseId,
name = currentArtistPage.name,
thumbnailUrl = currentArtistPage.thumbnail?.url,
timestamp = System.currentTimeMillis(),
bookmarkedAt = currentArtist?.bookmarkedAt
)
)
}
}
}
}
}
}
RouteHandler(listenToGlobalEmitter = true) {
@ -181,7 +181,7 @@ fun ArtistScreen(browseId: String) {
topIconButtonId = R.drawable.chevron_back,
onTopIconButtonClick = pop,
tabIndex = tabIndex,
onTabChanged = onTabIndexChanged,
onTabChanged = { tabIndex = it },
tabColumnContent = { Item ->
Item(0, "Overview", R.drawable.sparkles)
Item(1, "Songs", R.drawable.musical_notes)
@ -193,13 +193,13 @@ fun ArtistScreen(browseId: String) {
saveableStateHolder.SaveableStateProvider(key = currentTabIndex) {
when (currentTabIndex) {
0 -> ArtistOverview(
youtubeArtistPage = youtubeArtist,
youtubeArtistPage = artistPage,
thumbnailContent = thumbnailContent,
headerContent = headerContent,
onAlbumClick = { albumRoute(it) },
onViewAllSongsClick = { onTabIndexChanged(1) },
onViewAllAlbumsClick = { onTabIndexChanged(2) },
onViewAllSinglesClick = { onTabIndexChanged(3) },
onViewAllSongsClick = { tabIndex = 1 },
onViewAllAlbumsClick = { tabIndex = 2 },
onViewAllSinglesClick = { tabIndex = 3 },
)
1 -> {
@ -211,14 +211,14 @@ fun ArtistScreen(browseId: String) {
ItemsPage(
stateSaver = InnertubeSongsPageSaver,
headerContent = headerContent,
itemsPageProvider = youtubeArtist?.let {
itemsPageProvider = artistPage?.let {
({ continuation ->
continuation?.let {
Innertube.itemsPage(
body = ContinuationBody(continuation = continuation),
fromMusicResponsiveListItemRenderer = Innertube.SongItem::from,
)
} ?: youtubeArtist
} ?: artistPage
?.songsEndpoint
?.takeIf { it.browseId != null }
?.let { endpoint ->
@ -232,7 +232,7 @@ fun ArtistScreen(browseId: String) {
}
?: Result.success(
Innertube.ItemsPage(
items = youtubeArtist?.songs,
items = artistPage?.songs,
continuation = null
)
)
@ -275,14 +275,14 @@ fun ArtistScreen(browseId: String) {
stateSaver = InnertubeAlbumsPageSaver,
headerContent = headerContent,
emptyItemsText = "This artist didn't release any album",
itemsPageProvider = youtubeArtist?.let {
itemsPageProvider = artistPage?.let {
({ continuation ->
continuation?.let {
Innertube.itemsPage(
body = ContinuationBody(continuation = continuation),
fromMusicTwoRowItemRenderer = Innertube.AlbumItem::from,
)
} ?: youtubeArtist
} ?: artistPage
?.albumsEndpoint
?.takeIf { it.browseId != null }
?.let { endpoint ->
@ -296,7 +296,7 @@ fun ArtistScreen(browseId: String) {
}
?: Result.success(
Innertube.ItemsPage(
items = youtubeArtist?.albums,
items = artistPage?.albums,
continuation = null
)
)
@ -325,14 +325,14 @@ fun ArtistScreen(browseId: String) {
stateSaver = InnertubeAlbumsPageSaver,
headerContent = headerContent,
emptyItemsText = "This artist didn't release any single",
itemsPageProvider = youtubeArtist?.let {
itemsPageProvider = artistPage?.let {
({ continuation ->
continuation?.let {
Innertube.itemsPage(
body = ContinuationBody(continuation = continuation),
fromMusicTwoRowItemRenderer = Innertube.AlbumItem::from,
)
} ?: youtubeArtist
} ?: artistPage
?.singlesEndpoint
?.takeIf { it.browseId != null }
?.let { endpoint ->
@ -346,7 +346,7 @@ fun ArtistScreen(browseId: String) {
}
?: Result.success(
Innertube.ItemsPage(
items = youtubeArtist?.singles,
items = artistPage?.singles,
continuation = null
)
)

View file

@ -9,8 +9,11 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
@ -29,11 +32,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.only
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.SongSortBy

View file

@ -21,10 +21,11 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.autoSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -47,7 +48,6 @@ import it.vfsfitvnm.vimusic.ui.styling.favoritesIcon
import it.vfsfitvnm.vimusic.utils.bold
import it.vfsfitvnm.vimusic.utils.forceSeekToNext
import it.vfsfitvnm.vimusic.utils.forceSeekToPrevious
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.rememberRepeatMode
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
@ -76,16 +76,16 @@ fun Controls(
mutableStateOf<Long?>(null)
}
val likedAt by produceSaveableState<Long?>(
initialValue = null,
stateSaver = autoSaver(),
mediaId
) {
var likedAt by rememberSaveable {
mutableStateOf<Long?>(null)
}
LaunchedEffect(mediaId) {
Database
.likedAt(mediaId)
.flowOn(Dispatchers.IO)
.distinctUntilChanged()
.collect { value = it }
.collect { likedAt = it }
}
val shouldBePlayingTransition = updateTransition(shouldBePlaying, label = "shouldBePlaying")

View file

@ -7,8 +7,11 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
@ -22,8 +25,11 @@ import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.autoSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.paint
@ -41,9 +47,6 @@ import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.only
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.SearchQuery
import it.vfsfitvnm.vimusic.query
@ -57,7 +60,6 @@ import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.utils.align
import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.produceSaveableOneShotState
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.youtubemusic.Innertube
@ -90,13 +92,15 @@ fun OnlineSearch(
.collect { value = it }
}
val suggestionsResult by produceSaveableOneShotState(
initialValue = null,
stateSaver = resultSaver(autoSaver<List<String>?>()),
textFieldValue.text
) {
var suggestionsResult by rememberSaveable(stateSaver = resultSaver(autoSaver<List<String>?>())) {
mutableStateOf(null)
}
LaunchedEffect(textFieldValue.text) {
if (textFieldValue.text.isNotEmpty()) {
value = Innertube.searchSuggestions(SearchSuggestionsBody(input = textFieldValue.text))
delay(200)
suggestionsResult =
Innertube.searchSuggestions(SearchSuggestionsBody(input = textFieldValue.text))
}
}

View file

@ -7,11 +7,9 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.ProduceStateScope
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import kotlin.coroutines.CoroutineContext
import kotlin.experimental.ExperimentalTypeInference
import kotlinx.coroutines.suspendCancellableCoroutine
@ -51,31 +49,6 @@ fun <T> produceSaveableState(
return state
}
@Composable
fun <T> produceSaveableOneShotState(
initialValue: T,
stateSaver: Saver<T, out Any>,
key1: Any?,
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
): State<T> {
val state = rememberSaveable(stateSaver = stateSaver) {
mutableStateOf(initialValue)
}
var produced by rememberSaveable(key1) {
mutableStateOf(false)
}
LaunchedEffect(key1) {
if (!produced) {
ProduceSaveableStateScope(state, coroutineContext).producer()
produced = true
}
}
return state
}
@Composable
fun <T> produceSaveableState(
initialValue: T,