KMP DataStorePreferences
This commit is contained in:
1 parent
e017efe35f
commit
9a43c36758
19 files changed
+251
-84
No files matched your search
@@ -166,6 +166,7 @@ baselineProfile {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.data)
|
||||
implementation(projects.core.i18n)
|
||||
|
||||
// https://developer.android.com/jetpack/androidx/releases/activity
|
||||
@@ -254,8 +255,6 @@ kotlin {
|
||||
"coil3.annotation.ExperimentalCoilApi",
|
||||
"androidx.paging.ExperimentalPagingApi",
|
||||
"kotlinx.serialization.ExperimentalSerializationApi",
|
||||
"splitties.experimental.ExperimentalSplittiesApi",
|
||||
"splitties.preferences.DataStorePreferencesPreview",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ class EhApplication :
|
||||
launchIO { dataStateFlow.value }
|
||||
launchIO { OSUtils.totalMemory }
|
||||
launch {
|
||||
if (DownloadManager.labelList.isNotEmpty() && Settings.downloadFilterMode.key !in Settings.prefs) {
|
||||
if (DownloadManager.labelList.isNotEmpty() && Settings.downloadFilterMode !in Settings.snapshot()) {
|
||||
Settings.downloadFilterMode.value = DownloadsFilterMode.CUSTOM.flag
|
||||
}
|
||||
DownloadManager.readMetadataFromLocal()
|
||||
|
||||
@@ -9,6 +9,9 @@ import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import com.ehviewer.core.preferences.DataStorePreferences
|
||||
import com.ehviewer.core.preferences.PrefDelegate
|
||||
import com.ehviewer.core.preferences.edit
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import com.hippo.ehviewer.client.data.FavListUrlBuilder
|
||||
import com.hippo.ehviewer.download.DownloadsFilterMode
|
||||
@@ -25,47 +28,12 @@ import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.flatMapMerge
|
||||
import kotlinx.coroutines.flow.map
|
||||
import splitties.preferences.BoolPref
|
||||
import splitties.preferences.DataStorePreferences
|
||||
import splitties.preferences.FloatPref
|
||||
import splitties.preferences.IntPref
|
||||
import splitties.preferences.LongPref
|
||||
import splitties.preferences.PrefDelegate
|
||||
import splitties.preferences.StringOrNullPref
|
||||
import splitties.preferences.StringPref
|
||||
import splitties.preferences.StringSetOrNullPref
|
||||
import splitties.preferences.StringSetPref
|
||||
import splitties.preferences.edit
|
||||
|
||||
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
|
||||
fun <T> PrefDelegate<T>.getValue(): T = when (this) {
|
||||
is BoolPref -> value
|
||||
is IntPref -> value
|
||||
is FloatPref -> value
|
||||
is LongPref -> value
|
||||
is StringPref -> value
|
||||
is StringOrNullPref -> value
|
||||
is StringSetPref -> value
|
||||
is StringSetOrNullPref -> value
|
||||
} as T
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> PrefDelegate<T>.setValue(newValue: T) = when (this) {
|
||||
is BoolPref -> value = newValue as Boolean
|
||||
is IntPref -> value = newValue as Int
|
||||
is FloatPref -> value = newValue as Float
|
||||
is LongPref -> value = newValue as Long
|
||||
is StringPref -> value = newValue as String
|
||||
is StringOrNullPref -> value = newValue as? String
|
||||
is StringSetPref -> value = newValue as Set<String?>
|
||||
is StringSetOrNullPref -> value = newValue as? Set<String?>
|
||||
}
|
||||
|
||||
@Stable
|
||||
@Composable
|
||||
inline fun <R, T> PrefDelegate<T>.collectAsState(crossinline transform: @DisallowComposableCalls (T) -> R): State<R> {
|
||||
val flow = remember { valueFlow().map { transform(it) } }
|
||||
val init = getValue()
|
||||
val init = value
|
||||
return flow.collectAsState(transform(init))
|
||||
}
|
||||
|
||||
@@ -73,32 +41,28 @@ inline fun <R, T> PrefDelegate<T>.collectAsState(crossinline transform: @Disallo
|
||||
@Composable
|
||||
fun <T> PrefDelegate<T>.collectAsState(): State<T> {
|
||||
val flow = remember { valueFlow() }
|
||||
val init = getValue()
|
||||
val init = value
|
||||
return flow.collectAsState(init)
|
||||
}
|
||||
|
||||
@Stable
|
||||
@Composable
|
||||
fun <T> PrefDelegate<T>.asMutableState(): MutableState<T> {
|
||||
val flow = remember { valueFlow() }
|
||||
val init = getValue()
|
||||
val readOnly = flow.collectAsState(init)
|
||||
val readOnly = collectAsState()
|
||||
return remember {
|
||||
object : MutableState<T> {
|
||||
override var value: T
|
||||
get() = readOnly.value
|
||||
set(value) {
|
||||
setValue(value)
|
||||
this@asMutableState.value = value
|
||||
}
|
||||
override fun component1() = readOnly.value
|
||||
override fun component2() = ::setValue
|
||||
override fun component2() = this@asMutableState::value::set
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Settings : DataStorePreferences(null) {
|
||||
private const val KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations"
|
||||
|
||||
@Suppress("ktlint:standard:backing-property-naming")
|
||||
private val _favFlow = MutableSharedFlow<Unit>()
|
||||
val favChangesFlow = _favFlow.debounce(1000)
|
||||
@@ -121,7 +85,7 @@ 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(KEY_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() }
|
||||
@@ -167,15 +131,15 @@ object Settings : DataStorePreferences(null) {
|
||||
val needSignIn = boolPref("need_sign_in", true)
|
||||
val gridView = boolPref("grid_view", false)
|
||||
val qSSaveProgress = boolPref("qs_save_progress", true)
|
||||
val displayName = stringOrNullPref("display_name", null)
|
||||
val avatar = stringOrNullPref("avatar", null)
|
||||
val recentDownloadLabel = stringOrNullPref("recent_download_label", null)
|
||||
val displayName = stringOrNullPref("display_name")
|
||||
val avatar = stringOrNullPref("avatar")
|
||||
val recentDownloadLabel = stringOrNullPref("recent_download_label")
|
||||
|
||||
var downloadScheme by stringOrNullPref("image_scheme", null)
|
||||
var downloadAuthority by stringOrNullPref("image_authority", null)
|
||||
var downloadPath by stringOrNullPref("image_path", null)
|
||||
var downloadQuery by stringOrNullPref("image_query", null)
|
||||
var downloadFragment by stringOrNullPref("image_fragment", null)
|
||||
var downloadScheme by stringOrNullPref("image_scheme")
|
||||
var downloadAuthority by stringOrNullPref("image_authority")
|
||||
var downloadPath by stringOrNullPref("image_path")
|
||||
var downloadQuery by stringOrNullPref("image_query")
|
||||
var downloadFragment by stringOrNullPref("image_fragment")
|
||||
var archivePasswds by stringSetPref("archive_passwds")
|
||||
var appLinkVerifyTip by boolPref("app_link_verify_tip", false)
|
||||
var hasDefaultDownloadLabel by boolPref("has_default_download_label", false)
|
||||
@@ -185,7 +149,7 @@ object Settings : DataStorePreferences(null) {
|
||||
var requestNewsTime by intPref("request_news_time", 0).observed { updateWhenRequestNewsChanges() }
|
||||
var lastDawnDays by intPref("last_dawn_days", 0)
|
||||
var recentToplist by stringPref("recent_toplist", "11")
|
||||
var defaultDownloadLabel by stringOrNullPref("default_download_label", null)
|
||||
var defaultDownloadLabel by stringOrNullPref("default_download_label")
|
||||
var lastUpdateTime by longPref("last_update_time", BuildConfig.COMMIT_TIME)
|
||||
|
||||
// Reader
|
||||
@@ -223,13 +187,13 @@ object Settings : DataStorePreferences(null) {
|
||||
val stripExtraneousAds = boolPref("strip_extraneous_ads", false)
|
||||
|
||||
init {
|
||||
if ("CN" == Locale.getDefault().country) {
|
||||
edit {
|
||||
if (KEY_SHOW_TAG_TRANSLATIONS !in prefs) showTagTranslations.value = true
|
||||
edit { pref ->
|
||||
if ("CN" == Locale.getDefault().country) {
|
||||
if (showTagTranslations !in pref) pref[showTagTranslations] = true
|
||||
}
|
||||
if (pref[downloadFilterMode] == DownloadsFilterMode.ARTIST.flag && pref[recentDownloadLabel] == null) {
|
||||
pref[recentDownloadLabel] = ""
|
||||
}
|
||||
}
|
||||
if (downloadFilterMode.value == DownloadsFilterMode.ARTIST.flag && recentDownloadLabel.value == null) {
|
||||
recentDownloadLabel.value = ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +211,7 @@ object Settings : DataStorePreferences(null) {
|
||||
override fun getValue(thisRef: Any?, prop: KProperty<*>?): IntArray = _value.map { it.value }.toIntArray()
|
||||
override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: IntArray) {
|
||||
check(value.size == count)
|
||||
edit { value.zip(_value) { v, d -> d.value = v } }
|
||||
edit { pref -> value.zip(_value) { v, d -> pref[d] = v } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +223,7 @@ object Settings : DataStorePreferences(null) {
|
||||
override fun getValue(thisRef: Any?, prop: KProperty<*>?): Array<String> = _value.map { it.value }.toTypedArray()
|
||||
override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: Array<String>) {
|
||||
check(value.size == count)
|
||||
edit { value.zip(_value) { v, d -> d.value = v } }
|
||||
edit { pref -> value.zip(_value) { v, d -> pref[d] = v } }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.hippo.ehviewer
|
||||
|
||||
import android.app.UiModeManager
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.ehviewer.core.preferences.PrefDelegate
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
import com.hippo.ehviewer.dailycheck.updateDailyCheckWork
|
||||
@@ -16,7 +17,6 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import splitties.init.appCtx
|
||||
import splitties.preferences.PrefDelegate
|
||||
import splitties.systemservices.uiModeManager
|
||||
|
||||
private val collectScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
|
||||
@@ -26,6 +26,7 @@ import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.core.util.size
|
||||
import arrow.fx.coroutines.parMapNotNull
|
||||
import com.ehviewer.core.preferences.edit
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
@@ -64,7 +65,6 @@ import logcat.LogPriority
|
||||
import okio.Path
|
||||
import okio.Path.Companion.toOkioPath
|
||||
import okio.Path.Companion.toPath
|
||||
import splitties.preferences.edit
|
||||
|
||||
object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
override val coroutineContext = Dispatchers.IO + Job()
|
||||
|
||||
@@ -53,7 +53,7 @@ suspend inline fun <T> useArchivePageLoader(
|
||||
{ _, _ -> closeArchive() },
|
||||
)
|
||||
check(size > 0) { "Archive have no content!" }
|
||||
if (needPassword() && archivePasswds.filterNotNull().none(::providePassword)) {
|
||||
if (needPassword() && archivePasswds.none(::providePassword)) {
|
||||
archivePasswds += passwdProvider(::providePassword)
|
||||
}
|
||||
val loader = install(
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.net.http.UrlRequest
|
||||
import android.net.http.UrlResponseInfo
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresExtension
|
||||
import com.ehviewer.core.preferences.edit
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.util.restartApplication
|
||||
import io.ktor.client.engine.HttpClientEngineBase
|
||||
@@ -40,7 +41,6 @@ import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.consumeEach
|
||||
import kotlinx.coroutines.job
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import splitties.preferences.edit
|
||||
|
||||
class CronetEngine(override val config: CronetConfig) : HttpClientEngineBase("Cronet") {
|
||||
// Limit thread to 1 since we are async & non-blocking
|
||||
@@ -108,7 +108,7 @@ class CronetEngine(override val config: CronetConfig) : HttpClientEngineBase("Cr
|
||||
val message = error.message.orEmpty()
|
||||
if ("ERR_FILE_NOT_FOUND" in message || "ERR_PROXY_CONNECTION_FAILED" in message) {
|
||||
Settings.edit(true) {
|
||||
enableCronet.value = false
|
||||
it[enableCronet] = false
|
||||
}
|
||||
with(config.context) { restartApplication() }
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisallowComposableCalls
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.datastore.dataStoreFile
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.byteArrayPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.emptyPreferences
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -22,7 +22,7 @@ import kotlinx.serialization.serializer
|
||||
import splitties.init.appCtx
|
||||
|
||||
val dataStoreScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
val dataStore = PreferenceDataStoreFactory.create { appCtx.dataStoreFile("Remembered.preferences_pb") }
|
||||
val dataStore = PreferenceDataStoreFactory.create { appCtx.preferencesDataStoreFile("Remembered") }
|
||||
val dataStateFlow = dataStore.data.stateIn(dataStoreScope, SharingStarted.Eagerly, emptyPreferences())
|
||||
|
||||
// Find out how make this work with any generic `Serializable` type
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.util
|
||||
|
||||
import android.os.Looper
|
||||
import com.ehviewer.core.mainthread.checkNotMainThread
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import java.io.File
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -51,11 +51,4 @@ object OSUtils {
|
||||
}
|
||||
}
|
||||
|
||||
val isMainThread: Boolean
|
||||
get() = Looper.getMainLooper().thread === Thread.currentThread()
|
||||
|
||||
fun assertNotMainThread() {
|
||||
check(!isMainThread) { "Cannot access database on the main thread since" + " it may potentially lock the UI for a long period of time." }
|
||||
}
|
||||
|
||||
fun <T> runAssertingNotMainThread(block: suspend CoroutineScope.() -> T) = assertNotMainThread().run { runBlocking(block = block) }
|
||||
fun <T> runAssertingNotMainThread(block: suspend CoroutineScope.() -> T) = checkNotMainThread().run { runBlocking(block = block) }
|
||||
@@ -0,0 +1,21 @@
|
||||
plugins {
|
||||
alias(libs.plugins.ehviewer.multiplatform.library)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api(project.dependencies.platform(libs.okio.bom))
|
||||
api(libs.androidx.datastore)
|
||||
api(libs.kotlinx.coroutines.core)
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
dependencies {
|
||||
api(libs.kotlinx.coroutines.android)
|
||||
api(libs.splitties.appctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.ehviewer.core.mainthread
|
||||
|
||||
import android.os.Looper
|
||||
|
||||
internal actual val isMainThread: Boolean
|
||||
get() = Looper.getMainLooper().isCurrentThread
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.ehviewer.core.preferences
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.SharedPreferencesMigration
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import com.ehviewer.core.mainthread.checkNotMainThread
|
||||
import splitties.init.appCtx
|
||||
|
||||
internal actual fun getDateStore(name: String?): DataStore<Preferences> {
|
||||
checkNotMainThread()
|
||||
val context = appCtx
|
||||
val actualName = name ?: "${context.packageName}_preferences"
|
||||
return PreferenceDataStoreFactory.create(
|
||||
migrations = listOf(SharedPreferencesMigration(context = context, sharedPreferencesName = actualName)),
|
||||
) {
|
||||
context.preferencesDataStoreFile(actualName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.ehviewer.core.mainthread
|
||||
|
||||
internal expect val isMainThread: Boolean
|
||||
|
||||
fun checkMainThread() {
|
||||
check(isMainThread) { "Must be called from the main thread" }
|
||||
}
|
||||
|
||||
fun checkNotMainThread() {
|
||||
check(!isMainThread) { "Must not be called from the main thread" }
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.ehviewer.core.preferences
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.byteArrayPreferencesKey
|
||||
import androidx.datastore.preferences.core.doublePreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.floatPreferencesKey
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.core.longPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
internal expect fun getDateStore(name: String?): DataStore<Preferences>
|
||||
|
||||
abstract class DataStorePreferences(private val dataStore: DataStore<Preferences>) {
|
||||
constructor(name: String?) : this(getDateStore(name))
|
||||
|
||||
val data: Flow<Snapshot>
|
||||
get() = dataStore.data.map { Snapshot(it) }
|
||||
|
||||
suspend fun snapshot(): Snapshot = data.first()
|
||||
|
||||
suspend fun withMutableSnapshot(block: (MutableSnapshot) -> Unit) {
|
||||
dataStore.edit {
|
||||
block(MutableSnapshot(it))
|
||||
}
|
||||
}
|
||||
|
||||
protected fun boolPref(
|
||||
key: String,
|
||||
defaultValue: Boolean,
|
||||
) = PrefDelegate(this, booleanPreferencesKey(name = key), defaultValue = defaultValue)
|
||||
|
||||
protected fun intPref(
|
||||
key: String,
|
||||
defaultValue: Int,
|
||||
) = PrefDelegate(this, intPreferencesKey(name = key), defaultValue = defaultValue)
|
||||
|
||||
protected fun floatPref(
|
||||
key: String,
|
||||
defaultValue: Float,
|
||||
) = PrefDelegate(this, floatPreferencesKey(name = key), defaultValue = defaultValue)
|
||||
|
||||
protected fun doublePref(
|
||||
key: String,
|
||||
defaultValue: Double,
|
||||
) = PrefDelegate(this, doublePreferencesKey(name = key), defaultValue = defaultValue)
|
||||
|
||||
protected fun longPref(
|
||||
key: String,
|
||||
defaultValue: Long,
|
||||
) = PrefDelegate(this, longPreferencesKey(name = key), defaultValue = defaultValue)
|
||||
|
||||
protected fun stringPref(
|
||||
key: String,
|
||||
defaultValue: String,
|
||||
) = PrefDelegate(this, stringPreferencesKey(name = key), defaultValue = defaultValue)
|
||||
|
||||
protected fun stringOrNullPref(
|
||||
key: String,
|
||||
) = PrefDelegate(this, stringPreferencesKey(name = key), null)
|
||||
|
||||
protected fun stringSetPref(
|
||||
key: String,
|
||||
defaultValue: Set<String> = emptySet(),
|
||||
) = PrefDelegate(this, stringSetPreferencesKey(name = key), defaultValue = defaultValue)
|
||||
|
||||
protected fun stringSetOrNullPref(
|
||||
key: String,
|
||||
) = PrefDelegate(this, stringSetPreferencesKey(name = key), null)
|
||||
|
||||
protected fun byteArrayPref(
|
||||
key: String,
|
||||
defaultValue: ByteArray,
|
||||
) = PrefDelegate(this, byteArrayPreferencesKey(name = key), defaultValue = defaultValue)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.ehviewer.core.preferences
|
||||
|
||||
import com.ehviewer.core.mainthread.checkNotMainThread
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
fun <P : DataStorePreferences> P.edit(blocking: Boolean = false, block: P.(MutableSnapshot) -> Unit) {
|
||||
if (blocking) {
|
||||
checkNotMainThread()
|
||||
runBlocking {
|
||||
withMutableSnapshot { block(it) }
|
||||
}
|
||||
} else {
|
||||
scope.launch {
|
||||
withMutableSnapshot { block(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ehviewer.core.preferences
|
||||
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
class PrefDelegate<T> internal constructor(
|
||||
private val preferences: DataStorePreferences,
|
||||
val key: Preferences.Key<T & Any>,
|
||||
val defaultValue: T,
|
||||
) {
|
||||
fun changesFlow(): Flow<Unit> = valueFlow().drop(1).map {}
|
||||
|
||||
fun valueFlow(): Flow<T> = preferences.data.map { it[this] }.distinctUntilChanged()
|
||||
|
||||
var value: T
|
||||
get() = runBlocking { preferences.snapshot() }[this]
|
||||
set(value) = preferences.edit { it[this@PrefDelegate] = value }
|
||||
|
||||
inline operator fun getValue(thisRef: Any?, prop: KProperty<*>?): T = value
|
||||
inline operator fun setValue(thisRef: Any?, prop: KProperty<*>?, value: T) {
|
||||
this.value = value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ehviewer.core.preferences
|
||||
|
||||
import androidx.datastore.preferences.core.MutablePreferences
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
|
||||
open class Snapshot internal constructor(private val delegate: Preferences) {
|
||||
operator fun <T> contains(pref: PrefDelegate<T>): Boolean = pref.key in delegate
|
||||
operator fun <T> get(pref: PrefDelegate<T>): T = delegate[pref.key] ?: pref.defaultValue
|
||||
}
|
||||
|
||||
class MutableSnapshot internal constructor(private val delegate: MutablePreferences) : Snapshot(delegate) {
|
||||
operator fun <T> set(pref: PrefDelegate<T>, value: T) {
|
||||
if (value == null) {
|
||||
delegate.remove(pref.key)
|
||||
} else {
|
||||
delegate[pref.key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ androidx-biometric = { module = "androidx.biometric:biometric", version.ref = "a
|
||||
androidx-browser = { module = "androidx.browser:browser", version.ref = "androidx-browser" }
|
||||
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
|
||||
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-core-splashscreen" }
|
||||
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "androidx-datastore" }
|
||||
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
|
||||
androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "androidx-lifecycle" }
|
||||
androidx-lifecycle-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
|
||||
|
||||
@@ -91,6 +91,7 @@ desugar = "com.android.tools:desugar_jdk_libs:2.1.5"
|
||||
|
||||
diff = "io.github.petertrr:kotlin-multiplatform-diff:1.1.0"
|
||||
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
|
||||
|
||||
@@ -116,7 +117,6 @@ serialization-xml-io = { module = "io.github.pdvrieze.xmlutil:serialization-io",
|
||||
|
||||
splitties-appctx = { module = "com.louiscad.splitties:splitties-appctx", version.ref = "splitties" }
|
||||
splitties-arch-room = { module = "com.louiscad.splitties:splitties-arch-room", version.ref = "splitties" }
|
||||
splitties-preferences = { module = "com.louiscad.splitties:splitties-preferences", version.ref = "splitties" }
|
||||
splitties-systemservices = { module = "com.louiscad.splitties:splitties-systemservices", version.ref = "splitties" }
|
||||
|
||||
telephoto-zoomable = { module = "me.saket.telephoto:zoomable", version.ref = "telephoto" }
|
||||
@@ -135,7 +135,7 @@ arrow = ["arrow-fx-coroutines", "arrow-functions", "arrow-resilience"]
|
||||
coil = ["coil-compose", "coil-gif", "coil-network"]
|
||||
compose = ["compose-foundation", "compose-material-icons-extended", "compose-material3", "compose-material3-adaptive", "compose-ui-util"]
|
||||
kotlinx-serialization = ["serialization-cbor", "serialization-json", "serialization-json-io", "serialization-xml", "serialization-xml-io"]
|
||||
splitties = ["splitties-appctx", "splitties-arch-room", "splitties-preferences", "splitties-systemservices"]
|
||||
splitties = ["splitties-appctx", "splitties-arch-room", "splitties-systemservices"]
|
||||
|
||||
[plugins]
|
||||
aboutlibrariesPlugin = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" }
|
||||
|
||||
@@ -44,4 +44,5 @@ enableFeaturePreview("STABLE_CONFIGURATION_CACHE")
|
||||
rootProject.name = "EhViewer"
|
||||
include(":app")
|
||||
include(":benchmark")
|
||||
include(":core:data")
|
||||
include(":core:i18n")
|
||||
Reference in new issue
Block a user