fix: intercepter le back système pour revenir à l'accueil

Le geste back Android part du bord même de l'écran : l'OS intercepte
le geste avant le SwipeBack custom (zone de départ > 24 dp) et, sans
callback back enregistré, l'activité est envoyée en arrière-plan vers
le launcher au lieu de revenir à l'écran précédent. Enregistre un
BackHandler par écran (Historique et Résultats retournent à l'accueil,
la partie ouvre le dialogue d'abandon) et active le predictive back
dans le manifest.
This commit is contained in:
herel committed 2026-08-31 09:10:48 +02:00
1 parent b3fde748d2
commit bd070ba3a4
6 files changed
+57

No files matched your search

+1
View File
@@ -4,6 +4,7 @@
<application <application
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="Livrets" android:label="Livrets"
android:enableOnBackInvokedCallback="true"
android:theme="@android:style/Theme.Material.NoActionBar"> android:theme="@android:style/Theme.Material.NoActionBar">
<activity <activity
android:name=".AppActivity" android:name=".AppActivity"
+1
View File
@@ -21,6 +21,7 @@ compose-resources = { module = "org.jetbrains.compose.components:components-reso
compose-ui-tooling-preview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "compose-multiplatform" } compose-ui-tooling-preview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "compose-multiplatform" }
compose-ui-tooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "compose-multiplatform" } compose-ui-tooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "compose-multiplatform" }
compose-ui-test = { module = "org.jetbrains.compose.ui:ui-test", version.ref = "compose-multiplatform" } compose-ui-test = { module = "org.jetbrains.compose.ui:ui-test", version.ref = "compose-multiplatform" }
compose-backhandler = { module = "org.jetbrains.compose.ui:ui-backhandler", version.ref = "compose-multiplatform" }
compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "material3" } compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "material3" }
androidx-activityCompose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } androidx-activityCompose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
androidx-fragmentKtx = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragmentKtx" } androidx-fragmentKtx = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragmentKtx" }
+1
View File
@@ -31,6 +31,7 @@ kotlin {
api(libs.compose.foundation) api(libs.compose.foundation)
api(libs.compose.ui.tooling.preview) api(libs.compose.ui.tooling.preview)
api(libs.compose.material3) api(libs.compose.material3)
api(libs.compose.backhandler)
implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.core)
@@ -7,6 +7,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.backhandler.BackHandler
import com.livrets.data.HistoryRepository import com.livrets.data.HistoryRepository
import com.livrets.game.GameConfig import com.livrets.game.GameConfig
import com.livrets.game.GameResult import com.livrets.game.GameResult
@@ -32,6 +33,13 @@ sealed interface Screen {
data object History : Screen data object History : Screen
} }
fun Screen.backTarget(): Screen? = when (this) {
Screen.Home -> null
is Screen.Game -> null
is Screen.Results -> Screen.Home
Screen.History -> Screen.Home
}
@Composable @Composable
fun App( fun App(
filesDirPath: String, filesDirPath: String,
@@ -48,6 +56,11 @@ fun App(
HomeScreen(onPlay = {}, onHistory = {}, onLeaderboard = {}) HomeScreen(onPlay = {}, onHistory = {}, onLeaderboard = {})
} }
screen.backTarget()?.let { target ->
@OptIn(androidx.compose.ui.ExperimentalComposeUiApi::class)
BackHandler { screen = target }
}
when (val current = screen) { when (val current = screen) {
Screen.Home -> HomeScreen( Screen.Home -> HomeScreen(
onPlay = { config -> screen = Screen.Game(config) }, onPlay = { config -> screen = Screen.Game(config) },
@@ -30,6 +30,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.backhandler.BackHandler
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -52,6 +53,8 @@ fun GameScreen(
LaunchedEffect(engine) { engine.start() } LaunchedEffect(engine) { engine.start() }
var showQuitDialog by remember { mutableStateOf(false) } var showQuitDialog by remember { mutableStateOf(false) }
@OptIn(androidx.compose.ui.ExperimentalComposeUiApi::class)
BackHandler { showQuitDialog = true }
if (showQuitDialog) { if (showQuitDialog) {
AlertDialog( AlertDialog(
onDismissRequest = { showQuitDialog = false }, onDismissRequest = { showQuitDialog = false },
@@ -0,0 +1,38 @@
package com.livrets
import com.livrets.game.GameConfig
import com.livrets.game.GameResult
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
class BackNavigationTest {
private val config = GameConfig(questionCount = 10, secondsPerQuestion = 5, maxTable = 10)
private val result = GameResult(
date = kotlin.time.Instant.fromEpochMilliseconds(0L),
config = config,
records = emptyList(),
)
@Test
fun `back depuis l historique ramene a l accueil`() {
assertEquals(Screen.Home, Screen.History.backTarget())
}
@Test
fun `back depuis les resultats ramene a l accueil`() {
assertEquals(Screen.Home, Screen.Results(result).backTarget())
}
@Test
fun `pas de navigation back depuis l accueil`() {
assertNull(Screen.Home.backTarget())
}
@Test
fun `pas de navigation back depuis une partie`() {
assertNull(Screen.Game(config).backTarget())
}
}