diff --git a/AndroidCompat/build.gradle.kts b/AndroidCompat/build.gradle.kts index 20acf2f..fd782bc 100644 --- a/AndroidCompat/build.gradle.kts +++ b/AndroidCompat/build.gradle.kts @@ -1,6 +1,7 @@ plugins { application + kotlin("plugin.serialization") } @@ -46,6 +47,17 @@ dependencies { implementation("org.mozilla:rhino-runtime:1.7.13") // 'org.mozilla:rhino-engine' provides the same interface as 'javax.script' a.k.a Nashorn implementation("org.mozilla:rhino-engine:1.7.13") + + // Kotlin wrapper around Java Preferences, makes certain things easier + val multiplatformSettingsVersion = "0.7.7" + implementation("com.russhwolf:multiplatform-settings-jvm:$multiplatformSettingsVersion") + implementation("com.russhwolf:multiplatform-settings-serialization-jvm:$multiplatformSettingsVersion") +} + +tasks { + withType { + kotlinOptions.freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn") + } } //def fatJarTask = tasks.getByPath(':AndroidCompat:JVMPatch:fatJar') diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java index cdbb3ef..617a6f5 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java @@ -38,7 +38,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import xyz.nulldev.androidcompat.info.ApplicationInfoImpl; import xyz.nulldev.androidcompat.io.AndroidFiles; -import xyz.nulldev.androidcompat.io.sharedprefs.JsonSharedPreferences; +import xyz.nulldev.androidcompat.io.sharedprefs.JavaSharedPreferences; import xyz.nulldev.androidcompat.service.ServiceSupport; import xyz.nulldev.androidcompat.util.KodeinGlobalHelper; @@ -165,23 +165,22 @@ public class CustomContext extends Context implements DIAware { /** Fake shared prefs! **/ private Map prefs = new HashMap<>(); //Cache - private File sharedPrefsFileFromString(String s) { - return new File(androidFiles.getPrefsDir(), s + ".json"); - } - @Override public synchronized SharedPreferences getSharedPreferences(String s, int i) { SharedPreferences preferences = prefs.get(s); //Create new shared preferences if one does not exist if(preferences == null) { - preferences = getSharedPreferences(sharedPrefsFileFromString(s), i); + preferences = new JavaSharedPreferences(s); prefs.put(s, preferences); } return preferences; } - public SharedPreferences getSharedPreferences(File file, int mode) { - return new JsonSharedPreferences(file); + @Override + public SharedPreferences getSharedPreferences(@NotNull File file, int mode) { + String path = file.getAbsolutePath().replace('\\', '/'); + int firstSlash = path.indexOf("/"); + return new JavaSharedPreferences(path.substring(firstSlash)); } @Override @@ -191,8 +190,8 @@ public class CustomContext extends Context implements DIAware { @Override public boolean deleteSharedPreferences(String name) { - prefs.remove(name); - return sharedPrefsFileFromString(name).delete(); + JavaSharedPreferences item = (JavaSharedPreferences) prefs.remove(name); + return item.deleteAll(); } @Override diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt index ea55b49..dafa70e 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt @@ -26,8 +26,6 @@ class AndroidFiles(val configManager: ConfigManager = GlobalConfigManager) { val downloadCacheDir: File get() = registerFile(filesConfig.downloadCacheDir) val databasesDir: File get() = registerFile(filesConfig.databasesDir) - val prefsDir: File get() = registerFile(filesConfig.prefsDir) - val packagesDir: File get() = registerFile(filesConfig.packageDir) fun registerFile(file: String): File { diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt new file mode 100644 index 0000000..1c7f3ea --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt @@ -0,0 +1,168 @@ +package xyz.nulldev.androidcompat.io.sharedprefs + +import android.content.SharedPreferences +import com.russhwolf.settings.ExperimentalSettingsApi +import com.russhwolf.settings.ExperimentalSettingsImplementation +import com.russhwolf.settings.JvmPreferencesSettings +import com.russhwolf.settings.serialization.decodeValue +import com.russhwolf.settings.serialization.encodeValue +import com.russhwolf.settings.set +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerializationException +import kotlinx.serialization.builtins.SetSerializer +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer +import java.util.prefs.PreferenceChangeListener +import java.util.prefs.Preferences + +@OptIn(ExperimentalSettingsImplementation::class, ExperimentalSerializationApi::class, ExperimentalSettingsApi::class) +class JavaSharedPreferences(key: String) : SharedPreferences { + private val javaPreferences = Preferences.userRoot().node("suwayomi/tachidesk/$key") + private val preferences = JvmPreferencesSettings(javaPreferences) + private val listeners = mutableMapOf() + + // TODO: 2021-05-29 Need to find a way to get this working with all pref types + override fun getAll(): MutableMap { + return preferences.keys.associateWith { preferences.getStringOrNull(it) }.toMutableMap() + } + + override fun getString(key: String, defValue: String?): String? { + return if (defValue != null) { + preferences.getString(key, defValue) + } else { + preferences.getStringOrNull(key) + } + } + + override fun getStringSet(key: String, defValues: MutableSet?): MutableSet? { + try { + return if (defValues != null) { + preferences.decodeValue(SetSerializer(String.serializer()).nullable, key, defValues) + } else { + preferences.decodeValue(SetSerializer(String.serializer()).nullable, key, null) + }?.toMutableSet() + } catch (e: SerializationException) { + throw ClassCastException("$key was not a StringSet") + } + } + + override fun getInt(key: String, defValue: Int): Int { + return preferences.getInt(key, defValue) + } + + override fun getLong(key: String, defValue: Long): Long { + return preferences.getLong(key, defValue) + } + + override fun getFloat(key: String, defValue: Float): Float { + return preferences.getFloat(key, defValue) + } + + override fun getBoolean(key: String, defValue: Boolean): Boolean { + return preferences.getBoolean(key, defValue) + } + + override fun contains(key: String): Boolean { + return key in preferences.keys + } + + override fun edit(): SharedPreferences.Editor { + return Editor(preferences) + } + + class Editor(private val preferences: JvmPreferencesSettings) : SharedPreferences.Editor { + val itemsToAdd = mutableMapOf() + + override fun putString(key: String, value: String?): SharedPreferences.Editor { + if (value != null) { + itemsToAdd[key] = value + } else { + remove(key) + } + return this + } + + override fun putStringSet( + key: String, + values: MutableSet? + ): SharedPreferences.Editor { + if (values != null) { + itemsToAdd[key] = values + } else { + remove(key) + } + return this + } + + override fun putInt(key: String, value: Int): SharedPreferences.Editor { + itemsToAdd[key] = value + return this + } + + override fun putLong(key: String, value: Long): SharedPreferences.Editor { + itemsToAdd[key] = value + return this + } + + override fun putFloat(key: String, value: Float): SharedPreferences.Editor { + itemsToAdd[key] = value + return this + } + + override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor { + itemsToAdd[key] = value + return this + } + + override fun remove(key: String): SharedPreferences.Editor { + itemsToAdd.remove(key) + return this + } + + override fun clear(): SharedPreferences.Editor { + itemsToAdd.clear() + return this + } + + override fun commit(): Boolean { + addToPreferences() + return true + } + + override fun apply() { + addToPreferences() + } + + private fun addToPreferences() { + itemsToAdd.forEach { (key, value) -> + @Suppress("UNCHECKED_CAST") + when (value) { + is Set<*> -> preferences.encodeValue(SetSerializer(String.serializer()), key, value as Set) + else -> { + preferences[key] = value + } + } + } + } + } + + override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) { + val javaListener = PreferenceChangeListener { + listener.onSharedPreferenceChanged(this, it.key) + } + listeners[listener] = javaListener + javaPreferences.addPreferenceChangeListener(javaListener) + } + + override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) { + val registeredListener = listeners.remove(listener) + if (registeredListener != null) { + javaPreferences.removePreferenceChangeListener(registeredListener) + } + } + + fun deleteAll(): Boolean { + javaPreferences.removeNode() + return true + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index f8c4a24..532c923 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,6 +2,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("jvm") version "1.4.32" + kotlin("plugin.serialization") version "1.4.32" apply false } allprojects { @@ -50,6 +51,10 @@ configure(projects) { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion") + val kotlinSerializationVersion = "1.2.1" + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion") + // Dependency Injection implementation("org.kodein.di:kodein-di-conf-jvm:7.5.0")