diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4b57441..6a8a27d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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 diff --git a/app/src/androidTest/java/solutions/tretter/githugandroid/GitRepositoryRuntimeInstrumentedTest.kt b/app/src/androidTest/java/solutions/tretter/githugandroid/GitRepositoryRuntimeInstrumentedTest.kt index 9e14481..5ffebc7 100644 --- a/app/src/androidTest/java/solutions/tretter/githugandroid/GitRepositoryRuntimeInstrumentedTest.kt +++ b/app/src/androidTest/java/solutions/tretter/githugandroid/GitRepositoryRuntimeInstrumentedTest.kt @@ -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 { val context = InstrumentationRegistry.getInstrumentation().targetContext File(context.filesDir, "githug-sandboxes").deleteRecursively() diff --git a/app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt b/app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt index 34f1d41..ceabecb 100644 --- a/app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt +++ b/app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt @@ -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(null) } var gitMessageEditorState by remember { mutableStateOf(null) } var manPageState by remember { mutableStateOf(null) } @@ -233,11 +237,14 @@ fun GitHugApp() { index: Int, message: List = 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 = {