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
This commit is contained in:
15
app/src/main/java/solutions/tretter/githugandroid/AppLog.kt
Normal file
15
app/src/main/java/solutions/tretter/githugandroid/AppLog.kt
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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<Set<String>> = dataStore.data
|
||||
data class GameProgress(
|
||||
val completedLevels: Set<String> = emptySet(),
|
||||
val activeLevelId: String? = null,
|
||||
)
|
||||
|
||||
val progressFlow: Flow<GameProgress> = 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<String>) {
|
||||
suspend fun saveProgress(completedLevels: Set<String>, 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",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<String>, 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)
|
||||
|
||||
@@ -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<RepoState, List<String>> {
|
||||
AppLog.d("GitRuntime", "Executing command for level=${level.id}: $command")
|
||||
val nativeGit = nativeGitBinary()
|
||||
if (nativeGit == null) {
|
||||
return GitSandboxEngine.execute(currentRepo, command)
|
||||
|
||||
Reference in New Issue
Block a user