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

Changed files:\napp/build.gradle.kts
app/src/main/java/com/kawomi/githugandroid/GitHugApp.kt
This commit is contained in:
Joe Tretter
2026-04-22 13:13:38 -05:00
parent c93c37da09
commit 75462e784b
2 changed files with 56 additions and 26 deletions

View File

@@ -11,8 +11,8 @@ android {
applicationId = "com.kawomi.githugandroid" applicationId = "com.kawomi.githugandroid"
minSdk = 26 minSdk = 26
targetSdk = 34 targetSdk = 34
versionCode = 5 versionCode = 6
versionName = "0.1.4" versionName = "0.1.5"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@@ -70,7 +70,6 @@ dependencies {
implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.foundation:foundation") implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material:material-icons-extended")
implementation("androidx.compose.material3:material3:1.2.1") implementation("androidx.compose.material3:material3:1.2.1")
implementation("com.google.android.material:material:1.12.0") implementation("com.google.android.material:material:1.12.0")

View File

@@ -8,19 +8,12 @@ import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material.icons.filled.KeyboardTab
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
@@ -39,6 +32,8 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@@ -47,6 +42,8 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
private val AppBackground = Color(0xFF000000) private val AppBackground = Color(0xFF000000)
private val PanelPrimary = Color(0xFF121212) private val PanelPrimary = Color(0xFF121212)
@@ -83,6 +80,7 @@ fun GitHugApp() {
var repo by remember { mutableStateOf(levels.first().setup()) } var repo by remember { mutableStateOf(levels.first().setup()) }
var commandInput by remember { mutableStateOf(TextFieldValue("")) } var commandInput by remember { mutableStateOf(TextFieldValue("")) }
var inputFieldVersion by remember { mutableStateOf(0) } var inputFieldVersion by remember { mutableStateOf(0) }
var suppressedImeEcho by remember { mutableStateOf<String?>(null) }
var output by remember { mutableStateOf(listOf("Welcome to GitHug Android.")) } var output by remember { mutableStateOf(listOf("Welcome to GitHug Android.")) }
var hintIndex by remember { mutableStateOf(0) } var hintIndex by remember { mutableStateOf(0) }
var completedLevels by remember { mutableStateOf(setOf<String>()) } var completedLevels by remember { mutableStateOf(setOf<String>()) }
@@ -91,20 +89,21 @@ fun GitHugApp() {
var historyDraft by remember { mutableStateOf("") } var historyDraft by remember { mutableStateOf("") }
val currentLevel = levels[currentLevelIndex] val currentLevel = levels[currentLevelIndex]
val solved = currentLevel.validator(repo)
LaunchedEffect(currentLevelIndex) { LaunchedEffect(currentLevelIndex) {
screenScrollState.scrollTo(0) screenScrollState.scrollTo(0)
} }
fun clearCommandInput() { fun clearCommandInput(recreateField: Boolean = false) {
commandInput = TextFieldValue(text = "", selection = TextRange.Zero) commandInput = TextFieldValue(text = "", selection = TextRange.Zero)
if (recreateField) {
inputFieldVersion += 1 inputFieldVersion += 1
} }
}
fun resetCurrentLevel(message: String = "Level reset.") { fun resetCurrentLevel(message: String = "Level reset.") {
repo = currentLevel.setup() repo = currentLevel.setup()
clearCommandInput() clearCommandInput()
suppressedImeEcho = null
output = listOf(message) output = listOf(message)
hintIndex = 0 hintIndex = 0
historyIndex = -1 historyIndex = -1
@@ -164,10 +163,12 @@ fun GitHugApp() {
} }
fun runCommand() { fun runCommand() {
val raw = commandInput.text.trim() val submittedText = commandInput.text
val raw = submittedText.trim()
if (raw.isBlank()) return if (raw.isBlank()) return
clearCommandInput() suppressedImeEcho = submittedText
clearCommandInput(recreateField = true)
if (commandHistory.lastOrNull() != raw) { if (commandHistory.lastOrNull() != raw) {
commandHistory = commandHistory + raw commandHistory = commandHistory + raw
@@ -196,11 +197,13 @@ fun GitHugApp() {
repo = nextLevel.setup() repo = nextLevel.setup()
output = listOf("Loaded level: ${nextLevel.title}") output = listOf("Loaded level: ${nextLevel.title}")
clearCommandInput() clearCommandInput()
suppressedImeEcho = null
hintIndex = 0 hintIndex = 0
} else { } else {
repo = newRepo repo = newRepo
output = listOf("🏁 All available MVP levels completed.") output = listOf("🏁 All available MVP levels completed.")
clearCommandInput() clearCommandInput()
suppressedImeEcho = null
} }
} else { } else {
repo = newRepo repo = newRepo
@@ -227,6 +230,7 @@ fun GitHugApp() {
repo = levels[index].setup() repo = levels[index].setup()
output = listOf("Loaded level: ${levels[index].title}") output = listOf("Loaded level: ${levels[index].title}")
clearCommandInput() clearCommandInput()
suppressedImeEcho = null
hintIndex = 0 hintIndex = 0
historyIndex = -1 historyIndex = -1
historyDraft = "" historyDraft = ""
@@ -248,7 +252,17 @@ fun GitHugApp() {
output = output, output = output,
inputFieldVersion = inputFieldVersion, inputFieldVersion = inputFieldVersion,
commandInput = commandInput, commandInput = commandInput,
onValueChange = { commandInput = it }, onValueChange = {
val blockedEcho = suppressedImeEcho
if (blockedEcho != null && commandInput.text.isEmpty()) {
val blockedTrimmed = blockedEcho.trim()
if (it.text == blockedEcho || (blockedTrimmed.isNotEmpty() && it.text == blockedTrimmed)) {
return@TerminalPanel
}
}
suppressedImeEcho = null
commandInput = it
},
onRun = { runCommand() }, onRun = { runCommand() },
onTab = { tabComplete() }, onTab = { tabComplete() },
onCursorLeft = { moveCursor(-1) }, onCursorLeft = { moveCursor(-1) },
@@ -376,9 +390,20 @@ private fun TerminalPanel(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
val keyboardController = LocalSoftwareKeyboardController.current
LaunchedEffect(inputFieldVersion) { LaunchedEffect(inputFieldVersion) {
if (inputFieldVersion == 0) return@LaunchedEffect
delay(75)
focusRequester.requestFocus() focusRequester.requestFocus()
keyboardController?.show()
}
fun submitCommand() {
focusManager.clearFocus(force = true)
keyboardController?.hide()
onRun()
} }
Card(modifier = modifier, colors = CardDefaults.cardColors(containerColor = TerminalBackground)) { Card(modifier = modifier, colors = CardDefaults.cardColors(containerColor = TerminalBackground)) {
@@ -427,7 +452,7 @@ private fun TerminalPanel(
keyboardType = KeyboardType.Ascii, keyboardType = KeyboardType.Ascii,
imeAction = ImeAction.Done, imeAction = ImeAction.Done,
), ),
keyboardActions = KeyboardActions(onDone = { onRun() }) keyboardActions = KeyboardActions(onDone = { submitCommand() })
) )
} }
} }
@@ -448,29 +473,35 @@ private fun SpecialKeyBar(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
TerminalKeyButton(icon = { Icon(Icons.Filled.KeyboardTab, contentDescription = "Tab") }, onClick = onTab, modifier = Modifier.weight(1.3f)) TerminalKeyButton(label = "TAB", onClick = onTab, modifier = Modifier.width(68.dp))
TerminalKeyButton(icon = { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Move cursor left") }, onClick = onCursorLeft) TerminalKeyButton(label = "", onClick = onCursorLeft, modifier = Modifier.width(48.dp))
TerminalKeyButton(icon = { Icon(Icons.AutoMirrored.Filled.ArrowForward, contentDescription = "Move cursor right") }, onClick = onCursorRight) TerminalKeyButton(label = "", onClick = onCursorRight, modifier = Modifier.width(48.dp))
TerminalKeyButton(icon = { Icon(Icons.Filled.KeyboardArrowUp, contentDescription = "Previous command") }, onClick = onHistoryUp) TerminalKeyButton(label = "", onClick = onHistoryUp, modifier = Modifier.width(48.dp))
TerminalKeyButton(icon = { Icon(Icons.Filled.KeyboardArrowDown, contentDescription = "Next command") }, onClick = onHistoryDown) TerminalKeyButton(label = "", onClick = onHistoryDown, modifier = Modifier.width(48.dp))
} }
} }
@Composable @Composable
private fun TerminalKeyButton( private fun TerminalKeyButton(
icon: @Composable () -> Unit, label: String,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Button( Button(
onClick = onClick, onClick = onClick,
modifier = modifier, modifier = modifier.height(34.dp),
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp),
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = PanelSecondary, containerColor = PanelSecondary,
contentColor = TextPrimary contentColor = TextPrimary
) )
) { ) {
icon() Text(
text = label,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
fontSize = 13.sp
)
} }
} }