- Ajouter ADR-018 (librairies Go) dans TECHNICAL.md - Transformer Shared en menu dépliable dans mkdocs (cohérence avec autres domaines) - Corriger listes markdown (ajout lignes vides avant listes) - Corriger line breaks dans génération BDD (étapes "Et" sur nouvelles lignes) - Ajouter script fix-markdown-lists.sh pour corrections futures Impacte 86 fichiers de documentation et 164 fichiers BDD générés.
21 KiB
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 :
- User entend notification sonore + voit icône et compteur
- User appuie "Suivant" dans les 7 secondes → décompte 5s démarre
- Pendant décompte : contenu actuel continue, compteur visible (5...4...3...2...1)
- Si contenu actuel se termine pendant décompte → contenu suivant du buffer démarre
- À 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) :
<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) :
<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 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 :
- User tap notification push
- App s'ouvre sur la page du contenu
- User peut démarrer la lecture manuellement
- 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) :
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) :
// 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
-
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
-
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 :
// ✅ 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 :
- Audio termine → Timer 2 secondes démarre
- UI overlay : "Contenu suivant dans 2s..." + barre décompte
- Possibilité annuler : bouton "Rester sur ce contenu" (optionnel)
- 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 :
- Message : "Aucun contenu disponible dans cette zone"
- Proposition : "Élargir la zone de recherche ?" (bouton)
- Si accepté → relance algo avec rayon +50km
- 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