Skip to content

Commit bfabcad

Browse files
author
GreenGo Build
committed
Fix navigation: Splash never in backStack, BackHandler in ContentView, immersive mode reapplied on focus
1 parent 3fab77b commit bfabcad

3 files changed

Lines changed: 56 additions & 52 deletions

File tree

app/src/main/java/com/greengo/app/MainActivity.kt

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package com.greengo.app
22

33
import android.os.Bundle
44
import androidx.activity.ComponentActivity
5-
import androidx.activity.OnBackPressedCallback
65
import androidx.activity.compose.setContent
6+
import androidx.activity.enableEdgeToEdge
77
import androidx.activity.viewModels
88
import androidx.compose.material3.MaterialTheme
99
import androidx.compose.material3.darkColorScheme
@@ -15,6 +15,7 @@ import androidx.core.view.WindowInsetsCompat
1515
import androidx.core.view.WindowInsetsControllerCompat
1616
import com.greengo.app.data.AppStateViewModel
1717
import com.greengo.app.data.AppTheme
18+
import com.greengo.app.data.Screen
1819
import com.greengo.app.ui.ContentView
1920

2021
class MainActivity : ComponentActivity() {
@@ -24,27 +25,10 @@ class MainActivity : ComponentActivity() {
2425
override fun onCreate(savedInstanceState: Bundle?) {
2526
super.onCreate(savedInstanceState)
2627

27-
// Let Compose draw under system bars (edge‑to‑edge)
28+
// Edge-to-edge: Compose draws under system bars
2829
WindowCompat.setDecorFitsSystemWindows(window, false)
30+
hideSystemBars()
2931

30-
// Hide both status and nav bars (immersive experience)
31-
val insetsController = WindowInsetsControllerCompat(window, window.decorView)
32-
insetsController.hide(WindowInsetsCompat.Type.systemBars())
33-
insetsController.systemBarsBehavior =
34-
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
35-
36-
// Back button logic: internal navigation or exit
37-
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
38-
override fun handleOnBackPressed() {
39-
if (vm.canNavigateBack()) {
40-
vm.navigateBack()
41-
} else {
42-
finishAffinity() // exit app
43-
}
44-
}
45-
})
46-
47-
// Init your ViewModel state
4832
vm.init(applicationContext)
4933

5034
setContent {
@@ -57,4 +41,22 @@ class MainActivity : ComponentActivity() {
5741
}
5842
}
5943
}
60-
}
44+
45+
override fun onWindowFocusChanged(hasFocus: Boolean) {
46+
super.onWindowFocusChanged(hasFocus)
47+
// Re-apply immersive mode whenever the window regains focus
48+
// (system can restore bars after dialogs, notifications, etc.)
49+
if (hasFocus) hideSystemBars()
50+
}
51+
52+
private fun hideSystemBars() {
53+
WindowInsetsControllerCompat(window, window.decorView).apply {
54+
hide(WindowInsetsCompat.Type.systemBars())
55+
systemBarsBehavior =
56+
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
57+
}
58+
}
59+
60+
// Back press is handled entirely by the ViewModel — no override needed here.
61+
// ContentView / each screen handles BackHandler composable where appropriate.
62+
}

