From 6ec45aea32241ff5e6f1b9ec2934b285729f4a8b Mon Sep 17 00:00:00 2001 From: Joe Tretter Date: Fri, 24 Apr 2026 13:15:34 -0500 Subject: [PATCH] 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/GameProgressStore.kt app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt --- app/build.gradle.kts | 4 +- .../githugandroid/GameProgressStore.kt | 39 ++++++++++++++ .../tretter/githugandroid/GitHugApp.kt | 52 +++++++++++++++++-- 3 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/solutions/tretter/githugandroid/GameProgressStore.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e9ecb1b..b5df950 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "solutions.tretter.githugandroid" minSdk = 26 targetSdk = 34 - versionCode = 64 - versionName = "0.1.63" + versionCode = 67 + versionName = "0.1.66" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/solutions/tretter/githugandroid/GameProgressStore.kt b/app/src/main/java/solutions/tretter/githugandroid/GameProgressStore.kt new file mode 100644 index 0000000..7930963 --- /dev/null +++ b/app/src/main/java/solutions/tretter/githugandroid/GameProgressStore.kt @@ -0,0 +1,39 @@ +package solutions.tretter.githugandroid + +import android.content.Context +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.emptyPreferences +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStoreFile +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map +import java.io.IOException + +class GameProgressStore(private val context: Context) { + private val dataStore = PreferenceDataStoreFactory.create( + produceFile = { context.preferencesDataStoreFile("game_progress_preferences") } + ) + + private val completedLevelsKey = stringPreferencesKey("completed_levels") + + val completedLevelsFlow: Flow> = dataStore.data + .catch { error -> + if (error is IOException) emit(emptyPreferences()) else throw error + } + .map { preferences -> + preferences[completedLevelsKey] + ?.split(',') + ?.map { it.trim() } + ?.filter { it.isNotEmpty() } + ?.toSet() + ?: emptySet() + } + + suspend fun saveCompletedLevels(completedLevels: Set) { + dataStore.edit { preferences -> + preferences[completedLevelsKey] = completedLevels.joinToString(",") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt b/app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt index a9fdf33..27630ec 100644 --- a/app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt +++ b/app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt @@ -38,7 +38,9 @@ fun GitHugApp() { val levels = remember { sampleLevels() } val runtime = remember(context) { GitRepositoryRuntime(context.applicationContext) } val paneLayoutStore = remember(context) { PaneLayoutStore(context.applicationContext) } + val gameProgressStore = remember(context) { GameProgressStore(context.applicationContext) } val persistedPaneLayout by paneLayoutStore.layoutFlow.collectAsState(initial = defaultPaneLayout()) + val persistedCompletedLevels by gameProgressStore.completedLevelsFlow.collectAsState(initial = emptySet()) val scope = rememberCoroutineScope() val screenScrollState = rememberScrollState() val screenHeightDp = configuration.screenHeightDp @@ -57,6 +59,7 @@ fun GitHugApp() { var commandHistory by remember { mutableStateOf(listOf()) } var historyIndex by remember { mutableStateOf(-1) } var historyDraft by remember { mutableStateOf("") } + var hasRestoredProgress by remember { mutableStateOf(false) } val currentLevel = levels[currentLevelIndex] LaunchedEffect(persistedPaneLayout) { @@ -78,6 +81,45 @@ fun GitHugApp() { } } + LaunchedEffect(persistedCompletedLevels) { + completedLevels = persistedCompletedLevels + if (!hasRestoredProgress) { + hasRestoredProgress = true + val firstIncompleteIndex = levels.indexOfFirst { it.id !in persistedCompletedLevels } + val resumeIndex = firstIncompleteIndex.takeIf { it >= 0 } ?: levels.lastIndex + currentLevelIndex = resumeIndex + repo = runtime.prepareLevel(levels[resumeIndex]) + output = listOf( + if (persistedCompletedLevels.size == levels.size) { + "🏁 All Githug levels completed." + } else if (persistedCompletedLevels.isEmpty()) { + runtime.startupBanner() + } else { + "Resumed at level: ${levels[resumeIndex].title}" + } + ) + commandInput = TextFieldValue(text = "", selection = TextRange.Zero) + suppressedImeEcho = null + hintIndex = 0 + activeExerciseDetail = null + visibleHint = null + historyIndex = -1 + historyDraft = "" + paneLayout = paneLayout.copy( + weights = paneLayout.weights + recommendedPaneWeights( + heights = recommendedPaneHeights( + level = levels[resumeIndex], + levelCount = levels.size, + outputLineCount = 1, + screenHeightDp = screenHeightDp, + suggestionsVisible = false, + visibleHint = null, + ), + ) + ) + } + } + fun applyRecommendedPaneWeights(persist: Boolean = true) { updatePaneLayout( transform = { layout -> @@ -230,10 +272,12 @@ fun GitHugApp() { } } if (solvedAfterCommand && !wasAlreadyCompleted) { - completedLevels = completedLevels + currentLevel.id - val hasNextLevel = currentLevelIndex < levels.lastIndex - if (hasNextLevel) { - loadLevel(currentLevelIndex + 1) + val updatedCompletedLevels = completedLevels + currentLevel.id + completedLevels = updatedCompletedLevels + scope.launch { gameProgressStore.saveCompletedLevels(updatedCompletedLevels) } + val nextLevelIndex = levels.indexOfFirst { it.id !in updatedCompletedLevels } + if (nextLevelIndex >= 0) { + loadLevel(nextLevelIndex) } else { repo = newRepo output = listOf("🏁 All Githug levels completed.")