## 5. Interactions et navigation

### 5.1 File d'attente et commande "Suivant"

**Décision** : Pré-calcul 5 contenus avec insertion prioritaire pour points géographiques

**File d'attente** :
- **5 contenus pré-calculés** en cache (Redis)
- Recalcul automatique si :
  - Déplacement >10km
  - Toutes les 10 minutes (rafraîchissement contenu)
  - File d'attente <3 contenus restants

**Insertion prioritaire géo-ancrée (mode voiture uniquement)** :

**Détection** :
- Calcul ETA (Estimated Time of Arrival) via API GPS native iOS/Android
- Notification déclenchée **7 secondes avant** d'arriver au point GPS
- Si vitesse < 5 km/h ET distance < 50m → notification immédiate
- ⚠️ **App doit être ouverte** (pas de détection en arrière-plan en mode voiture)

**Notification** :
- **Sonore uniquement** : bip court ou son personnalisé RoadWave
- **Visuelle minimale** : icône selon type de contenu (🏛️ culture, 👨‍👩‍👧 famille, 🎵 musique, etc.)
- **Compteur visible** : 7...6...5...4...3...2...1 (décompte des secondes)
- **Pas de texte affiché** (éviter distraction conducteur)
- **Pas de bouton "Annuler"** : seul le bouton "Suivant" permet validation

**Actions utilisateur** :
1. User entend notification sonore + voit icône et compteur
2. User appuie "Suivant" dans les 7 secondes → décompte 5s démarre
3. Pendant décompte : contenu actuel continue, compteur visible (5...4...3...2...1)
4. Si contenu actuel se termine pendant décompte → contenu suivant du buffer démarre
5. À la fin du décompte → contenu géolocalisé démarre (fade out/in 0.3s)

