Make manual level selection responsive

This commit is contained in:
Joe Tretter
2026-06-25 10:02:06 -05:00
parent 82e38de3bb
commit 035851ac05
3 changed files with 57 additions and 7 deletions

View File

@@ -20,8 +20,8 @@ android {
applicationId = "solutions.tretter.githugandroid"
minSdk = 26
targetSdk = 35
versionCode = 175
versionName = "0.1.174"
versionCode = 176
versionName = "0.1.175"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true

View File

@@ -6,6 +6,7 @@ import androidx.compose.ui.test.junit4.createEmptyComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performImeAction
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performTextClearance
import androidx.compose.ui.test.performTextInput
import androidx.test.core.app.ActivityScenario
@@ -62,6 +63,21 @@ class GitRepositoryRuntimeInstrumentedTest {
}
}
@Test
fun selectingLevelFromLevelsPaneUpdatesExerciseImmediately() {
launchFreshApp().use {
composeRule.onNodeWithTag("level-submodule")
.performScrollTo()
.performClick()
composeRule.waitUntil(timeoutMillis = 1_500) {
composeRule.onAllNodes(hasText(submoduleLevel().title, substring = true))
.fetchSemanticsNodes()
.isNotEmpty()
}
}
}
private fun launchFreshApp(): ActivityScenario<MainActivity> {
val context = InstrumentationRegistry.getInstrumentation().targetContext
File(context.filesDir, "githug-sandboxes").deleteRecursively()

View File

@@ -34,8 +34,10 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Composable
fun GitHugApp() {
@@ -73,6 +75,8 @@ fun GitHugApp() {
var historyIndex by remember { mutableStateOf(-1) }
var historyDraft by remember { mutableStateOf("") }
var hasRestoredProgress by remember { mutableStateOf(false) }
var isPreparingLevel by remember { mutableStateOf(false) }
var levelLoadRequestId by remember { mutableStateOf(0) }
var editorState by remember { mutableStateOf<TextEditorState?>(null) }
var gitMessageEditorState by remember { mutableStateOf<GitMessageEditorState?>(null) }
var manPageState by remember { mutableStateOf<ManPageState?>(null) }
@@ -233,11 +237,14 @@ fun GitHugApp() {
index: Int,
message: List<String> = listOf("Loaded level: ${levels[index].title}"),
keepSuppressedImeEcho: Boolean = false,
prepareInBackground: Boolean = false,
) {
AppLog.d("GitHugApp", "Loading level index=$index id=${levels[index].id} title=${levels[index].title}")
val level = levels[index]
val requestId = levelLoadRequestId + 1
levelLoadRequestId = requestId
AppLog.d("GitHugApp", "Loading level index=$index id=${level.id} title=${level.title} background=$prepareInBackground")
currentLevelIndex = index
repo = runtime.prepareLevel(levels[index])
output = message
output = if (prepareInBackground) listOf("Preparing level: ${level.title}") else message
clearCommandInput(recreateField = true)
if (!keepSuppressedImeEcho) {
suppressedImeEcho = null
@@ -250,10 +257,29 @@ fun GitHugApp() {
editorState = null
gitMessageEditorState = null
manPageState = null
if (prepareInBackground) {
isPreparingLevel = true
repo = RepoState()
scope.launch {
val preparedRepo = withContext(Dispatchers.Default) {
runtime.prepareLevel(level)
}
if (levelLoadRequestId == requestId) {
AppLog.d("GitHugApp", "Prepared level index=$index id=${level.id}")
repo = preparedRepo
output = message
isPreparingLevel = false
clearCommandInput(recreateField = true)
}
}
} else {
isPreparingLevel = false
repo = runtime.prepareLevel(level)
}
paneLayout = paneLayout.copy(
weights = paneLayout.weights + recommendedPaneWeights(
heights = recommendedPaneHeights(
level = levels[index],
level = level,
levelCount = levels.size,
outputLineCount = 1,
screenHeightDp = screenHeightDp,
@@ -457,6 +483,12 @@ fun GitHugApp() {
val submittedText = commandInput.text
val raw = submittedText.trim()
if (raw.isBlank()) return
if (isPreparingLevel) {
output = output + "Still preparing ${currentLevel.title}. Try again in a moment."
clearCommandInput(recreateField = true)
applyRecommendedPaneWeights(persist = false, outputLineCount = terminalOutputLineCount(output))
return
}
val isInteractiveInput = repo.interactiveAddSession != null
showHelpOverlay = false
@@ -560,7 +592,9 @@ fun GitHugApp() {
})
},
levelsContent = {
LevelsPane(levels, currentLevelIndex, completedLevels) { index -> loadLevel(index) }
LevelsPane(levels, currentLevelIndex, completedLevels) { index ->
loadLevel(index, prepareInBackground = true)
}
},
visualContent = { VisualPane(repo) },
exerciseContent = {