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:
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId = "solutions.tretter.githugandroid"
|
applicationId = "solutions.tretter.githugandroid"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 71
|
versionCode = 72
|
||||||
versionName = "0.1.70"
|
versionName = "0.1.71"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|||||||
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 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 ->
|
.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 ->
|
.map { preferences ->
|
||||||
preferences[completedLevelsKey]
|
val completedLevels = preferences[completedLevelsKey]
|
||||||
?.split(',')
|
?.split(',')
|
||||||
?.map { it.trim() }
|
?.map { it.trim() }
|
||||||
?.filter { it.isNotEmpty() }
|
?.filter { it.isNotEmpty() }
|
||||||
?.toSet()
|
?.toSet()
|
||||||
?: emptySet()
|
?: 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 ->
|
dataStore.edit { preferences ->
|
||||||
preferences[completedLevelsKey] = completedLevels.joinToString(",")
|
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 paneLayoutStore = remember(context) { PaneLayoutStore(context.applicationContext) }
|
||||||
val gameProgressStore = remember(context) { GameProgressStore(context.applicationContext) }
|
val gameProgressStore = remember(context) { GameProgressStore(context.applicationContext) }
|
||||||
val persistedPaneLayout by paneLayoutStore.layoutFlow.collectAsState(initial = defaultPaneLayout())
|
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 scope = rememberCoroutineScope()
|
||||||
val screenScrollState = rememberScrollState()
|
val screenScrollState = rememberScrollState()
|
||||||
val screenHeightDp = configuration.screenHeightDp
|
val screenHeightDp = configuration.screenHeightDp
|
||||||
@@ -81,13 +81,28 @@ fun GitHugApp() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(persistedCompletedLevels) {
|
suspend fun persistProgress(completed: Set<String>, activeLevelId: String?) {
|
||||||
val restoredCompletedLevels = persistedCompletedLevels ?: return@LaunchedEffect
|
gameProgressStore.saveProgress(completed, activeLevelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(persistedProgress) {
|
||||||
|
val restoredProgress = persistedProgress ?: return@LaunchedEffect
|
||||||
|
val restoredCompletedLevels = restoredProgress.completedLevels
|
||||||
completedLevels = restoredCompletedLevels
|
completedLevels = restoredCompletedLevels
|
||||||
|
AppLog.d(
|
||||||
|
"GitHugApp",
|
||||||
|
"Observed persisted progress completed=${restoredCompletedLevels.sorted()} activeLevelId=${restoredProgress.activeLevelId} restored=$hasRestoredProgress",
|
||||||
|
)
|
||||||
if (!hasRestoredProgress) {
|
if (!hasRestoredProgress) {
|
||||||
hasRestoredProgress = true
|
hasRestoredProgress = true
|
||||||
val firstIncompleteIndex = levels.indexOfFirst { it.id !in restoredCompletedLevels }
|
val resumeIndex = restoredProgress.activeLevelId
|
||||||
val resumeIndex = firstIncompleteIndex.takeIf { it >= 0 } ?: levels.lastIndex
|
?.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
|
currentLevelIndex = resumeIndex
|
||||||
repo = runtime.prepareLevel(levels[resumeIndex])
|
repo = runtime.prepareLevel(levels[resumeIndex])
|
||||||
output = listOf(
|
output = listOf(
|
||||||
@@ -171,6 +186,7 @@ fun GitHugApp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun loadLevel(index: Int) {
|
fun loadLevel(index: Int) {
|
||||||
|
AppLog.d("GitHugApp", "Loading level index=$index id=${levels[index].id} title=${levels[index].title}")
|
||||||
currentLevelIndex = index
|
currentLevelIndex = index
|
||||||
repo = runtime.prepareLevel(levels[index])
|
repo = runtime.prepareLevel(levels[index])
|
||||||
output = listOf("Loaded level: ${levels[index].title}")
|
output = listOf("Loaded level: ${levels[index].title}")
|
||||||
@@ -264,6 +280,10 @@ fun GitHugApp() {
|
|||||||
val (newRepo, lines) = runtime.execute(currentLevel, repo, raw)
|
val (newRepo, lines) = runtime.execute(currentLevel, repo, raw)
|
||||||
val solvedAfterCommand = currentLevel.validator(newRepo, raw)
|
val solvedAfterCommand = currentLevel.validator(newRepo, raw)
|
||||||
val wasAlreadyCompleted = currentLevel.id in completedLevels
|
val wasAlreadyCompleted = currentLevel.id in completedLevels
|
||||||
|
AppLog.d(
|
||||||
|
"GitHugApp",
|
||||||
|
"Command='$raw' level=${currentLevel.id} solved=$solvedAfterCommand alreadyCompleted=$wasAlreadyCompleted completedBefore=${completedLevels.sorted()}",
|
||||||
|
)
|
||||||
val newOutput = buildList {
|
val newOutput = buildList {
|
||||||
addAll(output)
|
addAll(output)
|
||||||
add("$ $raw")
|
add("$ $raw")
|
||||||
@@ -275,17 +295,25 @@ fun GitHugApp() {
|
|||||||
if (solvedAfterCommand && !wasAlreadyCompleted) {
|
if (solvedAfterCommand && !wasAlreadyCompleted) {
|
||||||
val updatedCompletedLevels = completedLevels + currentLevel.id
|
val updatedCompletedLevels = completedLevels + currentLevel.id
|
||||||
completedLevels = updatedCompletedLevels
|
completedLevels = updatedCompletedLevels
|
||||||
scope.launch { gameProgressStore.saveCompletedLevels(updatedCompletedLevels) }
|
|
||||||
val nextLevelIndex = levels.indexOfFirst { it.id !in updatedCompletedLevels }
|
val nextLevelIndex = levels.indexOfFirst { it.id !in updatedCompletedLevels }
|
||||||
if (nextLevelIndex >= 0) {
|
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)
|
loadLevel(nextLevelIndex)
|
||||||
} else {
|
} else {
|
||||||
|
AppLog.d("GitHugApp", "All levels completed")
|
||||||
|
scope.launch { persistProgress(updatedCompletedLevels, levels.lastOrNull()?.id) }
|
||||||
repo = newRepo
|
repo = newRepo
|
||||||
output = listOf("🏁 All Githug levels completed.")
|
output = listOf("🏁 All Githug levels completed.")
|
||||||
clearCommandInput()
|
clearCommandInput()
|
||||||
suppressedImeEcho = null
|
suppressedImeEcho = null
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
AppLog.d("GitHugApp", "Staying on level=${currentLevel.id}")
|
||||||
repo = newRepo
|
repo = newRepo
|
||||||
output = newOutput
|
output = newOutput
|
||||||
applyRecommendedPaneWeights(persist = false)
|
applyRecommendedPaneWeights(persist = false)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class GitRepositoryRuntime(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun prepareLevel(level: Level): RepoState {
|
fun prepareLevel(level: Level): RepoState {
|
||||||
|
AppLog.d("GitRuntime", "Preparing level id=${level.id} title=${level.title}")
|
||||||
val nativeGit = nativeGitBinary()
|
val nativeGit = nativeGitBinary()
|
||||||
if (nativeGit == null) {
|
if (nativeGit == null) {
|
||||||
return level.setup()
|
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>> {
|
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()
|
val nativeGit = nativeGitBinary()
|
||||||
if (nativeGit == null) {
|
if (nativeGit == null) {
|
||||||
return GitSandboxEngine.execute(currentRepo, command)
|
return GitSandboxEngine.execute(currentRepo, command)
|
||||||
|
|||||||
Reference in New Issue
Block a user