**Si user n'appuie pas sur "Suivant"** :
- Notification disparaît après 7 secondes
- Contenu géolocalisé est perdu (pas d'insertion dans file)
- Pas de nouveau contenu géolocalisé pendant **10 minutes** (éviter spam)

**Limitation anti-spam** :
- Maximum **6 contenus géolocalisés par heure**
- Timer reset toutes les heures (rolling window)
- Exception : séquences d'un même audio-guide multi-séquences (comptent comme 1)
- Si quota atteint : notifications suivantes ignorées jusqu'à libération du quota

**Invalidation immédiate** :
- Utilisateur change ses préférences (curseurs géo/découverte/politique)
  - ⚠️ **Modification bloquée si vitesse GPS >10 km/h** (sécurité routière)
- Live démarre d'un créateur suivi dans la zone

**Implémentation** :
```
Redis cache :
  - Clé : user:{user_id}:queue
  - Structure : [content_1, content_2, ..., content_5]
  - Métadonnées : {last_lat, last_lon, computed_at, mode: "voiture"|"pieton"}
  - TTL : 15 minutes

Tracking GPS temps réel (mobile) :
  - Vérification toutes les 1 seconde
  - Calcul ETA vers points géolocalisés proches (rayon 500m)
  - Si ETA ≤ 7s → trigger notification
  - Historique GPS : 30 derniers points pour calcul vitesse moyenne

Quota anti-spam (Redis) :
  - Clé : user:{user_id}:geo_quota
  - Structure : sorted set avec timestamps des 6 derniers contenus
  - TTL : 1 heure
  - Vérification avant notification : ZCOUNT pour compter contenus dernière heure

Cooldown après ignorance (Redis) :
  - Clé : user:{user_id}:geo_cooldown
  - TTL : 10 minutes
  - Set après notification ignorée
```

**Justification** :
- **Expérience fluide** : pas de latence au clic "Suivant"
- **Réactivité géo** : contenu local inséré immédiatement
- **Coût optimisé** : recalcul uniquement si nécessaire
- **Sécurité** : pas de modification en conduite

---

### 5.1.2 Mode piéton (audio-guides)

**Décision** : Notifications push en arrière-plan avec rayon large

**Contexte** :
- Mode piéton détecté automatiquement si vitesse moyenne < 5 km/h
- Cas d'usage : visites à pied, musées, monuments, quartiers historiques
- User n'a pas besoin d'avoir l'app ouverte
- ⚠️ **Fonctionnalité optionnelle** : requiert permission "localisation en arrière-plan" (activée par user)

**Détection** :
- App peut être en arrière-plan (si permission accordée)
- Rayon de détection : **200 mètres** autour du point GPS
- Geofencing iOS/Android pour minimiser consommation batterie
- Permission demandée uniquement si user active "Notifications audio-guides piéton" dans settings

**Notification push système** :

Format :
```
Titre : "Audio-guide à proximité"
Body : "[Nom du contenu] - [Nom créateur]"
Action : Tap → ouvre app sur le contenu
```

Exemple :
```
Audio-guide à proximité
Musée du Louvre : La Joconde - @paris_museum
```

**Permissions requises** :

⚠️ **Important** : RoadWave utilise une **stratégie de permissions progressive** pour maximiser l'acceptation utilisateur et la validation stores.

**Étape 1 - Permission de base (tous utilisateurs)** :
- iOS : "Allow While Using App" (`locationWhenInUse`)
- Android : `ACCESS_FINE_LOCATION`
- **Demandée** : Au premier lancement (onboarding)
- **Permet** : Mode voiture complet ✅

**Étape 2 - Permission arrière-plan (optionnelle, mode piéton uniquement)** :
- iOS : "Allow Always" (`locationAlways`)
- Android : `ACCESS_BACKGROUND_LOCATION`
- **Demandée** : Uniquement si user active "Notifications audio-guides piéton" dans settings
- **Précédée** : Écran d'éducation expliquant l'usage (requis stores)
- **Permet** : Mode piéton avec notifications push en arrière-plan ✅

**Si permission arrière-plan refusée** :
- Mode piéton **désactivé** (toggle grisé dans settings)
- Mode voiture reste **pleinement fonctionnel**
- Audio-guides accessibles en mode **manuel** (user ouvre app, lance contenu)
- **Garantie RGPD** : App utilisable sans permission arrière-plan ✅

iOS (`Info.plist`) :
```xml
<key>NSLocationWhenInUseUsageDescription</key>
<string>RoadWave utilise votre position pour vous proposer des contenus audio géolocalisés adaptés à votre trajet en temps réel.</string>

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Si vous activez les notifications audio-guides piéton, RoadWave peut vous alerter lorsque vous passez près d'un monument ou musée, même quand l'app est en arrière-plan. Cette fonctionnalité est optionnelle et peut être désactivée à tout moment dans les réglages.</string>
```

Android (`AndroidManifest.xml`) :
```xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
```

> 📋 **Référence technique** : Voir [ADR-010 - Stratégie de Permissions](../adr/010-frontend-mobile.md#stratégie-de-permissions-iosandroid) pour détails d'implémentation.

**Disclosure avant demande permission** (Android requis, iOS recommandé) :

Écran affiché avant demande permission "Always Location" :

```
┌────────────────────────────────────────┐
│ 📍 Notifications audio-guides piéton   │
├────────────────────────────────────────┤
│ Pour vous alerter d'audio-guides à     │
│ proximité même quand vous marchez avec │
│ l'app fermée, RoadWave a besoin de     │
│ votre position en arrière-plan.        │
│                                        │
│ Votre position sera utilisée pour :   │
│ ✅ Détecter monuments à 200m           │
│ ✅ Vous envoyer une notification       │
│                                        │
│ Votre position ne sera jamais :        │
│ ❌ Vendue à des tiers                  │
│ ❌ Utilisée pour de la publicité       │
│                                        │
│ Cette fonctionnalité est optionnelle.  │
│ Vous pouvez utiliser RoadWave sans     │
│ cette permission.                      │
│                                        │
│ [Continuer] [Non merci]                │
│                                        │
│ Plus d'infos : Politique confidentialité│
└────────────────────────────────────────┘
```

**Si user refuse** :
- Mode piéton désactivé (uniquement mode voiture disponible)
- App fonctionne normalement avec permission "When In Use"
- Audio-guides accessibles en mode manuel (user ouvre app, sélectionne contenu)

**Comportement après tap sur notification** :
1. User tap notification push
2. App s'ouvre sur la page du contenu
3. User peut démarrer la lecture manuellement
4. Navigation libre (voir section 16.2 pour audio-guides piéton)

**Basculement automatique voiture ↔ piéton** :

Détection par vitesse GPS moyenne sur 30 secondes :
- Vitesse < 5 km/h (stable 10s) → mode piéton
- Vitesse ≥ 5 km/h (stable 10s) → mode voiture

Changements de mode :

| Mode actuel | Vitesse détectée | Nouveau mode | Effet |
|-------------|------------------|--------------|-------|
| Piéton | ≥ 5 km/h | Voiture | Notifications push → sonores + icône (app ouverte requise) |
| Voiture | < 5 km/h | Piéton | Notifications sonores → push arrière-plan |

**Pas de popup confirmation** :
- Basculement transparent et automatique
- User n'a rien à faire
- Hysteresis (10s) pour éviter basculements intempestifs

**Quota anti-spam mode piéton** :
- Même limitation que mode voiture : **6 contenus/heure**
- Cooldown 10 min si notification ignorée (app pas ouverte après tap)

**Justification** :
- ✅ Expérience adaptée aux visites à pied (rayon large, pas de timing précis)
- ✅ Économie batterie (geofencing natif iOS/Android)
- ✅ User peut garder téléphone en poche
- ✅ Basculement automatique = pas de friction

---

### 5.2 Commande "Précédent"

**Décision** : Comportement smart selon progression écoute

**Règles** :

| Situation | Temps écouté | Action "Précédent" |
|-----------|--------------|-------------------|
| **Début de contenu** | <10 secondes | Retour au contenu précédent (position exacte) |
| **Milieu/fin** | ≥10 secondes | Replay contenu actuel depuis le début |
| **Premier de session** | N/A | Replay depuis début (rien avant) |

**Historique de navigation** :
- **10 contenus maximum** en mémoire (Redis List)
- Structure : `[{content_id, position_seconds, listened_at}, ...]`
- FIFO : au-delà de 10, suppression du plus ancien

**Exemple scénario** :
```
Utilisateur écoute :
1. Contenu A → écoute 5s → "Suivant"
2. Contenu B → écoute 2min30 → "Suivant"
3. Contenu C → écoute 5s → "Précédent"
   → Retour Contenu B à 2min30 (car >10s)
4. Sur Contenu B → "Précédent"
   → Retour Contenu A à 5s (position exacte)
```

**Interface (responsabilité front)** :
- ❌ Pas de message UI
- ✅ Progress bar revient au début ou à position exacte
- ✅ Animation fluide (transition 0.3s)

**Justification** :
- **UX intuitive** : comportement standard Spotify/YouTube
- **Pas de frustration** : si début, vraiment revenir en arrière
- **Simplicité** : règle unique (seuil 10s)

---

### 5.3 Interactions au volant : Like automatique et engagement

**Décision** : Like automatique basé sur le temps d'écoute

**Problème technique identifié** :
- iOS et Android ne supportent **pas nativement** les appuis longs ou doubles-appuis sur les commandes média
- Les commandes physiques au volant varient selon les véhicules (pas de bouton "Pause" dédié sur beaucoup de modèles)
- Système de double-appui/appui long = **non-intuitif** et **risques sécurité** (regarder écran pour feedback)

---

#### Commandes au volant simplifiées

**Actions disponibles** (100% compatibles tous véhicules) :

| Commande physique | Action RoadWave |
|-------------------|-----------------|
| **Suivant** | Passer au contenu suivant |
| **Précédent** | Revenir au contenu précédent (règle 10s, voir section 5.2) |
| **Play/Pause** | Pause/reprise lecture (fade out 0.3s) |

**Aucune action complexe au volant** → Sécurité routière maximale.

---

#### Like automatique implicite

**Principe** : Le système détecte automatiquement l'intérêt utilisateur selon le temps d'écoute.

**Règles d'attribution** :

| Durée écoutée | Action automatique | Points jauge | Justification |
|---------------|-------------------|--------------|---------------|
| **≥ 80% du contenu** | Like renforcé | +2.0 | Écoute quasi-complète = fort intérêt |
| **30-79% du contenu** | Like standard | +1.0 | Écoute significative = intérêt |
| **< 30% du contenu** | Pas de like | 0 | Écoute trop courte |
| **Skip après <10s** | Signal négatif | -0.5 | Désintérêt marqué |

**Exemples concrets** :
```
Contenu de 3 minutes (180s) :
- Écoute 2min30 (83%) → Like renforcé (+2 points)
- Écoute 1min15 (42%) → Like standard (+1 point)
- Écoute 30s (17%) puis skip → Pas de like
- Skip après 5s → Signal négatif (-0.5 point)

Contenu de 15 minutes (900s) :
- Écoute 13min (87%) → Like renforcé (+2 points)
- Écoute 6min (40%) → Like standard (+1 point)
```

---

#### Actions complémentaires (mode piéton uniquement)

**Interface mobile** (vitesse < 5 km/h) :

| Action | Moyen | Effet |
|--------|-------|-------|
| **Like explicite** | Bouton cœur | +2 points jauge (même si déjà liké auto) |
| **Unlike** | Re-clic cœur (toggle) | -2 points jauge |
| **Abonnement** | Bouton "S'abonner" profil créateur | +5 points toutes jauges tags créateur |
| **Désabonnement** | Bouton "Se désabonner" | -5 points |
| **Signalement** | Menu contextuel "⋮" | Ouverture flux modération |

**Feedback visuel** :
- **Like automatique** : Badge discret "♥ Ajouté à vos favoris" (2s, bas de l'écran)
- **Like explicite** : Animation cœur rouge + vibration courte
- **Abonnement** : Animation étoile dorée + badge "Abonné ✓"

**Disponibilité** :
- ✅ Mode piéton (vitesse < 5 km/h) : toutes les actions disponibles
- ❌ Mode voiture (vitesse ≥ 5 km/h) : aucune de ces actions (sauf like automatique)

---

#### Gestion impacts jauges (algorithme)

**Like automatique** :
- Like renforcé (≥80%) → **+2% jauges** de tous les tags du contenu
- Like standard (30-79%) → **+1% jauges** des tags du contenu
- Signal négatif (skip <10s) → **-0.5% jauges** des tags du contenu

**Actions explicites** :
- Like manuel → **+2% jauges** (cumulable avec like auto)
- Unlike → **-2% jauges**
- Abonnement → **+5% toutes jauges** tags créateur
- Désabonnement → **-5% toutes jauges**

**Persistance** :
- Événements stockés en base (table `listen_events`)
- Mise à jour jauges : **immédiate** (Redis) + **async batch** (PostgreSQL)

---

#### Implémentation technique

**Backend** (Go) :

```go
type ListenEvent struct {
    UserID       string
    ContentID    string
    StartedAt    time.Time
    StoppedAt    time.Time
    Duration     int     // secondes écoutées
    ContentTotal int     // durée totale contenu
    Percentage   float64 // duration / contentTotal * 100
    Action       string  // "completed", "skipped", "paused"
}

func ProcessListenEvent(event ListenEvent) {
    percentage := event.Percentage

    // Signal négatif fort
    if event.Action == "skipped" && event.Duration < 10 {
        UpdateJauges(event.UserID, event.ContentID, -0.5)
        return
    }

    // Like automatique
    if percentage >= 80 {
        AutoLike(event.UserID, event.ContentID, 2.0) // Renforcé
    } else if percentage >= 30 {
        AutoLike(event.UserID, event.ContentID, 1.0) // Standard
    }
    // < 30% : pas de like
}
```

**Mobile** (iOS/Android) :

```swift
// iOS - Tracking écoute
class AudioPlayerManager {
    var startTime: Date?
    let contentDuration: TimeInterval

    func onPlay() {
        startTime = Date()
    }

    func onStop(action: String) { // "completed" | "skipped" | "paused"
        guard let start = startTime else { return }
        let duration = Date().timeIntervalSince(start)
        let percentage = (duration / contentDuration) * 100

        // API call
        API.track(ListenEvent(
            contentId: currentContentId,
            duration: Int(duration),
            percentage: percentage,
            action: action
        ))
    }
}
```

---

#### Justification

**Avantages** :
- ✅ **Sécurité routière maximale** : aucune action complexe au volant
- ✅ **UX intuitive** : comportement standard industrie (Spotify, YouTube Music, Deezer)
- ✅ **Compatibilité 100%** : fonctionne sur tous véhicules, tous OS
- ✅ **Engagement amélioré** : tous les contenus écoutés génèrent des signaux
- ✅ **Algorithme plus précis** : données granulaires (30%, 50%, 80%, 100%)
- ✅ **Simplicité développement** : pas de workarounds complexes iOS/Android

**Inconvénients mitigés** :
- ⚠️ Pas de like explicite en conduite → **Mitigation** : like automatique + vocal (CarPlay/Android Auto)
- ⚠️ Pas d'abonnement en conduite → **Mitigation** : liste "Créateurs à découvrir" dans app
- ⚠️ Like automatique peut surprendre → **Mitigation** : onboarding clair + unlike possible

---

#### Communication utilisateurs (onboarding)

**Écran onboarding 1** :
```
🚗 Conduite sécurisée

RoadWave détecte automatiquement vos goûts
selon vos écoutes.

Plus vous écoutez longtemps, plus
l'algorithme s'améliore !

[Suivant]
```

**Écran onboarding 2** :
```
❤️ Likes automatiques

Pas besoin de liker manuellement :
si vous écoutez >50% d'un contenu,
on comprend que vous aimez !

[Suivant]
```

**Écran onboarding 3** :
```
⏸️ Commandes simples

Utilisez les boutons au volant :
• Suivant → Prochain contenu
• Précédent → Contenu d'avant
• Pause → Mettre en pause

[Commencer]
```

---

#### Implémentation Technique (Backend)

**Architecture** : 2 services séparés pour responsabilités distinctes

1. **Gauge Calculation Service** :
   - Calcule l'ajustement basé sur % écoute (≥80% → +2%, 30-79% → +1%, skip <10s → -0.5%)
   - Retourne un ajustement absolu (float64, en points de pourcentage)
   - **Stateless** : logique métier pure, pas d'accès DB
   - **Testable** : unitairement sans dépendances

2. **Gauge Update Service** :
   - Applique l'ajustement aux jauges concernées (addition/soustraction)
   - Applique les bornes [0, 100] (via fonction `clamp`)
   - Gère les multi-tags : si contenu a N tags → mise à jour de N jauges
   - Persiste : Redis (immédiat) + PostgreSQL (batch async)

**Séparation des responsabilités** :
- ✅ **Calculation** : Logique métier pure (réutilisable pour auto-like, skip, actions manuelles)
- ✅ **Update** : Persistance et contraintes techniques

**Pattern de calcul** :
```go
// ✅ CORRECT : Addition de points absolus
newValue := currentValue + adjustment
newValue = clamp(newValue, 0.0, 100.0)

// ❌ INCORRECT : Multiplication (pourcentage relatif)
newValue := currentValue * (1 + adjustment/100)  // NE PAS FAIRE
```

**Persistance** :
- **Redis** : Mise à jour immédiate (latence <10ms)
- **PostgreSQL** : Batch async toutes les 5 minutes (cohérence finale)
- Raison : Jauges consultées fréquemment (recommandations temps réel)

---

### 5.4 Lecture en boucle et enchaînement

**Décision** : Passage automatique après 2s + insertion pub paramétrable

**Fin de contenu** :
1. Audio termine → **Timer 2 secondes** démarre
2. UI overlay : "Contenu suivant dans 2s..." + barre décompte
3. Possibilité annuler : bouton "Rester sur ce contenu" (optionnel)
4. Timer atteint 0 → passage automatique au contenu suivant

**Délai selon contexte** :

| Mode | Délai | Justification |
|------|-------|---------------|
| **Standard** | 2 secondes | Temps réaction confortable |
| **Mode Kids** | 1 seconde | Attention courte enfants |
| **Live** | 0 seconde | Enchaînement immédiat |

**Insertion publicité** :
- Pub s'insère **pendant le délai de 2s** (transition naturelle)
- Fréquence : **paramétrable admin** (défaut : 1 pub / 5 contenus)
- Message : "Publicité (15s)" puis lecture pub
- ⚠️ **Jamais d'interruption** d'un contenu en cours

**Publicité skippable** :
- Durée minimale visionnage : **paramétrable** (défaut : 5 secondes)
- Bouton "Passer" apparaît après délai
- Métriques engagement : taux skip, durée écoute moyenne
- **Like et abonnement autorisés sur pub** (engagement créateur pub)

**Si aucun contenu disponible** :
1. Message : "Aucun contenu disponible dans cette zone"
2. Proposition : "Élargir la zone de recherche ?" (bouton)
3. Si accepté → relance algo avec rayon +50km
4. Sinon → lecture en pause, attente action utilisateur

**Gestion erreurs** :
- Échec chargement contenu suivant → **retry 3× avec backoff exponentiel**
- Si 3 échecs → message "Connexion instable, basculement mode offline"
- Mode offline → lecture contenus téléchargés uniquement

**Justification** :
- **Fluidité** : enchaînement naturel sans action utilisateur
- **Contrôle** : possibilité annuler pendant délai
- **Paramétrabilité pub** : évite frustration excès publicité
- **Engagement pub** : like/abonnement autorisé = monétisation créateurs pub

---

## Récapitulatif Section 5
