Files
livrets/docs/superpowers/specs/2026-08-28-swipe-back-design.md

3.2 KiB

Design : swipe-back vers l'accueil — Livrets

Date : 2026-08-28 Statut : Validé par le produit

1. Objectif

Permettre de revenir au menu principal par un geste de swipe (glissement depuis le bord gauche vers la droite, style iOS) depuis l'écran des scores (Results) et l'historique (History), en complément des boutons existants. Fonctionne au doigt (Android/iOS) et à la souris (desktop).

2. Décisions clés

Décision Choix
Direction Horizontal, bord gauche → droite (convention iOS back)
Écrans actifs Results + History (pas Game : drag de jeu, pas de back simple)
Feedback Preview interactive : l'écran suit le doigt, Home révélé derrière
Validation offset > 40 % de la largeur OU vélocité > seuil, sinon retour en place animé
Zone de démarrage 24 dp du bord gauche uniquement (préserve le scroll vertical des listes)
Implémentation Wrapper SwipeBack réutilisable + Animatable + pointerInput custom
Desktop Drag souris : même chemin de code (pointerInput capte la souris)

Rejeté : backstack général + transitions animées (refactor disproportionné pour une profondeur de navigation = 1) ; API anchoredDraggable (pas de détection bord-native, code custom nécessaire autour) ; swipe vertical descendant.

3. Architecture

  • sharedUI/src/commonMain/kotlin/com/livrets/ui/SwipeBack.kt (nouveau) :
    @Composable
    fun SwipeBack(
        onBack: () -> Unit,
        background: @Composable () -> Unit,
        content: @Composable () -> Unit
    )
    
    • Structure : Box { background(); Box(translationX = offset) { content() } }
    • Fond composé uniquement quand offset > 0 (aucun coût hors drag)
    • Détection : pointerInput + awaitEachGesture — toucher initial dans les 24 dp du bord gauche, premier dépassement de slop horizontal (drag vertical abandonné, scroll des listes intact)
    • Suivi : offset Animatable(0f) clampé ≥ 0, appliqué via graphicsLayer.translationX (pas de recomposition de layout)
    • Relâchement : back si offset > 40 % largeur ou vélocité ≥ 1000 dp/s (seuil fling, converti en px/s via densité) ; sinon animateTo(0) ; à la validation animateTo(largeur) puis onBack()
  • Constantes de geste (24 dp, 40 %, vélocité) centralisées dans un objet dédié pour testabilité
  • App.kt : Results et History wrappés par SwipeBack(onBack = { screen = Screen.Home }, background = { HomeScreen(...) })

4. Faisabilité

pointerInput/Animatable/graphicsLayer sont dans Compose Multiplatform 1.12 (déjà en dépendance), disponibles sur les 3 cibles ; la souris est un pointer standard côté desktop.

5. Tests & vérifications

  • Test unitaire JVM : logique de décision de relâchement (offset/vélocité → back ou settle) dans commonTest
  • Vérification manuelle : drag souris sur desktop ; swipe sur device/simulateur si disponible
  • Régression : :sharedUI:jvmTest + ./gradlew build (les 3 cibles)

6. Hors périmètre

  • BackHandler Android (le bouton système ferme l'activité — comportement préexistant, suite possible)
  • Animation parallaxe de l'écran Home pendant le drag
  • Geste de retour sur Game ou en profondeur (backstack)