diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/Album.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/Album.kt index 75cc575..57f6827 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/Album.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/Album.kt @@ -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 ) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumOverview.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumOverview.kt index fdffff9..65520d5 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumOverview.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumOverview.kt @@ -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?, 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() diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumScreen.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumScreen.kt index 530a211..dad80ec 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumScreen.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumScreen.kt @@ -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::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> { +// override fun restore(value: List) = AlbumScreenState( +// initialIsLoading = value[0] as Boolean, +// initialError = value[1] as Throwable?, +// initialAlbum = (value[1] as List?)?.let(AlbumSaver::restore), +// ) +// +// override fun SaverScope.save(value: AlbumScreenState): List = +// 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) } } } diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/artist/ArtistScreen.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/artist/ArtistScreen.kt index e281687..ca61383 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/artist/ArtistScreen.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/artist/ArtistScreen.kt @@ -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) { diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeAlbumList.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeAlbumList.kt index b025763..8903be3 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeAlbumList.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeAlbumList.kt @@ -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 diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeArtistList.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeArtistList.kt index 9730504..0668205 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeArtistList.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeArtistList.kt @@ -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 diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomePlaylistList.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomePlaylistList.kt index 936ed92..7d49b05 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomePlaylistList.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomePlaylistList.kt @@ -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, diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeSongList.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeSongList.kt index f77f25a..2e46bd5 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeSongList.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeSongList.kt @@ -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 diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/LocalSongSearch.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/LocalSongSearch.kt index 243d5b2..200c7ec 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/LocalSongSearch.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/LocalSongSearch.kt @@ -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) +//fun Flow.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 diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/OnlineSearch.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/OnlineSearch.kt index b690e50..35b091a 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/OnlineSearch.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/OnlineSearch.kt @@ -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 diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/searchresult/SearchResult.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/searchresult/SearchResult.kt index 0750a88..9c039c7 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/searchresult/SearchResult.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/searchresult/SearchResult.kt @@ -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 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() diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/searchresult/SearchResultScreen.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/searchresult/SearchResultScreen.kt index ec8f8ab..4b3ce98 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/searchresult/SearchResultScreen.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/searchresult/SearchResultScreen.kt @@ -90,7 +90,7 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) { val thumbnailSizeDp = Dimensions.thumbnails.song val thumbnailSizePx = thumbnailSizeDp.px - SearchResult( + SearchResult( query = query, filter = searchFilter, onSearchAgain = onSearchAgain, diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/ProduceSaveableListState.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/ProduceSaveableListState.kt deleted file mode 100644 index f765c76..0000000 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/ProduceSaveableListState.kt +++ /dev/null @@ -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 produceSaveableListState( - flowProvider: () -> Flow>, - stateSaver: ListSaver>, -): State> { - 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 produceSaveableListState( - flowProvider: () -> Flow>, - stateSaver: ListSaver>, - key1: Any?, -): State> { - 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 produceSaveableListState( - flowProvider: () -> Flow>, - stateSaver: ListSaver>, - key1: Any?, - key2: Any?, -): State> { - 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 -} diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/ProduceSaveableState.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/ProduceSaveableState.kt index 720ddac..0b73804 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/ProduceSaveableState.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/ProduceSaveableState.kt @@ -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 produceSaveableState( initialValue: T, stateSaver: Saver, @BuilderInference producer: suspend ProduceStateScope.() -> Unit ): State { - 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 produceSaveableState( initialValue: T, @@ -42,20 +40,42 @@ fun produceSaveableState( key1: Any?, @BuilderInference producer: suspend ProduceStateScope.() -> Unit ): State { - 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 produceSaveableOneShotState( + initialValue: T, + stateSaver: Saver, + key1: Any?, + @BuilderInference producer: suspend ProduceStateScope.() -> Unit +): State { + 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 produceSaveableState( initialValue: T, @@ -64,42 +84,42 @@ fun produceSaveableState( key2: Any?, @BuilderInference producer: suspend ProduceStateScope.() -> Unit ): State { - 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 produceSaveableRelaunchableState( +fun produceSaveableRelaunchableOneShotState( initialValue: T, stateSaver: Saver, key1: Any?, key2: Any?, @BuilderInference producer: suspend ProduceStateScope.() -> Unit ): Pair, () -> 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() } } diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/RememberLazyListStates.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/RememberLazyListStates.kt deleted file mode 100644 index c914a7f..0000000 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/RememberLazyListStates.kt +++ /dev/null @@ -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 { - return rememberSaveable( - saver = listSaver( - save = { states: List -> - 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 { - return rememberSaveable( - saver = listSaver( - save = { states: List -> - 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) } - } -}