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
This commit is contained in:
Joe Tretter
2026-04-24 13:15:34 -05:00
parent 92e10ff518
commit 6ec45aea32
3 changed files with 89 additions and 6 deletions

View File

@@ -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

View File

@@ -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<Set<String>> = 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<String>) {
dataStore.edit { preferences ->
preferences[completedLevelsKey] = completedLevels.joinToString(",")
}
}
}

View File

@@ -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<String>()) }
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.")