Pārlūkot izejas kodu

Use 1.x preference abstraction (#8020)

* Use 1.x preference abstraction

- Uses SharedPreferences compared to 1.x impl which uses DataStore but it breaks all settings screens currently
- Move PreferencesHelper to new PreferenceStore
  - PreferencesHelper should be split into smaller preference stores and be in core or domain
- Remove flow preferences as new PreferenceStore handles changes for us

Co-authored-by: inorichi <[email protected]>

* Fix PreferenceMutableState not updating

* Fix changes not emitting on first subscription

Co-authored-by: inorichi <[email protected]>
Andreas 2 gadi atpakaļ
vecāks
revīzija
0086743a53
64 mainītis faili ar 698 papildinājumiem un 340 dzēšanām
  1. 0 1
      app/build.gradle.kts
  2. 2 4
      app/src/main/java/eu/kanade/core/prefs/PreferenceMutableState.kt
  3. 1 1
      app/src/main/java/eu/kanade/domain/chapter/interactor/SetReadStatus.kt
  4. 1 1
      app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionLanguages.kt
  5. 1 1
      app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionSources.kt
  6. 1 1
      app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionsByType.kt
  7. 4 4
      app/src/main/java/eu/kanade/domain/source/interactor/GetEnabledSources.kt
  8. 2 2
      app/src/main/java/eu/kanade/domain/source/interactor/GetLanguagesWithSources.kt
  9. 2 2
      app/src/main/java/eu/kanade/domain/source/interactor/GetSourcesWithFavoriteCount.kt
  10. 4 5
      app/src/main/java/eu/kanade/domain/source/interactor/ToggleLanguage.kt
  11. 3 4
      app/src/main/java/eu/kanade/domain/source/interactor/ToggleSource.kt
  12. 3 4
      app/src/main/java/eu/kanade/domain/source/interactor/ToggleSourcePin.kt
  13. 6 3
      app/src/main/java/eu/kanade/tachiyomi/App.kt
  14. 24 4
      app/src/main/java/eu/kanade/tachiyomi/AppModule.kt
  15. 8 4
      app/src/main/java/eu/kanade/tachiyomi/Migrations.kt
  16. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt
  17. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt
  18. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
  19. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt
  20. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt
  21. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt
  22. 4 4
      app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt
  23. 2 2
      app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
  24. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
  25. 0 6
      app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt
  26. 146 158
      app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
  27. 2 2
      app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt
  28. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt
  29. 1 1
      app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt
  30. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/SecureActivityDelegate.kt
  31. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/BasePresenter.kt
  32. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsPresenter.kt
  33. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesPresenter.kt
  34. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt
  35. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt
  36. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchPresenter.kt
  37. 3 4
      app/src/main/java/eu/kanade/tachiyomi/ui/library/setting/LibraryDisplayMode.kt
  38. 3 4
      app/src/main/java/eu/kanade/tachiyomi/ui/library/setting/LibrarySort.kt
  39. 15 5
      app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
  40. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
  41. 17 17
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
  42. 8 8
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
  43. 5 5
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderColorFilterSettings.kt
  44. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt
  45. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt
  46. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonConfig.kt
  47. 9 6
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt
  48. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt
  49. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt
  50. 4 4
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt
  51. 7 7
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt
  52. 12 12
      app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSettingsHelper.kt
  53. 6 7
      app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceDSL.kt
  54. 2 2
      app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceExtensions.kt
  55. 1 1
      app/src/main/java/eu/kanade/tachiyomi/widget/MaterialSpinnerView.kt
  56. 8 6
      app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiSearchView.kt
  57. 8 6
      app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiTextInputEditText.kt
  58. 178 0
      core/src/main/java/eu/kanade/tachiyomi/core/preference/AndroidPreference.kt
  59. 72 0
      core/src/main/java/eu/kanade/tachiyomi/core/preference/AndroidPreferenceStore.kt
  60. 27 0
      core/src/main/java/eu/kanade/tachiyomi/core/preference/Preference.kt
  61. 42 0
      core/src/main/java/eu/kanade/tachiyomi/core/preference/PreferenceStore.kt
  62. 5 5
      core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt
  63. 23 0
      core/src/main/java/eu/kanade/tachiyomi/network/NetworkPreferences.kt
  64. 0 1
      gradle/libs.versions.toml

+ 0 - 1
app/build.gradle.kts

@@ -226,7 +226,6 @@ dependencies {
 
     // Preferences
     implementation(libs.preferencektx)
-    implementation(libs.flowpreferences)
 
     // Model View Presenter
     implementation(libs.bundles.nucleus)

+ 2 - 4
app/src/main/java/eu/kanade/core/prefs/PreferenceMutableState.kt

@@ -2,9 +2,8 @@ package eu.kanade.core.prefs
 
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableStateOf
-import com.fredporciuncula.flow.preferences.Preference
+import eu.kanade.tachiyomi.core.preference.Preference
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 
@@ -16,8 +15,7 @@ class PreferenceMutableState<T>(
     private val state = mutableStateOf(preference.get())
 
     init {
-        preference.asFlow()
-            .distinctUntilChanged()
+        preference.changes()
             .onEach { state.value = it }
             .launchIn(scope)
     }

+ 1 - 1
app/src/main/java/eu/kanade/domain/chapter/interactor/SetReadStatus.kt

@@ -52,7 +52,7 @@ class SetReadStatus(
             return@withContext Result.InternalError(e)
         }
 
-        if (read && preferences.removeAfterMarkedAsRead()) {
+        if (read && preferences.removeAfterMarkedAsRead().get()) {
             manga.forEach {
                 deleteDownload.awaitAll(
                     manga = it,

+ 1 - 1
app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionLanguages.kt

@@ -12,7 +12,7 @@ class GetExtensionLanguages(
 ) {
     fun subscribe(): Flow<List<String>> {
         return combine(
-            preferences.enabledLanguages().asFlow(),
+            preferences.enabledLanguages().changes(),
             extensionManager.getAvailableExtensionsFlow(),
         ) { enabledLanguage, availableExtensions ->
             availableExtensions

+ 1 - 1
app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionSources.kt

@@ -16,7 +16,7 @@ class GetExtensionSources(
         val isMultiLangSingleSource =
             isMultiSource && extension.sources.map { it.name }.distinct().size == 1
 
-        return preferences.disabledSources().asFlow().map { disabledSources ->
+        return preferences.disabledSources().changes().map { disabledSources ->
             fun Source.isEnabled() = id.toString() !in disabledSources
 
             extension.sources

+ 1 - 1
app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionsByType.kt

@@ -16,7 +16,7 @@ class GetExtensionsByType(
         val showNsfwSources = preferences.showNsfwSource().get()
 
         return combine(
-            preferences.enabledLanguages().asFlow(),
+            preferences.enabledLanguages().changes(),
             extensionManager.getInstalledExtensionsFlow(),
             extensionManager.getUntrustedExtensionsFlow(),
             extensionManager.getAvailableExtensionsFlow(),

+ 4 - 4
app/src/main/java/eu/kanade/domain/source/interactor/GetEnabledSources.kt

@@ -17,10 +17,10 @@ class GetEnabledSources(
 
     fun subscribe(): Flow<List<Source>> {
         return combine(
-            preferences.pinnedSources().asFlow(),
-            preferences.enabledLanguages().asFlow(),
-            preferences.disabledSources().asFlow(),
-            preferences.lastUsedSource().asFlow(),
+            preferences.pinnedSources().changes(),
+            preferences.enabledLanguages().changes(),
+            preferences.disabledSources().changes(),
+            preferences.lastUsedSource().changes(),
             repository.getSources(),
         ) { pinnedSourceIds, enabledLanguages, disabledSources, lastUsedSource, sources ->
             val duplicatePins = preferences.duplicatePinnedSources().get()

+ 2 - 2
app/src/main/java/eu/kanade/domain/source/interactor/GetLanguagesWithSources.kt

@@ -14,8 +14,8 @@ class GetLanguagesWithSources(
 
     fun subscribe(): Flow<Map<String, List<Source>>> {
         return combine(
-            preferences.enabledLanguages().asFlow(),
-            preferences.disabledSources().asFlow(),
+            preferences.enabledLanguages().changes(),
+            preferences.disabledSources().changes(),
             repository.getOnlineSources(),
         ) { enabledLanguage, disabledSource, onlineSources ->
             val sortedSources = onlineSources.sortedWith(

+ 2 - 2
app/src/main/java/eu/kanade/domain/source/interactor/GetSourcesWithFavoriteCount.kt

@@ -16,8 +16,8 @@ class GetSourcesWithFavoriteCount(
 
     fun subscribe(): Flow<List<Pair<Source, Long>>> {
         return combine(
-            preferences.migrationSortingDirection().asFlow(),
-            preferences.migrationSortingMode().asFlow(),
+            preferences.migrationSortingDirection().changes(),
+            preferences.migrationSortingMode().changes(),
             repository.getSourcesWithFavoriteCount(),
         ) { direction, mode, list ->
             list.sortedWith(sortFn(direction, mode))

+ 4 - 5
app/src/main/java/eu/kanade/domain/source/interactor/ToggleLanguage.kt

@@ -1,5 +1,6 @@
 package eu.kanade.domain.source.interactor
 
+import eu.kanade.tachiyomi.core.preference.getAndSet
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.util.preference.minusAssign
 import eu.kanade.tachiyomi.util.preference.plusAssign
@@ -9,11 +10,9 @@ class ToggleLanguage(
 ) {
 
     fun await(language: String) {
-        val enabled = language in preferences.enabledLanguages().get()
-        if (enabled) {
-            preferences.enabledLanguages() -= language
-        } else {
-            preferences.enabledLanguages() += language
+        val isEnabled = language in preferences.enabledLanguages().get()
+        preferences.enabledLanguages().getAndSet { enabled ->
+            if (isEnabled) enabled.minus(language) else enabled.plus(language)
         }
     }
 }

+ 3 - 4
app/src/main/java/eu/kanade/domain/source/interactor/ToggleSource.kt

@@ -1,6 +1,7 @@
 package eu.kanade.domain.source.interactor
 
 import eu.kanade.domain.source.model.Source
+import eu.kanade.tachiyomi.core.preference.getAndSet
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.util.preference.minusAssign
 import eu.kanade.tachiyomi.util.preference.plusAssign
@@ -14,10 +15,8 @@ class ToggleSource(
     }
 
     fun await(sourceId: Long, enable: Boolean = sourceId.toString() in preferences.disabledSources().get()) {
-        if (enable) {
-            preferences.disabledSources() -= sourceId.toString()
-        } else {
-            preferences.disabledSources() += sourceId.toString()
+        preferences.disabledSources().getAndSet { disabled ->
+            if (enable) disabled.minus("$sourceId") else disabled.plus("$sourceId")
         }
     }
 }

+ 3 - 4
app/src/main/java/eu/kanade/domain/source/interactor/ToggleSourcePin.kt

@@ -1,6 +1,7 @@
 package eu.kanade.domain.source.interactor
 
 import eu.kanade.domain.source.model.Source
+import eu.kanade.tachiyomi.core.preference.getAndSet
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.util.preference.minusAssign
 import eu.kanade.tachiyomi.util.preference.plusAssign
@@ -11,10 +12,8 @@ class ToggleSourcePin(
 
     fun await(source: Source) {
         val isPinned = source.id.toString() in preferences.pinnedSources().get()
-        if (isPinned) {
-            preferences.pinnedSources() -= source.id.toString()
-        } else {
-            preferences.pinnedSources() += source.id.toString()
+        preferences.pinnedSources().getAndSet { pinned ->
+            if (isPinned) pinned.minus("${source.id}") else pinned.plus("${source.id}")
         }
     }
 }

+ 6 - 3
app/src/main/java/eu/kanade/tachiyomi/App.kt

@@ -37,6 +37,7 @@ import eu.kanade.tachiyomi.data.preference.PreferenceValues
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.glance.UpdatesGridGlanceWidget
 import eu.kanade.tachiyomi.network.NetworkHelper
+import eu.kanade.tachiyomi.network.NetworkPreferences
 import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
 import eu.kanade.tachiyomi.util.preference.asHotFlow
 import eu.kanade.tachiyomi.util.system.WebViewUtil
@@ -63,6 +64,7 @@ import java.security.Security
 class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
 
     private val preferences: PreferencesHelper by injectLazy()
+    private val networkPreferences: NetworkPreferences by injectLazy()
 
     private val disableIncognitoReceiver = DisableIncognitoReceiver()
 
@@ -82,6 +84,7 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
         }
 
         Injekt.importModule(AppModule(this))
+        Injekt.importModule(PreferenceModule(this))
         Injekt.importModule(DomainModule())
 
         setupAcra()
@@ -90,7 +93,7 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
         ProcessLifecycleOwner.get().lifecycle.addObserver(this)
 
         // Show notification to disable Incognito Mode when it's enabled
-        preferences.incognitoMode().asFlow()
+        preferences.incognitoMode().changes()
             .onEach { enabled ->
                 val notificationManager = NotificationManagerCompat.from(this)
                 if (enabled) {
@@ -141,7 +144,7 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
             }
             .launchIn(ProcessLifecycleOwner.get().lifecycleScope)
 
-        if (!LogcatLogger.isInstalled && preferences.verboseLogging()) {
+        if (!LogcatLogger.isInstalled && networkPreferences.verboseLogging().get()) {
             LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
         }
     }
@@ -168,7 +171,7 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
             diskCache(diskCacheInit)
             crossfade((300 * [email protected]).toInt())
             allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
-            if (preferences.verboseLogging()) logger(DebugLogger())
+            if (networkPreferences.verboseLogging().get()) logger(DebugLogger())
         }.build()
     }
 

+ 24 - 4
app/src/main/java/eu/kanade/tachiyomi/AppModule.kt

@@ -13,6 +13,8 @@ import eu.kanade.data.AndroidDatabaseHandler
 import eu.kanade.data.DatabaseHandler
 import eu.kanade.data.dateAdapter
 import eu.kanade.data.listOfStringsAdapter
+import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore
+import eu.kanade.tachiyomi.core.preference.PreferenceStore
 import eu.kanade.tachiyomi.data.cache.ChapterCache
 import eu.kanade.tachiyomi.data.cache.CoverCache
 import eu.kanade.tachiyomi.data.download.DownloadManager
@@ -22,7 +24,9 @@ import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.data.track.job.DelayedTrackingStore
 import eu.kanade.tachiyomi.extension.ExtensionManager
 import eu.kanade.tachiyomi.network.NetworkHelper
+import eu.kanade.tachiyomi.network.NetworkPreferences
 import eu.kanade.tachiyomi.source.SourceManager
+import eu.kanade.tachiyomi.util.system.isDevFlavor
 import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
 import kotlinx.serialization.json.Json
 import uy.kohesive.injekt.api.InjektModule
@@ -84,8 +88,6 @@ class AppModule(val app: Application) : InjektModule {
             }
         }
 
-        addSingletonFactory { PreferencesHelper(app) }
-
         addSingletonFactory { ChapterCache(app) }
 
         addSingletonFactory { CoverCache(app) }
@@ -106,8 +108,6 @@ class AppModule(val app: Application) : InjektModule {
 
         // Asynchronously init expensive components for a faster cold start
         ContextCompat.getMainExecutor(app).execute {
-            get<PreferencesHelper>()
-
             get<NetworkHelper>()
 
             get<SourceManager>()
@@ -118,3 +118,23 @@ class AppModule(val app: Application) : InjektModule {
         }
     }
 }
+
+class PreferenceModule(val application: Application) : InjektModule {
+    override fun InjektRegistrar.registerInjectables() {
+        addSingletonFactory<PreferenceStore> {
+            AndroidPreferenceStore(application)
+        }
+        addSingletonFactory {
+            NetworkPreferences(
+                preferenceStore = get(),
+                verboseLogging = isDevFlavor,
+            )
+        }
+        addSingletonFactory {
+            PreferencesHelper(
+                context = application,
+                preferenceStore = get(),
+            )
+        }
+    }
+}

+ 8 - 4
app/src/main/java/eu/kanade/tachiyomi/Migrations.kt

@@ -1,5 +1,6 @@
 package eu.kanade.tachiyomi
 
+import android.content.Context
 import android.os.Build
 import androidx.core.content.edit
 import androidx.preference.PreferenceManager
@@ -12,6 +13,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.data.updater.AppUpdateJob
 import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
+import eu.kanade.tachiyomi.network.NetworkPreferences
 import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
 import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
 import eu.kanade.tachiyomi.util.preference.minusAssign
@@ -31,9 +33,11 @@ object Migrations {
      * @param preferences Preferences of the application.
      * @return true if a migration is performed, false otherwise.
      */
-    fun upgrade(preferences: PreferencesHelper): Boolean {
-        val context = preferences.context
-
+    fun upgrade(
+        context: Context,
+        preferences: PreferencesHelper,
+        networkPreferences: NetworkPreferences,
+    ): Boolean {
         val oldVersion = preferences.lastVersionCode().get()
         if (oldVersion < BuildConfig.VERSION_CODE) {
             preferences.lastVersionCode().set(BuildConfig.VERSION_CODE)
@@ -143,7 +147,7 @@ object Migrations {
                 val wasDohEnabled = prefs.getBoolean("enable_doh", false)
                 if (wasDohEnabled) {
                     prefs.edit {
-                        putInt(PreferenceKeys.dohProvider, PREF_DOH_CLOUDFLARE)
+                        putInt(networkPreferences.dohProvider().key(), PREF_DOH_CLOUDFLARE)
                         remove("enable_doh")
                     }
                 }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt

@@ -84,7 +84,7 @@ class BackupNotifier(private val context: Context) {
         val builder = with(progressNotificationBuilder) {
             setContentTitle(context.getString(R.string.restoring_backup))
 
-            if (!preferences.hideNotificationContent()) {
+            if (!preferences.hideNotificationContent().get()) {
                 setContentText(content)
             }
 

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt

@@ -47,7 +47,7 @@ class DownloadCache(
     private var rootDir = RootDirectory(getDirectoryFromPreference())
 
     init {
-        preferences.downloadsDirectory().asFlow()
+        preferences.downloadsDirectory().changes()
             .onEach {
                 lastRenew = 0L // invalidate cache
                 rootDir = RootDirectory(getDirectoryFromPreference())

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt

@@ -414,7 +414,7 @@ class DownloadManager(
 
         return if (categoriesForManga.intersect(categoriesToExclude).isNotEmpty()) {
             chapters.filterNot { it.read }
-        } else if (!preferences.removeBookmarkedChapters()) {
+        } else if (!preferences.removeBookmarkedChapters().get()) {
             chapters.filterNot { it.bookmark }
         } else {
             chapters

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt

@@ -104,7 +104,7 @@ internal class DownloadNotifier(private val context: Context) {
                 download.pages!!.size,
             )
 
-            if (preferences.hideNotificationContent()) {
+            if (preferences.hideNotificationContent().get()) {
                 setContentTitle(downloadingProgressText)
                 setContentText(null)
             } else {

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt

@@ -39,7 +39,7 @@ class DownloadProvider(private val context: Context) {
     }
 
     init {
-        preferences.downloadsDirectory().asFlow()
+        preferences.downloadsDirectory().changes()
             .onEach { downloadsDir = UniFile.fromUri(context, it.toUri()) }
             .launchIn(scope)
     }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt

@@ -164,7 +164,7 @@ class DownloadService : Service() {
      */
     private fun onNetworkStateChanged() {
         if (isOnline()) {
-            if (preferences.downloadOnlyOverWifi() && !isConnectedToWifi()) {
+            if (preferences.downloadOnlyOverWifi().get() && !isConnectedToWifi()) {
                 stopDownloads(R.string.download_notifier_text_only_wifi)
             } else {
                 val started = downloadManager.startDownloads()

+ 4 - 4
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt

@@ -71,7 +71,7 @@ class LibraryUpdateNotifier(private val context: Context) {
      * @param total the total progress.
      */
     fun showProgressNotification(manga: List<Manga>, current: Int, total: Int) {
-        if (preferences.hideNotificationContent()) {
+        if (preferences.hideNotificationContent().get()) {
             progressNotificationBuilder
                 .setContentTitle(context.getString(R.string.notification_check_updates))
                 .setContentText("($current/$total)")
@@ -167,12 +167,12 @@ class LibraryUpdateNotifier(private val context: Context) {
                 Notifications.ID_NEW_CHAPTERS,
                 context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
                     setContentTitle(context.getString(R.string.notification_new_chapters))
-                    if (updates.size == 1 && !preferences.hideNotificationContent()) {
+                    if (updates.size == 1 && !preferences.hideNotificationContent().get()) {
                         setContentText(updates.first().first.title.chop(NOTIF_TITLE_MAX_LEN))
                     } else {
                         setContentText(context.resources.getQuantityString(R.plurals.notification_new_chapters_summary, updates.size, updates.size))
 
-                        if (!preferences.hideNotificationContent()) {
+                        if (!preferences.hideNotificationContent().get()) {
                             setStyle(
                                 NotificationCompat.BigTextStyle().bigText(
                                     updates.joinToString("\n") {
@@ -197,7 +197,7 @@ class LibraryUpdateNotifier(private val context: Context) {
             )
 
             // Per-manga notification
-            if (!preferences.hideNotificationContent()) {
+            if (!preferences.hideNotificationContent().get()) {
                 launchUI {
                     updates.forEach { (manga, chapters) ->
                         notify(manga.id.hashCode(), createNewChaptersNotification(manga, chapters))

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt

@@ -380,7 +380,7 @@ class LibraryUpdateService(
                                         failedUpdates.add(mangaWithNotif to errorMessage)
                                     }
 
-                                    if (preferences.autoUpdateTrackers()) {
+                                    if (preferences.autoUpdateTrackers().get()) {
                                         updateTrackings(mangaWithNotif, loggedServices)
                                     }
                                 }
@@ -430,7 +430,7 @@ class LibraryUpdateService(
         val source = sourceManager.getOrStub(manga.source)
 
         // Update manga metadata if needed
-        if (preferences.autoUpdateMetadata()) {
+        if (preferences.autoUpdateMetadata().get()) {
             val networkManga = source.getMangaDetails(manga.toSManga())
             updateManga.awaitUpdateFromSource(manga, networkManga, manualFetch = false, coverCache)
         }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt

@@ -248,7 +248,7 @@ class NotificationReceiver : BroadcastReceiver() {
             val toUpdate = chapterUrls.mapNotNull { getChapter.await(it, mangaId) }
                 .map {
                     val chapter = it.copy(read = true)
-                    if (preferences.removeAfterMarkedAsRead()) {
+                    if (preferences.removeAfterMarkedAsRead().get()) {
                         val manga = getManga.await(mangaId)
                         if (manga != null) {
                             val source = sourceManager.get(manga.source)

+ 0 - 6
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt

@@ -56,10 +56,6 @@ object PreferenceKeys {
 
     const val searchPinnedSourcesOnly = "search_pinned_sources_only"
 
-    const val dohProvider = "doh_provider"
-
-    const val defaultUserAgent = "default_user_agent"
-
     const val defaultChapterFilterByRead = "default_chapter_filter_by_read"
 
     const val defaultChapterFilterByDownloaded = "default_chapter_filter_by_downloaded"
@@ -72,8 +68,6 @@ object PreferenceKeys {
 
     const val defaultChapterDisplayByNameOrNumber = "default_chapter_display_by_name_or_number"
 
-    const val verboseLogging = "verbose_logging"
-
     const val autoClearChapterCache = "auto_clear_chapter_cache"
 
     fun trackUsername(syncId: Long) = "pref_mangasync_username_$syncId"

+ 146 - 158
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt

@@ -3,12 +3,11 @@ package eu.kanade.tachiyomi.data.preference
 import android.content.Context
 import android.os.Build
 import android.os.Environment
-import androidx.core.content.edit
 import androidx.core.net.toUri
-import androidx.preference.PreferenceManager
-import com.fredporciuncula.flow.preferences.FlowSharedPreferences
 import eu.kanade.domain.source.interactor.SetMigrateSorting
 import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.core.preference.PreferenceStore
+import eu.kanade.tachiyomi.core.preference.getEnum
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.track.TrackService
 import eu.kanade.tachiyomi.data.track.anilist.Anilist
@@ -18,7 +17,6 @@ import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
 import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
 import eu.kanade.tachiyomi.util.system.DeviceUtil
 import eu.kanade.tachiyomi.util.system.LocaleHelper
-import eu.kanade.tachiyomi.util.system.isDevFlavor
 import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
 import eu.kanade.tachiyomi.widget.ExtendedNavigationView
 import java.io.File
@@ -29,10 +27,10 @@ import eu.kanade.domain.manga.model.Manga as DomainManga
 import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
 import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
 
-class PreferencesHelper(val context: Context) {
-
-    private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
-    private val flowPrefs = FlowSharedPreferences(prefs)
+class PreferencesHelper(
+    val context: Context,
+    private val preferenceStore: PreferenceStore,
+) {
 
     private val defaultDownloadsDir = File(
         Environment.getExternalStorageDirectory().absolutePath + File.separator +
@@ -46,300 +44,290 @@ class PreferencesHelper(val context: Context) {
         "backup",
     ).toUri()
 
-    fun confirmExit() = prefs.getBoolean(Keys.confirmExit, false)
+    fun confirmExit() = this.preferenceStore.getBoolean(Keys.confirmExit, false)
 
-    fun sideNavIconAlignment() = flowPrefs.getInt("pref_side_nav_icon_alignment", 0)
+    fun sideNavIconAlignment() = this.preferenceStore.getInt("pref_side_nav_icon_alignment", 0)
 
-    fun useAuthenticator() = flowPrefs.getBoolean("use_biometric_lock", false)
+    fun useAuthenticator() = this.preferenceStore.getBoolean("use_biometric_lock", false)
 
-    fun lockAppAfter() = flowPrefs.getInt("lock_app_after", 0)
+    fun lockAppAfter() = this.preferenceStore.getInt("lock_app_after", 0)
 
     /**
      * For app lock. Will be set when there is a pending timed lock.
      * Otherwise this pref should be deleted.
      */
-    fun lastAppClosed() = flowPrefs.getLong("last_app_closed", 0)
+    fun lastAppClosed() = this.preferenceStore.getLong("last_app_closed", 0)
 
-    fun secureScreen() = flowPrefs.getEnum("secure_screen_v2", Values.SecureScreenMode.INCOGNITO)
+    fun secureScreen() = this.preferenceStore.getEnum("secure_screen_v2", Values.SecureScreenMode.INCOGNITO)
 
-    fun hideNotificationContent() = prefs.getBoolean(Keys.hideNotificationContent, false)
+    fun hideNotificationContent() = this.preferenceStore.getBoolean(Keys.hideNotificationContent, false)
 
-    fun autoUpdateMetadata() = prefs.getBoolean(Keys.autoUpdateMetadata, false)
+    fun autoUpdateMetadata() = this.preferenceStore.getBoolean(Keys.autoUpdateMetadata, false)
 
-    fun autoUpdateTrackers() = prefs.getBoolean(Keys.autoUpdateTrackers, false)
+    fun autoUpdateTrackers() = this.preferenceStore.getBoolean(Keys.autoUpdateTrackers, false)
 
-    fun themeMode() = flowPrefs.getEnum(
+    fun themeMode() = this.preferenceStore.getEnum(
         "pref_theme_mode_key",
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Values.ThemeMode.system } else { Values.ThemeMode.light },
     )
 
-    fun appTheme() = flowPrefs.getEnum(
+    fun appTheme() = this.preferenceStore.getEnum(
         "pref_app_theme",
         if (DeviceUtil.isDynamicColorAvailable) { Values.AppTheme.MONET } else { Values.AppTheme.DEFAULT },
     )
 
-    fun themeDarkAmoled() = flowPrefs.getBoolean("pref_theme_dark_amoled_key", false)
+    fun themeDarkAmoled() = this.preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
 
-    fun pageTransitions() = flowPrefs.getBoolean("pref_enable_transitions_key", true)
+    fun pageTransitions() = this.preferenceStore.getBoolean("pref_enable_transitions_key", true)
 
-    fun doubleTapAnimSpeed() = flowPrefs.getInt("pref_double_tap_anim_speed", 500)
+    fun doubleTapAnimSpeed() = this.preferenceStore.getInt("pref_double_tap_anim_speed", 500)
 
-    fun showPageNumber() = flowPrefs.getBoolean("pref_show_page_number_key", true)
+    fun showPageNumber() = this.preferenceStore.getBoolean("pref_show_page_number_key", true)
 
-    fun dualPageSplitPaged() = flowPrefs.getBoolean("pref_dual_page_split", false)
+    fun dualPageSplitPaged() = this.preferenceStore.getBoolean("pref_dual_page_split", false)
 
-    fun dualPageInvertPaged() = flowPrefs.getBoolean("pref_dual_page_invert", false)
+    fun dualPageInvertPaged() = this.preferenceStore.getBoolean("pref_dual_page_invert", false)
 
-    fun dualPageSplitWebtoon() = flowPrefs.getBoolean("pref_dual_page_split_webtoon", false)
+    fun dualPageSplitWebtoon() = this.preferenceStore.getBoolean("pref_dual_page_split_webtoon", false)
 
-    fun dualPageInvertWebtoon() = flowPrefs.getBoolean("pref_dual_page_invert_webtoon", false)
+    fun dualPageInvertWebtoon() = this.preferenceStore.getBoolean("pref_dual_page_invert_webtoon", false)
 
-    fun longStripSplitWebtoon() = flowPrefs.getBoolean("pref_long_strip_split_webtoon", true)
+    fun longStripSplitWebtoon() = this.preferenceStore.getBoolean("pref_long_strip_split_webtoon", true)
 
-    fun showReadingMode() = prefs.getBoolean(Keys.showReadingMode, true)
+    fun showReadingMode() = this.preferenceStore.getBoolean(Keys.showReadingMode, true)
 
-    fun trueColor() = flowPrefs.getBoolean("pref_true_color_key", false)
+    fun trueColor() = this.preferenceStore.getBoolean("pref_true_color_key", false)
 
-    fun fullscreen() = flowPrefs.getBoolean("fullscreen", true)
+    fun fullscreen() = this.preferenceStore.getBoolean("fullscreen", true)
 
-    fun cutoutShort() = flowPrefs.getBoolean("cutout_short", true)
+    fun cutoutShort() = this.preferenceStore.getBoolean("cutout_short", true)
 
-    fun keepScreenOn() = flowPrefs.getBoolean("pref_keep_screen_on_key", true)
+    fun keepScreenOn() = this.preferenceStore.getBoolean("pref_keep_screen_on_key", true)
 
-    fun customBrightness() = flowPrefs.getBoolean("pref_custom_brightness_key", false)
+    fun customBrightness() = this.preferenceStore.getBoolean("pref_custom_brightness_key", false)
 
-    fun customBrightnessValue() = flowPrefs.getInt("custom_brightness_value", 0)
+    fun customBrightnessValue() = this.preferenceStore.getInt("custom_brightness_value", 0)
 
-    fun colorFilter() = flowPrefs.getBoolean("pref_color_filter_key", false)
+    fun colorFilter() = this.preferenceStore.getBoolean("pref_color_filter_key", false)
 
-    fun colorFilterValue() = flowPrefs.getInt("color_filter_value", 0)
+    fun colorFilterValue() = this.preferenceStore.getInt("color_filter_value", 0)
 
-    fun colorFilterMode() = flowPrefs.getInt("color_filter_mode", 0)
+    fun colorFilterMode() = this.preferenceStore.getInt("color_filter_mode", 0)
 
-    fun grayscale() = flowPrefs.getBoolean("pref_grayscale", false)
+    fun grayscale() = this.preferenceStore.getBoolean("pref_grayscale", false)
 
-    fun invertedColors() = flowPrefs.getBoolean("pref_inverted_colors", false)
+    fun invertedColors() = this.preferenceStore.getBoolean("pref_inverted_colors", false)
 
-    fun defaultReadingMode() = prefs.getInt(Keys.defaultReadingMode, ReadingModeType.RIGHT_TO_LEFT.flagValue)
+    fun defaultReadingMode() = this.preferenceStore.getInt(Keys.defaultReadingMode, ReadingModeType.RIGHT_TO_LEFT.flagValue)
 
-    fun defaultOrientationType() = prefs.getInt(Keys.defaultOrientationType, OrientationType.FREE.flagValue)
+    fun defaultOrientationType() = this.preferenceStore.getInt(Keys.defaultOrientationType, OrientationType.FREE.flagValue)
 
-    fun imageScaleType() = flowPrefs.getInt("pref_image_scale_type_key", 1)
+    fun imageScaleType() = this.preferenceStore.getInt("pref_image_scale_type_key", 1)
 
-    fun zoomStart() = flowPrefs.getInt("pref_zoom_start_key", 1)
+    fun zoomStart() = this.preferenceStore.getInt("pref_zoom_start_key", 1)
 
-    fun readerTheme() = flowPrefs.getInt("pref_reader_theme_key", 1)
+    fun readerTheme() = this.preferenceStore.getInt("pref_reader_theme_key", 1)
 
-    fun alwaysShowChapterTransition() = flowPrefs.getBoolean("always_show_chapter_transition", true)
+    fun alwaysShowChapterTransition() = this.preferenceStore.getBoolean("always_show_chapter_transition", true)
 
-    fun cropBorders() = flowPrefs.getBoolean("crop_borders", false)
+    fun cropBorders() = this.preferenceStore.getBoolean("crop_borders", false)
 
-    fun navigateToPan() = flowPrefs.getBoolean("navigate_pan", true)
+    fun navigateToPan() = this.preferenceStore.getBoolean("navigate_pan", true)
 
-    fun landscapeZoom() = flowPrefs.getBoolean("landscape_zoom", true)
+    fun landscapeZoom() = this.preferenceStore.getBoolean("landscape_zoom", true)
 
-    fun cropBordersWebtoon() = flowPrefs.getBoolean("crop_borders_webtoon", false)
+    fun cropBordersWebtoon() = this.preferenceStore.getBoolean("crop_borders_webtoon", false)
 
-    fun webtoonSidePadding() = flowPrefs.getInt("webtoon_side_padding", 0)
+    fun webtoonSidePadding() = this.preferenceStore.getInt("webtoon_side_padding", 0)
 
-    fun pagerNavInverted() = flowPrefs.getEnum("reader_tapping_inverted", Values.TappingInvertMode.NONE)
+    fun pagerNavInverted() = this.preferenceStore.getEnum("reader_tapping_inverted", Values.TappingInvertMode.NONE)
 
-    fun webtoonNavInverted() = flowPrefs.getEnum("reader_tapping_inverted_webtoon", Values.TappingInvertMode.NONE)
+    fun webtoonNavInverted() = this.preferenceStore.getEnum("reader_tapping_inverted_webtoon", Values.TappingInvertMode.NONE)
 
-    fun readWithLongTap() = flowPrefs.getBoolean("reader_long_tap", true)
+    fun readWithLongTap() = this.preferenceStore.getBoolean("reader_long_tap", true)
 
-    fun readWithVolumeKeys() = flowPrefs.getBoolean("reader_volume_keys", false)
+    fun readWithVolumeKeys() = this.preferenceStore.getBoolean("reader_volume_keys", false)
 
-    fun readWithVolumeKeysInverted() = flowPrefs.getBoolean("reader_volume_keys_inverted", false)
+    fun readWithVolumeKeysInverted() = this.preferenceStore.getBoolean("reader_volume_keys_inverted", false)
 
-    fun navigationModePager() = flowPrefs.getInt("reader_navigation_mode_pager", 0)
+    fun navigationModePager() = this.preferenceStore.getInt("reader_navigation_mode_pager", 0)
 
-    fun navigationModeWebtoon() = flowPrefs.getInt("reader_navigation_mode_webtoon", 0)
+    fun navigationModeWebtoon() = this.preferenceStore.getInt("reader_navigation_mode_webtoon", 0)
 
-    fun showNavigationOverlayNewUser() = flowPrefs.getBoolean("reader_navigation_overlay_new_user", true)
+    fun showNavigationOverlayNewUser() = this.preferenceStore.getBoolean("reader_navigation_overlay_new_user", true)
 
-    fun showNavigationOverlayOnStart() = flowPrefs.getBoolean("reader_navigation_overlay_on_start", false)
+    fun showNavigationOverlayOnStart() = this.preferenceStore.getBoolean("reader_navigation_overlay_on_start", false)
 
-    fun readerHideThreshold() = flowPrefs.getEnum("reader_hide_threshold", Values.ReaderHideThreshold.LOW)
+    fun readerHideThreshold() = this.preferenceStore.getEnum("reader_hide_threshold", Values.ReaderHideThreshold.LOW)
 
-    fun portraitColumns() = flowPrefs.getInt("pref_library_columns_portrait_key", 0)
+    fun portraitColumns() = this.preferenceStore.getInt("pref_library_columns_portrait_key", 0)
 
-    fun landscapeColumns() = flowPrefs.getInt("pref_library_columns_landscape_key", 0)
+    fun landscapeColumns() = this.preferenceStore.getInt("pref_library_columns_landscape_key", 0)
 
-    fun autoUpdateTrack() = prefs.getBoolean(Keys.autoUpdateTrack, true)
+    fun autoUpdateTrack() = this.preferenceStore.getBoolean(Keys.autoUpdateTrack, true)
 
-    fun lastUsedSource() = flowPrefs.getLong("last_catalogue_source", -1)
+    fun lastUsedSource() = this.preferenceStore.getLong("last_catalogue_source", -1)
 
-    fun lastUsedCategory() = flowPrefs.getInt("last_used_category", 0)
+    fun lastUsedCategory() = this.preferenceStore.getInt("last_used_category", 0)
 
-    fun lastVersionCode() = flowPrefs.getInt("last_version_code", 0)
+    fun lastVersionCode() = this.preferenceStore.getInt("last_version_code", 0)
 
-    fun sourceDisplayMode() = flowPrefs.getObject("pref_display_mode_catalogue", LibraryDisplayMode.Serializer, LibraryDisplayMode.default)
+    fun sourceDisplayMode() = this.preferenceStore.getObject("pref_display_mode_catalogue", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
 
-    fun enabledLanguages() = flowPrefs.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages())
+    fun enabledLanguages() = this.preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages())
 
-    fun trackUsername(sync: TrackService) = prefs.getString(Keys.trackUsername(sync.id), "")
+    fun trackUsername(sync: TrackService) = this.preferenceStore.getString(Keys.trackUsername(sync.id), "")
 
-    fun trackPassword(sync: TrackService) = prefs.getString(Keys.trackPassword(sync.id), "")
+    fun trackPassword(sync: TrackService) = this.preferenceStore.getString(Keys.trackPassword(sync.id), "")
 
     fun setTrackCredentials(sync: TrackService, username: String, password: String) {
-        prefs.edit {
-            putString(Keys.trackUsername(sync.id), username)
-            putString(Keys.trackPassword(sync.id), password)
-        }
+        trackUsername(sync).set(username)
+        trackPassword(sync).set(password)
     }
 
-    fun trackToken(sync: TrackService) = flowPrefs.getString(Keys.trackToken(sync.id), "")
+    fun trackToken(sync: TrackService) = this.preferenceStore.getString(Keys.trackToken(sync.id), "")
 
-    fun anilistScoreType() = flowPrefs.getString("anilist_score_type", Anilist.POINT_10)
+    fun anilistScoreType() = this.preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
 
-    fun backupsDirectory() = flowPrefs.getString("backup_directory", defaultBackupDir.toString())
+    fun backupsDirectory() = this.preferenceStore.getString("backup_directory", defaultBackupDir.toString())
 
-    fun relativeTime() = flowPrefs.getInt("relative_time", 7)
+    fun relativeTime() = this.preferenceStore.getInt("relative_time", 7)
 
-    fun dateFormat(format: String = flowPrefs.getString(Keys.dateFormat, "").get()): DateFormat = when (format) {
+    fun dateFormat(format: String = this.preferenceStore.getString(Keys.dateFormat, "").get()): DateFormat = when (format) {
         "" -> DateFormat.getDateInstance(DateFormat.SHORT)
         else -> SimpleDateFormat(format, Locale.getDefault())
     }
 
-    fun downloadsDirectory() = flowPrefs.getString("download_directory", defaultDownloadsDir.toString())
-
-    fun downloadOnlyOverWifi() = prefs.getBoolean(Keys.downloadOnlyOverWifi, true)
+    fun downloadsDirectory() = this.preferenceStore.getString("download_directory", defaultDownloadsDir.toString())
 
-    fun saveChaptersAsCBZ() = flowPrefs.getBoolean("save_chapter_as_cbz", true)
+    fun downloadOnlyOverWifi() = this.preferenceStore.getBoolean(Keys.downloadOnlyOverWifi, true)
 
-    fun splitTallImages() = flowPrefs.getBoolean("split_tall_images", false)
+    fun saveChaptersAsCBZ() = this.preferenceStore.getBoolean("save_chapter_as_cbz", true)
 
-    fun folderPerManga() = prefs.getBoolean(Keys.folderPerManga, false)
+    fun splitTallImages() = this.preferenceStore.getBoolean("split_tall_images", false)
 
-    fun numberOfBackups() = flowPrefs.getInt("backup_slots", 2)
+    fun folderPerManga() = this.preferenceStore.getBoolean(Keys.folderPerManga, false)
 
-    fun backupInterval() = flowPrefs.getInt("backup_interval", 12)
+    fun numberOfBackups() = this.preferenceStore.getInt("backup_slots", 2)
 
-    fun removeAfterReadSlots() = prefs.getInt(Keys.removeAfterReadSlots, -1)
+    fun backupInterval() = this.preferenceStore.getInt("backup_interval", 12)
 
-    fun removeAfterMarkedAsRead() = prefs.getBoolean(Keys.removeAfterMarkedAsRead, false)
+    fun removeAfterReadSlots() = this.preferenceStore.getInt(Keys.removeAfterReadSlots, -1)
 
-    fun removeBookmarkedChapters() = prefs.getBoolean(Keys.removeBookmarkedChapters, false)
+    fun removeAfterMarkedAsRead() = this.preferenceStore.getBoolean(Keys.removeAfterMarkedAsRead, false)
 
-    fun removeExcludeCategories() = flowPrefs.getStringSet("remove_exclude_categories", emptySet())
+    fun removeBookmarkedChapters() = this.preferenceStore.getBoolean(Keys.removeBookmarkedChapters, false)
 
-    fun libraryUpdateInterval() = flowPrefs.getInt("pref_library_update_interval_key", 24)
-    fun libraryUpdateLastTimestamp() = flowPrefs.getLong("library_update_last_timestamp", 0L)
+    fun removeExcludeCategories() = this.preferenceStore.getStringSet("remove_exclude_categories", emptySet())
 
-    fun libraryUpdateDeviceRestriction() = flowPrefs.getStringSet("library_update_restriction", setOf(DEVICE_ONLY_ON_WIFI))
-    fun libraryUpdateMangaRestriction() = flowPrefs.getStringSet("library_update_manga_restriction", setOf(MANGA_HAS_UNREAD, MANGA_NON_COMPLETED, MANGA_NON_READ))
+    fun libraryUpdateInterval() = this.preferenceStore.getInt("pref_library_update_interval_key", 24)
+    fun libraryUpdateLastTimestamp() = this.preferenceStore.getLong("library_update_last_timestamp", 0L)
 
-    fun showUpdatesNavBadge() = flowPrefs.getBoolean("library_update_show_tab_badge", false)
-    fun unreadUpdatesCount() = flowPrefs.getInt("library_unread_updates_count", 0)
+    fun libraryUpdateDeviceRestriction() = this.preferenceStore.getStringSet("library_update_restriction", setOf(DEVICE_ONLY_ON_WIFI))
+    fun libraryUpdateMangaRestriction() = this.preferenceStore.getStringSet("library_update_manga_restriction", setOf(MANGA_HAS_UNREAD, MANGA_NON_COMPLETED, MANGA_NON_READ))
 
-    fun libraryUpdateCategories() = flowPrefs.getStringSet("library_update_categories", emptySet())
-    fun libraryUpdateCategoriesExclude() = flowPrefs.getStringSet("library_update_categories_exclude", emptySet())
+    fun showUpdatesNavBadge() = this.preferenceStore.getBoolean("library_update_show_tab_badge", false)
+    fun unreadUpdatesCount() = this.preferenceStore.getInt("library_unread_updates_count", 0)
 
-    fun libraryDisplayMode() = flowPrefs.getObject("pref_display_mode_library", LibraryDisplayMode.Serializer, LibraryDisplayMode.default)
+    fun libraryUpdateCategories() = this.preferenceStore.getStringSet("library_update_categories", emptySet())
+    fun libraryUpdateCategoriesExclude() = this.preferenceStore.getStringSet("library_update_categories_exclude", emptySet())
 
-    fun downloadBadge() = flowPrefs.getBoolean("display_download_badge", false)
+    fun libraryDisplayMode() = this.preferenceStore.getObject("pref_display_mode_library", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
 
-    fun localBadge() = flowPrefs.getBoolean("display_local_badge", true)
+    fun downloadBadge() = this.preferenceStore.getBoolean("display_download_badge", false)
 
-    fun downloadedOnly() = flowPrefs.getBoolean("pref_downloaded_only", false)
+    fun localBadge() = this.preferenceStore.getBoolean("display_local_badge", true)
 
-    fun unreadBadge() = flowPrefs.getBoolean("display_unread_badge", true)
+    fun downloadedOnly() = this.preferenceStore.getBoolean("pref_downloaded_only", false)
 
-    fun languageBadge() = flowPrefs.getBoolean("display_language_badge", false)
+    fun unreadBadge() = this.preferenceStore.getBoolean("display_unread_badge", true)
 
-    fun categoryTabs() = flowPrefs.getBoolean("display_category_tabs", true)
+    fun languageBadge() = this.preferenceStore.getBoolean("display_language_badge", false)
 
-    fun categoryNumberOfItems() = flowPrefs.getBoolean("display_number_of_items", false)
+    fun categoryTabs() = this.preferenceStore.getBoolean("display_category_tabs", true)
 
-    fun filterDownloaded() = flowPrefs.getInt(Keys.filterDownloaded, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
+    fun categoryNumberOfItems() = this.preferenceStore.getBoolean("display_number_of_items", false)
 
-    fun filterUnread() = flowPrefs.getInt(Keys.filterUnread, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
+    fun filterDownloaded() = this.preferenceStore.getInt(Keys.filterDownloaded, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
 
-    fun filterStarted() = flowPrefs.getInt(Keys.filterStarted, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
+    fun filterUnread() = this.preferenceStore.getInt(Keys.filterUnread, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
 
-    fun filterCompleted() = flowPrefs.getInt(Keys.filterCompleted, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
+    fun filterStarted() = this.preferenceStore.getInt(Keys.filterStarted, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
 
-    fun filterTracking(name: Int) = flowPrefs.getInt("${Keys.filterTracked}_$name", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
+    fun filterCompleted() = this.preferenceStore.getInt(Keys.filterCompleted, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
 
-    fun librarySortingMode() = flowPrefs.getObject(Keys.librarySortingMode, LibrarySort.Serializer, LibrarySort.default)
+    fun filterTracking(name: Int) = this.preferenceStore.getInt("${Keys.filterTracked}_$name", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
 
-    fun migrationSortingMode() = flowPrefs.getEnum(Keys.migrationSortingMode, SetMigrateSorting.Mode.ALPHABETICAL)
-    fun migrationSortingDirection() = flowPrefs.getEnum(Keys.migrationSortingDirection, SetMigrateSorting.Direction.ASCENDING)
+    fun librarySortingMode() = this.preferenceStore.getObject(Keys.librarySortingMode, LibrarySort.default, LibrarySort.Serializer::serialize, LibrarySort.Serializer::deserialize)
 
-    fun automaticExtUpdates() = flowPrefs.getBoolean("automatic_ext_updates", true)
+    fun migrationSortingMode() = this.preferenceStore.getEnum(Keys.migrationSortingMode, SetMigrateSorting.Mode.ALPHABETICAL)
+    fun migrationSortingDirection() = this.preferenceStore.getEnum(Keys.migrationSortingDirection, SetMigrateSorting.Direction.ASCENDING)
 
-    fun showNsfwSource() = flowPrefs.getBoolean("show_nsfw_source", true)
+    fun automaticExtUpdates() = this.preferenceStore.getBoolean("automatic_ext_updates", true)
 
-    fun extensionUpdatesCount() = flowPrefs.getInt("ext_updates_count", 0)
+    fun showNsfwSource() = this.preferenceStore.getBoolean("show_nsfw_source", true)
 
-    fun lastAppCheck() = flowPrefs.getLong("last_app_check", 0)
-    fun lastExtCheck() = flowPrefs.getLong("last_ext_check", 0)
+    fun extensionUpdatesCount() = this.preferenceStore.getInt("ext_updates_count", 0)
 
-    fun searchPinnedSourcesOnly() = prefs.getBoolean(Keys.searchPinnedSourcesOnly, false)
+    fun lastAppCheck() = this.preferenceStore.getLong("last_app_check", 0)
+    fun lastExtCheck() = this.preferenceStore.getLong("last_ext_check", 0)
 
-    fun disabledSources() = flowPrefs.getStringSet("hidden_catalogues", emptySet())
+    fun searchPinnedSourcesOnly() = this.preferenceStore.getBoolean(Keys.searchPinnedSourcesOnly, false)
 
-    fun pinnedSources() = flowPrefs.getStringSet("pinned_catalogues", emptySet())
+    fun disabledSources() = this.preferenceStore.getStringSet("hidden_catalogues", emptySet())
 
-    fun downloadNewChapters() = flowPrefs.getBoolean("download_new", false)
+    fun pinnedSources() = this.preferenceStore.getStringSet("pinned_catalogues", emptySet())
 
-    fun downloadNewChapterCategories() = flowPrefs.getStringSet("download_new_categories", emptySet())
-    fun downloadNewChapterCategoriesExclude() = flowPrefs.getStringSet("download_new_categories_exclude", emptySet())
+    fun downloadNewChapters() = this.preferenceStore.getBoolean("download_new", false)
 
-    fun autoDownloadWhileReading() = flowPrefs.getInt("auto_download_while_reading", 0)
+    fun downloadNewChapterCategories() = this.preferenceStore.getStringSet("download_new_categories", emptySet())
+    fun downloadNewChapterCategoriesExclude() = this.preferenceStore.getStringSet("download_new_categories_exclude", emptySet())
 
-    fun defaultCategory() = prefs.getInt(Keys.defaultCategory, -1)
+    fun autoDownloadWhileReading() = this.preferenceStore.getInt("auto_download_while_reading", 0)
 
-    fun categorizedDisplaySettings() = flowPrefs.getBoolean("categorized_display", false)
+    fun defaultCategory() = this.preferenceStore.getInt(Keys.defaultCategory, -1)
 
-    fun skipRead() = prefs.getBoolean(Keys.skipRead, false)
+    fun categorizedDisplaySettings() = this.preferenceStore.getBoolean("categorized_display", false)
 
-    fun skipFiltered() = prefs.getBoolean(Keys.skipFiltered, true)
+    fun skipRead() = this.preferenceStore.getBoolean(Keys.skipRead, false)
 
-    fun migrateFlags() = flowPrefs.getInt("migrate_flags", Int.MAX_VALUE)
+    fun skipFiltered() = this.preferenceStore.getBoolean(Keys.skipFiltered, true)
 
-    fun trustedSignatures() = flowPrefs.getStringSet("trusted_signatures", emptySet())
+    fun migrateFlags() = this.preferenceStore.getInt("migrate_flags", Int.MAX_VALUE)
 
-    fun dohProvider() = prefs.getInt(Keys.dohProvider, -1)
+    fun trustedSignatures() = this.preferenceStore.getStringSet("trusted_signatures", emptySet())
 
-    fun defaultUserAgent() = flowPrefs.getString(Keys.defaultUserAgent, "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0")
+    fun filterChapterByRead() = this.preferenceStore.getInt(Keys.defaultChapterFilterByRead, DomainManga.SHOW_ALL.toInt())
 
-    fun filterChapterByRead() = prefs.getInt(Keys.defaultChapterFilterByRead, DomainManga.SHOW_ALL.toInt())
+    fun filterChapterByDownloaded() = this.preferenceStore.getInt(Keys.defaultChapterFilterByDownloaded, DomainManga.SHOW_ALL.toInt())
 
-    fun filterChapterByDownloaded() = prefs.getInt(Keys.defaultChapterFilterByDownloaded, DomainManga.SHOW_ALL.toInt())
+    fun filterChapterByBookmarked() = this.preferenceStore.getInt(Keys.defaultChapterFilterByBookmarked, DomainManga.SHOW_ALL.toInt())
 
-    fun filterChapterByBookmarked() = prefs.getInt(Keys.defaultChapterFilterByBookmarked, DomainManga.SHOW_ALL.toInt())
+    fun sortChapterBySourceOrNumber() = this.preferenceStore.getInt(Keys.defaultChapterSortBySourceOrNumber, DomainManga.CHAPTER_SORTING_SOURCE.toInt())
 
-    fun sortChapterBySourceOrNumber() = prefs.getInt(Keys.defaultChapterSortBySourceOrNumber, DomainManga.CHAPTER_SORTING_SOURCE.toInt())
+    fun displayChapterByNameOrNumber() = this.preferenceStore.getInt(Keys.defaultChapterDisplayByNameOrNumber, DomainManga.CHAPTER_DISPLAY_NAME.toInt())
 
-    fun displayChapterByNameOrNumber() = prefs.getInt(Keys.defaultChapterDisplayByNameOrNumber, DomainManga.CHAPTER_DISPLAY_NAME.toInt())
+    fun sortChapterByAscendingOrDescending() = this.preferenceStore.getInt(Keys.defaultChapterSortByAscendingOrDescending, DomainManga.CHAPTER_SORT_DESC.toInt())
 
-    fun sortChapterByAscendingOrDescending() = prefs.getInt(Keys.defaultChapterSortByAscendingOrDescending, DomainManga.CHAPTER_SORT_DESC.toInt())
+    fun incognitoMode() = this.preferenceStore.getBoolean("incognito_mode", false)
 
-    fun incognitoMode() = flowPrefs.getBoolean("incognito_mode", false)
+    fun tabletUiMode() = this.preferenceStore.getEnum("tablet_ui_mode", Values.TabletUiMode.AUTOMATIC)
 
-    fun tabletUiMode() = flowPrefs.getEnum("tablet_ui_mode", Values.TabletUiMode.AUTOMATIC)
-
-    fun extensionInstaller() = flowPrefs.getEnum(
+    fun extensionInstaller() = this.preferenceStore.getEnum(
         "extension_installer",
         if (DeviceUtil.isMiui) Values.ExtensionInstaller.LEGACY else Values.ExtensionInstaller.PACKAGEINSTALLER,
     )
 
-    fun verboseLogging() = prefs.getBoolean(Keys.verboseLogging, isDevFlavor)
-
-    fun autoClearChapterCache() = prefs.getBoolean(Keys.autoClearChapterCache, false)
+    fun autoClearChapterCache() = this.preferenceStore.getBoolean(Keys.autoClearChapterCache, false)
 
-    fun duplicatePinnedSources() = flowPrefs.getBoolean("duplicate_pinned_sources", false)
+    fun duplicatePinnedSources() = this.preferenceStore.getBoolean("duplicate_pinned_sources", false)
 
     fun setChapterSettingsDefault(manga: Manga) {
-        prefs.edit {
-            putInt(Keys.defaultChapterFilterByRead, manga.readFilter)
-            putInt(Keys.defaultChapterFilterByDownloaded, manga.downloadedFilter)
-            putInt(Keys.defaultChapterFilterByBookmarked, manga.bookmarkedFilter)
-            putInt(Keys.defaultChapterSortBySourceOrNumber, manga.sorting)
-            putInt(Keys.defaultChapterDisplayByNameOrNumber, manga.displayMode)
-            putInt(Keys.defaultChapterSortByAscendingOrDescending, if (manga.sortDescending()) DomainManga.CHAPTER_SORT_DESC.toInt() else DomainManga.CHAPTER_SORT_ASC.toInt())
-        }
+        filterChapterByRead().set(manga.readFilter)
+        filterChapterByDownloaded().set(manga.downloadedFilter)
+        filterChapterByBookmarked().set(manga.bookmarkedFilter)
+        sortChapterBySourceOrNumber().set(manga.sorting)
+        displayChapterByNameOrNumber().set(manga.displayMode)
+        sortChapterByAscendingOrDescending().set(if (manga.sortDescending()) DomainManga.CHAPTER_SORT_DESC.toInt() else DomainManga.CHAPTER_SORT_ASC.toInt())
     }
 }

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt

@@ -69,9 +69,9 @@ abstract class TrackService(val id: Long) {
         get() = getUsername().isNotEmpty() &&
             getPassword().isNotEmpty()
 
-    fun getUsername() = preferences.trackUsername(this)!!
+    fun getUsername() = preferences.trackUsername(this).get()
 
-    fun getPassword() = preferences.trackPassword(this)!!
+    fun getPassword() = preferences.trackPassword(this).get()
 
     fun saveCredentials(username: String, password: String) {
         preferences.setTrackCredentials(this, username, password)

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt

@@ -99,6 +99,6 @@ class MangaUpdates(private val context: Context, id: Long) : TrackService(id) {
     }
 
     fun restoreSession(): String? {
-        return preferences.trackPassword(this)
+        return preferences.trackPassword(this).get()
     }
 }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt

@@ -193,7 +193,7 @@ class ExtensionManager(
             .map(AvailableSources::lang)
 
         val deviceLanguage = Locale.getDefault().language
-        val defaultLanguages = preferences.enabledLanguages().defaultValue
+        val defaultLanguages = preferences.enabledLanguages().defaultValue()
         val languagesToEnable = availableLanguages.filter {
             it != deviceLanguage && it.startsWith(deviceLanguage)
         }

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/SecureActivityDelegate.kt

@@ -82,8 +82,8 @@ class SecureActivityDelegateImpl : SecureActivityDelegate, DefaultLifecycleObser
     }
 
     private fun setSecureScreen() {
-        val secureScreenFlow = preferences.secureScreen().asFlow()
-        val incognitoModeFlow = preferences.incognitoMode().asFlow()
+        val secureScreenFlow = preferences.secureScreen().changes()
+        val incognitoModeFlow = preferences.incognitoMode().changes()
         combine(secureScreenFlow, incognitoModeFlow) { secureScreen, incognitoMode ->
             secureScreen == PreferenceValues.SecureScreenMode.ALWAYS ||
                 secureScreen == PreferenceValues.SecureScreenMode.INCOGNITO && incognitoMode

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/BasePresenter.kt

@@ -1,8 +1,8 @@
 package eu.kanade.tachiyomi.ui.base.presenter
 
 import android.os.Bundle
-import com.fredporciuncula.flow.preferences.Preference
 import eu.kanade.core.prefs.PreferenceMutableState
+import eu.kanade.tachiyomi.core.preference.Preference
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.MainScope
 import kotlinx.coroutines.cancel

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsPresenter.kt

@@ -123,7 +123,7 @@ class ExtensionsPresenter(
 
         presenterScope.launchIO { findAvailableExtensions() }
 
-        preferences.extensionUpdatesCount().asFlow()
+        preferences.extensionUpdatesCount().changes()
             .onEach { state.updates = it }
             .launchIn(presenterScope)
     }

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesPresenter.kt

@@ -42,11 +42,11 @@ class MigrationSourcesPresenter(
                 }
         }
 
-        preferences.migrationSortingDirection().asFlow()
+        preferences.migrationSortingDirection().changes()
             .onEach { state.sortingDirection = it }
             .launchIn(presenterScope)
 
-        preferences.migrationSortingMode().asFlow()
+        preferences.migrationSortingMode().changes()
             .onEach { state.sortingMode = it }
             .launchIn(presenterScope)
     }

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt

@@ -111,7 +111,7 @@ open class BrowseSourcePresenter(
         val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
         return produceState<GridCells>(initialValue = GridCells.Adaptive(128.dp), isLandscape) {
             (if (isLandscape) preferences.landscapeColumns() else preferences.portraitColumns())
-                .asFlow()
+                .changes()
                 .collectLatest { columns ->
                     value = if (columns == 0) GridCells.Adaptive(128.dp) else GridCells.Fixed(columns)
                 }
@@ -257,7 +257,7 @@ open class BrowseSourcePresenter(
     fun addFavorite(manga: DomainManga) {
         presenterScope.launch {
             val categories = getCategories()
-            val defaultCategoryId = preferences.defaultCategory()
+            val defaultCategoryId = preferences.defaultCategory().get()
             val defaultCategory = categories.find { it.id == defaultCategoryId.toLong() }
 
             when {

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt

@@ -174,7 +174,7 @@ open class GlobalSearchController(
      * @param searchResult result of search.
      */
     fun setItems(searchResult: List<GlobalSearchItem>) {
-        if (searchResult.isEmpty() && preferences.searchPinnedSourcesOnly()) {
+        if (searchResult.isEmpty() && preferences.searchPinnedSourcesOnly().get()) {
             binding.emptyView.show(R.string.no_pinned_sources)
         } else {
             binding.emptyView.hide()

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchPresenter.kt

@@ -123,7 +123,7 @@ open class GlobalSearchPresenter(
             return filteredSources
         }
 
-        val onlyPinnedSources = preferences.searchPinnedSourcesOnly()
+        val onlyPinnedSources = preferences.searchPinnedSourcesOnly().get()
         val pinnedSourceIds = preferences.pinnedSources().get()
 
         return enabledSources

+ 3 - 4
app/src/main/java/eu/kanade/tachiyomi/ui/library/setting/LibraryDisplayMode.kt

@@ -1,7 +1,6 @@
 package eu.kanade.tachiyomi.ui.library.setting
 
 import eu.kanade.domain.category.model.Category
-import com.fredporciuncula.flow.preferences.Serializer as PreferencesSerializer
 
 sealed class LibraryDisplayMode(
     override val flag: Long,
@@ -14,12 +13,12 @@ sealed class LibraryDisplayMode(
     object List : LibraryDisplayMode(0b00000010)
     object CoverOnlyGrid : LibraryDisplayMode(0b00000011)
 
-    object Serializer : PreferencesSerializer<LibraryDisplayMode> {
-        override fun deserialize(serialized: String): LibraryDisplayMode {
+    object Serializer {
+        fun deserialize(serialized: String): LibraryDisplayMode {
             return LibraryDisplayMode.deserialize(serialized)
         }
 
-        override fun serialize(value: LibraryDisplayMode): String {
+        fun serialize(value: LibraryDisplayMode): String {
             return value.serialize()
         }
     }

+ 3 - 4
app/src/main/java/eu/kanade/tachiyomi/ui/library/setting/LibrarySort.kt

@@ -1,7 +1,6 @@
 package eu.kanade.tachiyomi.ui.library.setting
 
 import eu.kanade.domain.category.model.Category
-import com.fredporciuncula.flow.preferences.Serializer as PreferencesSerializer
 
 data class LibrarySort(
     val type: Type,
@@ -57,12 +56,12 @@ data class LibrarySort(
         }
     }
 
-    object Serializer : PreferencesSerializer<LibrarySort> {
-        override fun deserialize(serialized: String): LibrarySort {
+    object Serializer {
+        fun deserialize(serialized: String): LibrarySort {
             return LibrarySort.deserialize(serialized)
         }
 
-        override fun serialize(value: LibrarySort): String {
+        fun serialize(value: LibrarySort): String {
             return value.serialize()
         }
     }

+ 15 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt

@@ -72,6 +72,8 @@ import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
 import logcat.LogPriority
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
 import uy.kohesive.injekt.injectLazy
 
 class MainActivity : BaseActivity() {
@@ -105,7 +107,15 @@ class MainActivity : BaseActivity() {
 
         super.onCreate(savedInstanceState)
 
-        val didMigration = if (savedInstanceState == null) Migrations.upgrade(preferences) else false
+        val didMigration = if (savedInstanceState == null) {
+            Migrations.upgrade(
+                context = applicationContext,
+                preferences = preferences,
+                networkPreferences = Injekt.get(),
+            )
+        } else {
+            false
+        }
 
         binding = MainActivityBinding.inflate(layoutInflater)
 
@@ -240,7 +250,7 @@ class MainActivity : BaseActivity() {
             }
         }
 
-        merge(preferences.showUpdatesNavBadge().asFlow(), preferences.unreadUpdatesCount().asFlow())
+        merge(preferences.showUpdatesNavBadge().changes(), preferences.unreadUpdatesCount().changes())
             .onEach { setUnreadUpdatesBadge() }
             .launchIn(lifecycleScope)
 
@@ -253,7 +263,7 @@ class MainActivity : BaseActivity() {
             .launchIn(lifecycleScope)
 
         binding.incognitoMode.isVisible = preferences.incognitoMode().get()
-        preferences.incognitoMode().asFlow()
+        preferences.incognitoMode().changes()
             .drop(1)
             .onEach {
                 binding.incognitoMode.isVisible = it
@@ -490,7 +500,7 @@ class MainActivity : BaseActivity() {
             lifecycleScope.launchUI { resetExitConfirmation() }
         } else if (backstackSize == 1 || !router.handleBack()) {
             // Regular back (i.e. closing the app)
-            if (preferences.autoClearChapterCache()) {
+            if (preferences.autoClearChapterCache().get()) {
                 chapterCache.clear()
             }
             super.onBackPressed()
@@ -534,7 +544,7 @@ class MainActivity : BaseActivity() {
     private fun shouldHandleExitConfirmation(): Boolean {
         return router.backstackSize == 1 &&
             router.getControllerWithTag("$startScreenId") != null &&
-            preferences.confirmExit() &&
+            preferences.confirmExit().get() &&
             !isConfirmingExit
     }
 

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt

@@ -283,7 +283,7 @@ class MangaPresenter(
 
                 // Now check if user previously set categories, when available
                 val categories = getCategories()
-                val defaultCategoryId = preferences.defaultCategory().toLong()
+                val defaultCategoryId = preferences.defaultCategory().get().toLong()
                 val defaultCategory = categories.find { it.id == defaultCategoryId }
                 when {
                     // Default category set

+ 17 - 17
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt

@@ -194,7 +194,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
         initializeMenu()
 
         // Finish when incognito mode is disabled
-        preferences.incognitoMode().asFlow()
+        preferences.incognitoMode().changes()
             .drop(1)
             .onEach { if (!it) finish() }
             .launchIn(lifecycleScope)
@@ -446,7 +446,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
                     presenter.setMangaReadingMode(newReadingMode.flagValue)
 
                     menuToggleToast?.cancel()
-                    if (!preferences.showReadingMode()) {
+                    if (!preferences.showReadingMode().get()) {
                         menuToggleToast = toast(newReadingMode.stringRes)
                     }
 
@@ -480,7 +480,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
         updateCropBordersShortcut()
         listOf(preferences.cropBorders(), preferences.cropBordersWebtoon())
             .forEach { pref ->
-                pref.asFlow()
+                pref.changes()
                     .onEach { updateCropBordersShortcut() }
                     .launchIn(lifecycleScope)
             }
@@ -493,7 +493,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
                 popupMenu(
                     items = OrientationType.values().map { it.flagValue to it.stringRes },
                     selectedItemId = presenter.manga?.orientationType
-                        ?: preferences.defaultOrientationType(),
+                        ?: preferences.defaultOrientationType().get(),
                 ) {
                     val newOrientation = OrientationType.fromPreference(itemId)
 
@@ -635,7 +635,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
         updateViewerInset(preferences.fullscreen().get())
         binding.viewerContainer.addView(newViewer.getView())
 
-        if (preferences.showReadingMode()) {
+        if (preferences.showReadingMode().get()) {
             showReadingModeToast(presenter.getMangaReadingMode())
         }
 
@@ -949,7 +949,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
          * Initializes the reader subscriptions.
          */
         init {
-            preferences.readerTheme().asFlow()
+            preferences.readerTheme().changes()
                 .onEach {
                     binding.readerContainer.setBackgroundResource(
                         when (preferences.readerTheme().get()) {
@@ -962,41 +962,41 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
                 }
                 .launchIn(lifecycleScope)
 
-            preferences.showPageNumber().asFlow()
+            preferences.showPageNumber().changes()
                 .onEach { setPageNumberVisibility(it) }
                 .launchIn(lifecycleScope)
 
-            preferences.trueColor().asFlow()
+            preferences.trueColor().changes()
                 .onEach { setTrueColor(it) }
                 .launchIn(lifecycleScope)
 
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
-                preferences.cutoutShort().asFlow()
+                preferences.cutoutShort().changes()
                     .onEach { setCutoutShort(it) }
                     .launchIn(lifecycleScope)
             }
 
-            preferences.keepScreenOn().asFlow()
+            preferences.keepScreenOn().changes()
                 .onEach { setKeepScreenOn(it) }
                 .launchIn(lifecycleScope)
 
-            preferences.customBrightness().asFlow()
+            preferences.customBrightness().changes()
                 .onEach { setCustomBrightness(it) }
                 .launchIn(lifecycleScope)
 
-            preferences.colorFilter().asFlow()
+            preferences.colorFilter().changes()
                 .onEach { setColorFilter(it) }
                 .launchIn(lifecycleScope)
 
-            preferences.colorFilterMode().asFlow()
+            preferences.colorFilterMode().changes()
                 .onEach { setColorFilter(preferences.colorFilter().get()) }
                 .launchIn(lifecycleScope)
 
-            merge(preferences.grayscale().asFlow(), preferences.invertedColors().asFlow())
+            merge(preferences.grayscale().changes(), preferences.invertedColors().changes())
                 .onEach { setLayerPaint(preferences.grayscale().get(), preferences.invertedColors().get()) }
                 .launchIn(lifecycleScope)
 
-            preferences.fullscreen().asFlow()
+            preferences.fullscreen().changes()
                 .onEach {
                     WindowCompat.setDecorFitsSystemWindows(window, !it)
                     updateViewerInset(it)
@@ -1060,7 +1060,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
          */
         private fun setCustomBrightness(enabled: Boolean) {
             if (enabled) {
-                preferences.customBrightnessValue().asFlow()
+                preferences.customBrightnessValue().changes()
                     .sample(100)
                     .onEach { setCustomBrightnessValue(it) }
                     .launchIn(lifecycleScope)
@@ -1074,7 +1074,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
          */
         private fun setColorFilter(enabled: Boolean) {
             if (enabled) {
-                preferences.colorFilterValue().asFlow()
+                preferences.colorFilterValue().changes()
                     .sample(100)
                     .onEach { setColorFilterValue(it) }
                     .launchIn(lifecycleScope)

+ 8 - 8
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt

@@ -140,11 +140,11 @@ class ReaderPresenter(
             ?: error("Requested chapter of id $chapterId not found in chapter list")
 
         val chaptersForReader = when {
-            (preferences.skipRead() || preferences.skipFiltered()) -> {
+            (preferences.skipRead().get() || preferences.skipFiltered().get()) -> {
                 val filteredChapters = chapters.filterNot {
                     when {
-                        preferences.skipRead() && it.read -> true
-                        preferences.skipFiltered() -> {
+                        preferences.skipRead().get() && it.read -> true
+                        preferences.skipFiltered().get() -> {
                             (manga.readFilter == DomainManga.CHAPTER_SHOW_READ.toInt() && !it.read) ||
                                 (manga.readFilter == DomainManga.CHAPTER_SHOW_UNREAD.toInt() && it.read) ||
                                 (manga.downloadedFilter == DomainManga.CHAPTER_SHOW_DOWNLOADED.toInt() && !downloadManager.isChapterDownloaded(it.name, it.scanlator, manga.title, manga.source)) ||
@@ -502,7 +502,7 @@ class ReaderPresenter(
     private fun deleteChapterIfNeeded(currentChapter: ReaderChapter) {
         // Determine which chapter should be deleted and enqueue
         val currentChapterPosition = chapterList.indexOf(currentChapter)
-        val removeAfterReadSlots = preferences.removeAfterReadSlots()
+        val removeAfterReadSlots = preferences.removeAfterReadSlots().get()
         val chapterToDelete = chapterList.getOrNull(currentChapterPosition - removeAfterReadSlots)
 
         if (removeAfterReadSlots != 0 && chapterDownload != null) {
@@ -619,7 +619,7 @@ class ReaderPresenter(
      * Returns the viewer position used by this manga or the default one.
      */
     fun getMangaReadingMode(resolveDefault: Boolean = true): Int {
-        val default = preferences.defaultReadingMode()
+        val default = preferences.defaultReadingMode().get()
         val readingMode = ReadingModeType.fromPreference(manga?.readingModeType)
         return when {
             resolveDefault && readingMode == ReadingModeType.DEFAULT -> default
@@ -656,7 +656,7 @@ class ReaderPresenter(
      * Returns the orientation type used by this manga or the default one.
      */
     fun getMangaOrientationType(resolveDefault: Boolean = true): Int {
-        val default = preferences.defaultOrientationType()
+        val default = preferences.defaultOrientationType().get()
         val orientation = OrientationType.fromPreference(manga?.orientationType)
         return when {
             resolveDefault && orientation == OrientationType.DEFAULT -> default
@@ -714,7 +714,7 @@ class ReaderPresenter(
         val filename = generateFilename(manga, page)
 
         // Pictures directory.
-        val relativePath = if (preferences.folderPerManga()) DiskUtil.buildValidFilename(manga.title) else ""
+        val relativePath = if (preferences.folderPerManga().get()) DiskUtil.buildValidFilename(manga.title) else ""
 
         // Copy file in background.
         try {
@@ -818,7 +818,7 @@ class ReaderPresenter(
      * will run in a background thread and errors are ignored.
      */
     private fun updateTrackChapterRead(readerChapter: ReaderChapter) {
-        if (!preferences.autoUpdateTrack()) return
+        if (!preferences.autoUpdateTrack().get()) return
         val manga = manga ?: return
 
         val chapterRead = readerChapter.chapter.chapter_number.toDouble()

+ 5 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderColorFilterSettings.kt

@@ -32,15 +32,15 @@ class ReaderColorFilterSettings @JvmOverloads constructor(context: Context, attr
     init {
         addView(binding.root)
 
-        preferences.colorFilter().asFlow()
+        preferences.colorFilter().changes()
             .onEach { setColorFilter(it) }
             .launchIn((context as ReaderActivity).lifecycleScope)
 
-        preferences.colorFilterMode().asFlow()
+        preferences.colorFilterMode().changes()
             .onEach { setColorFilter(preferences.colorFilter().get()) }
             .launchIn(context.lifecycleScope)
 
-        preferences.customBrightness().asFlow()
+        preferences.customBrightness().changes()
             .onEach { setCustomBrightness(it) }
             .launchIn(context.lifecycleScope)
 
@@ -139,7 +139,7 @@ class ReaderColorFilterSettings @JvmOverloads constructor(context: Context, attr
      */
     private fun setCustomBrightness(enabled: Boolean) {
         if (enabled) {
-            preferences.customBrightnessValue().asFlow()
+            preferences.customBrightnessValue().changes()
                 .sample(100)
                 .onEach { setCustomBrightnessValue(it) }
                 .launchIn((context as ReaderActivity).lifecycleScope)
@@ -167,7 +167,7 @@ class ReaderColorFilterSettings @JvmOverloads constructor(context: Context, attr
      */
     private fun setColorFilter(enabled: Boolean) {
         if (enabled) {
-            preferences.colorFilterValue().asFlow()
+            preferences.colorFilterValue().changes()
                 .sample(100)
                 .onEach { setColorFilterValue(it) }
                 .launchIn((context as ReaderActivity).lifecycleScope)

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt

@@ -1,6 +1,6 @@
 package eu.kanade.tachiyomi.ui.reader.viewer
 
-import com.fredporciuncula.flow.preferences.Preference
+import eu.kanade.tachiyomi.core.preference.Preference
 import eu.kanade.tachiyomi.data.preference.PreferenceValues.TappingInvertMode
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import kotlinx.coroutines.CoroutineScope
@@ -80,7 +80,7 @@ abstract class ViewerConfig(preferences: PreferencesHelper, private val scope: C
         valueAssignment: (T) -> Unit,
         onChanged: (T) -> Unit = {},
     ) {
-        asFlow()
+        changes()
             .onEach { valueAssignment(it) }
             .distinctUntilChanged()
             .onEach { onChanged(it) }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt

@@ -78,7 +78,7 @@ class PagerConfig(
 
         preferences.pagerNavInverted()
             .register({ tappingInverted = it }, { navigator.invertMode = it })
-        preferences.pagerNavInverted().asFlow()
+        preferences.pagerNavInverted().changes()
             .drop(1)
             .onEach { navigationModeChangedListener?.invoke() }
             .launchIn(scope)

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonConfig.kt

@@ -51,7 +51,7 @@ class WebtoonConfig(
 
         preferences.webtoonNavInverted()
             .register({ tappingInverted = it }, { navigator.invertMode = it })
-        preferences.webtoonNavInverted().asFlow()
+        preferences.webtoonNavInverted().changes()
             .drop(1)
             .onEach { navigationModeChangedListener?.invoke() }
             .launchIn(scope)
@@ -71,7 +71,7 @@ class WebtoonConfig(
                 },
             )
 
-        preferences.readerTheme().asFlow()
+        preferences.readerTheme().changes()
             .drop(1)
             .distinctUntilChanged()
             .onEach { themeChangedListener?.invoke() }

+ 9 - 6
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt

@@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Target
 import eu.kanade.tachiyomi.data.preference.PreferenceValues
 import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.network.NetworkHelper
+import eu.kanade.tachiyomi.network.NetworkPreferences
 import eu.kanade.tachiyomi.network.PREF_DOH_360
 import eu.kanade.tachiyomi.network.PREF_DOH_ADGUARD
 import eu.kanade.tachiyomi.network.PREF_DOH_ALIDNS
@@ -69,6 +70,7 @@ class SettingsAdvancedController(
     private val network: NetworkHelper by injectLazy()
     private val chapterCache: ChapterCache by injectLazy()
     private val trackManager: TrackManager by injectLazy()
+    private val networkPreferences: NetworkPreferences by injectLazy()
 
     @SuppressLint("BatteryLife")
     override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
@@ -96,7 +98,7 @@ class SettingsAdvancedController(
         }
 
         switchPreference {
-            key = Keys.verboseLogging
+            key = networkPreferences.verboseLogging().key()
             titleRes = R.string.pref_verbose_logging
             summaryRes = R.string.pref_verbose_logging_summary
             defaultValue = isDevFlavor
@@ -189,7 +191,7 @@ class SettingsAdvancedController(
                 onClick { clearWebViewData() }
             }
             intListPreference {
-                key = Keys.dohProvider
+                key = networkPreferences.dohProvider().key()
                 titleRes = R.string.pref_dns_over_https
                 entries = arrayOf(
                     context.getString(R.string.disabled),
@@ -227,10 +229,11 @@ class SettingsAdvancedController(
                     true
                 }
             }
+            val defaultUserAgent = networkPreferences.defaultUserAgent()
             editTextPreference {
-                key = Keys.defaultUserAgent
+                key = defaultUserAgent.key()
                 titleRes = R.string.pref_user_agent_string
-                text = preferences.defaultUserAgent().get()
+                text = defaultUserAgent.get()
                 summary = network.defaultUserAgent
 
                 onChange {
@@ -247,10 +250,10 @@ class SettingsAdvancedController(
                 key = "pref_reset_user_agent"
                 titleRes = R.string.pref_reset_user_agent_string
 
-                visibleIf(preferences.defaultUserAgent()) { it != preferences.defaultUserAgent().defaultValue }
+                visibleIf(defaultUserAgent) { it != defaultUserAgent.defaultValue() }
 
                 onClick {
-                    preferences.defaultUserAgent().delete()
+                    defaultUserAgent.delete()
                     activity?.toast(R.string.requires_app_restart)
                 }
             }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt

@@ -136,7 +136,7 @@ class SettingsBackupController : SettingsController() {
                     }
                 }
 
-                preferences.backupsDirectory().asFlow()
+                preferences.backupsDirectory().changes()
                     .onEach { path ->
                         val dir = UniFile.fromUri(context, path.toUri())
                         summary = dir.filePath + "/automatic"

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt

@@ -130,7 +130,7 @@ abstract class SettingsController : PreferenceController() {
         (activity as? AppCompatActivity)?.supportActionBar?.title = getTitle()
     }
 
-    inline fun <T> Preference.visibleIf(preference: com.fredporciuncula.flow.preferences.Preference<T>, crossinline block: (T) -> Boolean) {
+    inline fun <T> Preference.visibleIf(preference: eu.kanade.tachiyomi.core.preference.Preference<T>, crossinline block: (T) -> Boolean) {
         preference.asHotFlow { isVisible = block(it) }
             .launchIn(viewScope)
     }

+ 4 - 4
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt

@@ -58,7 +58,7 @@ class SettingsDownloadController : SettingsController() {
                 ctrl.showDialog(router)
             }
 
-            preferences.downloadsDirectory().asFlow()
+            preferences.downloadsDirectory().changes()
                 .onEach { path ->
                     val dir = UniFile.fromUri(context, path.toUri())
                     summary = dir.filePath ?: path
@@ -114,7 +114,7 @@ class SettingsDownloadController : SettingsController() {
                 entries = categories.map { it.visualName(context) }.toTypedArray()
                 entryValues = categories.map { it.id.toString() }.toTypedArray()
 
-                preferences.removeExcludeCategories().asFlow()
+                preferences.removeExcludeCategories().changes()
                     .onEach { mutable ->
                         val selected = mutable
                             .mapNotNull { id -> categories.find { it.id == id.toLong() } }
@@ -171,10 +171,10 @@ class SettingsDownloadController : SettingsController() {
                     }
                 }
 
-                preferences.downloadNewChapterCategories().asFlow()
+                preferences.downloadNewChapterCategories().changes()
                     .onEach { updateSummary() }
                     .launchIn(viewScope)
-                preferences.downloadNewChapterCategoriesExclude().asFlow()
+                preferences.downloadNewChapterCategoriesExclude().changes()
                     .onEach { updateSummary() }
                     .launchIn(viewScope)
             }

+ 7 - 7
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt

@@ -79,7 +79,7 @@ class SettingsLibraryController : SettingsController() {
                     }
                 }
 
-                combine(preferences.portraitColumns().asFlow(), preferences.landscapeColumns().asFlow()) { portraitCols, landscapeCols -> Pair(portraitCols, landscapeCols) }
+                combine(preferences.portraitColumns().changes(), preferences.landscapeColumns().changes()) { portraitCols, landscapeCols -> Pair(portraitCols, landscapeCols) }
                     .onEach { (portraitCols, landscapeCols) ->
                         val portrait = getColumnValue(portraitCols)
                         val landscape = getColumnValue(landscapeCols)
@@ -114,7 +114,7 @@ class SettingsLibraryController : SettingsController() {
                 entryValues = arrayOf("-1") + allCategories.map { it.id.toString() }.toTypedArray()
                 defaultValue = "-1"
 
-                val selectedCategory = allCategories.find { it.id == preferences.defaultCategory().toLong() }
+                val selectedCategory = allCategories.find { it.id == preferences.defaultCategory().get().toLong() }
                 summary = selectedCategory?.visualName(context)
                     ?: context.getString(R.string.default_category_summary)
                 onChange { newValue ->
@@ -129,7 +129,7 @@ class SettingsLibraryController : SettingsController() {
                 bindTo(preferences.categorizedDisplaySettings())
                 titleRes = R.string.categorized_display_settings
 
-                preferences.categorizedDisplaySettings().asFlow()
+                preferences.categorizedDisplaySettings().changes()
                     .onEach {
                         if (it.not()) {
                             resetCategoryFlags.await()
@@ -197,7 +197,7 @@ class SettingsLibraryController : SettingsController() {
                     summary = context.getString(R.string.restrictions, restrictionsText)
                 }
 
-                preferences.libraryUpdateDeviceRestriction().asFlow()
+                preferences.libraryUpdateDeviceRestriction().changes()
                     .onEach { updateSummary() }
                     .launchIn(viewScope)
             }
@@ -226,7 +226,7 @@ class SettingsLibraryController : SettingsController() {
                     summary = restrictionsText
                 }
 
-                preferences.libraryUpdateMangaRestriction().asFlow()
+                preferences.libraryUpdateMangaRestriction().changes()
                     .onEach { updateSummary() }
                     .launchIn(viewScope)
             }
@@ -269,10 +269,10 @@ class SettingsLibraryController : SettingsController() {
                     }
                 }
 
-                preferences.libraryUpdateCategories().asFlow()
+                preferences.libraryUpdateCategories().changes()
                     .onEach { updateSummary() }
                     .launchIn(viewScope)
-                preferences.libraryUpdateCategoriesExclude().asFlow()
+                preferences.libraryUpdateCategoriesExclude().changes()
                     .onEach { updateSummary() }
                     .launchIn(viewScope)
             }

+ 12 - 12
app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSettingsHelper.kt

@@ -27,12 +27,12 @@ object ChapterSettingsHelper {
     suspend fun applySettingDefaults(mangaId: Long) {
         setMangaChapterFlags.awaitSetAllFlags(
             mangaId = mangaId,
-            unreadFilter = preferences.filterChapterByRead().toLong(),
-            downloadedFilter = preferences.filterChapterByDownloaded().toLong(),
-            bookmarkedFilter = preferences.filterChapterByBookmarked().toLong(),
-            sortingMode = preferences.sortChapterBySourceOrNumber().toLong(),
-            sortingDirection = preferences.sortChapterByAscendingOrDescending().toLong(),
-            displayMode = preferences.displayChapterByNameOrNumber().toLong(),
+            unreadFilter = preferences.filterChapterByRead().get().toLong(),
+            downloadedFilter = preferences.filterChapterByDownloaded().get().toLong(),
+            bookmarkedFilter = preferences.filterChapterByBookmarked().get().toLong(),
+            sortingMode = preferences.sortChapterBySourceOrNumber().get().toLong(),
+            sortingDirection = preferences.sortChapterByAscendingOrDescending().get().toLong(),
+            displayMode = preferences.displayChapterByNameOrNumber().get().toLong(),
         )
     }
 
@@ -45,12 +45,12 @@ object ChapterSettingsHelper {
                 .map { manga ->
                     setMangaChapterFlags.awaitSetAllFlags(
                         mangaId = manga.id,
-                        unreadFilter = preferences.filterChapterByRead().toLong(),
-                        downloadedFilter = preferences.filterChapterByDownloaded().toLong(),
-                        bookmarkedFilter = preferences.filterChapterByBookmarked().toLong(),
-                        sortingMode = preferences.sortChapterBySourceOrNumber().toLong(),
-                        sortingDirection = preferences.sortChapterByAscendingOrDescending().toLong(),
-                        displayMode = preferences.displayChapterByNameOrNumber().toLong(),
+                        unreadFilter = preferences.filterChapterByRead().get().toLong(),
+                        downloadedFilter = preferences.filterChapterByDownloaded().get().toLong(),
+                        bookmarkedFilter = preferences.filterChapterByBookmarked().get().toLong(),
+                        sortingMode = preferences.sortChapterBySourceOrNumber().get().toLong(),
+                        sortingDirection = preferences.sortChapterByAscendingOrDescending().get().toLong(),
+                        displayMode = preferences.displayChapterByNameOrNumber().get().toLong(),
                     )
                 }
         }

+ 6 - 7
app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceDSL.kt

@@ -106,15 +106,14 @@ inline fun <P : Preference> PreferenceGroup.addThenInit(p: P, block: P.() -> Uni
     }
 }
 
-inline fun <T> Preference.bindTo(preference: com.fredporciuncula.flow.preferences.Preference<T>) {
-    key = preference.key
-    defaultValue = preference.defaultValue
+inline fun <T> Preference.bindTo(preference: eu.kanade.tachiyomi.core.preference.Preference<T>) {
+    key = preference.key()
+    defaultValue = preference.defaultValue()
 }
 
-inline fun <T> ListPreference.bindTo(preference: com.fredporciuncula.flow.preferences.Preference<T>) {
-    key = preference.key
-    // ListPreferences persist values as strings, even when we're using our IntListPreference
-    defaultValue = preference.defaultValue.toString()
+inline fun <T> ListPreference.bindTo(preference: eu.kanade.tachiyomi.core.preference.Preference<T>) {
+    key = preference.key()
+    defaultValue = preference.defaultValue().toString()
 }
 
 inline fun Preference.onClick(crossinline block: () -> Unit) {

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceExtensions.kt

@@ -1,7 +1,7 @@
 package eu.kanade.tachiyomi.util.preference
 
 import android.widget.CompoundButton
-import com.fredporciuncula.flow.preferences.Preference
+import eu.kanade.tachiyomi.core.preference.Preference
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.onEach
 
@@ -15,7 +15,7 @@ fun CompoundButton.bindToPreference(pref: Preference<Boolean>) {
 
 fun <T> Preference<T>.asHotFlow(block: (T) -> Unit): Flow<T> {
     block(get())
-    return asFlow()
+    return changes()
         .onEach { block(it) }
 }
 

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/widget/MaterialSpinnerView.kt

@@ -14,8 +14,8 @@ import androidx.appcompat.widget.PopupMenu
 import androidx.core.content.withStyledAttributes
 import androidx.core.view.forEach
 import androidx.core.view.get
-import com.fredporciuncula.flow.preferences.Preference
 import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.core.preference.Preference
 import eu.kanade.tachiyomi.databinding.PrefSpinnerBinding
 import eu.kanade.tachiyomi.util.system.getResourceColor
 

+ 8 - 6
app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiSearchView.kt

@@ -31,13 +31,15 @@ class TachiyomiSearchView @JvmOverloads constructor(
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
         scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
-        Injekt.get<PreferencesHelper>().incognitoMode().asHotFlow {
-            imeOptions = if (it) {
-                imeOptions or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING
-            } else {
-                imeOptions and EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING.inv()
+        Injekt.get<PreferencesHelper>().incognitoMode()
+            .asHotFlow {
+                imeOptions = if (it) {
+                    imeOptions or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING
+                } else {
+                    imeOptions and EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING.inv()
+                }
             }
-        }.launchIn(scope!!)
+            .launchIn(scope!!)
     }
 
     override fun setOnQueryTextListener(listener: OnQueryTextListener?) {

+ 8 - 6
app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiTextInputEditText.kt

@@ -49,13 +49,15 @@ class TachiyomiTextInputEditText @JvmOverloads constructor(
          * if [PreferencesHelper.incognitoMode] is true. Some IMEs may not respect this flag.
          */
         fun EditText.setIncognito(viewScope: CoroutineScope) {
-            Injekt.get<PreferencesHelper>().incognitoMode().asHotFlow {
-                imeOptions = if (it) {
-                    imeOptions or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING
-                } else {
-                    imeOptions and EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING.inv()
+            Injekt.get<PreferencesHelper>().incognitoMode()
+                .asHotFlow {
+                    imeOptions = if (it) {
+                        imeOptions or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING
+                    } else {
+                        imeOptions and EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING.inv()
+                    }
                 }
-            }.launchIn(viewScope)
+                .launchIn(viewScope)
         }
     }
 }

+ 178 - 0
core/src/main/java/eu/kanade/tachiyomi/core/preference/AndroidPreference.kt

@@ -0,0 +1,178 @@
+package eu.kanade.tachiyomi.core.preference
+
+import android.content.SharedPreferences
+import android.content.SharedPreferences.Editor
+import androidx.core.content.edit
+import eu.kanade.tachiyomi.util.system.logcat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+sealed class AndroidPreference<T>(
+    private val preferences: SharedPreferences,
+    private val keyFlow: Flow<String?>,
+    private val key: String,
+    private val defaultValue: T,
+) : Preference<T> {
+
+    abstract fun read(preferences: SharedPreferences, key: String, defaultValue: T): T
+
+    abstract fun write(key: String, value: T): Editor.() -> Unit
+
+    override fun key(): String {
+        return key
+    }
+
+    override fun get(): T {
+        return read(preferences, key, defaultValue)
+    }
+
+    override fun set(value: T) {
+        preferences.edit(action = write(key, value))
+    }
+
+    override fun isSet(): Boolean {
+        return preferences.contains(key)
+    }
+
+    override fun delete() {
+        preferences.edit {
+            remove(key)
+        }
+    }
+
+    override fun defaultValue(): T {
+        return defaultValue
+    }
+
+    override fun changes(): Flow<T> {
+        return keyFlow
+            .filter { it == key || it == null }
+            .onStart { emit("ignition") }
+            .map { get() }
+            .conflate()
+    }
+
+    override fun stateIn(scope: CoroutineScope): StateFlow<T> {
+        return changes().stateIn(scope, SharingStarted.Eagerly, get())
+    }
+
+    class StringPrimitive(
+        preferences: SharedPreferences,
+        keyFlow: Flow<String?>,
+        key: String,
+        defaultValue: String
+    ) : AndroidPreference<String>(preferences, keyFlow, key, defaultValue) {
+        override fun read(preferences: SharedPreferences, key: String, defaultValue: String): String {
+            return preferences.getString(key, defaultValue) ?: defaultValue
+        }
+
+        override fun write(key: String, value: String): Editor.() -> Unit = {
+            putString(key, value)
+        }
+    }
+
+    class LongPrimitive(
+        preferences: SharedPreferences,
+        keyFlow: Flow<String?>,
+        key: String,
+        defaultValue: Long
+    ) : AndroidPreference<Long>(preferences, keyFlow, key, defaultValue) {
+        override fun read(preferences: SharedPreferences, key: String, defaultValue: Long): Long {
+            return preferences.getLong(key, defaultValue)
+        }
+
+        override fun write(key: String, value: Long): Editor.() -> Unit = {
+            putLong(key, value)
+        }
+    }
+
+    class IntPrimitive(
+        preferences: SharedPreferences,
+        keyFlow: Flow<String?>,
+        key: String,
+        defaultValue: Int
+    ) : AndroidPreference<Int>(preferences, keyFlow, key, defaultValue) {
+        override fun read(preferences: SharedPreferences, key: String, defaultValue: Int): Int {
+            return preferences.getInt(key, defaultValue)
+        }
+
+        override fun write(key: String, value: Int): Editor.() -> Unit = {
+            putInt(key, value)
+        }
+    }
+
+    class FloatPrimitive(
+        preferences: SharedPreferences,
+        keyFlow: Flow<String?>,
+        key: String,
+        defaultValue: Float
+    ) : AndroidPreference<Float>(preferences, keyFlow, key, defaultValue) {
+        override fun read(preferences: SharedPreferences, key: String, defaultValue: Float): Float {
+            return preferences.getFloat(key, defaultValue)
+        }
+
+        override fun write(key: String, value: Float): Editor.() -> Unit = {
+            putFloat(key, value)
+        }
+    }
+
+    class BooleanPrimitive(
+        preferences: SharedPreferences,
+        keyFlow: Flow<String?>,
+        key: String,
+        defaultValue: Boolean
+    ) : AndroidPreference<Boolean>(preferences, keyFlow, key, defaultValue) {
+        override fun read(preferences: SharedPreferences, key: String, defaultValue: Boolean): Boolean {
+            return preferences.getBoolean(key, defaultValue)
+        }
+
+        override fun write(key: String, value: Boolean): Editor.() -> Unit = {
+            putBoolean(key, value)
+        }
+    }
+
+    class StringSetPrimitive(
+        preferences: SharedPreferences,
+        keyFlow: Flow<String?>,
+        key: String,
+        defaultValue: Set<String>
+    ) : AndroidPreference<Set<String>>(preferences, keyFlow, key, defaultValue) {
+        override fun read(preferences: SharedPreferences, key: String, defaultValue: Set<String>): Set<String> {
+            return preferences.getStringSet(key, defaultValue) ?: defaultValue
+        }
+
+        override fun write(key: String, value: Set<String>): Editor.() -> Unit = {
+            putStringSet(key, value)
+        }
+    }
+
+    class Object<T>(
+        preferences: SharedPreferences,
+        keyFlow: Flow<String?>,
+        key: String,
+        defaultValue: T,
+        val serializer: (T) -> String,
+        val deserializer: (String) -> T
+    ) : AndroidPreference<T>(preferences, keyFlow, key, defaultValue) {
+        override fun read(preferences: SharedPreferences, key: String, defaultValue: T): T {
+            return try {
+                preferences.getString(key, null)?.let(deserializer) ?: defaultValue
+            } catch (e: Exception) {
+                defaultValue
+            }
+        }
+
+        override fun write(key: String, value: T): Editor.() -> Unit = {
+            putString(key, serializer(value))
+        }
+    }
+
+}

+ 72 - 0
core/src/main/java/eu/kanade/tachiyomi/core/preference/AndroidPreferenceStore.kt

@@ -0,0 +1,72 @@
+package eu.kanade.tachiyomi.core.preference
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.preference.PreferenceManager
+import eu.kanade.tachiyomi.core.preference.AndroidPreference.BooleanPrimitive
+import eu.kanade.tachiyomi.core.preference.AndroidPreference.FloatPrimitive
+import eu.kanade.tachiyomi.core.preference.AndroidPreference.IntPrimitive
+import eu.kanade.tachiyomi.core.preference.AndroidPreference.LongPrimitive
+import eu.kanade.tachiyomi.core.preference.AndroidPreference.Object
+import eu.kanade.tachiyomi.core.preference.AndroidPreference.StringPrimitive
+import eu.kanade.tachiyomi.core.preference.AndroidPreference.StringSetPrimitive
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.callbackFlow
+
+class AndroidPreferenceStore(
+    context: Context
+) : PreferenceStore {
+
+    private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
+
+    private val keyFlow = sharedPreferences.keyFlow
+
+    override fun getString(key: String, defaultValue: String): Preference<String> {
+        return StringPrimitive(sharedPreferences, keyFlow, key, defaultValue)
+    }
+
+    override fun getLong(key: String, defaultValue: Long): Preference<Long> {
+        return LongPrimitive(sharedPreferences, keyFlow,key, defaultValue)
+    }
+
+    override fun getInt(key: String, defaultValue: Int): Preference<Int> {
+        return IntPrimitive(sharedPreferences, keyFlow,key, defaultValue)
+    }
+
+    override fun getFloat(key: String, defaultValue: Float): Preference<Float> {
+        return FloatPrimitive(sharedPreferences, keyFlow,key, defaultValue)
+    }
+
+    override fun getBoolean(key: String, defaultValue: Boolean): Preference<Boolean> {
+        return BooleanPrimitive(sharedPreferences, keyFlow, key, defaultValue)
+    }
+
+    override fun getStringSet(key: String, defaultValue: Set<String>): Preference<Set<String>> {
+        return StringSetPrimitive(sharedPreferences, keyFlow, key, defaultValue)
+    }
+
+    override fun <T> getObject(
+        key: String,
+        defaultValue: T,
+        serializer: (T) -> String,
+        deserializer: (String) -> T,
+    ): Preference<T> {
+        return Object(
+            preferences = sharedPreferences,
+            keyFlow = keyFlow,
+            key = key,
+            defaultValue = defaultValue,
+            serializer = serializer,
+            deserializer = deserializer
+        )
+}
+}
+
+private val SharedPreferences.keyFlow
+    get() = callbackFlow {
+        val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key: String? -> trySend(key) }
+        registerOnSharedPreferenceChangeListener(listener)
+        awaitClose {
+            unregisterOnSharedPreferenceChangeListener(listener)
+        }
+    }

+ 27 - 0
core/src/main/java/eu/kanade/tachiyomi/core/preference/Preference.kt

@@ -0,0 +1,27 @@
+package eu.kanade.tachiyomi.core.preference
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+interface Preference<T> {
+
+    fun key(): String
+
+    fun get(): T
+
+    fun set(value: T)
+
+    fun isSet(): Boolean
+
+    fun delete()
+
+    fun defaultValue(): T
+
+    fun changes(): Flow<T>
+
+    fun stateIn(scope: CoroutineScope): StateFlow<T>
+
+}
+
+inline fun <reified T, R : T> Preference<T>.getAndSet(crossinline block: (T) -> R) = set(block(get()))

+ 42 - 0
core/src/main/java/eu/kanade/tachiyomi/core/preference/PreferenceStore.kt

@@ -0,0 +1,42 @@
+package eu.kanade.tachiyomi.core.preference
+
+interface PreferenceStore {
+
+    fun getString(key: String, defaultValue: String = ""): Preference<String>
+
+    fun getLong(key: String, defaultValue: Long = 0): Preference<Long>
+
+    fun getInt(key: String, defaultValue: Int = 0): Preference<Int>
+
+    fun getFloat(key: String, defaultValue: Float = 0f): Preference<Float>
+
+    fun getBoolean(key: String, defaultValue: Boolean = false): Preference<Boolean>
+
+    fun getStringSet(key: String, defaultValue: Set<String> = emptySet()): Preference<Set<String>>
+
+    fun <T> getObject(
+        key: String,
+        defaultValue: T,
+        serializer: (T) -> String,
+        deserializer: (String) -> T
+    ): Preference<T>
+
+}
+
+inline fun <reified T : Enum<T>> PreferenceStore.getEnum(
+    key: String,
+    defaultValue: T
+) : Preference<T> {
+    return getObject(
+        key = key,
+        defaultValue = defaultValue,
+        serializer = { it.name },
+        deserializer = {
+            try {
+                enumValueOf(it)
+            } catch (e: IllegalArgumentException) {
+                defaultValue
+            }
+        }
+    )
+}

+ 5 - 5
core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt

@@ -8,13 +8,13 @@ import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
 import okhttp3.Cache
 import okhttp3.OkHttpClient
 import okhttp3.logging.HttpLoggingInterceptor
+import uy.kohesive.injekt.injectLazy
 import java.io.File
 import java.util.concurrent.TimeUnit
 
 class NetworkHelper(context: Context) {
 
-    // TODO: Abstract preferences similar to 1.x
-    private val preferences = PreferenceManager.getDefaultSharedPreferences(context)
+    private val preferences: NetworkPreferences by injectLazy()
 
     private val cacheDir = File(context.cacheDir, "network_cache")
     private val cacheSize = 5L * 1024 * 1024 // 5 MiB
@@ -36,14 +36,14 @@ class NetworkHelper(context: Context) {
                 .addInterceptor(userAgentInterceptor)
                 .addNetworkInterceptor(http103Interceptor)
 
-            if (preferences.getBoolean("verbose_logging", false)) {
+            if (preferences.verboseLogging().get()) {
                 val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
                     level = HttpLoggingInterceptor.Level.HEADERS
                 }
                 builder.addNetworkInterceptor(httpLoggingInterceptor)
             }
 
-            when (preferences.getInt("doh_provider", -1)) {
+            when (preferences.dohProvider().get()) {
                 PREF_DOH_CLOUDFLARE -> builder.dohCloudflare()
                 PREF_DOH_GOOGLE -> builder.dohGoogle()
                 PREF_DOH_ADGUARD -> builder.dohAdGuard()
@@ -70,6 +70,6 @@ class NetworkHelper(context: Context) {
     }
 
     val defaultUserAgent by lazy {
-        preferences.getString("default_user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0")!!
+        preferences.defaultUserAgent().get()
     }
 }

+ 23 - 0
core/src/main/java/eu/kanade/tachiyomi/network/NetworkPreferences.kt

@@ -0,0 +1,23 @@
+package eu.kanade.tachiyomi.network
+
+import eu.kanade.tachiyomi.core.preference.Preference
+import eu.kanade.tachiyomi.core.preference.PreferenceStore
+
+class NetworkPreferences(
+    private val preferenceStore: PreferenceStore,
+    private val verboseLogging: Boolean = false
+) {
+
+    fun verboseLogging(): Preference<Boolean> {
+        return preferenceStore.getBoolean("verbose_logging", verboseLogging)
+    }
+
+    fun dohProvider(): Preference<Int> {
+        return preferenceStore.getInt("doh_provider", -1)
+    }
+
+    fun defaultUserAgent(): Preference<String> {
+        return preferenceStore.getString("default_user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0")
+    }
+
+}

+ 0 - 1
gradle/libs.versions.toml

@@ -37,7 +37,6 @@ sqlitektx = "androidx.sqlite:sqlite-ktx:2.3.0-alpha05"
 sqlite-android = "com.github.requery:sqlite-android:3.36.0"
 
 preferencektx = "androidx.preference:preference-ktx:1.2.0"
-flowpreferences = "com.fredporciuncula:flow-preferences:1.8.0"
 
 nucleus-core = { module = "info.android15.nucleus:nucleus", version.ref = "nucleus_version" }
 nucleus-supportv7 = { module = "info.android15.nucleus:nucleus-support-v7", version.ref = "nucleus_version" }