Add native macOS folder picker for staging directory

Uses AWT FileDialog with apple.awt.fileDialogForDirectories=true to open
the native macOS folder chooser. Falls back gracefully (dialog cancelled)
with no change to the text field.
This commit is contained in:
Kyle Bolen
2026-07-28 03:47:31 +00:00
parent 105077baba
commit 05897e6d62

View File

@@ -3,12 +3,16 @@ package com.bolenpad.photophetch.ui
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.FolderOpen
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import java.awt.FileDialog
import java.awt.Frame
import java.io.File
@Composable @Composable
fun SettingsScreen(state: AppState) { fun SettingsScreen(state: AppState) {
@@ -33,14 +37,30 @@ fun SettingsScreen(state: AppState) {
// Staging directory // Staging directory
Text("Staging Directory", style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold) Text("Staging Directory", style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold)
OutlinedTextField( Row(
value = stagingRoot, horizontalArrangement = Arrangement.spacedBy(8.dp),
onValueChange = { stagingRoot = it }, verticalAlignment = Alignment.CenterVertically,
label = { Text("Path to _staging/ directory") }, ) {
placeholder = { Text("/Volumes/delphi/_staging") }, OutlinedTextField(
modifier = Modifier.fillMaxWidth(), value = stagingRoot,
singleLine = true, onValueChange = { stagingRoot = it },
) label = { Text("Path to _staging/ directory") },
placeholder = { Text("/Volumes/delphi/_staging") },
modifier = Modifier.weight(1f),
singleLine = true,
)
OutlinedIconButton(
onClick = {
val chosen = pickFolder(
title = "Select _staging directory",
initialDir = stagingRoot.ifBlank { "/Volumes" },
)
if (chosen != null) stagingRoot = chosen
},
) {
Icon(Icons.Default.FolderOpen, contentDescription = "Browse…")
}
}
Text( Text(
"This should be the _staging directory on your NAS (must be mounted).", "This should be the _staging directory on your NAS (must be mounted).",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
@@ -78,7 +98,6 @@ fun SettingsScreen(state: AppState) {
Button( Button(
onClick = { onClick = {
state.updateStagingRoot(stagingRoot) state.updateStagingRoot(stagingRoot)
// Also save template update
state.saveConfig() state.saveConfig()
}, },
modifier = Modifier.fillMaxWidth().height(52.dp), modifier = Modifier.fillMaxWidth().height(52.dp),
@@ -87,3 +106,24 @@ fun SettingsScreen(state: AppState) {
} }
} }
} }
/**
* Opens the native macOS folder picker (System.setProperty trick to make
* AWT FileDialog show directories instead of files).
* Returns the chosen absolute path, or null if cancelled.
*/
private fun pickFolder(title: String, initialDir: String): String? {
// On macOS, this system property switches FileDialog to directory-selection mode
System.setProperty("apple.awt.fileDialogForDirectories", "true")
return try {
val dialog = FileDialog(null as Frame?, title, FileDialog.LOAD).apply {
directory = initialDir
isVisible = true
}
val dir = dialog.directory
val file = dialog.file
if (file != null) File(dir, file).absolutePath else null
} finally {
System.setProperty("apple.awt.fileDialogForDirectories", "false")
}
}