Auto-commit after successful build: update app gameplay/UI, improve build setup

Changed files:\napp/build.gradle.kts
app/src/main/java/solutions/tretter/githugandroid/EditorCommands.kt
app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt
app/src/main/java/solutions/tretter/githugandroid/GitRuntime.kt
app/src/main/java/solutions/tretter/githugandroid/TextEditorDialog.kt
app/src/test/java/solutions/tretter/githugandroid/EditorCommandsTest.kt
This commit is contained in:
Joe Tretter
2026-05-02 21:50:34 -05:00
parent d6c4c4a2df
commit 17f19ca0ba
6 changed files with 327 additions and 42 deletions

View File

@@ -60,6 +60,7 @@ fun GitHugApp() {
var historyIndex by remember { mutableStateOf(-1) }
var historyDraft by remember { mutableStateOf("") }
var hasRestoredProgress by remember { mutableStateOf(false) }
var editorState by remember { mutableStateOf<TextEditorState?>(null) }
val currentLevel = levels[currentLevelIndex]
LaunchedEffect(persistedPaneLayout) {
@@ -166,6 +167,7 @@ fun GitHugApp() {
visibleHint = null
historyIndex = -1
historyDraft = ""
editorState = null
applyRecommendedPaneWeights(persist = false)
}
@@ -181,6 +183,7 @@ fun GitHugApp() {
visibleHint = null
historyIndex = -1
historyDraft = ""
editorState = null
paneLayout = paneLayout.copy(
weights = paneLayout.weights + recommendedPaneWeights(
heights = recommendedPaneHeights(
@@ -247,6 +250,87 @@ fun GitHugApp() {
commandInput = TextFieldValue(newText, selection = TextRange(newCursor))
}
fun applyCommandResult(raw: String, newRepo: RepoState, lines: List<String>, echoCommand: Boolean) {
val levelForResult = currentLevel
val solvedAfterCommand = levelForResult.validator(newRepo, raw)
val wasAlreadyCompleted = currentLevel.id in completedLevels
AppLog.d(
"GitHugApp",
"Command='$raw' level=${levelForResult.id} solved=$solvedAfterCommand alreadyCompleted=$wasAlreadyCompleted completedBefore=${completedLevels.sorted()}",
)
val newOutput = buildList {
addAll(output)
if (echoCommand) {
add("$ $raw")
}
addAll(lines)
if (solvedAfterCommand && !wasAlreadyCompleted) {
add("✔ Level solved: ${levelForResult.title}")
}
}
if (solvedAfterCommand && !wasAlreadyCompleted) {
val updatedCompletedLevels = completedLevels + levelForResult.id
completedLevels = updatedCompletedLevels
val nextLevelIndex = levels.indexOfFirst { it.id !in updatedCompletedLevels }
if (nextLevelIndex >= 0) {
val nextLevelId = levels[nextLevelIndex].id
AppLog.d(
"GitHugApp",
"Level solved ${levelForResult.id}; advancing to next incomplete index=$nextLevelIndex id=$nextLevelId",
)
scope.launch { persistProgress(updatedCompletedLevels, nextLevelId) }
loadLevel(nextLevelIndex)
} else {
AppLog.d("GitHugApp", "All levels completed")
scope.launch { persistProgress(updatedCompletedLevels, levels.lastOrNull()?.id) }
repo = newRepo
output = listOf("🏁 All Githug levels completed.")
clearCommandInput()
suppressedImeEcho = null
}
} else {
AppLog.d("GitHugApp", "Staying on level=${levelForResult.id}")
repo = newRepo
output = newOutput
applyRecommendedPaneWeights(persist = false)
}
}
fun openEditor(invocation: VisualEditorInvocation) {
val path = invocation.path.orEmpty()
val (content, lines) = if (path.isBlank()) {
"" to emptyList()
} else {
runtime.readEditorFile(currentLevel, repo, path)
}
output = buildList {
addAll(output)
add("$ ${invocation.command}")
addAll(lines)
if (lines.isEmpty()) {
add("Opened ${invocation.editor} editor${if (path.isBlank()) "" else " for $path"}")
}
}
if (lines.isEmpty()) {
editorState = TextEditorState(
editor = invocation.editor,
originalCommand = invocation.command,
path = path,
content = content,
saveAsPath = path,
)
}
applyRecommendedPaneWeights(persist = false)
}
fun saveEditor(targetPath: String) {
val state = editorState ?: return
if (targetPath.isBlank()) return
val (newRepo, lines) = runtime.writeEditorFile(currentLevel, repo, targetPath, state.content)
editorState = null
applyCommandResult(state.originalCommand, newRepo, lines, echoCommand = false)
}
fun runCommand() {
val submittedText = commandInput.text
val raw = submittedText.trim()
@@ -261,47 +345,14 @@ fun GitHugApp() {
historyIndex = -1
historyDraft = ""
val editorInvocation = parseVisualEditorInvocation(raw)
if (editorInvocation != null) {
openEditor(editorInvocation)
return
}
val (newRepo, lines) = runtime.execute(currentLevel, repo, raw)
val solvedAfterCommand = currentLevel.validator(newRepo, raw)
val wasAlreadyCompleted = currentLevel.id in completedLevels
AppLog.d(
"GitHugApp",
"Command='$raw' level=${currentLevel.id} solved=$solvedAfterCommand alreadyCompleted=$wasAlreadyCompleted completedBefore=${completedLevels.sorted()}",
)
val newOutput = buildList {
addAll(output)
add("$ $raw")
addAll(lines)
if (solvedAfterCommand && !wasAlreadyCompleted) {
add("✔ Level solved: ${currentLevel.title}")
}
}
if (solvedAfterCommand && !wasAlreadyCompleted) {
val updatedCompletedLevels = completedLevels + currentLevel.id
completedLevels = updatedCompletedLevels
val nextLevelIndex = levels.indexOfFirst { it.id !in updatedCompletedLevels }
if (nextLevelIndex >= 0) {
val nextLevelId = levels[nextLevelIndex].id
AppLog.d(
"GitHugApp",
"Level solved ${currentLevel.id}; advancing to next incomplete index=$nextLevelIndex id=$nextLevelId",
)
scope.launch { persistProgress(updatedCompletedLevels, nextLevelId) }
loadLevel(nextLevelIndex)
} else {
AppLog.d("GitHugApp", "All levels completed")
scope.launch { persistProgress(updatedCompletedLevels, levels.lastOrNull()?.id) }
repo = newRepo
output = listOf("🏁 All Githug levels completed.")
clearCommandInput()
suppressedImeEcho = null
}
} else {
AppLog.d("GitHugApp", "Staying on level=${currentLevel.id}")
repo = newRepo
output = newOutput
applyRecommendedPaneWeights(persist = false)
}
applyCommandResult(raw, newRepo, lines, echoCommand = true)
}
fun showCommandHelp() {
@@ -395,5 +446,25 @@ fun GitHugApp() {
}
}
}
editorState?.let { state ->
TextEditorDialog(
state = state,
onContentChange = { editorState = state.copy(content = it) },
onSaveAsPathChange = { editorState = state.copy(saveAsPath = it) },
onCancel = {
output = output + "Editor closed without saving"
editorState = null
},
onSave = { saveEditor(state.path) },
onSaveAs = {
val targetPath = state.saveAsPath.trim()
if (targetPath.isNotEmpty()) {
editorState = state.copy(path = targetPath, saveAsPath = targetPath)
saveEditor(targetPath)
}
},
)
}
}
}