(doc) : ajout et modification de docs après arbitrage

This commit is contained in:
jpgiannetti
2026-01-31 21:09:59 +01:00
parent f99fb3c614
commit 841028d1b2
24 changed files with 3081 additions and 301 deletions

View File

@@ -0,0 +1,863 @@
# Stratégie de Permissions Géolocalisation
**Date** : 2026-01-31
**Auteur** : Architecture Mobile RoadWave
**Statut** : Approuvé
**Version** : 1.0
---
## Contexte
La géolocalisation est **critique** pour RoadWave, mais les permissions arrière-plan sont le **#1 motif de rejet** sur iOS App Store et Android Play Store.
### Problématiques Identifiées
#### iOS App Store
- **Taux de rejet ~70%** si permission "Always Location" mal justifiée
- Apple exige que l'app soit **pleinement utilisable** sans "Always Location"
- Textes `Info.plist` scrutés manuellement par reviewers humains
- Rejection si suspicion de tracking publicitaire ou vente de données
#### Android Play Store
- Depuis Android 10 : `ACCESS_BACKGROUND_LOCATION` nécessite **déclaration justifiée**
- Vidéo démo **obligatoire** montrant le flow de demande (< 30s)
- Google vérifie que la permission est **réellement optionnelle**
- Foreground service notification **obligatoire** en arrière-plan (Android 12+)
#### RGPD (Règle 02)
- Permissions doivent être **optionnelles**
- Utilisateur doit pouvoir **refuser sans pénalité**
- App doit fonctionner en **mode dégradé acceptable**
---
## Stratégie Progressive (2 Étapes)
### Vue d'Ensemble
```
┌─────────────────────────────────────────────────────────┐
│ ONBOARDING │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Étape 1: Permission "When In Use" │ │
│ │ → Mode voiture complet ✅ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│ User utilise l'app normalement
┌─────────────────────────────────────────────────────────┐
│ SETTINGS (Plus tard, si besoin) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Étape 2: Permission "Always" (optionnelle) │ │
│ │ → Mode piéton avec notifications push ✅ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
---
## Étape 1 : Permission de Base (Onboarding)
### Quand
- **Premier lancement** de l'app
- Avant de pouvoir utiliser les fonctionnalités principales
### Permission Demandée
| Platform | Permission | Nom Utilisateur |
|----------|-----------|----------------|
| **iOS** | `NSLocationWhenInUseUsageDescription` | "Allow While Using App" |
| **Android** | `ACCESS_FINE_LOCATION` | "Autorisez uniquement lorsque l'application est en cours d'utilisation" |
### Flow UI
**Écran pré-permission** (recommandé pour taux d'acceptation) :
```
┌────────────────────────────────────────┐
│ 🗺️ Bienvenue sur RoadWave │
├────────────────────────────────────────┤
│ │
│ RoadWave vous propose du contenu audio│
│ adapté à votre position en temps réel.│
│ │
│ Nous avons besoin de votre localisation│
│ pour : │
│ │
│ ✅ Recommander du contenu proche │
│ ✅ Détecter votre mode (voiture/piéton)│
│ ✅ Synchroniser avec vos trajets │
│ │
│ [Continuer] │
│ │
│ Votre vie privée est protégée │
└────────────────────────────────────────┘
```
**Puis demande système iOS/Android**
### Si Permission Acceptée
- Mode voiture **complet**
- Détection POI quand app **ouverte**
- Recommandations géolocalisées temps réel
- **Pas de demande supplémentaire** sauf si user veut mode piéton
### Si Permission Refusée
**Mode dégradé (IP2Location)** :
- Détection pays/ville via adresse IP (IP2Location Lite, voir [ADR-021](../adr/021-geolocalisation-ip.md))
- Contenus nationaux et régionaux disponibles
- Pas de contenus hyperlocaux (< 10km)
**UI** :
```
┌────────────────────────────────────────┐
│ ⚠️ Géolocalisation désactivée │
├────────────────────────────────────────┤
│ Vous écoutez des contenus de votre │
│ région (détection approximative). │
│ │
│ Pour débloquer les contenus proches : │
│ [Activer la géolocalisation] │
└────────────────────────────────────────┘
```
**Tap "Activer"**`openAppSettings()` (réglages système)
### Code d'Implémentation
```dart
// lib/presentation/onboarding/location_onboarding_screen.dart
class LocationOnboardingScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.map, size: 80, color: Colors.blue),
SizedBox(height: 32),
Text(
'Bienvenue sur RoadWave',
style: Theme.of(context).textTheme.headlineMedium,
),
SizedBox(height: 16),
Text(
'RoadWave vous propose du contenu audio '
'adapté à votre position en temps réel.',
textAlign: TextAlign.center,
),
SizedBox(height: 32),
_buildFeatureList(),
SizedBox(height: 48),
ElevatedButton(
onPressed: () => _requestLocationPermission(context),
child: Text('Continuer'),
),
SizedBox(height: 16),
Text(
'Votre vie privée est protégée',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
),
);
}
Widget _buildFeatureList() {
return Column(
children: [
_buildFeature('Recommander du contenu proche'),
_buildFeature('Détecter votre mode (voiture/piéton)'),
_buildFeature('Synchroniser avec vos trajets'),
],
);
}
Widget _buildFeature(String text) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Icon(Icons.check_circle, color: Colors.green),
SizedBox(width: 16),
Expanded(child: Text(text)),
],
),
);
}
Future<void> _requestLocationPermission(BuildContext context) async {
final service = context.read<LocationPermissionService>();
final granted = await service.requestBasicPermission();
if (granted) {
// Navigation vers écran principal
Navigator.pushReplacementNamed(context, '/home');
} else {
// Afficher mode dégradé disponible
_showDegradedModeDialog(context);
}
}
void _showDegradedModeDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Géolocalisation désactivée'),
content: Text(
'Vous pouvez toujours utiliser RoadWave avec des contenus '
'de votre région (détection approximative).',
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
Navigator.pushReplacementNamed(context, '/home');
},
child: Text('Continuer sans GPS'),
),
TextButton(
onPressed: () async {
Navigator.pop(context);
await openAppSettings();
},
child: Text('Ouvrir réglages'),
),
],
),
);
}
}
```
---
## Étape 2 : Permission Arrière-Plan (Optionnelle)
### Quand
- User **active explicitement** "Notifications audio-guides piéton" dans Settings
- **Jamais au premier lancement**
### Permission Demandée
| Platform | Permission | Nom Utilisateur |
|----------|-----------|----------------|
| **iOS** | `NSLocationAlwaysAndWhenInUseUsageDescription` | "Allow Always" |
| **Android** | `ACCESS_BACKGROUND_LOCATION` | "Toujours autoriser" |
### Flow UI (Critique pour Validation Stores)
**1. Toggle dans Settings**
```
Settings > Notifications
┌────────────────────────────────────────┐
│ 🔔 Notifications │
├────────────────────────────────────────┤
│ Recommendations de contenu │
│ ├─ En conduite [ON] │
│ └─ Au volant [ON] │
│ │
│ Audio-guides piéton [OFF] │
│ ⓘ Nécessite localisation arrière-plan │
│ │
│ Live de créateurs suivis [ON] │
└────────────────────────────────────────┘
```
**2. Écran d'éducation (OBLIGATOIRE avant demande OS)**
```
┌────────────────────────────────────────┐
│ 📍 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é │
│ ❌ Partagée sans votre consentement │
│ │
│ Cette fonctionnalité est optionnelle. │
│ Vous pouvez utiliser RoadWave sans │
│ cette permission. │
│ │
│ [Continuer] [Non merci] │
│ │
│ Plus d'infos : Politique confidentialité│
└────────────────────────────────────────┘
```
**3. Demande système iOS/Android**
**4. Si permission accordée**
```
✅ Mode piéton activé !
Vous recevrez une notification lorsque
vous passez près d'un audio-guide.
```
**5. Si permission refusée**
```
⚠️ Mode piéton non disponible
Sans permission "Toujours autoriser",
nous ne pouvons pas détecter les
audio-guides en arrière-plan.
Vous pouvez toujours :
✅ Utiliser le mode voiture
✅ Lancer manuellement les audio-guides
[Ouvrir réglages] [Fermer]
```
### Code d'Implémentation
```dart
// lib/presentation/settings/notifications_settings_screen.dart
class NotificationsSettingsScreen extends StatefulWidget {
@override
_NotificationsSettingsScreenState createState() => _NotificationsSettingsScreenState();
}
class _NotificationsSettingsScreenState extends State<NotificationsSettingsScreen> {
bool _pedestrianModeEnabled = false;
@override
void initState() {
super.initState();
_loadPermissionStatus();
}
Future<void> _loadPermissionStatus() async {
final service = context.read<LocationPermissionService>();
final level = await service.getCurrentLevel();
setState(() {
_pedestrianModeEnabled = (level == LocationPermissionLevel.always);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Notifications')),
body: ListView(
children: [
SwitchListTile(
title: Text('Recommendations de contenu'),
subtitle: Text('En conduite'),
value: true,
onChanged: (value) { /* ... */ },
),
SwitchListTile(
title: Text('Audio-guides piéton'),
subtitle: Text('Nécessite localisation arrière-plan'),
value: _pedestrianModeEnabled,
onChanged: _handlePedestrianModeToggle,
),
],
),
);
}
Future<void> _handlePedestrianModeToggle(bool enabled) async {
if (enabled) {
// User veut activer → demander permission
final granted = await _requestBackgroundPermission();
setState(() {
_pedestrianModeEnabled = granted;
});
} else {
// User veut désactiver → juste disable service
setState(() {
_pedestrianModeEnabled = false;
});
// Arrêter geofencing service
context.read<GeofencingService>().stop();
}
}
Future<bool> _requestBackgroundPermission() async {
// Étape 1: Afficher écran d'éducation
final userWantsToContinue = await _showEducationDialog();
if (!userWantsToContinue) return false;
// Étape 2: Demander permission OS
final service = context.read<LocationPermissionService>();
final granted = await service.requestBackgroundPermission(context: context);
if (granted) {
_showSuccessDialog();
// Démarrer geofencing service
context.read<GeofencingService>().start();
} else {
_showDeniedDialog();
}
return granted;
}
Future<bool> _showEducationDialog() async {
return await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: Text('📍 Notifications audio-guides piéton'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'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.',
),
SizedBox(height: 16),
Text('🔍 Votre position sera utilisée pour :',
style: TextStyle(fontWeight: FontWeight.bold)),
_buildListItem('Détecter monuments à 200m'),
_buildListItem('Vous envoyer une notification'),
SizedBox(height: 16),
Text('🔒 Votre position ne sera jamais :',
style: TextStyle(fontWeight: FontWeight.bold)),
_buildListItem('Vendue à des tiers', isNegative: true),
_buildListItem('Utilisée pour de la publicité', isNegative: true),
_buildListItem('Partagée sans votre consentement', isNegative: true),
SizedBox(height: 16),
Text(
'Cette fonctionnalité est optionnelle. '
'Vous pouvez utiliser RoadWave sans cette permission.',
style: TextStyle(fontStyle: FontStyle.italic, fontSize: 12),
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text('Non merci'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
child: Text('Continuer'),
),
],
),
) ?? false;
}
Widget _buildListItem(String text, {bool isNegative = false}) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Icon(
isNegative ? Icons.cancel : Icons.check_circle,
color: isNegative ? Colors.red : Colors.green,
size: 20,
),
SizedBox(width: 8),
Expanded(child: Text(text)),
],
),
);
}
void _showSuccessDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('✅ Mode piéton activé !'),
content: Text(
'Vous recevrez une notification lorsque vous passez près d\'un audio-guide.',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('OK'),
),
],
),
);
}
void _showDeniedDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('⚠️ Mode piéton non disponible'),
content: Text(
'Sans permission "Toujours autoriser", nous ne pouvons pas '
'détecter les audio-guides en arrière-plan.\n\n'
'Vous pouvez toujours :\n'
'✅ Utiliser le mode voiture\n'
'✅ Lancer manuellement les audio-guides',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Fermer'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
openAppSettings();
},
child: Text('Ouvrir réglages'),
),
],
),
);
}
}
```
---
## Tableau de Dégradation Gracieuse
| Niveau Permission | Mode Voiture | Mode Piéton | Contenus Hyperlocaux | Notifications |
|-------------------|--------------|-------------|---------------------|--------------|
| **Always** | ✅ Complet | ✅ Complet | ✅ Tous | Push en arrière-plan |
| **When In Use** | ✅ Complet | ❌ Désactivé | ✅ Si app ouverte | Sonores (app ouverte) |
| **Denied** | ⚠️ IP2Location (ville) | ❌ Désactivé | ❌ Aucun | Aucune |
**Garanties** :
- App **utilisable** à tous niveaux de permission ✅
- Pas de fonctionnalité **bloquante** sans permission ✅
- Mode dégradé **acceptable** (contenus régionaux) ✅
---
## Configuration Plateformes
### iOS (`ios/Runner/Info.plist`)
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- ÉTAPE 1: Permission "When In Use" -->
<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>
<!-- ÉTAPE 2: Permission "Always" (optionnelle) -->
<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>
<!-- Background modes -->
<key>UIBackgroundModes</key>
<array>
<string>location</string>
<string>remote-notification</string>
</array>
<!-- Privacy - Location Always Usage Description (fallback iOS < 11) -->
<key>NSLocationAlwaysUsageDescription</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.</string>
</dict>
</plist>
```
### Android (`android/app/src/main/AndroidManifest.xml`)
```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.roadwave.app">
<!-- ÉTAPE 1: Permission "When In Use" -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- ÉTAPE 2: Permission "Always" (Android 10+, optionnelle) -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- Foreground service (requis Android 12+ pour background location) -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<!-- Notifications -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:label="RoadWave"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<!-- Foreground service declaration -->
<service
android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
android:foregroundServiceType="location"
android:exported="false" />
<!-- ... rest of manifest ... -->
</application>
</manifest>
```
---
## Checklist Validation Stores
### iOS App Store
- [ ] Permission "Always" demandée **uniquement** après activation explicite mode piéton
- [ ] Écran d'éducation **avant** demande OS (avec raisons claires)
- [ ] Texte `NSLocationAlwaysAndWhenInUseUsageDescription` mentionne :
- [ ] Fonctionnalité précise ("audio-guides piéton")
- [ ] **Optionnalité** ("Cette fonctionnalité est optionnelle")
- [ ] Pas de mention tracking/publicité
- [ ] App fonctionne **complètement** avec permission "When In Use" uniquement
- [ ] App fonctionne en **mode dégradé** sans aucune permission (IP2Location)
- [ ] Screenshots montrant app fonctionnelle sans permission "Always"
- [ ] Video demo flow de permissions (< 1 min, optionnel mais recommandé)
### Android Play Store
- [ ] Déclaration `ACCESS_BACKGROUND_LOCATION` avec justification dans Play Console :
- [ ] "Notifications géolocalisées pour audio-guides touristiques en arrière-plan"
- [ ] "Permet aux utilisateurs de recevoir des alertes lorsqu'ils passent près de monuments"
- [ ] **Vidéo démo obligatoire** (< 30s) montrant :
- [ ] Activation toggle "Mode piéton" dans Settings
- [ ] Écran d'éducation pré-permission
- [ ] Demande permission système Android
- [ ] App fonctionnelle si permission refusée
- [ ] Foreground service notification visible en mode piéton (Android 12+)
- [ ] App fonctionne **complètement** avec `ACCESS_FINE_LOCATION` uniquement
- [ ] App fonctionne en **mode dégradé** sans permissions
- [ ] Screenshots montrant app fonctionnelle sans permission background
---
## Tests Requis
### Tests Unitaires
```dart
// test/core/services/location_permission_service_test.dart
void main() {
group('LocationPermissionService', () {
test('getCurrentLevel returns denied when no permission', () async {
// ...
});
test('getCurrentLevel returns whenInUse with basic permission', () async {
// ...
});
test('getCurrentLevel returns always with background permission', () async {
// ...
});
test('requestBasicPermission shows system dialog', () async {
// ...
});
test('requestBackgroundPermission requires education dialog first', () async {
// ...
});
});
}
```
### Tests d'Intégration
```dart
// integration_test/permissions_flow_test.dart
void main() {
testWidgets('Onboarding flow with permission acceptance', (tester) async {
app.main();
await tester.pumpAndSettle();
// Voir écran onboarding
expect(find.text('Bienvenue sur RoadWave'), findsOneWidget);
// Tap continuer
await tester.tap(find.text('Continuer'));
await tester.pumpAndSettle();
// Permission acceptée (mock) → navigation home
expect(find.byType(HomeScreen), findsOneWidget);
});
testWidgets('Settings pedestrian mode activation flow', (tester) async {
// ...
await tester.tap(find.byType(SwitchListTile).last);
await tester.pumpAndSettle();
// Voir écran d'éducation
expect(find.text('Notifications audio-guides piéton'), findsOneWidget);
expect(find.text('Votre position sera utilisée pour'), findsOneWidget);
// Tap continuer
await tester.tap(find.text('Continuer'));
await tester.pumpAndSettle();
// Vérifier demande système (mock)
// ...
});
}
```
### Tests Manuels (Devices Réels)
**iOS** :
- [ ] iPhone avec iOS 14, 15, 16, 17, 18
- [ ] Tester flow onboarding permission "When In Use"
- [ ] Tester activation mode piéton avec permission "Always"
- [ ] Tester refus permission "Always" → app reste fonctionnelle
- [ ] Tester changement permission dans Settings iOS → app réagit correctement
**Android** :
- [ ] Android 10, 11, 12, 13, 14, 15
- [ ] Tester flow onboarding permission `FINE_LOCATION`
- [ ] Tester activation mode piéton avec `BACKGROUND_LOCATION`
- [ ] Tester refus permission background → app reste fonctionnelle
- [ ] Vérifier foreground notification visible en arrière-plan (Android 12+)
---
## Validation TestFlight / Internal Testing
### Phase 1 : TestFlight Beta (iOS)
**Objectif** : Valider que Apple accepte notre stratégie de permissions
**Participants** : 10-20 beta testers externes
**Durée** : 2 semaines
**Checklist** :
- [ ] Upload build vers TestFlight
- [ ] Compléter questionnaire App Store Connect :
- [ ] "Why does your app use background location?"
→ "To send push notifications when users walk near tourist audio-guides, even when app is closed. This feature is optional and can be disabled in settings."
- [ ] Screenshots montrant app fonctionnelle sans permission "Always"
- [ ] Attendre review Apple (24-48h)
- [ ] Si rejet : analyser feedback, ajuster textes/flow, re-soumettre
- [ ] Si accepté : lancer beta test avec testeurs
**Scénarios de test beta** :
1. Installation fresh → onboarding → accepter "When In Use"
2. Utiliser mode voiture pendant 1 semaine
3. Activer mode piéton dans settings → accepter "Always"
4. Vérifier réception notifications push en arrière-plan
5. Désactiver mode piéton → vérifier app toujours fonctionnelle
**Métriques collectées** :
- Taux acceptation permission "When In Use" : cible >85%
- Taux acceptation permission "Always" : cible >40%
- Taux rejet App Review : cible 0%
### Phase 2 : Internal Testing (Android)
**Objectif** : Valider conformité Play Store + foreground service
**Participants** : 5-10 beta testers internes
**Durée** : 1 semaine
**Checklist** :
- [ ] Upload build vers Play Console (Internal Testing)
- [ ] Compléter déclaration permissions :
- [ ] `ACCESS_BACKGROUND_LOCATION` justification
- [ ] Upload vidéo démo (< 30s)
- [ ] Tester sur Android 10, 11, 12, 13, 14, 15
- [ ] Vérifier foreground notification visible (Android 12+)
**Scénarios de test** :
1. Installation → onboarding → accepter `FINE_LOCATION`
2. Utiliser app mode voiture
3. Activer mode piéton → voir écran éducation → accepter `BACKGROUND_LOCATION`
4. App en arrière-plan → marcher près d'un POI → vérifier notification push
5. Vérifier notification foreground service visible dans panneau notifications
**Métriques collectées** :
- Consommation batterie mode piéton : cible <5% par heure
- Taux crash background service : cible <0.1%
---
## Vidéo Démo Play Store (Script)
**Durée** : 25 secondes
**Format** : MP4 1080p, portrait
**Voix off** : Optionnel
**Storyboard** :
| Seconde | Écran | Action | Texte Overlay |
|---------|-------|--------|---------------|
| 0-5 | Settings > Notifications | Scroll vers "Audio-guides piéton" | "Utilisateur active mode piéton" |
| 5-8 | Toggle OFF → ON | Tap toggle | |
| 8-15 | Écran d'éducation | Scroll, lire texte | "Écran explicatif affiché" |
| 15-18 | Tap "Continuer" | Demande permission Android | "Permission arrière-plan demandée" |
| 18-22 | Dialog Android | Tap "Toujours autoriser" | "Utilisateur accepte (optionnel)" |
| 22-25 | Retour Settings | Toggle ON | "Mode piéton activé" |
**Fichier** : `android/play-store-assets/background-location-demo.mp4`
---
## FAQ
### Q1 : Pourquoi ne pas demander "Always" dès le début ?
**R** : Taux d'acceptation ~15% vs ~85% pour "When In Use". Strategy progressive maximise utilisateurs avec permissions.
### Q2 : Que se passe-t-il si user change permission dans Settings OS ?
**R** : App détecte changement via `AppLifecycleState` et `permission_handler`. Si downgrade "Always" → "When In Use", mode piéton désactivé automatiquement avec notification in-app.
### Q3 : Est-ce que IP2Location suffit pour le MVP ?
**R** : Non. Mode voiture nécessite GPS précis pour ETA et notifications géolocalisées (règle métier 05). IP2Location = fallback uniquement.
### Q4 : Combien de temps pour validation TestFlight/Play Store ?
**R** :
- TestFlight : 24-48h (review Apple)
- Play Console Internal Testing : Immédiat (pas de review)
- Play Console Production : 3-7 jours (review Google)
---
## Références
- **ADR-014** : [Frontend Mobile](../adr/014-frontend-mobile.md)
- **Règle 05** : [Mode Piéton](../regles-metier/05-interactions-navigation.md#512-mode-piéton-audio-guides)
- **Règle 02** : [Conformité RGPD](../regles-metier/02-conformite-rgpd.md)
- **Apple Guidelines** : [Location Best Practices](https://developer.apple.com/design/human-interface-guidelines/location)
- **Android Guidelines** : [Request Background Location](https://developer.android.com/training/location/permissions#request-background-location)
---
**Dernière mise à jour** : 2026-01-31
**Prochaine revue** : Après validation TestFlight (Sprint 3)

View File

@@ -0,0 +1,618 @@
# Plan de Validation TestFlight & Play Store
**Date** : 2026-01-31
**Auteur** : QA & Mobile Team RoadWave
**Objectif** : Valider stratégie de permissions géolocalisation pour acceptation stores
**Statut** : Prêt à exécuter
---
## Vue d'Ensemble
### Objectifs
1. **Valider acceptation Apple App Store** pour permission "Always Location"
2. **Valider acceptation Google Play Store** pour `ACCESS_BACKGROUND_LOCATION`
3. **Mesurer taux d'acceptation** utilisateurs réels (permissions progressives)
4. **Identifier bugs** flow de permissions sur différents OS/devices
5. **Optimiser textes** pour maximiser acceptation
### Timeline
```
Semaine 1 : Préparation builds + documentation stores
Semaine 2-3: Beta testing iOS (TestFlight)
Semaine 3-4: Beta testing Android (Internal Testing)
Semaine 5 : Corrections + re-soumission si nécessaire
Semaine 6 : Validation finale + go/no-go production
```
---
## Phase 1 : Préparation (Semaine 1)
### Checklist Build iOS
- [ ] **Code freeze** branche `release/testflight-permissions-v1`
- [ ] Vérifier `Info.plist` textes permissions :
- [ ] `NSLocationWhenInUseUsageDescription` ≤ 200 caractères
- [ ] `NSLocationAlwaysAndWhenInUseUsageDescription` ≤ 200 caractères
- [ ] Pas de mention tracking/publicité
- [ ] Mention explicite "optionnel"
- [ ] Vérifier `UIBackgroundModes` contient `location`
- [ ] Build & Archive (Xcode)
- [ ] Version : `1.0.0 (1)` (beta)
- [ ] Bundle ID : `com.roadwave.app`
- [ ] Signing : Distribution certificate
- [ ] Upload vers App Store Connect
- [ ] Attendre processing (15-30 min)
### Checklist Build Android
- [ ] **Code freeze** même branche que iOS
- [ ] Vérifier `AndroidManifest.xml` permissions :
- [ ] `ACCESS_FINE_LOCATION`
- [ ] `ACCESS_BACKGROUND_LOCATION`
- [ ] `FOREGROUND_SERVICE`
- [ ] `FOREGROUND_SERVICE_LOCATION`
- [ ] Vérifier foreground service déclaration
- [ ] Build AAB (Android App Bundle)
- [ ] Version : `1.0.0 (1)`
- [ ] Package : `com.roadwave.app`
- [ ] Signing : Release keystore
- [ ] Upload vers Play Console (Internal Testing)
### Documentation App Store Connect
**Questionnaire "Background Location"** :
**Q1** : "Why does your app use background location?"
**A1** (réponse exacte) :
```
RoadWave sends push notifications to users when they walk near tourist
audio-guides and monuments, even when the app is closed. This allows
tourists to discover local content while exploring a city on foot.
This feature is entirely optional and can be disabled in the app settings.
Users can use RoadWave fully without enabling background location - they
will simply use the "car mode" which only requires location "while using".
Background location is ONLY used for:
- Detecting proximity to audio-guide points of interest (200m radius)
- Sending a single push notification to alert the user
Background location is NEVER used for:
- Advertising or tracking
- Selling data to third parties
- Analytics beyond core functionality
```
**Q2** : "How do users benefit from background location?"
**A2** :
```
Users walking in a city receive timely notifications about nearby cultural
content (museums, monuments, historical sites) without having to keep the
app open. This improves the tourist experience while preserving battery life
through native iOS geofencing.
```
**Screenshots à fournir** (5 minimum) :
1. Onboarding demandant permission "When In Use" uniquement
2. App fonctionnelle en mode voiture (avec permission "When In Use")
3. Settings montrant toggle "Mode piéton" désactivé
4. Écran d'éducation avant demande "Always"
5. App fonctionnelle en mode voiture après refus "Always"
### Documentation Play Console
**Déclaration Permission Background Location** :
**Justification** (max 1000 caractères) :
```
RoadWave utilise ACCESS_BACKGROUND_LOCATION uniquement pour envoyer des
notifications push géolocalisées aux utilisateurs en mode piéton (touristes
à pied).
Usage précis :
- Geofencing radius 200m autour des points d'intérêt (monuments, musées)
- Notification push unique lorsque l'utilisateur entre dans la zone
- Permet découverte de contenus audio-guides sans ouvrir l'app
Cette fonctionnalité est OPTIONNELLE :
- Demandée uniquement si utilisateur active "Mode piéton" dans Settings
- Écran explicatif affiché AVANT demande permission système
- L'app fonctionne pleinement sans cette permission (mode voiture disponible)
Foreground service notification visible (Android 12+) lorsque geofencing actif.
Données de localisation :
- JAMAIS vendues ou partagées avec tiers
- JAMAIS utilisées pour publicité ciblée
- Anonymisées après 24h (conformité RGPD)
```
**Vidéo démo** (requis) :
- [ ] Enregistrer screen recording (25-30s)
- [ ] Montrer activation mode piéton depuis Settings
- [ ] Montrer écran d'éducation
- [ ] Montrer demande permission système Android
- [ ] Montrer app fonctionnelle si refusé
- [ ] Format : MP4, 1080p portrait, < 50MB
- [ ] Upload vers Play Console (section "Permissions")
---
## Phase 2 : Beta Testing iOS (Semaine 2-3)
### Configuration TestFlight
**Groupes de testeurs** :
| Groupe | Nombre | Profil | Objectif |
|--------|--------|--------|----------|
| **Internal** | 3-5 | Équipe dev/QA | Tests rapides pre-external |
| **External 1** | 10-15 | Early adopters tech-savvy | Tests fonctionnels détaillés |
| **External 2** | 20-30 | Grand public varié | Tests UX/acceptation réelle |
**Configuration** :
- [ ] Créer groupe "Internal Testers" (accès immédiat)
- [ ] Créer groupe "External Beta 1" (review Apple requise, 24-48h)
- [ ] Créer groupe "External Beta 2" (après succès Beta 1)
- [ ] Activer feedback automatique TestFlight
- [ ] Préparer questionnaire post-test (Google Forms)
### Scénarios de Test (Internal)
**Durée** : 2-3 jours
**Devices** :
- iPhone 12 (iOS 15)
- iPhone 13 Pro (iOS 16)
- iPhone 14 (iOS 17)
- iPhone 15 Pro (iOS 18)
**Test Case 1 : Onboarding Fresh Install**
```
Given: App jamais installée
When: Installation depuis TestFlight
Then:
- Écran onboarding demande permission "When In Use"
- Texte clair et rassurant
- Acceptation → navigation home
- Refus → mode dégradé disponible
```
**Test Case 2 : Mode Voiture (Permission When In Use)**
```
Given: Permission "When In Use" accordée
When: Utilisation normale app pendant 1h de conduite
Then:
- GPS actif quand app ouverte
- Notifications géolocalisées sonores fonctionnent
- ETA calcul correct (7s avant POI)
- Pas de demande permission supplémentaire
```
**Test Case 3 : Activation Mode Piéton**
```
Given: App utilisée en mode voiture depuis quelques jours
When: User active toggle "Mode piéton" dans Settings
Then:
- Écran d'éducation s'affiche AVANT demande OS
- Texte mentionne "optionnel"
- Tap "Continuer" → demande iOS "Allow Always"
- Tap "Non merci" → toggle reste OFF, app fonctionnelle
```
**Test Case 4 : Mode Piéton Actif**
```
Given: Permission "Always" accordée
When: App en arrière-plan, user marche près d'un POI
Then:
- Notification push reçue (200m du POI)
- Tap notification → app ouvre contenu
- Geofencing ne vide pas batterie (< 5%/h)
```
**Test Case 5 : Refus Permission Always**
```
Given: User refuse permission "Always" dans dialog iOS
When: Retour dans app
Then:
- Message "Mode piéton non disponible"
- Bouton "Ouvrir réglages" disponible
- Mode voiture toujours pleinement fonctionnel
- Pas de popup récurrent de demande permission
```
**Test Case 6 : Changement Permission dans Settings iOS**
```
Given: Permission "Always" active
When: User change dans Settings iOS → "While Using"
Then:
- App détecte changement (AppLifecycleState)
- Mode piéton désactivé automatiquement
- Notification in-app : "Mode piéton désactivé (permission changée)"
- Mode voiture reste fonctionnel
```
### Scénarios de Test (External Beta 1)
**Durée** : 1 semaine
**Instructions aux testeurs** :
```
Bienvenue sur la beta RoadWave !
Nous testons notre système de permissions géolocalisation.
Jour 1-2 : Installation & Mode Voiture
1. Installez l'app depuis TestFlight
2. Suivez l'onboarding (acceptez permission "When In Use")
3. Utilisez l'app normalement en voiture pendant 2 jours
4. Notez : bugs, crashs, notifications fonctionnent ?
Jour 3-5 : Mode Piéton (optionnel)
5. Allez dans Settings > Notifications
6. Activez "Audio-guides piéton"
7. Lisez l'écran explicatif
8. Acceptez OU refusez permission "Always" (votre choix !)
9. Testez mode piéton en marchant en ville
Jour 6-7 : Feedback
10. Répondez au questionnaire (lien ci-dessous)
11. Signalez tout bug via TestFlight feedback
Questionnaire : [lien Google Forms]
```
**Questionnaire Post-Test** (Google Forms) :
1. Avez-vous accepté permission "When In Use" au démarrage ? (Oui/Non)
2. Pourquoi ? (Texte libre)
3. Le texte de permission était-il clair ? (1-5)
4. Avez-vous essayé d'activer le mode piéton ? (Oui/Non)
5. Si oui, avez-vous accepté permission "Always" ? (Oui/Non/N'ai pas essayé)
6. Pourquoi ? (Texte libre)
7. L'écran explicatif avant permission "Always" était-il rassurant ? (1-5)
8. Si vous avez refusé "Always", l'app reste-t-elle utilisable ? (Oui/Non/N/A)
9. Bugs rencontrés ? (Texte libre)
10. Suggestions d'amélioration textes permissions ? (Texte libre)
### Métriques Collectées (Firebase Analytics)
**Events trackés** :
```dart
// Onboarding
analytics.logEvent(
name: 'permission_when_in_use_requested',
);
analytics.logEvent(
name: 'permission_when_in_use_granted',
parameters: {'granted': true},
);
// Mode piéton
analytics.logEvent(
name: 'pedestrian_mode_toggle_attempted',
);
analytics.logEvent(
name: 'permission_education_shown',
);
analytics.logEvent(
name: 'permission_education_continued', // User tap "Continuer"
);
analytics.logEvent(
name: 'permission_education_dismissed', // User tap "Non merci"
);
analytics.logEvent(
name: 'permission_always_granted',
parameters: {'granted': true},
);
// Fallback
analytics.logEvent(
name: 'degraded_mode_activated',
parameters: {'reason': 'permission_denied'},
);
```
**Dashboards Firebase** :
- Taux acceptation "When In Use" : `granted / requested`
- Cible : >85%
- Taux activation mode piéton : `toggle_attempted / total_users`
- Cible : >30%
- Taux acceptation "Always" : `always_granted / education_continued`
- Cible : >40%
- Taux abandon education : `education_dismissed / education_shown`
- Cible : <60%
### Critères de Succès Beta 1
- [ ] Taux acceptation "When In Use" ≥ 80%
- [ ] Taux acceptation "Always" ≥ 35%
- [ ] 0 crash lié aux permissions
- [ ] 0 feedback "app inutilisable sans Always"
- [ ] Score satisfaction écran éducation ≥ 4/5
- [ ] **Apple approuve External Beta** (critique !)
Si Apple **rejette** External Beta :
1. Analyser raison rejet (email App Store Connect)
2. Ajuster textes `Info.plist` si problème wording
3. Ajuster flow si problème UX (ex: trop insistant)
4. Re-soumettre sous 48h
5. Itérer jusqu'à acceptation
---
## Phase 3 : Beta Testing Android (Semaine 3-4)
### Configuration Play Console Internal Testing
**Testeurs** :
- [ ] Ajouter emails testeurs (max 100 pour Internal Testing)
- [ ] Créer "testers list" dans Play Console
- [ ] Share lien installation (pas de review Google pour Internal)
**Devices** :
- Google Pixel 5 (Android 12)
- Samsung Galaxy S21 (Android 13)
- OnePlus 9 (Android 14)
- Google Pixel 8 (Android 15)
### Scénarios de Test (Focus Android)
**Test Case 1 : Foreground Service Notification (Android 12+)**
```
Given: Permission background accordée, mode piéton actif
When: App en arrière-plan avec geofencing actif
Then:
- Notification foreground service visible dans panneau
- Texte : "RoadWave détecte audio-guides à proximité"
- Icône RoadWave visible
- Tap notification → ouvre app
- Notification ne peut pas être swipée (persistent)
```
**Test Case 2 : Permission Background Android 10+**
```
Given: Android 10, 11, 12, 13, 14, ou 15
When: Activation mode piéton
Then:
- Écran éducation s'affiche
- Dialog Android demande "Toujours autoriser"
- Options : "Toujours" / "Seulement pendant utilisation" / "Refuser"
- Selection "Toujours" → mode piéton activé
- Selection autre → mode piéton désactivé
```
**Test Case 3 : Battery Drain**
```
Given: Mode piéton actif, app en arrière-plan
When: 4 heures d'utilisation continue (marche en ville)
Then:
- Consommation batterie < 20% (< 5%/h)
- Pas de "Battery draining" warning Android
- Geofencing utilise location updates optimisés (pas de polling continu)
```
**Test Case 4 : Permission Revocation**
```
Given: Permission background accordée
When: User révoque dans Settings Android
Then:
- App détecte changement (broadcast receiver)
- Mode piéton désactivé automatiquement
- Foreground service arrêté
- Notification in-app : "Mode piéton désactivé"
```
### Vidéo Démo Play Store
**Enregistrement** :
- [ ] Device : Pixel 8 (Android 15, UI stock)
- [ ] Screen recorder : Android natif (Game Toolbar)
- [ ] Durée : 25-30s
- [ ] Orientation : Portrait
- [ ] Résolution : 1080p
**Script** (voir [permissions-strategy.md](permissions-strategy.md#vidéo-démo-play-store-script)) :
1. (0-5s) Settings > Notifications > scroll vers "Audio-guides piéton"
2. (5-8s) Toggle OFF → ON
3. (8-15s) Écran d'éducation affiché, scroll pour lire
4. (15-18s) Tap "Continuer" → demande permission Android
5. (18-22s) Tap "Toujours autoriser"
6. (22-25s) Retour Settings, toggle ON confirmé
**Post-production** :
- [ ] Ajouter text overlays : "Utilisateur active mode piéton", "Écran explicatif affiché", etc.
- [ ] Exporter MP4 < 50MB
- [ ] Upload Play Console > Permissions > Background Location > Video demo
### Critères de Succès Android
- [ ] Foreground service notification visible et claire
- [ ] Consommation batterie acceptable (< 5%/h)
- [ ] 0 crash sur Android 10-15
- [ ] Vidéo démo uploadée et acceptée Play Console
- [ ] Déclaration permission background validée
---
## Phase 4 : Validation & Go/No-Go (Semaine 5-6)
### Analyse Résultats
**Tableau consolidé** :
| Métrique | iOS (Cible) | iOS (Réel) | Android (Cible) | Android (Réel) | Status |
|----------|-------------|-----------|-----------------|----------------|--------|
| Taux acceptation permission base | >85% | ? | >85% | ? | ? |
| Taux activation mode piéton | >30% | ? | >30% | ? | ? |
| Taux acceptation permission background | >40% | ? | >40% | ? | ? |
| Crash rate permissions | 0% | ? | 0% | ? | ? |
| Battery drain mode piéton | <5%/h | ? | <5%/h | ? | ? |
| Feedback "app inutilisable" | 0 | ? | 0 | ? | ? |
### Décision Go/No-Go Production
**Critères GO** (tous doivent être ✅) :
- [ ] Apple a approuvé External Beta TestFlight
- [ ] Taux acceptation permission base iOS ≥ 80%
- [ ] Taux acceptation permission base Android ≥ 80%
- [ ] 0 crash critique lié aux permissions
- [ ] 0 feedback utilisateur "app inutilisable sans background permission"
- [ ] Vidéo démo Android uploadée et validée
- [ ] Battery drain mode piéton < 5%/h (iOS & Android)
**Si NO-GO** :
1. Identifier problème bloquant (cf métriques)
2. Planifier corrections (ex: rewording textes, ajustement flow)
3. Nouvelle itération beta (1-2 semaines)
4. Re-validation
**Si GO** :
1. Merge branche `release/testflight-permissions-v1``main`
2. Tag version `v1.0.0`
3. Préparer soumission production (Semaine 7)
---
## Phase 5 : Soumission Production (Semaine 7+)
### iOS App Store
**Checklist soumission** :
- [ ] Build production uploadé (même code que TestFlight validé)
- [ ] Version : `1.0.0 (1)`
- [ ] Screenshots stores (5 minimum, incluant permissions flow)
- [ ] Description mentionnant "mode piéton optionnel"
- [ ] Keywords : roadwave, audio, gps, tourisme, voyage
- [ ] Pricing : Gratuit
- [ ] App Privacy : Déclarer usage location (voir section)
- [ ] Submit for Review
**App Privacy (obligatoire iOS 14+)** :
Location Data Collection :
- [ ] "Precise Location" : Yes
- [ ] Purpose : "App Functionality" + "Product Personalization"
- [ ] Linked to user : Yes
- [ ] Used for tracking : No
- [ ] "Coarse Location" : No
**Timing** :
- Review Apple : 24-48h (généralement)
- Si rejet : corrections + re-soumission (24h)
- **Total prévu** : 3-7 jours
### Android Play Store
**Checklist soumission** :
- [ ] Build production (Release AAB)
- [ ] Version : `1.0.0 (1)`
- [ ] Screenshots (8 minimum)
- [ ] Description courte (80 caractères)
- [ ] Description longue (4000 caractères max)
- [ ] Catégorie : Travel & Local
- [ ] Pricing : Gratuit
- [ ] Data Safety : Déclarer usage location
- [ ] Submit for Review (Production track)
**Data Safety Form** :
Location Data :
- [ ] "Approximate location" : No
- [ ] "Precise location" : Yes
- [ ] Purpose : "App functionality" + "Personalization"
- [ ] Shared with third parties : No
- [ ] Optional : Yes (mode dégradé disponible)
- [ ] User can request deletion : Yes
**Timing** :
- Review Google : 3-7 jours
- Si rejet : corrections + re-soumission (1-2 jours)
- **Total prévu** : 5-10 jours
---
## Contingences & Risques
### Risque 1 : Apple rejette permission "Always"
**Probabilité** : Moyenne (30%)
**Impact** : Critique (bloque production)
**Mitigation** :
1. Rewording `Info.plist` pour insister sur "optionnel"
2. Ajouter screenshots montrant app sans permission "Always"
3. Écrire email explicatif à App Review Team
4. Si blocage persistant : envisager retrait mode piéton du MVP
### Risque 2 : Taux acceptation permission trop faible
**Probabilité** : Faible (20%)
**Impact** : Modéré (feature peu utilisée)
**Mitigation** :
1. A/B testing textes écran éducation
2. Améliorer wording pour rassurer utilisateurs
3. Ajouter testimonials/reviews dans écran éducation
4. Retarder demande "Always" (demander après 1 semaine d'usage)
### Risque 3 : Battery drain trop élevé
**Probabilité** : Faible (15%)
**Impact** : Critique (rejets stores + mauvaises reviews)
**Mitigation** :
1. Optimiser geofencing radius (200m → 500m)
2. Augmenter interval updates (30s → 60s)
3. Utiliser "significant location changes" iOS au lieu de "continuous"
4. Désactiver geofencing si batterie < 20%
### Risque 4 : Crash sur anciens OS
**Probabilité** : Faible (10%)
**Impact** : Modéré (fragmentation utilisateurs)
**Mitigation** :
1. Tester sur iOS 14, Android 10 (versions minimales)
2. Fallback gracieux si API geofencing non disponible
3. Considérer min SDK Android 11 (au lieu de 10) si trop de bugs
---
## Contacts & Ressources
### Équipe
- **Mobile Lead** : Responsable builds & soumissions stores
- **QA Lead** : Coordination testeurs beta, analyse métriques
- **Product Owner** : Décision go/no-go, priorisation corrections
- **Legal/RGPD** : Validation textes permissions conformité
### Outils
- **TestFlight** : https://appstoreconnect.apple.com
- **Play Console** : https://play.google.com/console
- **Firebase Analytics** : https://console.firebase.google.com
- **Questionnaire Beta** : Google Forms (lien à créer)
- **Tracking Issues** : GitHub Issues avec label `[testflight]`
### Documentation
- [Stratégie Permissions](permissions-strategy.md)
- [ADR-014 Frontend Mobile](../adr/014-frontend-mobile.md)
- [Règle 05 Mode Piéton](../regles-metier/05-interactions-navigation.md)
---
**Plan approuvé par** : [Nom Product Owner]
**Date d'approbation** : [Date]
**Prochaine revue** : Fin Semaine 2 (après External Beta 1)