mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-03 11:17:32 +01:00
chore(preferences): migration to multiplatform-settings and encryption support
This commit is contained in:
parent
ae99a7c2c2
commit
49706c603d
@ -28,12 +28,18 @@ kotlin {
|
||||
baseName = "core-preferences"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sourceSets {
|
||||
val androidMain by getting {
|
||||
dependencies {
|
||||
implementation(libs.multiplatform.settings)
|
||||
implementation(libs.androidx.security.crypto)
|
||||
}
|
||||
}
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
implementation(libs.koin.core)
|
||||
implementation(libs.androidx.datastore)
|
||||
implementation(libs.multiplatform.settings)
|
||||
}
|
||||
}
|
||||
val commonTest by getting {
|
||||
|
@ -1,9 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core_preferences
|
||||
|
||||
import android.content.Context
|
||||
|
||||
internal class AndroidKeyStoreFilePathProvider(
|
||||
private val context: Context,
|
||||
) {
|
||||
fun get(): String = context.filesDir.resolve(DefaultTemporaryKeyStore.FILE_NAME).absolutePath
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core_preferences
|
||||
|
||||
import org.koin.java.KoinJavaComponent.inject
|
||||
|
||||
|
||||
actual fun getKeyStoreFilePath(): String {
|
||||
val provider: AndroidKeyStoreFilePathProvider by inject(
|
||||
AndroidKeyStoreFilePathProvider::class.java
|
||||
)
|
||||
return provider.get()
|
||||
}
|
||||
|
@ -1,18 +1,51 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core_preferences.di
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.TemporaryKeyStore
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.AndroidKeyStoreFilePathProvider
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKeys
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.DefaultTemporaryKeyStore
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.TemporaryKeyStore
|
||||
import com.russhwolf.settings.Settings
|
||||
import com.russhwolf.settings.SharedPreferencesSettings
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
import org.koin.java.KoinJavaComponent.inject
|
||||
|
||||
actual val corePreferencesModule = module {
|
||||
singleOf(::AndroidKeyStoreFilePathProvider)
|
||||
singleOf<TemporaryKeyStore>(::DefaultTemporaryKeyStore)
|
||||
singleOf(::SharedPreferencesProvider)
|
||||
single<Settings> {
|
||||
val sharedPreferencesProvider: SharedPreferencesProvider by inject()
|
||||
SharedPreferencesSettings(
|
||||
sharedPreferencesProvider.sharedPreferences,
|
||||
false,
|
||||
)
|
||||
}
|
||||
single<TemporaryKeyStore> {
|
||||
DefaultTemporaryKeyStore(settings = get())
|
||||
}
|
||||
}
|
||||
|
||||
actual fun getTemporaryKeyStore(): TemporaryKeyStore {
|
||||
val res: TemporaryKeyStore by inject(TemporaryKeyStore::class.java)
|
||||
val res by inject<TemporaryKeyStore>(TemporaryKeyStore::class.java)
|
||||
return res
|
||||
}
|
||||
|
||||
private class SharedPreferencesProvider(
|
||||
context: Context,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
const val PREFERENCES_NAME = "secret_shared_prefs"
|
||||
}
|
||||
|
||||
private val masterKeyAlias: String = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
|
||||
|
||||
val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create(
|
||||
PREFERENCES_NAME,
|
||||
masterKeyAlias,
|
||||
context,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
}
|
@ -1,103 +1,42 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core_preferences
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
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.stringPreferencesKey
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import okio.Path.Companion.toPath
|
||||
import com.russhwolf.settings.Settings
|
||||
import com.russhwolf.settings.get
|
||||
import com.russhwolf.settings.set
|
||||
|
||||
internal expect fun getKeyStoreFilePath(): String
|
||||
internal class DefaultTemporaryKeyStore(
|
||||
private val settings: Settings,
|
||||
) : TemporaryKeyStore {
|
||||
|
||||
internal class DefaultTemporaryKeyStore : TemporaryKeyStore {
|
||||
|
||||
companion object {
|
||||
internal const val FILE_NAME = "main.preferences_pb"
|
||||
}
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Default)
|
||||
private val dataStore: DataStore<Preferences> = createDataStore()
|
||||
|
||||
private fun createDataStore(): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.createWithPath(
|
||||
corruptionHandler = null,
|
||||
migrations = emptyList(),
|
||||
scope = scope,
|
||||
produceFile = { getKeyStoreFilePath().toPath() }
|
||||
)
|
||||
|
||||
override suspend fun containsKey(key: String): Boolean = withTimeoutOrNull(500) {
|
||||
dataStore.data.map {
|
||||
it.asMap().keys.any { k -> k.name == key }
|
||||
}.first()
|
||||
} ?: false
|
||||
override suspend fun containsKey(key: String): Boolean = settings.keys.contains(key)
|
||||
|
||||
override suspend fun save(key: String, value: Boolean) {
|
||||
dataStore.edit {
|
||||
it[booleanPreferencesKey(key)] = value
|
||||
}
|
||||
settings[key] = value
|
||||
}
|
||||
|
||||
override suspend fun get(key: String, default: Boolean) = withTimeoutOrNull(500) {
|
||||
dataStore.data.map {
|
||||
it[booleanPreferencesKey(key)]
|
||||
}.first()
|
||||
} ?: default
|
||||
override suspend fun get(key: String, default: Boolean): Boolean = settings[key, default]
|
||||
|
||||
override suspend fun save(key: String, value: String) {
|
||||
dataStore.edit {
|
||||
it[stringPreferencesKey(key)] = value
|
||||
}
|
||||
settings[key] = value
|
||||
}
|
||||
|
||||
override suspend fun get(key: String, default: String): String = withTimeoutOrNull(500) {
|
||||
dataStore.data.map {
|
||||
it[stringPreferencesKey(key)]
|
||||
}.first()
|
||||
} ?: default
|
||||
override suspend fun get(key: String, default: String): String = settings[key, default]
|
||||
|
||||
override suspend fun save(key: String, value: Int) {
|
||||
dataStore.edit {
|
||||
it[intPreferencesKey(key)] = value
|
||||
}
|
||||
settings[key] = value
|
||||
}
|
||||
|
||||
override suspend fun get(key: String, default: Int): Int = withTimeoutOrNull(500) {
|
||||
dataStore.data.map {
|
||||
it[intPreferencesKey(key)]
|
||||
}.first()
|
||||
} ?: default
|
||||
override suspend fun get(key: String, default: Int): Int = settings[key, default]
|
||||
|
||||
override suspend fun save(key: String, value: Float) {
|
||||
dataStore.edit {
|
||||
it[floatPreferencesKey(key)] = value
|
||||
}
|
||||
settings[key] = value
|
||||
}
|
||||
|
||||
override suspend fun get(key: String, default: Float): Float = withTimeoutOrNull(500) {
|
||||
dataStore.data.map {
|
||||
it[floatPreferencesKey(key)]
|
||||
}.first()
|
||||
} ?: default
|
||||
override suspend fun get(key: String, default: Float): Float = settings[key, default]
|
||||
|
||||
override suspend fun save(key: String, value: Double) {
|
||||
dataStore.edit {
|
||||
it[doublePreferencesKey(key)] = value
|
||||
}
|
||||
settings[key] = value
|
||||
}
|
||||
|
||||
override suspend fun get(key: String, default: Double): Double = withTimeoutOrNull(500) {
|
||||
dataStore.data.map {
|
||||
it[doublePreferencesKey(key)]
|
||||
}.first()
|
||||
} ?: default
|
||||
override suspend fun get(key: String, default: Double): Double = settings[key, default]
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core_preferences
|
||||
|
||||
import platform.Foundation.NSDocumentDirectory
|
||||
import platform.Foundation.NSFileManager
|
||||
import platform.Foundation.NSURL
|
||||
import platform.Foundation.NSUserDomainMask
|
||||
|
||||
actual fun getKeyStoreFilePath(): String {
|
||||
val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory(
|
||||
directory = NSDocumentDirectory,
|
||||
inDomain = NSUserDomainMask,
|
||||
appropriateForURL = null,
|
||||
create = false,
|
||||
error = null,
|
||||
)
|
||||
return requireNotNull(documentDirectory).path + "/${DefaultTemporaryKeyStore.FILE_NAME}"
|
||||
}
|
@ -1,20 +1,26 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.core_preferences.di
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.TemporaryKeyStore
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.DefaultTemporaryKeyStore
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.TemporaryKeyStore
|
||||
import com.russhwolf.settings.KeychainSettings
|
||||
import com.russhwolf.settings.Settings
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
actual val corePreferencesModule = module {
|
||||
singleOf<TemporaryKeyStore>(::DefaultTemporaryKeyStore)
|
||||
single<Settings> {
|
||||
KeychainSettings(service = "secret_shared_prefs")
|
||||
}
|
||||
single<TemporaryKeyStore> {
|
||||
DefaultTemporaryKeyStore(settings = get())
|
||||
}
|
||||
}
|
||||
|
||||
actual fun getTemporaryKeyStore(): TemporaryKeyStore {
|
||||
return TemporaryKeyStoreHelper.keyStore
|
||||
return TemporaryKeyStoreHelper.temporaryKeyStore
|
||||
}
|
||||
|
||||
object TemporaryKeyStoreHelper : KoinComponent {
|
||||
val keyStore: TemporaryKeyStore by inject()
|
||||
val temporaryKeyStore: TemporaryKeyStore by inject()
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[versions]
|
||||
androidx_activity_compose = "1.7.2"
|
||||
androidx_datastore = "1.1.0-dev01"
|
||||
androidx_crypto = "1.0.0"
|
||||
android_gradle = "7.4.2"
|
||||
compose = "1.4.3"
|
||||
compose_imageloader = "1.6.0"
|
||||
@ -12,15 +12,17 @@ ktorfit_gradle = "1.0.0"
|
||||
ktorfit_lib = "1.4.3"
|
||||
markdown = "0.4.1"
|
||||
moko_resources = "0.23.0"
|
||||
multiplatform_settings = "1.0.0"
|
||||
voyager = "1.0.0-rc05"
|
||||
|
||||
[libraries]
|
||||
|
||||
androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "androidx.activity.compose" }
|
||||
androidx_datastore = { module = "androidx.datastore:datastore-preferences-core", version.ref = "androidx.datastore" }
|
||||
androidx_security_crypto = { module = "androidx.security:security-crypto", version.ref = "androidx.crypto" }
|
||||
|
||||
compose_imageloader = { module = "io.github.qdsfdhvh:image-loader", version.ref = "compose.imageloader" }
|
||||
markdown = { module = "org.jetbrains:markdown", version.ref = "markdown" }
|
||||
multiplatform_settings = { module = "com.russhwolf:multiplatform-settings", version.ref = "multiplatform.settings" }
|
||||
|
||||
koin_core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||
koin_test = { module = "io.insert-koin:koin-test", version.ref = "koin" }
|
||||
|
Loading…
x
Reference in New Issue
Block a user