Files
roadwave/docs/regles-metier/05-interactions-navigation.md

540 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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-014 - Stratégie de Permissions](../adr/014-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
> ⚠️ **Architecture Decision Record** : Voir [ADR-010](../adr/010-commandes-volant.md) pour les détails techniques complets
**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]
```
---
### 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