Skip to content

Commit

Permalink
add java home option and refine some ui
Browse files Browse the repository at this point in the history
  • Loading branch information
eskatos committed Apr 3, 2024
1 parent b4152b7 commit 1ce66f7
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 109 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ Building release distributables will fail if the required JDK tools are not avai
# Run release build type from build installation
./gradlew :gradle-client:runReleaseDistributable
```

To add more actions start from [GetModelAction.kt](./gradle-client/src/jvmMain/kotlin/org/gradle/client/ui/connected/actions/GetModelAction.kt).
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import kotlinx.serialization.Serializable
@Serializable
data class GradleConnectionParameters(
val rootDir: String,
val javaHome: String,
val javaHomeDir: String?,
val gradleUserHomeDir: String?,
val distribution: GradleDistribution,
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.gradle.client.core.util

import java.util.UUID
import java.util.*

fun generateIdentity(): String =
UUID.randomUUID().toString()
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,38 @@ package org.gradle.client.ui.build

import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Folder
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import kotlinx.coroutines.launch
import org.gradle.client.core.gradle.GradleConnectionParameters
import org.gradle.client.core.gradle.GradleDistribution
import org.gradle.client.ui.composables.BackIcon
import org.gradle.client.ui.composables.DirChooserDialog
import org.gradle.client.ui.composables.Loading
import org.gradle.client.ui.composables.PathChooserDialog
import org.gradle.client.ui.composables.PlainTextTooltip
import org.gradle.client.core.gradle.GradleDistribution
import org.gradle.client.ui.theme.plusPaneSpacing
import java.io.File

@Composable
fun BuildContent(component: BuildComponent) {
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
topBar = { TopBar(component) }
topBar = { TopBar(component) },
snackbarHost = { SnackbarHost(snackbarHostState) },
) { scaffoldPadding ->
Surface(modifier = Modifier.padding(scaffoldPadding.plusPaneSpacing())) {
val model by component.model.subscribeAsState()
when (val current = model) {
BuildModel.Loading -> Loading()
is BuildModel.Loaded -> BuildMainContent(component, current)
is BuildModel.Loaded -> BuildMainContent(component, current, snackbarHostState)
}
}
}
Expand All @@ -42,23 +49,46 @@ enum class GradleDistSource(

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun BuildMainContent(component: BuildComponent, model: BuildModel.Loaded) {
private fun BuildMainContent(
component: BuildComponent,
model: BuildModel.Loaded,
snackbarHostState: SnackbarHostState,
) {

var javaHome by remember { mutableStateOf(System.getenv("JAVA_HOME") ?: "") }
var gradleDistSource by remember { mutableStateOf(GradleDistSource.DEFAULT) }
var gradleDistVersion by remember { mutableStateOf("") }
var gradleDistLocalDir by remember { mutableStateOf("") }
val scope = rememberCoroutineScope()

val isJavaHomeValid by derivedStateOf { javaHome.isNotBlank() }
val isGradleDistVersionValid by derivedStateOf { gradleDistVersion.isNotBlank() }
val isGradleDistLocalDirValid by derivedStateOf { gradleDistLocalDir.isNotBlank() }
val isCanConnect by derivedStateOf {
isJavaHomeValid && when (gradleDistSource) {
GradleDistSource.DEFAULT -> true
GradleDistSource.VERSION -> isGradleDistVersionValid
GradleDistSource.LOCAL -> isGradleDistLocalDirValid
var javaHome by rememberSaveable { mutableStateOf("") }
var gradleUserHome by rememberSaveable { mutableStateOf("") }
var gradleDistSource by rememberSaveable { mutableStateOf(GradleDistSource.DEFAULT) }
var gradleDistVersion by rememberSaveable { mutableStateOf("") }
var gradleDistLocalDir by rememberSaveable { mutableStateOf("") }

val isJavaHomeValid by derivedStateOf {
javaHome.isBlank() || File(javaHome).let {
it.isDirectory && it.resolve("bin").listFiles { file ->
file.nameWithoutExtension == "java"
}?.isNotEmpty() ?: false
}
}
val isGradleUserHomeValid by derivedStateOf {
gradleUserHome.isBlank() || File(gradleUserHome).let { !it.exists() || it.isDirectory }
}
val isGradleDistVersionValid by derivedStateOf {
gradleDistVersion.isNotBlank()
}
val isGradleDistLocalDirValid by derivedStateOf {
gradleDistLocalDir.isNotBlank() && File(gradleDistLocalDir).resolve("bin").listFiles { file ->
file.nameWithoutExtension == "gradle"
}?.isNotEmpty() ?: false
}
val isCanConnect by derivedStateOf {
isJavaHomeValid && isGradleUserHomeValid &&
when (gradleDistSource) {
GradleDistSource.DEFAULT -> true
GradleDistSource.VERSION -> isGradleDistVersionValid
GradleDistSource.LOCAL -> isGradleDistLocalDirValid
}
}

Column(
modifier = Modifier.fillMaxWidth(),
Expand All @@ -75,28 +105,77 @@ private fun BuildMainContent(component: BuildComponent, model: BuildModel.Loaded
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = javaHome,
readOnly = false,
onValueChange = { javaHome = it },
label = { Text("Java Home") },
placeholder = { Text(System.getenv("JAVA_HOME") ?: "", color = Color.Gray) },
isError = !isJavaHomeValid,
trailingIcon = {
val helpText = "Select a java executable"
var isPathChooserOpen by remember { mutableStateOf(false) }
if (isPathChooserOpen) {
PathChooserDialog(
val helpText = "Select a Java home"
var isDirChooserOpen by remember { mutableStateOf(false) }
if (isDirChooserOpen) {
DirChooserDialog(
helpText = helpText,
selectableFilter = { path -> path.isFile && path.nameWithoutExtension == "java" },
choiceMapper = { path -> path.parentFile.parentFile },
onPathChosen = { path ->
isPathChooserOpen = false
if (path != null) {
javaHome = path.absolutePath
showHiddenFiles = true,
onDirChosen = { dir ->
isDirChooserOpen = false
if (dir == null) {
scope.launch { snackbarHostState.showSnackbar("No Java home selected") }
} else {
javaHome = dir.absolutePath
}
}
)
}
PlainTextTooltip(helpText) {
IconButton(onClick = { isPathChooserOpen = true }) {
Icon(Icons.Default.Folder, helpText)
Row {
IconButton(
enabled = javaHome.isNotBlank(),
onClick = { javaHome = "" },
content = { Icon(Icons.Default.Clear, "Clear") }
)
PlainTextTooltip(helpText) {
IconButton(onClick = { isDirChooserOpen = true }) {
Icon(Icons.Default.Folder, helpText)
}
}
}
}
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = gradleUserHome,
readOnly = false,
onValueChange = { gradleUserHome = it },
label = { Text("Gradle User Home") },
placeholder = { Text(System.getProperty("user.home") + "/.gradle", color = Color.Gray) },
isError = !isGradleUserHomeValid,
trailingIcon = {
val helpText = "Select a Gradle user home"
var isDirChooserOpen by remember { mutableStateOf(false) }
if (isDirChooserOpen) {
DirChooserDialog(
helpText = helpText,
showHiddenFiles = true,
onDirChosen = { dir ->
isDirChooserOpen = false
if (dir == null) {
scope.launch { snackbarHostState.showSnackbar("No Gradle user home selected") }
} else {
gradleUserHome = dir.absolutePath
}
}
)
}
Row {
IconButton(
enabled = gradleUserHome.isNotBlank(),
onClick = { gradleUserHome = "" },
content = { Icon(Icons.Default.Clear, "Clear") }
)
PlainTextTooltip(helpText) {
IconButton(onClick = { isDirChooserOpen = true }) {
Icon(Icons.Default.Folder, helpText)
}
}
}
}
Expand Down Expand Up @@ -178,24 +257,31 @@ private fun BuildMainContent(component: BuildComponent, model: BuildModel.Loaded
label = { Text("Local installation path") },
isError = !isGradleDistLocalDirValid,
trailingIcon = {
val helpText = "Select a gradle executable"
var isPathChooserOpen by remember { mutableStateOf(false) }
if (isPathChooserOpen) {
PathChooserDialog(
val helpText = "Select a Gradle installation"
var isDirChooserOpen by remember { mutableStateOf(false) }
if (isDirChooserOpen) {
DirChooserDialog(
helpText = helpText,
selectableFilter = { path -> path.isFile && path.nameWithoutExtension == "gradle" },
choiceMapper = { path -> path.parentFile.parentFile },
onPathChosen = { path ->
isPathChooserOpen = false
if (path != null) {
gradleDistLocalDir = path.absolutePath
onDirChosen = { dir ->
isDirChooserOpen = false
if (dir == null) {
scope.launch { snackbarHostState.showSnackbar("No Gradle installation selected") }
} else {
gradleDistLocalDir = dir.absolutePath
}
}
)
}
PlainTextTooltip(helpText) {
IconButton(onClick = { isPathChooserOpen = true }) {
Icon(Icons.Default.Folder, helpText)
Row {
IconButton(
enabled = gradleDistLocalDir.isNotBlank(),
onClick = { gradleDistLocalDir = "" },
content = { Icon(Icons.Default.Clear, "Clear") }
)
PlainTextTooltip(helpText) {
IconButton(onClick = { isDirChooserOpen = true }) {
Icon(Icons.Default.Folder, helpText)
}
}
}
}
Expand All @@ -209,9 +295,10 @@ private fun BuildMainContent(component: BuildComponent, model: BuildModel.Loaded
onClick = {
component.onConnectClicked(
GradleConnectionParameters(
model.build.rootDir.absolutePath,
javaHome,
when (gradleDistSource) {
rootDir = model.build.rootDir.absolutePath,
javaHomeDir = javaHome.takeIf { it.isNotBlank() },
gradleUserHomeDir = gradleUserHome.takeIf { it.isNotBlank() },
distribution = when (gradleDistSource) {
GradleDistSource.DEFAULT -> GradleDistribution.Default
GradleDistSource.VERSION -> GradleDistribution.Version(gradleDistVersion)
GradleDistSource.LOCAL -> GradleDistribution.Local(gradleDistLocalDir)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,21 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import kotlinx.coroutines.launch
import org.gradle.client.core.Constants.APPLICATION_DISPLAY_NAME
import org.gradle.client.core.database.Build
import org.gradle.client.ui.composables.DirChooserDialog
import org.gradle.client.ui.composables.Loading
import org.gradle.client.ui.composables.PathChooserDialog
import org.gradle.client.ui.composables.PlainTextTooltip
import org.gradle.client.ui.theme.plusPaneSpacing

@Composable
fun BuildListContent(component: BuildListComponent) {
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
topBar = { TopBar() },
floatingActionButton = { AddBuildButton(component) },
floatingActionButton = { AddBuildButton(component, snackbarHostState) },
snackbarHost = { SnackbarHost(snackbarHostState) },
) { scaffoldPadding ->
Surface(Modifier.padding(scaffoldPadding.plusPaneSpacing())) {
val model by component.model.subscribeAsState()
Expand Down Expand Up @@ -89,21 +92,25 @@ private fun TopBar() {
}

@Composable
private fun AddBuildButton(component: BuildListComponent) {
var isPathChooserOpen by remember { mutableStateOf(false) }
if (isPathChooserOpen) {
PathChooserDialog(
private fun AddBuildButton(component: BuildListComponent, snackbarHostState: SnackbarHostState) {
val scope = rememberCoroutineScope()
var isDirChooserOpen by remember { mutableStateOf(false) }
if (isDirChooserOpen) {
DirChooserDialog(
helpText = addBuildHelpText,
selectableFilter = { path ->
path.isFile && path.name.startsWith("settings.gradle")
},
choiceMapper = { path ->
path.parentFile
},
onPathChosen = { rootDir ->
isPathChooserOpen = false
if (rootDir != null) {
component.onNewBuildRootDirChosen(rootDir)
onDirChosen = { rootDir ->
isDirChooserOpen = false
if (rootDir == null) {
scope.launch { snackbarHostState.showSnackbar("No build selected") }
} else {
val valid = rootDir.listFiles { file ->
file.name.startsWith("settings.gradle")
}?.isNotEmpty() ?: false
if (!valid) {
scope.launch { snackbarHostState.showSnackbar("Directory is not a Gradle build!") }
} else {
component.onNewBuildRootDirChosen(rootDir)
}
}
}
)
Expand All @@ -112,9 +119,9 @@ private fun AddBuildButton(component: BuildListComponent) {
ExtendedFloatingActionButton(
icon = { Icon(Icons.Default.Add, "") },
text = { Text(text = "Add build", Modifier.testTag("add_build")) },
onClick = { isPathChooserOpen = true },
onClick = { isDirChooserOpen = true },
)
}
}

private const val addBuildHelpText = "Choose a Gradle settings script"
private const val addBuildHelpText = "Choose a Gradle build directory"
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.gradle.client.ui.composables

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import java.io.File
import javax.swing.JFileChooser

@Composable
fun DirChooserDialog(
helpText: String,
showHiddenFiles: Boolean = false,
onDirChosen: (dir: File?) -> Unit,
) {
LaunchedEffect(Unit) {
val chooser = JFileChooser()
chooser.dialogTitle = helpText
chooser.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
chooser.isAcceptAllFileFilterUsed = false
chooser.isMultiSelectionEnabled = false
chooser.isFileHidingEnabled = !showHiddenFiles
if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
onDirChosen(chooser.selectedFile)
} else {
onDirChosen(null)
}
}
}
Loading

0 comments on commit 1ce66f7

Please sign in to comment.