- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD) * DEV: Clés TEST Pierre (mode test) * REC: Clés TEST Client (mode test) * PROD: Clés LIVE Client (mode live) - Ajout de la gestion des bases de données immeubles/bâtiments * Configuration buildings_database pour DEV/REC/PROD * Service BuildingService pour enrichissement des adresses - Optimisations pages et améliorations ergonomie - Mises à jour des dépendances Composer - Nettoyage des fichiers obsolètes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
396 lines
12 KiB
Markdown
396 lines
12 KiB
Markdown
# TODO - Cache Web pour tuiles Mapbox
|
|
|
|
## Contexte
|
|
|
|
**Problème** : En PROD, les admins travaillent intensivement sur la plateforme web et la carte. Actuellement, les tuiles Mapbox ne sont pas mises en cache sur le web, ce qui provoque :
|
|
- Téléchargements répétés des mêmes tuiles
|
|
- Consommation excessive de bande passante
|
|
- Navigation peu fluide
|
|
- Coûts API Mapbox potentiellement élevés
|
|
|
|
**Cause** : Mapbox envoie des headers `Cache-Control: no-cache` qui empêchent le cache navigateur par défaut, et `flutter_map_cache` ne supporte pas la plateforme web.
|
|
|
|
**Solution** : Implémenter un cache manuel des tuiles via IndexedDB en Dart, uniquement pour le web.
|
|
|
|
## Architecture technique
|
|
|
|
### Flux de données
|
|
|
|
```
|
|
flutter_map demande une tuile
|
|
↓
|
|
CachedWebTileProvider (nouveau)
|
|
↓
|
|
WebTileCacheService.getTile()
|
|
↓
|
|
Vérifie IndexedDB
|
|
↓
|
|
┌─────────────┴─────────────┐
|
|
│ │
|
|
Cache HIT Cache MISS
|
|
│ │
|
|
Retourne image Télécharge depuis Mapbox
|
|
depuis IndexedDB ↓
|
|
Stocke dans IndexedDB
|
|
↓
|
|
Retourne image
|
|
```
|
|
|
|
### Composants
|
|
|
|
1. **WebTileCacheService** (Singleton)
|
|
- Gère IndexedDB pour stocker les tuiles
|
|
- Méthodes : `getTile()`, `storeTile()`, `clearCache()`, `clearExpiredTiles()`
|
|
- Durée de cache : 30 jours (aligné avec mobile/desktop)
|
|
|
|
2. **CachedWebTileProvider** (Custom TileProvider)
|
|
- Implémente `TileProvider` de `flutter_map`
|
|
- Utilise `WebTileCacheService` pour récupérer/stocker
|
|
- Fallback sur téléchargement direct si erreur
|
|
|
|
3. **MapboxMap** (Modifié)
|
|
- Utilise `CachedWebTileProvider` sur web
|
|
- Garde `HiveCacheStore` sur mobile/desktop
|
|
|
|
## Fichiers à créer/modifier
|
|
|
|
### ✅ Fichiers à créer
|
|
|
|
#### 1. `lib/core/services/web_tile_cache_service.dart`
|
|
|
|
Service singleton pour gérer le cache IndexedDB des tuiles Mapbox.
|
|
|
|
**Responsabilités** :
|
|
- Initialiser la base IndexedDB `mapbox_tiles_cache`
|
|
- Stocker les tuiles avec clé unique (URL de la tuile)
|
|
- Récupérer les tuiles depuis le cache
|
|
- Gérer l'expiration (30 jours)
|
|
- Nettoyer les tuiles expirées
|
|
|
|
**Structure IndexedDB** :
|
|
```
|
|
Database: mapbox_tiles_cache
|
|
ObjectStore: tiles
|
|
- key: String (URL de la tuile)
|
|
- value: Object {
|
|
data: Uint8List (bytes de l'image)
|
|
timestamp: int (millisecondsSinceEpoch)
|
|
}
|
|
```
|
|
|
|
**API publique** :
|
|
```dart
|
|
class WebTileCacheService {
|
|
static final instance = WebTileCacheService._();
|
|
|
|
Future<void> initialize();
|
|
Future<Uint8List?> getTile(String url);
|
|
Future<void> storeTile(String url, Uint8List data);
|
|
Future<void> clearCache();
|
|
Future<void> clearExpiredTiles();
|
|
}
|
|
```
|
|
|
|
**Dépendances** :
|
|
- `dart:indexed_db` (natif web)
|
|
- `dart:typed_data`
|
|
- `package:flutter/foundation.dart` (kIsWeb)
|
|
|
|
#### 2. `lib/presentation/widgets/cached_web_tile_provider.dart`
|
|
|
|
TileProvider custom qui utilise le cache IndexedDB.
|
|
|
|
**Responsabilités** :
|
|
- Implémenter `TileProvider` de `flutter_map`
|
|
- Vérifier le cache avant téléchargement
|
|
- Télécharger et stocker si absent du cache
|
|
- Fallback sur comportement par défaut si erreur
|
|
|
|
**API publique** :
|
|
```dart
|
|
class CachedWebTileProvider extends TileProvider {
|
|
final String accessToken;
|
|
final String styleId;
|
|
|
|
CachedWebTileProvider({
|
|
required this.accessToken,
|
|
required this.styleId,
|
|
});
|
|
|
|
@override
|
|
ImageProvider getImage(TileCoordinates coordinates, TileLayer options);
|
|
}
|
|
```
|
|
|
|
**Logique** :
|
|
1. Construire l'URL de la tuile
|
|
2. Appeler `WebTileCacheService.getTile(url)`
|
|
3. Si trouvée → `MemoryImage(cachedData)`
|
|
4. Sinon → Télécharger → Stocker → `MemoryImage(downloadedData)`
|
|
|
|
**Dépendances** :
|
|
- `flutter_map`
|
|
- `WebTileCacheService`
|
|
- `dart:typed_data`
|
|
- `package:http/http.dart` (ou `Dio` si préféré)
|
|
|
|
### ✅ Fichiers à modifier
|
|
|
|
#### 3. `lib/presentation/widgets/mapbox_map.dart`
|
|
|
|
Modifier pour utiliser le nouveau provider sur web.
|
|
|
|
**Modifications** :
|
|
- Lignes 96-139 : Refactoriser `_initializeCache()`
|
|
- Utiliser `CachedWebTileProvider` quand `kIsWeb == true`
|
|
- Garder `HiveCacheStore` pour mobile/desktop
|
|
- Initialiser `WebTileCacheService` au démarrage sur web
|
|
|
|
**Avant** (lignes 98-103) :
|
|
```dart
|
|
if (kIsWeb) {
|
|
// Pas de cache sur Web (non supporté)
|
|
setState(() {
|
|
_cacheInitialized = true;
|
|
});
|
|
return;
|
|
}
|
|
```
|
|
|
|
**Après** :
|
|
```dart
|
|
if (kIsWeb) {
|
|
// Cache manuel via IndexedDB
|
|
await WebTileCacheService.instance.initialize();
|
|
setState(() {
|
|
_cacheInitialized = true;
|
|
});
|
|
return;
|
|
}
|
|
```
|
|
|
|
**TileLayer config** (ligne ~180) :
|
|
```dart
|
|
TileLayer(
|
|
urlTemplate: 'https://api.mapbox.com/styles/v1/mapbox/$styleId/tiles/256/{z}/{x}/{y}@2x?access_token=$accessToken',
|
|
tileProvider: kIsWeb
|
|
? CachedWebTileProvider(
|
|
accessToken: accessToken,
|
|
styleId: styleId,
|
|
)
|
|
: (_cacheStore != null
|
|
? CachedTileProvider(store: _cacheStore!)
|
|
: NetworkTileProvider()),
|
|
// ...
|
|
),
|
|
```
|
|
|
|
## Étapes d'implémentation
|
|
|
|
### Phase 1 : Service de cache IndexedDB
|
|
|
|
**Tâche 1.1** : Créer `web_tile_cache_service.dart`
|
|
- [ ] Créer la classe singleton
|
|
- [ ] Implémenter `initialize()` avec création de la DB IndexedDB
|
|
- [ ] Implémenter `getTile(url)` avec vérification d'expiration
|
|
- [ ] Implémenter `storeTile(url, data)` avec timestamp
|
|
- [ ] Implémenter `clearCache()` pour vider toute la DB
|
|
- [ ] Implémenter `clearExpiredTiles()` pour le nettoyage automatique
|
|
- [ ] Ajouter des logs de debug (`debugPrint`)
|
|
|
|
**Tâche 1.2** : Gestion des erreurs
|
|
- [ ] Try/catch sur toutes les opérations IndexedDB
|
|
- [ ] Fallback gracieux si IndexedDB indisponible
|
|
- [ ] Logs d'erreur explicites
|
|
|
|
**Tâche 1.3** : Tests unitaires
|
|
- [ ] Tester `initialize()` crée bien la DB
|
|
- [ ] Tester `storeTile()` + `getTile()` roundtrip
|
|
- [ ] Tester expiration (mock timestamp)
|
|
- [ ] Tester `clearCache()`
|
|
|
|
### Phase 2 : TileProvider custom
|
|
|
|
**Tâche 2.1** : Créer `cached_web_tile_provider.dart`
|
|
- [ ] Créer la classe qui extend `TileProvider`
|
|
- [ ] Implémenter `getImage()` avec logique de cache
|
|
- [ ] Gérer le téléchargement des tuiles manquantes
|
|
- [ ] Stocker les tuiles téléchargées dans le cache
|
|
- [ ] Ajouter des logs de performance (cache hit/miss)
|
|
|
|
**Tâche 2.2** : Gestion des erreurs réseau
|
|
- [ ] Try/catch sur téléchargement
|
|
- [ ] Retry logic (3 tentatives max)
|
|
- [ ] Placeholder si échec total
|
|
- [ ] Logs d'erreur explicites
|
|
|
|
**Tâche 2.3** : Tests
|
|
- [ ] Tester avec tuile en cache → Pas de requête réseau
|
|
- [ ] Tester avec tuile absente → Téléchargement + stockage
|
|
- [ ] Tester avec erreur réseau → Fallback gracieux
|
|
|
|
### Phase 3 : Intégration dans MapboxMap
|
|
|
|
**Tâche 3.1** : Modifier `mapbox_map.dart`
|
|
- [ ] Importer `CachedWebTileProvider` et `WebTileCacheService`
|
|
- [ ] Modifier `_initializeCache()` pour initialiser le service web
|
|
- [ ] Modifier `TileLayer` pour utiliser le bon provider selon la plateforme
|
|
- [ ] Tester sur web que le nouveau provider est bien utilisé
|
|
|
|
**Tâche 3.2** : Validation visuelle
|
|
- [ ] Lancer l'app en mode web DEV
|
|
- [ ] Ouvrir DevTools → Console pour voir les logs
|
|
- [ ] Naviguer sur la carte → Vérifier logs "Cache MISS" au premier passage
|
|
- [ ] Revenir sur la même zone → Vérifier logs "Cache HIT"
|
|
- [ ] Vérifier dans DevTools → Application → IndexedDB que les tuiles sont stockées
|
|
|
|
**Tâche 3.3** : Tests de performance
|
|
- [ ] Mesurer le temps de chargement initial (sans cache)
|
|
- [ ] Mesurer le temps de chargement avec cache
|
|
- [ ] Vérifier qu'il n'y a plus de requêtes réseau pour les tuiles en cache
|
|
- [ ] Tester sur une session longue (plusieurs déplacements sur la carte)
|
|
|
|
### Phase 4 : Nettoyage et documentation
|
|
|
|
**Tâche 4.1** : Nettoyage automatique
|
|
- [ ] Appeler `clearExpiredTiles()` au démarrage de l'app (dans `initialize()`)
|
|
- [ ] Ajouter un bouton admin pour vider manuellement le cache (optionnel)
|
|
|
|
**Tâche 4.2** : Logs et monitoring
|
|
- [ ] Ajouter des statistiques de cache (hit rate, taille totale)
|
|
- [ ] Logger les performances (temps de chargement moyen)
|
|
|
|
**Tâche 4.3** : Documentation
|
|
- [ ] Commenter le code (dartdoc)
|
|
- [ ] Mettre à jour `FLOW-*.md` si nécessaire
|
|
- [ ] Ajouter section dans README technique
|
|
|
|
## Tests de validation
|
|
|
|
### Tests fonctionnels
|
|
|
|
1. **Cache vide → Premier chargement**
|
|
- [ ] Ouvrir l'app web en mode incognito
|
|
- [ ] Naviguer sur la carte
|
|
- [ ] Vérifier que les tuiles se chargent
|
|
- [ ] Vérifier les logs "Cache MISS" + "Storing tile"
|
|
|
|
2. **Cache rempli → Rechargement**
|
|
- [ ] Recharger la page (F5)
|
|
- [ ] Revenir sur la même zone de carte
|
|
- [ ] Vérifier que les tuiles se chargent instantanément
|
|
- [ ] Vérifier les logs "Cache HIT"
|
|
- [ ] Vérifier dans Network tab : pas de requêtes Mapbox pour les tuiles en cache
|
|
|
|
3. **Navigation étendue**
|
|
- [ ] Se déplacer sur plusieurs zones de la carte
|
|
- [ ] Revenir sur les zones précédentes
|
|
- [ ] Vérifier mix de "Cache HIT" et "Cache MISS"
|
|
|
|
4. **Gestion de l'expiration**
|
|
- [ ] Modifier temporairement l'expiration à 10 secondes (pour test)
|
|
- [ ] Charger des tuiles
|
|
- [ ] Attendre 15 secondes
|
|
- [ ] Recharger → Vérifier que les tuiles sont re-téléchargées
|
|
|
|
5. **Gestion d'erreur réseau**
|
|
- [ ] Désactiver le réseau en plein chargement
|
|
- [ ] Vérifier que les tuiles en cache s'affichent quand même
|
|
- [ ] Vérifier que les tuiles manquantes ne bloquent pas l'app
|
|
|
|
### Tests de performance
|
|
|
|
6. **Mesure de bande passante**
|
|
- [ ] Vider le cache
|
|
- [ ] Naviguer sur une zone avec ~100 tuiles visibles
|
|
- [ ] Noter la bande passante consommée (DevTools Network)
|
|
- [ ] Recharger la page et revenir sur la même zone
|
|
- [ ] Vérifier que la bande passante est ~0 KB
|
|
|
|
7. **Mesure de vitesse**
|
|
- [ ] Mesurer le temps de chargement initial : ____ ms
|
|
- [ ] Mesurer le temps de chargement avec cache : ____ ms
|
|
- [ ] Objectif : >50% de réduction
|
|
|
|
### Tests multi-plateformes
|
|
|
|
8. **Mobile/Desktop non régressé**
|
|
- [ ] Lancer sur Android → Vérifier que HiveCacheStore fonctionne toujours
|
|
- [ ] Lancer sur iOS → Vérifier que HiveCacheStore fonctionne toujours
|
|
- [ ] Vérifier les logs "Cache initialized with HiveCacheStore"
|
|
|
|
9. **Web multi-navigateurs**
|
|
- [ ] Tester sur Chrome
|
|
- [ ] Tester sur Firefox
|
|
- [ ] Tester sur Safari (si IndexedDB supporté)
|
|
- [ ] Tester sur Edge
|
|
|
|
## Checklist de validation finale
|
|
|
|
- [ ] Aucune nouvelle dépendance ajoutée au `pubspec.yaml`
|
|
- [ ] Code compatible web uniquement (guards `kIsWeb`)
|
|
- [ ] Mobile/Desktop non impactés (toujours HiveCacheStore)
|
|
- [ ] Logs de debug présents et clairs
|
|
- [ ] Gestion d'erreur complète (try/catch)
|
|
- [ ] Expiration automatique (30 jours)
|
|
- [ ] Performance mesurée et améliorée
|
|
- [ ] Tests manuels passés
|
|
- [ ] Code documenté (dartdoc)
|
|
- [ ] Pas de régression sur les autres plateformes
|
|
|
|
## Notes techniques
|
|
|
|
### Pourquoi IndexedDB et pas localStorage ?
|
|
|
|
- **localStorage** : Limite de 5-10 MB → Insuffisant pour des tuiles (1 tuile = ~60 KB)
|
|
- **IndexedDB** : Limite de ~50% de l'espace disque disponible → Largement suffisant
|
|
|
|
### Structure de la clé de cache
|
|
|
|
Format recommandé : `{styleId}_{z}_{x}_{y}@{scale}x`
|
|
|
|
Exemple : `streets-v11_15_16234_11378@2x`
|
|
|
|
Cela permet :
|
|
- Identification unique de chaque tuile
|
|
- Support multi-styles (streets, satellite, etc.)
|
|
- Support multi-résolutions (@1x, @2x)
|
|
|
|
### Taille estimée du cache
|
|
|
|
- 1 tuile Mapbox @2x : ~60 KB
|
|
- Carte complète niveau zoom 15 : ~500 tuiles
|
|
- Total pour une zone : ~30 MB
|
|
- IndexedDB peut stocker plusieurs GB → Pas de souci
|
|
|
|
### Maintenance du cache
|
|
|
|
Le nettoyage automatique (`clearExpiredTiles()`) s'exécute :
|
|
- Au démarrage de l'app
|
|
- Évite l'accumulation de vieilles tuiles
|
|
- Garde le cache sous contrôle
|
|
|
|
## Dépendances
|
|
|
|
### Packages Flutter (déjà présents)
|
|
- `flutter_map: ^7.0.2` → Fournit `TileProvider`
|
|
- `flutter_map_cache: ^1.5.1` → Pour mobile/desktop uniquement
|
|
|
|
### Bibliothèques Dart natives (aucun ajout)
|
|
- `dart:indexed_db` → Pour IndexedDB (web seulement)
|
|
- `dart:typed_data` → Pour manipuler les bytes
|
|
- `package:flutter/foundation.dart` → Pour `kIsWeb`
|
|
|
|
### Pas de nouvelle dépendance externe ✅
|
|
|
|
## Estimation
|
|
|
|
- **Temps de développement** : 2-3 heures
|
|
- **Complexité** : Moyenne
|
|
- **Risque** : Faible (fallback sur comportement actuel)
|
|
- **Gain** : Important pour les admins PROD
|
|
|
|
## Prochaine étape
|
|
|
|
**Attente de validation utilisateur** pour démarrer l'implémentation.
|