Code tweaks

This commit is contained in:
vfsfitvnm 2022-09-26 21:36:05 +02:00
parent f981725062
commit 82c2a952aa
15 changed files with 284 additions and 337 deletions

View file

@ -8,11 +8,11 @@ import androidx.room.PrimaryKey
@Entity
data class Album(
@PrimaryKey val id: String,
val title: String?,
val title: String? = null,
val thumbnailUrl: String? = null,
val year: String? = null,
val authorsText: String? = null,
val shareUrl: String? = null,
val timestamp: Long?,
val timestamp: Long? = null,
val bookmarkedAt: Long? = null
)

View file

@ -43,7 +43,9 @@ import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.Album
import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.models.SongAlbumMap
import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.savers.AlbumResultSaver
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
import it.vfsfitvnm.vimusic.ui.components.themed.Header
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
@ -61,29 +63,74 @@ import it.vfsfitvnm.vimusic.utils.enqueue
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.produceSaveableListState
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.vimusic.utils.thumbnail
import it.vfsfitvnm.vimusic.utils.toMediaItem
import it.vfsfitvnm.youtubemusic.YouTube
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
@ExperimentalAnimationApi
@ExperimentalFoundationApi
@Composable
fun AlbumOverview(
albumResult: Result<Album>?,
browseId: String,
) {
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
val binder = LocalPlayerServiceBinder.current
val context = LocalContext.current
val songs by produceSaveableListState(
flowProvider = {
Database.albumSongs(browseId)
},
val albumResult by produceSaveableState(
initialValue = null,
stateSaver = AlbumResultSaver,
) {
withContext(Dispatchers.IO) {
Database.album(browseId).collect { album ->
if (album?.timestamp == null) {
YouTube.album(browseId)?.map { youtubeAlbum ->
Database.upsert(
Album(
id = browseId,
title = youtubeAlbum.title,
thumbnailUrl = youtubeAlbum.thumbnail?.url,
year = youtubeAlbum.year,
authorsText = youtubeAlbum.authors?.joinToString("") { it.name },
shareUrl = youtubeAlbum.url,
timestamp = System.currentTimeMillis()
),
youtubeAlbum.items?.mapIndexedNotNull { position, albumItem ->
albumItem.toMediaItem(browseId, youtubeAlbum)?.let { mediaItem ->
Database.insert(mediaItem)
SongAlbumMap(
songId = mediaItem.mediaId,
albumId = browseId,
position = position
)
}
} ?: emptyList()
)
null
}
} else {
value = Result.success(album)
}
}
}
}
val songs by produceSaveableState(
initialValue = emptyList(),
stateSaver = DetailedSongListSaver
)
) {
Database
.albumSongs(browseId)
.flowOn(Dispatchers.IO)
.collect { value = it }
}
BoxWithConstraints {
val thumbnailSizeDp = maxWidth - Dimensions.verticalBarWidth
@ -270,6 +317,7 @@ fun AlbumOverview(
modifier = Modifier
.padding(LocalPlayerAwarePaddingValues.current)
.shimmer()
.fillMaxSize()
) {
HeaderPlaceholder()

View file

@ -3,21 +3,95 @@ package it.vfsfitvnm.vimusic.ui.screens.album
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.Album
import it.vfsfitvnm.vimusic.models.SongAlbumMap
import it.vfsfitvnm.vimusic.savers.AlbumResultSaver
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.toMediaItem
import it.vfsfitvnm.youtubemusic.YouTube
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
//@Stable
//class AlbumScreenState(
// initialIsLoading: Boolean = false,
// initialError: Throwable? = null,
// initialAlbum: Album? = null,
// initialYouTubeAlbum: YouTube.PlaylistOrAlbum? = null,
//) {
// var isLoading by mutableStateOf(initialIsLoading)
// var error by mutableStateOf(initialError)
// var album by mutableStateOf(initialAlbum)
// var youtubeAlbum by mutableStateOf(initialYouTubeAlbum)
//
// suspend fun loadAlbum(browseId: String) {
// println("loadAlbum $browseId")
// Database.album(browseId).flowOn(Dispatchers.IO).collect {
// if (it == null) {
// loadYouTubeAlbum(browseId)
// } else {
// album = it
// }
// }
// }
//
// suspend fun loadYouTubeAlbum(browseId: String) {
// println("loadYouTubeAlbum $browseId")
// if (youtubeAlbum == null) {
// isLoading = true
// withContext(Dispatchers.IO) {
// YouTube.album(browseId)
// }?.onSuccess {
// youtubeAlbum = it
// isLoading = false
//
// query {
// Database.upsert(
// Album(
// id = browseId,
// title = it.title,
// thumbnailUrl = it.thumbnail?.url,
// year = it.year,
// authorsText = it.authors?.joinToString(
// "",
// transform = YouTube.Info<NavigationEndpoint.Endpoint.Browse>::name
// ),
// shareUrl = it.url,
// timestamp = System.currentTimeMillis()
// ),
// it.items?.mapIndexedNotNull { position, albumItem ->
// albumItem.toMediaItem(browseId, it)?.let { mediaItem ->
// Database.insert(mediaItem)
// SongAlbumMap(
// songId = mediaItem.mediaId,
// albumId = browseId,
// position = position
// )
// }
// } ?: emptyList()
// )
// }
//
// }?.onFailure {
// error = it
// isLoading = false
// }
// }
// }
//}
//
//object AlbumScreenStateSaver : Saver<AlbumScreenState, List<Any?>> {
// override fun restore(value: List<Any?>) = AlbumScreenState(
// initialIsLoading = value[0] as Boolean,
// initialError = value[1] as Throwable?,
// initialAlbum = (value[1] as List<Any?>?)?.let(AlbumSaver::restore),
// )
//
// override fun SaverScope.save(value: AlbumScreenState): List<Any?> =
// listOf(
// value.isLoading,
// value.error,
// value.album?.let { with(AlbumSaver) { save(it) } },
//// value.youtubeAlbum?.let { with(YouTubeAlbumSaver) { save(it) } },
// )
//}
@OptIn(ExperimentalFoundationApi::class)
@ExperimentalAnimationApi
@ -29,59 +103,17 @@ fun AlbumScreen(browseId: String) {
globalRoutes()
host {
val albumResult by produceSaveableState(
initialValue = null,
stateSaver = AlbumResultSaver,
) {
withContext(Dispatchers.IO) {
Database.album(browseId).collect { album ->
if (album?.timestamp == null) {
YouTube.album(browseId)?.map { youtubeAlbum ->
Database.upsert(
Album(
id = browseId,
title = youtubeAlbum.title,
thumbnailUrl = youtubeAlbum.thumbnail?.url,
year = youtubeAlbum.year,
authorsText = youtubeAlbum.authors?.joinToString("") { it.name },
shareUrl = youtubeAlbum.url,
timestamp = System.currentTimeMillis()
),
youtubeAlbum.items?.mapIndexedNotNull { position, albumItem ->
albumItem.toMediaItem(browseId, youtubeAlbum)?.let { mediaItem ->
Database.insert(mediaItem)
SongAlbumMap(
songId = mediaItem.mediaId,
albumId = browseId,
position = position
)
}
} ?: emptyList()
)
null
}
} else {
value = Result.success(album)
}
}
}
}
Scaffold(
topIconButtonId = R.drawable.chevron_back,
onTopIconButtonClick = pop,
tabIndex = 0,
onTabChanged = {},
onTabChanged = { },
tabColumnContent = { Item ->
Item(0, "Overview", R.drawable.sparkles)
}
) { currentTabIndex ->
) { currentTabIndex ->
saveableStateHolder.SaveableStateProvider(key = currentTabIndex) {
AlbumOverview(
albumResult = albumResult,
browseId = browseId,
)
AlbumOverview(browseId = browseId)
}
}
}

View file

@ -97,6 +97,9 @@ fun ArtistScreen(browseId: String) {
onTabChanged = onTabIndexChanged,
tabColumnContent = { Item ->
Item(0, "Overview", R.drawable.sparkles)
Item(1, "Songs", R.drawable.musical_notes)
Item(2, "Albums", R.drawable.disc)
Item(3, "Singles", R.drawable.disc)
}
) { currentTabIndex ->
saveableStateHolder.SaveableStateProvider(key = currentTabIndex) {

View file

@ -50,11 +50,13 @@ import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.ui.styling.px
import it.vfsfitvnm.vimusic.utils.albumSortByKey
import it.vfsfitvnm.vimusic.utils.albumSortOrderKey
import it.vfsfitvnm.vimusic.utils.produceSaveableListState
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.vimusic.utils.thumbnail
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
@ExperimentalFoundationApi
@ExperimentalAnimationApi
@ -67,12 +69,16 @@ fun HomeAlbumList(
var sortBy by rememberPreference(albumSortByKey, AlbumSortBy.DateAdded)
var sortOrder by rememberPreference(albumSortOrderKey, SortOrder.Descending)
val items by produceSaveableListState(
flowProvider = { Database.albums(sortBy, sortOrder) },
val items by produceSaveableState(
initialValue = emptyList(),
stateSaver = AlbumListSaver,
key1 = sortBy,
key2 = sortOrder
)
sortBy, sortOrder,
) {
Database
.albums(sortBy, sortOrder)
.flowOn(Dispatchers.IO)
.collect { value = it }
}
val thumbnailSizeDp = Dimensions.thumbnails.song * 2
val thumbnailSizePx = thumbnailSizeDp.px

View file

@ -53,10 +53,12 @@ import it.vfsfitvnm.vimusic.ui.styling.px
import it.vfsfitvnm.vimusic.utils.artistSortByKey
import it.vfsfitvnm.vimusic.utils.artistSortOrderKey
import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.produceSaveableListState
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.vimusic.utils.thumbnail
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
@ExperimentalFoundationApi
@ExperimentalAnimationApi
@ -69,12 +71,16 @@ fun HomeArtistList(
var sortBy by rememberPreference(artistSortByKey, ArtistSortBy.DateAdded)
var sortOrder by rememberPreference(artistSortOrderKey, SortOrder.Descending)
val items by produceSaveableListState(
flowProvider = { Database.artists(sortBy, sortOrder) },
val items by produceSaveableState(
initialValue = emptyList(),
stateSaver = ArtistListSaver,
key1 = sortBy,
key2 = sortOrder
)
sortBy, sortOrder,
) {
Database
.artists(sortBy, sortOrder)
.flowOn(Dispatchers.IO)
.collect { value = it }
}
val thumbnailSizeDp = Dimensions.thumbnails.song * 2
val thumbnailSizePx = thumbnailSizeDp.px

View file

@ -53,8 +53,10 @@ import it.vfsfitvnm.vimusic.ui.views.PlaylistPreviewItem
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.playlistSortByKey
import it.vfsfitvnm.vimusic.utils.playlistSortOrderKey
import it.vfsfitvnm.vimusic.utils.produceSaveableListState
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.rememberPreference
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
@ExperimentalFoundationApi
@Composable
@ -85,12 +87,16 @@ fun HomePlaylistList(
var sortBy by rememberPreference(playlistSortByKey, PlaylistSortBy.DateAdded)
var sortOrder by rememberPreference(playlistSortOrderKey, SortOrder.Descending)
val items by produceSaveableListState(
flowProvider = { Database.playlistPreviews(sortBy, sortOrder) },
val items by produceSaveableState(
initialValue = emptyList(),
stateSaver = PlaylistPreviewListSaver,
key1 = sortBy,
key2 = sortOrder
)
sortBy, sortOrder,
) {
Database
.playlistPreviews(sortBy, sortOrder)
.flowOn(Dispatchers.IO)
.collect { value = it }
}
val sortOrderIconRotation by animateFloatAsState(
targetValue = if (sortOrder == SortOrder.Ascending) 0f else 180f,

View file

@ -52,17 +52,18 @@ import it.vfsfitvnm.vimusic.utils.asMediaItem
import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.color
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
import it.vfsfitvnm.vimusic.utils.produceSaveableListState
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.vimusic.utils.songSortByKey
import it.vfsfitvnm.vimusic.utils.songSortOrderKey
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
@ExperimentalFoundationApi
@ExperimentalAnimationApi
@Composable
fun HomeSongList() {
println("[${System.currentTimeMillis()}] HomeSongList")
val (colorPalette, typography) = LocalAppearance.current
val binder = LocalPlayerServiceBinder.current
@ -71,33 +72,16 @@ fun HomeSongList() {
var sortBy by rememberPreference(songSortByKey, SongSortBy.DateAdded)
var sortOrder by rememberPreference(songSortOrderKey, SortOrder.Descending)
val items by produceSaveableListState(
flowProvider = { Database.songs(sortBy, sortOrder) },
val items by produceSaveableState(
initialValue = emptyList(),
stateSaver = DetailedSongListSaver,
key1 = sortBy,
key2 = sortOrder
)
// var items by rememberSaveable(stateSaver = DetailedSongListSaver) {
// mutableStateOf(emptyList())
// }
//
// var hasToRecollect by rememberSaveable(sortBy, sortOrder) {
// println("hasToRecollect: $sortBy, $sortOrder")
// mutableStateOf(true)
// }
//
// LaunchedEffect(sortBy, sortOrder) {
// println("[${System.currentTimeMillis()}] LaunchedEffect, $hasToRecollect, $sortBy, $sortOrder")
// Database.songs(sortBy, sortOrder)
// .flowOn(Dispatchers.IO)
// .drop(if (hasToRecollect) 0 else 1)
// .collect {
// hasToRecollect = false
// println("[${System.currentTimeMillis()}] collecting... ")
// items = it
// }
// }
sortBy, sortOrder,
) {
Database
.songs(sortBy, sortOrder)
.flowOn(Dispatchers.IO)
.collect { value = it }
}
val sortOrderIconRotation by animateFloatAsState(
targetValue = if (sortOrder == SortOrder.Ascending) 0f else 180f,
@ -110,8 +94,6 @@ fun HomeSongList() {
.background(colorPalette.background0)
.fillMaxSize()
) {
// println("[${System.currentTimeMillis()}] LazyColumn")
item(
key = "header",
contentType = 0
@ -174,10 +156,8 @@ fun HomeSongList() {
song = song,
thumbnailSize = thumbnailSize,
onClick = {
items.map(DetailedSong::asMediaItem)?.let { mediaItems ->
binder?.stopRadio()
binder?.player?.forcePlayAtIndex(mediaItems, index)
}
binder?.stopRadio()
binder?.player?.forcePlayAtIndex(items.map(DetailedSong::asMediaItem), index)
},
menuContent = {
InHistoryMediaItemMenu(song = song)
@ -192,9 +172,7 @@ fun HomeSongList() {
) {
BasicText(
text = song.formattedTotalPlayTime,
style = typography.xxs.semiBold.center.color(
Color.White
),
style = typography.xxs.semiBold.center.color(Color.White),
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier

View file

@ -41,9 +41,15 @@ import it.vfsfitvnm.vimusic.utils.align
import it.vfsfitvnm.vimusic.utils.asMediaItem
import it.vfsfitvnm.vimusic.utils.forcePlay
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.produceSaveableListState
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
//context(ProduceStateScope<T>)
//fun <T> Flow<T>.distinctUntilChangedWithProducedState() =
// distinctUntilChanged { old, new -> new != old && new != value }
@ExperimentalFoundationApi
@ExperimentalAnimationApi
@ -55,13 +61,16 @@ fun LocalSongSearch(
val (colorPalette, typography) = LocalAppearance.current
val binder = LocalPlayerServiceBinder.current
val items by produceSaveableListState(
flowProvider = {
Database.search("%${textFieldValue.text}%")
},
val items by produceSaveableState(
initialValue = emptyList(),
stateSaver = DetailedSongListSaver,
key1 = textFieldValue.text
)
) {
Database
.search("%${textFieldValue.text}%")
.flowOn(Dispatchers.IO)
.collect { value = it }
}
val thumbnailSize = Dimensions.thumbnails.song.px

View file

@ -52,12 +52,14 @@ import it.vfsfitvnm.vimusic.ui.components.themed.LoadingOrError
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.utils.align
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.produceSaveableListState
import it.vfsfitvnm.vimusic.utils.produceSaveableOneShotState
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.youtubemusic.YouTube
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
@Composable
fun OnlineSearch(
@ -69,17 +71,18 @@ fun OnlineSearch(
) {
val (colorPalette, typography) = LocalAppearance.current
val history by produceSaveableListState(
flowProvider = {
Database.queries("%${textFieldValue.text}%").distinctUntilChanged { old, new ->
old.size == new.size
}
},
val history by produceSaveableState(
initialValue = emptyList(),
stateSaver = SearchQueryListSaver,
key1 = textFieldValue.text
)
) {
Database.queries("%${textFieldValue.text}%")
.flowOn(Dispatchers.IO)
.distinctUntilChanged { old, new -> old.size == new.size }
.collect { value = it }
}
val suggestionsResult by produceSaveableState(
val suggestionsResult by produceSaveableOneShotState(
initialValue = null,
stateSaver = StringListResultSaver,
key1 = textFieldValue.text

View file

@ -22,7 +22,7 @@ import it.vfsfitvnm.vimusic.savers.StringResultSaver
import it.vfsfitvnm.vimusic.ui.components.themed.Header
import it.vfsfitvnm.vimusic.ui.components.themed.TextCard
import it.vfsfitvnm.vimusic.ui.views.SearchResultLoadingOrError
import it.vfsfitvnm.vimusic.utils.produceSaveableRelaunchableState
import it.vfsfitvnm.vimusic.utils.produceSaveableRelaunchableOneShotState
import it.vfsfitvnm.youtubemusic.YouTube
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -41,11 +41,10 @@ inline fun <T : YouTube.Item> SearchResult(
mutableStateOf(listOf())
}
val (continuationResultState, fetch) = produceSaveableRelaunchableState(
val (continuationResultState, fetch) = produceSaveableRelaunchableOneShotState(
initialValue = null,
stateSaver = StringResultSaver,
key1 = query,
key2 = filter
query, filter
) {
val token = value?.getOrNull()

View file

@ -90,7 +90,7 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
val thumbnailSizeDp = Dimensions.thumbnails.song
val thumbnailSizePx = thumbnailSizeDp.px
SearchResult<YouTube.Item.Song>(
SearchResult(
query = query,
filter = searchFilter,
onSearchAgain = onSearchAgain,

View file

@ -1,102 +0,0 @@
package it.vfsfitvnm.vimusic.utils
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import it.vfsfitvnm.vimusic.savers.ListSaver
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.flowOn
@Composable
fun <T> produceSaveableListState(
flowProvider: () -> Flow<List<T>>,
stateSaver: ListSaver<T, List<Any?>>,
): State<List<T>> {
val state = rememberSaveable(stateSaver = stateSaver) {
mutableStateOf(emptyList())
}
var hasToRecollect by rememberSaveable {
mutableStateOf(true)
}
LaunchedEffect(Unit) {
flowProvider()
.flowOn(Dispatchers.IO)
.drop(if (hasToRecollect) 0 else 1)
.collect {
hasToRecollect = false
state.value = it
}
}
return state
}
@Composable
fun <T> produceSaveableListState(
flowProvider: () -> Flow<List<T>>,
stateSaver: ListSaver<T, List<Any?>>,
key1: Any?,
): State<List<T>> {
val state = rememberSaveable(stateSaver = stateSaver) {
mutableStateOf(emptyList())
}
var hasToRecollect by rememberSaveable(key1) {
// println("hasToRecollect: $sortBy, $sortOrder")
mutableStateOf(true)
}
LaunchedEffect(key1) {
// println("[${System.currentTimeMillis()}] LaunchedEffect, $hasToRecollect, $sortBy, $sortOrder")
flowProvider()
.flowOn(Dispatchers.IO)
.drop(if (hasToRecollect) 0 else 1)
.collect {
hasToRecollect = false
// println("[${System.currentTimeMillis()}] collecting... ")
state.value = it
}
}
return state
}
@Composable
fun <T> produceSaveableListState(
flowProvider: () -> Flow<List<T>>,
stateSaver: ListSaver<T, List<Any?>>,
key1: Any?,
key2: Any?,
): State<List<T>> {
val state = rememberSaveable(stateSaver = stateSaver) {
mutableStateOf(emptyList())
}
// var hasToRecollect by rememberSaveable(key1, key2) {
//// println("hasToRecollect: $sortBy, $sortOrder")
// mutableStateOf(true)
// }
LaunchedEffect(key1, key2) {
// println("[${System.currentTimeMillis()}] LaunchedEffect, $hasToRecollect, $sortBy, $sortOrder")
flowProvider()
.flowOn(Dispatchers.IO)
// .drop(if (hasToRecollect) 0 else 1)
.collect {
// hasToRecollect = false
// println("[${System.currentTimeMillis()}] collecting... ")
state.value = it
}
}
return state
}

View file

@ -1,3 +1,5 @@
@file:OptIn(ExperimentalTypeInference::class)
package it.vfsfitvnm.vimusic.utils
import androidx.compose.runtime.Composable
@ -14,27 +16,23 @@ import kotlin.coroutines.CoroutineContext
import kotlin.experimental.ExperimentalTypeInference
import kotlinx.coroutines.suspendCancellableCoroutine
@OptIn(ExperimentalTypeInference::class)
@Composable
fun <T> produceSaveableState(
initialValue: T,
stateSaver: Saver<T, out Any>,
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
): State<T> {
val result = rememberSaveable(stateSaver = stateSaver) { mutableStateOf(initialValue) }
var hasToFetch by rememberSaveable { mutableStateOf(true) }
val result = rememberSaveable(stateSaver = stateSaver) {
mutableStateOf(initialValue)
}
LaunchedEffect(Unit) {
if (hasToFetch) {
ProduceSaveableStateScope(result, coroutineContext).producer()
hasToFetch = false
}
ProduceSaveableStateScope(result, coroutineContext).producer()
}
return result
}
@OptIn(ExperimentalTypeInference::class)
@Composable
fun <T> produceSaveableState(
initialValue: T,
@ -42,20 +40,42 @@ fun <T> produceSaveableState(
key1: Any?,
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
): State<T> {
val result = rememberSaveable(stateSaver = stateSaver) { mutableStateOf(initialValue) }
var hasToFetch by rememberSaveable(key1) { mutableStateOf(true) }
val state = rememberSaveable(stateSaver = stateSaver) {
mutableStateOf(initialValue)
}
LaunchedEffect(key1) {
if (hasToFetch) {
ProduceSaveableStateScope(result, coroutineContext).producer()
hasToFetch = false
}
ProduceSaveableStateScope(state, coroutineContext).producer()
}
return result
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
}
@OptIn(ExperimentalTypeInference::class)
@Composable
fun <T> produceSaveableState(
initialValue: T,
@ -64,42 +84,42 @@ fun <T> produceSaveableState(
key2: Any?,
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
): State<T> {
val result = rememberSaveable(stateSaver = stateSaver) { mutableStateOf(initialValue) }
val result = rememberSaveable(stateSaver = stateSaver) {
mutableStateOf(initialValue)
}
var hasToFetch by rememberSaveable(key1, key2) { mutableStateOf(true) }
LaunchedEffect(Unit) {
if (hasToFetch) {
ProduceSaveableStateScope(result, coroutineContext).producer()
hasToFetch = false
}
LaunchedEffect(key1, key2) {
ProduceSaveableStateScope(result, coroutineContext).producer()
}
return result
}
@OptIn(ExperimentalTypeInference::class)
@Composable
fun <T> produceSaveableRelaunchableState(
fun <T> produceSaveableRelaunchableOneShotState(
initialValue: T,
stateSaver: Saver<T, out Any>,
key1: Any?,
key2: Any?,
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
): Pair<State<T>, () -> Unit> {
val result = rememberSaveable(stateSaver = stateSaver) { mutableStateOf(initialValue) }
val result = rememberSaveable(stateSaver = stateSaver) {
mutableStateOf(initialValue)
}
var hasToFetch by rememberSaveable(key1, key2) { mutableStateOf(true) }
var produced by rememberSaveable(key1, key2) {
mutableStateOf(false)
}
val relaunchableEffect = relaunchableEffect(key1, key2) {
if (hasToFetch) {
if (!produced) {
ProduceSaveableStateScope(result, coroutineContext).producer()
hasToFetch = false
produced = true
}
}
return result to {
hasToFetch = true
produced = false
relaunchableEffect()
}
}

View file

@ -1,61 +0,0 @@
package it.vfsfitvnm.vimusic.utils
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.saveable.rememberSaveable
@Composable
fun rememberLazyListStates(count: Int): List<LazyListState> {
return rememberSaveable(
saver = listSaver(
save = { states: List<LazyListState> ->
List(states.size * 2) {
when (it % 2) {
0 -> states[it / 2].firstVisibleItemIndex
1 -> states[it / 2].firstVisibleItemScrollOffset
else -> error("unreachable")
}
}
},
restore = { states ->
List(states.size / 2) {
LazyListState(
firstVisibleItemIndex = states[it * 2],
firstVisibleItemScrollOffset = states[it * 2 + 1]
)
}
}
)
) {
List(count) { LazyListState(0, 0) }
}
}
@Composable
fun rememberLazyGridStates(count: Int): List<LazyGridState> {
return rememberSaveable(
saver = listSaver(
save = { states: List<LazyGridState> ->
List(states.size * 2) {
when (it % 2) {
0 -> states[it / 2].firstVisibleItemIndex
1 -> states[it / 2].firstVisibleItemScrollOffset
else -> error("unreachable")
}
}
},
restore = { states ->
List(states.size / 2) {
LazyGridState(
firstVisibleItemIndex = states[it * 2],
firstVisibleItemScrollOffset = states[it * 2 + 1]
)
}
}
)
) {
List(count) { LazyGridState(0, 0) }
}
}