Refactor settings observation

This commit is contained in:
FooIbar committed 2025-08-13 00:42:20 +08:00
1 parent 9a43c36758
commit a1a204d0d5
7 files changed
+60 -59

No files matched your search

@@ -71,9 +71,9 @@ object Settings : DataStorePreferences(null) {
var favCloudCount by intPref("fav_cloud", 0).emitTo(_favFlow)
// Eh
val gallerySite = intPref("gallery_site_2", 0).observed { updateWhenGallerySiteChanges() }
val gallerySite = intPref("gallery_site_2", 0).observed(::updateWhenGallerySiteChanges)
val defaultFavSlot = intPref("default_favorite_slot", -2)
val theme = intPref("theme_2", -1).observed { updateWhenThemeChanges() }
val theme = intPref("theme_2", -1).observed(::updateWhenThemeChanges)
val blackDarkTheme = boolPref("black_dark_theme", false)
val harmonizeCategoryColor = boolPref("harmonize_category_color", true)
val launchPage = intPref("launch_page_2", 0)
@@ -85,14 +85,14 @@ object Settings : DataStorePreferences(null) {
val showVoteStatus = boolPref("show_vote_status", false)
val showComments = boolPref("show_gallery_comments", true)
val commentThreshold = intPref("comment_threshold", -100)
val showTagTranslations = boolPref("show_tag_translations", false).observed { updateWhenTagTranslationChanges() }
val showTagTranslations = boolPref("show_tag_translations", false).observed(::updateWhenTagTranslationChanges)
val meteredNetworkWarning = boolPref("cellular_network_warning", false)
val showJpnTitle = boolPref("show_jpn_title", false)
val requestNews = boolPref("request_news", false).observed { updateWhenRequestNewsChanges() }
val hideHvEvents = boolPref("hide_hv_events", false)
// Download
val mediaScan = boolPref("media_scan", false).observed { updateWhenKeepMediaStatusChanges() }
val mediaScan = boolPref("media_scan", false).observed(::updateWhenKeepMediaStatusChanges)
val multiThreadDownload = intPref("download_thread_2", 3)
val downloadDelay = intPref("download_delay_3", 1000)
val connTimeout = intPref("conn_timeout", 10)
@@ -198,32 +198,28 @@ object Settings : DataStorePreferences(null) {
}
interface Delegate<R> {
val flowGetter: () -> Flow<Unit>
fun changesFlow(): Flow<Unit>
operator fun getValue(thisRef: Any?, prop: KProperty<*>?): R
operator fun setValue(thisRef: Any?, prop: KProperty<*>?, value: R)
}
private fun intArrayPref(key: String, count: Int) = object : Delegate<IntArray> {
override val flowGetter: () -> Flow<Unit> = { _value.asFlow().flatMapMerge { it.changesFlow() }.conflate() }
@Suppress("ktlint:standard:backing-property-naming")
private var _value = (0 until count).map { intPref("${key}_$it", 0) }.toTypedArray()
override fun getValue(thisRef: Any?, prop: KProperty<*>?): IntArray = _value.map { it.value }.toIntArray()
private val delegates = Array(count) { intPref("${key}_$it", 0) }
override fun changesFlow(): Flow<Unit> = delegates.asFlow().flatMapMerge { it.changesFlow() }.conflate()
override fun getValue(thisRef: Any?, prop: KProperty<*>?) = IntArray(delegates.size) { delegates[it].value }
override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: IntArray) {
check(value.size == count)
edit { pref -> value.zip(_value) { v, d -> pref[d] = v } }
edit { pref -> value.zip(delegates) { v, d -> pref[d] = v } }
}
}
private fun stringArrayPref(key: String, count: Int, defMetaValue: String) = object : Delegate<Array<String>> {
override val flowGetter: () -> Flow<Unit> = { _value.asFlow().flatMapMerge { it.changesFlow() }.conflate() }
@Suppress("ktlint:standard:backing-property-naming")
private var _value = (0 until count).map { stringPref("${key}_$it", "$defMetaValue $it") }.toTypedArray()
override fun getValue(thisRef: Any?, prop: KProperty<*>?): Array<String> = _value.map { it.value }.toTypedArray()
private val delegates = Array(count) { stringPref("${key}_$it", "$defMetaValue $it") }
override fun changesFlow(): Flow<Unit> = delegates.asFlow().flatMapMerge { it.changesFlow() }.conflate()
override fun getValue(thisRef: Any?, prop: KProperty<*>?) = Array(delegates.size) { delegates[it].value }
override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: Array<String>) {
check(value.size == count)
edit { pref -> value.zip(_value) { v, d -> pref[d] = v } }
edit { pref -> value.zip(delegates) { v, d -> pref[d] = v } }
}
}
}
@@ -2,68 +2,73 @@ package com.hippo.ehviewer
import android.app.UiModeManager
import androidx.appcompat.app.AppCompatDelegate
import arrow.core.Either.Companion.catch
import com.ehviewer.core.preferences.PrefDelegate
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.EhTagDatabase
import com.hippo.ehviewer.dailycheck.updateDailyCheckWork
import com.hippo.ehviewer.ui.keepNoMediaFileStatus
import com.hippo.ehviewer.util.isAtLeastS
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.launch
import splitties.init.appCtx
import splitties.systemservices.uiModeManager
private val collectScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
fun <T, R : PrefDelegate<T>> R.observed(func: (Unit) -> Unit) = apply { collectScope.launch { changesFlow().collect(func) } }
fun <T, R : Settings.Delegate<T>> R.observed(func: (Unit) -> Unit) = apply { collectScope.launch { flowGetter().collect(func) } }
fun <T, R : Settings.Delegate<T>> R.emitTo(flow: MutableSharedFlow<Unit>) = apply { collectScope.launch { flowGetter().collect { flow.emit(Unit) } } }
fun <T, R : PrefDelegate<T>> R.emitTo(flow: MutableSharedFlow<Unit>) = apply { collectScope.launch { changesFlow().collect { flow.emit(Unit) } } }
private const val TAG = "SettingsCollector"
fun updateWhenKeepMediaStatusChanges() {
collectScope.launchIO {
runCatching {
keepNoMediaFileStatus()
private val collectScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
fun <T, R : PrefDelegate<T>> R.observed(func: suspend (T) -> Unit) = apply { collectScope.launch { valueFlow().drop(1).collectLatest(func) } }
fun <T, R : Settings.Delegate<T>> R.emitTo(flow: MutableSharedFlow<Unit>) = apply { collectScope.launch { flow.emitAll(changesFlow()) } }
fun <T, R : PrefDelegate<T>> R.emitTo(flow: MutableSharedFlow<Unit>) = apply { collectScope.launch { flow.emitAll(changesFlow()) } }
suspend fun updateWhenKeepMediaStatusChanges(mediaScan: Boolean) {
withIOContext {
catch {
keepNoMediaFileStatus(mediaScan = mediaScan)
}.onLeft {
logcat(TAG, it)
}
}
}
fun updateWhenThemeChanges() {
collectScope.launch {
delay(100) // Avoid recompose being cancelled
if (isAtLeastS) {
val mode = when (Settings.theme.value) {
AppCompatDelegate.MODE_NIGHT_NO -> UiModeManager.MODE_NIGHT_NO
AppCompatDelegate.MODE_NIGHT_YES -> UiModeManager.MODE_NIGHT_YES
else -> UiModeManager.MODE_NIGHT_AUTO
}
uiModeManager.setApplicationNightMode(mode)
suspend fun updateWhenThemeChanges(theme: Int) {
delay(100) // Avoid recompose being cancelled
if (isAtLeastS) {
val mode = when (theme) {
AppCompatDelegate.MODE_NIGHT_NO -> UiModeManager.MODE_NIGHT_NO
AppCompatDelegate.MODE_NIGHT_YES -> UiModeManager.MODE_NIGHT_YES
else -> UiModeManager.MODE_NIGHT_AUTO
}
AppCompatDelegate.setDefaultNightMode(Settings.theme.value)
uiModeManager.setApplicationNightMode(mode)
}
AppCompatDelegate.setDefaultNightMode(theme)
}
fun updateWhenRequestNewsChanges() {
updateDailyCheckWork(appCtx)
}
fun updateWhenGallerySiteChanges() {
suspend fun updateWhenGallerySiteChanges(gallerySite: Int) {
if (Settings.hasSignedIn.value) {
collectScope.launchIO {
runCatching {
EhEngine.getUConfig()
}.onFailure {
logcat(it)
}
catch {
EhEngine.getUConfig(gallerySite)
}.onLeft {
logcat(TAG, it)
}
}
}
fun updateWhenTagTranslationChanges() {
EhTagDatabase.launchUpdate()
fun updateWhenTagTranslationChanges(enabled: Boolean) {
if (enabled) {
EhTagDatabase.launchUpdate()
}
}
@@ -212,7 +212,8 @@ object EhEngine {
return ehRequest(url, EhUrl.URL_FORUMS).fetchUsingAsByteBuffer(ProfileParser::parse)
}
suspend fun getUConfig(url: String = EhUrl.uConfigUrl) {
suspend fun getUConfig(gallerySite: Int) {
val url = EhUrl.getUConfigUrl(gallerySite)
runSuspendCatching {
ehRequest(url).fetchUsingAsByteBuffer(UserConfigParser::parse)
}.onFailure { throwable ->
@@ -80,12 +80,11 @@ object EhUrl {
else -> ORIGIN_E
}
val uConfigUrl: String
get() = when (Settings.gallerySite.value) {
SITE_E -> URL_UCONFIG_E
SITE_EX -> URL_UCONFIG_EX
else -> URL_UCONFIG_E
}
fun getUConfigUrl(gallerySite: Int = Settings.gallerySite.value) = when (gallerySite) {
SITE_E -> URL_UCONFIG_E
SITE_EX -> URL_UCONFIG_EX
else -> URL_UCONFIG_E
}
val myTagsUrl: String
get() = when (Settings.gallerySite.value) {
@@ -100,10 +100,10 @@ private fun ensureNoMediaFile(downloadDir: Path) {
private val lck = Mutex()
suspend fun keepNoMediaFileStatus(downloadDir: Path = downloadLocation) {
suspend fun keepNoMediaFileStatus(downloadDir: Path = downloadLocation, mediaScan: Boolean = Settings.mediaScan.value) {
if (downloadDir.isDirectory) {
lck.withLock {
if (Settings.mediaScan.value) {
if (mediaScan) {
removeNoMediaFile(downloadDir)
} else {
ensureNoMediaFile(downloadDir)
@@ -36,7 +36,7 @@ fun postLogin() = GlobalScope.async(Dispatchers.IO) {
// Get cookies for image limits
launch {
catch {
EhEngine.getUConfig(EhUrl.URL_UCONFIG_E)
EhEngine.getUConfig(EhUrl.SITE_E)
EhCookieStore.flush()
}.onLeft {
logcat(it)
@@ -45,7 +45,7 @@ fun postLogin() = GlobalScope.async(Dispatchers.IO) {
// Sad panda check
catch {
EhEngine.getUConfig(EhUrl.URL_UCONFIG_EX)
EhEngine.getUConfig(EhUrl.SITE_EX)
EhCookieStore.flush()
Settings.gallerySite.value = EhUrl.SITE_EX
}.onLeft {
@@ -35,7 +35,7 @@ private const val APPLY_JS = "javascript:(function(){var apply = document.getEle
@Destination<RootGraph>
@Composable
fun AnimatedVisibilityScope.UConfigScreen(navigator: DestinationsNavigator) = Screen(navigator) {
val url = EhUrl.uConfigUrl
val url = EhUrl.getUConfigUrl()
val wvNavigator = rememberWebViewNavigator()
Scaffold(
topBar = {