From 1e6b1e488e6645d8fc6ddde712cde3fbfdd810d6 Mon Sep 17 00:00:00 2001 From: Rene Luria Date: Fri, 28 Aug 2026 16:56:00 +0200 Subject: [PATCH] =?UTF-8?q?refactor:=20densit=C3=A9=20vivante,=20constante?= =?UTF-8?q?=20settle,=20tests=20de=20limite=20swipe-back?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plans/2026-08-28-swipe-back.md | 22 +++++++++---------- .../kotlin/com/livrets/ui/SwipeBack.kt | 15 ++++++------- .../com/livrets/ui/SwipeBackGestureTest.kt | 22 +++++++++++++++++++ 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/docs/superpowers/plans/2026-08-28-swipe-back.md b/docs/superpowers/plans/2026-08-28-swipe-back.md index 5ef0657..9b09234 100644 --- a/docs/superpowers/plans/2026-08-28-swipe-back.md +++ b/docs/superpowers/plans/2026-08-28-swipe-back.md @@ -32,7 +32,7 @@ Spec : `docs/superpowers/specs/2026-08-28-swipe-back-design.md` - Consumes: rien (fonction pure) - Produces: `SwipeBackGesture.EdgeZone: Dp`, `SwipeBackGesture.CommitRatio: Float`, `SwipeBackGesture.CommitVelocityDpPerSec: Float`, `SwipeBackGesture.shouldCommit(offsetPx: Float, containerWidthPx: Float, velocityDpPerSec: Float): Boolean` — consommés par le Task 2 -- [ ] **Step 1: Écrire le test qui échoue** +- [x] **Step 1: Écrire le test qui échoue** Créer `sharedUI/src/commonTest/kotlin/com/livrets/ui/SwipeBackGestureTest.kt` : @@ -91,12 +91,12 @@ class SwipeBackGestureTest { } ``` -- [ ] **Step 2: Vérifier que le test échoue** +- [x] **Step 2: Vérifier que le test échoue** Run: `./gradlew :sharedUI:jvmTest --tests "com.livrets.ui.SwipeBackGestureTest"` Expected: FAIL — compilation error `unresolved reference: SwipeBackGesture` -- [ ] **Step 3: Implémenter la logique minimale** +- [x] **Step 3: Implémenter la logique minimale** Créer `sharedUI/src/commonMain/kotlin/com/livrets/ui/SwipeBack.kt` (le composable viendra au Task 2, ce fichier ne contient que l'objet pour l'instant) : @@ -118,12 +118,12 @@ object SwipeBackGesture { } ``` -- [ ] **Step 4: Vérifier que le test passe** +- [x] **Step 4: Vérifier que le test passe** Run: `./gradlew :sharedUI:jvmTest --tests "com.livrets.ui.SwipeBackGestureTest"` Expected: PASS — 4 tests -- [ ] **Step 5: Commit** +- [x] **Step 5: Commit** ```bash git add sharedUI/src/commonMain/kotlin/com/livrets/ui/SwipeBack.kt sharedUI/src/commonTest/kotlin/com/livrets/ui/SwipeBackGestureTest.kt @@ -142,7 +142,7 @@ git commit -m "feat: logique de décision du swipe-back" - Consumes: `SwipeBackGesture` (Task 1) - Produces: `@Composable fun SwipeBack(onBack: () -> Unit, background: @Composable () -> Unit, content: @Composable () -> Unit)` — utilisé dans `App.kt` -- [ ] **Step 1: Compléter `SwipeBack.kt` avec le composable** +- [x] **Step 1: Compléter `SwipeBack.kt` avec le composable** Ajouter sous l'objet `SwipeBackGesture` (le fichier final complet) : @@ -302,12 +302,12 @@ Points de conception (pour le relecteur) : - `showBackground` via `derivedStateOf` : recomposition uniquement au passage 0 ↔ >0, le `translationX` animé ne recompose pas le layout - Le fond n'est composé que pendant un drag (aucun coût hors geste) -- [ ] **Step 2: Vérifier la compilation et les tests existants** +- [x] **Step 2: Vérifier la compilation et les tests existants** Run: `./gradlew :sharedUI:jvmTest` Expected: PASS — les 20 tests existants + 4 nouveaux (24 tests) -- [ ] **Step 3: Brancher Results et History dans `App.kt`** +- [x] **Step 3: Brancher Results et History dans `App.kt`** Dans `sharedUI/src/commonMain/kotlin/com/livrets/App.kt`, remplacer (lignes 51-60) : @@ -367,12 +367,12 @@ import com.livrets.ui.ResultsScreen import com.livrets.ui.SwipeBack ``` -- [ ] **Step 4: Recompiler et relancer les tests** +- [x] **Step 4: Recompiler et relancer les tests** Run: `./gradlew :sharedUI:jvmTest` Expected: PASS — 24 tests -- [ ] **Step 5: Vérification manuelle desktop du geste** +- [x] **Step 5: Vérification manuelle desktop du geste** Run: `./gradlew :desktopApp:run` Vérifier (fenêtre 480×800) : @@ -384,7 +384,7 @@ Vérifier (fenêtre 480×800) : 6. Vérifier que le scroll de la liste d'erreurs et les clics boutons fonctionnent toujours 7. Vérifier qu'un swipe vertical dans la liste ne déclenche pas le retour -- [ ] **Step 6: Commit** +- [x] **Step 6: Commit** ```bash git add sharedUI/src/commonMain/kotlin/com/livrets/ui/SwipeBack.kt sharedUI/src/commonMain/kotlin/com/livrets/App.kt diff --git a/sharedUI/src/commonMain/kotlin/com/livrets/ui/SwipeBack.kt b/sharedUI/src/commonMain/kotlin/com/livrets/ui/SwipeBack.kt index 8551319..75f0d24 100644 --- a/sharedUI/src/commonMain/kotlin/com/livrets/ui/SwipeBack.kt +++ b/sharedUI/src/commonMain/kotlin/com/livrets/ui/SwipeBack.kt @@ -21,11 +21,12 @@ 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 +private const val SettleMillis = 200 + object SwipeBackGesture { val EdgeZone = 24.dp const val CommitRatio = 0.4f @@ -44,10 +45,8 @@ fun SwipeBack( 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 } } @@ -66,7 +65,7 @@ fun SwipeBack( .pointerInput(Unit) { awaitEachGesture { val down = awaitFirstDown() - if (down.position.x > edgeZonePx) return@awaitEachGesture + if (down.position.x > SwipeBackGesture.EdgeZone.toPx()) return@awaitEachGesture val tracker = VelocityTracker() tracker.addPosition(down.uptimeMillis, down.position) @@ -81,7 +80,7 @@ fun SwipeBack( if (change.isConsumed && !change.pressed && change.previousPressed) { if (dragging) { - scope.launch { offset.animateTo(0f, tween(200)) } + scope.launch { offset.animateTo(0f, tween(SettleMillis)) } } break } @@ -116,7 +115,7 @@ fun SwipeBack( if (change.changedToUpIgnoreConsumed()) { if (dragging) { val velocityDpPerSec = - tracker.calculateVelocity().x / density.density + tracker.calculateVelocity().x / density val commit = SwipeBackGesture.shouldCommit( offsetPx = dragged, containerWidthPx = widthPx, @@ -125,11 +124,11 @@ fun SwipeBack( scope.launch { if (commit) { offset.snapTo(dragged) - offset.animateTo(widthPx, tween(200)) + offset.animateTo(widthPx, tween(SettleMillis)) onBack() } else { offset.snapTo(dragged) - offset.animateTo(0f, tween(200)) + offset.animateTo(0f, tween(SettleMillis)) } } } diff --git a/sharedUI/src/commonTest/kotlin/com/livrets/ui/SwipeBackGestureTest.kt b/sharedUI/src/commonTest/kotlin/com/livrets/ui/SwipeBackGestureTest.kt index 2c9a035..07b6212 100644 --- a/sharedUI/src/commonTest/kotlin/com/livrets/ui/SwipeBackGestureTest.kt +++ b/sharedUI/src/commonTest/kotlin/com/livrets/ui/SwipeBackGestureTest.kt @@ -49,4 +49,26 @@ class SwipeBackGestureTest { ) ) } + + @Test + fun `pas de commit a la limite exacte de 40 pourcent sans velocite`() { + assertFalse( + SwipeBackGesture.shouldCommit( + offsetPx = 400f, + containerWidthPx = 1000f, + velocityDpPerSec = 0f, + ) + ) + } + + @Test + fun `pas de commit avec un offset negatif`() { + assertFalse( + SwipeBackGesture.shouldCommit( + offsetPx = -100f, + containerWidthPx = 1000f, + velocityDpPerSec = 0f, + ) + ) + } }