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:
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId = "com.kawomi.githugandroid"
|
applicationId = "com.kawomi.githugandroid"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 26
|
versionCode = 27
|
||||||
versionName = "0.1.25"
|
versionName = "0.1.26"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@@ -205,6 +206,7 @@ fun GitHugApp() {
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val screenScrollState = rememberScrollState()
|
val screenScrollState = rememberScrollState()
|
||||||
val workspaceHeight = (configuration.screenHeightDp.dp * 1.05f).coerceAtLeast(560.dp)
|
val workspaceHeight = (configuration.screenHeightDp.dp * 1.05f).coerceAtLeast(560.dp)
|
||||||
|
val screenHeightDp = configuration.screenHeightDp
|
||||||
|
|
||||||
var paneLayout by remember { mutableStateOf(defaultPaneLayout()) }
|
var paneLayout by remember { mutableStateOf(defaultPaneLayout()) }
|
||||||
var currentLevelIndex by remember { mutableStateOf(0) }
|
var currentLevelIndex by remember { mutableStateOf(0) }
|
||||||
@@ -214,6 +216,7 @@ fun GitHugApp() {
|
|||||||
var suppressedImeEcho by remember { mutableStateOf<String?>(null) }
|
var suppressedImeEcho by remember { mutableStateOf<String?>(null) }
|
||||||
var output by remember { mutableStateOf(listOf(runtime.startupBanner())) }
|
var output by remember { mutableStateOf(listOf(runtime.startupBanner())) }
|
||||||
var hintIndex by remember { mutableStateOf(0) }
|
var hintIndex by remember { mutableStateOf(0) }
|
||||||
|
var suggestionsExpanded by remember { mutableStateOf(false) }
|
||||||
var completedLevels by remember { mutableStateOf(setOf<String>()) }
|
var completedLevels by remember { mutableStateOf(setOf<String>()) }
|
||||||
var commandHistory by remember { mutableStateOf(listOf<String>()) }
|
var commandHistory by remember { mutableStateOf(listOf<String>()) }
|
||||||
var historyIndex by remember { mutableStateOf(-1) }
|
var historyIndex by remember { mutableStateOf(-1) }
|
||||||
@@ -239,14 +242,32 @@ fun GitHugApp() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun applyRecommendedPaneWeights(persist: Boolean = true) {
|
||||||
|
updatePaneLayout(
|
||||||
|
transform = { layout ->
|
||||||
|
layout.copy(
|
||||||
|
weights = layout.weights + recommendedPaneWeights(
|
||||||
|
level = currentLevel,
|
||||||
|
levelCount = levels.size,
|
||||||
|
outputLineCount = output.size,
|
||||||
|
screenHeightDp = screenHeightDp,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
persist = persist,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun resetCurrentLevel(message: String = "Level reset.") {
|
fun resetCurrentLevel(message: String = "Level reset.") {
|
||||||
repo = runtime.prepareLevel(currentLevel)
|
repo = runtime.prepareLevel(currentLevel)
|
||||||
clearCommandInput()
|
clearCommandInput()
|
||||||
suppressedImeEcho = null
|
suppressedImeEcho = null
|
||||||
output = listOf(message)
|
output = listOf(message)
|
||||||
hintIndex = 0
|
hintIndex = 0
|
||||||
|
suggestionsExpanded = false
|
||||||
historyIndex = -1
|
historyIndex = -1
|
||||||
historyDraft = ""
|
historyDraft = ""
|
||||||
|
applyRecommendedPaneWeights(persist = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadLevel(index: Int) {
|
fun loadLevel(index: Int) {
|
||||||
@@ -256,8 +277,17 @@ fun GitHugApp() {
|
|||||||
clearCommandInput()
|
clearCommandInput()
|
||||||
suppressedImeEcho = null
|
suppressedImeEcho = null
|
||||||
hintIndex = 0
|
hintIndex = 0
|
||||||
|
suggestionsExpanded = false
|
||||||
historyIndex = -1
|
historyIndex = -1
|
||||||
historyDraft = ""
|
historyDraft = ""
|
||||||
|
paneLayout = paneLayout.copy(
|
||||||
|
weights = paneLayout.weights + recommendedPaneWeights(
|
||||||
|
level = levels[index],
|
||||||
|
levelCount = levels.size,
|
||||||
|
outputLineCount = 1,
|
||||||
|
screenHeightDp = screenHeightDp,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCommandText(text: String) {
|
fun setCommandText(text: String) {
|
||||||
@@ -351,6 +381,7 @@ fun GitHugApp() {
|
|||||||
} else {
|
} else {
|
||||||
repo = newRepo
|
repo = newRepo
|
||||||
output = newOutput
|
output = newOutput
|
||||||
|
applyRecommendedPaneWeights(persist = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,6 +434,8 @@ fun GitHugApp() {
|
|||||||
exerciseContent = {
|
exerciseContent = {
|
||||||
ExercisePane(
|
ExercisePane(
|
||||||
level = currentLevel,
|
level = currentLevel,
|
||||||
|
suggestionsExpanded = suggestionsExpanded,
|
||||||
|
onToggleSuggestions = { suggestionsExpanded = !suggestionsExpanded },
|
||||||
onHint = {
|
onHint = {
|
||||||
val hint = currentLevel.hints.getOrNull(hintIndex) ?: "No more hints for this level."
|
val hint = currentLevel.hints.getOrNull(hintIndex) ?: "No more hints for this level."
|
||||||
output = output + "hint> $hint"
|
output = output + "hint> $hint"
|
||||||
@@ -766,9 +799,7 @@ private fun LevelsPane(
|
|||||||
onSelect: (Int) -> Unit,
|
onSelect: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth()
|
|
||||||
.verticalScroll(rememberScrollState()),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
) {
|
) {
|
||||||
levels.forEachIndexed { index, level ->
|
levels.forEachIndexed { index, level ->
|
||||||
@@ -859,13 +890,13 @@ private fun VisualPane(repo: RepoState) {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ExercisePane(
|
private fun ExercisePane(
|
||||||
level: Level,
|
level: Level,
|
||||||
|
suggestionsExpanded: Boolean,
|
||||||
|
onToggleSuggestions: () -> Unit,
|
||||||
onHint: () -> Unit,
|
onHint: () -> Unit,
|
||||||
onReset: () -> Unit,
|
onReset: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth()
|
|
||||||
.verticalScroll(rememberScrollState()),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
) {
|
) {
|
||||||
Text(level.title, style = MaterialTheme.typography.titleLarge, color = TextPrimary)
|
Text(level.title, style = MaterialTheme.typography.titleLarge, color = TextPrimary)
|
||||||
@@ -880,6 +911,12 @@ private fun ExercisePane(
|
|||||||
) {
|
) {
|
||||||
Text("Hint")
|
Text("Hint")
|
||||||
}
|
}
|
||||||
|
TextButton(
|
||||||
|
onClick = onToggleSuggestions,
|
||||||
|
colors = ButtonDefaults.textButtonColors(contentColor = Accent),
|
||||||
|
) {
|
||||||
|
Text("Suggestions")
|
||||||
|
}
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = onReset,
|
onClick = onReset,
|
||||||
colors = ButtonDefaults.textButtonColors(contentColor = Accent),
|
colors = ButtonDefaults.textButtonColors(contentColor = Accent),
|
||||||
@@ -887,7 +924,7 @@ private fun ExercisePane(
|
|||||||
Text("Reset level")
|
Text("Reset level")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (level.commandSuggestions.isNotEmpty()) {
|
if (suggestionsExpanded && level.commandSuggestions.isNotEmpty()) {
|
||||||
Text("Suggestions", color = TextPrimary, fontWeight = FontWeight.Bold)
|
Text("Suggestions", color = TextPrimary, fontWeight = FontWeight.Bold)
|
||||||
level.commandSuggestions.forEach { suggestion ->
|
level.commandSuggestions.forEach { suggestion ->
|
||||||
Text(suggestion, color = TextSecondary, fontFamily = FontFamily.Monospace)
|
Text(suggestion, color = TextSecondary, fontFamily = FontFamily.Monospace)
|
||||||
@@ -938,6 +975,7 @@ private fun TerminalPane(
|
|||||||
onHistoryUp: () -> Unit,
|
onHistoryUp: () -> Unit,
|
||||||
onHistoryDown: () -> Unit,
|
onHistoryDown: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val configuration = LocalConfiguration.current
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
@@ -945,16 +983,26 @@ private fun TerminalPane(
|
|||||||
val horizontalScroll = rememberScrollState()
|
val horizontalScroll = rememberScrollState()
|
||||||
val outputVerticalScroll = rememberScrollState()
|
val outputVerticalScroll = rememberScrollState()
|
||||||
val terminalMinWidth = 80.dp * 7.2f
|
val terminalMinWidth = 80.dp * 7.2f
|
||||||
|
val maxOutputHeight = configuration.screenHeightDp.dp * 0.7f
|
||||||
|
val outputLineHeight = 20.dp
|
||||||
|
val desiredOutputHeight = (output.size.coerceAtLeast(6) * outputLineHeight.value).dp.coerceAtMost(maxOutputHeight)
|
||||||
|
|
||||||
LaunchedEffect(inputFieldVersion) {
|
LaunchedEffect(inputFieldVersion) {
|
||||||
focusRequester.requestFocus()
|
focusRequester.requestFocus()
|
||||||
keyboardController?.show()
|
keyboardController?.show()
|
||||||
|
horizontalScroll.scrollTo(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(output.size) {
|
LaunchedEffect(output.size) {
|
||||||
outputVerticalScroll.animateScrollTo(outputVerticalScroll.maxValue)
|
outputVerticalScroll.animateScrollTo(outputVerticalScroll.maxValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(output.size, commandInput.text) {
|
||||||
|
if (output.isNotEmpty() && commandInput.text.isEmpty()) {
|
||||||
|
horizontalScroll.scrollTo(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun submitCommand() {
|
fun submitCommand() {
|
||||||
focusManager.clearFocus(force = true)
|
focusManager.clearFocus(force = true)
|
||||||
keyboardController?.hide()
|
keyboardController?.hide()
|
||||||
@@ -976,7 +1024,8 @@ private fun TerminalPane(
|
|||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.heightIn(min = 120.dp, max = maxOutputHeight)
|
||||||
|
.height(desiredOutputHeight)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(PanelPrimary, RoundedCornerShape(10.dp))
|
.background(PanelPrimary, RoundedCornerShape(10.dp))
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
@@ -1114,6 +1163,30 @@ private fun resizePanes(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun recommendedPaneWeights(
|
||||||
|
level: Level,
|
||||||
|
levelCount: Int,
|
||||||
|
outputLineCount: Int,
|
||||||
|
screenHeightDp: Int,
|
||||||
|
): Map<PaneId, Float> {
|
||||||
|
val screenHeight = screenHeightDp.coerceAtLeast(560)
|
||||||
|
val levelsHeight = (56 + levelCount * 44).coerceAtMost((screenHeight * 0.35f).toInt())
|
||||||
|
val descriptionLines = (level.description.length / 42).coerceAtLeast(2) + 2
|
||||||
|
val suggestionsLines = level.commandSuggestions.size
|
||||||
|
val exerciseHeight = (120 + descriptionLines * 24 + suggestionsLines * 22).coerceAtLeast(180)
|
||||||
|
val terminalHeight = (120 + outputLineCount.coerceAtLeast(1) * 20).coerceAtMost((screenHeight * 0.7f).toInt())
|
||||||
|
val visualHeight = (screenHeight * 0.24f).toInt().coerceAtLeast(170)
|
||||||
|
|
||||||
|
fun weightFor(height: Int): Float = (height.toFloat() / screenHeight.toFloat()).coerceAtLeast(0.6f)
|
||||||
|
|
||||||
|
return mapOf(
|
||||||
|
PaneId.EXERCISE to weightFor(exerciseHeight),
|
||||||
|
PaneId.TERMINAL to weightFor(terminalHeight),
|
||||||
|
PaneId.VISUAL to weightFor(visualHeight),
|
||||||
|
PaneId.LEVELS to weightFor(levelsHeight),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun commonPrefix(values: List<String>): String {
|
private fun commonPrefix(values: List<String>): String {
|
||||||
if (values.isEmpty()) return ""
|
if (values.isEmpty()) return ""
|
||||||
var prefix = values.first()
|
var prefix = values.first()
|
||||||
|
|||||||
Reference in New Issue
Block a user