Attempt fix #401

This commit is contained in:
vfsfitvnm 2022-10-26 13:08:45 +02:00
parent f4a55ff2ad
commit ed8eb7cf8a
64 changed files with 373 additions and 944 deletions

View file

@ -70,6 +70,7 @@ kapt {
}
dependencies {
implementation(projects.composePersist)
implementation(projects.composeRouting)
implementation(projects.composeReordering)

View file

@ -58,6 +58,8 @@ import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import com.valentinilk.shimmer.LocalShimmerTheme
import com.valentinilk.shimmer.defaultShimmerTheme
import it.vfsfitvnm.compose.persist.PersistMap
import it.vfsfitvnm.compose.persist.PersistMapOwner
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.bodies.BrowseBody
import it.vfsfitvnm.innertube.requests.playlistPage
@ -99,7 +101,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MainActivity : ComponentActivity() {
class MainActivity : ComponentActivity(), PersistMapOwner {
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
if (service is PlayerService.Binder) {
@ -114,20 +116,20 @@ class MainActivity : ComponentActivity() {
private var binder by mutableStateOf<PlayerService.Binder?>(null)
override lateinit var persistMap: PersistMap
override fun onStart() {
super.onStart()
bindService(intent<PlayerService>(), serviceConnection, Context.BIND_AUTO_CREATE)
}
override fun onStop() {
unbindService(serviceConnection)
super.onStop()
}
@OptIn(ExperimentalFoundationApi::class, ExperimentalAnimationApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@Suppress("DEPRECATION", "UNCHECKED_CAST")
persistMap = lastCustomNonConfigurationInstance as? PersistMap ?: PersistMap()
WindowCompat.setDecorFitsSystemWindows(window, false)
val launchedFromNotification = intent?.extras?.getBoolean("expandPlayerBottomSheet") == true
@ -451,6 +453,21 @@ class MainActivity : ComponentActivity() {
}
}
override fun onRetainCustomNonConfigurationInstance() = persistMap
override fun onStop() {
unbindService(serviceConnection)
super.onStop()
}
override fun onDestroy() {
if (!isChangingConfigurations) {
persistMap.clear()
}
super.onDestroy()
}
private fun setSystemBarAppearance(isDark: Boolean) {
with(WindowCompat.getInsetsController(window, window.decorView.rootView)) {
isAppearanceLightStatusBars = !isDark

View file

@ -1,31 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.vimusic.models.Album
object AlbumSaver : Saver<Album, List<Any?>> {
override fun SaverScope.save(value: Album): List<Any?> = listOf(
value.id,
value.title,
value.thumbnailUrl,
value.year,
value.authorsText,
value.shareUrl,
value.timestamp,
value.bookmarkedAt,
)
override fun restore(value: List<Any?>): Album = Album(
id = value[0] as String,
title = value[1] as String,
thumbnailUrl = value[2] as String?,
year = value[3] as String?,
authorsText = value[4] as String?,
shareUrl = value[5] as String?,
timestamp = value[6] as Long?,
bookmarkedAt = value[7] as Long?,
)
}
val AlbumListSaver = listSaver(AlbumSaver)

View file

@ -1,25 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.vimusic.models.Artist
object ArtistSaver : Saver<Artist, List<Any?>> {
override fun SaverScope.save(value: Artist): List<Any?> = listOf(
value.id,
value.name,
value.thumbnailUrl,
value.timestamp,
value.bookmarkedAt,
)
override fun restore(value: List<Any?>): Artist = Artist(
id = value[0] as String,
name = value[1] as String?,
thumbnailUrl = value[2] as String?,
timestamp = value[3] as Long?,
bookmarkedAt = value[4] as Long?,
)
}
val ArtistListSaver = listSaver(ArtistSaver)

View file

@ -1,33 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.vimusic.models.DetailedSong
object DetailedSongSaver : Saver<DetailedSong, List<Any?>> {
override fun SaverScope.save(value: DetailedSong) =
listOf(
value.id,
value.title,
value.artistsText,
value.durationText,
value.thumbnailUrl,
value.totalPlayTimeMs,
value.albumId,
value.artists?.let { with(InfoListSaver) { save(it) } }
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = DetailedSong(
id = value[0] as String,
title = value[1] as String,
artistsText = value[2] as String?,
durationText = value[3] as String?,
thumbnailUrl = value[4] as String?,
totalPlayTimeMs = value[5] as Long,
albumId = value[6] as String?,
artists = (value[7] as List<List<String>>?)?.let(InfoListSaver::restore)
)
}
val DetailedSongListSaver = listSaver(DetailedSongSaver)

View file

@ -1,13 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.vimusic.models.Info
object InfoSaver : Saver<Info, List<String?>> {
override fun SaverScope.save(value: Info) = listOf(value.id, value.name)
override fun restore(value: List<String?>) = Info(id = value[0] as String, name = value[1])
}
val InfoListSaver = listSaver(InfoSaver)

View file

@ -1,24 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.innertube.Innertube
object InnertubeAlbumItemSaver : Saver<Innertube.AlbumItem, List<Any?>> {
override fun SaverScope.save(value: Innertube.AlbumItem): List<Any?> = listOf(
value.info?.let { with(InnertubeBrowseInfoSaver) { save(it) } },
value.authors?.let { with(InnertubeBrowseInfoListSaver) { save(it) } },
value.year,
value.thumbnail?.let { with(InnertubeThumbnailSaver) { save(it) } }
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = Innertube.AlbumItem(
info = (value[0] as List<Any?>?)?.let(InnertubeBrowseInfoSaver::restore),
authors = (value[1] as List<List<Any?>>?)?.let(InnertubeBrowseInfoListSaver::restore),
year = value[2] as String?,
thumbnail = (value[3] as List<Any?>?)?.let(InnertubeThumbnailSaver::restore)
)
}
val InnertubeAlbumItemListSaver = listSaver(InnertubeAlbumItemSaver)

View file

@ -1,21 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.innertube.Innertube
object InnertubeArtistItemSaver : Saver<Innertube.ArtistItem, List<Any?>> {
override fun SaverScope.save(value: Innertube.ArtistItem): List<Any?> = listOf(
value.info?.let { with(InnertubeBrowseInfoSaver) { save(it) } },
value.subscribersCountText,
value.thumbnail?.let { with(InnertubeThumbnailSaver) { save(it) } }
)
override fun restore(value: List<Any?>) = Innertube.ArtistItem(
info = (value[0] as List<Any?>?)?.let(InnertubeBrowseInfoSaver::restore),
subscribersCountText = value[1] as String?,
thumbnail = (value[2] as List<Any?>?)?.let(InnertubeThumbnailSaver::restore)
)
}
val InnertubeArtistItemListSaver = listSaver(InnertubeArtistItemSaver)

View file

@ -1,36 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.innertube.Innertube
object InnertubeArtistPageSaver : Saver<Innertube.ArtistPage, List<Any?>> {
override fun SaverScope.save(value: Innertube.ArtistPage) = listOf(
value.name,
value.description,
value.thumbnail?.let { with(InnertubeThumbnailSaver) { save(it) } },
value.shuffleEndpoint?.let { with(InnertubeWatchEndpointSaver) { save(it) } },
value.radioEndpoint?.let { with(InnertubeWatchEndpointSaver) { save(it) } },
value.songs?.let { with(InnertubeSongItemListSaver) { save(it) } },
value.songsEndpoint?.let { with(InnertubeBrowseEndpointSaver) { save(it) } },
value.albums?.let { with(InnertubeAlbumItemListSaver) { save(it) } },
value.albumsEndpoint?.let { with(InnertubeBrowseEndpointSaver) { save(it) } },
value.singles?.let { with(InnertubeAlbumItemListSaver) { save(it) } },
value.singlesEndpoint?.let { with(InnertubeBrowseEndpointSaver) { save(it) } },
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = Innertube.ArtistPage(
name = value[0] as String?,
description = value[1] as String?,
thumbnail = (value[2] as List<Any?>?)?.let(InnertubeThumbnailSaver::restore),
shuffleEndpoint = (value[3] as List<Any?>?)?.let(InnertubeWatchEndpointSaver::restore),
radioEndpoint = (value[4] as List<Any?>?)?.let(InnertubeWatchEndpointSaver::restore),
songs = (value[5] as List<List<Any?>>?)?.let(InnertubeSongItemListSaver::restore),
songsEndpoint = (value[6] as List<Any?>?)?.let(InnertubeBrowseEndpointSaver::restore),
albums = (value[7] as List<List<Any?>>?)?.let(InnertubeAlbumItemListSaver::restore),
albumsEndpoint = (value[8] as List<Any?>?)?.let(InnertubeBrowseEndpointSaver::restore),
singles = (value[9] as List<List<Any?>>?)?.let(InnertubeAlbumItemListSaver::restore),
singlesEndpoint = (value[10] as List<Any?>?)?.let(InnertubeBrowseEndpointSaver::restore),
)
}

View file

@ -1,18 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.innertube.models.NavigationEndpoint
object InnertubeBrowseEndpointSaver : Saver<NavigationEndpoint.Endpoint.Browse, List<Any?>> {
override fun SaverScope.save(value: NavigationEndpoint.Endpoint.Browse) = listOf(
value.browseId,
value.params
)
override fun restore(value: List<Any?>) = NavigationEndpoint.Endpoint.Browse(
browseId = value[0] as String,
params = value[1] as String?,
browseEndpointContextSupportedConfigs = null
)
}

View file

@ -1,20 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.NavigationEndpoint
object InnertubeBrowseInfoSaver : Saver<Innertube.Info<NavigationEndpoint.Endpoint.Browse>, List<Any?>> {
override fun SaverScope.save(value: Innertube.Info<NavigationEndpoint.Endpoint.Browse>) = listOf(
value.name,
value.endpoint?.let { with(InnertubeBrowseEndpointSaver) { save(it) } }
)
override fun restore(value: List<Any?>) = Innertube.Info(
name = value[0] as String?,
endpoint = (value[1] as List<Any?>?)?.let(InnertubeBrowseEndpointSaver::restore)
)
}
val InnertubeBrowseInfoListSaver = listSaver(InnertubeBrowseInfoSaver)

View file

@ -1,4 +0,0 @@
package it.vfsfitvnm.vimusic.savers
val InnertubeSongsPageSaver = innertubeItemsPageSaver(InnertubeSongItemListSaver)
val InnertubeAlbumsPageSaver = innertubeItemsPageSaver(InnertubeAlbumItemListSaver)

View file

@ -1,23 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.innertube.Innertube
object InnertubePlaylistItemSaver : Saver<Innertube.PlaylistItem, List<Any?>> {
override fun SaverScope.save(value: Innertube.PlaylistItem): List<Any?> = listOf(
value.info?.let { with(InnertubeBrowseInfoSaver) { save(it) } },
value.channel?.let { with(InnertubeBrowseInfoSaver) { save(it) } },
value.songCount,
value.thumbnail?.let { with(InnertubeThumbnailSaver) { save(it) } }
)
override fun restore(value: List<Any?>) = Innertube.PlaylistItem(
info = (value[0] as List<Any?>?)?.let(InnertubeBrowseInfoSaver::restore),
channel = (value[1] as List<Any?>?)?.let(InnertubeBrowseInfoSaver::restore),
songCount = value[2] as Int?,
thumbnail = (value[3] as List<Any?>?)?.let(InnertubeThumbnailSaver::restore)
)
}
val InnertubePlaylistItemListSaver = listSaver(InnertubePlaylistItemSaver)

View file

@ -1,28 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.innertube.Innertube
object InnertubePlaylistOrAlbumPageSaver : Saver<Innertube.PlaylistOrAlbumPage, List<Any?>> {
override fun SaverScope.save(value: Innertube.PlaylistOrAlbumPage): List<Any?> = listOf(
value.title,
value.authors?.let { with(InnertubeBrowseInfoListSaver) { save(it) } },
value.year,
value.thumbnail?.let { with(InnertubeThumbnailSaver) { save(it) } } ,
value.url,
value.songsPage?.let { with(InnertubeSongsPageSaver) { save(it) } },
value.otherVersions?.let { with(InnertubeAlbumItemListSaver) { save(it) } },
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = Innertube.PlaylistOrAlbumPage(
title = value[0] as String?,
authors = (value[1] as List<List<Any?>>?)?.let(InnertubeBrowseInfoListSaver::restore),
year = value[2] as String?,
thumbnail = (value[3] as List<Any?>?)?.let(InnertubeThumbnailSaver::restore),
url = value[4] as String?,
songsPage = (value[5] as List<Any?>?)?.let(InnertubeSongsPageSaver::restore),
otherVersions = (value[6] as List<List<Any?>>?)?.let(InnertubeAlbumItemListSaver::restore),
)
}

View file

@ -1,22 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.innertube.Innertube
object InnertubeRelatedPageSaver : Saver<Innertube.RelatedPage, List<Any?>> {
override fun SaverScope.save(value: Innertube.RelatedPage): List<Any?> = listOf(
value.songs?.let { with(InnertubeSongItemListSaver) { save(it) } },
value.playlists?.let { with(InnertubePlaylistItemListSaver) { save(it) } },
value.albums?.let { with(InnertubeAlbumItemListSaver) { save(it) } },
value.artists?.let { with(InnertubeArtistItemListSaver) { save(it) } },
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = Innertube.RelatedPage(
songs = (value[0] as List<List<Any?>>?)?.let(InnertubeSongItemListSaver::restore),
playlists = (value[1] as List<List<Any?>>?)?.let(InnertubePlaylistItemListSaver::restore),
albums = (value[2] as List<List<Any?>>?)?.let(InnertubeAlbumItemListSaver::restore),
artists = (value[3] as List<List<Any?>>?)?.let(InnertubeArtistItemListSaver::restore),
)
}

View file

@ -1,26 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.innertube.Innertube
object InnertubeSongItemSaver : Saver<Innertube.SongItem, List<Any?>> {
override fun SaverScope.save(value: Innertube.SongItem): List<Any?> = listOf(
value.info?.let { with(InnertubeWatchInfoSaver) { save(it) } },
value.authors?.let { with(InnertubeBrowseInfoListSaver) { save(it) } },
value.album?.let { with(InnertubeBrowseInfoSaver) { save(it) } },
value.durationText,
value.thumbnail?.let { with(InnertubeThumbnailSaver) { save(it) } }
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = Innertube.SongItem(
info = (value[0] as List<Any?>?)?.let(InnertubeWatchInfoSaver::restore),
authors = (value[1] as List<List<Any?>>?)?.let(InnertubeBrowseInfoListSaver::restore),
album = (value[2] as List<Any?>?)?.let(InnertubeBrowseInfoSaver::restore),
durationText = value[3] as String?,
thumbnail = (value[4] as List<Any?>?)?.let(InnertubeThumbnailSaver::restore)
)
}
val InnertubeSongItemListSaver = listSaver(InnertubeSongItemSaver)

View file

@ -1,19 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.innertube.models.Thumbnail
object InnertubeThumbnailSaver : Saver<Thumbnail, List<Any?>> {
override fun SaverScope.save(value: Thumbnail) = listOf(
value.url,
value.width,
value.height
)
override fun restore(value: List<Any?>) = Thumbnail(
url = value[0] as String,
width = value[1] as Int,
height = value[2] as Int?,
)
}

View file

@ -1,26 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.innertube.Innertube
object InnertubeVideoItemSaver : Saver<Innertube.VideoItem, List<Any?>> {
override fun SaverScope.save(value: Innertube.VideoItem): List<Any?> = listOf(
value.info?.let { with(InnertubeWatchInfoSaver) { save(it) } },
value.authors?.let { with(InnertubeBrowseInfoListSaver) { save(it) } },
value.viewsText,
value.durationText,
value.thumbnail?.let { with(InnertubeThumbnailSaver) { save(it) } }
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = Innertube.VideoItem(
info = (value[0] as List<Any?>?)?.let(InnertubeWatchInfoSaver::restore),
authors = (value[1] as List<List<Any?>>?)?.let(InnertubeBrowseInfoListSaver::restore),
viewsText = value[2] as String?,
durationText = value[3] as String?,
thumbnail = (value[4] as List<Any?>?)?.let(InnertubeThumbnailSaver::restore)
)
}
val InnertubeVideoItemListSaver = listSaver(InnertubeVideoItemSaver)

View file

@ -1,24 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.innertube.models.NavigationEndpoint
object InnertubeWatchEndpointSaver : Saver<NavigationEndpoint.Endpoint.Watch, List<Any?>> {
override fun SaverScope.save(value: NavigationEndpoint.Endpoint.Watch) = listOf(
value.params,
value.playlistId,
value.videoId,
value.index,
value.playlistSetVideoId,
)
override fun restore(value: List<Any?>) = NavigationEndpoint.Endpoint.Watch(
params = value[0] as String?,
playlistId = value[1] as String?,
videoId = value[2] as String?,
index = value[3] as Int?,
playlistSetVideoId = value[4] as String?,
watchEndpointMusicSupportedConfigs = null
)
}

View file

@ -1,18 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.NavigationEndpoint
object InnertubeWatchInfoSaver : Saver<Innertube.Info<NavigationEndpoint.Endpoint.Watch>, List<Any?>> {
override fun SaverScope.save(value: Innertube.Info<NavigationEndpoint.Endpoint.Watch>) = listOf(
value.name,
value.endpoint?.let { with(InnertubeWatchEndpointSaver) { save(it) } },
)
override fun restore(value: List<Any?>) = Innertube.Info(
name = value[0] as String?,
endpoint = (value[1] as List<Any?>?)?.let(InnertubeWatchEndpointSaver::restore)
)
}

View file

@ -1,19 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.vimusic.models.PlaylistPreview
object PlaylistPreviewSaver : Saver<PlaylistPreview, List<Any>> {
override fun SaverScope.save(value: PlaylistPreview) = listOf(
with(PlaylistSaver) { save(value.playlist) },
value.songCount,
)
override fun restore(value: List<Any>) = PlaylistPreview(
playlist = PlaylistSaver.restore(value[0] as List<Any?>),
songCount = value[1] as Int,
)
}
val PlaylistPreviewListSaver = listSaver(PlaylistPreviewSaver)

View file

@ -1,19 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.vimusic.models.Playlist
object PlaylistSaver : Saver<Playlist, List<Any?>> {
override fun SaverScope.save(value: Playlist): List<Any?> = listOf(
value.id,
value.name,
value.browseId,
)
override fun restore(value: List<Any?>): Playlist = Playlist(
id = value[0] as Long,
name = value[1] as String,
browseId = value[2] as String?,
)
}

View file

@ -1,18 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.vimusic.models.PlaylistWithSongs
object PlaylistWithSongsSaver : Saver<PlaylistWithSongs, List<Any>> {
override fun SaverScope.save(value: PlaylistWithSongs) = listOf(
with(PlaylistSaver) { save(value.playlist) },
with(DetailedSongListSaver) { save(value.songs) },
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any>): PlaylistWithSongs = PlaylistWithSongs(
playlist = PlaylistSaver.restore(value[0] as List<Any?>),
songs = DetailedSongListSaver.restore(value[1] as List<List<Any?>>)
)
}

View file

@ -1,52 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.innertube.Innertube
interface ListSaver<Original, Saveable : Any> : Saver<List<Original>, List<Saveable>> {
override fun SaverScope.save(value: List<Original>): List<Saveable>
override fun restore(value: List<Saveable>): List<Original>
}
fun <Original, Saveable : Any> resultSaver(saver: Saver<Original, Saveable>) =
object : Saver<Result<Original>?, Pair<Saveable?, Throwable?>> {
override fun restore(value: Pair<Saveable?, Throwable?>) =
value.first?.let(saver::restore)?.let(Result.Companion::success)
?: value.second?.let(Result.Companion::failure)
override fun SaverScope.save(value: Result<Original>?) =
with(saver) { value?.getOrNull()?.let { save(it) } } to value?.exceptionOrNull()
}
fun <Original, Saveable : Any> listSaver(saver: Saver<Original, Saveable>) =
object : ListSaver<Original, Saveable> {
override fun restore(value: List<Saveable>) =
value.mapNotNull(saver::restore)
override fun SaverScope.save(value: List<Original>) =
with(saver) { value.mapNotNull { save(it) } }
}
fun <Original, Saveable : Any> nullableSaver(saver: Saver<Original, Saveable>) =
object : Saver<Original?, Saveable> {
override fun SaverScope.save(value: Original?): Saveable? =
value?.let { with(saver) { save(it) } }
override fun restore(value: Saveable): Original? =
saver.restore(value)
}
fun <Original : Innertube.Item> innertubeItemsPageSaver(saver: ListSaver<Original, List<Any?>>) =
object : Saver<Innertube.ItemsPage<Original>, List<Any?>> {
override fun SaverScope.save(value: Innertube.ItemsPage<Original>) = listOf(
value.items?.let { with(saver) { save(it) } },
value.continuation
)
@Suppress("UNCHECKED_CAST")
override fun restore(value: List<Any?>) = Innertube.ItemsPage(
items = (value[0] as List<List<Any?>>?)?.let(saver::restore),
continuation = value[1] as String?
)
}

View file

@ -1,17 +0,0 @@
package it.vfsfitvnm.vimusic.savers
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import it.vfsfitvnm.vimusic.models.SearchQuery
object SearchQuerySaver : Saver<SearchQuery, List<Any?>> {
override fun SaverScope.save(value: SearchQuery): List<Any?> = listOf(
value.id,
value.query,
)
override fun restore(value: List<Any?>) = SearchQuery(
id = value[0] as Long,
query = value[1] as String
)
}

View file

@ -963,7 +963,6 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
private class NotificationActionReceiver(private val player: Player) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
println(intent.action)
when (intent.action) {
Action.pause.value -> player.pause()
Action.play.value -> player.play()

View file

@ -17,17 +17,17 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.valentinilk.shimmer.shimmer
import it.vfsfitvnm.compose.persist.PersistMapCleanup
import it.vfsfitvnm.compose.persist.persist
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.bodies.BrowseBody
import it.vfsfitvnm.innertube.requests.albumPage
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.query
import it.vfsfitvnm.vimusic.savers.AlbumSaver
import it.vfsfitvnm.vimusic.savers.InnertubeAlbumItemListSaver
import it.vfsfitvnm.vimusic.savers.InnertubePlaylistOrAlbumPageSaver
import it.vfsfitvnm.vimusic.savers.innertubeItemsPageSaver
import it.vfsfitvnm.vimusic.savers.nullableSaver
import it.vfsfitvnm.vimusic.ui.components.themed.Header
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
@ -41,9 +41,6 @@ import it.vfsfitvnm.vimusic.ui.screens.searchresult.ItemsPage
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.ui.styling.px
import it.vfsfitvnm.vimusic.utils.asMediaItem
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.bodies.BrowseBody
import it.vfsfitvnm.innertube.requests.albumPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.withContext
@ -58,13 +55,10 @@ fun AlbumScreen(browseId: String) {
mutableStateOf(0)
}
var album by rememberSaveable(stateSaver = nullableSaver(AlbumSaver)) {
mutableStateOf(null)
}
var album by persist<Album?>("album/$browseId/album")
var albumPage by persist<Innertube.PlaylistOrAlbumPage?>("album/$browseId/albumPage")
var albumPage by rememberSaveable(stateSaver = nullableSaver(InnertubePlaylistOrAlbumPageSaver)) {
mutableStateOf(null)
}
PersistMapCleanup(tagPrefix = "album/$browseId/")
LaunchedEffect(Unit) {
Database
@ -205,7 +199,7 @@ fun AlbumScreen(browseId: String) {
val thumbnailSizePx = thumbnailSizeDp.px
ItemsPage(
stateSaver = innertubeItemsPageSaver(InnertubeAlbumItemListSaver),
tag = "album/$browseId/alternatives",
headerContent = headerContent,
initialPlaceholderCount = 1,
continuationPlaceholderCount = 1,

View file

@ -6,26 +6,28 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import it.vfsfitvnm.compose.persist.persistList
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.only
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
@ -43,10 +45,7 @@ import it.vfsfitvnm.vimusic.utils.enqueue
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
import it.vfsfitvnm.vimusic.utils.isLandscape
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.semiBold
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
@ExperimentalAnimationApi
@ExperimentalFoundationApi
@ -60,14 +59,10 @@ fun AlbumSongs(
val binder = LocalPlayerServiceBinder.current
val menuState = LocalMenuState.current
val songs by produceSaveableState(
initialValue = emptyList(),
stateSaver = DetailedSongListSaver
) {
Database
.albumSongs(browseId)
.flowOn(Dispatchers.IO)
.collect { value = it }
var songs by persistList<DetailedSong>("album/$browseId/songs")
LaunchedEffect(Unit) {
Database.albumSongs(browseId).collect { songs = it }
}
val thumbnailSizeDp = Dimensions.thumbnails.song

View file

@ -6,24 +6,25 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import it.vfsfitvnm.compose.persist.persist
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.only
import androidx.compose.ui.Alignment
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
import it.vfsfitvnm.vimusic.savers.nullableSaver
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
@ -39,9 +40,6 @@ import it.vfsfitvnm.vimusic.utils.asMediaItem
import it.vfsfitvnm.vimusic.utils.enqueue
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
@ExperimentalFoundationApi
@ExperimentalAnimationApi
@ -55,14 +53,10 @@ fun ArtistLocalSongs(
val (colorPalette) = LocalAppearance.current
val menuState = LocalMenuState.current
val songs by produceSaveableState(
initialValue = null,
stateSaver = nullableSaver(DetailedSongListSaver)
) {
Database
.artistSongs(browseId)
.flowOn(Dispatchers.IO)
.collect { value = it }
var songs by persist<List<DetailedSong>?>("artist/$browseId/localSongs")
LaunchedEffect(Unit) {
Database.artistSongs(browseId).collect { songs = it }
}
val songThumbnailSizeDp = Dimensions.thumbnails.song

View file

@ -26,6 +26,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.NavigationEndpoint
import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
@ -49,8 +51,6 @@ import it.vfsfitvnm.vimusic.utils.color
import it.vfsfitvnm.vimusic.utils.forcePlay
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.NavigationEndpoint
@ExperimentalFoundationApi
@ExperimentalAnimationApi

View file

@ -10,8 +10,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
@ -19,17 +17,20 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.valentinilk.shimmer.shimmer
import it.vfsfitvnm.compose.persist.PersistMapCleanup
import it.vfsfitvnm.compose.persist.persist
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.bodies.BrowseBody
import it.vfsfitvnm.innertube.models.bodies.ContinuationBody
import it.vfsfitvnm.innertube.requests.artistPage
import it.vfsfitvnm.innertube.requests.itemsPage
import it.vfsfitvnm.innertube.utils.from
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.Artist
import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.savers.ArtistSaver
import it.vfsfitvnm.vimusic.savers.InnertubeAlbumsPageSaver
import it.vfsfitvnm.vimusic.savers.InnertubeArtistPageSaver
import it.vfsfitvnm.vimusic.savers.InnertubeSongsPageSaver
import it.vfsfitvnm.vimusic.savers.nullableSaver
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.themed.Header
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
@ -51,12 +52,6 @@ import it.vfsfitvnm.vimusic.utils.artistScreenTabIndexKey
import it.vfsfitvnm.vimusic.utils.asMediaItem
import it.vfsfitvnm.vimusic.utils.forcePlay
import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.bodies.BrowseBody
import it.vfsfitvnm.innertube.models.bodies.ContinuationBody
import it.vfsfitvnm.innertube.requests.artistPage
import it.vfsfitvnm.innertube.requests.itemsPage
import it.vfsfitvnm.innertube.utils.from
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@ -71,13 +66,11 @@ fun ArtistScreen(browseId: String) {
var tabIndex by rememberPreference(artistScreenTabIndexKey, defaultValue = 0)
var artist by rememberSaveable(stateSaver = nullableSaver(ArtistSaver)) {
mutableStateOf(null)
}
PersistMapCleanup(tagPrefix = "artist/$browseId/")
var artistPage by rememberSaveable(stateSaver = nullableSaver(InnertubeArtistPageSaver)) {
mutableStateOf(null)
}
var artist by persist<Artist?>("artist/$browseId/artist")
var artistPage by persist<Innertube.ArtistPage?>("artist/$browseId/artistPage")
LaunchedEffect(Unit) {
Database
@ -209,7 +202,7 @@ fun ArtistScreen(browseId: String) {
val thumbnailSizePx = thumbnailSizeDp.px
ItemsPage(
stateSaver = InnertubeSongsPageSaver,
tag = "artist/$browseId/songs",
headerContent = headerContent,
itemsPageProvider = artistPage?.let {
({ continuation ->
@ -272,7 +265,7 @@ fun ArtistScreen(browseId: String) {
val thumbnailSizePx = thumbnailSizeDp.px
ItemsPage(
stateSaver = InnertubeAlbumsPageSaver,
tag = "artist/$browseId/albums",
headerContent = headerContent,
emptyItemsText = "This artist didn't release any album",
itemsPageProvider = artistPage?.let {
@ -322,7 +315,7 @@ fun ArtistScreen(browseId: String) {
val thumbnailSizePx = thumbnailSizeDp.px
ItemsPage(
stateSaver = InnertubeAlbumsPageSaver,
tag = "artist/$browseId/singles",
headerContent = headerContent,
emptyItemsText = "This artist didn't release any single",
itemsPageProvider = artistPage?.let {

View file

@ -6,6 +6,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import it.vfsfitvnm.compose.persist.PersistMapCleanup
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.BuiltInPlaylist
@ -25,6 +26,8 @@ fun BuiltInPlaylistScreen(builtInPlaylist: BuiltInPlaylist) {
})
}
PersistMapCleanup(tagPrefix = "${builtInPlaylist.name}/")
RouteHandler(listenToGlobalEmitter = true) {
globalRoutes()

View file

@ -15,16 +15,18 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import it.vfsfitvnm.compose.persist.persistList
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.BuiltInPlaylist
import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
import it.vfsfitvnm.vimusic.ui.components.themed.Header
@ -39,7 +41,6 @@ import it.vfsfitvnm.vimusic.utils.asMediaItem
import it.vfsfitvnm.vimusic.utils.enqueue
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@ -52,14 +53,13 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
val binder = LocalPlayerServiceBinder.current
val menuState = LocalMenuState.current
val songs by produceSaveableState(
initialValue = emptyList(),
stateSaver = DetailedSongListSaver
) {
var songs by persistList<DetailedSong>("${builtInPlaylist.name}/songs")
LaunchedEffect(Unit) {
when (builtInPlaylist) {
BuiltInPlaylist.Favorites -> Database
.favorites()
.flowOn(Dispatchers.IO)
BuiltInPlaylist.Offline -> Database
.songsWithContentLength()
.flowOn(Dispatchers.IO)
@ -70,7 +70,7 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
} ?: false
}
}
}.collect { value = it }
}.collect { songs = it }
}
val thumbnailSizeDp = Dimensions.thumbnails.song

View file

@ -15,21 +15,24 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import it.vfsfitvnm.compose.persist.persist
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.AlbumSortBy
import it.vfsfitvnm.vimusic.enums.SortOrder
import it.vfsfitvnm.vimusic.models.Album
import it.vfsfitvnm.vimusic.savers.AlbumListSaver
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
import it.vfsfitvnm.vimusic.ui.components.themed.Header
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
@ -39,10 +42,7 @@ 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.produceSaveableState
import it.vfsfitvnm.vimusic.utils.rememberPreference
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
@ExperimentalFoundationApi
@ExperimentalAnimationApi
@ -56,15 +56,10 @@ fun HomeAlbums(
var sortBy by rememberPreference(albumSortByKey, AlbumSortBy.DateAdded)
var sortOrder by rememberPreference(albumSortOrderKey, SortOrder.Descending)
val items by produceSaveableState(
initialValue = emptyList(),
stateSaver = AlbumListSaver,
sortBy, sortOrder,
) {
Database
.albums(sortBy, sortOrder)
.flowOn(Dispatchers.IO)
.collect { value = it }
var items by persist<List<Album>>(tag = "home/albums", emptyList())
LaunchedEffect(sortBy, sortOrder) {
Database.albums(sortBy, sortOrder).collect { items = it }
}
val thumbnailSizeDp = Dimensions.thumbnails.song * 2

View file

@ -10,7 +10,10 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
@ -18,22 +21,20 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import it.vfsfitvnm.compose.persist.persistList
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.only
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.ArtistSortBy
import it.vfsfitvnm.vimusic.enums.SortOrder
import it.vfsfitvnm.vimusic.models.Artist
import it.vfsfitvnm.vimusic.savers.ArtistListSaver
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
import it.vfsfitvnm.vimusic.ui.components.themed.Header
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
@ -43,10 +44,7 @@ import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.ui.styling.px
import it.vfsfitvnm.vimusic.utils.artistSortByKey
import it.vfsfitvnm.vimusic.utils.artistSortOrderKey
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.rememberPreference
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
@ExperimentalFoundationApi
@ExperimentalAnimationApi
@ -60,15 +58,10 @@ fun HomeArtistList(
var sortBy by rememberPreference(artistSortByKey, ArtistSortBy.DateAdded)
var sortOrder by rememberPreference(artistSortOrderKey, SortOrder.Descending)
val items by produceSaveableState(
initialValue = emptyList(),
stateSaver = ArtistListSaver,
sortBy, sortOrder,
) {
Database
.artists(sortBy, sortOrder)
.flowOn(Dispatchers.IO)
.collect { value = it }
var items by persistList<Artist>("home/artists")
LaunchedEffect(sortBy, sortOrder) {
Database.artists(sortBy, sortOrder).collect { items = it }
}
val thumbnailSizeDp = Dimensions.thumbnails.song * 2

View file

@ -21,6 +21,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
@ -29,6 +30,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import it.vfsfitvnm.compose.persist.persistList
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import it.vfsfitvnm.vimusic.R
@ -36,8 +38,8 @@ import it.vfsfitvnm.vimusic.enums.BuiltInPlaylist
import it.vfsfitvnm.vimusic.enums.PlaylistSortBy
import it.vfsfitvnm.vimusic.enums.SortOrder
import it.vfsfitvnm.vimusic.models.Playlist
import it.vfsfitvnm.vimusic.models.PlaylistPreview
import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.savers.PlaylistPreviewListSaver
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
import it.vfsfitvnm.vimusic.ui.components.themed.Header
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
@ -49,10 +51,7 @@ import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.ui.styling.px
import it.vfsfitvnm.vimusic.utils.playlistSortByKey
import it.vfsfitvnm.vimusic.utils.playlistSortOrderKey
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.rememberPreference
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
@ExperimentalAnimationApi
@ExperimentalFoundationApi
@ -85,15 +84,10 @@ fun HomePlaylists(
var sortBy by rememberPreference(playlistSortByKey, PlaylistSortBy.DateAdded)
var sortOrder by rememberPreference(playlistSortOrderKey, SortOrder.Descending)
val items by produceSaveableState(
initialValue = emptyList(),
stateSaver = PlaylistPreviewListSaver,
sortBy, sortOrder,
) {
Database
.playlistPreviews(sortBy, sortOrder)
.flowOn(Dispatchers.IO)
.collect { value = it }
var items by persistList<PlaylistPreview>("home/playlists")
LaunchedEffect(sortBy, sortOrder) {
Database.playlistPreviews(sortBy, sortOrder).collect { items = it }
}
val sortOrderIconRotation by animateFloatAsState(

View file

@ -5,6 +5,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import androidx.compose.ui.platform.LocalContext
import it.vfsfitvnm.compose.persist.PersistMapCleanup
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.route.defaultStacking
import it.vfsfitvnm.route.defaultStill
@ -42,6 +43,8 @@ import it.vfsfitvnm.vimusic.utils.rememberPreference
fun HomeScreen(onPlaylistUrl: (String) -> Unit) {
val saveableStateHolder = rememberSaveableStateHolder()
PersistMapCleanup("home/")
RouteHandler(
listenToGlobalEmitter = true,
transitionSpec = {

View file

@ -21,6 +21,7 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
@ -30,6 +31,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import it.vfsfitvnm.compose.persist.persistList
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
@ -37,7 +39,6 @@ import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.SongSortBy
import it.vfsfitvnm.vimusic.enums.SortOrder
import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
import it.vfsfitvnm.vimusic.ui.components.themed.Header
@ -53,13 +54,10 @@ 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.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
@ -77,15 +75,10 @@ fun HomeSongs(
var sortBy by rememberPreference(songSortByKey, SongSortBy.DateAdded)
var sortOrder by rememberPreference(songSortOrderKey, SortOrder.Descending)
val items by produceSaveableState(
initialValue = emptyList(),
stateSaver = DetailedSongListSaver,
sortBy, sortOrder,
) {
Database
.songs(sortBy, sortOrder)
.flowOn(Dispatchers.IO)
.collect { value = it }
var items by persistList<DetailedSong>("home/songs")
LaunchedEffect(sortBy, sortOrder) {
Database.songs(sortBy, sortOrder).collect { items = it }
}
val sortOrderIconRotation by animateFloatAsState(

View file

@ -31,24 +31,24 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import it.vfsfitvnm.compose.persist.persist
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.NavigationEndpoint
import it.vfsfitvnm.innertube.models.bodies.NextBody
import it.vfsfitvnm.innertube.requests.relatedPage
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.savers.DetailedSongSaver
import it.vfsfitvnm.vimusic.savers.InnertubeRelatedPageSaver
import it.vfsfitvnm.vimusic.savers.nullableSaver
import it.vfsfitvnm.vimusic.savers.resultSaver
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
@ -70,16 +70,10 @@ import it.vfsfitvnm.vimusic.utils.SnapLayoutInfoProvider
import it.vfsfitvnm.vimusic.utils.asMediaItem
import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.forcePlay
import it.vfsfitvnm.vimusic.utils.isLandscape
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.NavigationEndpoint
import it.vfsfitvnm.innertube.models.bodies.NextBody
import it.vfsfitvnm.innertube.requests.relatedPage
import it.vfsfitvnm.vimusic.utils.isLandscape
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
@ExperimentalFoundationApi
@ExperimentalAnimationApi
@ -95,19 +89,12 @@ fun QuickPicks(
val menuState = LocalMenuState.current
val windowInsets = LocalPlayerAwareWindowInsets.current
var trending by rememberSaveable(stateSaver = nullableSaver(DetailedSongSaver)) {
mutableStateOf(null)
}
var trending by persist<DetailedSong?>("home/trending")
var relatedPageResult by rememberSaveable(stateSaver = resultSaver(nullableSaver(InnertubeRelatedPageSaver))) {
mutableStateOf(null)
}
var relatedPageResult by persist<Result<Innertube.RelatedPage?>?>(tag = "home/relatedPageResult")
LaunchedEffect(Unit) {
Database.trending()
.flowOn(Dispatchers.IO)
.distinctUntilChanged()
.collect { song ->
Database.trending().distinctUntilChanged().collect { song ->
if ((song == null && relatedPageResult == null) || trending?.id != song?.id) {
relatedPageResult =
Innertube.relatedPage(NextBody(videoId = (song?.id ?: "J7p4bzqLvCw")))

View file

@ -4,6 +4,7 @@ import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import it.vfsfitvnm.compose.persist.PersistMapCleanup
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
@ -15,6 +16,8 @@ import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
fun LocalPlaylistScreen(playlistId: Long) {
val saveableStateHolder = rememberSaveableStateHolder()
PersistMapCleanup(tagPrefix = "localPlaylist/$playlistId/")
RouteHandler(listenToGlobalEmitter = true) {
globalRoutes()
@ -28,8 +31,9 @@ fun LocalPlaylistScreen(playlistId: Long) {
Item(0, "Songs", R.drawable.musical_notes)
}
) { currentTabIndex ->
saveableStateHolder.SaveableStateProvider(key = currentTabIndex) {
LocalPlaylistSongs(
saveableStateHolder.SaveableStateProvider(currentTabIndex) {
when (currentTabIndex) {
0 -> LocalPlaylistSongs(
playlistId = playlistId,
onDelete = pop
)
@ -37,4 +41,5 @@ fun LocalPlaylistScreen(playlistId: Long) {
}
}
}
}
}

View file

@ -16,12 +16,17 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import it.vfsfitvnm.compose.persist.persist
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.bodies.BrowseBody
import it.vfsfitvnm.innertube.requests.playlistPage
import it.vfsfitvnm.reordering.ReorderingLazyColumn
import it.vfsfitvnm.reordering.animateItemPlacement
import it.vfsfitvnm.reordering.draggedItem
@ -32,10 +37,9 @@ import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.models.PlaylistWithSongs
import it.vfsfitvnm.vimusic.models.SongPlaylistMap
import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.savers.PlaylistWithSongsSaver
import it.vfsfitvnm.vimusic.savers.nullableSaver
import it.vfsfitvnm.vimusic.transaction
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.themed.ConfirmationDialog
@ -57,10 +61,6 @@ import it.vfsfitvnm.vimusic.utils.completed
import it.vfsfitvnm.vimusic.utils.enqueue
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.bodies.BrowseBody
import it.vfsfitvnm.innertube.requests.playlistPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.runBlocking
@ -77,14 +77,10 @@ fun LocalPlaylistSongs(
val binder = LocalPlayerServiceBinder.current
val menuState = LocalMenuState.current
val playlistWithSongs by produceSaveableState(
initialValue = null,
stateSaver = nullableSaver(PlaylistWithSongsSaver)
) {
Database
.playlistWithSongs(playlistId)
.filterNotNull()
.collect { value = it }
var playlistWithSongs by persist<PlaylistWithSongs?>("localPlaylist/$playlistId/playlistWithSongs")
LaunchedEffect(Unit) {
Database.playlistWithSongs(playlistId).filterNotNull().collect { playlistWithSongs = it }
}
val lazyListState = rememberLazyListState()

View file

@ -52,9 +52,7 @@ import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.vimusic.utils.trackLoopEnabledKey
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
@Composable
fun Controls(
@ -82,11 +80,7 @@ fun Controls(
}
LaunchedEffect(mediaId) {
Database
.likedAt(mediaId)
.flowOn(Dispatchers.IO)
.distinctUntilChanged()
.collect { likedAt = it }
Database.likedAt(mediaId).distinctUntilChanged().collect { likedAt = it }
}
val shouldBePlayingTransition = updateTransition(shouldBePlaying, label = "shouldBePlaying")

View file

@ -32,7 +32,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.autoSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -70,14 +70,12 @@ import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.color
import it.vfsfitvnm.vimusic.utils.isShowingSynchronizedLyricsKey
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.vimusic.utils.toast
import it.vfsfitvnm.vimusic.utils.verticalFadingEdge
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
@ -108,19 +106,16 @@ fun Lyrics(
mutableStateOf(false)
}
val lyrics by produceSaveableState(
initialValue = ".",
stateSaver = autoSaver<String?>(),
mediaId, isShowingSynchronizedLyrics
) {
var lyrics by rememberSaveable {
mutableStateOf<String?>(".")
}
LaunchedEffect(mediaId, isShowingSynchronizedLyrics) {
if (isShowingSynchronizedLyrics) {
Database.synchronizedLyrics(mediaId)
} else {
Database.lyrics(mediaId)
}
.flowOn(Dispatchers.IO)
.distinctUntilChanged()
.collect { value = it }
}.distinctUntilChanged().collect { lyrics = it }
}
var isError by remember(lyrics) {

View file

@ -4,6 +4,7 @@ import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import it.vfsfitvnm.compose.persist.PersistMapCleanup
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
@ -14,6 +15,7 @@ import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
@Composable
fun PlaylistScreen(browseId: String) {
val saveableStateHolder = rememberSaveableStateHolder()
PersistMapCleanup(tagPrefix = "playlist/$browseId")
RouteHandler(listenToGlobalEmitter = true) {
globalRoutes()

View file

@ -8,11 +8,15 @@ import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
@ -21,18 +25,17 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.valentinilk.shimmer.shimmer
import it.vfsfitvnm.compose.persist.persist
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.bodies.BrowseBody
import it.vfsfitvnm.innertube.requests.playlistPage
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.only
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.Playlist
import it.vfsfitvnm.vimusic.models.SongPlaylistMap
import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.savers.InnertubePlaylistOrAlbumPageSaver
import it.vfsfitvnm.vimusic.savers.nullableSaver
import it.vfsfitvnm.vimusic.transaction
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
@ -56,10 +59,6 @@ import it.vfsfitvnm.vimusic.utils.enqueue
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
import it.vfsfitvnm.vimusic.utils.isLandscape
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.bodies.BrowseBody
import it.vfsfitvnm.innertube.requests.playlistPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -74,13 +73,12 @@ fun PlaylistSongList(
val context = LocalContext.current
val menuState = LocalMenuState.current
val playlistPage by produceSaveableState(
initialValue = null,
stateSaver = nullableSaver(InnertubePlaylistOrAlbumPageSaver),
) {
if (value != null && value?.songsPage?.continuation == null) return@produceSaveableState
var playlistPage by persist<Innertube.PlaylistOrAlbumPage?>("playlist/$browseId/playlistPage")
value = withContext(Dispatchers.IO) {
LaunchedEffect(Unit) {
if (playlistPage != null && playlistPage?.songsPage?.continuation == null) return@LaunchedEffect
playlistPage = withContext(Dispatchers.IO) {
Innertube.playlistPage(BrowseBody(browseId = browseId))?.completed()?.getOrNull()
}
}

View file

@ -4,27 +4,30 @@ import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import it.vfsfitvnm.compose.persist.persistList
import it.vfsfitvnm.innertube.models.NavigationEndpoint
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.only
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
import it.vfsfitvnm.vimusic.ui.components.themed.Header
@ -38,10 +41,6 @@ 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.produceSaveableState
import it.vfsfitvnm.innertube.models.NavigationEndpoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
@ExperimentalFoundationApi
@ExperimentalAnimationApi
@ -55,16 +54,11 @@ fun LocalSongSearch(
val binder = LocalPlayerServiceBinder.current
val menuState = LocalMenuState.current
val items by produceSaveableState(
initialValue = emptyList(),
stateSaver = DetailedSongListSaver,
key1 = textFieldValue.text
) {
var items by persistList<DetailedSong>("search/local/songs")
LaunchedEffect(textFieldValue.text) {
if (textFieldValue.text.length > 1) {
Database
.search("%${textFieldValue.text}%")
.flowOn(Dispatchers.IO)
.collect { value = it }
Database.search("%${textFieldValue.text}%").collect { items = it }
}
}

View file

@ -25,10 +25,7 @@ import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.autoSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -46,14 +43,16 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import it.vfsfitvnm.compose.persist.persist
import it.vfsfitvnm.compose.persist.persistList
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.bodies.SearchSuggestionsBody
import it.vfsfitvnm.innertube.requests.searchSuggestions
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.SearchQuery
import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.savers.SearchQuerySaver
import it.vfsfitvnm.vimusic.savers.listSaver
import it.vfsfitvnm.vimusic.savers.resultSaver
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
import it.vfsfitvnm.vimusic.ui.components.themed.Header
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
@ -61,17 +60,11 @@ import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.utils.align
import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.bodies.SearchSuggestionsBody
import it.vfsfitvnm.innertube.requests.searchSuggestions
import it.vfsfitvnm.vimusic.utils.pauseSearchHistoryKey
import it.vfsfitvnm.vimusic.utils.preferences
import kotlinx.coroutines.Dispatchers
import it.vfsfitvnm.vimusic.utils.secondary
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
@ExperimentalAnimationApi
@Composable
@ -86,22 +79,17 @@ fun OnlineSearch(
val (colorPalette, typography) = LocalAppearance.current
val history by produceSaveableState(
initialValue = emptyList(),
stateSaver = listSaver(SearchQuerySaver),
key1 = textFieldValue.text
) {
var history by persistList<SearchQuery>("search/online/history")
LaunchedEffect(textFieldValue.text) {
if (!context.preferences.getBoolean(pauseSearchHistoryKey, false)) {
Database.queries("%${textFieldValue.text}%")
.flowOn(Dispatchers.IO)
.distinctUntilChanged { old, new -> old.size == new.size }
.collect { value = it }
.collect { history = it }
}
}
var suggestionsResult by rememberSaveable(stateSaver = resultSaver(autoSaver<List<String>?>())) {
mutableStateOf(null)
}
var suggestionsResult by persist<Result<List<String>?>?>("search/online/suggestionsResult")
LaunchedEffect(textFieldValue.text) {
if (textFieldValue.text.isNotEmpty()) {

View file

@ -16,6 +16,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import it.vfsfitvnm.compose.persist.PersistMapCleanup
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
@ -49,6 +50,8 @@ fun SearchScreen(
)
}
PersistMapCleanup(tagPrefix = "search/")
RouteHandler(listenToGlobalEmitter = true) {
globalRoutes()

View file

@ -2,8 +2,11 @@ package it.vfsfitvnm.vimusic.ui.screens.searchresult
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyItemScope
@ -11,32 +14,29 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import it.vfsfitvnm.compose.persist.persist
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.utils.plus
import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.only
import it.vfsfitvnm.vimusic.savers.nullableSaver
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.utils.plus
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ExperimentalAnimationApi
@Composable
inline fun <T : Innertube.Item> ItemsPage(
stateSaver: Saver<Innertube.ItemsPage<T>, List<Any?>>,
tag: String,
crossinline headerContent: @Composable (textButton: (@Composable () -> Unit)?) -> Unit,
crossinline itemContent: @Composable LazyItemScope.(T) -> Unit,
noinline itemPlaceholderContent: @Composable () -> Unit,
@ -47,29 +47,29 @@ inline fun <T : Innertube.Item> ItemsPage(
noinline itemsPageProvider: (suspend (String?) -> Result<Innertube.ItemsPage<T>?>?)? = null,
) {
val (_, typography) = LocalAppearance.current
val lazyListState = rememberLazyListState()
val updatedItemsPageProvider by rememberUpdatedState(itemsPageProvider)
val itemsPage by produceSaveableState(
initialValue = null,
stateSaver = nullableSaver(stateSaver),
lazyListState, updatedItemsPageProvider
) {
val currentItemsPageProvider = updatedItemsPageProvider ?: return@produceSaveableState
val lazyListState = rememberLazyListState()
var itemsPage by persist<Innertube.ItemsPage<T>?>(tag)
LaunchedEffect(lazyListState, updatedItemsPageProvider) {
val currentItemsPageProvider = updatedItemsPageProvider ?: return@LaunchedEffect
snapshotFlow { lazyListState.layoutInfo.visibleItemsInfo.any { it.key == "loading" } }
.collect { shouldLoadMore ->
if (!shouldLoadMore) return@collect
withContext(Dispatchers.IO) {
currentItemsPageProvider(value?.continuation)
currentItemsPageProvider(itemsPage?.continuation)
}?.onSuccess {
if (it == null) {
if (value == null) {
value = Innertube.ItemsPage(null, null)
if (itemsPage == null) {
itemsPage = Innertube.ItemsPage(null, null)
}
} else {
value += it
itemsPage += it
}
}
}

View file

@ -9,16 +9,18 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import it.vfsfitvnm.compose.persist.PersistMapCleanup
import it.vfsfitvnm.compose.persist.persistMap
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.bodies.ContinuationBody
import it.vfsfitvnm.innertube.models.bodies.SearchBody
import it.vfsfitvnm.innertube.requests.searchPage
import it.vfsfitvnm.innertube.utils.from
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.savers.InnertubeAlbumsPageSaver
import it.vfsfitvnm.vimusic.savers.InnertubeArtistItemListSaver
import it.vfsfitvnm.vimusic.savers.InnertubePlaylistItemListSaver
import it.vfsfitvnm.vimusic.savers.InnertubeSongsPageSaver
import it.vfsfitvnm.vimusic.savers.InnertubeVideoItemListSaver
import it.vfsfitvnm.vimusic.savers.innertubeItemsPageSaver
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.themed.Header
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
@ -43,19 +45,17 @@ import it.vfsfitvnm.vimusic.utils.asMediaItem
import it.vfsfitvnm.vimusic.utils.forcePlay
import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.vimusic.utils.searchResultScreenTabIndexKey
import it.vfsfitvnm.innertube.Innertube
import it.vfsfitvnm.innertube.models.bodies.ContinuationBody
import it.vfsfitvnm.innertube.models.bodies.SearchBody
import it.vfsfitvnm.innertube.requests.searchPage
import it.vfsfitvnm.innertube.utils.from
@ExperimentalFoundationApi
@ExperimentalAnimationApi
@Composable
fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
val context = LocalContext.current
val saveableStateHolder = rememberSaveableStateHolder()
val (tabIndex, onTabIndexChanges) = rememberPreference(searchResultScreenTabIndexKey, 0)
PersistMapCleanup(tagPrefix = "searchResults/$query/")
RouteHandler(listenToGlobalEmitter = true) {
globalRoutes()
@ -66,6 +66,9 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
modifier = Modifier
.pointerInput(Unit) {
detectTapGestures {
context.persistMap?.keys?.removeAll {
it.startsWith("searchResults/$query/")
}
onSearchAgain()
}
}
@ -74,8 +77,6 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
val emptyItemsText = "No results found. Please try a different query or category"
Scaffold(
topIconButtonId = R.drawable.chevron_back,
onTopIconButtonClick = pop,
@ -99,7 +100,7 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
val thumbnailSizePx = thumbnailSizeDp.px
ItemsPage(
stateSaver = InnertubeSongsPageSaver,
tag = "searchResults/$query/songs",
itemsPageProvider = { continuation ->
if (continuation == null) {
Innertube.searchPage(
@ -149,7 +150,7 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
val thumbnailSizePx = thumbnailSizeDp.px
ItemsPage(
stateSaver = InnertubeAlbumsPageSaver,
tag = "searchResults/$query/albums",
itemsPageProvider = { continuation ->
if (continuation == null) {
Innertube.searchPage(
@ -186,7 +187,7 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
val thumbnailSizePx = thumbnailSizeDp.px
ItemsPage(
stateSaver = innertubeItemsPageSaver(InnertubeArtistItemListSaver),
tag = "searchResults/$query/artists",
itemsPageProvider = { continuation ->
if (continuation == null) {
Innertube.searchPage(
@ -224,7 +225,7 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
val thumbnailWidthDp = 128.dp
ItemsPage(
stateSaver = innertubeItemsPageSaver(InnertubeVideoItemListSaver),
tag = "searchResults/$query/videos",
itemsPageProvider = { continuation ->
if (continuation == null) {
Innertube.searchPage(
@ -277,7 +278,7 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
val thumbnailSizePx = thumbnailSizeDp.px
ItemsPage(
stateSaver = innertubeItemsPageSaver(InnertubePlaylistItemListSaver),
tag = "searchResults/$query/${if (tabIndex == 4) "playlists" else "featured"}",
itemsPageProvider = { continuation ->
if (continuation == null) {
val filter = if (tabIndex == 4) {

View file

@ -1,6 +1,5 @@
package it.vfsfitvnm.vimusic.ui.screens.settings
import android.os.Build
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.border

View file

@ -15,8 +15,9 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.saveable.autoSaver
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import it.vfsfitvnm.vimusic.Database
@ -28,16 +29,13 @@ import it.vfsfitvnm.vimusic.service.PlayerService
import it.vfsfitvnm.vimusic.ui.components.themed.Header
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.utils.intent
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.toast
import java.io.FileInputStream
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.Date
import kotlin.system.exitProcess
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
@ExperimentalAnimationApi
@Composable
@ -45,12 +43,9 @@ fun DatabaseSettings() {
val context = LocalContext.current
val (colorPalette) = LocalAppearance.current
val eventsCount by produceSaveableState(initialValue = 0, stateSaver = autoSaver()) {
Database.eventsCount()
.flowOn(Dispatchers.IO)
.distinctUntilChanged()
.collect { value = it }
}
val eventsCount by remember {
Database.eventsCount().distinctUntilChanged()
}.collectAsState(initial = 0)
val backupLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/vnd.sqlite3")) { uri ->

View file

@ -21,10 +21,10 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SnapshotMutationPolicy
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.autoSaver
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@ -39,12 +39,9 @@ import it.vfsfitvnm.vimusic.utils.isAtLeastAndroid6
import it.vfsfitvnm.vimusic.utils.isIgnoringBatteryOptimizations
import it.vfsfitvnm.vimusic.utils.isInvincibilityEnabledKey
import it.vfsfitvnm.vimusic.utils.pauseSearchHistoryKey
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.vimusic.utils.toast
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
@SuppressLint("BatteryLife")
@ExperimentalAnimationApi
@ -86,12 +83,9 @@ fun OtherSettings() {
var pauseSearchHistory by rememberPreference(pauseSearchHistoryKey, false)
val queriesCount by produceSaveableState(initialValue = 0, stateSaver = autoSaver()) {
Database.queriesCount()
.flowOn(Dispatchers.IO)
.distinctUntilChanged()
.collect { value = it }
}
val queriesCount by remember {
Database.queriesCount().distinctUntilChanged()
}.collectAsState(initial = 0)
Column(
modifier = Modifier

View file

@ -11,6 +11,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.*
import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
import androidx.compose.ui.unit.dp
import it.vfsfitvnm.route.*
import it.vfsfitvnm.vimusic.R

View file

@ -1,82 +0,0 @@
@file:OptIn(ExperimentalTypeInference::class)
package it.vfsfitvnm.vimusic.utils
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.ProduceStateScope
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import kotlin.coroutines.CoroutineContext
import kotlin.experimental.ExperimentalTypeInference
import kotlinx.coroutines.suspendCancellableCoroutine
@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)
}
LaunchedEffect(Unit) {
ProduceSaveableStateScope(result, coroutineContext).producer()
}
return result
}
@Composable
fun <T> produceSaveableState(
initialValue: T,
stateSaver: Saver<T, out Any>,
key1: Any?,
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
): State<T> {
val state = rememberSaveable(stateSaver = stateSaver) {
mutableStateOf(initialValue)
}
LaunchedEffect(key1) {
ProduceSaveableStateScope(state, coroutineContext).producer()
}
return state
}
@Composable
fun <T> produceSaveableState(
initialValue: T,
stateSaver: Saver<T, out Any>,
key1: Any?,
key2: Any?,
@BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
): State<T> {
val result = rememberSaveable(stateSaver = stateSaver) {
mutableStateOf(initialValue)
}
LaunchedEffect(key1, key2) {
ProduceSaveableStateScope(result, coroutineContext).producer()
}
return result
}
private class ProduceSaveableStateScope<T>(
state: MutableState<T>,
override val coroutineContext: CoroutineContext
) : ProduceStateScope<T>, MutableState<T> by state {
override suspend fun awaitDispose(onDispose: () -> Unit): Nothing {
try {
suspendCancellableCoroutine<Nothing> { }
} finally {
onDispose()
}
}
}

1
compose-persist/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,46 @@
plugins {
id("com.android.library")
kotlin("android")
}
android {
namespace = "it.vfsfitvnm.compose.persist"
compileSdk = 33
defaultConfig {
minSdk = 21
targetSdk = 33
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
}
}
sourceSets.all {
kotlin.srcDir("src/$name/kotlin")
}
buildFeatures {
compose = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation(libs.compose.foundation)
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest>
</manifest>

View file

@ -0,0 +1,26 @@
package it.vfsfitvnm.compose.persist
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
@Suppress("UNCHECKED_CAST")
@Composable
fun <T> persist(tag: String, initialValue: T): MutableState<T> {
val context = LocalContext.current
return remember {
context.persistMap?.getOrPut(tag) { mutableStateOf(initialValue) } as? MutableState<T>
?: mutableStateOf(initialValue)
}
}
@Composable
fun <T> persistList(tag: String): MutableState<List<T>> =
persist(tag = tag, initialValue = emptyList())
@Composable
fun <T : Any?> persist(tag: String): MutableState<T?> =
persist(tag = tag, initialValue = null)

View file

@ -0,0 +1,3 @@
package it.vfsfitvnm.compose.persist
typealias PersistMap = HashMap<String, Any?>

View file

@ -0,0 +1,19 @@
package it.vfsfitvnm.compose.persist
import android.app.Activity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.platform.LocalContext
@Composable
fun PersistMapCleanup(tagPrefix: String) {
val context = LocalContext.current
DisposableEffect(context) {
onDispose {
if (context.findOwner<Activity>()?.isChangingConfigurations == false) {
context.persistMap?.keys?.removeAll { it.startsWith(tagPrefix) }
}
}
}
}

View file

@ -0,0 +1,5 @@
package it.vfsfitvnm.compose.persist
interface PersistMapOwner {
val persistMap: PersistMap
}

View file

@ -0,0 +1,16 @@
package it.vfsfitvnm.compose.persist
import android.content.Context
import android.content.ContextWrapper
val Context.persistMap: PersistMap?
get() = findOwner<PersistMapOwner>()?.persistMap
internal inline fun <reified T> Context.findOwner(): T? {
var context = this
while (context is ContextWrapper) {
if (context is T) return context
context = context.baseContext
}
return null
}

View file

@ -62,6 +62,7 @@ rootProject.name = "ViMusic"
include(":app")
include(":compose-routing")
include(":compose-reordering")
include(":compose-persist")
include(":innertube")
include(":ktor-client-brotli")
include(":kugou")