app/src/main/java/com/greengo/app/data/AppStateViewModel.kt

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,36 +14,46 @@ class AppStateViewModel : ViewModel() {
1414

1515
fun init(context: Context) {
1616
prefs = context.getSharedPreferences("greengo_prefs", Context.MODE_PRIVATE)
17-
_theme.value = AppTheme.fromRawValue(prefs.getString("appTheme", "mint") ?: "mint")
18-
_triviaScore.value = prefs.getInt("score_trivia", 0)
19-
_memoryScore.value = prefs.getInt("score_memory", 0)
20-
_oceanScore.value = prefs.getInt("score_ocean", 0)
21-
_skipMapInfo.value = prefs.getBoolean("skip_mapInfo", false)
17+
_theme.value = AppTheme.fromRawValue(prefs.getString("appTheme", "mint") ?: "mint")
18+
_triviaScore.value = prefs.getInt("score_trivia", 0)
19+
_memoryScore.value = prefs.getInt("score_memory", 0)
20+
_oceanScore.value = prefs.getInt("score_ocean", 0)
21+
_skipMapInfo.value = prefs.getBoolean("skip_mapInfo", false)
2222
_skipOceanInfo.value = prefs.getBoolean("skip_oceanInfo", false)
23-
_skipMemoryInfo.value = prefs.getBoolean("skip_memoryInfo", false)
23+
_skipMemoryInfo.value= prefs.getBoolean("skip_memoryInfo", false)
2424
}
2525

2626
// ── Navigation ────────────────────────────────────────────────────────────
27+
// Rules:
28+
// • Splash is NEVER added to the back stack — it's a one-way gate.
29+
// • Home is the root. Back from Home exits the app.
30+
// • All other screens push to the stack normally.
2731

2832
private val _screen = MutableStateFlow<Screen>(Screen.Splash)
2933
val screen: StateFlow<Screen> = _screen.asStateFlow()
3034

3135
private val backStack = mutableListOf<Screen>()
3236

3337
fun navigate(screen: Screen) {
34-
backStack.add(_screen.value)
38+
val current = _screen.value
39+
// Never push Splash onto the back stack
40+
if (current !is Screen.Splash) {
41+
backStack.add(current)
42+
}
3543
_screen.value = screen
3644
}
3745

3846
fun navigateBack() {
3947
if (backStack.isNotEmpty()) {
4048
_screen.value = backStack.removeLast()
4149
}
50+
// If stack is empty we're at Home — caller handles app exit
4251
}
4352

44-
fun canNavigateBack(): Boolean {
45-
return backStack.isNotEmpty()
46-
}
53+
fun canNavigateBack(): Boolean = backStack.isNotEmpty()
54+
55+
// Home is the root — back from Home should exit the app
56+
fun isAtRoot(): Boolean = _screen.value is Screen.Home && backStack.isEmpty()
4757

4858
// ── Theme ─────────────────────────────────────────────────────────────────
4959

@@ -104,20 +114,9 @@ class AppStateViewModel : ViewModel() {
104114
private val _skipMemoryInfo = MutableStateFlow(false)
105115
val skipMemoryInfo: StateFlow<Boolean> = _skipMemoryInfo.asStateFlow()
106116

107-
fun setSkipMapInfo(v: Boolean) {
108-
_skipMapInfo.value = v
109-
prefs.edit { putBoolean("skip_mapInfo", v) }
110-
}
111-
112-
fun setSkipOceanInfo(v: Boolean) {
113-
_skipOceanInfo.value = v
114-
prefs.edit { putBoolean("skip_oceanInfo", v) }
115-
}
116-
117-
fun setSkipMemoryInfo(v: Boolean) {
118-
_skipMemoryInfo.value = v
119-
prefs.edit { putBoolean("skip_memoryInfo", v) }
120-
}
117+
fun setSkipMapInfo(v: Boolean) { _skipMapInfo.value = v; prefs.edit { putBoolean("skip_mapInfo", v) } }
118+
fun setSkipOceanInfo(v: Boolean) { _skipOceanInfo.value = v; prefs.edit { putBoolean("skip_oceanInfo", v) } }
119+
fun setSkipMemoryInfo(v: Boolean) { _skipMemoryInfo.value = v; prefs.edit { putBoolean("skip_memoryInfo", v) } }
121120

122121
fun resetPreferences() {
123122
setSkipMapInfo(false); setSkipOceanInfo(false); setSkipMemoryInfo(false)
@@ -128,4 +127,4 @@ class AppStateViewModel : ViewModel() {
128127
resetPreferences()
129128
setTheme(AppTheme.MINT)
130129
}
131-
}
130+
}

app/src/main/java/com/greengo/app/ui/ContentView.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.greengo.app.ui
22

3+
import androidx.activity.compose.BackHandler
34
import androidx.compose.animation.AnimatedContent
45
import androidx.compose.animation.ExperimentalAnimationApi
56
import androidx.compose.animation.core.tween
@@ -30,17 +31,19 @@ import com.greengo.app.ui.screens.TriviaScoreScreen
3031
import com.greengo.app.ui.screens.TriviaScreen
3132
import com.greengo.app.ui.screens.WildRecallScreen
3233

33-
// ─────────────────────────────────────────────────────────────────────────────
34-
// MARK: - ContentView
35-
// Root composable: reads screen from ViewModel, renders matching screen.
36-
// AnimatedContent provides the .easeInOut(duration: 0.22) crossfade equivalent.
37-
// ─────────────────────────────────────────────────────────────────────────────
38-
3934
@OptIn(ExperimentalAnimationApi::class)
4035
@Composable
4136
fun ContentView(vm: AppStateViewModel) {
4237
val screen by vm.screen.collectAsState()
4338

39+
// Global back handler:
40+
// - On Splash: do nothing (can't go back, splash is one-way)
41+
// - On Home with empty stack: do nothing (let Android handle — exits app)
42+
// - Everywhere else: pop the back stack
43+
BackHandler(enabled = screen !is Screen.Splash && !vm.isAtRoot()) {
44+
vm.navigateBack()
45+
}
46+
4447
AnimatedContent(
4548
targetState = screen,
4649
transitionSpec = {

0 commit comments

Comments
 (0)