Skip to content

Commit 00d4898

Browse files
committed
[UI] Implement Save File Dialog (currently desktop-only)
Fix dialog robustness on Mac fix #61
1 parent 579feee commit 00d4898

File tree

8 files changed

+159
-24
lines changed

8 files changed

+159
-24
lines changed

composeApp/src/androidMain/kotlin/org/dots/game/PlatformUtils.android.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,19 @@ actual suspend fun downloadFileText(fileUrl: String): String = URI.create(fileUr
5757
it.readBytes().decodeToString()
5858
}
5959

60+
@Composable
61+
actual fun SaveFileDialog(
62+
title: String?,
63+
selectedFile: String?,
64+
extension: String,
65+
onFileSelected: (String?) -> Unit,
66+
content: String
67+
) {
68+
}
69+
6070
@Composable
6171
actual fun OpenFileDialog(
62-
title: String,
72+
title: String?,
6373
selectedFile: String?,
6474
allowedExtensions: List<String>,
6575
onFileSelected: (String?) -> Unit
@@ -146,4 +156,4 @@ private object AndroidPickedFiles {
146156
fun exists(path: String): Boolean = nameToUri.containsKey(path)
147157
}
148158

149-
actual val platform: String = "android"
159+
actual val platform: String = "android"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.dots.game.views
2+
3+
import androidx.compose.runtime.Composable
4+
import org.dots.game.KataGoDotsEngine
5+
import org.dots.game.KataGoDotsSettings
6+
7+
@Composable
8+
actual fun KataGoDotsSettingsForm(
9+
kataGoDotsSettings: KataGoDotsSettings,
10+
onSettingsChange: (KataGoDotsEngine) -> Unit,
11+
onDismiss: () -> Unit
12+
) {
13+
}

composeApp/src/commonMain/kotlin/org/dots/game/App.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,13 +192,20 @@ fun App(currentGameSettings: CurrentGameSettings = loadClassSettings(CurrentGame
192192
SaveDialog(
193193
games,
194194
getField(),
195+
currentGameSettings.path,
195196
dumpParameters,
196197
uiSettings,
197-
onDismiss = {
198+
onDismiss = { newDumpParameters, newPath ->
198199
showSaveGameDialog = false
199200
focusRequester.requestFocus()
200-
dumpParameters = it
201-
saveClassSettings(it)
201+
dumpParameters = newDumpParameters
202+
saveClassSettings(newDumpParameters)
203+
if (newPath != null) {
204+
openGameSettings = openGameSettings.copy(pathOrContent = newPath)
205+
saveClassSettings(openGameSettings)
206+
currentGameSettings.path = newPath
207+
saveClassSettings(currentGameSettings, games)
208+
}
202209
})
203210
}
204211

composeApp/src/commonMain/kotlin/org/dots/game/PlatformUtils.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,21 @@ expect fun fileExists(filePath: String): Boolean
1616

1717
expect suspend fun downloadFileText(fileUrl: String): String
1818

19+
@Composable
20+
expect fun SaveFileDialog(
21+
title: String?,
22+
selectedFile: String?,
23+
extension: String,
24+
onFileSelected: (String?) -> Unit,
25+
content: String,
26+
)
27+
1928
@Composable
2029
expect fun OpenFileDialog(
21-
title: String = "Open File",
30+
title: String?,
2231
selectedFile: String?,
2332
allowedExtensions: List<String> = emptyList(),
24-
onFileSelected: (String?) -> Unit
33+
onFileSelected: (String?) -> Unit,
2534
)
2635

2736
expect val platform: String

composeApp/src/commonMain/kotlin/org/dots/game/views/SaveDialog.kt

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import androidx.compose.ui.text.TextStyle
1010
import androidx.compose.ui.text.font.FontFamily
1111
import androidx.compose.ui.unit.dp
1212
import androidx.compose.ui.window.Dialog
13+
import org.dots.game.SaveFileDialog
1314
import org.dots.game.UiSettings
1415
import org.dots.game.core.Field
1516
import org.dots.game.core.Games
@@ -20,9 +21,10 @@ import org.dots.game.sgf.SgfWriter
2021
fun SaveDialog(
2122
games: Games,
2223
field: Field,
24+
path: String?,
2325
dumpParameters: DumpParameters,
2426
uiSettings: UiSettings,
25-
onDismiss: (DumpParameters) -> Unit,
27+
onDismiss: (dumpParameters: DumpParameters, newPath: String?) -> Unit,
2628
) {
2729
val strings by remember { mutableStateOf(uiSettings.language.getStrings()) }
2830
var minX = field.realWidth - 1
@@ -55,6 +57,35 @@ fun SaveDialog(
5557
var isSgf by remember { mutableStateOf(dumpParameters.isSgf) }
5658
var fieldRepresentation by remember { mutableStateOf("") }
5759

60+
var path by remember { mutableStateOf(path ?: "") }
61+
var showSaveDialog by remember { mutableStateOf(false) }
62+
63+
fun createDumpParameters(): DumpParameters {
64+
return DumpParameters(
65+
printNumbers = printNumbers,
66+
padding = padding,
67+
printCoordinates = printCoordinates,
68+
debugInfo = debugInfo,
69+
isSgf = isSgf
70+
)
71+
}
72+
73+
if (showSaveDialog) {
74+
SaveFileDialog(
75+
title = "Save game to ${if (isSgf) "sgf" else "txt"}",
76+
selectedFile = path,
77+
extension = if (isSgf) "sgf" else "txt",
78+
onFileSelected = {
79+
if (it != null) {
80+
path = it
81+
onDismiss(createDumpParameters(), path)
82+
}
83+
showSaveDialog = false
84+
},
85+
content = fieldRepresentation,
86+
)
87+
}
88+
5889
fun updateFieldRepresentation() {
5990
fieldRepresentation = if (isSgf) {
6091
SgfWriter.write(games)
@@ -66,13 +97,7 @@ fun SaveDialog(
6697
updateFieldRepresentation()
6798

6899
Dialog(onDismissRequest = {
69-
onDismiss(DumpParameters(
70-
printNumbers = printNumbers,
71-
padding = padding,
72-
printCoordinates = printCoordinates,
73-
debugInfo = debugInfo,
74-
isSgf = isSgf
75-
))
100+
onDismiss(createDumpParameters(), null)
76101
}) {
77102
Card(modifier = Modifier.wrapContentHeight()) {
78103
Column(modifier = Modifier.padding(20.dp)) {
@@ -125,6 +150,23 @@ fun SaveDialog(
125150
})
126151
}
127152
}
153+
154+
Row(verticalAlignment = Alignment.CenterVertically) {
155+
Text("Path", Modifier.fillMaxWidth(0.1f))
156+
TextField(path, {
157+
path = it
158+
},
159+
modifier = Modifier.fillMaxWidth(0.8f).padding(vertical = 10.dp),
160+
maxLines = 1,
161+
singleLine = true,
162+
)
163+
Button(
164+
onClick = { showSaveDialog = true },
165+
Modifier.padding(horizontal = 10.dp),
166+
) {
167+
Text("Save")
168+
}
169+
}
128170
}
129171
}
130172
}

composeApp/src/desktopMain/kotlin/org/dots/game/PlatformUtils.desktop.kt

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,41 @@ data class WindowSettings(
107107
}
108108
}
109109

110+
@Composable
111+
actual fun SaveFileDialog(
112+
title: String?,
113+
selectedFile: String?,
114+
extension: String,
115+
onFileSelected: (String?) -> Unit,
116+
content: String,
117+
) {
118+
FileDialog(title, selectedFile, listOf(extension), onFileSelected, content)
119+
}
120+
110121
@Composable
111122
actual fun OpenFileDialog(
112-
title: String,
123+
title: String?,
124+
selectedFile: String?,
125+
allowedExtensions: List<String>,
126+
onFileSelected: (String?) -> Unit,
127+
) {
128+
FileDialog(title, selectedFile, allowedExtensions, onFileSelected, content = null)
129+
}
130+
131+
@Composable
132+
private fun FileDialog(
133+
title: String?,
113134
selectedFile: String?,
114135
allowedExtensions: List<String>,
115-
onFileSelected: (String?) -> Unit
136+
onFileSelected: (String?) -> Unit,
137+
content: String?,
116138
) {
139+
// Save mode doesn't allow multiple extensions
140+
require(content == null || allowedExtensions.size <= 1)
141+
117142
val fileDialog = remember {
118-
FileDialog(null as Frame?, title, FileDialog.LOAD).apply {
143+
val mode = if (content != null) FileDialog.SAVE else FileDialog.LOAD
144+
FileDialog(null as Frame?, title, mode).apply {
119145
if (allowedExtensions.isNotEmpty()) {
120146
// TODO: figure out why it doesn't work on Windows and fix
121147
filenameFilter = FilenameFilter { _, name ->
@@ -127,17 +153,24 @@ actual fun OpenFileDialog(
127153
}
128154
}
129155
}
130-
file = selectedFile
156+
if (selectedFile != null) {
157+
val fileObj = File(selectedFile)
158+
directory = fileObj.parent
159+
file = fileObj.name
160+
}
131161
isVisible = true
132162
}
133163
}
134164

135165
LaunchedEffect(Unit) {
136166
withContext(Dispatchers.IO) {
137167
val selectedFile = fileDialog.file?.let {
138-
File(fileDialog.directory, it).absolutePath
168+
File(fileDialog.directory, it)
169+
}
170+
if (selectedFile != null && content != null) {
171+
selectedFile.writeText(content)
139172
}
140-
onFileSelected(selectedFile)
173+
onFileSelected(selectedFile?.absolutePath)
141174
}
142175
}
143176
}

composeApp/src/nativeMain/kotlin/org/dots/game/PlatformUtils.native.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,19 @@ actual fun fileExists(filePath: String): Boolean = false
2525

2626
actual suspend fun downloadFileText(fileUrl: String): String = error("File downloading by url is not supported")
2727

28+
@Composable
29+
actual fun SaveFileDialog(
30+
title: String?,
31+
selectedFile: String?,
32+
extension: String,
33+
onFileSelected: (String?) -> Unit,
34+
content: String
35+
) {
36+
}
37+
2838
@Composable
2939
actual fun OpenFileDialog(
30-
title: String,
40+
title: String?,
3141
selectedFile: String?,
3242
allowedExtensions: List<String>,
3343
onFileSelected: (String?) -> Unit

composeApp/src/webMain/kotlin/org/dots/game/PlatformUtils.web.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,21 @@ actual suspend fun downloadFileText(fileUrl: String): String {
3838
return response.text().await()
3939
}
4040

41+
@Composable
42+
actual fun SaveFileDialog(
43+
title: String?,
44+
selectedFile: String?,
45+
extension: String,
46+
onFileSelected: (String?) -> Unit,
47+
content: String
48+
) {
49+
// TODO: See https://github.com/KvanTTT/dots-game/issues/65
50+
}
51+
4152
@OptIn(ExperimentalWasmJsInterop::class)
4253
@Composable
4354
actual fun OpenFileDialog(
44-
title: String,
55+
title: String?,
4556
selectedFile: String?,
4657
allowedExtensions: List<String>,
4758
onFileSelected: (String?) -> Unit
@@ -132,4 +143,4 @@ private object WasmVirtualFS {
132143
fun exists(path: String): Boolean = files.containsKey(path)
133144
}
134145

135-
actual val platform: String = "web"
146+
actual val platform: String = "web"

0 commit comments

Comments
 (0)