## 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 NSLocationWhenInUseUsageDescription RoadWave utilise votre position pour vous proposer des contenus audio géolocalisés adaptés à votre trajet en temps réel. NSLocationAlwaysAndWhenInUseUsageDescription 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. ``` Android (`AndroidManifest.xml`) : ```xml ``` > 📋 **Référence technique** : Voir [ADR-010 - Stratégie de Permissions](../../../adr/012-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