Application Android native pour la gestion des signalements citoyens auprès des mairies. Version 100% Kotlin avec Material Design 3, architecture offline-first et synchronisation différée.
UrbaFix permet aux citoyens de signaler des incidents dans leur quartier (nids de poule, déchets, éclairage défectueux, etc.) même sans connexion réseau. Les signalements sont stockés localement et synchronisés automatiquement avec le serveur dès que le réseau est disponible.
Cas d’usage principal : Fonctionnement dans les zones blanches (zones sans couverture réseau) avec synchronisation différée.
app/src/main/java/com/example/monquartierkotlin/
├── data/
│ ├── local/
│ │ ├── dao/ # Data Access Objects (Room)
│ │ │ ├── IncidentDao.kt
│ │ │ └── PhotoDao.kt
│ │ ├── entities/ # Entités de base de données
│ │ │ ├── IncidentEntity.kt # Table incidents
│ │ │ └── PhotoEntity.kt # Table photos
│ │ ├── AppDatabase.kt # Configuration Room
│ │ └── Converters.kt # Convertisseurs de types
│ ├── remote/
│ │ ├── dto/ # Data Transfer Objects (API)
│ │ │ ├── ApiResponses.kt # DTOs incidents/types/mairie
│ │ │ ├── ProfileDto.kt # DTOs profil citoyen
│ │ │ └── SubmitIncidentRequest.kt
│ │ ├── UrbafixApi.kt # Interface Retrofit
│ │ └── RetrofitClient.kt # Configuration HTTP
│ └── repository/
│ └── IncidentRepository.kt # Couche d'accès aux données
├── ui/
│ ├── screens/
│ │ ├── home/ # Écran d'accueil
│ │ │ ├── HomeScreen.kt # Carte + Liste + Groupes
│ │ │ ├── HomeViewModel.kt
│ │ │ ├── HomeViewModelFactory.kt
│ │ │ ├── IncidentDetailScreen.kt # Vue détaillée complète
│ │ │ └── IncidentDetailViewModel.kt # Chargement photos
│ │ └── profile/ # Écran profil
│ │ ├── ProfileScreen.kt # + Partage app
│ │ └── ProfileViewModel.kt
│ ├── components/ # Composants réutilisables
│ │ ├── IncidentCard.kt # Carte de signalement
│ │ ├── IncidentGroupCard.kt # Carte de groupe empilée
│ │ └── IncidentDetailSheet.kt # Bottom sheet simple
│ └── theme/ # Thème Material Design 3
├── domain/
│ └── model/
│ └── IncidentGroup.kt # Modèle de groupe d'incidents
├── workers/
│ └── SyncIncidentsWorker.kt # Synchronisation en arrière-plan
├── utils/
│ ├── DeviceIdManager.kt # Gestion Device ID unique
│ ├── GeoUtils.kt # Calcul distances Haversine + groupement
│ └── MarkerUtils.kt # Génération marqueurs avec badges
├── UrbafixApplication.kt # Application class
└── MainActivity.kt # Activité principale
data class IncidentEntity(
val id: Long, // ID local
val serverId: Long?, // ID sur le serveur
val mairieId: Long,
val typeId: Long,
val typeName: String,
val typeColor: String, // Hex color
val adresse: String,
val latitude: Double,
val longitude: Double,
val description: String,
val statut: String, // nouveau, en_cours, resolu, ferme
val syncStatus: SyncStatus, // PENDING, SYNCING, SYNCED, ERROR
val createdAt: Long, // Timestamp
val reponseMairie: String?, // Réponse de la mairie
val dateReponse: Long?, // Date de la réponse
val deviceId: String // ID unique de l'appareil
)enum class SyncStatus {
PENDING, // En attente d'envoi
SYNCING, // En cours d'envoi
SYNCED, // Synchronisé avec succès
ERROR // Erreur lors de l'envoi
}L’application se connecte à l’API REST hébergée sur https://urbafix.fr
GET /api/get_incidents.php?code_postal=06500&limit=100
Récupère tous les incidents d’un code postal
GET /api/get_types.php?code_postal=06500
Récupère les types d’incidents disponibles pour une mairie
POST /api/submit_incident.php
Content-Type: multipart/form-data
type_id, description, latitude, longitude, adresse, mairie_id, photos[]
Soumet un nouvel incident
POST /api/register_device.php
Content-Type: application/x-www-form-urlencoded
device_id, code_postal, fcm_token (optionnel)
Enregistre un nouvel appareil
GET /api/get_profile.php?device_id=xxx&code_postal=06500
Récupère le profil d’un citoyen
POST /api/update_profile.php
Content-Type: application/json
{
"device_id": "xxx",
"nom": "Dupont",
"prenom": "Jean",
"email": "jean@example.com",
"telephone": "0612345678",
"code_postal": "06500"
}
Met à jour le profil utilisateur
Toutes les réponses suivent ce format :
{
"success": true|false,
"message": "Message explicatif",
"data": { ... }
}Exemple de réponse d’incidents :
{
"success": true,
"count": 12,
"mairie": {
"id": 1,
"nom": "Mairie de Menton",
"code_postal": "06500"
},
"incidents": [
{
"id": 22,
"type_id": 3,
"type_nom": "Déchets",
"type_couleur": "#10B981",
"adresse": "Cours Georges V, 06500, Menton",
"latitude": "43.77378910",
"longitude": "7.49685980",
"description": "Matelas déposé...",
"statut": "resolu",
"created_at": "2025-11-19 14:43:58",
"photos": ["/uploads/incidents/22_1763559838_0.jpeg"],
"nb_photos": 1
}
]
}syncStatus = PENDINGsyncStatus = SYNCED et reçoivent leur
serverId// Configuration (toutes les 15 minutes, uniquement avec réseau)
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val syncRequest = PeriodicWorkRequestBuilder<SyncIncidentsWorker>(
15, TimeUnit.MINUTES
)
.setConstraints(constraints)
.build()Utilisation de osmdroid au lieu de Google Maps pour : - ✅ Aucune clé API nécessaire - ✅ Pas de restrictions d’utilisation - ✅ Données OpenStreetMap libres - ✅ Cohérence avec la version web
MapView(context).apply {
setTileSource(TileSourceFactory.MAPNIK)
setMultiTouchControls(true)
controller.setZoom(13.0)
controller.setCenter(GeoPoint(43.7737, 7.4951)) // Menton
}<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />4 onglets dans la Bottom Navigation : 1. Accueil 🏠 : Carte + Liste des signalements 2. Signaler ➕ : Créer un nouveau signalement (à venir) 3. Mes signalements 📋 : Mes incidents (à venir) 4. Profil 👤 : Gestion du profil utilisateur
┌─────────────────────────────────┐
│ UrbaFix │
├─────────────────────────────────┤
│ │
│ [Carte OpenStreetMap] │ 40% de la hauteur
│ 🔴 (3) ← Groupe 3 incidents │ Marqueurs avec badges
│ 🟢 ← Incident seul │
│ │
├─────────────────────────────────┤
│ ┌──────────────────┐ │
│ │📍 Nid de poule │ [3] │ Carte empilée = groupe
│ │15 Ave Boyer │ │
│ └──────────────────┘ │ 60% hauteur (scrollable)
│ ─────────────────────────── │
│ 🗑️ Déchets │ Carte simple = individuel
│ Cours Georges V • Résolu │
│ ─────────────────────────── │
│ 💡 Éclairage défectueux │
│ Rue Partouneaux • En cours │
└─────────────────────────────────┘
Clic sur groupe → IncidentDetailScreen :
┌─────────────────────────────────┐
│ ← Détail de l'incident │
├─────────────────────────────────┤
│ [Mini carte OSM centrée] │
│ │
├─────────────────────────────────┤
│ ⭐ Incident principal │
│ 3 incidents dans cette zone │
├─────────────────────────────────┤
│ 📌 Type: Nid de poule │
│ 📝 Description: ... │
│ 📍 Adresse: ... │
│ 📅 Date: 28 déc. à 14:30 │
│ ℹ️ Statut: Nouveau │
├─────────────────────────────────┤
│ Photos (5) • De tous incidents │
│ [🖼️] [🖼️] [🖼️] [🖼️] [🖼️] ──→ │
├─────────────────────────────────┤
│ 📋 Autres incidents (2) │
│ • Nid de poule | il y a 2h │
│ Description... │
│ ─────────────────────────── │
│ • Nid de poule | il y a 1 jour │
│ Description... │
└─────────────────────────────────┘
cd /home/franck/AndroidStudioProjects/UrbafixKotlin
# Build debug APK
./gradlew clean assembleDebug
# APK généré dans :
# app/build/outputs/apk/debug/app-debug.apk# Via ADB
adb install -r app/build/outputs/apk/debug/app-debug.apk
# Taille de l'APK : ~17 MBPour changer l’URL de l’API (dev/prod), éditer :
// app/src/main/java/.../data/remote/RetrofitClient.kt
private const val BASE_URL = "https://urbafix.fr/"
// Pour développement local :
// private const val BASE_URL = "http://10.0.2.2:8080/" // Émulateur
// private const val BASE_URL = "http://192.168.1.X:8080/" // Appareil physique// app/src/main/java/.../data/repository/IncidentRepository.kt
// app/src/main/java/.../ui/screens/profile/ProfileViewModel.kt
private const val DEFAULT_CODE_POSTAL = "06500" // Menton// AppDatabase.kt
val passphrase = FingerprintManager.generateFingerprint(context).toByteArray()
val factory = SupportFactory(passphrase)// RetrofitClient.kt
val certificatePinner = CertificatePinner.Builder()
.add("urbafix.fr", "sha256/PU4bTrzvP9A7Ft6KDCL+E90q/7+CT/H9oB0iwTy6iS8=")
.build()<!-- network_security_config.xml -->
<base-config cleartextTrafficPermitted="false">level = if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}// ImageCompressor.kt
val anonymizedBitmap = FaceBlurrer.blurFaces(resizedBitmap, usePixelation = false)
if (anonymizedBitmap == null) {
// En cas d'erreur de floutage, on rejette l'image (sécurité RGPD)
return null
}SECURITE.md# Obtenir le nouveau pin
echo | openssl s_client -connect urbafix.fr:443 -servername urbafix.fr \
| openssl x509 -pubkey -noout \
| openssl pkey -pubin -outform der \
| openssl dgst -sha256 -binary | base64
# Mettre à jour :
# - RetrofitClient.kt ligne 72
# - network_security_config.xml ligne 17dependencies {
// Compose BOM
implementation(platform("androidx.compose:compose-bom:2024.02.00"))
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
// Room + SQLCipher
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
ksp("androidx.room:room-compiler:2.6.1")
implementation("net.zetetic:android-database-sqlcipher:4.5.4@aar")
implementation("androidx.sqlite:sqlite:2.4.0")
// Retrofit + Gson + Certificate Pinning
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
// WorkManager
implementation("androidx.work:work-runtime-ktx:2.9.0")
// OpenStreetMap
implementation("org.osmdroid:osmdroid-android:6.1.18")
// DataStore
implementation("androidx.datastore:datastore-preferences:1.0.0")
// Coil (images)
implementation("io.coil-kt:coil-compose:2.4.0")
// ML Kit Face Detection (RGPD)
implementation("com.google.mlkit:face-detection:16.1.6")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3")
}# Filtrer les logs de l'application
adb logcat | grep -E "(IncidentRepository|HomeViewModel|ProfileViewModel)"
# Voir les logs de synchronisation
adb logcat | grep -E "(SyncIncidentsWorker|WorkManager)"
# Logs HTTP (Retrofit)
adb logcat | grep -E "(OkHttp)"# Via ADB shell
adb shell
run-as com.example.monquartierkotlin
cd databases
sqlite3 incidents_database
# Requêtes utiles
SELECT * FROM incidents;
SELECT * FROM incidents WHERE syncStatus = 'PENDING';
SELECT COUNT(*) FROM incidents;Démarrage app
↓
HomeViewModel.init()
↓
syncIncidents() appelé
↓
IncidentRepository.syncIncidentsFromApi()
↓
GET https://urbafix.fr/api/get_incidents.php?code_postal=06500
↓
Réponse JSON avec 12 incidents
↓
Conversion DTO → Entity
↓
Insertion/Update dans Room Database
↓
Flow<List<IncidentEntity>> mis à jour
↓
HomeScreen recompose automatiquement
↓
Affichage carte + liste
ProfileScreen affiché
↓
ProfileViewModel.loadProfile()
↓
DeviceIdManager.getDeviceId(context)
↓
GET https://urbafix.fr/api/get_profile.php?device_id=xxx
↓
Si profil n'existe pas : registerDevice()
↓
POST https://urbafix.fr/api/register_device.php
↓
Profil créé (anonyme par défaut)
↓
Utilisateur remplit le formulaire
↓
ProfileViewModel.updateProfile(nom, prenom, email, tel)
↓
POST https://urbafix.fr/api/update_profile.php
↓
Profil mis à jour, isAnonymous = false
L'application UrbaFix (WebView) charge index.html dans un WebView Android. L'application UrbafixKotlin est 100% native avec : - ✅ Meilleures performances - ✅ Intégration native Android (permissions, caméra, GPS) - ✅ Offline-first réel avec Room - ✅ Synchronisation robuste avec WorkManager - ✅ Architecture testable et maintenable - ✅ Expérience utilisateur Material Design 3
Pourquoi Jetpack Compose ? - UI déclarative moderne - Moins de code boilerplate - Hot reload - Intégration parfaite avec Material Design 3
Pourquoi Room ? - ORM officiel Android - Support des Flow pour la réactivité - Compile-time verification des requêtes SQL - Migration automatique
Pourquoi WorkManager ? - Garantit l’exécution en arrière-plan - Respect des contraintes système (batterie, réseau) - Retry automatique en cas d’échec - Persistent même après redémarrage
Pourquoi osmdroid ? - Pas de clé API requise - Pas de quotas - Cohérence avec la version web - Données libres OpenStreetMap
Application propriétaire pour la gestion des signalements citoyens.
Version : 1.5.0 Dernière mise à jour : 03 janvier 2026 API Backend : https://urbafix.fr Changelog v1.5.0 : - ✅ Support des vidéos de 5 secondes max (capture + galerie) - ✅ Avertissement RGPD obligatoire avant capture vidéo - ✅ Compression et troncature automatique des vidéos - ✅ Aperçu vidéos avec indicateurs visuels (▶, durée, bordure bleue) - ✅ Upload vidéo en Base64 (compatible infrastructure existante) - ⚠️ Floutage visages non implémenté pour vidéos (mitigation : dialogue RGPD)