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 <3521738+inorichi@users.noreply.github.com>

* Fix PreferenceMutableState not updating

* Fix changes not emitting on first subscription

Co-authored-by: inorichi <3521738+inorichi@users.noreply.github.com>
This commit is contained in:
Andreas 2022-09-17 17:48:24 +02:00 committed by GitHub
parent bc8c45832e
commit 0086743a53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 698 additions and 340 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -16,7 +16,7 @@ class GetExtensionSources(
val isMultiLangSingleSource = val isMultiLangSingleSource =
isMultiSource && extension.sources.map { it.name }.distinct().size == 1 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 fun Source.isEnabled() = id.toString() !in disabledSources
extension.sources extension.sources

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,6 +13,8 @@ import eu.kanade.data.AndroidDatabaseHandler
import eu.kanade.data.DatabaseHandler import eu.kanade.data.DatabaseHandler
import eu.kanade.data.dateAdapter import eu.kanade.data.dateAdapter
import eu.kanade.data.listOfStringsAdapter 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.ChapterCache
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.download.DownloadManager 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.data.track.job.DelayedTrackingStore
import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.util.system.isDevFlavor
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.api.InjektModule import uy.kohesive.injekt.api.InjektModule
@ -84,8 +88,6 @@ class AppModule(val app: Application) : InjektModule {
} }
} }
addSingletonFactory { PreferencesHelper(app) }
addSingletonFactory { ChapterCache(app) } addSingletonFactory { ChapterCache(app) }
addSingletonFactory { CoverCache(app) } addSingletonFactory { CoverCache(app) }
@ -106,8 +108,6 @@ class AppModule(val app: Application) : InjektModule {
// Asynchronously init expensive components for a faster cold start // Asynchronously init expensive components for a faster cold start
ContextCompat.getMainExecutor(app).execute { ContextCompat.getMainExecutor(app).execute {
get<PreferencesHelper>()
get<NetworkHelper>() get<NetworkHelper>()
get<SourceManager>() 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(),
)
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -56,10 +56,6 @@ object PreferenceKeys {
const val searchPinnedSourcesOnly = "search_pinned_sources_only" 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 defaultChapterFilterByRead = "default_chapter_filter_by_read"
const val defaultChapterFilterByDownloaded = "default_chapter_filter_by_downloaded" 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 defaultChapterDisplayByNameOrNumber = "default_chapter_display_by_name_or_number"
const val verboseLogging = "verbose_logging"
const val autoClearChapterCache = "auto_clear_chapter_cache" const val autoClearChapterCache = "auto_clear_chapter_cache"
fun trackUsername(syncId: Long) = "pref_mangasync_username_$syncId" fun trackUsername(syncId: Long) = "pref_mangasync_username_$syncId"

View file

@ -3,12 +3,11 @@ package eu.kanade.tachiyomi.data.preference
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import androidx.core.content.edit
import androidx.core.net.toUri 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.domain.source.interactor.SetMigrateSorting
import eu.kanade.tachiyomi.R 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.database.models.Manga
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.anilist.Anilist 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.ui.reader.setting.ReadingModeType
import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.LocaleHelper 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.util.system.isDynamicColorAvailable
import eu.kanade.tachiyomi.widget.ExtendedNavigationView import eu.kanade.tachiyomi.widget.ExtendedNavigationView
import java.io.File 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.PreferenceKeys as Keys
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
class PreferencesHelper(val context: Context) { class PreferencesHelper(
val context: Context,
private val prefs = PreferenceManager.getDefaultSharedPreferences(context) private val preferenceStore: PreferenceStore,
private val flowPrefs = FlowSharedPreferences(prefs) ) {
private val defaultDownloadsDir = File( private val defaultDownloadsDir = File(
Environment.getExternalStorageDirectory().absolutePath + File.separator + Environment.getExternalStorageDirectory().absolutePath + File.separator +
@ -46,300 +44,290 @@ class PreferencesHelper(val context: Context) {
"backup", "backup",
).toUri() ).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. * For app lock. Will be set when there is a pending timed lock.
* Otherwise this pref should be deleted. * 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", "pref_theme_mode_key",
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Values.ThemeMode.system } else { Values.ThemeMode.light }, 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", "pref_app_theme",
if (DeviceUtil.isDynamicColorAvailable) { Values.AppTheme.MONET } else { Values.AppTheme.DEFAULT }, 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) { fun setTrackCredentials(sync: TrackService, username: String, password: String) {
prefs.edit { trackUsername(sync).set(username)
putString(Keys.trackUsername(sync.id), username) trackPassword(sync).set(password)
putString(Keys.trackPassword(sync.id), 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) "" -> DateFormat.getDateInstance(DateFormat.SHORT)
else -> SimpleDateFormat(format, Locale.getDefault()) else -> SimpleDateFormat(format, Locale.getDefault())
} }
fun downloadsDirectory() = flowPrefs.getString("download_directory", defaultDownloadsDir.toString()) fun downloadsDirectory() = this.preferenceStore.getString("download_directory", defaultDownloadsDir.toString())
fun downloadOnlyOverWifi() = prefs.getBoolean(Keys.downloadOnlyOverWifi, true) fun downloadOnlyOverWifi() = this.preferenceStore.getBoolean(Keys.downloadOnlyOverWifi, true)
fun saveChaptersAsCBZ() = flowPrefs.getBoolean("save_chapter_as_cbz", true) fun saveChaptersAsCBZ() = this.preferenceStore.getBoolean("save_chapter_as_cbz", true)
fun splitTallImages() = flowPrefs.getBoolean("split_tall_images", false) fun splitTallImages() = this.preferenceStore.getBoolean("split_tall_images", false)
fun folderPerManga() = prefs.getBoolean(Keys.folderPerManga, false) fun folderPerManga() = this.preferenceStore.getBoolean(Keys.folderPerManga, false)
fun numberOfBackups() = flowPrefs.getInt("backup_slots", 2) fun numberOfBackups() = this.preferenceStore.getInt("backup_slots", 2)
fun backupInterval() = flowPrefs.getInt("backup_interval", 12) fun backupInterval() = this.preferenceStore.getInt("backup_interval", 12)
fun removeAfterReadSlots() = prefs.getInt(Keys.removeAfterReadSlots, -1) fun removeAfterReadSlots() = this.preferenceStore.getInt(Keys.removeAfterReadSlots, -1)
fun removeAfterMarkedAsRead() = prefs.getBoolean(Keys.removeAfterMarkedAsRead, false) fun removeAfterMarkedAsRead() = this.preferenceStore.getBoolean(Keys.removeAfterMarkedAsRead, false)
fun removeBookmarkedChapters() = prefs.getBoolean(Keys.removeBookmarkedChapters, false) fun removeBookmarkedChapters() = this.preferenceStore.getBoolean(Keys.removeBookmarkedChapters, false)
fun removeExcludeCategories() = flowPrefs.getStringSet("remove_exclude_categories", emptySet()) fun removeExcludeCategories() = this.preferenceStore.getStringSet("remove_exclude_categories", emptySet())
fun libraryUpdateInterval() = flowPrefs.getInt("pref_library_update_interval_key", 24) fun libraryUpdateInterval() = this.preferenceStore.getInt("pref_library_update_interval_key", 24)
fun libraryUpdateLastTimestamp() = flowPrefs.getLong("library_update_last_timestamp", 0L) fun libraryUpdateLastTimestamp() = this.preferenceStore.getLong("library_update_last_timestamp", 0L)
fun libraryUpdateDeviceRestriction() = flowPrefs.getStringSet("library_update_restriction", setOf(DEVICE_ONLY_ON_WIFI)) fun libraryUpdateDeviceRestriction() = this.preferenceStore.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 libraryUpdateMangaRestriction() = this.preferenceStore.getStringSet("library_update_manga_restriction", setOf(MANGA_HAS_UNREAD, MANGA_NON_COMPLETED, MANGA_NON_READ))
fun showUpdatesNavBadge() = flowPrefs.getBoolean("library_update_show_tab_badge", false) fun showUpdatesNavBadge() = this.preferenceStore.getBoolean("library_update_show_tab_badge", false)
fun unreadUpdatesCount() = flowPrefs.getInt("library_unread_updates_count", 0) fun unreadUpdatesCount() = this.preferenceStore.getInt("library_unread_updates_count", 0)
fun libraryUpdateCategories() = flowPrefs.getStringSet("library_update_categories", emptySet()) fun libraryUpdateCategories() = this.preferenceStore.getStringSet("library_update_categories", emptySet())
fun libraryUpdateCategoriesExclude() = flowPrefs.getStringSet("library_update_categories_exclude", emptySet()) fun libraryUpdateCategoriesExclude() = this.preferenceStore.getStringSet("library_update_categories_exclude", emptySet())
fun libraryDisplayMode() = flowPrefs.getObject("pref_display_mode_library", LibraryDisplayMode.Serializer, LibraryDisplayMode.default) fun libraryDisplayMode() = this.preferenceStore.getObject("pref_display_mode_library", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
fun downloadBadge() = flowPrefs.getBoolean("display_download_badge", false) fun downloadBadge() = this.preferenceStore.getBoolean("display_download_badge", false)
fun localBadge() = flowPrefs.getBoolean("display_local_badge", true) fun localBadge() = this.preferenceStore.getBoolean("display_local_badge", true)
fun downloadedOnly() = flowPrefs.getBoolean("pref_downloaded_only", false) fun downloadedOnly() = this.preferenceStore.getBoolean("pref_downloaded_only", false)
fun unreadBadge() = flowPrefs.getBoolean("display_unread_badge", true) fun unreadBadge() = this.preferenceStore.getBoolean("display_unread_badge", true)
fun languageBadge() = flowPrefs.getBoolean("display_language_badge", false) fun languageBadge() = this.preferenceStore.getBoolean("display_language_badge", false)
fun categoryTabs() = flowPrefs.getBoolean("display_category_tabs", true) fun categoryTabs() = this.preferenceStore.getBoolean("display_category_tabs", true)
fun categoryNumberOfItems() = flowPrefs.getBoolean("display_number_of_items", false) fun categoryNumberOfItems() = this.preferenceStore.getBoolean("display_number_of_items", false)
fun filterDownloaded() = flowPrefs.getInt(Keys.filterDownloaded, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value) fun filterDownloaded() = this.preferenceStore.getInt(Keys.filterDownloaded, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterUnread() = flowPrefs.getInt(Keys.filterUnread, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value) fun filterUnread() = this.preferenceStore.getInt(Keys.filterUnread, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterStarted() = flowPrefs.getInt(Keys.filterStarted, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value) fun filterStarted() = this.preferenceStore.getInt(Keys.filterStarted, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterCompleted() = flowPrefs.getInt(Keys.filterCompleted, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value) fun filterCompleted() = this.preferenceStore.getInt(Keys.filterCompleted, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterTracking(name: Int) = flowPrefs.getInt("${Keys.filterTracked}_$name", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value) fun filterTracking(name: Int) = this.preferenceStore.getInt("${Keys.filterTracked}_$name", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun librarySortingMode() = flowPrefs.getObject(Keys.librarySortingMode, LibrarySort.Serializer, LibrarySort.default) fun librarySortingMode() = this.preferenceStore.getObject(Keys.librarySortingMode, LibrarySort.default, LibrarySort.Serializer::serialize, LibrarySort.Serializer::deserialize)
fun migrationSortingMode() = flowPrefs.getEnum(Keys.migrationSortingMode, SetMigrateSorting.Mode.ALPHABETICAL) fun migrationSortingMode() = this.preferenceStore.getEnum(Keys.migrationSortingMode, SetMigrateSorting.Mode.ALPHABETICAL)
fun migrationSortingDirection() = flowPrefs.getEnum(Keys.migrationSortingDirection, SetMigrateSorting.Direction.ASCENDING) fun migrationSortingDirection() = this.preferenceStore.getEnum(Keys.migrationSortingDirection, SetMigrateSorting.Direction.ASCENDING)
fun automaticExtUpdates() = flowPrefs.getBoolean("automatic_ext_updates", true) fun automaticExtUpdates() = this.preferenceStore.getBoolean("automatic_ext_updates", true)
fun showNsfwSource() = flowPrefs.getBoolean("show_nsfw_source", true) fun showNsfwSource() = this.preferenceStore.getBoolean("show_nsfw_source", true)
fun extensionUpdatesCount() = flowPrefs.getInt("ext_updates_count", 0) fun extensionUpdatesCount() = this.preferenceStore.getInt("ext_updates_count", 0)
fun lastAppCheck() = flowPrefs.getLong("last_app_check", 0) fun lastAppCheck() = this.preferenceStore.getLong("last_app_check", 0)
fun lastExtCheck() = flowPrefs.getLong("last_ext_check", 0) fun lastExtCheck() = this.preferenceStore.getLong("last_ext_check", 0)
fun searchPinnedSourcesOnly() = prefs.getBoolean(Keys.searchPinnedSourcesOnly, false) fun searchPinnedSourcesOnly() = this.preferenceStore.getBoolean(Keys.searchPinnedSourcesOnly, false)
fun disabledSources() = flowPrefs.getStringSet("hidden_catalogues", emptySet()) fun disabledSources() = this.preferenceStore.getStringSet("hidden_catalogues", emptySet())
fun pinnedSources() = flowPrefs.getStringSet("pinned_catalogues", emptySet()) fun pinnedSources() = this.preferenceStore.getStringSet("pinned_catalogues", emptySet())
fun downloadNewChapters() = flowPrefs.getBoolean("download_new", false) fun downloadNewChapters() = this.preferenceStore.getBoolean("download_new", false)
fun downloadNewChapterCategories() = flowPrefs.getStringSet("download_new_categories", emptySet()) fun downloadNewChapterCategories() = this.preferenceStore.getStringSet("download_new_categories", emptySet())
fun downloadNewChapterCategoriesExclude() = flowPrefs.getStringSet("download_new_categories_exclude", emptySet()) fun downloadNewChapterCategoriesExclude() = this.preferenceStore.getStringSet("download_new_categories_exclude", emptySet())
fun autoDownloadWhileReading() = flowPrefs.getInt("auto_download_while_reading", 0) fun autoDownloadWhileReading() = this.preferenceStore.getInt("auto_download_while_reading", 0)
fun defaultCategory() = prefs.getInt(Keys.defaultCategory, -1) fun defaultCategory() = this.preferenceStore.getInt(Keys.defaultCategory, -1)
fun categorizedDisplaySettings() = flowPrefs.getBoolean("categorized_display", false) fun categorizedDisplaySettings() = this.preferenceStore.getBoolean("categorized_display", false)
fun skipRead() = prefs.getBoolean(Keys.skipRead, false) fun skipRead() = this.preferenceStore.getBoolean(Keys.skipRead, false)
fun skipFiltered() = prefs.getBoolean(Keys.skipFiltered, true) fun skipFiltered() = this.preferenceStore.getBoolean(Keys.skipFiltered, true)
fun migrateFlags() = flowPrefs.getInt("migrate_flags", Int.MAX_VALUE) fun migrateFlags() = this.preferenceStore.getInt("migrate_flags", Int.MAX_VALUE)
fun trustedSignatures() = flowPrefs.getStringSet("trusted_signatures", emptySet()) fun trustedSignatures() = this.preferenceStore.getStringSet("trusted_signatures", emptySet())
fun dohProvider() = prefs.getInt(Keys.dohProvider, -1) fun filterChapterByRead() = this.preferenceStore.getInt(Keys.defaultChapterFilterByRead, DomainManga.SHOW_ALL.toInt())
fun defaultUserAgent() = flowPrefs.getString(Keys.defaultUserAgent, "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0") fun filterChapterByDownloaded() = this.preferenceStore.getInt(Keys.defaultChapterFilterByDownloaded, DomainManga.SHOW_ALL.toInt())
fun filterChapterByRead() = prefs.getInt(Keys.defaultChapterFilterByRead, DomainManga.SHOW_ALL.toInt()) fun filterChapterByBookmarked() = this.preferenceStore.getInt(Keys.defaultChapterFilterByBookmarked, DomainManga.SHOW_ALL.toInt())
fun filterChapterByDownloaded() = prefs.getInt(Keys.defaultChapterFilterByDownloaded, DomainManga.SHOW_ALL.toInt()) fun sortChapterBySourceOrNumber() = this.preferenceStore.getInt(Keys.defaultChapterSortBySourceOrNumber, DomainManga.CHAPTER_SORTING_SOURCE.toInt())
fun filterChapterByBookmarked() = prefs.getInt(Keys.defaultChapterFilterByBookmarked, DomainManga.SHOW_ALL.toInt()) fun displayChapterByNameOrNumber() = this.preferenceStore.getInt(Keys.defaultChapterDisplayByNameOrNumber, DomainManga.CHAPTER_DISPLAY_NAME.toInt())
fun sortChapterBySourceOrNumber() = prefs.getInt(Keys.defaultChapterSortBySourceOrNumber, DomainManga.CHAPTER_SORTING_SOURCE.toInt()) fun sortChapterByAscendingOrDescending() = this.preferenceStore.getInt(Keys.defaultChapterSortByAscendingOrDescending, DomainManga.CHAPTER_SORT_DESC.toInt())
fun displayChapterByNameOrNumber() = prefs.getInt(Keys.defaultChapterDisplayByNameOrNumber, DomainManga.CHAPTER_DISPLAY_NAME.toInt()) fun incognitoMode() = this.preferenceStore.getBoolean("incognito_mode", false)
fun sortChapterByAscendingOrDescending() = prefs.getInt(Keys.defaultChapterSortByAscendingOrDescending, DomainManga.CHAPTER_SORT_DESC.toInt()) fun tabletUiMode() = this.preferenceStore.getEnum("tablet_ui_mode", Values.TabletUiMode.AUTOMATIC)
fun incognitoMode() = flowPrefs.getBoolean("incognito_mode", false) fun extensionInstaller() = this.preferenceStore.getEnum(
fun tabletUiMode() = flowPrefs.getEnum("tablet_ui_mode", Values.TabletUiMode.AUTOMATIC)
fun extensionInstaller() = flowPrefs.getEnum(
"extension_installer", "extension_installer",
if (DeviceUtil.isMiui) Values.ExtensionInstaller.LEGACY else Values.ExtensionInstaller.PACKAGEINSTALLER, if (DeviceUtil.isMiui) Values.ExtensionInstaller.LEGACY else Values.ExtensionInstaller.PACKAGEINSTALLER,
) )
fun verboseLogging() = prefs.getBoolean(Keys.verboseLogging, isDevFlavor) fun autoClearChapterCache() = this.preferenceStore.getBoolean(Keys.autoClearChapterCache, false)
fun autoClearChapterCache() = prefs.getBoolean(Keys.autoClearChapterCache, false) fun duplicatePinnedSources() = this.preferenceStore.getBoolean("duplicate_pinned_sources", false)
fun duplicatePinnedSources() = flowPrefs.getBoolean("duplicate_pinned_sources", false)
fun setChapterSettingsDefault(manga: Manga) { fun setChapterSettingsDefault(manga: Manga) {
prefs.edit { filterChapterByRead().set(manga.readFilter)
putInt(Keys.defaultChapterFilterByRead, manga.readFilter) filterChapterByDownloaded().set(manga.downloadedFilter)
putInt(Keys.defaultChapterFilterByDownloaded, manga.downloadedFilter) filterChapterByBookmarked().set(manga.bookmarkedFilter)
putInt(Keys.defaultChapterFilterByBookmarked, manga.bookmarkedFilter) sortChapterBySourceOrNumber().set(manga.sorting)
putInt(Keys.defaultChapterSortBySourceOrNumber, manga.sorting) displayChapterByNameOrNumber().set(manga.displayMode)
putInt(Keys.defaultChapterDisplayByNameOrNumber, manga.displayMode) sortChapterByAscendingOrDescending().set(if (manga.sortDescending()) DomainManga.CHAPTER_SORT_DESC.toInt() else DomainManga.CHAPTER_SORT_ASC.toInt())
putInt(Keys.defaultChapterSortByAscendingOrDescending, if (manga.sortDescending()) DomainManga.CHAPTER_SORT_DESC.toInt() else DomainManga.CHAPTER_SORT_ASC.toInt())
}
} }
} }

View file

@ -69,9 +69,9 @@ abstract class TrackService(val id: Long) {
get() = getUsername().isNotEmpty() && get() = getUsername().isNotEmpty() &&
getPassword().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) { fun saveCredentials(username: String, password: String) {
preferences.setTrackCredentials(this, username, password) preferences.setTrackCredentials(this, username, password)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -130,7 +130,7 @@ abstract class SettingsController : PreferenceController() {
(activity as? AppCompatActivity)?.supportActionBar?.title = getTitle() (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) } preference.asHotFlow { isVisible = block(it) }
.launchIn(viewScope) .launchIn(viewScope)
} }

View file

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

View file

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

View file

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

View file

@ -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>) { inline fun <T> Preference.bindTo(preference: eu.kanade.tachiyomi.core.preference.Preference<T>) {
key = preference.key key = preference.key()
defaultValue = preference.defaultValue defaultValue = preference.defaultValue()
} }
inline fun <T> ListPreference.bindTo(preference: com.fredporciuncula.flow.preferences.Preference<T>) { inline fun <T> ListPreference.bindTo(preference: eu.kanade.tachiyomi.core.preference.Preference<T>) {
key = preference.key key = preference.key()
// ListPreferences persist values as strings, even when we're using our IntListPreference defaultValue = preference.defaultValue().toString()
defaultValue = preference.defaultValue.toString()
} }
inline fun Preference.onClick(crossinline block: () -> Unit) { inline fun Preference.onClick(crossinline block: () -> Unit) {

View file

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.util.preference package eu.kanade.tachiyomi.util.preference
import android.widget.CompoundButton 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.Flow
import kotlinx.coroutines.flow.onEach 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> { fun <T> Preference<T>.asHotFlow(block: (T) -> Unit): Flow<T> {
block(get()) block(get())
return asFlow() return changes()
.onEach { block(it) } .onEach { block(it) }
} }

View file

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

View file

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

View file

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

View file

@ -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))
}
}
}

View file

@ -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)
}
}

View file

@ -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()))

View file

@ -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
}
}
)
}

View file

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

View file

@ -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")
}
}

View file

@ -37,7 +37,6 @@ sqlitektx = "androidx.sqlite:sqlite-ktx:2.3.0-alpha05"
sqlite-android = "com.github.requery:sqlite-android:3.36.0" sqlite-android = "com.github.requery:sqlite-android:3.36.0"
preferencektx = "androidx.preference:preference-ktx:1.2.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-core = { module = "info.android15.nucleus:nucleus", version.ref = "nucleus_version" }
nucleus-supportv7 = { module = "info.android15.nucleus:nucleus-support-v7", version.ref = "nucleus_version" } nucleus-supportv7 = { module = "info.android15.nucleus:nucleus-support-v7", version.ref = "nucleus_version" }