Code tweaks
This commit is contained in:
@@ -8,11 +8,11 @@ import androidx.room.PrimaryKey
|
|||||||
@Entity
|
@Entity
|
||||||
data class Album(
|
data class Album(
|
||||||
@PrimaryKey val id: String,
|
@PrimaryKey val id: String,
|
||||||
val title: String?,
|
val title: String? = null,
|
||||||
val thumbnailUrl: String? = null,
|
val thumbnailUrl: String? = null,
|
||||||
val year: String? = null,
|
val year: String? = null,
|
||||||
val authorsText: String? = null,
|
val authorsText: String? = null,
|
||||||
val shareUrl: String? = null,
|
val shareUrl: String? = null,
|
||||||
val timestamp: Long?,
|
val timestamp: Long? = null,
|
||||||
val bookmarkedAt: Long? = null
|
val bookmarkedAt: Long? = null
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
|||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
import it.vfsfitvnm.vimusic.models.Album
|
import it.vfsfitvnm.vimusic.models.Album
|
||||||
import it.vfsfitvnm.vimusic.models.DetailedSong
|
import it.vfsfitvnm.vimusic.models.DetailedSong
|
||||||
|
import it.vfsfitvnm.vimusic.models.SongAlbumMap
|
||||||
import it.vfsfitvnm.vimusic.query
|
import it.vfsfitvnm.vimusic.query
|
||||||
|
import it.vfsfitvnm.vimusic.savers.AlbumResultSaver
|
||||||
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
|
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.forcePlayAtIndex
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
|
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
|
||||||
import it.vfsfitvnm.vimusic.utils.medium
|
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.secondary
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
import it.vfsfitvnm.vimusic.utils.thumbnail
|
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
|
@ExperimentalAnimationApi
|
||||||
@ExperimentalFoundationApi
|
@ExperimentalFoundationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun AlbumOverview(
|
fun AlbumOverview(
|
||||||
albumResult: Result<Album>?,
|
|
||||||
browseId: String,
|
browseId: String,
|
||||||
) {
|
) {
|
||||||
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val songs by produceSaveableListState(
|
val albumResult by produceSaveableState(
|
||||||
flowProvider = {
|
initialValue = null,
|
||||||
Database.albumSongs(browseId)
|
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
|
stateSaver = DetailedSongListSaver
|
||||||
|
) {
|
||||||
)
|
Database
|
||||||
|
.albumSongs(browseId)
|
||||||
|
.flowOn(Dispatchers.IO)
|
||||||
|
.collect { value = it }
|
||||||
|
}
|
||||||
|
|
||||||
BoxWithConstraints {
|
BoxWithConstraints {
|
||||||
val thumbnailSizeDp = maxWidth - Dimensions.verticalBarWidth
|
val thumbnailSizeDp = maxWidth - Dimensions.verticalBarWidth
|
||||||
@@ -270,6 +317,7 @@ fun AlbumOverview(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(LocalPlayerAwarePaddingValues.current)
|
.padding(LocalPlayerAwarePaddingValues.current)
|
||||||
.shimmer()
|
.shimmer()
|
||||||
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
HeaderPlaceholder()
|
HeaderPlaceholder()
|
||||||
|
|
||||||
|
|||||||
@@ -3,21 +3,95 @@ package it.vfsfitvnm.vimusic.ui.screens.album
|
|||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
|
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
import it.vfsfitvnm.route.RouteHandler
|
||||||
import it.vfsfitvnm.vimusic.Database
|
|
||||||
import it.vfsfitvnm.vimusic.R
|
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.components.themed.Scaffold
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
|
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
|
||||||
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
|
||||||
import it.vfsfitvnm.vimusic.utils.toMediaItem
|
//@Stable
|
||||||
import it.vfsfitvnm.youtubemusic.YouTube
|
//class AlbumScreenState(
|
||||||
import kotlinx.coroutines.Dispatchers
|
// initialIsLoading: Boolean = false,
|
||||||
import kotlinx.coroutines.withContext
|
// 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)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@@ -29,59 +103,17 @@ fun AlbumScreen(browseId: String) {
|
|||||||
globalRoutes()
|
globalRoutes()
|
||||||
|
|
||||||
host {
|
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(
|
Scaffold(
|
||||||
topIconButtonId = R.drawable.chevron_back,
|
topIconButtonId = R.drawable.chevron_back,
|
||||||
onTopIconButtonClick = pop,
|
onTopIconButtonClick = pop,
|
||||||
tabIndex = 0,
|
tabIndex = 0,
|
||||||
onTabChanged = {},
|
onTabChanged = { },
|
||||||
tabColumnContent = { Item ->
|
tabColumnContent = { Item ->
|
||||||
Item(0, "Overview", R.drawable.sparkles)
|
Item(0, "Overview", R.drawable.sparkles)
|
||||||
}
|
}
|
||||||
) { currentTabIndex ->
|
) { currentTabIndex ->
|
||||||
saveableStateHolder.SaveableStateProvider(key = currentTabIndex) {
|
saveableStateHolder.SaveableStateProvider(key = currentTabIndex) {
|
||||||
AlbumOverview(
|
AlbumOverview(browseId = browseId)
|
||||||
albumResult = albumResult,
|
|
||||||
browseId = browseId,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ fun ArtistScreen(browseId: String) {
|
|||||||
onTabChanged = onTabIndexChanged,
|
onTabChanged = onTabIndexChanged,
|
||||||
tabColumnContent = { Item ->
|
tabColumnContent = { Item ->
|
||||||
Item(0, "Overview", R.drawable.sparkles)
|
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 ->
|
) { currentTabIndex ->
|
||||||
saveableStateHolder.SaveableStateProvider(key = currentTabIndex) {
|
saveableStateHolder.SaveableStateProvider(key = currentTabIndex) {
|
||||||
|
|||||||
@@ -50,11 +50,13 @@ import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
|||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.utils.albumSortByKey
|
import it.vfsfitvnm.vimusic.utils.albumSortByKey
|
||||||
import it.vfsfitvnm.vimusic.utils.albumSortOrderKey
|
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.rememberPreference
|
||||||
import it.vfsfitvnm.vimusic.utils.secondary
|
import it.vfsfitvnm.vimusic.utils.secondary
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
import it.vfsfitvnm.vimusic.utils.thumbnail
|
import it.vfsfitvnm.vimusic.utils.thumbnail
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
|
||||||
@ExperimentalFoundationApi
|
@ExperimentalFoundationApi
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@@ -67,12 +69,16 @@ fun HomeAlbumList(
|
|||||||
var sortBy by rememberPreference(albumSortByKey, AlbumSortBy.DateAdded)
|
var sortBy by rememberPreference(albumSortByKey, AlbumSortBy.DateAdded)
|
||||||
var sortOrder by rememberPreference(albumSortOrderKey, SortOrder.Descending)
|
var sortOrder by rememberPreference(albumSortOrderKey, SortOrder.Descending)
|
||||||
|
|
||||||
val items by produceSaveableListState(
|
val items by produceSaveableState(
|
||||||
flowProvider = { Database.albums(sortBy, sortOrder) },
|
initialValue = emptyList(),
|
||||||
stateSaver = AlbumListSaver,
|
stateSaver = AlbumListSaver,
|
||||||
key1 = sortBy,
|
sortBy, sortOrder,
|
||||||
key2 = sortOrder
|
) {
|
||||||
)
|
Database
|
||||||
|
.albums(sortBy, sortOrder)
|
||||||
|
.flowOn(Dispatchers.IO)
|
||||||
|
.collect { value = it }
|
||||||
|
}
|
||||||
|
|
||||||
val thumbnailSizeDp = Dimensions.thumbnails.song * 2
|
val thumbnailSizeDp = Dimensions.thumbnails.song * 2
|
||||||
val thumbnailSizePx = thumbnailSizeDp.px
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|||||||
@@ -53,10 +53,12 @@ import it.vfsfitvnm.vimusic.ui.styling.px
|
|||||||
import it.vfsfitvnm.vimusic.utils.artistSortByKey
|
import it.vfsfitvnm.vimusic.utils.artistSortByKey
|
||||||
import it.vfsfitvnm.vimusic.utils.artistSortOrderKey
|
import it.vfsfitvnm.vimusic.utils.artistSortOrderKey
|
||||||
import it.vfsfitvnm.vimusic.utils.center
|
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.rememberPreference
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
import it.vfsfitvnm.vimusic.utils.thumbnail
|
import it.vfsfitvnm.vimusic.utils.thumbnail
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
|
||||||
@ExperimentalFoundationApi
|
@ExperimentalFoundationApi
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@@ -69,12 +71,16 @@ fun HomeArtistList(
|
|||||||
var sortBy by rememberPreference(artistSortByKey, ArtistSortBy.DateAdded)
|
var sortBy by rememberPreference(artistSortByKey, ArtistSortBy.DateAdded)
|
||||||
var sortOrder by rememberPreference(artistSortOrderKey, SortOrder.Descending)
|
var sortOrder by rememberPreference(artistSortOrderKey, SortOrder.Descending)
|
||||||
|
|
||||||
val items by produceSaveableListState(
|
val items by produceSaveableState(
|
||||||
flowProvider = { Database.artists(sortBy, sortOrder) },
|
initialValue = emptyList(),
|
||||||
stateSaver = ArtistListSaver,
|
stateSaver = ArtistListSaver,
|
||||||
key1 = sortBy,
|
sortBy, sortOrder,
|
||||||
key2 = sortOrder
|
) {
|
||||||
)
|
Database
|
||||||
|
.artists(sortBy, sortOrder)
|
||||||
|
.flowOn(Dispatchers.IO)
|
||||||
|
.collect { value = it }
|
||||||
|
}
|
||||||
|
|
||||||
val thumbnailSizeDp = Dimensions.thumbnails.song * 2
|
val thumbnailSizeDp = Dimensions.thumbnails.song * 2
|
||||||
val thumbnailSizePx = thumbnailSizeDp.px
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|||||||
@@ -53,8 +53,10 @@ import it.vfsfitvnm.vimusic.ui.views.PlaylistPreviewItem
|
|||||||
import it.vfsfitvnm.vimusic.utils.medium
|
import it.vfsfitvnm.vimusic.utils.medium
|
||||||
import it.vfsfitvnm.vimusic.utils.playlistSortByKey
|
import it.vfsfitvnm.vimusic.utils.playlistSortByKey
|
||||||
import it.vfsfitvnm.vimusic.utils.playlistSortOrderKey
|
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 it.vfsfitvnm.vimusic.utils.rememberPreference
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
|
||||||
@ExperimentalFoundationApi
|
@ExperimentalFoundationApi
|
||||||
@Composable
|
@Composable
|
||||||
@@ -85,12 +87,16 @@ fun HomePlaylistList(
|
|||||||
var sortBy by rememberPreference(playlistSortByKey, PlaylistSortBy.DateAdded)
|
var sortBy by rememberPreference(playlistSortByKey, PlaylistSortBy.DateAdded)
|
||||||
var sortOrder by rememberPreference(playlistSortOrderKey, SortOrder.Descending)
|
var sortOrder by rememberPreference(playlistSortOrderKey, SortOrder.Descending)
|
||||||
|
|
||||||
val items by produceSaveableListState(
|
val items by produceSaveableState(
|
||||||
flowProvider = { Database.playlistPreviews(sortBy, sortOrder) },
|
initialValue = emptyList(),
|
||||||
stateSaver = PlaylistPreviewListSaver,
|
stateSaver = PlaylistPreviewListSaver,
|
||||||
key1 = sortBy,
|
sortBy, sortOrder,
|
||||||
key2 = sortOrder
|
) {
|
||||||
)
|
Database
|
||||||
|
.playlistPreviews(sortBy, sortOrder)
|
||||||
|
.flowOn(Dispatchers.IO)
|
||||||
|
.collect { value = it }
|
||||||
|
}
|
||||||
|
|
||||||
val sortOrderIconRotation by animateFloatAsState(
|
val sortOrderIconRotation by animateFloatAsState(
|
||||||
targetValue = if (sortOrder == SortOrder.Ascending) 0f else 180f,
|
targetValue = if (sortOrder == SortOrder.Ascending) 0f else 180f,
|
||||||
|
|||||||
@@ -52,17 +52,18 @@ import it.vfsfitvnm.vimusic.utils.asMediaItem
|
|||||||
import it.vfsfitvnm.vimusic.utils.center
|
import it.vfsfitvnm.vimusic.utils.center
|
||||||
import it.vfsfitvnm.vimusic.utils.color
|
import it.vfsfitvnm.vimusic.utils.color
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
|
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.rememberPreference
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
import it.vfsfitvnm.vimusic.utils.songSortByKey
|
import it.vfsfitvnm.vimusic.utils.songSortByKey
|
||||||
import it.vfsfitvnm.vimusic.utils.songSortOrderKey
|
import it.vfsfitvnm.vimusic.utils.songSortOrderKey
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
|
||||||
@ExperimentalFoundationApi
|
@ExperimentalFoundationApi
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeSongList() {
|
fun HomeSongList() {
|
||||||
println("[${System.currentTimeMillis()}] HomeSongList")
|
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
|
||||||
@@ -71,33 +72,16 @@ fun HomeSongList() {
|
|||||||
var sortBy by rememberPreference(songSortByKey, SongSortBy.DateAdded)
|
var sortBy by rememberPreference(songSortByKey, SongSortBy.DateAdded)
|
||||||
var sortOrder by rememberPreference(songSortOrderKey, SortOrder.Descending)
|
var sortOrder by rememberPreference(songSortOrderKey, SortOrder.Descending)
|
||||||
|
|
||||||
val items by produceSaveableListState(
|
val items by produceSaveableState(
|
||||||
flowProvider = { Database.songs(sortBy, sortOrder) },
|
initialValue = emptyList(),
|
||||||
stateSaver = DetailedSongListSaver,
|
stateSaver = DetailedSongListSaver,
|
||||||
key1 = sortBy,
|
sortBy, sortOrder,
|
||||||
key2 = sortOrder
|
) {
|
||||||
)
|
Database
|
||||||
|
.songs(sortBy, sortOrder)
|
||||||
// var items by rememberSaveable(stateSaver = DetailedSongListSaver) {
|
.flowOn(Dispatchers.IO)
|
||||||
// mutableStateOf(emptyList())
|
.collect { value = it }
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// 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
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
val sortOrderIconRotation by animateFloatAsState(
|
val sortOrderIconRotation by animateFloatAsState(
|
||||||
targetValue = if (sortOrder == SortOrder.Ascending) 0f else 180f,
|
targetValue = if (sortOrder == SortOrder.Ascending) 0f else 180f,
|
||||||
@@ -110,8 +94,6 @@ fun HomeSongList() {
|
|||||||
.background(colorPalette.background0)
|
.background(colorPalette.background0)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
// println("[${System.currentTimeMillis()}] LazyColumn")
|
|
||||||
|
|
||||||
item(
|
item(
|
||||||
key = "header",
|
key = "header",
|
||||||
contentType = 0
|
contentType = 0
|
||||||
@@ -174,10 +156,8 @@ fun HomeSongList() {
|
|||||||
song = song,
|
song = song,
|
||||||
thumbnailSize = thumbnailSize,
|
thumbnailSize = thumbnailSize,
|
||||||
onClick = {
|
onClick = {
|
||||||
items.map(DetailedSong::asMediaItem)?.let { mediaItems ->
|
binder?.stopRadio()
|
||||||
binder?.stopRadio()
|
binder?.player?.forcePlayAtIndex(items.map(DetailedSong::asMediaItem), index)
|
||||||
binder?.player?.forcePlayAtIndex(mediaItems, index)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
menuContent = {
|
menuContent = {
|
||||||
InHistoryMediaItemMenu(song = song)
|
InHistoryMediaItemMenu(song = song)
|
||||||
@@ -192,9 +172,7 @@ fun HomeSongList() {
|
|||||||
) {
|
) {
|
||||||
BasicText(
|
BasicText(
|
||||||
text = song.formattedTotalPlayTime,
|
text = song.formattedTotalPlayTime,
|
||||||
style = typography.xxs.semiBold.center.color(
|
style = typography.xxs.semiBold.center.color(Color.White),
|
||||||
Color.White
|
|
||||||
),
|
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -41,9 +41,15 @@ import it.vfsfitvnm.vimusic.utils.align
|
|||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlay
|
import it.vfsfitvnm.vimusic.utils.forcePlay
|
||||||
import it.vfsfitvnm.vimusic.utils.medium
|
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.secondary
|
||||||
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
|
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
|
@ExperimentalFoundationApi
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@@ -55,13 +61,16 @@ fun LocalSongSearch(
|
|||||||
val (colorPalette, typography) = LocalAppearance.current
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
|
||||||
val items by produceSaveableListState(
|
val items by produceSaveableState(
|
||||||
flowProvider = {
|
initialValue = emptyList(),
|
||||||
Database.search("%${textFieldValue.text}%")
|
|
||||||
},
|
|
||||||
stateSaver = DetailedSongListSaver,
|
stateSaver = DetailedSongListSaver,
|
||||||
key1 = textFieldValue.text
|
key1 = textFieldValue.text
|
||||||
)
|
) {
|
||||||
|
Database
|
||||||
|
.search("%${textFieldValue.text}%")
|
||||||
|
.flowOn(Dispatchers.IO)
|
||||||
|
.collect { value = it }
|
||||||
|
}
|
||||||
|
|
||||||
val thumbnailSize = Dimensions.thumbnails.song.px
|
val thumbnailSize = Dimensions.thumbnails.song.px
|
||||||
|
|
||||||
|
|||||||
@@ -52,12 +52,14 @@ import it.vfsfitvnm.vimusic.ui.components.themed.LoadingOrError
|
|||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.utils.align
|
import it.vfsfitvnm.vimusic.utils.align
|
||||||
import it.vfsfitvnm.vimusic.utils.medium
|
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.produceSaveableState
|
||||||
import it.vfsfitvnm.vimusic.utils.secondary
|
import it.vfsfitvnm.vimusic.utils.secondary
|
||||||
import it.vfsfitvnm.youtubemusic.YouTube
|
import it.vfsfitvnm.youtubemusic.YouTube
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun OnlineSearch(
|
fun OnlineSearch(
|
||||||
@@ -69,17 +71,18 @@ fun OnlineSearch(
|
|||||||
) {
|
) {
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
|
|
||||||
val history by produceSaveableListState(
|
val history by produceSaveableState(
|
||||||
flowProvider = {
|
initialValue = emptyList(),
|
||||||
Database.queries("%${textFieldValue.text}%").distinctUntilChanged { old, new ->
|
|
||||||
old.size == new.size
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stateSaver = SearchQueryListSaver,
|
stateSaver = SearchQueryListSaver,
|
||||||
key1 = textFieldValue.text
|
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,
|
initialValue = null,
|
||||||
stateSaver = StringListResultSaver,
|
stateSaver = StringListResultSaver,
|
||||||
key1 = textFieldValue.text
|
key1 = textFieldValue.text
|
||||||
|
|||||||
@@ -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.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextCard
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextCard
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SearchResultLoadingOrError
|
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 it.vfsfitvnm.youtubemusic.YouTube
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -41,11 +41,10 @@ inline fun <T : YouTube.Item> SearchResult(
|
|||||||
mutableStateOf(listOf())
|
mutableStateOf(listOf())
|
||||||
}
|
}
|
||||||
|
|
||||||
val (continuationResultState, fetch) = produceSaveableRelaunchableState(
|
val (continuationResultState, fetch) = produceSaveableRelaunchableOneShotState(
|
||||||
initialValue = null,
|
initialValue = null,
|
||||||
stateSaver = StringResultSaver,
|
stateSaver = StringResultSaver,
|
||||||
key1 = query,
|
query, filter
|
||||||
key2 = filter
|
|
||||||
) {
|
) {
|
||||||
val token = value?.getOrNull()
|
val token = value?.getOrNull()
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
|
|||||||
val thumbnailSizeDp = Dimensions.thumbnails.song
|
val thumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
val thumbnailSizePx = thumbnailSizeDp.px
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
SearchResult<YouTube.Item.Song>(
|
SearchResult(
|
||||||
query = query,
|
query = query,
|
||||||
filter = searchFilter,
|
filter = searchFilter,
|
||||||
onSearchAgain = onSearchAgain,
|
onSearchAgain = onSearchAgain,
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:OptIn(ExperimentalTypeInference::class)
|
||||||
|
|
||||||
package it.vfsfitvnm.vimusic.utils
|
package it.vfsfitvnm.vimusic.utils
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -14,27 +16,23 @@ import kotlin.coroutines.CoroutineContext
|
|||||||
import kotlin.experimental.ExperimentalTypeInference
|
import kotlin.experimental.ExperimentalTypeInference
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
|
||||||
@OptIn(ExperimentalTypeInference::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T> produceSaveableState(
|
fun <T> produceSaveableState(
|
||||||
initialValue: T,
|
initialValue: T,
|
||||||
stateSaver: Saver<T, out Any>,
|
stateSaver: Saver<T, out Any>,
|
||||||
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
|
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
|
||||||
): State<T> {
|
): State<T> {
|
||||||
val result = rememberSaveable(stateSaver = stateSaver) { mutableStateOf(initialValue) }
|
val result = rememberSaveable(stateSaver = stateSaver) {
|
||||||
|
mutableStateOf(initialValue)
|
||||||
var hasToFetch by rememberSaveable { mutableStateOf(true) }
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (hasToFetch) {
|
ProduceSaveableStateScope(result, coroutineContext).producer()
|
||||||
ProduceSaveableStateScope(result, coroutineContext).producer()
|
|
||||||
hasToFetch = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalTypeInference::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T> produceSaveableState(
|
fun <T> produceSaveableState(
|
||||||
initialValue: T,
|
initialValue: T,
|
||||||
@@ -42,20 +40,42 @@ fun <T> produceSaveableState(
|
|||||||
key1: Any?,
|
key1: Any?,
|
||||||
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
|
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
|
||||||
): State<T> {
|
): State<T> {
|
||||||
val result = rememberSaveable(stateSaver = stateSaver) { mutableStateOf(initialValue) }
|
val state = rememberSaveable(stateSaver = stateSaver) {
|
||||||
|
mutableStateOf(initialValue)
|
||||||
var hasToFetch by rememberSaveable(key1) { mutableStateOf(true) }
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1) {
|
LaunchedEffect(key1) {
|
||||||
if (hasToFetch) {
|
ProduceSaveableStateScope(state, coroutineContext).producer()
|
||||||
ProduceSaveableStateScope(result, coroutineContext).producer()
|
|
||||||
hasToFetch = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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
|
@Composable
|
||||||
fun <T> produceSaveableState(
|
fun <T> produceSaveableState(
|
||||||
initialValue: T,
|
initialValue: T,
|
||||||
@@ -64,42 +84,42 @@ fun <T> produceSaveableState(
|
|||||||
key2: Any?,
|
key2: Any?,
|
||||||
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
|
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
|
||||||
): State<T> {
|
): 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(key1, key2) {
|
||||||
|
ProduceSaveableStateScope(result, coroutineContext).producer()
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
if (hasToFetch) {
|
|
||||||
ProduceSaveableStateScope(result, coroutineContext).producer()
|
|
||||||
hasToFetch = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalTypeInference::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T> produceSaveableRelaunchableState(
|
fun <T> produceSaveableRelaunchableOneShotState(
|
||||||
initialValue: T,
|
initialValue: T,
|
||||||
stateSaver: Saver<T, out Any>,
|
stateSaver: Saver<T, out Any>,
|
||||||
key1: Any?,
|
key1: Any?,
|
||||||
key2: Any?,
|
key2: Any?,
|
||||||
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
|
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
|
||||||
): Pair<State<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) {
|
val relaunchableEffect = relaunchableEffect(key1, key2) {
|
||||||
if (hasToFetch) {
|
if (!produced) {
|
||||||
ProduceSaveableStateScope(result, coroutineContext).producer()
|
ProduceSaveableStateScope(result, coroutineContext).producer()
|
||||||
hasToFetch = false
|
produced = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result to {
|
return result to {
|
||||||
hasToFetch = true
|
produced = false
|
||||||
relaunchableEffect()
|
relaunchableEffect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user