Refactor preference code

- Allow migrations in all store types
- Remove blocking functions from the preference module
- Move Preference.xx functions to global functions
This commit is contained in:
Niels van Velzen authored and Niels van Velzen committed 2022-04-15 16:07:36 +02:00
1 parent 0e9281388a
commit 33005e3939
22 files changed
+155 -130

No files matched your search

@@ -3,25 +3,27 @@ package org.jellyfin.androidtv.preference
import android.content.Context
import org.jellyfin.androidtv.auth.model.AuthenticationSortBy
import org.jellyfin.androidtv.preference.constant.UserSelectBehavior
import org.jellyfin.preference.Preference
import org.jellyfin.preference.booleanPreference
import org.jellyfin.preference.enumPreference
import org.jellyfin.preference.store.SharedPreferenceStore
import org.jellyfin.preference.stringPreference
class AuthenticationPreferences(context: Context) : SharedPreferenceStore(
sharedPreferences = context.getSharedPreferences("authentication", Context.MODE_PRIVATE)
) {
companion object {
val autoLoginUserBehavior = Preference.enum("auto_login_user_behavior", UserSelectBehavior.LAST_USER)
val autoLoginUserId = Preference.string("auto_login_user_id", "")
val autoLoginUserBehavior = enumPreference("auto_login_user_behavior", UserSelectBehavior.LAST_USER)
val autoLoginUserId = stringPreference("auto_login_user_id", "")
val systemUserBehavior = Preference.enum("system_user_behavior", UserSelectBehavior.LAST_USER)
val systemUserId = Preference.string("system_user_id", "")
val systemUserBehavior = enumPreference("system_user_behavior", UserSelectBehavior.LAST_USER)
val systemUserId = stringPreference("system_user_id", "")
val sortBy = Preference.enum("sort_by", AuthenticationSortBy.LAST_USE)
val alwaysAuthenticate = Preference.boolean("always_authenticate", false)
val sortBy = enumPreference("sort_by", AuthenticationSortBy.LAST_USE)
val alwaysAuthenticate = booleanPreference("always_authenticate", false)
/**
* Do not set directly, use [SessionRepository] instead.
*/
val lastUserId = Preference.string("last_user_id", "")
val lastUserId = stringPreference("last_user_id", "")
}
}
@@ -6,7 +6,9 @@ import org.jellyfin.androidtv.constant.PosterSize
import org.jellyfin.androidtv.preference.store.DisplayPreferencesStore
import org.jellyfin.apiclient.model.entities.SortOrder
import org.jellyfin.apiclient.model.querying.ItemSortBy
import org.jellyfin.preference.Preference
import org.jellyfin.preference.booleanPreference
import org.jellyfin.preference.enumPreference
import org.jellyfin.preference.stringPreference
import org.jellyfin.sdk.api.client.ApiClient
class LibraryPreferences(
@@ -17,17 +19,17 @@ class LibraryPreferences(
api = api,
) {
companion object {
val posterSize = Preference.enum("PosterSize", PosterSize.AUTO)
val imageType = Preference.enum("ImageType", ImageType.DEFAULT)
val gridDirection = Preference.enum("GridDirection", GridDirection.HORIZONTAL)
val enableSmartScreen = Preference.boolean("SmartScreen", false)
val posterSize = enumPreference("PosterSize", PosterSize.AUTO)
val imageType = enumPreference("ImageType", ImageType.DEFAULT)
val gridDirection = enumPreference("GridDirection", GridDirection.HORIZONTAL)
val enableSmartScreen = booleanPreference("SmartScreen", false)
// Filters
val filterFavoritesOnly = Preference.boolean("FilterFavoritesOnly", false)
val filterUnwatchedOnly = Preference.boolean("FilterUnwatchedOnly", false)
val filterFavoritesOnly = booleanPreference("FilterFavoritesOnly", false)
val filterUnwatchedOnly = booleanPreference("FilterUnwatchedOnly", false)
// Item sorting
val sortBy = Preference.string("SortBy", ItemSortBy.SortName)
val sortOrder = Preference.enum("SortOrder", SortOrder.Ascending)
val sortBy = stringPreference("SortBy", ItemSortBy.SortName)
val sortOrder = enumPreference("SortOrder", SortOrder.Ascending)
}
}
@@ -2,7 +2,8 @@ package org.jellyfin.androidtv.preference
import org.jellyfin.androidtv.preference.store.DisplayPreferencesStore
import org.jellyfin.apiclient.model.querying.ItemSortBy
import org.jellyfin.preference.Preference
import org.jellyfin.preference.booleanPreference
import org.jellyfin.preference.stringPreference
import org.jellyfin.sdk.api.client.ApiClient
class LiveTvPreferences(
@@ -12,13 +13,13 @@ class LiveTvPreferences(
api = api,
) {
companion object {
val channelOrder = Preference.string("livetv-channelorder", ItemSortBy.DatePlayed)
val colorCodeGuide = Preference.boolean("guide-colorcodedbackgrounds", false)
val favsAtTop = Preference.boolean("livetv-favoritechannelsattop", true)
val showHDIndicator = Preference.boolean("guide-indicator-hd", false)
val showLiveIndicator = Preference.boolean("guide-indicator-live", true)
val showNewIndicator = Preference.boolean("guide-indicator-new", false)
val showPremiereIndicator = Preference.boolean("guide-indicator-premiere", true)
val showRepeatIndicator = Preference.boolean("guide-indicator-repeat", false)
val channelOrder = stringPreference("livetv-channelorder", ItemSortBy.DatePlayed)
val colorCodeGuide = booleanPreference("guide-colorcodedbackgrounds", false)
val favsAtTop = booleanPreference("livetv-favoritechannelsattop", true)
val showHDIndicator = booleanPreference("guide-indicator-hd", false)
val showLiveIndicator = booleanPreference("guide-indicator-live", true)
val showNewIndicator = booleanPreference("guide-indicator-new", false)
val showPremiereIndicator = booleanPreference("guide-indicator-premiere", true)
val showRepeatIndicator = booleanPreference("guide-indicator-repeat", false)
}
}
@@ -1,5 +1,6 @@
package org.jellyfin.androidtv.preference
import kotlinx.coroutines.runBlocking
import org.jellyfin.sdk.api.client.ApiClient
import kotlin.collections.set
@@ -19,7 +20,7 @@ class PreferencesRepository(
libraryPreferences[preferencesId] = store
// FIXME: Make [getLibraryPreferences] suspended when usages are converted to Kotlin
if (store.shouldUpdate) store.updateBlocking()
if (store.shouldUpdate) runBlocking { store.update() }
return store
}
@@ -2,8 +2,10 @@ package org.jellyfin.androidtv.preference
import android.content.Context
import org.jellyfin.androidtv.preference.constant.PreferredVideoPlayer
import org.jellyfin.preference.Preference
import org.jellyfin.preference.booleanPreference
import org.jellyfin.preference.enumPreference
import org.jellyfin.preference.store.SharedPreferenceStore
import org.jellyfin.preference.stringPreference
/**
* System preferences are not possible to modify by the user.
@@ -19,48 +21,48 @@ class SystemPreferences(context: Context) : SharedPreferenceStore(
/**
* Stores the channel that was active before leaving the app
*/
val liveTvLastChannel = Preference.string("sys_pref_last_tv_channel", "")
val liveTvLastChannel = stringPreference("sys_pref_last_tv_channel", "")
/**
* Also stores the channel that was active before leaving the app I think
*/
val liveTvPrevChannel = Preference.string("sys_pref_prev_tv_channel", "")
val liveTvPrevChannel = stringPreference("sys_pref_prev_tv_channel", "")
// Live TV - Guide Filters
/**
* Stores whether the kids filter is active in the channel guide or not
*/
val liveTvGuideFilterKids = Preference.boolean("guide_filter_kids", false)
val liveTvGuideFilterKids = booleanPreference("guide_filter_kids", false)
/**
* Stores whether the movies filter is active in the channel guide or not
*/
val liveTvGuideFilterMovies = Preference.boolean("guide_filter_movies", false)
val liveTvGuideFilterMovies = booleanPreference("guide_filter_movies", false)
/**
* Stores whether the news filter is active in the channel guide or not
*/
val liveTvGuideFilterNews = Preference.boolean("guide_filter_news", false)
val liveTvGuideFilterNews = booleanPreference("guide_filter_news", false)
/**
* Stores whether the premiere filter is active in the channel guide or not
*/
val liveTvGuideFilterPremiere = Preference.boolean("guide_filter_premiere", false)
val liveTvGuideFilterPremiere = booleanPreference("guide_filter_premiere", false)
/**
* Stores whether the series filter is active in the channel guide or not
*/
val liveTvGuideFilterSeries = Preference.boolean("guide_filter_series", false)
val liveTvGuideFilterSeries = booleanPreference("guide_filter_series", false)
/**
* Stores whether the sports filter is active in the channel guide or not
*/
val liveTvGuideFilterSports = Preference.boolean("guide_filter_sports", false)
val liveTvGuideFilterSports = booleanPreference("guide_filter_sports", false)
// Other persistent variables
/**
* Chosen player for play with button. Changes every time user chooses a player with "play with" button.
*/
var chosenPlayer = Preference.enum("chosen_player", PreferredVideoPlayer.VLC)
var chosenPlayer = enumPreference("chosen_player", PreferredVideoPlayer.VLC)
}
}
@@ -13,9 +13,12 @@ import org.jellyfin.androidtv.preference.constant.RatingType
import org.jellyfin.androidtv.preference.constant.WatchedIndicatorBehavior
import org.jellyfin.androidtv.preference.constant.defaultAudioBehavior
import org.jellyfin.androidtv.util.DeviceUtils
import org.jellyfin.preference.Preference
import org.jellyfin.preference.booleanPreference
import org.jellyfin.preference.enumPreference
import org.jellyfin.preference.intPreference
import org.jellyfin.preference.migration.putEnum
import org.jellyfin.preference.store.SharedPreferenceStore
import org.jellyfin.preference.stringPreference
/**
* User preferences are configurable by the user and change behavior of the application.
@@ -36,170 +39,170 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
/**
* Select the app theme
*/
var appTheme = Preference.enum("app_theme", AppTheme.DARK)
var appTheme = enumPreference("app_theme", AppTheme.DARK)
/**
* Enable background images while browsing
*/
var backdropEnabled = Preference.boolean("pref_show_backdrop", true)
var backdropEnabled = booleanPreference("pref_show_backdrop", true)
/**
* Show premieres on home screen
*/
var premieresEnabled = Preference.boolean("pref_enable_premieres", false)
var premieresEnabled = booleanPreference("pref_enable_premieres", false)
/**
* Show a little notification to celebrate a set of holidays
*/
var seasonalGreetingsEnabled = Preference.boolean("pref_enable_themes", true)
var seasonalGreetingsEnabled = booleanPreference("pref_enable_themes", true)
/* Playback - General*/
/**
* Maximum bitrate in megabit for playback. A value of [MAX_BITRATE_AUTO] is used when
* the bitrate should be automatically detected.
*/
var maxBitrate = Preference.string("pref_max_bitrate", "100")
var maxBitrate = stringPreference("pref_max_bitrate", "100")
/**
* Auto-play next item
*/
var mediaQueuingEnabled = Preference.boolean("pref_enable_tv_queuing", true)
var mediaQueuingEnabled = booleanPreference("pref_enable_tv_queuing", true)
/**
* Enable the next up screen or not
*/
var nextUpBehavior = Preference.enum("next_up_behavior", NextUpBehavior.EXTENDED)
var nextUpBehavior = enumPreference("next_up_behavior", NextUpBehavior.EXTENDED)
/**
* Next up timeout before playing next item
* Stored in milliseconds
*/
var nextUpTimeout = Preference.int("next_up_timeout", 1000 * 7)
var nextUpTimeout = intPreference("next_up_timeout", 1000 * 7)
/**
* Duration in seconds to subtract from resume time
*/
var resumeSubtractDuration = Preference.string("pref_resume_preroll", "0")
var resumeSubtractDuration = stringPreference("pref_resume_preroll", "0")
/**
* Enable cinema mode
*/
var cinemaModeEnabled = Preference.boolean("pref_enable_cinema_mode", true)
var cinemaModeEnabled = booleanPreference("pref_enable_cinema_mode", true)
/* Playback - Video */
/**
* Preferred video player.
*/
var videoPlayer = Preference.enum("video_player", PreferredVideoPlayer.EXOPLAYER)
var videoPlayer = enumPreference("video_player", PreferredVideoPlayer.EXOPLAYER)
/**
* Enable refresh rate switching when device supports it
*/
var refreshRateSwitchingEnabled = Preference.boolean("pref_refresh_switching", false)
var refreshRateSwitchingEnabled = booleanPreference("pref_refresh_switching", false)
/**
* Send a path instead to the external player
*/
var externalVideoPlayerSendPath = Preference.boolean("pref_send_path_external", false)
var externalVideoPlayerSendPath = booleanPreference("pref_send_path_external", false)
/* Playback - Audio related */
/**
* Preferred behavior for audio streaming.
*/
var audioBehaviour = Preference.enum("audio_behavior", defaultAudioBehavior)
var audioBehaviour = enumPreference("audio_behavior", defaultAudioBehavior)
/**
* Enable DTS
*/
var dtsEnabled = Preference.boolean("pref_bitstream_dts", false)
var dtsEnabled = booleanPreference("pref_bitstream_dts", false)
/**
* Enable AC3
*/
var ac3Enabled = Preference.boolean("pref_bitstream_ac3", !DeviceUtils.isFireTvStickGen1())
var ac3Enabled = booleanPreference("pref_bitstream_ac3", !DeviceUtils.isFireTvStickGen1())
/**
* Default audio delay in milliseconds for libVLC
*/
var libVLCAudioDelay = Preference.int("libvlc_audio_delay", 0)
var libVLCAudioDelay = intPreference("libvlc_audio_delay", 0)
/* Live TV */
/**
* Use direct play
*/
var liveTvDirectPlayEnabled = Preference.boolean("pref_live_direct", true)
var liveTvDirectPlayEnabled = booleanPreference("pref_live_direct", true)
/**
* Preferred video player for live TV
*/
var liveTvVideoPlayer = Preference.enum("live_tv_video_player", PreferredVideoPlayer.AUTO)
var liveTvVideoPlayer = enumPreference("live_tv_video_player", PreferredVideoPlayer.AUTO)
/**
* Shortcut used for changing the audio track
*/
var shortcutAudioTrack = Preference.int("shortcut_audio_track", KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK)
var shortcutAudioTrack = intPreference("shortcut_audio_track", KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK)
/**
* Shortcut used for changing the subtitle track
*/
var shortcutSubtitleTrack = Preference.int("shortcut_subtitle_track", KeyEvent.KEYCODE_CAPTIONS)
var shortcutSubtitleTrack = intPreference("shortcut_subtitle_track", KeyEvent.KEYCODE_CAPTIONS)
/* Developer options */
/**
* Show additional debug information
*/
var debuggingEnabled = Preference.boolean("pref_enable_debug", false)
var debuggingEnabled = booleanPreference("pref_enable_debug", false)
/**
* Use playback rewrite module
*/
var playbackRewriteEnabled = Preference.boolean("playback_new", false)
var playbackRewriteEnabled = booleanPreference("playback_new", false)
/* ACRA */
/**
* Enable ACRA crash reporting
*/
var acraEnabled = Preference.boolean(ACRA.PREF_ENABLE_ACRA, true)
var acraEnabled = booleanPreference(ACRA.PREF_ENABLE_ACRA, true)
/**
* Never prompt to report crash logs
*/
var acraNoPrompt = Preference.boolean(ACRA.PREF_ALWAYS_ACCEPT, false)
var acraNoPrompt = booleanPreference(ACRA.PREF_ALWAYS_ACCEPT, false)
/**
* Include system logs in crash reports
*/
var acraIncludeSystemLogs = Preference.boolean(ACRA.PREF_ENABLE_SYSTEM_LOGS, true)
var acraIncludeSystemLogs = booleanPreference(ACRA.PREF_ENABLE_SYSTEM_LOGS, true)
/**
* When to show the clock.
*/
var clockBehavior = Preference.enum("pref_clock_behavior", ClockBehavior.ALWAYS)
var clockBehavior = enumPreference("pref_clock_behavior", ClockBehavior.ALWAYS)
/**
* Set which ratings provider should show on MyImageCardViews
*/
var defaultRatingType = Preference.enum("pref_rating_type", RatingType.RATING_TOMATOES)
var defaultRatingType = enumPreference("pref_rating_type", RatingType.RATING_TOMATOES)
/**
* Set when watched indicators should show on MyImageCardViews
*/
var watchedIndicatorBehavior = Preference.enum("pref_watched_indicator_behavior", WatchedIndicatorBehavior.ALWAYS)
var watchedIndicatorBehavior = enumPreference("pref_watched_indicator_behavior", WatchedIndicatorBehavior.ALWAYS)
/**
* Enable series thumbnails in home screen rows
*/
var seriesThumbnailsEnabled = Preference.boolean("pref_enable_series_thumbnails", true)
var seriesThumbnailsEnabled = booleanPreference("pref_enable_series_thumbnails", true)
/**
* Enable subtitles background
*/
var subtitlesBackgroundEnabled = Preference.boolean("subtitles_background_enabled", true)
var subtitlesBackgroundEnabled = booleanPreference("subtitles_background_enabled", true)
/**
* Set default subtitles font size
*/
var defaultSubtitlesSize = Preference.int("subtitles_size", 28)
var defaultSubtitlesSize = intPreference("subtitles_size", 28)
}
init {
@@ -2,7 +2,8 @@ package org.jellyfin.androidtv.preference
import org.jellyfin.androidtv.constant.HomeSectionType
import org.jellyfin.androidtv.preference.store.DisplayPreferencesStore
import org.jellyfin.preference.Preference
import org.jellyfin.preference.enumPreference
import org.jellyfin.preference.intPreference
import org.jellyfin.sdk.api.client.ApiClient
class UserSettingPreferences(
@@ -13,16 +14,16 @@ class UserSettingPreferences(
app = "emby",
) {
companion object {
val skipBackLength = Preference.int("skipBackLength", 10000)
val skipForwardLength = Preference.int("skipForwardLength", 30000)
val skipBackLength = intPreference("skipBackLength", 10000)
val skipForwardLength = intPreference("skipForwardLength", 30000)
val homesection0 = Preference.enum("homesection0", HomeSectionType.LIBRARY_TILES_SMALL)
val homesection1 = Preference.enum("homesection1", HomeSectionType.RESUME)
val homesection2 = Preference.enum("homesection2", HomeSectionType.RESUME_AUDIO)
val homesection3 = Preference.enum("homesection3", HomeSectionType.RESUME_BOOK)
val homesection4 = Preference.enum("homesection4", HomeSectionType.LIVE_TV)
val homesection5 = Preference.enum("homesection5", HomeSectionType.NEXT_UP)
val homesection6 = Preference.enum("homesection6", HomeSectionType.LATEST_MEDIA)
val homesection0 = enumPreference("homesection0", HomeSectionType.LIBRARY_TILES_SMALL)
val homesection1 = enumPreference("homesection1", HomeSectionType.RESUME)
val homesection2 = enumPreference("homesection2", HomeSectionType.RESUME_AUDIO)
val homesection3 = enumPreference("homesection3", HomeSectionType.RESUME_BOOK)
val homesection4 = enumPreference("homesection4", HomeSectionType.LIVE_TV)
val homesection5 = enumPreference("homesection5", HomeSectionType.NEXT_UP)
val homesection6 = enumPreference("homesection6", HomeSectionType.LATEST_MEDIA)
}
val homesections
@@ -2,6 +2,7 @@ package org.jellyfin.androidtv.preference.store
import org.jellyfin.preference.Preference
import org.jellyfin.preference.PreferenceEnum
import org.jellyfin.preference.migration.MigrationContext
import org.jellyfin.preference.store.AsyncPreferenceStore
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.exception.ApiClientException
@@ -11,11 +12,12 @@ import org.jellyfin.sdk.model.api.ScrollDirection
import org.jellyfin.sdk.model.api.SortOrder
import timber.log.Timber
@Suppress("TooManyFunctions")
abstract class DisplayPreferencesStore(
protected var displayPreferencesId: String,
protected var app: String = "jellyfin-androidtv",
private val api: ApiClient,
) : AsyncPreferenceStore() {
) : AsyncPreferenceStore<Unit, Unit>() {
private var displayPreferencesDto: DisplayPreferencesDto? = null
private var cachedPreferences: MutableMap<String, String?> = mutableMapOf()
override val shouldUpdate: Boolean
@@ -122,6 +124,10 @@ abstract class DisplayPreferencesStore(
}
)
override fun runMigrations(body: MigrationContext<Unit, Unit>.() -> Unit) {
TODO("The DisplayPreferencesStore does not support migrations")
}
/**
* Create an empty [DisplayPreferencesDto] with default values.
*/
@@ -22,7 +22,7 @@ class DisplayPreferencesScreen : OptionsFragment() {
private val preferencesId by lazy { requireArguments().getString(ARG_PREFERENCES_ID) }
private val allowViewSelection by lazy { requireArguments().getBoolean(ARG_ALLOW_VIEW_SELECTION) }
override val stores: Array<PreferenceStore>
override val stores: Array<PreferenceStore<*, *>>
get() = arrayOf(libraryPreferences)
override val screen by optionsScreen {
@@ -50,6 +50,7 @@ import org.jellyfin.androidtv.ui.presentation.HorizontalGridPresenter;
import org.jellyfin.androidtv.ui.shared.BaseActivity;
import org.jellyfin.androidtv.ui.shared.KeyListener;
import org.jellyfin.androidtv.ui.shared.MessageListener;
import org.jellyfin.androidtv.util.CoroutineUtils;
import org.jellyfin.androidtv.util.KeyProcessor;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.apiclient.interaction.EmptyResponse;
@@ -348,7 +349,7 @@ public class StdGridFragment extends GridFragment {
libraryPreferences.set(LibraryPreferences.Companion.getFilterUnwatchedOnly(), mGridAdapter.getFilters().isUnwatchedOnly());
libraryPreferences.set(LibraryPreferences.Companion.getSortBy(), mGridAdapter.getSortBy());
libraryPreferences.set(LibraryPreferences.Companion.getSortOrder(), getSortOption(mGridAdapter.getSortBy()).order);
libraryPreferences.commitBlocking();
CoroutineUtils.runBlocking((coroutineScope, continuation) -> libraryPreferences.commit(continuation));
}
protected void addTools() {
@@ -46,6 +46,7 @@ import org.jellyfin.androidtv.ui.ProgramGridCell;
import org.jellyfin.androidtv.ui.ScrollViewListener;
import org.jellyfin.androidtv.ui.shared.BaseActivity;
import org.jellyfin.androidtv.ui.shared.MessageListener;
import org.jellyfin.androidtv.util.CoroutineUtils;
import org.jellyfin.androidtv.util.ImageUtils;
import org.jellyfin.androidtv.util.InfoLayoutHelper;
import org.jellyfin.androidtv.util.TimeUtils;
@@ -595,7 +596,7 @@ public class LiveTvGuideActivity extends BaseActivity implements LiveTvGuide {
prefs.set(LiveTvPreferences.Companion.getShowRepeatIndicator(), mRepeat.isChecked());
prefs.set(LiveTvPreferences.Companion.getChannelOrder(), mCurrentSort);
prefs.set(LiveTvPreferences.Companion.getShowLiveIndicator(), mLive.isChecked());
prefs.commitBlocking();
CoroutineUtils.runBlocking((coroutineScope, continuation) -> prefs.commit(continuation));
TvManager.clearCache();
@@ -22,7 +22,7 @@ abstract class OptionsFragment : LeanbackPreferenceFragmentCompat() {
* Preference stores used in current screen. Fragment will automatically call the update and
* commit functions for all async stores.
*/
protected open val stores: Array<PreferenceStore> = emptyArray()
protected open val stores: Array<PreferenceStore<*, *>> = emptyArray()
// Used to not build twice during onCreate()
private var skippedInitialResume = false
@@ -31,8 +31,8 @@ abstract class OptionsFragment : LeanbackPreferenceFragmentCompat() {
// Refresh all data in async stores
runBlocking {
stores
.filterIsInstance<AsyncPreferenceStore>()
.map { async { it.update() } }
.filterIsInstance<AsyncPreferenceStore<*, *>>()
.map { async { it.update() } }
.awaitAll()
}
@@ -45,8 +45,8 @@ abstract class OptionsFragment : LeanbackPreferenceFragmentCompat() {
// Save all data in async stores
runBlocking {
stores
.filterIsInstance<AsyncPreferenceStore>()
.map { async { it.commit() } }
.filterIsInstance<AsyncPreferenceStore<*, *>>()
.map { async { it.commit() } }
.awaitAll()
}
}
@@ -18,7 +18,7 @@ class OptionsItemEnum<T : Enum<T>>(
// Add exact copy of the OptionsItemMutable.bind function so the correct
// store getter and setter will be used.
override fun bind(store: PreferenceStore, preference: Preference<T>) = bind {
override fun bind(store: PreferenceStore<*, *>, preference: Preference<T>) = bind {
get { store[preference] }
set { store[preference] = it }
default { store.getDefaultValue(preference) }
@@ -11,7 +11,7 @@ abstract class OptionsItemMutable<T : Any> : OptionsItem {
protected var dependencyCheckFun: () -> Boolean = { true }
protected lateinit var binder: OptionsBinder<T>
open fun bind(store: PreferenceStore, preference: Preference<T>) = bind {
open fun bind(store: PreferenceStore<*, *>, preference: Preference<T>) = bind {
get { store[preference] }
set { store[preference] = it }
default { store.getDefaultValue(preference) }
@@ -12,7 +12,7 @@ import org.koin.android.ext.android.inject
class HomePreferencesScreen : OptionsFragment() {
private val userSettingPreferences: UserSettingPreferences by inject()
override val stores: Array<PreferenceStore>
override val stores: Array<PreferenceStore<*, *>>
get() = arrayOf(userSettingPreferences)
override val screen by optionsScreen {
@@ -0,0 +1,8 @@
@file:JvmName("CoroutineUtils")
package org.jellyfin.androidtv.util
import kotlinx.coroutines.CoroutineScope
fun <T: Any> runBlocking(block: suspend CoroutineScope.() -> T) = kotlinx.coroutines.runBlocking {
block()
}
+9 -9
View File
@@ -6,12 +6,12 @@ data class Preference<T : Any>(
val key: String,
val defaultValue: T,
val type: KClass<T>
) {
companion object {
fun int(key: String, defaultValue: Int) = Preference(key, defaultValue, Int::class)
fun long(key: String, defaultValue: Long) = Preference(key, defaultValue, Long::class)
fun boolean(key: String, defaultValue: Boolean) = Preference(key, defaultValue, Boolean::class)
fun string(key: String, defaultValue: String) = Preference(key, defaultValue, String::class)
inline fun <reified T : Any> enum(key: String, defaultValue: T) = Preference(key, defaultValue, T::class)
}
}
)
fun intPreference(key: String, defaultValue: Int) = Preference(key, defaultValue, Int::class)
fun longPreference(key: String, defaultValue: Long) = Preference(key, defaultValue, Long::class)
fun booleanPreference(key: String, defaultValue: Boolean) = Preference(key, defaultValue, Boolean::class)
fun stringPreference(key: String, defaultValue: String) = Preference(key, defaultValue, String::class)
fun <T : Any> enumPreference(key: String, defaultValue: T, type: KClass<T>) = Preference(key, defaultValue, type)
inline fun <reified T : Any> enumPreference(key: String, defaultValue: T) = enumPreference(key, defaultValue, T::class)
@@ -2,8 +2,6 @@ package org.jellyfin.preference.migration
import android.content.SharedPreferences
typealias MigrationEditor = SharedPreferences.Editor
fun <T : Enum<T>> MigrationEditor.putEnum(key: String, value: T) {
fun <T : Enum<T>> SharedPreferences.Editor.putEnum(key: String, value: T) {
putString(key, value.toString())
}
@@ -1,8 +1,6 @@
package org.jellyfin.preference.store
import kotlinx.coroutines.runBlocking
abstract class AsyncPreferenceStore : PreferenceStore() {
abstract class AsyncPreferenceStore<ME, MV> : PreferenceStore<ME, MV>() {
abstract val shouldUpdate: Boolean
/**
@@ -34,21 +32,11 @@ abstract class AsyncPreferenceStore : PreferenceStore() {
* }
* ```
*/
suspend fun transaction(body: AsyncPreferenceStore.() -> Unit): Boolean {
suspend fun transaction(body: AsyncPreferenceStore<ME, MV>.() -> Unit): Boolean {
if (shouldUpdate) update()
body()
return commit()
}
/**
* Compatability with old Java classes.
*/
fun updateBlocking() = runBlocking { update() }
/**
* Compatability with old Java classes.
*/
fun commitBlocking() = runBlocking { commit() }
}
@@ -1,6 +1,7 @@
package org.jellyfin.preference.store
import org.jellyfin.preference.Preference
import org.jellyfin.preference.migration.MigrationContext
/**
* Abstract class defining the required functions for a preference store.
@@ -8,7 +9,8 @@ import org.jellyfin.preference.Preference
* This implements shared functionality (such as Enum handling), whilst allowing for
* different backing stores.
*/
abstract class PreferenceStore {
@Suppress("TooManyFunctions")
abstract class PreferenceStore<ME, MV> {
// val value = store[Preference.x]
@Suppress("UNCHECKED_CAST")
operator fun <T : Any> get(preference: Preference<T>): T =
@@ -62,4 +64,7 @@ abstract class PreferenceStore {
protected abstract fun <T : Enum<T>> getEnum(preference: Preference<T>): T
protected abstract fun <V : Enum<V>> setEnum(preference: Preference<*>, value: Enum<V>)
// Migrations
protected abstract fun runMigrations(body: MigrationContext<ME, MV>.() -> Unit)
}
@@ -3,8 +3,8 @@ package org.jellyfin.preference.store
import android.content.SharedPreferences
import org.jellyfin.preference.Preference
import org.jellyfin.preference.PreferenceEnum
import org.jellyfin.preference.intPreference
import org.jellyfin.preference.migration.MigrationContext
import org.jellyfin.preference.migration.MigrationEditor
import timber.log.Timber
/**
@@ -26,12 +26,13 @@ import timber.log.Timber
* }
* ```
*/
@Suppress("TooManyFunctions")
abstract class SharedPreferenceStore(
/**
* SharedPreferences to read from and write to
*/
protected val sharedPreferences: SharedPreferences
) : PreferenceStore() {
) : PreferenceStore<SharedPreferences.Editor, SharedPreferences>() {
// Internal helpers
private fun transaction(body: SharedPreferences.Editor.() -> Unit) {
val editor = sharedPreferences.edit()
@@ -79,8 +80,8 @@ abstract class SharedPreferenceStore(
}
// Migrations
protected fun runMigrations(body: MigrationContext<MigrationEditor, SharedPreferences>.() -> Unit) {
val context = MigrationContext<MigrationEditor, SharedPreferences>()
override fun runMigrations(body: MigrationContext<SharedPreferences.Editor, SharedPreferences>.() -> Unit) {
val context = MigrationContext<SharedPreferences.Editor, SharedPreferences>()
context.body()
this[VERSION] = context.applyMigrations(this[VERSION]) { migration ->
@@ -95,6 +96,6 @@ abstract class SharedPreferenceStore(
/**
* Version of the preference store. Used for migration.
*/
val VERSION = Preference.int("store_version", -1)
val VERSION = intPreference("store_version", -1)
}
}
@@ -2,6 +2,7 @@ package org.jellyfin.preference
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import org.jellyfin.preference.migration.MigrationContext
import org.jellyfin.preference.store.PreferenceStore
/**
@@ -17,14 +18,14 @@ class PreferenceStoreTests : FunSpec({
}
test("Reading and writing primitives works correctly") {
verifySimpleType(1, Preference.int("key", 0))
verifySimpleType(1L, Preference.long("key", 0L))
verifySimpleType(true, Preference.boolean("key", false))
verifySimpleType("string", Preference.string("key", ""))
verifySimpleType(1, intPreference("key", 0))
verifySimpleType(1L, longPreference("key", 0L))
verifySimpleType(true, booleanPreference("key", false))
verifySimpleType("string", stringPreference("key", ""))
}
test("Reading and writing enums works correctly") {
val pref = Preference.enum("key", TestEnum.NOT_SET)
val pref = enumPreference("key", TestEnum.NOT_SET)
val expectedVal = TestEnum.SET
val instance = TestStub()
instance[pref] = expectedVal
@@ -33,7 +34,7 @@ class PreferenceStoreTests : FunSpec({
}
})
private class TestStub : PreferenceStore() {
private class TestStub : PreferenceStore<Unit, Unit>() {
var key: String? = null
private var int: Int? = null
private var long: Long? = null
@@ -79,6 +80,10 @@ private class TestStub : PreferenceStore() {
override fun <T : Any> delete(preference: Preference<T>) {
throw NotImplementedError("Not required for tests")
}
override fun runMigrations(body: MigrationContext<Unit, Unit>.() -> Unit) {
throw NotImplementedError("Not required for tests")
}
}
private enum class TestEnum { NOT_SET, SET }