Skip to content

Commit 2f12d13

Browse files
committed
Saveable/Reactive Preferences
1 parent 8ed2d1b commit 2f12d13

File tree

3 files changed

+50
-30
lines changed

3 files changed

+50
-30
lines changed

app/src/processing/app/Preferences.kt

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ package processing.app
22

33
import androidx.compose.runtime.*
44
import kotlinx.coroutines.Dispatchers
5+
import kotlinx.coroutines.FlowPreview
6+
import kotlinx.coroutines.flow.debounce
7+
import kotlinx.coroutines.flow.dropWhile
58
import kotlinx.coroutines.launch
69
import java.io.File
710
import java.io.InputStream
11+
import java.io.OutputStream
812
import java.nio.file.*
913
import java.util.Properties
1014

@@ -15,35 +19,58 @@ const val DEFAULTS_FILE_NAME = "defaults.txt"
1519
fun PlatformStart(){
1620
Platform.inst ?: Platform.init()
1721
}
22+
class ReactiveProperties: Properties() {
23+
val _stateMap = mutableStateMapOf<String, String>()
1824

25+
override fun setProperty(key: String, value: String) {
26+
super.setProperty(key, value)
27+
_stateMap[key] = value
28+
}
29+
30+
override fun getProperty(key: String): String? {
31+
return _stateMap[key] ?: super.getProperty(key)
32+
}
33+
34+
operator fun get(key: String): String? = getProperty(key)
35+
}
36+
val LocalPreferences = compositionLocalOf<ReactiveProperties> { error("No preferences provided") }
37+
@OptIn(FlowPreview::class)
1938
@Composable
20-
fun loadPreferences(): Properties{
39+
fun PreferencesProvider(content: @Composable () -> Unit){
2140
PlatformStart()
2241

2342
val settingsFolder = Platform.getSettingsFolder()
2443
val preferencesFile = settingsFolder.resolve(PREFERENCES_FILE_NAME)
2544

26-
if(!preferencesFile.exists()){
27-
preferencesFile.createNewFile()
28-
}
29-
watchFile(preferencesFile)
30-
var update by remember { mutableStateOf(System.currentTimeMillis()) }
45+
val update = watchFile(preferencesFile)
46+
val properties = remember(preferencesFile, update) { ReactiveProperties().apply {
47+
load((ClassLoader.getSystemResourceAsStream(DEFAULTS_FILE_NAME)?: InputStream.nullInputStream()).reader(Charsets.UTF_8))
48+
load(preferencesFile.inputStream().reader(Charsets.UTF_8))
49+
}}
3150

32-
// TODO: Make observable when preferences change
33-
// TODO: Save to file when preferences change
34-
class ObservableProperties : Properties() {
35-
override fun setProperty(key: String, value: String): Any? {
36-
update = System.currentTimeMillis()
37-
return super.setProperty(key, value)
38-
}
51+
val initialState = remember(properties) { properties._stateMap.toMap() }
52+
53+
LaunchedEffect(properties) {
54+
snapshotFlow { properties._stateMap.toMap() }
55+
.dropWhile { it == initialState }
56+
.debounce(1000)
57+
.collect {
58+
preferencesFile.outputStream().use { output ->
59+
output.write(
60+
properties.entries
61+
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.key.toString() })
62+
.joinToString("\n") { (key, value) -> "$key=$value" }
63+
.toByteArray()
64+
)
65+
}
66+
}
3967
}
4068

41-
return ObservableProperties().apply {
42-
load(ClassLoader.getSystemResourceAsStream(DEFAULTS_FILE_NAME) ?: InputStream.nullInputStream())
43-
load(preferencesFile.inputStream())
69+
CompositionLocalProvider(LocalPreferences provides properties){
70+
content()
4471
}
45-
}
4672

73+
}
4774
@Composable
4875
fun watchFile(file: File): Any? {
4976
val scope = rememberCoroutineScope()
@@ -72,12 +99,4 @@ fun watchFile(file: File): Any? {
7299
}
73100
}
74101
return event
75-
}
76-
val LocalPreferences = compositionLocalOf<Properties> { error("No preferences provided") }
77-
@Composable
78-
fun PreferencesProvider(content: @Composable () -> Unit){
79-
val preferences = loadPreferences()
80-
CompositionLocalProvider(LocalPreferences provides preferences){
81-
content()
82-
}
83102
}

app/src/processing/app/contrib/ui/ContributionManager.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ import androidx.compose.ui.window.application
2222
import com.charleskorn.kaml.Yaml
2323
import com.charleskorn.kaml.YamlConfiguration
2424
import kotlinx.serialization.Serializable
25+
import processing.app.LocalPreferences
2526
import processing.app.Platform
26-
import processing.app.loadPreferences
27+
import processing.app.ReactiveProperties
2728
import java.net.URL
2829
import java.util.*
2930
import javax.swing.JFrame
@@ -106,7 +107,7 @@ fun contributionsManager(){
106107
var localContributions by remember { mutableStateOf(listOf<Contribution>()) }
107108
var error by remember { mutableStateOf<Exception?>(null) }
108109

109-
val preferences = loadPreferences()
110+
val preferences = LocalPreferences.current
110111

111112
LaunchedEffect(preferences){
112113
try {
@@ -284,9 +285,9 @@ fun contributionsManager(){
284285
}
285286

286287

287-
fun loadContributionProperties(preferences: Properties): List<Pair<Type, Properties>>{
288+
fun loadContributionProperties(preferences: ReactiveProperties): List<Pair<Type, Properties>>{
288289
val result = mutableListOf<Pair<Type, Properties>>()
289-
val sketchBook = Path(preferences.getProperty("sketchbook.path.four", Platform.getDefaultSketchbookFolder().path))
290+
val sketchBook = Path(preferences.getProperty("sketchbook.path.four") ?: Platform.getDefaultSketchbookFolder().path)
290291
sketchBook.forEachDirectoryEntry{ contributionsFolder ->
291292
if(!contributionsFolder.isDirectory()) return@forEachDirectoryEntry
292293
val typeName = contributionsFolder.fileName.toString()

app/src/processing/app/ui/theme/Theme.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import java.util.Properties
1616
class Theme(themeFile: String? = "") : Properties() {
1717
init {
1818
load(ClassLoader.getSystemResourceAsStream("theme.txt"))
19-
load(ClassLoader.getSystemResourceAsStream(themeFile) ?: InputStream.nullInputStream())
19+
load(ClassLoader.getSystemResourceAsStream(themeFile ?: "") ?: InputStream.nullInputStream())
2020
}
2121
fun getColor(key: String): Color {
2222
return Color(getProperty(key).toColorInt())

0 commit comments

Comments
 (0)