diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/MainApplication.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/MainApplication.kt index 84efd6d..7488daf 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/MainApplication.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/MainApplication.kt @@ -4,7 +4,7 @@ import android.app.Application import coil.ImageLoader import coil.ImageLoaderFactory import coil.disk.DiskCache -import it.vfsfitvnm.vimusic.utils.preferences +import it.vfsfitvnm.vimusic.utils.Preferences class MainApplication : Application(), ImageLoaderFactory { @@ -19,7 +19,7 @@ class MainApplication : Application(), ImageLoaderFactory { .diskCache( DiskCache.Builder() .directory(filesDir.resolve("coil")) - .maxSizeBytes(preferences.coilDiskCacheMaxSizeBytes) + .maxSizeBytes(Preferences().coilDiskCacheMaxSizeBytes) .build() ) .build() diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerService.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerService.kt index f007899..0b0137a 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerService.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerService.kt @@ -127,6 +127,8 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback createNotificationChannel() + val preferences = Preferences() + val cacheEvictor = LeastRecentlyUsedCacheEvictor(preferences.exoPlayerDiskCacheMaxSizeBytes) cache = SimpleCache(cacheDir, cacheEvictor, StandaloneDatabaseProvider(this)) @@ -201,7 +203,7 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback } override fun onDestroy() { - if (preferences.persistentQueue) { + if (Preferences().persistentQueue) { val mediaItems = player.currentTimeline.mediaItems val mediaItemIndex = player.currentMediaItemIndex val mediaItemPosition = player.currentPosition @@ -262,7 +264,7 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback } private fun normalizeVolume() { - if (preferences.volumeNormalization) { + if (Preferences().volumeNormalization) { player.volume = player.currentMediaItem?.let { mediaItem -> songPendingLoudnessDb.getOrElse(mediaItem.mediaId) { mediaItem.mediaMetadata.extras?.getFloatOrNull("loudnessDb") diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Preferences.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Preferences.kt index b4a709a..f0be85b 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Preferences.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Preferences.kt @@ -4,10 +4,6 @@ import android.content.Context import android.content.SharedPreferences import androidx.compose.runtime.* import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.TextUnit -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.core.content.edit import androidx.media3.common.Player import it.vfsfitvnm.vimusic.enums.ColorPaletteMode @@ -15,105 +11,128 @@ import it.vfsfitvnm.vimusic.enums.SongCollection import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness import it.vfsfitvnm.youtubemusic.YouTube -@Stable -class Preferences(holder: SharedPreferences) : SharedPreferences by holder { - var colorPaletteMode by preference("colorPaletteMode", ColorPaletteMode.System) - var searchFilter by preference("searchFilter", YouTube.Item.Song.Filter.value) - var repeatMode by preference("repeatMode", Player.REPEAT_MODE_OFF) - var homePageSongCollection by preference("homePageSongCollection", SongCollection.MostPlayed) - var thumbnailRoundness by preference("thumbnailRoundness", ThumbnailRoundness.Light) - var coilDiskCacheMaxSizeBytes by preference("coilDiskCacheMaxSizeBytes", 512L * 1024 * 1024) - var exoPlayerDiskCacheMaxSizeBytes by preference("exoPlayerDiskCacheMaxSizeBytes", 512L * 1024 * 1024) - var skipSilence by preference("skipSilence", false) - var volumeNormalization by preference("volumeNormalization", true) - var persistentQueue by preference("persistentQueue", false) -} -val Context.preferences: Preferences - get() = Preferences(getSharedPreferences("preferences", Context.MODE_PRIVATE)) +@Stable +class Preferences( + private val edit: (action: SharedPreferences.Editor.() -> Unit) -> Unit, + initialColorPaletteMode: ColorPaletteMode, + initialSearchFilter: String, + initialRepeatMode: Int, + initialHomePageSongCollection: SongCollection, + initialThumbnailRoundness: ThumbnailRoundness, + initialCoilDiskCacheMaxSizeBytes: Long, + initialExoPlayerDiskCacheMaxSizeBytes: Long, + initialSkipSilence: Boolean, + initialVolumeNormalization: Boolean, + initialPersistentQueue: Boolean, +) { + constructor(preferences: SharedPreferences) : this( + edit = { action: SharedPreferences.Editor.() -> Unit -> + preferences.edit(action = action) + }, + initialColorPaletteMode = preferences.getEnum(Keys.colorPaletteMode, ColorPaletteMode.System), + initialSearchFilter = preferences.getString(Keys.searchFilter, YouTube.Item.Song.Filter.value)!!, + initialRepeatMode = preferences.getInt(Keys.repeatMode, Player.REPEAT_MODE_OFF), + initialHomePageSongCollection = preferences.getEnum(Keys.homePageSongCollection, SongCollection.History), + initialThumbnailRoundness = preferences.getEnum(Keys.thumbnailRoundness, ThumbnailRoundness.Light), + initialCoilDiskCacheMaxSizeBytes = preferences.getLong(Keys.coilDiskCacheMaxSizeBytes, 512L * 1024 * 1024), + initialExoPlayerDiskCacheMaxSizeBytes = preferences.getLong(Keys.exoPlayerDiskCacheMaxSizeBytes, 512L * 1024 * 1024), + initialSkipSilence = preferences.getBoolean(Keys.skipSilence, false), + initialVolumeNormalization = preferences.getBoolean(Keys.volumeNormalization, false), + initialPersistentQueue = preferences.getBoolean(Keys.persistentQueue, false) + ) + + var colorPaletteMode = initialColorPaletteMode + set(value) = edit { putEnum(Keys.colorPaletteMode, value) } + + var searchFilter = initialSearchFilter + set(value) = edit { putString(Keys.searchFilter, value) } + + var repeatMode = initialRepeatMode + set(value) = edit { putInt(Keys.repeatMode, value) } + + var homePageSongCollection = initialHomePageSongCollection + set(value) = edit { putEnum(Keys.homePageSongCollection, value) } + + var thumbnailRoundness = initialThumbnailRoundness + set(value) = edit { putEnum(Keys.thumbnailRoundness, value) } + + var coilDiskCacheMaxSizeBytes = initialCoilDiskCacheMaxSizeBytes + set(value) = edit { putLong(Keys.coilDiskCacheMaxSizeBytes, value) } + + var exoPlayerDiskCacheMaxSizeBytes = initialExoPlayerDiskCacheMaxSizeBytes + set(value) = edit { putLong(Keys.exoPlayerDiskCacheMaxSizeBytes, value) } + + var skipSilence = initialSkipSilence + set(value) = edit { putBoolean(Keys.skipSilence, value) } + + var volumeNormalization = initialVolumeNormalization + set(value) = edit { putBoolean(Keys.volumeNormalization, value) } + + var persistentQueue = initialPersistentQueue + set(value) = edit { putBoolean(Keys.persistentQueue, value) } + + private object Keys { + const val colorPaletteMode = "colorPaletteMode" + const val searchFilter = "searchFilter" + const val repeatMode = "repeatMode" + const val homePageSongCollection = "homePageSongCollection" + const val thumbnailRoundness = "thumbnailRoundness" + const val coilDiskCacheMaxSizeBytes = "coilDiskCacheMaxSizeBytes" + const val exoPlayerDiskCacheMaxSizeBytes = "exoPlayerDiskCacheMaxSizeBytes" + const val skipSilence = "skipSilence" + const val volumeNormalization = "volumeNormalization" + const val persistentQueue = "persistentQueue" + } + + companion object { + const val fileName = "preferences" + + context(Context) + operator fun invoke() = + Preferences(getSharedPreferences(fileName, Context.MODE_PRIVATE)) + } +} val LocalPreferences = staticCompositionLocalOf { TODO() } @Composable fun rememberPreferences(): Preferences { val context = LocalContext.current - return remember { - context.preferences + var preferences by remember { + mutableStateOf(context.run { Preferences() }) } + + DisposableEffect(Unit) { + val holder = context.getSharedPreferences(Preferences.fileName, Context.MODE_PRIVATE) + + val listener = + SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, _ -> + preferences = Preferences(sharedPreferences) + } + + holder.registerOnSharedPreferenceChangeListener(listener) + + onDispose { + holder.unregisterOnSharedPreferenceChangeListener(listener) + } + } + + return preferences } -private fun SharedPreferences.preference(key: String, defaultValue: Boolean) = - mutableStateOf(value = getBoolean(key, defaultValue)) { - edit { - putBoolean(key, it) - } - } - -private fun SharedPreferences.preference(key: String, defaultValue: Int) = - mutableStateOf(value = getInt(key, defaultValue)) { - edit { - putInt(key, it) - } - } - -private fun SharedPreferences.preference(key: String, defaultValue: Long) = - mutableStateOf(value = getLong(key, defaultValue)) { - edit { - putLong(key, it) - } - } - -private fun SharedPreferences.preference(key: String, defaultValue: Float) = - mutableStateOf(value = getFloat(key, defaultValue)) { - edit { - putFloat(key, it) - } - } - -private fun SharedPreferences.preference(key: String, defaultValue: String) = - mutableStateOf(value = getString(key, defaultValue)!!) { - edit { - putString(key, it) - } - } - -private fun SharedPreferences.preference(key: String, defaultValue: Set) = - mutableStateOf(value = getStringSet(key, defaultValue)!!) { - edit { - putStringSet(key, it) - } - } - -private fun SharedPreferences.preference(key: String, defaultValue: Dp) = - mutableStateOf(value = getFloat(key, defaultValue.value).dp) { - edit { - putFloat(key, it.value) - } - } - -private fun SharedPreferences.preference(key: String, defaultValue: TextUnit) = - mutableStateOf(value = getFloat(key, defaultValue.value).sp) { - edit { - putFloat(key, it.value) - } - } - -private inline fun > SharedPreferences.preference( +private inline fun > SharedPreferences.getEnum( key: String, defaultValue: T -) = mutableStateOf(value = enumValueOf(getString(key, defaultValue.name)!!)) { - edit { - putString(key, it.name) +): T = + getString(key, null)?.let { + try { + enumValueOf(it) + } catch (e: IllegalArgumentException) { + null } - } + } ?: defaultValue + +private inline fun > SharedPreferences.Editor.putEnum(key: String, value: T) = + putString(key, value.name) -private fun mutableStateOf(value: T, onStructuralInequality: (newValue: T) -> Unit) = - mutableStateOf( - value = value, - policy = object : SnapshotMutationPolicy { - override fun equivalent(a: T, b: T): Boolean { - val areEquals = a == b - if (!areEquals) onStructuralInequality(b) - return areEquals - } - })