feat: swipe-back vers l'accueil depuis les scores et l'historique
This commit is contained in:
2 files changed
+154
-9
No files matched your search
@@ -14,6 +14,7 @@ import com.livrets.ui.GameScreen
|
||||
import com.livrets.ui.HistoryScreen
|
||||
import com.livrets.ui.HomeScreen
|
||||
import com.livrets.ui.ResultsScreen
|
||||
import com.livrets.ui.SwipeBack
|
||||
import kotlinx.coroutines.launch
|
||||
import okio.Path.Companion.toPath
|
||||
|
||||
@@ -33,6 +34,10 @@ fun App(
|
||||
val history = remember { HistoryRepository("$filesDirPath/history.json".toPath()) }
|
||||
var screen by remember { mutableStateOf<Screen>(Screen.Home) }
|
||||
|
||||
val homePreview: @Composable () -> Unit = {
|
||||
HomeScreen(onPlay = {}, onHistory = {})
|
||||
}
|
||||
|
||||
when (val current = screen) {
|
||||
Screen.Home -> HomeScreen(
|
||||
onPlay = { config -> screen = Screen.Game(config) },
|
||||
@@ -48,15 +53,25 @@ fun App(
|
||||
onQuit = { screen = Screen.Home },
|
||||
)
|
||||
|
||||
is Screen.Results -> ResultsScreen(
|
||||
result = current.result,
|
||||
onReplay = { screen = Screen.Game(current.result.config) },
|
||||
onHome = { screen = Screen.Home },
|
||||
)
|
||||
|
||||
Screen.History -> HistoryScreen(
|
||||
history = history,
|
||||
is Screen.Results -> SwipeBack(
|
||||
onBack = { screen = Screen.Home },
|
||||
)
|
||||
background = homePreview,
|
||||
) {
|
||||
ResultsScreen(
|
||||
result = current.result,
|
||||
onReplay = { screen = Screen.Game(current.result.config) },
|
||||
onHome = { screen = Screen.Home },
|
||||
)
|
||||
}
|
||||
|
||||
Screen.History -> SwipeBack(
|
||||
onBack = { screen = Screen.Home },
|
||||
background = homePreview,
|
||||
) {
|
||||
HistoryScreen(
|
||||
history = history,
|
||||
onBack = { screen = Screen.Home },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,30 @@
|
||||
package com.livrets.ui
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.gestures.awaitEachGesture
|
||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.PointerEventPass
|
||||
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.input.pointer.positionChange
|
||||
import androidx.compose.ui.input.pointer.util.VelocityTracker
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlin.math.abs
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object SwipeBackGesture {
|
||||
val EdgeZone = 24.dp
|
||||
@@ -13,3 +37,109 @@ object SwipeBackGesture {
|
||||
velocityDpPerSec >= CommitVelocityDpPerSec
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SwipeBack(
|
||||
onBack: () -> Unit,
|
||||
background: @Composable () -> Unit,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val offset = remember { Animatable(0f) }
|
||||
val edgeZonePx = with(density) { SwipeBackGesture.EdgeZone.toPx() }
|
||||
var widthPx by remember { mutableFloatStateOf(0f) }
|
||||
val showBackground by remember { derivedStateOf { offset.value > 0f } }
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.onSizeChanged { widthPx = it.width.toFloat() },
|
||||
) {
|
||||
if (showBackground) {
|
||||
Box(Modifier.fillMaxSize()) { background() }
|
||||
}
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer { translationX = offset.value }
|
||||
.pointerInput(Unit) {
|
||||
awaitEachGesture {
|
||||
val down = awaitFirstDown()
|
||||
if (down.position.x > edgeZonePx) return@awaitEachGesture
|
||||
|
||||
val tracker = VelocityTracker()
|
||||
tracker.addPosition(down.uptimeMillis, down.position)
|
||||
var dragging = false
|
||||
var accumulatedX = 0f
|
||||
var accumulatedY = 0f
|
||||
var dragged = 0f
|
||||
|
||||
while (true) {
|
||||
val event = awaitPointerEvent(PointerEventPass.Main)
|
||||
val change = event.changes.firstOrNull { it.id == down.id } ?: break
|
||||
|
||||
if (change.isConsumed && !change.pressed && change.previousPressed) {
|
||||
if (dragging) {
|
||||
scope.launch { offset.animateTo(0f, tween(200)) }
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (!dragging && change.isConsumed) break
|
||||
|
||||
val delta = change.positionChange()
|
||||
accumulatedX += delta.x
|
||||
accumulatedY += delta.y
|
||||
|
||||
if (dragging) {
|
||||
change.consume()
|
||||
tracker.addPosition(change.uptimeMillis, change.position)
|
||||
dragged = (dragged + delta.x).coerceAtLeast(0f)
|
||||
scope.launch { offset.snapTo(dragged) }
|
||||
} else {
|
||||
val slop = viewConfiguration.touchSlop
|
||||
if (abs(accumulatedX) > slop || abs(accumulatedY) > slop) {
|
||||
if (abs(accumulatedX) > abs(accumulatedY)) {
|
||||
dragging = true
|
||||
change.consume()
|
||||
tracker.resetTracking()
|
||||
tracker.addPosition(change.uptimeMillis, change.position)
|
||||
dragged = accumulatedX.coerceAtLeast(0f)
|
||||
scope.launch { offset.snapTo(dragged) }
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (change.changedToUpIgnoreConsumed()) {
|
||||
if (dragging) {
|
||||
val velocityDpPerSec =
|
||||
tracker.calculateVelocity().x / density.density
|
||||
val commit = SwipeBackGesture.shouldCommit(
|
||||
offsetPx = dragged,
|
||||
containerWidthPx = widthPx,
|
||||
velocityDpPerSec = velocityDpPerSec,
|
||||
)
|
||||
scope.launch {
|
||||
if (commit) {
|
||||
offset.snapTo(dragged)
|
||||
offset.animateTo(widthPx, tween(200))
|
||||
onBack()
|
||||
} else {
|
||||
offset.snapTo(dragged)
|
||||
offset.animateTo(0f, tween(200))
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user