Projet Niv. 3 — Notes
Le brief : l’app de notes rapide — capture immédiate, recherche instantanée, partage sortant ET entrant, et un dossier « privé » derrière la biométrie. Le projet des entrées/ sorties : fichiers, partage, verrou.
Ce que vous allez pratiquer
La recherche plein texte SQLite (FTS5 — le niveau au-dessus de la P8), le débounce d’une recherche live, expo-local-authentication (ch. 9.5), le partage bidirectionnel (ch. 9.4), l’export fichiers (ch. 9.5). Chapitres liés : P8, 9.4, 9.5, 3.5.
Les features
- Capture : l’app s’ouvre SUR l’éditeur d’une nouvelle note (la friction zéro est LA feature d’une app de notes) ; titre auto = première ligne.
- Liste + recherche live : instantanée, surlignage des correspondances (bonus).
- Épingler / archiver (sections), suppression avec corbeille (7 jours).
- Dossier privé : les notes marquées privées exigent la biométrie pour s’afficher.
- Partage : exporter une note (texte ou .txt), et RECEVOIR du texte partagé d’autres apps (bonus — module hors SDK).
L’architecture recommandée
- FTS5 — la vraie leçon du projet : une table virtuelle de recherche synchronisée par triggers :
CREATE TABLE notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
body TEXT NOT NULL,
pinned INTEGER NOT NULL DEFAULT 0,
private INTEGER NOT NULL DEFAULT 0,
deleted_at INTEGER, -- la corbeille : soft delete
updated_at INTEGER NOT NULL
);
CREATE VIRTUAL TABLE notes_fts USING fts5(body, content=notes, content_rowid=id);
-- + les 3 triggers INSERT/UPDATE/DELETE qui synchronisent (recette standard FTS5)La recherche devient SELECT … FROM notes_fts WHERE notes_fts MATCH ? ORDER BY rank —
instantanée sur des milliers de notes. (La doc SQLite FTS5 donne les triggers — les
recopier EST l’exercice : lire une doc de référence.)
- Le débounce de la recherche (250 ms) : un petit hook useDebouncedValue — et la
requête via Query avec
queryKey: ['notes', 'search', debounced]. - Le verrou : un état
unlockedUntil(timestamp, store volatile — PAS persisté) ; ouvrir une note privée →LocalAuthentication.authenticateAsync()si expiré → 5 min de grâce. Le verrou protège l’AFFICHAGE (le contenu reste en clair en base — chiffrer serait l’upgrade, et une vraie leçon de gestion de clés : hors périmètre, dit explicitement dans votre README — l’honnêteté sur le modèle de menace fait partie du métier). - La corbeille :
deleted_at+ purge des > 7 jours au démarrage — le soft delete que toute app réelle finit par avoir.
Les jalons
- Schéma + FTS5 + triggers (vérifiés en SQL brut avant toute UI).
- Capture + liste + l’éditeur (autosave au blur/onChange débouncé — jamais de bouton Enregistrer).
- La recherche live (débounce + rank + états vides).
- Épingle, archive, corbeille (avec la purge).
- Le dossier privé (biométrie + grâce de 5 min).
- Partage sortant (.txt via cache + expo-sharing) + APK.
Critères de réussite
- 1 000 notes seedées : la recherche répond < 50 ms (FTS, pas un LIKE %…%)
- L’autosave ne perd JAMAIS une frappe (kill sauvage pendant la saisie → tout est là)
- La biométrie annulée n’affiche PAS la note (et l’échec est silencieux, pas une erreur)
- La corbeille restaure intégralement ; la purge ne touche que > 7 jours
- Une note de 50 000 caractères s’ouvre et défile sans saccade
- APK + vos vraies notes dedans (la confiance est le critère ultime d’une app de notes)
Pièges connus
- FTS5 et l’UPDATE : sans le trigger de mise à jour, la recherche renvoie l’ANCIEN texte — le bug silencieux type. Testez : créer, modifier, rechercher le nouveau mot.
- L’autosave à chaque frappe = une écriture SQLite par caractère : débouncez l’écriture (500 ms) — et flush au blur/unmount (le cleanup d’useEffect, enfin utile).
authenticateAsyncen boucle si l’utilisateur annule : gardez un état « refusé il y a < 30 s » pour ne pas re-prompter immédiatement (le harcèlement biométrique).
💡 L’upgrade : recevoir du texte partagé (expo-share-intent — votre premier module hors SDK, dev build à l’appui), et le markdown en lecture (un rendu léger — PAS un éditeur riche : connaître la frontière d’un week-end fait partie de l’exercice).