Corrections: - Liens vers ADR: docs/adr/ → adr/ dans index.md et technical.md - Liens internes entre règles métier (anciens noms numérotés) - Chemins relatifs ADR depuis les domaines: ../adr/ → ../../../adr/ - Lien ADR-010 → ADR-012 (frontend-mobile) - Suppression référence vers sequences/scoring-recommandation.md (non créé) Script: scripts/fix-remaining-links.sh
30 KiB
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.plistscrutés manuellement par reviewers humains - Rejection si suspicion de tracking publicitaire ou vente de données
Android Play Store
- Depuis Android 10 :
ACCESS_BACKGROUND_LOCATIONné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-019)
- 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
// 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
// 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 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)
<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
NSLocationAlwaysAndWhenInUseUsageDescriptionmentionne :- 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_LOCATIONavec 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_LOCATIONuniquement - App fonctionne en mode dégradé sans permissions
- Screenshots montrant app fonctionnelle sans permission background
Tests Requis
Tests Unitaires
// 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
// 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 :
- Installation fresh → onboarding → accepter "When In Use"
- Utiliser mode voiture pendant 1 semaine
- Activer mode piéton dans settings → accepter "Always"
- Vérifier réception notifications push en arrière-plan
- 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_LOCATIONjustification- 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 :
- Installation → onboarding → accepter
FINE_LOCATION - Utiliser app mode voiture
- Activer mode piéton → voir écran éducation → accepter
BACKGROUND_LOCATION - App en arrière-plan → marcher près d'un POI → vérifier notification push
- 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-010 : Frontend Mobile
- Règle 05 : Mode Piéton
- Règle 02 : Conformité RGPD
- Apple Guidelines : Location Best Practices
- Android Guidelines : Request Background Location
Dernière mise à jour : 2026-01-31 Prochaine revue : Après validation TestFlight (Sprint 3)