(doc) : ajout et modification de docs après arbitrage
This commit is contained in:
863
docs/mobile/permissions-strategy.md
Normal file
863
docs/mobile/permissions-strategy.md
Normal 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)
|
||||
618
docs/mobile/testflight-validation-plan.md
Normal file
618
docs/mobile/testflight-validation-plan.md
Normal 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)
|
||||
Reference in New Issue
Block a user