Saveable/Reactive Preferences

This commit is contained in:
Stef Tervelde
2025-02-07 21:53:49 +01:00
parent 8ed2d1b40a
commit 2f12d13422
3 changed files with 50 additions and 30 deletions
+44 -25
View File
@@ -2,9 +2,13 @@ package processing.app
import androidx.compose.runtime.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.dropWhile
import kotlinx.coroutines.launch
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.nio.file.*
import java.util.Properties
@@ -15,35 +19,58 @@ const val DEFAULTS_FILE_NAME = "defaults.txt"
fun PlatformStart(){
Platform.inst ?: Platform.init()
}
class ReactiveProperties: Properties() {
val _stateMap = mutableStateMapOf<String, String>()
override fun setProperty(key: String, value: String) {
super.setProperty(key, value)
_stateMap[key] = value
}
override fun getProperty(key: String): String? {
return _stateMap[key] ?: super.getProperty(key)
}
operator fun get(key: String): String? = getProperty(key)
}
val LocalPreferences = compositionLocalOf<ReactiveProperties> { error("No preferences provided") }
@OptIn(FlowPreview::class)
@Composable
fun loadPreferences(): Properties{
fun PreferencesProvider(content: @Composable () -> Unit){
PlatformStart()
val settingsFolder = Platform.getSettingsFolder()
val preferencesFile = settingsFolder.resolve(PREFERENCES_FILE_NAME)
if(!preferencesFile.exists()){
preferencesFile.createNewFile()
}
watchFile(preferencesFile)
var update by remember { mutableStateOf(System.currentTimeMillis()) }
val update = watchFile(preferencesFile)
val properties = remember(preferencesFile, update) { ReactiveProperties().apply {
load((ClassLoader.getSystemResourceAsStream(DEFAULTS_FILE_NAME)?: InputStream.nullInputStream()).reader(Charsets.UTF_8))
load(preferencesFile.inputStream().reader(Charsets.UTF_8))
}}
// TODO: Make observable when preferences change
// TODO: Save to file when preferences change
class ObservableProperties : Properties() {
override fun setProperty(key: String, value: String): Any? {
update = System.currentTimeMillis()
return super.setProperty(key, value)
}
val initialState = remember(properties) { properties._stateMap.toMap() }
LaunchedEffect(properties) {
snapshotFlow { properties._stateMap.toMap() }
.dropWhile { it == initialState }
.debounce(1000)
.collect {
preferencesFile.outputStream().use { output ->
output.write(
properties.entries
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.key.toString() })
.joinToString("\n") { (key, value) -> "$key=$value" }
.toByteArray()
)
}
}
}
return ObservableProperties().apply {
load(ClassLoader.getSystemResourceAsStream(DEFAULTS_FILE_NAME) ?: InputStream.nullInputStream())
load(preferencesFile.inputStream())
CompositionLocalProvider(LocalPreferences provides properties){
content()
}
}
@Composable
fun watchFile(file: File): Any? {
val scope = rememberCoroutineScope()
@@ -72,12 +99,4 @@ fun watchFile(file: File): Any? {
}
}
return event
}
val LocalPreferences = compositionLocalOf<Properties> { error("No preferences provided") }
@Composable
fun PreferencesProvider(content: @Composable () -> Unit){
val preferences = loadPreferences()
CompositionLocalProvider(LocalPreferences provides preferences){
content()
}
}
@@ -22,8 +22,9 @@ import androidx.compose.ui.window.application
import com.charleskorn.kaml.Yaml
import com.charleskorn.kaml.YamlConfiguration
import kotlinx.serialization.Serializable
import processing.app.LocalPreferences
import processing.app.Platform
import processing.app.loadPreferences
import processing.app.ReactiveProperties
import java.net.URL
import java.util.*
import javax.swing.JFrame
@@ -106,7 +107,7 @@ fun contributionsManager(){
var localContributions by remember { mutableStateOf(listOf<Contribution>()) }
var error by remember { mutableStateOf<Exception?>(null) }
val preferences = loadPreferences()
val preferences = LocalPreferences.current
LaunchedEffect(preferences){
try {
@@ -284,9 +285,9 @@ fun contributionsManager(){
}
fun loadContributionProperties(preferences: Properties): List<Pair<Type, Properties>>{
fun loadContributionProperties(preferences: ReactiveProperties): List<Pair<Type, Properties>>{
val result = mutableListOf<Pair<Type, Properties>>()
val sketchBook = Path(preferences.getProperty("sketchbook.path.four", Platform.getDefaultSketchbookFolder().path))
val sketchBook = Path(preferences.getProperty("sketchbook.path.four") ?: Platform.getDefaultSketchbookFolder().path)
sketchBook.forEachDirectoryEntry{ contributionsFolder ->
if(!contributionsFolder.isDirectory()) return@forEachDirectoryEntry
val typeName = contributionsFolder.fileName.toString()
+1 -1
View File
@@ -16,7 +16,7 @@ import java.util.Properties
class Theme(themeFile: String? = "") : Properties() {
init {
load(ClassLoader.getSystemResourceAsStream("theme.txt"))
load(ClassLoader.getSystemResourceAsStream(themeFile) ?: InputStream.nullInputStream())
load(ClassLoader.getSystemResourceAsStream(themeFile ?: "") ?: InputStream.nullInputStream())
}
fun getColor(key: String): Color {
return Color(getProperty(key).toColorInt())