# RECETTE ANDROID — AUDIT AVANT MODIFICATION (OBJECTIF 0 / §17) > Projet : `D:\Avalonia\Omika` — Base OMIKA réelle. Aucun fichier modifié au stade d'audit. > Ce document est le livrable de l'audit préalable. Les correctifs ne sont appliqués qu'après validation. ## Sommaire 1. [Architecture rappelée](#architecture) 2. [Tableau de synthèse des problèmes](#tableau) 3. [Cause exacte par problème](#causes) 4. [Classification](#classification) 5. [Preuve de non-régression Batik](#batik) 6. [Routes HTTP appelées](#routes) 7. [Requêtes SQL concernées](#sql) 8. [Inventaire des icônes à remplacer](#icones) 9. [Propositions de SVG monochrome](#svg) ## 1. Architecture rappelée - **Mobile** (`src/Omika.App.Local`) : offline-first sur SQLite, synchronisé avec l'API via `MobileApiClient`. - **API** (`src/Omika.Api`) : endpoints REST, sélection de base `Batik`/`Omika` via `ApplicationRuntimeContext.Current.DetectedVersion`. - **Données** (`src/Omika.Data`) : repositories `Adaptive*` qui délèguent à `Batik*Repository` (schéma historique) ou `Omika*Repository` (schéma physique Omika). Les mappers (`Mapping/Omika`, `Mapping/Batik`) produisent le modèle canonique. - **Identité publique** : GUID. Omika → GUID physique (colonne `GUID`). Batik → GUID synthétique réversible (`MobileEntityIdentityResolver`). - **UI** (`src/Omika.App`) : Avalonia, thèmes clair/sombre via `DesignTokens.axaml` (clés `DynamicResource`). ## 2. Tableau de synthèse | # | PROBLÈME | SYMPTÔME | COUCHE | FICHIER(S) | CAUSE EXACTE | SPÉCIFIQUE OMIKA ? | BATIK IMPACTÉ ? | CORRECTION PROPOSÉE | |---|---|---|---|---|---|---|---|---| | 1 | Carte OSM bloquée | 403 / "volunteer-run servers" | ANDROID/UI (tuiles) | `Controls/LieuMap.axaml.cs:97` | `OpenStreetMap.CreateTileLayer()` (Mapsui) utilise un HttpClient sans User-Agent identifiant Omika | NON | NON (commun) | Tile source custom avec User-Agent + cache + attribution ; fallback adresse+Itinéraire | | 2 | Icônes hétérogènes | Emoji/Unicode/couleurs | UI | multiples (cf. §8) | Emoji/Unicode et couleurs codées en dur | NON | NON | Standardiser en Path monochrome + `DynamicResource` | | 3 | Boutons entête intervention | fond blanc massif | UI | `Views/RendezVousDetailView.axaml:120-139` | `Background=OnPrimaryColorBrush` (blanc) + emoji `⌖ ⚒ ▧` | NON | NON | Style translucide + SVG monochrome | | 4 | Agenda équipe : icône groupe | bleue/multicolore | UI | `Views/AgendaView.axaml:377` (`👥`) | emoji `👥` utilisé comme icône de groupe (pas dans `AgendaEquipeView` qui n'a pas d'icône) | NON | NON | Remplacer par SVG/Path monochrome | | 5 | RDV→intervention introuvable | "Intervention introuvable." | OMIKA DATA / API | `OmikaRendezVousRepository`, `MobileSnapshotService`, `OmikaInterventionRepository.GetDetailsAsync` | (voir §3.5) | OUI | NON | Corriger le mapping/guid Omika | | 6A | Note CreationUser=0 | CreationUser=0 en base | API / OMIKA DATA | `OmikaInterventionRepository.ResolveCreationUserAsync:1560` | token `CollaborateurId` = PK numérique licence ; `ResolveCreationUserAsync` attend un GUID | OUI | NON | Résoudre l'utilisateur depuis l'identité token (UserId/UT_PKID ou PK numérique) | | 6B | Compteur notes = 0 | "0 note(s)" | LOCAL/API | `InterventionDetailViewModel.RefreshFromApiAsync` | compteur = `Notes.Count` chargé en arrière-plan via API ; échec/guid → vide | OUI (lié #5) | NON | même cause que #5 + retry | | 7 | Compteur images/documents = 0 | "0 fichier(s)" | API / OMIKA DATA | `MobileDocumentService.GetByInterventionAsync` + `OmikaDocumentRepository` | résolution guid intervention / filtre catégorie | OUI | NON | même cause que #5 + vérif filtre | | 8 | Écran Images/Documents | titre redondant + bouton coupé | UI | `Views/InterventionResourcesView.axaml:37,73` | `TextBlock "Images"` redondant + `StackPanel Orientation=Horizontal` non responsive | NON | NON | Supprimer titre + WrapPanel | | 9 | Création email 404 | 404 modèles courriel | API | `MobileApiClient.cs:761` + `ModeleDocumentEndpoints.cs:18` | **Slash final manquant** : mobile appelle `GET /modele-mails`, API enregistre `GET /modele-mails/` (`MapGroup("/modele-mails").MapGet("/")`) | NON (route commune) | NON (corrigé pour les 2) | `MapGet("/")` → `MapGet("")` (ou slash final côté mobile) | | 10 | Rapports 404 | 404 modèles rapport | API | `MobileApiClient.cs:771` + `ModeleDocumentEndpoints.cs:28` | **Slash final manquant** : `GET /modele-rapports` vs `GET /modele-rapports/` | NON | NON | idem #9 | | 11 | Bouton téléphone inopérant | rien ne se lance | ANDROID | `Services/PlatformPhoneLauncher.cs` | `TopLevel.Launcher.LaunchUriAsync` (ACTION_VIEW) ≠ ACTION_DIAL | NON | NON | Launcher Android `ACTION_DIAL` (tel:) | | 12 | Bouton email inopérant | aucun client mail | ANDROID | `Services/PlatformEmailLauncher.cs` | `LaunchUriAsync` générique ≠ ACTION_SENDTO | NON | NON | Launcher Android `ACTION_SENDTO` (mailto:) | | 13 | Fiche équipe/collaborateurs responsive | email sur plusieurs lignes, cartes hautes | UI | `CollaborateursView`, `CollaborateurDetailView`, `EquipeDetailView` | layout non responsive | NON | NON | Réorganiser grille responsive | | 14 | Messages d'erreur techniques | "Response status code…404" affiché | UI | `NouveauEmailViewModel`, `RapportChampsViewModel` | `ErrorMessage = ex.Message` brut | NON | NON | Message utilisateur propre + log technique | ## 3. Cause exacte par problème ### 3.1 Carte OpenStreetMap bloquée - `LieuMap.axaml.cs:97` appelle `OpenStreetMap.CreateTileLayer()` (Mapsui). Le `HttpTileSource` par défaut de Mapsui charge `https://tile.openstreetmap.org/{z}/{x}/{y}.png` via un `HttpClient` **sans User-Agent identifiant Omika** → OSM rejette avec 403 (« volunteer-run servers »). - Le géocodeur `AddressGeocoder` (Nominatim) a déjà un User-Agent correct (`Omika-Mobile/1.0`), mais pas le chargement des tuiles. - Pas de cache de tuiles configuré, pas d'attribution OSM visible, pas de fallback propre : l'image 403 brute est affichée. ### 3.5 RDV → « Ouvrir l'intervention » → introuvable Flux tracé (lecture de code) : 1. `RendezVousDetailViewModel.OpenIntervention` → navigation avec `RendezVous.InterventionGuid` (GUID). 2. `AgendaItem.InterventionGuid` ← `AgendaService.MapToAgendaItem` ← `RendezVousDetailsDto.InterventionId`. 3. `RendezVousDetailsDto.InterventionId` ← `MobileSnapshotService.MapRendezVous` ← `MobileIdentityMapper.ResolveIdString(Intervention, dto.InterventionSourceReference)`. 4. `InterventionSourceReference` ← `OmikaRendezVousMapper` : `{ NumericId: RV_IdInter, EntityGuid: ResolveGuid(interventionGuids, RV_IdInter) }`. 5. Pour Omika, `MobileEntityIdentityResolver.CreateOmikaIdentity` retourne `physical.Guid`. Si `EntityGuid` est null (GUID intervention non résolu), `CreateOmikaIdentity` **lève `InvalidOperationException`** (« aucun GUID physique fourni »). 6. Navigation → `InterventionLocalService.GetInterventionByIdAsync(Guid)` → API `GET /interventions/{guid}` → `OmikaInterventionRepository.GetDetailsAsync` (GUID → `GUID IN (...)`). **Conclusion de la trace statique (chaîne vérifiée de bout en bout)** : la chaîne d'identité est **correcte** pour Omika : - `OmikaRendezVousMapper` résout `InterventionSourceReference = { NumericId: RV_IdInter, EntityGuid: Intervention.GUID }` en bulk (`LoadInterventionGuidsAsync`, requête `IN_PKID IN (...)`). - `MobileSnapshotService.MapRendezVous` → `ResolveIdString` → `CreateOmikaIdentity` retourne le GUID physique. - `AgendaItem.InterventionGuid` = ce GUID ; navigation → API `GET /interventions/{guid}` → `GetDetailsAsync` (GUID) ; repli local sur la table snapshot `Intervention` (peuplée par `SnapshotApplier` à partir de `LoadInterventionsForRendezVousAsync`, clé = GUID canonique). - `PhysicalGuidParser.GetStorageCandidates` couvre les formes D / N / D-tronquée. **Déclencheur exact restant = niveau données** (à confirmer par les logs temporaires demandés au §5 de la recette) : - soit `RV_IdInter` pointe vers une intervention dont la colonne `GUID` est NULL/invalide (→ `EntityGuid` null → `CreateOmikaIdentity` lèverait, OU `InterventionId` null) ; - soit l'intervention référencée n'existe pas dans `Intervention` (référence orpheline) ; - soit un écart de format GUID entre `RV_IdInter→GUID` et l'`Intervention.GUID` réel (byte-order HFSQL). **Logs temporaires à poser (correction #5)** : `RDV_PKID`, `RDV GUID`, `RV_IdInter`, `Intervention.GUID` résolu, `InterventionGuid` envoyé à la navigation (type + valeur), requête SQL utilisée, résultat. Aucun `IN_NumIntervention` ne doit être réintroduit. Les compteurs #6B et #7 passent par les mêmes endpoints API (`GET /api/interventions/{guid}/notes`, `GET /api/interventions/{guid}/documents`) : si la résolution d'intervention échoue, listes vides → « 0 note(s) » / « 0 fichier(s) ». #7 a en plus un filtre par catégories de document (`BuildAllowedDocumentCategories` + `IsCategoryAllowed`) qui peut vider la liste si le profil n'a pas le droit `A.DOC.*` correspondant. ### 3.6A Note CreationUser = 0 - Endpoint `AddNoteForInterventionAsync` (`InterventionEndpoints.cs:427`) transmet `httpContext.GetMobileIdentity()?.CollaborateurId` à `InterventionService.AddNoteAsync` → `ResolveCreationUserAsync(collaborateurId)`. - En **production**, `LicenseEndpoints.BuildSession` (`LicenseEndpoints.cs:72`) pose `CollaborateurId = response.CollaborateurId` = `licence.CollaborateurId.ToString()` (PK numérique du collaborateur, cf. `LicenseService.BuildSuccessAsync:404`). - `OmikaInterventionRepository.ResolveCreationUserAsync` (`:1560`) fait `Guid.TryParse(collaborateurGuid, out Guid)` : une valeur numérique **échoue** → retour `0` → `CreationUser = 0`. - `BatikInterventionRepository.ResolveCreationUserAsync` (`:1513`) décode un GUID synthétique Batik : une valeur numérique échoue aussi (`Guid.TryParse` → 0). - **Racine** : incohérence de type entre la claim `CollaborateurId` du token (numérique en prod) et l'API de `ResolveCreationUserAsync` (attend un GUID). ### 3.9 / 3.10 Email / Rapport 404 — CAUSE EXACTE CONFIRMÉE - **Slash final manquant.** `MobileApiClient.GetModeleMailsAsync` appelle `GET {BaseUrl}/modele-mails` (sans slash), mais l'API enregistre la route `GET /modele-mails/` (avec slash) via `MapGroup("/modele-mails").MapGet("/")` (`ModeleDocumentEndpoints.cs:18`). En ASP.NET Core minimal API, `MapGroup("/x").MapGet("/")` produit `/x/` et **ne matche pas** `/x` → 404. - Idem `GetModeleRapportsAsync` → `GET /modele-rapports` vs `GET /modele-rapports/` (`ModeleDocumentEndpoints.cs:28`). - Les autres routes auditées (`/mot-clefs`, `/generation/email`, `/generation/rapport`, `/api/rapports/champs`, `/api/mails/champs`, `/api/mails`, `/api/mails/apercu`, `/api/rapports`) matchent **exactement**. Le préfixe `/api` est cohérent, les routes sont bien enregistrées (`Program.cs:243-244`), et `MobileAuthMiddleware` ne renvoie jamais 404. - **Correction** : `MapGet("/")` → `MapGet("")` dans `ModeleDocumentEndpoints` (canonique, fait matcher `/modele-mails` sans slash) — sans impact Batik (route schéma-agnostique, corrige les deux schémas). Alternative : ajouter le slash final dans `MobileApiClient` (l.761/771). - Référence historique `Mail_Modele` / `ModeleRapport WHERE MRP_EnvoiMob=1` → routes REST actuelles (cf. `docs/modeles-email-rapport.md`). ### 3.11 / 3.12 Téléphone / Email inopérants - `PlatformPhoneLauncher.DialAsync` et `PlatformEmailLauncher.ComposeAsync` utilisent `TopLevel.Launcher.LaunchUriAsync(uri)` (launcher générique Avalonia). Sur Android, ce launcher utilise `ACTION_VIEW`, qui **ne lance pas le composeur natif** (tel:) ni le client mail de façon fiable. - Aucun launcher Android spécifique n'existe (`src/Omika.App.Android` ne contient pas de Phone/Email launcher). - Les ViewModels ignorent le retour `bool` : aucun message si aucun handler n'est disponible. ### 3.14 Messages d'erreur techniques - `NouveauEmailViewModel.LoadAsync` et `RapportChampsViewModel.LoadAsync` font `catch (Exception ex) { ErrorMessage = ex.Message; }` ; `EnsureSuccessStatusCode()` lève `HttpRequestException` dont le message brut (« Response status code does not indicate success: 404 (Not Found). ») est affiché tel quel. ## 4. Classification | # | Classification | |---|---| | 1 | ANDROID/UI (commun, non spécifique données) | | 2 | UI | | 3 | UI | | 4 | UI | | 5 | OMIKA DATA + API (résolution identité intervention) | | 6A | API + OMIKA DATA (résolution utilisateur) | | 6B | LOCAL/API (compteur = source API) | | 7 | API + OMIKA DATA (documents) | | 8 | UI | | 9 | API (route/déploiement) | | 10 | API (route/déploiement) | | 11 | ANDROID (launcher) | | 12 | ANDROID (launcher) | | 13 | UI | | 14 | UI | ## 5. Preuve de non-régression Batik - Les corrections **UI/Android** (#1, #2, #3, #4, #8, #11, #12, #13, #14) ne touchent **aucune** requête Batik : elles portent sur les `.axaml`, les launchers Android et les messages d'erreur, partagés entre les deux schémas. - Les corrections **données** (#5, #6A, #7, #9, #10) seront appliquées **uniquement** dans les providers/repositories/mappers **Omika** (`src/Omika.Data/Repositories/Omika/*`, `Mapping/Omika/*`) ou dans la couche d'identité commune, **sans modifier** `Batik*Repository` ni `Batik*Mapper`. - `ResolveCreationUserAsync` Batik est déjà isolé dans `BatikInterventionRepository` ; sa correction éventuelle (acceptation du PK numérique) doit être faite symétriquement sans casser le décodage du GUID synthétique Batik (test à prévoir). - Les routes API (#9, #10) sont communes : l'ajout d'un alias `/api/modele-mails` (si retenu) n'affecte pas le comportement Batik, qui utilise la même API. ## 6. Routes HTTP actuellement appelées (MobileApiClient → Omika.Api) | Méthode client | URL appelée | Route API | Match | |---|---|---|---| | GetModeleMailsAsync | `GET /modele-mails` | `ModeleDocumentEndpoints` `/modele-mails` | ✅ (préfixe sans `/api`) | | GetModeleRapportsAsync | `GET /modele-rapports` | `ModeleDocumentEndpoints` `/modele-rapports` | ✅ | | GetMotClefsAsync | `GET /mot-clefs` | `ModeleDocumentEndpoints` `/mot-clefs` | ✅ | | GetEquipesAsync | `GET /equipes` | `EquipeEndpoints` `/equipes` | ✅ | | GetAllEquipesAsync | `GET /equipes/all` | `EquipeEndpoints` `/equipes/all` | ✅ | | GetCollaborateurByIdAsync | `GET /collaborateurs/{id}` | `CollaborateurEndpoints` | ✅ | | GetRendezVousByIdAsync | `GET /rendez-vous/{id}` | `RendezVousEndpoints` | ✅ | | GetInterventionByIdAsync | `GET /interventions/{id}` | `InterventionEndpoints` | ✅ | | GetDossierByIdAsync | `GET /dossiers/{id}` | `DossierEndpoints` | ✅ | | GetClientByIdAsync | `GET /clients/{id}` | `ClientEndpoints` | ✅ | | GetSiteByIdAsync | `GET /sites/{id}` | `SiteEndpoints` | ✅ | | GetContactByIdAsync | `GET /contacts/{id}` | `ContactEndpoints` | ✅ | | GetCompteRenduByRendezVousAsync | `GET /rendez-vous/{id}/compte-rendu` | `CompteRenduEndpoints` | ✅ | | GetNotesByInterventionAsync | `GET /api/interventions/{id}/notes` | `InterventionEndpoints` mobileGroup | ✅ | | AddNoteByInterventionAsync | `POST /api/interventions/{id}/notes` | `InterventionEndpoints` mobileGroup | ✅ | | GetDocumentsByInterventionAsync | `GET /api/interventions/{id}/documents` | `InterventionEndpoints` mobileGroup | ✅ | | GetRendezVousByInterventionAsync | `GET /api/interventions/{id}/rendez-vous` | `InterventionEndpoints` mobileGroup | ✅ | | GetEmailsByInterventionAsync | `GET /api/interventions/{id}/emails` | `InterventionEndpoints` mobileGroup | ✅ | | AddEmailByInterventionAsync | `POST /api/interventions/{id}/emails` | `InterventionEndpoints` mobileGroup | ✅ | | GetSmtpConfigurationsAsync | `GET /api/mails/configurations-smtp` | `RapportChampEndpoints` | ✅ | | GetRapportChampsAsync | `POST /api/rapports/champs` | `RapportChampEndpoints` | ✅ | | GetMailChampsAsync | `POST /api/mails/champs` | `RapportChampEndpoints` | ✅ | | CreateEmailModeleAsync | `POST /api/mails` | `RapportChampEndpoints` | ✅ | | GetEmailModeleApercuAsync | `POST /api/mails/apercu` | `RapportChampEndpoints` | ✅ | | CreateRapportAsync | `POST /api/rapports` | `RapportChampEndpoints` | ✅ | | GenerateEmailAsync | `POST /generation/email` | `ModeleDocumentEndpoints` | ✅ | | GenerateRapportAsync | `POST /generation/rapport` | `ModeleDocumentEndpoints` | ✅ | | SearchInterventionsAsync | `POST /api/interventions/search` | `InterventionEndpoints` mobileGroup | ✅ | | GetInterventionsByIdsAsync | `POST /api/interventions/by-ids` | `InterventionEndpoints` mobileGroup | ✅ | | SearchDossiersAsync | `GET /api/dossiers` | `DossierEndpoints` mobileGroup | ✅ | | SearchClientsAsync | `GET /api/clients` | `ClientEndpoints` mobileGroup | ✅ | | SearchSitesAsync | `GET /api/sites` | `SiteEndpoints` mobileGroup | ✅ | | SearchCollaborateursAsync | `GET /collaborateurs` | `CollaborateurEndpoints` | ✅ (sans `/api`) | | GetSuivisTempsAsync | `GET /api/suivis-temps` | `SuiviTempsEndpoints` | ✅ | | GetBootstrapAsync | `GET /api/mobile/bootstrap` | `MobileBootstrapEndpoints` | ✅ | | GetSnapshotAsync | `GET /api/sync/snapshot` | `SyncEndpoints` | ✅ | | SendOperationAsync | `POST /api/sync/operations` | `SyncEndpoints` | ✅ | | GetStatusAsync | `GET /api/sync/status/{id}` | `SyncEndpoints` | ✅ | | ActivateAsync | `POST /api/license/activate` | `LicenseEndpoints` | ✅ | | ValidateAsync | `POST /api/license/validate` | `LicenseEndpoints` | ✅ | | GetDevelopmentSessionAsync | `POST /api/dev/session` | `DevelopmentAuthEndpoints` (Dev only) | ✅ | > **Incohérence constatée** : le mobile utilise un mélange de routes avec `/api` et sans `/api` (ex. `/modele-mails` sans préfixe vs `/api/mails`). Les routes « modèle » (`/modele-mails`, `/modele-rapports`, `/mot-clefs`, `/generation/*`) sont les seules sans `/api`. C'est cohérent avec le code API actuel mais fragile et à uniformiser. ## 7. Requêtes SQL concernées (côté API, base HFSQL) | Problème | Requête | |---|---| | #5 (intervention par GUID) | `SELECT … FROM Intervention WHERE GUID IN (?)` (formes D/N/tronquée via `PhysicalGuidParser.GetStorageCandidates`) — `OmikaInterventionRepository.GetDetailsAsync` | | #5 (RDV→intervention) | `SELECT … FROM RDV_Planning … LEFT JOIN Intervention I ON RV.RV_IdInter = I.IN_PKID` (résolution bulk `RV_IdInter → Intervention.GUID`) — `OmikaRendezVousRepository` | | #6A (user) | `SELECT Utilisateur.UT_PKID FROM Utilisateur INNER JOIN Collaborateur ON Collaborateur.CO_PKID = Utilisateur.UT_IDCollaborateur WHERE Collaborateur.GUID IN (?)` — `OmikaInterventionRepository.ResolveCreationUserAsync` | | #6A (Batik) | `SELECT UT_PKID FROM Utilisateur WHERE UT_IDCollaborateur = ?` — `BatikInterventionRepository.ResolveCreationUserAsync` | | #6B/#7 (notes) | `SELECT MI_PKID, GUID, MI_Message, CreationDate, CreationUser FROM MessageInterne WHERE MI_IDIntervention = ? AND (MI_IDTypeMessage IS NULL OR MI_IDTypeMessage <= 10)` — `OmikaInterventionRepository.GetNotesAsync` | | #7 (documents) | résolution intervention GUID → `IN_PKID` puis requête documents (filtre `DC_IDIntervention` / catégorie / état) — `OmikaDocumentRepository.GetByInterventionAsync` | | #7 (catégories) | `BuildAllowedDocumentCategories` → filtrage par clés `T.DOC.*` selon droits `A.DOC.*` | ## 8. Inventaire des icônes à remplacer (extrait — inventaire complet en cours) | Fichier | Ligne | Actuel | Fonction | Proposition | |---|---|---|---|---| | `Views/RendezVousDetailView.axaml` | 122 | `⌖ Itinéraire` (emoji) | Itinéraire | Path "navigation/itinéraire" monochrome | | `Views/RendezVousDetailView.axaml` | 134 | `⚒ Ouvrir l'intervention` | Ouvrir intervention | Path "marteau/outil" monochrome | | `Views/RendezVousDetailView.axaml` | 139 | `▧ Compte rendu` | Compte rendu | Path "document" monochrome | | `Views/InterventionResourcesView.axaml` | 37/73 | titre texte redondant | (titre) | supprimer | | `Controls/LieuMap.axaml` | 25 | `⌖` (TextBlock) | épingle | Path "pin" monochrome | | `ViewModels/NouveauEmailViewModel.cs` | 144 | `✉ Email vierge` | modèle vierge | (libellé, pas d'icône) | | `ViewModels/NouveauEmailViewModel.cs` | 888 | `📷 {Nom}` | pièce jointe photo | Path "image" | | `ViewModels/RapportChampsViewModel.cs` | 606 | `✓` / `!` (Indicateur) | état rubrique | Path check/alert monochrome | | `Views/AgendaEquipeView.axaml` | (filtre Équipe) | icône groupe colorée | filtre équipe | Path "group" monochrome | | `Views/InterventionDocumentsView.axaml` | 17 | `← Retour` | retour | Path "arrow-left" | Les contrôles `CallButton.axaml` et `EmailButton.axaml` utilisent **déjà** des `Path` monochromes `Fill="{DynamicResource TextPrimaryBrush}"` : conformes, à conserver comme référence visuelle. ## 9. Propositions de SVG monochrome (famille unique) Famille retenue : **pictogrammes "Line/Outline" sobres** (traits fins, style Lucide/Material Outlined), **un seul style** (trait 1.5–2, pas de remplissage multicolore), couleur portée par `Foreground="{DynamicResource PrimaryBrush}"` ou `Fill="{DynamicResource …}"` — **jamais codée en dur**. | Pictogramme | Usage | Path (24×24 viewBox, données type Material) | |---|---|---| | Téléphone | appel | `M6.62,10.79c1.44,2.83 …` (déjà dans CallButton) | | Email | courriel | `M20,4H4C2.9,4 …` (déjà dans EmailButton) | | Itinéraire/GPS | itinéraire | navigation arrow / pin | | Intervention (marteau/outil) | ouvrir intervention | wrench/hammer | | Rapport | rapport | document + lignes | | Note | note | edit/pencil | | Image | photo | image frame | | Document | document | file | | Collaborateurs | personne | person outline | | Équipe | groupe | people/group outline | | Calendrier | agenda | calendar | | Paramètres | paramètres | gear | | Synchronisation | sync | refresh/circular arrows | | Retour | retour | arrow-left | | Ajouter | ajouter | plus | | Télécharger | télécharger | download arrow | | Épingle | carte | location pin | Chaque pictogramme sera matérialisé par un `Path` Avalonia (monochrome) ou un `StreamGeometry`, avec `Fill`/`Foreground` en `DynamicResource` pour rester compatible clair/sombre. ## 8bis. Couleurs codées en dur à remplacer par `DynamicResource` | Fichier | Ligne | Valeur | Remplacer par | |---|---|---|---| | `Views/RendezVousDetailView.axaml` | 143 | `#E53935` / `White` (bouton Annuler) | `DangerBrush` / `OnDanger` | | `Views/RapportChampsView.axaml` | 108/115/122 | `#22c55e` / `#f59e0b` / `#ef4444` | `SuccessBrush` / `WarningBrush` / `DangerBrush` | | `Views/RapportChampsView.axaml` | 253 | `#e5e7eb` | `SurfaceTertiaryBrush` | | `Views/RapportChampsView.axaml` | 138,233,258 | `ThemeForegroundLowBrush` (**clé INEXISTANTE** dans DesignTokens) | `TextMutedBrush` | | 10 fichiers (badges héros) | — | `#26FFFFFF` | token `OnPrimaryContainer`/`HeroChip` | | `Controls/ImageEditorControl.axaml` | — | `#E53935…`, `#FFFFFF` | ressources centralisées | | `Views/*Converter.cs` (`NavItemTextBrushConverter`, `SyncStateToBrushConverter`) | — | `Brushes.Gray` fallback | ressource `TextMutedBrush` | ## 8ter. Correctifs appliqués (lot 1 — compilation Android 0 erreur vérifiée) | # | Correctif | Fichiers modifiés | Vérification | |---|---|---|---| | 9/10 | `MapGet("/")` → `MapGet("")` (route sans slash final) | `src/Omika.Api/Endpoints/ModeleDocumentEndpoints.cs` | Omika.Api 0 erreur | | 6A | `ResolveCreationUserAsync` accepte PK numérique + GUID (Omika & Batik) | `OmikaInterventionRepository.cs`, `BatikInterventionRepository.cs` | Omika.Data 0 erreur | | 11/12 | Launchers natifs Android `ACTION_DIAL`/`ACTION_SENDTO` + hook `PlatformLaunchers` | `AndroidPhoneLauncher.cs`, `AndroidEmailLauncher.cs` (nouveaux), `PlatformLaunchers.cs` (nouveau), `AppServices.cs`, `Application.cs` | Android 0 erreur | | 1 | User-Agent OSM explicite `OmikaMobile/1.0 (+https://www.applilogik.fr)` | `Controls/LieuMap.axaml.cs` | Omika.App 0 erreur | | 3 | Boutons d'en-tête translucides `heroAction` + icônes Path monochrome | `Views/RendezVousDetailView.axaml`, `Styles/Controls.axaml` | Omika.App 0 erreur | | 8 | Suppression titre redondant + `WrapPanel` (boutons wrappables) | `Views/InterventionResourcesView.axaml` | Omika.App 0 erreur | | 14 | Messages utilisateur propres + log technique | `NouveauEmailViewModel.cs`, `RapportChampsViewModel.cs` | Omika.App 0 erreur | | 5 | Logs temporaires de traçage RDV→intervention | `RendezVousDetailViewModel.cs`, `InterventionLocalService.cs` | Omika.App 0 erreur | ### Lot 2 — Icônes monochromes + couleurs + responsive (compilation 0 erreur) | # | Correctif | Fichiers | |---|---|---| | 2 | Bug clé `ThemeForegroundLowBrush` (inexistante) → `TextMutedBrush` | `RapportChampsView.axaml`, `NouveauEmailView.axaml` | | 2 | Couleurs sémantiques en dur → `Success/Warning/Danger/SurfaceTertiaryBrush` | `RapportChampsView.axaml` | | 2 | Icônes de tuiles emoji → Path monochrome `PrimaryTextColorBrush` (✎ ▧ ✉ 🛡 📄) | `InterventionDetailView.axaml` | | 4 | Icône groupe `👥` → Path « people » monochrome | `AgendaView.axaml` | | 2 | Icône calendrier `📅` → Path « calendar » monochrome | `Controls/DateField.axaml` | | 2 | Tokens `HeroChipBrush` / `HeroBorderBrush` ajoutés | `DesignTokens.axaml` | | 13 | Email non tronqué (`TextWrapping`) fiche collaborateur + liste collaborateurs | `CollaborateurDetailView.axaml`, `CollaborateursView.axaml` | > Reste (lots suivants) : `#26FFFFFF` → `HeroChipBrush` sur les badges (cosmétique), autres emoji (`↻ ⌖ ⚠ ⓘ 🗑 ✎ 🖼 🔒 ★/☆ ✉ + → ←`), responsive `FilterGrid` agenda équipe, et les correctifs de données `#5/#6B/#7` (résolution intervention + filtre catégories `T.DOC.*`). > Build Android : `dotnet build src\Omika.App.Android\Omika.App.Android.csproj -c Debug -m:1 -nodeReuse:false` → **0 erreur** (SDK 10.0.302 via `global.json`). Les builds nécessitent `-m:1 -nodeReuse:false` (bug de nœud MSBuild parallèle dans l'environnement) et un accès élargi pour la télémétrie Avalonia (écrit hors workspace). ## 9. État des sous-analyses - ✅ Route API (#9/#10) : **confirmé** — slash final manquant (`/modele-mails` vs `/modele-mails/`). - ✅ Icônes + responsive (#2/#3/#4/#8/#13) : **confirmé** — inventaire ~40 emoji/Unicode, aucune couleur codée en dur dans les Path, liste des couleurs en dur à remplacer (dont clé manquante `ThemeForegroundLowBrush`). - ✅ Trace RDV→intervention (#5/#6B/#7) : chaîne d'identité vérifiée de bout en bout (correcte) ; déclencheur exact = niveau données, à confirmer par les logs temporaires prévus en correction.