Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.openflocon.flocon.preferences.datastores.model

import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreference
import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreferenceType
import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreferenceValue

interface FloconDatastoreMapper {
Expand All @@ -14,6 +15,8 @@ class FloconDatastorePreference(
@Suppress("UNUSED_PARAMETER") mapper: FloconDatastoreMapper? = null,
) : FloconPreference {

override val type: String = FloconPreferenceType.DataStore

override suspend fun set(
columnName: String,
value: FloconPreferenceValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import io.github.openflocon.flocon.FloconLogger
import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreference
import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreferenceType
import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreferenceValue
import kotlinx.coroutines.flow.first

Expand All @@ -25,6 +26,8 @@ class FloconDatastorePreference(
private val mapper: FloconDatastoreMapper? = null // if we encrypted the datastore
) : FloconPreference {

override val type: String = FloconPreferenceType.DataStore

override suspend fun set(
columnName: String,
value: FloconPreferenceValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreferenceVal
object SamplePreferences : FloconPreference {
override val name: String = "Sample preferences"

// free value, any storage name can be displayed by the desktop app
override val type: String = "In memory"

/** Backed by a Compose state, so the screen follows the edits made from the desktop app. */
var counter: Int by mutableIntStateOf(0)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ package io.github.openflocon.flocon.plugins.sharedprefs.model
interface FloconPreference {
val name: String

/**
* Storage behind this preference, displayed next to its name in the desktop app.
* Use one of [FloconPreferenceType] or any free value ("MMKV", "Realm"...).
*/
val type: String get() = FloconPreferenceType.Custom

suspend fun set(
columnName: String,
value: FloconPreferenceValue,
Expand All @@ -23,3 +29,27 @@ data class FloconPreferenceValue(
val longValue: Long? = null,
val setStringValue: Set<String>? = null,
)

/**
* Suggested values for [FloconPreference.type]. Any other string is accepted.
*/
object FloconPreferenceType {
// Android
const val SharedPreferences = "SharedPreferences"

// Android, iOS, JVM
const val DataStore = "DataStore"

// iOS
const val UserDefaults = "UserDefaults"
const val Keychain = "Keychain"

// JVM
const val JavaPreferences = "Java Preferences"

// Wasm
const val LocalStorage = "LocalStorage"

// default value
const val Custom = "Custom"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ package io.github.openflocon.flocon.plugins.sharedprefs

import android.content.SharedPreferences
import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreference
import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreferenceType
import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreferenceValue

data class FloconSharedPreference(
override val name: String,
val sharedPreferences: SharedPreferences,
) : FloconPreference {

override val type: String = FloconPreferenceType.SharedPreferences

override suspend fun set(
columnName: String,
value: FloconPreferenceValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ internal class FloconSharedPrefsPluginImpl(
private fun sendAllSharedPrefs() {
val descriptors = preferences.value.values
.sortedBy { it.name }
.map { PreferencesDescriptor(it.name) }
.map { PreferencesDescriptor(name = it.name, type = it.type) }

try {
sender.send(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ package io.github.openflocon.flocon.plugins.sharedprefs.model
interface FloconPreference {
val name: String

/**
* Storage behind this preference, displayed next to its name in the desktop app.
* Use one of [FloconPreferenceType] or any free value ("MMKV", "Realm"...).
*/
val type: String get() = FloconPreferenceType.Custom

suspend fun set(
columnName: String,
value: FloconPreferenceValue,
Expand All @@ -23,3 +29,27 @@ data class FloconPreferenceValue(
val longValue: Long? = null,
val setStringValue: Set<String>? = null,
)

/**
* Suggested values for [FloconPreference.type]. Any other string is accepted.
*/
object FloconPreferenceType {
// Android
const val SharedPreferences = "SharedPreferences"

// Android, iOS, JVM
const val DataStore = "DataStore"

// iOS
const val UserDefaults = "UserDefaults"
const val Keychain = "Keychain"

// JVM
const val JavaPreferences = "Java Preferences"

// Wasm
const val LocalStorage = "LocalStorage"

// default value
const val Custom = "Custom"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ import kotlinx.serialization.Serializable
@Serializable
internal data class PreferencesDescriptor(
val name: String,
)
val type: String? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.openflocon.flocon.plugins.sharedprefs

import io.github.openflocon.flocon.FloconContext
import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreference
import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreferenceType
import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreferenceValue
import platform.Foundation.NSUserDefaults

Expand All @@ -18,6 +19,8 @@ private class UserDefaultsPreference(

override val name: String = "UserDefaults"

override val type: String = FloconPreferenceType.UserDefaults

private fun app(): Map<String, Any?> = defaults.dictionaryRepresentation()
.mapNotNull { (key, value) -> (key as? String)?.let { it to value } }
.toMap()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.github.openflocon.flocon.Protocol
import io.github.openflocon.flocon.core.FloconEncoder
import io.github.openflocon.flocon.core.FloconMessageSender
import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreference
import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreferenceType
import io.github.openflocon.flocon.plugins.sharedprefs.model.FloconPreferenceValue
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
Expand All @@ -21,7 +22,10 @@ class FloconSharedPrefsPluginTest {
override fun sendPendingMessages() {}
}

private class InMemoryPreference(override val name: String) : FloconPreference {
private class InMemoryPreference(
override val name: String,
override val type: String = FloconPreferenceType.Custom,
) : FloconPreference {
val values = mutableMapOf("age" to FloconPreferenceValue(intValue = 34))
override suspend fun set(columnName: String, value: FloconPreferenceValue) {
values[columnName] = value
Expand All @@ -45,7 +49,19 @@ class FloconSharedPrefsPluginTest {
val (pluginName, method, body) = sender.messages.single()
assertEquals(Protocol.FromDevice.SharedPreferences.Plugin, pluginName)
assertEquals(Protocol.FromDevice.SharedPreferences.Method.GetSharedPreferences, method)
assertEquals("[{\"name\":\"user\"}]", body)
assertEquals("[{\"name\":\"user\",\"type\":\"Custom\"}]", body)
}

@Test
fun `register sends the type of each preference`() {
plugin.register(InMemoryPreference(name = "settings", type = FloconPreferenceType.DataStore))
plugin.register(InMemoryPreference(name = "room", type = "Room"))

val (_, _, body) = sender.messages.last()
assertEquals(
"[{\"name\":\"room\",\"type\":\"Room\"},{\"name\":\"settings\",\"type\":\"DataStore\"}]",
body,
)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package io.github.openflocon.flocondesktop.features.sharedpreferences

import io.github.openflocon.domain.sharedpreference.models.DeviceSharedPreferenceDomainModel
import io.github.openflocon.domain.sharedpreference.models.SharedPreferenceRowDomainModel
import io.github.openflocon.flocondesktop.features.sharedpreferences.model.DeviceSharedPrefUiModel
import io.github.openflocon.flocondesktop.features.sharedpreferences.model.PreferenceTypeUiModel
import io.github.openflocon.flocondesktop.features.sharedpreferences.model.SharedPreferencesRowUiModel

internal fun SharedPreferenceRowDomainModel.Value.toUi(): SharedPreferencesRowUiModel.Value = when (this) {
Expand Down Expand Up @@ -28,3 +31,21 @@ internal fun SharedPreferenceRowDomainModel.Value.toUi(): SharedPreferencesRowUi
value,
)
}

internal fun DeviceSharedPreferenceDomainModel.toUi(): DeviceSharedPrefUiModel = DeviceSharedPrefUiModel(
id = id,
name = name,
type = type,
typeKind = preferenceTypeKind(type),
)

// the type is a free value sent by the app: known storages get their own icon, the others a generic one
private fun preferenceTypeKind(type: String?): PreferenceTypeUiModel = when (type?.lowercase()) {
"sharedpreferences" -> PreferenceTypeUiModel.SharedPreferences
"datastore" -> PreferenceTypeUiModel.DataStore
"userdefaults" -> PreferenceTypeUiModel.UserDefaults
"keychain" -> PreferenceTypeUiModel.Keychain
"java preferences" -> PreferenceTypeUiModel.JavaPreferences
"localstorage" -> PreferenceTypeUiModel.LocalStorage
else -> PreferenceTypeUiModel.Custom
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package io.github.openflocon.flocondesktop.features.sharedpreferences.delegate

import io.github.openflocon.domain.common.DispatcherProvider
import io.github.openflocon.domain.device.usecase.ObserveCurrentDeviceIdUseCase
import io.github.openflocon.domain.sharedpreference.models.DeviceSharedPreferenceDomainModel
import io.github.openflocon.domain.sharedpreference.usecase.AskForDeviceSharedPreferencesUseCase
import io.github.openflocon.domain.sharedpreference.usecase.GetCurrentDeviceSharedPreferenceValuesUseCase
import io.github.openflocon.domain.sharedpreference.usecase.ObserveCurrentDeviceSelectedSharedPreferenceUseCase
Expand All @@ -12,6 +11,7 @@ import io.github.openflocon.flocondesktop.common.coroutines.closeable.CloseableD
import io.github.openflocon.flocondesktop.common.coroutines.closeable.CloseableScoped
import io.github.openflocon.flocondesktop.features.sharedpreferences.model.DeviceSharedPrefUiModel
import io.github.openflocon.flocondesktop.features.sharedpreferences.model.SharedPrefsStateUiModel
import io.github.openflocon.flocondesktop.features.sharedpreferences.toUi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
Expand Down Expand Up @@ -39,16 +39,12 @@ class SharedPrefSelectorDelegate(
if (sharedPrefs.isEmpty()) {
SharedPrefsStateUiModel.Empty
} else {
val selectedOrFirst = selected ?: sharedPrefs.first().also {
selectCurrentDeviceSharedPrefUseCase(it.id)
}
SharedPrefsStateUiModel.WithContent(
preferences = sharedPrefs.map { toUi(it) },
selected =
toUi(
selected ?: run {
sharedPrefs.first().also {
selectCurrentDeviceSharedPrefUseCase(it.id)
}
},
),
preferences = sharedPrefs.map { it.toUi() },
selected = selectedOrFirst.toUi(),
)
}
}.flowOn(dispatcherProvider.viewModel)
Expand All @@ -58,11 +54,6 @@ class SharedPrefSelectorDelegate(
SharedPrefsStateUiModel.Loading,
)

fun toUi(sharedPref: DeviceSharedPreferenceDomainModel) = DeviceSharedPrefUiModel(
id = sharedPref.id,
name = sharedPref.name,
)

fun onSharedPreferenceSelected(sharedPref: DeviceSharedPrefUiModel) {
coroutineScope.launch(dispatcherProvider.viewModel) {
selectCurrentDeviceSharedPrefUseCase(sharedPref.id)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package io.github.openflocon.flocondesktop.features.sharedpreferences.model

import androidx.compose.runtime.Immutable
import io.github.openflocon.domain.database.models.DeviceDataBaseId

@Immutable
data class DeviceSharedPrefUiModel(
val id: DeviceDataBaseId,
val name: String,
val type: String?,
val typeKind: PreferenceTypeUiModel,
)

fun previewDeviceSharedPrefUiModel(id: String = "id") = DeviceSharedPrefUiModel(
id = id,
name = "sharedPref_$id",
type = "SharedPreferences",
typeKind = PreferenceTypeUiModel.SharedPreferences,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.github.openflocon.flocondesktop.features.sharedpreferences.model

// storage of a preference, each kind has its own icon in the selector
enum class PreferenceTypeUiModel {
SharedPreferences,
DataStore,
UserDefaults,
Keychain,
JavaPreferences,
LocalStorage,
Custom,
}
Loading
Loading