eric / piclead-client

client mobile pour Piclead, permet les capture de cartes de visites ainsi que de séparer son carnet d'adresse pro et perso.

README.md

PicLead

Documentation build/technique de l'app Android. Vue d'ensemble du produit : README racine · mode d'emploi écran par écran : guide utilisateur.

Application Android hors-ligne : scan d’une carte de visite → OCR local (Tesseract) → brouillon éditable → carnet local (SQLite), contact Android et export .vcf.

Aucune dépendance Google Play Services / ML Kit, aucun LLM. OCR et carnet restent 100 % hors-ligne ; la permission Internet sert uniquement à la sync optionnelle avec un serveur PicLead (voir ci-dessous).

Licence: AGPL v3 Plateforme 100% on-device

Mini-CRM (carnet local)

L’écran d’accueil est un carnet offline stocké en SQLite (Room) sur l’appareil :

  • Recherche FTS sur nom, entreprise, téléphones, e-mails et notes
  • Tri (nom, prénom, entreprise, date) + abcédaire
  • Fiche contact (swipe), notes, édition, photo de profil, rescan de la carte (les notes sont conservées)
  • Import VCF : lecture vCard 3.0 et 4.0, anti-doublons (skip si déjà présent)
  • Export VCF : écriture vCard 3.0 uniquement
  • Gestion des doublons : badge, revue, fusion ou marquage « contacts différents »
  • Création : scan, saisie manuelle ou import

Sans configuration sync : aucun compte, aucune donnée CRM envoyée hors appareil (export VCF manuel possible).

Sync PicLead (optionnelle)

Sync offline-first avec un serveur PicLead (piclead-server) : carnet, projets, tâches kanban et comptes-rendus. L’OCR et le scan ne nécessitent pas Internet.

