From eee8827f760af3e634274a601b86b44c8c5d667d Mon Sep 17 00:00:00 2001 From: Joe Tretter Date: Fri, 24 Apr 2026 13:52:19 -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/AppLog.kt app/src/main/java/solutions/tretter/githugandroid/GameProgressStore.kt app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt app/src/main/java/solutions/tretter/githugandroid/GitRuntime.kt --- app/build.gradle.kts | 4 +- .../solutions/tretter/githugandroid/AppLog.kt | 15 +++++++ .../githugandroid/GameProgressStore.kt | 37 +++++++++++++++-- .../tretter/githugandroid/GitHugApp.kt | 40 ++++++++++++++++--- .../tretter/githugandroid/GitRuntime.kt | 2 + 5 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/solutions/tretter/githugandroid/AppLog.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2343a23..36454e1 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 = 71 - versionName = "0.1.70" + versionCode = 72 + versionName = "0.1.71" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/solutions/tretter/githugandroid/AppLog.kt b/app/src/main/java/solutions/tretter/githugandroid/AppLog.kt new file mode 100644 index 0000000..a38ed0a --- /dev/null +++ b/app/src/main/java/solutions/tretter/githugandroid/AppLog.kt @@ -0,0 +1,15 @@ +package solutions.tretter.githugandroid + +import android.util.Log + +object AppLog { + private const val TAG = "GitHugAndroid" + + fun d(area: String, message: String) { + Log.d(TAG, "[$area] $message") + } + + fun e(area: String, message: String, error: Throwable? = null) { + Log.e(TAG, "[$area] $message", error) + } +} \ No newline at end of file diff --git a/app/src/main/java/solutions/tretter/githugandroid/GameProgressStore.kt b/app/src/main/java/solutions/tretter/githugandroid/GameProgressStore.kt index 7930963..38fe94f 100644 --- a/app/src/main/java/solutions/tretter/githugandroid/GameProgressStore.kt +++ b/app/src/main/java/solutions/tretter/githugandroid/GameProgressStore.kt @@ -17,23 +17,52 @@ class GameProgressStore(private val context: Context) { ) private val completedLevelsKey = stringPreferencesKey("completed_levels") + private val activeLevelIdKey = stringPreferencesKey("active_level_id") - val completedLevelsFlow: Flow> = dataStore.data + data class GameProgress( + val completedLevels: Set = emptySet(), + val activeLevelId: String? = null, + ) + + val progressFlow: Flow = dataStore.data .catch { error -> - if (error is IOException) emit(emptyPreferences()) else throw error + if (error is IOException) { + AppLog.e("ProgressStore", "Failed reading progress preferences, using defaults", error) + emit(emptyPreferences()) + } else { + throw error + } } .map { preferences -> - preferences[completedLevelsKey] + val completedLevels = preferences[completedLevelsKey] ?.split(',') ?.map { it.trim() } ?.filter { it.isNotEmpty() } ?.toSet() ?: emptySet() + val activeLevelId = preferences[activeLevelIdKey]?.takeIf { it.isNotBlank() } + AppLog.d( + "ProgressStore", + "Loaded progress completed=${completedLevels.sorted()} activeLevelId=$activeLevelId", + ) + GameProgress( + completedLevels = completedLevels, + activeLevelId = activeLevelId, + ) } - suspend fun saveCompletedLevels(completedLevels: Set) { + suspend fun saveProgress(completedLevels: Set, activeLevelId: String?) { dataStore.edit { preferences -> preferences[completedLevelsKey] = completedLevels.joinToString(",") + if (activeLevelId.isNullOrBlank()) { + preferences.remove(activeLevelIdKey) + } else { + preferences[activeLevelIdKey] = activeLevelId + } } + AppLog.d( + "ProgressStore", + "Saved progress completed=${completedLevels.sorted()} activeLevelId=$activeLevelId", + ) } } \ 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 1234d06..ec67508 100644 --- a/app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt +++ b/app/src/main/java/solutions/tretter/githugandroid/GitHugApp.kt @@ -40,7 +40,7 @@ fun GitHugApp() { 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 = null) + val persistedProgress by gameProgressStore.progressFlow.collectAsState(initial = null) val scope = rememberCoroutineScope() val screenScrollState = rememberScrollState() val screenHeightDp = configuration.screenHeightDp @@ -81,13 +81,28 @@ fun GitHugApp() { } } - LaunchedEffect(persistedCompletedLevels) { - val restoredCompletedLevels = persistedCompletedLevels ?: return@LaunchedEffect + suspend fun persistProgress(completed: Set, activeLevelId: String?) { + gameProgressStore.saveProgress(completed, activeLevelId) + } + + LaunchedEffect(persistedProgress) { + val restoredProgress = persistedProgress ?: return@LaunchedEffect + val restoredCompletedLevels = restoredProgress.completedLevels completedLevels = restoredCompletedLevels + AppLog.d( + "GitHugApp", + "Observed persisted progress completed=${restoredCompletedLevels.sorted()} activeLevelId=${restoredProgress.activeLevelId} restored=$hasRestoredProgress", + ) if (!hasRestoredProgress) { hasRestoredProgress = true - val firstIncompleteIndex = levels.indexOfFirst { it.id !in restoredCompletedLevels } - val resumeIndex = firstIncompleteIndex.takeIf { it >= 0 } ?: levels.lastIndex + val resumeIndex = restoredProgress.activeLevelId + ?.let { activeId -> levels.indexOfFirst { it.id == activeId }.takeIf { it >= 0 } } + ?: levels.indexOfFirst { it.id !in restoredCompletedLevels }.takeIf { it >= 0 } + ?: levels.lastIndex + AppLog.d( + "GitHugApp", + "Restoring app to levelIndex=$resumeIndex levelId=${levels[resumeIndex].id}", + ) currentLevelIndex = resumeIndex repo = runtime.prepareLevel(levels[resumeIndex]) output = listOf( @@ -171,6 +186,7 @@ fun GitHugApp() { } fun loadLevel(index: Int) { + AppLog.d("GitHugApp", "Loading level index=$index id=${levels[index].id} title=${levels[index].title}") currentLevelIndex = index repo = runtime.prepareLevel(levels[index]) output = listOf("Loaded level: ${levels[index].title}") @@ -264,6 +280,10 @@ fun GitHugApp() { val (newRepo, lines) = runtime.execute(currentLevel, repo, raw) val solvedAfterCommand = currentLevel.validator(newRepo, raw) val wasAlreadyCompleted = currentLevel.id in completedLevels + AppLog.d( + "GitHugApp", + "Command='$raw' level=${currentLevel.id} solved=$solvedAfterCommand alreadyCompleted=$wasAlreadyCompleted completedBefore=${completedLevels.sorted()}", + ) val newOutput = buildList { addAll(output) add("$ $raw") @@ -275,17 +295,25 @@ fun GitHugApp() { if (solvedAfterCommand && !wasAlreadyCompleted) { val updatedCompletedLevels = completedLevels + currentLevel.id completedLevels = updatedCompletedLevels - scope.launch { gameProgressStore.saveCompletedLevels(updatedCompletedLevels) } val nextLevelIndex = levels.indexOfFirst { it.id !in updatedCompletedLevels } if (nextLevelIndex >= 0) { + val nextLevelId = levels[nextLevelIndex].id + AppLog.d( + "GitHugApp", + "Level solved ${currentLevel.id}; advancing to next incomplete index=$nextLevelIndex id=$nextLevelId", + ) + scope.launch { persistProgress(updatedCompletedLevels, nextLevelId) } loadLevel(nextLevelIndex) } else { + AppLog.d("GitHugApp", "All levels completed") + scope.launch { persistProgress(updatedCompletedLevels, levels.lastOrNull()?.id) } repo = newRepo output = listOf("🏁 All Githug levels completed.") clearCommandInput() suppressedImeEcho = null } } else { + AppLog.d("GitHugApp", "Staying on level=${currentLevel.id}") repo = newRepo output = newOutput applyRecommendedPaneWeights(persist = false) diff --git a/app/src/main/java/solutions/tretter/githugandroid/GitRuntime.kt b/app/src/main/java/solutions/tretter/githugandroid/GitRuntime.kt index 29d8066..bb1fb9c 100644 --- a/app/src/main/java/solutions/tretter/githugandroid/GitRuntime.kt +++ b/app/src/main/java/solutions/tretter/githugandroid/GitRuntime.kt @@ -17,6 +17,7 @@ class GitRepositoryRuntime(private val context: Context) { } fun prepareLevel(level: Level): RepoState { + AppLog.d("GitRuntime", "Preparing level id=${level.id} title=${level.title}") val nativeGit = nativeGitBinary() if (nativeGit == null) { return level.setup() @@ -52,6 +53,7 @@ class GitRepositoryRuntime(private val context: Context) { } fun execute(level: Level, currentRepo: RepoState, command: String): Pair> { + AppLog.d("GitRuntime", "Executing command for level=${level.id}: $command") val nativeGit = nativeGitBinary() if (nativeGit == null) { return GitSandboxEngine.execute(currentRepo, command)