From 32774cdd46b8c0ed0d7c25461ea9829b6c4f9f79 Mon Sep 17 00:00:00 2001 From: Florent Champigny Date: Fri, 30 Oct 2026 09:22:26 +0100 Subject: [PATCH 1/6] feat: [PREFERENCES] Send the preference type and show it in the selector --- .../model/FloconDatastorePreference.kt | 3 ++ .../model/FloconDatastorePreference.kt | 3 ++ .../multi/preferences/SamplePreferences.kt | 3 ++ .../sharedprefs/model/FloconPreference.kt | 30 +++++++++++++++++++ .../sharedprefs/FloconSharedPreference.kt | 3 ++ .../sharedprefs/FloconSharedPrefsPlugin.kt | 2 +- .../sharedprefs/model/FloconPreference.kt | 30 +++++++++++++++++++ .../model/FloconPreferenceWrapper.kt | 3 +- .../FloconSharedPrefsPlugin.ios.kt | 3 ++ .../FloconSharedPrefsPluginTest.kt | 20 +++++++++++-- .../delegate/SharedPrefSelectorDelegate.kt | 1 + .../model/DeviceSharedPrefUiModel.kt | 2 ++ .../view/SharedPreferenceSelector.kt | 15 ++++++++-- ...iceSharedPreferencesLocalDataSourceImpl.kt | 5 +++- .../models/DeviceSharedPreferenceDataModel.kt | 2 ++ .../DeviceSharedPreferenceDomainModel.kt | 2 ++ .../SharedPreferencesMcpModels.kt | 2 ++ .../SharedPreferencesMcpToolProvider.kt | 4 +-- .../SharedPreferencesMcpToolProviderTest.kt | 3 +- docs/sharedpref.md | 24 ++++++++++++++- docs2/mcp.md | 2 +- docs2/migration.md | 1 + docs2/sharedpref.md | 22 ++++++++++++++ plugins/flocon/skills/flocon-debug/SKILL.md | 2 +- protocol/shared-preferences.md | 11 +++---- 25 files changed, 180 insertions(+), 18 deletions(-) diff --git a/FloconAndroid/datastores-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/preferences/datastores/model/FloconDatastorePreference.kt b/FloconAndroid/datastores-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/preferences/datastores/model/FloconDatastorePreference.kt index da64aa488..d5f4592fa 100644 --- a/FloconAndroid/datastores-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/preferences/datastores/model/FloconDatastorePreference.kt +++ b/FloconAndroid/datastores-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/preferences/datastores/model/FloconDatastorePreference.kt @@ -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 { @@ -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 diff --git a/FloconAndroid/datastores/src/commonMain/kotlin/io/github/openflocon/flocon/preferences/datastores/model/FloconDatastorePreference.kt b/FloconAndroid/datastores/src/commonMain/kotlin/io/github/openflocon/flocon/preferences/datastores/model/FloconDatastorePreference.kt index a6b77fa6c..efb24ef9a 100644 --- a/FloconAndroid/datastores/src/commonMain/kotlin/io/github/openflocon/flocon/preferences/datastores/model/FloconDatastorePreference.kt +++ b/FloconAndroid/datastores/src/commonMain/kotlin/io/github/openflocon/flocon/preferences/datastores/model/FloconDatastorePreference.kt @@ -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 @@ -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 diff --git a/FloconAndroid/sample-multiplatform/src/commonMain/kotlin/io/github/openflocon/flocon/myapplication/multi/preferences/SamplePreferences.kt b/FloconAndroid/sample-multiplatform/src/commonMain/kotlin/io/github/openflocon/flocon/myapplication/multi/preferences/SamplePreferences.kt index 9e456ebc3..2a26e1760 100644 --- a/FloconAndroid/sample-multiplatform/src/commonMain/kotlin/io/github/openflocon/flocon/myapplication/multi/preferences/SamplePreferences.kt +++ b/FloconAndroid/sample-multiplatform/src/commonMain/kotlin/io/github/openflocon/flocon/myapplication/multi/preferences/SamplePreferences.kt @@ -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) diff --git a/FloconAndroid/sharedprefs-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/model/FloconPreference.kt b/FloconAndroid/sharedprefs-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/model/FloconPreference.kt index 83f7fe963..ee935109f 100644 --- a/FloconAndroid/sharedprefs-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/model/FloconPreference.kt +++ b/FloconAndroid/sharedprefs-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/model/FloconPreference.kt @@ -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, @@ -23,3 +29,27 @@ data class FloconPreferenceValue( val longValue: Long? = null, val setStringValue: Set? = 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" +} diff --git a/FloconAndroid/sharedprefs/src/androidMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPreference.kt b/FloconAndroid/sharedprefs/src/androidMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPreference.kt index 1bbe8d4ac..1bc2e3047 100644 --- a/FloconAndroid/sharedprefs/src/androidMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPreference.kt +++ b/FloconAndroid/sharedprefs/src/androidMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPreference.kt @@ -2,6 +2,7 @@ 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( @@ -9,6 +10,8 @@ data class FloconSharedPreference( val sharedPreferences: SharedPreferences, ) : FloconPreference { + override val type: String = FloconPreferenceType.SharedPreferences + override suspend fun set( columnName: String, value: FloconPreferenceValue diff --git a/FloconAndroid/sharedprefs/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPrefsPlugin.kt b/FloconAndroid/sharedprefs/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPrefsPlugin.kt index aa853a884..ae7e4d6a2 100644 --- a/FloconAndroid/sharedprefs/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPrefsPlugin.kt +++ b/FloconAndroid/sharedprefs/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPrefsPlugin.kt @@ -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( diff --git a/FloconAndroid/sharedprefs/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/model/FloconPreference.kt b/FloconAndroid/sharedprefs/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/model/FloconPreference.kt index 83f7fe963..ee935109f 100644 --- a/FloconAndroid/sharedprefs/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/model/FloconPreference.kt +++ b/FloconAndroid/sharedprefs/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/model/FloconPreference.kt @@ -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, @@ -23,3 +29,27 @@ data class FloconPreferenceValue( val longValue: Long? = null, val setStringValue: Set? = 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" +} diff --git a/FloconAndroid/sharedprefs/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/model/FloconPreferenceWrapper.kt b/FloconAndroid/sharedprefs/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/model/FloconPreferenceWrapper.kt index b8a05cf04..53579f993 100644 --- a/FloconAndroid/sharedprefs/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/model/FloconPreferenceWrapper.kt +++ b/FloconAndroid/sharedprefs/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/model/FloconPreferenceWrapper.kt @@ -5,4 +5,5 @@ import kotlinx.serialization.Serializable @Serializable internal data class PreferencesDescriptor( val name: String, -) \ No newline at end of file + val type: String? = null, +) diff --git a/FloconAndroid/sharedprefs/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPrefsPlugin.ios.kt b/FloconAndroid/sharedprefs/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPrefsPlugin.ios.kt index 3ccb44f42..28a71b0eb 100644 --- a/FloconAndroid/sharedprefs/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPrefsPlugin.ios.kt +++ b/FloconAndroid/sharedprefs/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPrefsPlugin.ios.kt @@ -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 @@ -18,6 +19,8 @@ private class UserDefaultsPreference( override val name: String = "UserDefaults" + override val type: String = FloconPreferenceType.UserDefaults + private fun app(): Map = defaults.dictionaryRepresentation() .mapNotNull { (key, value) -> (key as? String)?.let { it to value } } .toMap() diff --git a/FloconAndroid/sharedprefs/src/jvmTest/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPrefsPluginTest.kt b/FloconAndroid/sharedprefs/src/jvmTest/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPrefsPluginTest.kt index ee00a6f45..129f6fce6 100644 --- a/FloconAndroid/sharedprefs/src/jvmTest/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPrefsPluginTest.kt +++ b/FloconAndroid/sharedprefs/src/jvmTest/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPrefsPluginTest.kt @@ -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 @@ -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 @@ -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 diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/delegate/SharedPrefSelectorDelegate.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/delegate/SharedPrefSelectorDelegate.kt index e53d1439d..bd45ed42f 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/delegate/SharedPrefSelectorDelegate.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/delegate/SharedPrefSelectorDelegate.kt @@ -61,6 +61,7 @@ class SharedPrefSelectorDelegate( fun toUi(sharedPref: DeviceSharedPreferenceDomainModel) = DeviceSharedPrefUiModel( id = sharedPref.id, name = sharedPref.name, + type = sharedPref.type, ) fun onSharedPreferenceSelected(sharedPref: DeviceSharedPrefUiModel) { diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/DeviceSharedPrefUiModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/DeviceSharedPrefUiModel.kt index e2398b763..cc7b7766c 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/DeviceSharedPrefUiModel.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/DeviceSharedPrefUiModel.kt @@ -5,9 +5,11 @@ import io.github.openflocon.domain.database.models.DeviceDataBaseId data class DeviceSharedPrefUiModel( val id: DeviceDataBaseId, val name: String, + val type: String?, ) fun previewDeviceSharedPrefUiModel(id: String = "id") = DeviceSharedPrefUiModel( id = id, name = "sharedPref_$id", + type = "SharedPreferences", ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceSelector.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceSelector.kt index 55ba34df6..4bd66da32 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceSelector.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceSelector.kt @@ -60,6 +60,7 @@ internal fun SharedPreferenceSelector( is SharedPrefsStateUiModel.WithContent -> FloconPageSelector( value = state.selected.name, + caption = state.selected.type, onClick = onClick, ) } @@ -80,7 +81,8 @@ internal fun SharedPreferenceSelector( if (state is SharedPrefsStateUiModel.WithContent) { val filteredItems = remember(filterText, state.preferences) { state.preferences.filter { - it.name.contains(filterText, ignoreCase = true) + it.name.contains(filterText, ignoreCase = true) || + it.type?.contains(filterText, ignoreCase = true) == true } } @@ -90,7 +92,16 @@ internal fun SharedPreferenceSelector( onClick = { onSharedPrefSelected(preference) expanded = false - } + }, + secondaryAction = preference.type?.let { type -> + { + Text( + text = type, + style = FloconTheme.typography.bodySmall, + color = FloconTheme.colors.inkFaint, + ) + } + }, ) } } diff --git a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/sharedpreference/datasources/DeviceSharedPreferencesLocalDataSourceImpl.kt b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/sharedpreference/datasources/DeviceSharedPreferencesLocalDataSourceImpl.kt index 95e486b35..946116f3d 100644 --- a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/sharedpreference/datasources/DeviceSharedPreferencesLocalDataSourceImpl.kt +++ b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/sharedpreference/datasources/DeviceSharedPreferencesLocalDataSourceImpl.kt @@ -42,11 +42,14 @@ internal class DeviceSharedPreferencesLocalDataSourceImpl : DeviceSharedPreferen ) { deviceSharedPreferences.update { val actual = it[deviceIdAndPackageName] + // keyed by id, so a preference sent again replaces the previous one val newList = buildList { actual?.let(::addAll) addAll(sharedPreferences) } - .distinct() + .associateBy { it.id } + .values + .toList() it + (deviceIdAndPackageName to newList) } diff --git a/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/sharedpreference/models/DeviceSharedPreferenceDataModel.kt b/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/sharedpreference/models/DeviceSharedPreferenceDataModel.kt index 7e8e3c133..830bc6c73 100644 --- a/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/sharedpreference/models/DeviceSharedPreferenceDataModel.kt +++ b/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/sharedpreference/models/DeviceSharedPreferenceDataModel.kt @@ -6,11 +6,13 @@ import kotlinx.serialization.Serializable @Serializable data class DeviceSharedPreferenceDataModel( val name: String, + val type: String? = null, ) fun toDeviceSharedPreferenceDomain(list: List): List = list.map { DeviceSharedPreferenceDomainModel( id = it.name, name = it.name, + type = it.type, ) } diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/sharedpreference/models/DeviceSharedPreferenceDomainModel.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/sharedpreference/models/DeviceSharedPreferenceDomainModel.kt index b5b73f7b2..4dc1f3dad 100644 --- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/sharedpreference/models/DeviceSharedPreferenceDomainModel.kt +++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/sharedpreference/models/DeviceSharedPreferenceDomainModel.kt @@ -5,4 +5,6 @@ typealias DeviceSharedPreferenceId = String data class DeviceSharedPreferenceDomainModel( val id: DeviceSharedPreferenceId, val name: String, + // storage sent by the app (SharedPreferences, DataStore, UserDefaults...), null for older SDKs + val type: String? = null, ) diff --git a/FloconDesktop/mcp/src/desktopMain/kotlin/io/github/openflocon/mcp/sharedpreferences/SharedPreferencesMcpModels.kt b/FloconDesktop/mcp/src/desktopMain/kotlin/io/github/openflocon/mcp/sharedpreferences/SharedPreferencesMcpModels.kt index ef03ab22b..e4d7d313f 100644 --- a/FloconDesktop/mcp/src/desktopMain/kotlin/io/github/openflocon/mcp/sharedpreferences/SharedPreferencesMcpModels.kt +++ b/FloconDesktop/mcp/src/desktopMain/kotlin/io/github/openflocon/mcp/sharedpreferences/SharedPreferencesMcpModels.kt @@ -14,6 +14,8 @@ internal data class SharedPreferencesMcpResult( internal data class SharedPreferenceMcpModel( val sharedPreferenceId: String, val name: String, + /** storage of the preference (SharedPreferences, DataStore, UserDefaults, Keychain... or a free value), null for older SDKs */ + val type: String?, ) @Serializable diff --git a/FloconDesktop/mcp/src/desktopMain/kotlin/io/github/openflocon/mcp/sharedpreferences/SharedPreferencesMcpToolProvider.kt b/FloconDesktop/mcp/src/desktopMain/kotlin/io/github/openflocon/mcp/sharedpreferences/SharedPreferencesMcpToolProvider.kt index 07eec2c0a..143d04f1d 100644 --- a/FloconDesktop/mcp/src/desktopMain/kotlin/io/github/openflocon/mcp/sharedpreferences/SharedPreferencesMcpToolProvider.kt +++ b/FloconDesktop/mcp/src/desktopMain/kotlin/io/github/openflocon/mcp/sharedpreferences/SharedPreferencesMcpToolProvider.kt @@ -29,7 +29,7 @@ internal class SharedPreferencesMcpToolProvider( name = "list_shared_preferences", title = "List shared preferences", description = "Lists the SharedPreferences files (Android) / UserDefaults suites (iOS) of the mobile app, with their id " + - "(to give to get_shared_preference_values).", + "(to give to get_shared_preference_values) and their storage type (SharedPreferences, DataStore, UserDefaults, Keychain...).", annotations = readOnlyToolAnnotations, handler = ::listSharedPreferences, ) @@ -56,7 +56,7 @@ internal class SharedPreferencesMcpToolProvider( return SharedPreferencesMcpResult( deviceId = target.deviceId, packageName = target.packageName, - sharedPreferences = preferences.map { SharedPreferenceMcpModel(sharedPreferenceId = it.id, name = it.name) }, + sharedPreferences = preferences.map { SharedPreferenceMcpModel(sharedPreferenceId = it.id, name = it.name, type = it.type) }, ) } diff --git a/FloconDesktop/mcp/src/desktopTest/kotlin/io/github/openflocon/mcp/sharedpreferences/SharedPreferencesMcpToolProviderTest.kt b/FloconDesktop/mcp/src/desktopTest/kotlin/io/github/openflocon/mcp/sharedpreferences/SharedPreferencesMcpToolProviderTest.kt index 7ae8ff67e..90f0bfbb9 100644 --- a/FloconDesktop/mcp/src/desktopTest/kotlin/io/github/openflocon/mcp/sharedpreferences/SharedPreferencesMcpToolProviderTest.kt +++ b/FloconDesktop/mcp/src/desktopTest/kotlin/io/github/openflocon/mcp/sharedpreferences/SharedPreferencesMcpToolProviderTest.kt @@ -38,7 +38,7 @@ class SharedPreferencesMcpToolProviderTest { selectedApps = mapOf("pixel" to "io.flocon.sample"), ) private val repository = FakeSharedPreferencesRepository( - files = listOf(DeviceSharedPreferenceDomainModel(id = "settings", name = "settings")), + files = listOf(DeviceSharedPreferenceDomainModel(id = "settings", name = "settings", type = "DataStore")), values = mapOf( "settings" to listOf( SharedPreferenceRowDomainModel("theme", Value.StringValue("dark")), @@ -79,6 +79,7 @@ class SharedPreferencesMcpToolProviderTest { assertEquals(1, repository.askedForFiles) val files = result["sharedPreferences"]!!.jsonArray assertEquals(listOf("settings"), files.map { it.jsonObject["sharedPreferenceId"]!!.jsonPrimitive.content }) + assertEquals(listOf("DataStore"), files.map { it.jsonObject["type"]!!.jsonPrimitive.content }) } @Test diff --git a/docs/sharedpref.md b/docs/sharedpref.md index c5eddca9a..93896d8a6 100644 --- a/docs/sharedpref.md +++ b/docs/sharedpref.md @@ -70,6 +70,7 @@ You can expose any proprietary storage system (such as EncryptedSharedPreference ```kotlin class MyCustomPreference : FloconPreference { override val name: String = "Encrypted Vault" + override val type: String = "EncryptedSharedPreferences" override suspend fun columns(): List = listOf("token", "is_authenticated") @@ -83,4 +84,25 @@ class MyCustomPreference : FloconPreference { } floconRegisterPreference(MyCustomPreference()) -``` \ No newline at end of file +``` + +--- + +### Preference type + +Each preference tells the desktop app which storage it comes from. The type is displayed next to its name in the preference selector (and returned by the MCP `list_shared_preferences` tool). + +The built-in preferences set it for you. For your own `FloconPreference`, override `type` with one of the `FloconPreferenceType` constants or any free value (defaults to `"Custom"`): + +| Constant | Value | Platform | +|---|---|---| +| `FloconPreferenceType.SharedPreferences` | `SharedPreferences` | Android | +| `FloconPreferenceType.DataStore` | `DataStore` | Android, iOS, JVM | +| `FloconPreferenceType.UserDefaults` | `UserDefaults` | iOS | +| `FloconPreferenceType.Keychain` | `Keychain` | iOS | +| `FloconPreferenceType.JavaPreferences` | `Java Preferences` | JVM | +| `FloconPreferenceType.LocalStorage` | `LocalStorage` | Wasm | +| `FloconPreferenceType.Custom` | `Custom` | default | + +!!! note "Swift" + Kotlin default getters are not inherited by Swift classes: a Swift class implementing `FloconPreference` must declare `type`. diff --git a/docs2/mcp.md b/docs2/mcp.md index c115afe1b..b7247e2be 100644 --- a/docs2/mcp.md +++ b/docs2/mcp.md @@ -318,7 +318,7 @@ By default, every tool targets the **device and app currently selected in Flocon | `start_screen_recording` | Starts recording the device screen. One recording per device; on Android, `screenrecord` stops by itself after 3 minutes. | | `stop_screen_recording` | Stops the recording and returns the path of the mp4 saved in `ScreenRecord/`. Without target, stops the only running recording. | | `open_flocon_folder` | Opens the Flocon documents folder (screenshots, screen records, exports), or a sub folder (`Screenshot`, `ScreenRecord`), in the file manager of the computer, and returns its path. | -| `list_shared_preferences` | SharedPreferences files (Android) / UserDefaults suites (iOS) of the app, with their `sharedPreferenceId`. | +| `list_shared_preferences` | SharedPreferences files (Android) / UserDefaults suites (iOS) of the app, with their `sharedPreferenceId` and storage `type` (SharedPreferences, DataStore, UserDefaults, Keychain...). | | `get_shared_preference_values` | Keys and typed values of a SharedPreferences file, as stored. Optional text filter (key or value) and limit. | | `list_tables` | Custom tables sent by the app (Table plugin), with their `tableId`. | | `list_table_items` | Rows of a table as column -> value maps, newest first. Filters: text, order, limit. | diff --git a/docs2/migration.md b/docs2/migration.md index 75ad500d5..04df4d655 100644 --- a/docs2/migration.md +++ b/docs2/migration.md @@ -114,5 +114,6 @@ The accessors are: `analyticsPlugin`, `crashReporterPlugin`, `dashboardPlugin`, untouched when `FloconNetwork` is not installed. - **Desktop menu**: when it connects, the SDK tells the desktop app which plugins are installed. The desktop only shows the menu items of the installed plugins (an older SDK that does not send them shows everything). Sections can be collapsed, and items can be pinned (pin icon on hover) to stay at the top. - **Encrypted connection**: the SDK connects to the desktop with `wss://` / `https://` instead of `ws://` / `http://`. The desktop no longer serves the plain ports, so **update the SDK and Flocon Desktop together** (an old SDK cannot connect to a new desktop, and the other way around). The desktop certificate is self-signed and is not verified by the SDK: the connection is encrypted, not authenticated. The cleartext rule for `localhost` / `127.0.0.1` (and the one `flocon-connect` used to add for any IP address) is no longer needed, you can remove it from your `network_security_config.xml`. +- **Preference type**: `FloconPreference` has a new `type` property, shown next to the preference name in the desktop app. It defaults to `"Custom"` in Kotlin; a Swift class implementing `FloconPreference` must declare it. - **Release builds** now use `flocon-no-op` + one no-op per plugin: nothing is started and no connection is made to the desktop app. diff --git a/docs2/sharedpref.md b/docs2/sharedpref.md index 0626164de..f68b0c609 100644 --- a/docs2/sharedpref.md +++ b/docs2/sharedpref.md @@ -92,6 +92,7 @@ You can expose any proprietary storage system (such as EncryptedSharedPreference ```kotlin class MyCustomPreference : FloconPreference { override val name: String = "Encrypted Vault" + override val type: String = "EncryptedSharedPreferences" override suspend fun columns(): List = listOf("token", "is_authenticated") @@ -106,3 +107,24 @@ class MyCustomPreference : FloconPreference { Flocon.preferencesPlugin.register(MyCustomPreference()) ``` + +--- + +### Preference type + +Each preference tells the desktop app which storage it comes from. The type is displayed next to its name in the preference selector (and returned by the MCP `list_shared_preferences` tool). + +The built-in preferences set it for you. For your own `FloconPreference`, override `type` with one of the `FloconPreferenceType` constants or any free value (defaults to `"Custom"`): + +| Constant | Value | Platform | +|---|---|---| +| `FloconPreferenceType.SharedPreferences` | `SharedPreferences` | Android | +| `FloconPreferenceType.DataStore` | `DataStore` | Android, iOS, JVM | +| `FloconPreferenceType.UserDefaults` | `UserDefaults` | iOS | +| `FloconPreferenceType.Keychain` | `Keychain` | iOS | +| `FloconPreferenceType.JavaPreferences` | `Java Preferences` | JVM | +| `FloconPreferenceType.LocalStorage` | `LocalStorage` | Wasm | +| `FloconPreferenceType.Custom` | `Custom` | default | + +!!! note "Swift" + Kotlin default getters are not inherited by Swift classes: a Swift class implementing `FloconPreference` must declare `type`. diff --git a/plugins/flocon/skills/flocon-debug/SKILL.md b/plugins/flocon/skills/flocon-debug/SKILL.md index 745d79cdd..950a5fe14 100644 --- a/plugins/flocon/skills/flocon-debug/SKILL.md +++ b/plugins/flocon/skills/flocon-debug/SKILL.md @@ -31,7 +31,7 @@ By default every tool targets the device and app selected in Flocon Desktop. Cal | See what the Flocon desktop app itself displays (the screen the user is looking at in Flocon) | `take_flocon_desktop_screenshot` (returns the image of the visible window, nothing is saved; not the device screen) | | Reproduce a bug on video | `start_screen_recording`, do the scenario, then `stop_screen_recording` (returns the mp4 path; Android stops by itself after 3 minutes) | | Show the user the saved screenshots, videos or exports | `open_flocon_folder` (opens the Flocon documents folder, optional `Screenshot` / `ScreenRecord` sub folder) | -| Check SharedPreferences | `list_shared_preferences` (gives the `sharedPreferenceId`), `get_shared_preference_values` (typed values, optional text filter) | +| Check SharedPreferences | `list_shared_preferences` (gives the `sharedPreferenceId` and the storage `type`), `get_shared_preference_values` (typed values, optional text filter) | | Check feature flags | `list_feature_flags` (provider, name, state, variant, filters), `get_feature_flag` for the full payload | | Check what a screen reader gets (focus order, announcements, content descriptions) | `get_accessibility_state` first (is TalkBack / VoiceOver on, is capture supported), then `list_accessibility_events` (type and text filters), `clear_accessibility_events`; walk the focus order with `move_accessibility_focus` (`next` / `previous`, needs TalkBack on) and read where it is with `get_accessibility_focused_node` | | Check which images the app loaded | `list_images` (urls and load times) | diff --git a/protocol/shared-preferences.md b/protocol/shared-preferences.md index 63a522cb7..f4322c53a 100644 --- a/protocol/shared-preferences.md +++ b/protocol/shared-preferences.md @@ -16,14 +16,15 @@ Desktop → Mobile: empty body. Mobile → Desktop: list of preference files (al ### `Preference` -| Key | Type | Description | -|--------|----------|---------------------------| -| `name` | `String` | Preference file / store name | +| Key | Type | Description | +|--------|-----------|---------------------------------------------------------------------------------------------------------| +| `name` | `String` | Preference file / store name | +| `type` | `String?` | Storage, displayed by the desktop: `SharedPreferences`, `DataStore`, `UserDefaults`, `Keychain`, `Java Preferences`, `LocalStorage`, `Custom` or any free value. Absent for older SDKs | ```json [ - { "name": "com.example.app_preferences" }, - { "name": "session" } + { "name": "com.example.app_preferences", "type": "SharedPreferences" }, + { "name": "session", "type": "DataStore" } ] ``` From 39c7a7606585a7e6378856c03274c2f8ba24e0d7 Mon Sep 17 00:00:00 2001 From: Florent Champigny Date: Fri, 30 Oct 2026 09:25:20 +0100 Subject: [PATCH 2/6] feat: [PREFERENCES] Show an icon per preference type in the selector --- .../features/sharedpreferences/Mapper.kt | 29 +++++++++++++++++++ .../delegate/SharedPrefSelectorDelegate.kt | 22 ++++---------- .../model/DeviceSharedPrefUiModel.kt | 8 +++++ .../view/SharedPreferenceSelector.kt | 2 ++ docs/sharedpref.md | 2 +- docs2/sharedpref.md | 2 +- 6 files changed, 47 insertions(+), 18 deletions(-) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/Mapper.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/Mapper.kt index afb45e72a..51d79a7de 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/Mapper.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/Mapper.kt @@ -1,6 +1,17 @@ package io.github.openflocon.flocondesktop.features.sharedpreferences +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Android +import androidx.compose.material.icons.outlined.Coffee +import androidx.compose.material.icons.outlined.Key +import androidx.compose.material.icons.outlined.Language +import androidx.compose.material.icons.outlined.PhoneIphone +import androidx.compose.material.icons.outlined.Storage +import androidx.compose.material.icons.outlined.Tune +import androidx.compose.ui.graphics.vector.ImageVector +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.SharedPreferencesRowUiModel internal fun SharedPreferenceRowDomainModel.Value.toUi(): SharedPreferencesRowUiModel.Value = when (this) { @@ -28,3 +39,21 @@ internal fun SharedPreferenceRowDomainModel.Value.toUi(): SharedPreferencesRowUi value, ) } + +internal fun DeviceSharedPreferenceDomainModel.toUi(): DeviceSharedPrefUiModel = DeviceSharedPrefUiModel( + id = id, + name = name, + type = type, + typeIcon = preferenceTypeIcon(type), +) + +// the type is a free value sent by the app: known storages get their own icon, the others a generic one +private fun preferenceTypeIcon(type: String?): ImageVector = when (type?.lowercase()) { + "sharedpreferences" -> Icons.Outlined.Android + "datastore" -> Icons.Outlined.Storage + "userdefaults" -> Icons.Outlined.PhoneIphone + "keychain" -> Icons.Outlined.Key + "java preferences" -> Icons.Outlined.Coffee + "localstorage" -> Icons.Outlined.Language + else -> Icons.Outlined.Tune +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/delegate/SharedPrefSelectorDelegate.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/delegate/SharedPrefSelectorDelegate.kt index bd45ed42f..211139e72 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/delegate/SharedPrefSelectorDelegate.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/delegate/SharedPrefSelectorDelegate.kt @@ -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 @@ -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 @@ -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) @@ -58,12 +54,6 @@ class SharedPrefSelectorDelegate( SharedPrefsStateUiModel.Loading, ) - fun toUi(sharedPref: DeviceSharedPreferenceDomainModel) = DeviceSharedPrefUiModel( - id = sharedPref.id, - name = sharedPref.name, - type = sharedPref.type, - ) - fun onSharedPreferenceSelected(sharedPref: DeviceSharedPrefUiModel) { coroutineScope.launch(dispatcherProvider.viewModel) { selectCurrentDeviceSharedPrefUseCase(sharedPref.id) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/DeviceSharedPrefUiModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/DeviceSharedPrefUiModel.kt index cc7b7766c..5c25d0741 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/DeviceSharedPrefUiModel.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/DeviceSharedPrefUiModel.kt @@ -1,15 +1,23 @@ package io.github.openflocon.flocondesktop.features.sharedpreferences.model +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Android +import androidx.compose.runtime.Immutable +import androidx.compose.ui.graphics.vector.ImageVector import io.github.openflocon.domain.database.models.DeviceDataBaseId +@Immutable data class DeviceSharedPrefUiModel( val id: DeviceDataBaseId, val name: String, val type: String?, + // depends on the type, generic for a custom or unknown one + val typeIcon: ImageVector, ) fun previewDeviceSharedPrefUiModel(id: String = "id") = DeviceSharedPrefUiModel( id = id, name = "sharedPref_$id", type = "SharedPreferences", + typeIcon = Icons.Outlined.Android, ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceSelector.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceSelector.kt index 4bd66da32..99e095ebd 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceSelector.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceSelector.kt @@ -60,6 +60,7 @@ internal fun SharedPreferenceSelector( is SharedPrefsStateUiModel.WithContent -> FloconPageSelector( value = state.selected.name, + icon = state.selected.typeIcon, caption = state.selected.type, onClick = onClick, ) @@ -89,6 +90,7 @@ internal fun SharedPreferenceSelector( filteredItems.fastForEach { preference -> FloconDropdownMenuItem( text = preference.name, + leadingIcon = preference.typeIcon, onClick = { onSharedPrefSelected(preference) expanded = false diff --git a/docs/sharedpref.md b/docs/sharedpref.md index 93896d8a6..b13095dc4 100644 --- a/docs/sharedpref.md +++ b/docs/sharedpref.md @@ -90,7 +90,7 @@ floconRegisterPreference(MyCustomPreference()) ### Preference type -Each preference tells the desktop app which storage it comes from. The type is displayed next to its name in the preference selector (and returned by the MCP `list_shared_preferences` tool). +Each preference tells the desktop app which storage it comes from. The type is displayed with its icon next to the name in the preference selector (known types have their own icon, the others a generic one) (and returned by the MCP `list_shared_preferences` tool). The built-in preferences set it for you. For your own `FloconPreference`, override `type` with one of the `FloconPreferenceType` constants or any free value (defaults to `"Custom"`): diff --git a/docs2/sharedpref.md b/docs2/sharedpref.md index f68b0c609..002321a50 100644 --- a/docs2/sharedpref.md +++ b/docs2/sharedpref.md @@ -112,7 +112,7 @@ Flocon.preferencesPlugin.register(MyCustomPreference()) ### Preference type -Each preference tells the desktop app which storage it comes from. The type is displayed next to its name in the preference selector (and returned by the MCP `list_shared_preferences` tool). +Each preference tells the desktop app which storage it comes from. The type is displayed with its icon next to the name in the preference selector (known types have their own icon, the others a generic one) (and returned by the MCP `list_shared_preferences` tool). The built-in preferences set it for you. For your own `FloconPreference`, override `type` with one of the `FloconPreferenceType` constants or any free value (defaults to `"Custom"`): From 0ca0bbbf22ef7970983534627bd11a1db1bee080 Mon Sep 17 00:00:00 2001 From: Florent Champigny Date: Fri, 30 Oct 2026 09:40:27 +0100 Subject: [PATCH 3/6] feat: [PREFERENCES] Tinted glyph per preference type in the selector --- .../features/sharedpreferences/Mapper.kt | 28 ++-- .../model/DeviceSharedPrefUiModel.kt | 8 +- .../model/PreferenceTypeUiModel.kt | 12 ++ .../view/PreferenceTypeIcon.kt | 128 ++++++++++++++++++ .../view/SharedPreferenceSelector.kt | 4 +- .../designsystem/components/FloconDropdown.kt | 2 + .../designsystem/components/FloconMenuItem.kt | 9 +- .../components/FloconPageSelector.kt | 7 +- .../components/FloconTintedIcon.kt | 56 ++++++++ 9 files changed, 223 insertions(+), 31 deletions(-) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/PreferenceTypeUiModel.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/PreferenceTypeIcon.kt create mode 100644 FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconTintedIcon.kt diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/Mapper.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/Mapper.kt index 51d79a7de..89ba76eb1 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/Mapper.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/Mapper.kt @@ -1,17 +1,9 @@ package io.github.openflocon.flocondesktop.features.sharedpreferences -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Android -import androidx.compose.material.icons.outlined.Coffee -import androidx.compose.material.icons.outlined.Key -import androidx.compose.material.icons.outlined.Language -import androidx.compose.material.icons.outlined.PhoneIphone -import androidx.compose.material.icons.outlined.Storage -import androidx.compose.material.icons.outlined.Tune -import androidx.compose.ui.graphics.vector.ImageVector 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) { @@ -44,16 +36,16 @@ internal fun DeviceSharedPreferenceDomainModel.toUi(): DeviceSharedPrefUiModel = id = id, name = name, type = type, - typeIcon = preferenceTypeIcon(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 preferenceTypeIcon(type: String?): ImageVector = when (type?.lowercase()) { - "sharedpreferences" -> Icons.Outlined.Android - "datastore" -> Icons.Outlined.Storage - "userdefaults" -> Icons.Outlined.PhoneIphone - "keychain" -> Icons.Outlined.Key - "java preferences" -> Icons.Outlined.Coffee - "localstorage" -> Icons.Outlined.Language - else -> Icons.Outlined.Tune +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 } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/DeviceSharedPrefUiModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/DeviceSharedPrefUiModel.kt index 5c25d0741..20848c9fd 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/DeviceSharedPrefUiModel.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/DeviceSharedPrefUiModel.kt @@ -1,9 +1,6 @@ package io.github.openflocon.flocondesktop.features.sharedpreferences.model -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Android import androidx.compose.runtime.Immutable -import androidx.compose.ui.graphics.vector.ImageVector import io.github.openflocon.domain.database.models.DeviceDataBaseId @Immutable @@ -11,13 +8,12 @@ data class DeviceSharedPrefUiModel( val id: DeviceDataBaseId, val name: String, val type: String?, - // depends on the type, generic for a custom or unknown one - val typeIcon: ImageVector, + val typeKind: PreferenceTypeUiModel, ) fun previewDeviceSharedPrefUiModel(id: String = "id") = DeviceSharedPrefUiModel( id = id, name = "sharedPref_$id", type = "SharedPreferences", - typeIcon = Icons.Outlined.Android, + typeKind = PreferenceTypeUiModel.SharedPreferences, ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/PreferenceTypeUiModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/PreferenceTypeUiModel.kt new file mode 100644 index 000000000..87a1cafe1 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/model/PreferenceTypeUiModel.kt @@ -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, +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/PreferenceTypeIcon.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/PreferenceTypeIcon.kt new file mode 100644 index 000000000..3cf821591 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/PreferenceTypeIcon.kt @@ -0,0 +1,128 @@ +package io.github.openflocon.flocondesktop.features.sharedpreferences.view + +import androidx.compose.foundation.layout.Row +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.addPathNodes +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import io.github.openflocon.flocondesktop.features.sharedpreferences.model.PreferenceTypeUiModel +import io.github.openflocon.library.designsystem.FloconTheme +import io.github.openflocon.library.designsystem.components.FloconTintedIcon + +@Composable +internal fun PreferenceTypeIcon( + type: PreferenceTypeUiModel, + modifier: Modifier = Modifier, + size: Dp = 22.dp, +) { + val colors = FloconTheme.colors + val (icon, color) = when (type) { + PreferenceTypeUiModel.SharedPreferences -> SharedPreferencesIcon to colors.catTable + PreferenceTypeUiModel.DataStore -> DataStoreIcon to colors.catDatabase + PreferenceTypeUiModel.UserDefaults -> UserDefaultsIcon to colors.catNetwork + PreferenceTypeUiModel.Keychain -> KeychainIcon to colors.accent + PreferenceTypeUiModel.JavaPreferences -> JavaPreferencesIcon to colors.catAccessibility + PreferenceTypeUiModel.LocalStorage -> LocalStorageIcon to colors.catAnalytics + PreferenceTypeUiModel.Custom -> CustomIcon to colors.catAdb + } + FloconTintedIcon( + imageVector = icon, + color = color, + size = size, + modifier = modifier, + ) +} + +// line glyphs on a 24x24 grid, stroked (the tint is applied by the icon) +private fun strokeIcon(name: String, vararg paths: String): ImageVector = ImageVector.Builder( + name = name, + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f, +).apply { + paths.forEach { path -> + addPath( + pathData = addPathNodes(path), + fill = null, + stroke = SolidColor(Color.Black), + strokeLineWidth = 2f, + strokeLineCap = StrokeCap.Round, + strokeLineJoin = StrokeJoin.Round, + ) + } +}.build() + +// key / value list +private val SharedPreferencesIcon = strokeIcon( + "SharedPreferences", + "M3.5 6a1.5 1.5 0 1 0 3 0a1.5 1.5 0 1 0 -3 0", + "M3.5 12a1.5 1.5 0 1 0 3 0a1.5 1.5 0 1 0 -3 0", + "M3.5 18a1.5 1.5 0 1 0 3 0a1.5 1.5 0 1 0 -3 0", + "M10 6h10M10 12h10M10 18h7", +) + +// database +private val DataStoreIcon = strokeIcon( + "DataStore", + "M5 5a7 3 0 1 0 14 0a7 3 0 1 0 -14 0", + "M5 5v14c0 1.7 3.1 3 7 3s7-1.3 7-3V5", + "M5 12c0 1.7 3.1 3 7 3s7-1.3 7-3", +) + +// settings sliders +private val UserDefaultsIcon = strokeIcon( + "UserDefaults", + "M4 7h10M18 7h2M4 17h4M12 17h8", + "M14 7a2 2 0 1 0 4 0a2 2 0 1 0 -4 0", + "M8 17a2 2 0 1 0 4 0a2 2 0 1 0 -4 0", +) + +// key +private val KeychainIcon = strokeIcon( + "Keychain", + "M4 15a4 4 0 1 0 8 0a4 4 0 1 0 -8 0", + "M10.8 12.2L19 4M16 7l2 2M14 9l2 2", +) + +// coffee cup +private val JavaPreferencesIcon = strokeIcon( + "JavaPreferences", + "M4 9h12v5a5 5 0 0 1-5 5H9a5 5 0 0 1-5-5z", + "M16 11h1.5a2.5 2.5 0 0 1 0 5H16", + "M8 3v3M12 3v3", +) + +// browser window +private val LocalStorageIcon = strokeIcon( + "LocalStorage", + "M5 4h14a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", + "M3 9h18", + "M6.5 6.5h0.01M9 6.5h0.01", +) + +// shapes: any other storage +private val CustomIcon = strokeIcon( + "Custom", + "M5 4h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1z", + "M14 7a3 3 0 1 0 6 0a3 3 0 1 0 -6 0", + "M7 14l3.5 6h-7z", + "M14 17a3 3 0 1 0 6 0a3 3 0 1 0 -6 0", +) + +@Preview +@Composable +private fun PreferenceTypeIconPreview() { + FloconTheme { + Row { + PreferenceTypeUiModel.entries.forEach { PreferenceTypeIcon(it) } + } + } +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceSelector.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceSelector.kt index 99e095ebd..fe8687105 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceSelector.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceSelector.kt @@ -60,7 +60,7 @@ internal fun SharedPreferenceSelector( is SharedPrefsStateUiModel.WithContent -> FloconPageSelector( value = state.selected.name, - icon = state.selected.typeIcon, + leadingContent = { PreferenceTypeIcon(state.selected.typeKind) }, caption = state.selected.type, onClick = onClick, ) @@ -90,7 +90,7 @@ internal fun SharedPreferenceSelector( filteredItems.fastForEach { preference -> FloconDropdownMenuItem( text = preference.name, - leadingIcon = preference.typeIcon, + leadingContent = { PreferenceTypeIcon(preference.typeKind) }, onClick = { onSharedPrefSelected(preference) expanded = false diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconDropdown.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconDropdown.kt index e215ed3d2..bcbfdbd0f 100644 --- a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconDropdown.kt +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconDropdown.kt @@ -105,6 +105,7 @@ fun FloconDropdownMenuItem( secondaryAction: (@Composable () -> Unit)? = null, leadingIcon: ImageVector? = null, trailingIcon: ImageVector? = null, + leadingContent: (@Composable () -> Unit)? = null, ) { FloconMenuItem( text = text, @@ -113,6 +114,7 @@ fun FloconDropdownMenuItem( secondaryAction = secondaryAction, leadingIcon = leadingIcon, trailingIcon = trailingIcon, + leadingContent = leadingContent, ) } diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconMenuItem.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconMenuItem.kt index deb307e5e..fa1814eca 100644 --- a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconMenuItem.kt +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconMenuItem.kt @@ -37,7 +37,8 @@ fun FloconMenuItem( modifier: Modifier = Modifier, secondaryAction: (@Composable () -> Unit)? = null, leadingIcon: ImageVector? = null, - trailingIcon: ImageVector? = null + trailingIcon: ImageVector? = null, + leadingContent: (@Composable () -> Unit)? = null, ) { val interaction = remember { MutableInteractionSource() } @@ -52,8 +53,10 @@ fun FloconMenuItem( ) .padding(horizontal = FloconTheme.spacing.small) ) { - leadingIcon?.let { FloconMenuIcon(leadingIcon) } ?: run { - Spacer(Modifier.width(FloconTheme.spacing.extraSmall)) + when { + leadingContent != null -> leadingContent() + leadingIcon != null -> FloconMenuIcon(leadingIcon) + else -> Spacer(Modifier.width(FloconTheme.spacing.extraSmall)) } MenuItemText(text) secondaryAction?.invoke() diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconPageSelector.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconPageSelector.kt index 74adac20c..ada25ee91 100644 --- a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconPageSelector.kt +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconPageSelector.kt @@ -29,7 +29,7 @@ private val SelectorChevronSize = 16.dp /** * Selector button of a page ([FloconPageTopBar] `selector` slot): same height and shape as the filter field, * a subtle 1dp border over a translucent light fill (a bit darker than the filter field), then an optional leading - * [icon], an optional faint [caption], the [value] in semibold and a chevron. [onClick] usually opens a dropdown menu + * [icon] (or a custom [leadingContent], e.g. a [FloconTintedIcon]), an optional faint [caption], the [value] in semibold and a chevron. [onClick] usually opens a dropdown menu * anchored on it. */ @Composable @@ -39,6 +39,7 @@ fun FloconPageSelector( modifier: Modifier = Modifier, icon: ImageVector? = null, caption: String? = null, + leadingContent: (@Composable () -> Unit)? = null, ) { val colors = FloconTheme.colors val shape = FloconTheme.shapes.medium @@ -54,7 +55,9 @@ fun FloconPageSelector( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(FloconTheme.spacing.small), ) { - if (icon != null) { + if (leadingContent != null) { + leadingContent() + } else if (icon != null) { FloconIcon( imageVector = icon, modifier = Modifier.size(SelectorIconSize), diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconTintedIcon.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconTintedIcon.kt new file mode 100644 index 000000000..29ecac4d9 --- /dev/null +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconTintedIcon.kt @@ -0,0 +1,56 @@ +package io.github.openflocon.library.designsystem.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Storage +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import io.github.openflocon.library.designsystem.FloconTheme + +private const val TileAlpha = 0.16f + +/** + * An [imageVector] tinted with [color], on a rounded tile of the same [color] (translucent). + * Tells kinds of items apart at a glance, e.g. the storage of a preference in a selector. + */ +@Composable +fun FloconTintedIcon( + imageVector: ImageVector, + color: Color, + modifier: Modifier = Modifier, + size: Dp = 22.dp, +) { + Box( + modifier = modifier + .size(size) + .clip(FloconTheme.shapes.small) + .background(color.copy(alpha = TileAlpha)), + contentAlignment = Alignment.Center, + ) { + FloconIcon( + imageVector = imageVector, + tint = color, + modifier = Modifier.size(size * 0.6f), + ) + } +} + +@Preview +@Composable +private fun FloconTintedIconPreview() { + FloconTheme { + FloconTintedIcon( + imageVector = Icons.Outlined.Storage, + color = FloconTheme.colors.catDatabase, + ) + } +} From 650c1f08ed77ef94904f34bea6d1b90bb3e8d92e Mon Sep 17 00:00:00 2001 From: Florent Champigny Date: Fri, 30 Oct 2026 09:42:07 +0100 Subject: [PATCH 4/6] fix: [PREFERENCES] Tighter start padding before the type tile in the selector --- .../library/designsystem/components/FloconPageSelector.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconPageSelector.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconPageSelector.kt index ada25ee91..822af4ed8 100644 --- a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconPageSelector.kt +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconPageSelector.kt @@ -51,7 +51,11 @@ fun FloconPageSelector( .background(colors.bg300.copy(alpha = colors.bg300.alpha * 0.55f)) .border(width = 1.dp, color = colors.line, shape = shape) .clickable(onClick = onClick) - .padding(horizontal = FloconTheme.spacing.medium), + .padding( + // a leading tile carries its own background: keep the same inset around it as above and below + start = if (leadingContent != null) FloconTheme.spacing.extraSmall else FloconTheme.spacing.medium, + end = FloconTheme.spacing.medium, + ), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(FloconTheme.spacing.small), ) { From 7b61d133b72063b6b855e78cd84e3993bf25c46d Mon Sep 17 00:00:00 2001 From: Florent Champigny Date: Fri, 30 Oct 2026 09:44:08 +0100 Subject: [PATCH 5/6] fix: [PREFERENCES] Tighter end padding in the selector with a type tile --- .../library/designsystem/components/FloconPageSelector.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconPageSelector.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconPageSelector.kt index 822af4ed8..36baf7f62 100644 --- a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconPageSelector.kt +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconPageSelector.kt @@ -52,9 +52,10 @@ fun FloconPageSelector( .border(width = 1.dp, color = colors.line, shape = shape) .clickable(onClick = onClick) .padding( - // a leading tile carries its own background: keep the same inset around it as above and below + // a leading tile carries its own background: keep the same inset around it as above and below, + // and balance the end (the chevron glyph already has some inner space) start = if (leadingContent != null) FloconTheme.spacing.extraSmall else FloconTheme.spacing.medium, - end = FloconTheme.spacing.medium, + end = if (leadingContent != null) FloconTheme.spacing.compact else FloconTheme.spacing.medium, ), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(FloconTheme.spacing.small), From 6a5cbe2f99ccae03fa5fbaa0bf734a413622a101 Mon Sep 17 00:00:00 2001 From: Florent Champigny Date: Fri, 30 Oct 2026 10:04:41 +0100 Subject: [PATCH 6/6] docs: [PREFERENCES] Keep docs/sharedpref.md as on main --- docs/sharedpref.md | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/docs/sharedpref.md b/docs/sharedpref.md index b13095dc4..c5eddca9a 100644 --- a/docs/sharedpref.md +++ b/docs/sharedpref.md @@ -70,7 +70,6 @@ You can expose any proprietary storage system (such as EncryptedSharedPreference ```kotlin class MyCustomPreference : FloconPreference { override val name: String = "Encrypted Vault" - override val type: String = "EncryptedSharedPreferences" override suspend fun columns(): List = listOf("token", "is_authenticated") @@ -84,25 +83,4 @@ class MyCustomPreference : FloconPreference { } floconRegisterPreference(MyCustomPreference()) -``` - ---- - -### Preference type - -Each preference tells the desktop app which storage it comes from. The type is displayed with its icon next to the name in the preference selector (known types have their own icon, the others a generic one) (and returned by the MCP `list_shared_preferences` tool). - -The built-in preferences set it for you. For your own `FloconPreference`, override `type` with one of the `FloconPreferenceType` constants or any free value (defaults to `"Custom"`): - -| Constant | Value | Platform | -|---|---|---| -| `FloconPreferenceType.SharedPreferences` | `SharedPreferences` | Android | -| `FloconPreferenceType.DataStore` | `DataStore` | Android, iOS, JVM | -| `FloconPreferenceType.UserDefaults` | `UserDefaults` | iOS | -| `FloconPreferenceType.Keychain` | `Keychain` | iOS | -| `FloconPreferenceType.JavaPreferences` | `Java Preferences` | JVM | -| `FloconPreferenceType.LocalStorage` | `LocalStorage` | Wasm | -| `FloconPreferenceType.Custom` | `Custom` | default | - -!!! note "Swift" - Kotlin default getters are not inherited by Swift classes: a Swift class implementing `FloconPreference` must declare `type`. +``` \ No newline at end of file