Prérequis serveur : variable PICLEAD_API_CLES (clés API par utilisateur), endpoints /api/auth/cle, /api/sync/status, /api/sync/pull. Le contrat de ces endpoints est figé par les fixtures Server/contrats/*.json, rejouées par les tests des trois plateformes.

Configuration dans l’app : Paramètres (engrenage) → URL HTTPS du serveur → identifiant + mot de passe → « Obtenir la clé » → retaper le mot de passe pour déchiffrer la clé API (stockée chiffrée sur l’appareil ; le mot de passe n’est jamais conservé).

Pour le développement, un réseau interne ou une démo sans TLS : cocher « Autoriser HTTP (clair) » avant de saisir une URL http://…. Sans cette case, l’app refuse le HTTP (HTTPS obligatoire). Ne pas activer cette option en production sur Internet.

Utilisation : à l’ouverture, un bandeau signale les changements distants sans pull automatique. Le bouton Sync pousse les modifications locales (contacts créés/édités dans le carnet y compris l’image de carte scannée et la photo de profil si présentes, tâches, CR…) puis tire le delta serveur. Sans sync configurée, le carnet reste local uniquement.

Upgrade schéma v2 : la migration Room peut réinitialiser les données locales (documenté dans le plan sync).

Tâches et planification (dépend de la sync)

Les projets et leurs tâches arrivent par la sync ; l’écran projet affiche un kanban par colonnes de workflow. Il n’y a pas de diagramme de Gantt sur téléphone : l’écran retenu est la carte kanban enrichie.

Quand le projet a la planification activée (réglage web), chaque carte porte :

  • une pastille de couleur d’étiquette — même teinte que sur le web, le hachage étant reproduit à l’identique sur les trois plateformes ;
  • la période « 22/07 → 24/07 », calculée en jours ouvrés selon le rythme du projet (5 = lun-ven, 6 = lun-sam, 7 = tous les jours) ;
  • l’avancement des sous-tâches (« 2/5 fait ») ;
  • un repère de retard si la fin dépasse l’échéance du projet ;
  • la mention « ↳ regroupée » si la tâche a une tâche parente.

Si le projet n’active pas la planification, aucun de ces repères ne s’affiche — comme sur le web.

Éditable depuis le téléphone : titre, assigné, date de début (saisie en texte ISO AAAA-MM-JJ ; une date malformée bloque la validation plutôt que de partir au serveur), durée en jours ouvrés, étiquette (avec suggestions tirées des tâches du projet) et cases des sous-tâches.

Réservé au web : le diagramme de Gantt, l’édition des dépendances et du regroupement (transportés et conservés, mais non modifiables ici), ainsi que l’échéance et le rythme du projet.

duree_jours se compte en jours ouvrés, pas en jours calendaires. Les projets antérieurs sont neutralisés par jours_ouvres = 7 ; basculer un projet en rythme 5 allonge visiblement ses tâches en durée calendaire.

Agenda / calendriers (optionnel, dépend de la sync)

PicLead ne contient aucun écran agenda : les rendez-vous et réservations de ressources se consultent et se modifient dans l’app Agenda système (calendriers locaux, hors-GMS — aucun compte Google requis), via des calendriers dédiés que l’app crée et synchronise avec PicLead.

Permissions : READ_CALENDAR / WRITE_CALENDAR, demandées à l’exécution depuis Paramètres. Sans autorisation, le pont Agenda reste désactivé ; le carnet et la sync CRM (contacts/projets/tâches) continuent de fonctionner normalement.

Configuration dans l’app : Paramètres → section Calendriers (visible une fois connecté) :

  • Toggle « Mes RDV » → crée/lie le calendrier local PicLead — Mes RDV ; les événements qui y sont créés/modifiés deviennent des RDV PicLead à la sync.
  • Si des identifiants sont configurés, la liste des ressources (salles, matériel, véhicules) du serveur s’affiche avec un toggle par ressource active ; les ressources inactives sont grisées (non sélectionnables). Activer une ressource crée le calendrier PicLead — {Salle|Matériel|Véhicule} {nom} ; un événement qui y est créé devient une réservation Active poussée au serveur.
  • Désactiver un toggle demande confirmation avant retrait. Le retrait supprime réellement le calendrier local et ses événements de l’appareil (CalendarBridge.deleteCalendar, suppression des événements en cascade par Android), puis retire la liaison : le calendrier disparaît de l’app Agenda système. Les données serveur ne sont pas supprimées — vos RDV et réservations restent dans PicLead. Si l’autorisation Agenda a été révoquée entre-temps, la suppression échoue proprement : la liaison est retirée malgré tout et un message le signale dans la section Calendriers.

Sync et conflits : le bouton Sync pousse aussi les événements Agenda liés (RDV + réservations) puis tire le delta serveur (RDV, réservations actives, indisponibilités en lecture). Un bandeau signale un calendrier local en avance (événements pas encore poussés). Si une réservation entre en conflit avec le serveur (HTTP 409, chevauchement), PicLead conserve l’événement local, retire le blocage serveur pour affichage, puis propose un dialog « Annuler ma réservation ? » :

  • Oui → la réservation locale et son événement Agenda sont supprimés.
  • Non → rien n’est modifié ; l’arbitrage se fait sur le serveur web (demandes / réclamations restent hors périmètre mobile).

Plan d’implémentation : docs/plans/sync_agenda_rdv_ressources_v2.md. La spec de conception correspondante n’est pas versionnée (voir Documentation locale).

Room v3 (agenda) : l’ajout des tables RDV/réservations/indisponibilités passe la base Room en version 3 avec fallbackToDestructiveMigration — comme pour l’upgrade v2 ci-dessus, la mise à jour réinitialise les données locales (carnet inclus) ; ré-exportez en VCF ou synchronisez avant de mettre à jour l’app si nécessaire.

Fonctionnement (scan / OCR)

  1. Cadrez la carte et capturez (CameraX).
  2. Correction géométrique (OpenCV) + orientation EXIF.
  3. Prétraitement contraste (gris + CLAHE + binarisation adaptative, repli sur l’image brute) puis OCR Tesseract (7 langues) + post-traitement.
  4. Structuration heuristique (pas de modèle de langage).
  5. Vérifiez le brouillon, puis :
    • Enregistrer → carnet local (SQLite)
    • Créer le contact → ACTION_INSERT Contacts
    • Exporter VCF → partage d’un fichier .vcf (vCard 3.0)

Langues OCR

CodeLangue
fraFrançais
deuAllemand
engAnglais
spaEspagnol
porPortugais
itaItalien
polPolonais

Sélection des langues dans l’app

Les langues effectivement chargées par le moteur sont celles que l’utilisateur coche dans Paramètres → Modèles hors-ligne, parmi celles réellement installées. Par défaut : fra et eng. Une langue cochée mais non installée est simplement ignorée ; si aucune langue n’est disponible, le scan n’échoue pas en erreur : il propose le téléchargement, ou la saisie manuelle sur la photo de carte (voir « Saveurs » ci-dessous).

Saveurs de distribution : fdroid et direct

SaveurModèles dans le APKTaille releaseUsage
fdroidaucun — téléchargés depuis l’app~94 Mocatalogue F-Droid
directtessdata + Vosk embarqués~182 Mo (full)bêta-testeurs, distribution directe
cd android
./gradlew :app:assembleFdroidDebug                  # sans modèle, aucun téléchargement au build
./gradlew :app:assembleDirectDebug                  # modèles embarqués, variante fast
./gradlew :app:assembleDirectDebug -Ptessdata=full  # modèles embarqués, variante full

Les propriétés -Ptessdata=fast|full et -Pvosk=standard|none ne concernent que la saveur direct : la saveur fdroid n’embarque rien et ne déclenche donc aucun téléchargement au build.

Variante directPropriété GradleSourceTaille approx.
fast (défaut)-Ptessdata=fasttessdata_fast~18 Mo
full-Ptessdata=fulltessdata~80 Mo+

Premier build d’une variante direct : téléchargement dans android/tessdata-cache/{fast,full}/ (gitignored), puis injection dans les assets générés. Réseau requis uniquement si le cache est vide.

Suivi des modèles (le modèle a-t-il changé ?)

Les modèles sont figés au build : jamais re-téléchargés tant que le cache existe, jamais mis à jour au runtime. Les dépôts upstream tessdata_fast/tessdata sont d’ailleurs stables depuis des années ; les gains de précision viennent de la variante full, du prétraitement d’image (OcrPreprocessor) et des montées de version de Tesseract4Android — pas de nouveaux traineddata.

cd android
./gradlew tessdataStatus                  # variante fast
./gradlew tessdataStatus -Ptessdata=full  # variante full

Pour chaque langue : état vs upstream GitHub (à jour / DIFFÉRENT / cache absent), taille et SHA-256. Le build embarque aussi model-info.txt (taille + SHA-256 par langue) dans les assets, à côté de variant.txt. Pour forcer un re-téléchargement : supprimer android/tessdata-cache/ puis relancer un build.

Chargement ultérieur (sans rebuild)

Au runtime, Tesseract lit filesDir/tesseract/tessdata/ :

  1. Saveur direct, première exécution (ou changement fast↔full du APK) → copie depuis les assets embarqués.
  2. Saveur fdroid → rien dans les assets : les modèles arrivent par Paramètres → Modèles hors-ligne, téléchargés depuis tessdata_fast (ou un miroir interne configurable) et vérifiés par SHA-256 épinglé.
  3. Ensuite → réutilisation des fichiers locaux.
  4. Sideload : déposer/remplacer les *.traineddata dans ce dossier (ex. adb push), puis éventuellement écrire le stamp external pour empêcher l’écrasement au prochain changement de variante APK :
/data/data/fr.ebii.piclead/files/tesseract/tessdata/
  fra.traineddata
  …
  .card2vcf-tessdata-variant   # "fast" | "full" | "external"

Le téléchargement des modèles est explicitement déclenché par l’utilisateur (bouton dédié) : rien n’est récupéré en arrière-plan, conformément à la politique d’inclusion F-Droid. Les modèles tessdata_fast et vosk-model-small-fr sont sous licence Apache 2.0.

APK debug filtré arm64-v8a. Pour émulateur x86, retirez temporairement ndk.abiFilters dans app/build.gradle.kts.

Release signée (bêta-testeurs)

La signature release est lue depuis android/keystore.properties (gitignoré), qui pointe vers le keystore android/keystore/piclead-release.jks (gitignoré aussi). Sans ce fichier, assembleRelease produit un APK non signé, ininstallable par les testeurs.

⚠️ Sauvegardez keystore/piclead-release.jks + keystore.properties hors git (coffre, sauvegarde chiffrée). Toute mise à jour doit être signée avec la même clé : une clé perdue ou changée force les testeurs à désinstaller/réinstaller (perte des données locales).

Mise en place sur une nouvelle machine (à partir du keystore sauvegardé) — format de android/keystore.properties :

storeFile=keystore/piclead-release.jks
storePassword=…
keyAlias=piclead
keyPassword=…

À chaque bêta :

cd android
# 1. Incrémenter versionCode (obligatoire pour qu'Android accepte la mise à jour)
#    et versionName dans app/build.gradle.kts
# 2. Builder (mêmes variantes tessdata que le debug)
./gradlew :app:assembleDirectRelease                 # bêta-testeurs, fast (défaut)
./gradlew :app:assembleDirectRelease -Ptessdata=full # bêta-testeurs, full
./gradlew :app:assembleFdroidRelease                 # sans modèle
# 3. Récupérer l'APK nommé d'après l'application et sa saveur
ls app/build/outputs/apk/*/release/PicLead-*-release.apk

