11package com.stephenwanjala.multiply.game
22
3- import androidx.activity.compose.BackHandler
4- import androidx.compose.animation.*
3+ import androidx.activity.BackEventCompat
4+ import androidx.activity.compose.PredictiveBackHandler
5+ import androidx.compose.animation.AnimatedVisibility
56import androidx.compose.animation.core.Spring
67import androidx.compose.animation.core.animateFloatAsState
78import androidx.compose.animation.core.spring
89import androidx.compose.animation.core.tween
10+ import androidx.compose.animation.expandVertically
11+ import androidx.compose.animation.fadeIn
12+ import androidx.compose.animation.fadeOut
13+ import androidx.compose.animation.scaleIn
14+ import androidx.compose.animation.scaleOut
15+ import androidx.compose.animation.shrinkVertically
16+ import androidx.compose.animation.slideInVertically
17+ import androidx.compose.animation.slideOutVertically
918import androidx.compose.foundation.Image
1019import androidx.compose.foundation.background
1120import androidx.compose.foundation.clickable
1221import androidx.compose.foundation.interaction.MutableInteractionSource
13- import androidx.compose.foundation.layout.*
22+ import androidx.compose.foundation.layout.Arrangement
23+ import androidx.compose.foundation.layout.Box
24+ import androidx.compose.foundation.layout.Column
25+ import androidx.compose.foundation.layout.ExperimentalLayoutApi
26+ import androidx.compose.foundation.layout.FlowRow
27+ import androidx.compose.foundation.layout.Row
28+ import androidx.compose.foundation.layout.Spacer
29+ import androidx.compose.foundation.layout.fillMaxSize
30+ import androidx.compose.foundation.layout.fillMaxWidth
31+ import androidx.compose.foundation.layout.height
32+ import androidx.compose.foundation.layout.padding
33+ import androidx.compose.foundation.layout.size
34+ import androidx.compose.foundation.layout.statusBarsPadding
35+ import androidx.compose.foundation.layout.width
1436import androidx.compose.foundation.shape.RoundedCornerShape
1537import androidx.compose.material.icons.Icons
1638import androidx.compose.material.icons.filled.CheckCircle
1739import androidx.compose.material.icons.filled.Star
18- import androidx.compose.material3.*
19- import androidx.compose.runtime.*
40+ import androidx.compose.material3.Card
41+ import androidx.compose.material3.CardDefaults
42+ import androidx.compose.material3.Icon
43+ import androidx.compose.material3.MaterialTheme
44+ import androidx.compose.material3.Surface
45+ import androidx.compose.material3.Text
46+ import androidx.compose.runtime.Composable
47+ import androidx.compose.runtime.getValue
48+ import androidx.compose.runtime.mutableFloatStateOf
49+ import androidx.compose.runtime.mutableStateOf
50+ import androidx.compose.runtime.remember
51+ import androidx.compose.runtime.rememberCoroutineScope
2052import androidx.compose.runtime.saveable.rememberSaveable
53+ import androidx.compose.runtime.setValue
2154import androidx.compose.ui.Alignment
2255import androidx.compose.ui.Modifier
2356import androidx.compose.ui.draw.clip
@@ -42,8 +75,15 @@ import com.stephenwanjala.multiply.game.components.animatedBackground
4275import com.stephenwanjala.multiply.game.components.glowingOrbs
4376import com.stephenwanjala.multiply.game.components.neumorphicShadow
4477import com.stephenwanjala.multiply.game.components.repeatLiquidBackground
45- import com.stephenwanjala.multiply.game.models.*
78+ import com.stephenwanjala.multiply.game.models.BubbleMathDifficulty
79+ import com.stephenwanjala.multiply.game.models.GameMode
80+ import com.stephenwanjala.multiply.game.models.GameModeSaver
81+ import com.stephenwanjala.multiply.game.models.ModeDifficulty
82+ import com.stephenwanjala.multiply.game.models.ModeDifficultySaver
83+ import com.stephenwanjala.multiply.game.models.QuizDifficulty
4684import com.stephenwanjala.multiply.ui.theme.MultiplyTheme
85+ import kotlinx.coroutines.CancellationException
86+ import kotlinx.coroutines.flow.Flow
4787import kotlinx.coroutines.launch
4888
4989@Composable
@@ -60,16 +100,29 @@ fun GameModeSelectionScreen(
60100 mutableStateOf<ModeDifficulty ?>(null )
61101 }
62102
103+ var backProgress by remember { mutableFloatStateOf(0f ) }
63104
64105 val modes = listOf (
65106 GameMode .BubbleMathBlitz (BubbleMathDifficulty .EASY ),
66107 GameMode .QuizGenius (QuizDifficulty .BEGINNER )
67108 )
68109
110+
69111 if (selectedMode != null ) {
70- BackHandler {
71- selectedMode = null
72- selectedDifficulty = null
112+ PredictiveBackHandler { progress: Flow <BackEventCompat > ->
113+ try {
114+ progress.collect { backEvent ->
115+ backProgress = backEvent.progress
116+ }
117+ selectedMode = null
118+ selectedDifficulty = null
119+ backProgress = 0f
120+ } catch (e: CancellationException ) {
121+ // Cancellation: user released the gesture without completing
122+ // Animate back to normal state
123+ backProgress = 0f
124+ throw e
125+ }
73126 }
74127 }
75128
@@ -93,7 +146,14 @@ fun GameModeSelectionScreen(
93146 modifier = Modifier
94147 .fillMaxSize()
95148 .animatedBackground()
96- .padding(24 .dp),
149+ .padding(24 .dp)
150+ // Apply predictive back animation transforms
151+ .graphicsLayer(
152+ scaleX = 1f - (backProgress * 0.05f ),
153+ scaleY = 1f - (backProgress * 0.05f ),
154+ translationX = backProgress * 100f ,
155+ alpha = 1f - (backProgress * 0.2f )
156+ ),
97157 horizontalAlignment = Alignment .CenterHorizontally
98158 ) {
99159 // Header
@@ -121,7 +181,7 @@ fun GameModeSelectionScreen(
121181
122182 Spacer (modifier = Modifier .height(32 .dp))
123183
124- // Step 1: GameMode selection - Always visible but fades/scales out when mode is selected
184+ // Step 1: GameMode selection
125185 AnimatedVisibility (
126186 visible = selectedMode == null ,
127187 enter = fadeIn(animationSpec = tween(400 , delayMillis = 200 )) +
@@ -154,7 +214,7 @@ fun GameModeSelectionScreen(
154214 modes.forEach { modeOption ->
155215 Material3GameModeCard (
156216 mode = modeOption,
157- isSelected = false , // Never show as selected in this view
217+ isSelected = false ,
158218 onClick = {
159219 selectedMode = modeOption
160220 selectedDifficulty = null
@@ -164,12 +224,12 @@ fun GameModeSelectionScreen(
164224 }
165225 }
166226
167- // Step 2: Difficulty selection - Slides up from bottom with smooth animation
227+ // Step 2: Difficulty selection
168228 AnimatedVisibility (
169229 visible = selectedMode != null ,
170230 enter = fadeIn(animationSpec = tween(400 , delayMillis = 150 )) +
171231 slideInVertically(
172- initialOffsetY = { it / 2 }, // Start from halfway down
232+ initialOffsetY = { it / 2 },
173233 animationSpec = spring(
174234 dampingRatio = Spring .DampingRatioMediumBouncy ,
175235 stiffness = Spring .StiffnessLow
@@ -222,7 +282,6 @@ fun GameModeSelectionScreen(
222282 }
223283 )
224284
225- // High Score display with smooth entrance
226285 AnimatedVisibility (
227286 visible = selectedDifficulty != null ,
228287 enter = fadeIn(animationSpec = tween(300 , delayMillis = 200 )) +
@@ -268,7 +327,6 @@ fun GameModeSelectionScreen(
268327
269328 Spacer (modifier = Modifier .height(24 .dp))
270329
271- // Action buttons with staggered animation
272330 AnimatedVisibility (
273331 visible = true ,
274332 enter = fadeIn(animationSpec = tween(300 , delayMillis = 300 )) +
@@ -296,28 +354,6 @@ fun GameModeSelectionScreen(
296354 }
297355 }
298356 )
299-
300- /*
301- OutlinedButton(
302- onClick = {
303- selectedMode = null
304- selectedDifficulty = null
305- },
306- colors = ButtonDefaults.outlinedButtonColors(
307- contentColor = MaterialTheme.colorScheme.onSurface
308- ),
309- border = ButtonDefaults.outlinedButtonBorder().copy(
310- brush = Brush.linearGradient(
311- listOf(
312- MaterialTheme.colorScheme.outline,
313- MaterialTheme.colorScheme.outline.copy(alpha = 0.5f)
314- )
315- )
316- )
317- ) {
318- Text("Back to Mode Selection")
319- }
320- */
321357 }
322358 }
323359 }
@@ -463,12 +499,11 @@ fun Material3LevelSelectionGrid(
463499) {
464500 val haptics = LocalHapticFeedback .current
465501
466-
467502 val difficultyColors = listOf (
468- MaterialTheme .colorScheme.primary, // Easy - Primary color
469- MaterialTheme .colorScheme.secondary, // Medium - Secondary color
470- MaterialTheme .colorScheme.tertiary, // Hard - Tertiary color
471- MaterialTheme .colorScheme.error // Expert - Error color for danger
503+ MaterialTheme .colorScheme.primary,
504+ MaterialTheme .colorScheme.secondary,
505+ MaterialTheme .colorScheme.tertiary,
506+ MaterialTheme .colorScheme.error
472507 )
473508
474509 FlowRow (
@@ -682,7 +717,6 @@ fun MaterialSectionTitle(text: String) {
682717 }
683718}
684719
685-
686720private val GameMode .name: String
687721 get() = when (this ) {
688722 is GameMode .BubbleMathBlitz -> " Bubble Blitz"
0 commit comments