Vérification optionnelle de la signature avant envoi :

"$HOME"/Android/Sdk/build-tools/*/apksigner verify --print-certs \
  app/build/outputs/apk/direct/release/PicLead-*-release.apk

Distribution : envoyez le fichier PicLead-<version>-direct-release.apk tel quel (mail, lien) ; à l'installation, Android demandera d'autoriser l'installation depuis une source inconnue.

Publication en magasin : docs/publication-fdroid.md (F-Droid) et docs/publication-google-play.md (Google Play, App Bundle signé avec la même clé d’upload). La politique de confidentialité déclarée à Play est CONFIDENTIALITE.md, versionnée à la racine de ce dossier.

Build

Quatre variantes : deux saveurs (fdroid, direct) × deux types (debug, release). Toutes les commandes se lancent depuis android/.

VarianteCommandeAPK produit
fdroid debug./gradlew :app:assembleFdroidDebugapp/build/outputs/apk/fdroid/debug/PicLead-<version>-fdroid-debug.apk
fdroid release./gradlew :app:assembleFdroidReleaseapp/build/outputs/apk/fdroid/release/PicLead-<version>-fdroid-release.apk
direct debug./gradlew :app:assembleDirectDebugapp/build/outputs/apk/direct/debug/PicLead-<version>-direct-debug.apk
direct release./gradlew :app:assembleDirectReleaseapp/build/outputs/apk/direct/release/PicLead-<version>-direct-release.apk

Ajouter -Ptessdata=full aux variantes direct pour embarquer les modèles full au lieu des fast (voir « Saveurs de distribution » ci-dessus). La saveur fdroid ignore ces propriétés : elle n’embarque rien et n’accède pas au réseau au build.

cd android
./gradlew clean :app:assembleFdroidRelease   # build F-Droid reproductible, hors ligne
./gradlew :app:testFdroidDebugUnitTest
./gradlew :app:testDirectDebugUnitTest

Les tests d’absence de modèle vivent dans la source set app/src/testFdroid/ : la saveur direct embarque ses traineddata, l’absence n’y est pas reproductible.

Vérifier qu’un APK fdroid est bien vide de modèles (doit afficher 0) :

unzip -l app/build/outputs/apk/fdroid/release/PicLead-*-fdroid-release.apk \
  | grep -cE 'traineddata|vosk-model'

Install

adb install -r android/app/build/outputs/apk/fdroid/debug/PicLead-<version>-fdroid-debug.apk
adb install -r android/app/build/outputs/apk/direct/release/PicLead-<version>-direct-release.apk

Prérequis : JDK 21, Android SDK (API 35).

Limites (assumées)

  • Pas de LLM : la qualité du brouillon dépend des heuristiques + du texte OCR. Cartes très décoratives ou manuscrites peuvent échouer.
  • Caméra obligatoire (android.hardware.camera required) pour le scan ; le carnet et l’import VCF restent utilisables sans.
  • Pas de sync obligatoire : sans configuration PicLead, le carnet est local uniquement ; perte/désinstallation = perte des données sauf export VCF ou sync préalable.
  • Structuration sémantique avancée (homonymes, mises en page atypiques) hors périmètre.

Design

UI N&B éditoriale (design.md) : coins carrés, hairlines, polices TTF embarquées (Playfair Display, Lora, Manrope — sous licence OFL). Icône : icon.png.

L’OFL impose que sa copie accompagne les polices redistribuées : le texte et les trois notices de copyright sont embarqués dans l’app (app/src/main/res/raw/licence_polices_ofl.txt) et accessibles par À propos → Texte de la licence des polices. C’est la source unique ; docs/licenses/OFL-fonts.txt n’est qu’une archive locale.

Documentation locale (non versionnée)

Le .gitignore racine exclut docs/plans, Server/docs/plans, Server/docs/superpowers, Client/docs/plans/ et Client/docs/superpowers/ ; Client/.gitignore exclut en plus tout docs/*. Une partie de la documentation de travail n’est donc pas dans le dépôt, et les liens qui y pointaient ont été retirés de ce README.

Présent en local seulement, côté client :

FichierContenu
docs/publication-fdroid.mdProcédure de publication F-Droid (recette YAML, checklist)
docs/publication-google-play.mdProcédure de publication Google Play (signature, AAB, fiche, Sécurité des données)
docs/superpowers/specs/2026-07-22-sync-agenda-rdv-ressources-v2-design.mdConception de la sync agenda / ressources v2
docs/audit-securite-2026-07-27.mdAudit de sécurité
docs/licenses/OFL-fonts.txtArchive locale de la licence des polices (la version embarquée est dans res/raw)
docs/bandeau-play.shGénère le bandeau 1024 × 500 de Play depuis l’icône et la charte
docs/captures-fastlane.shCaptures fastlane (téléphone et tablettes) sur une copie isolée de l’app

Les plans du monorepo (docs/plans/ à la racine, dont les chantiers modèles OCR téléchargeables et planification des tâches) sont dans le même cas.

Seuls les six plans historiques de Client/docs/plans/ restent suivis : ils avaient été committés avant l’ajout des règles d’exclusion.

Un point à trancher, hors périmètre de ce README : l’exclusion de docs/plans contredit son propre commentaire dans le .gitignore (« les plans versionnés restent dans docs/plans/ ») et la consigne du CLAUDE.md (« les plans vivent dans le dépôt, avec le code »).

Origine du code

Pipeline scan/OCR/contact adapté depuis le projet open source luciole-mobile (AGPL), sans le module LLM (cerveau).

Licence

AGPL v3