- Fix keyboard hiding subtask input: use precise scrollTo with onLayout position instead of unreliable scrollToEnd (#6) - Add expand/collapse button in widget for tasks with subtasks (#9) - Subtasks are now toggleable directly from the widget - Widget state (expanded tasks) persisted via AsyncStorage - Update CLAUDE.md with widget docs, build/deploy process, and release workflow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7.7 KiB
Simpl-Liste
Application mobile de gestion de tâches minimaliste par La Compagnie Maximus.
Bundle ID : com.lacompagniemaximus.simpliste — Scheme : simplliste
Stack
- React Native 0.81 + Expo SDK 54 (New Architecture)
- Expo Router 6 (file-based, typed routes)
- TypeScript 5.9 (strict)
- NativeWind 4.2 (Tailwind CSS 3.4 pour RN)
- Drizzle ORM 0.45 + expo-sqlite 16 (SQLite local,
simpliste.db) - Zustand 5 + AsyncStorage (state UI persisté)
- i18next 25 + react-i18next (FR/EN, français par défaut)
- lucide-react-native (icônes)
- date-fns 4 (dates)
Scripts
npm start # Expo dev server
npm run android # Lancer sur Android
npm run ios # Lancer sur iOS
Structure
app/
├── _layout.tsx # Root stack (fonts, migrations, theme)
├── (tabs)/
│ ├── _layout.tsx # Tab bar (3 onglets)
│ ├── index.tsx # Inbox
│ ├── lists.tsx # Toutes les listes
│ └── settings.tsx # Paramètres + gestion tags
├── task/
│ ├── new.tsx # Création de tâche (modal)
│ └── [id].tsx # Détail/édition tâche
└── list/
└── [id].tsx # Détail d'une liste
src/
├── components/
│ ├── FilterMenu.tsx # Modal filtres (bottom sheet)
│ ├── SortMenu.tsx # Modal tri (bottom sheet)
│ └── task/
│ ├── TagChip.tsx # Pill tag réutilisable
│ └── TaskItem.tsx # Rangée de tâche
├── db/
│ ├── client.ts # Config SQLite + Drizzle
│ ├── schema.ts # Tables : lists, tasks, tags, task_tags
│ ├── migrations/ # SQL migrations (auto-appliquées au démarrage)
│ └── repository/
│ ├── lists.ts # CRUD listes + ensureInbox()
│ ├── tags.ts # CRUD tags + relations task-tag
│ └── tasks.ts # CRUD tâches, filtres, tri, sous-tâches
├── i18n/
│ ├── index.ts # Init i18next (détection locale appareil)
│ ├── fr.json # Traductions françaises
│ └── en.json # Traductions anglaises
├── lib/
│ ├── priority.ts # Helpers couleurs priorité
│ ├── recurrence.ts # Types récurrence + calcul prochaine occurrence
│ ├── uuid.ts # Wrapper expo-crypto randomUUID
│ └── validation.ts # Validation UUID pour deep links
├── services/
│ ├── calendar.ts # Sync expo-calendar
│ ├── icsExport.ts # Export .ics + partage
│ ├── notifications.ts # Planification expo-notifications
│ └── widgetSync.ts # Sync tâches + thème vers widget Android
├── stores/
│ ├── useSettingsStore.ts # Thème, locale, notifs, calendrier
│ └── useTaskStore.ts # État tri/filtre
├── theme/
│ └── colors.ts # Palette centralisée (bleu, crème, terracotta)
└── widgets/
├── TaskListWidget.tsx # Composant widget Android (3 tailles, dark mode)
└── widgetTaskHandler.ts # Handler headless pour actions widget
Base de données
4 tables SQLite gérées par Drizzle ORM. Migrations auto-appliquées via useMigrations().
- lists : id, name, color, icon, position, is_inbox, timestamps
- tasks : id, title, notes, completed, priority (0-3), due_date, list_id → lists, parent_id (sous-tâches), position, recurrence, calendar_event_id, timestamps
- tags : id, name, color, timestamps
- task_tags : task_id → tasks (CASCADE), tag_id → tags (CASCADE)
Générer une migration
npx drizzle-kit generate
Puis mettre à jour src/db/migrations/migrations.js si nécessaire.
Conventions
- Français par défaut, anglais en second. Toutes les chaînes visibles dans
fr.json/en.json - Dark mode : résolu localement avec
isDarkternary (pas de NativeWind dark: prefix) - Composants : headers custom par écran (pas de React Navigation header)
- Polling 500ms : les écrans rechargent les données par intervalle (pas de live queries)
- Pas de state global pour les tâches : chargées depuis SQLite par écran
- Icônes via lucide-react-native, polices Inter (400/500/600/700)
- UUID générés via
expo-crypto - Sous-tâches = tâches avec
parentIdnon-null
Palette de couleurs
| Token | Valeur |
|---|---|
| bleu | #4A90A4 |
| crème | #FFF8F0 |
| terracotta | #C17767 |
| vert | #8BA889 |
| sable | #D4A574 |
| violet | #7B68EE |
| rouge | #E57373 |
| teal | #4DB6AC |
Couleurs sombres : fond #1A1A1A, surface #2A2A2A, bordure #3A3A3A, texte #F5F5F5, secondaire #A0A0A0
Widget Android
3 tailles configurées dans app.json (plugin react-native-android-widget) :
- SimplListeSmall (2×2) — Compteur de tâches + bouton ajout
- SimplListeMedium (2×4) — Liste de 4 tâches avec indicateur couleur de liste
- SimplListeLarge (4×4) — Liste de 8 tâches
Sync des données
widgetSync.tslit les tâches depuis SQLite et les cache dans AsyncStorage (widget:tasks)- Le thème est lu depuis AsyncStorage (
simpl-liste-settings→state.theme), résolu sisystemviaAppearance.getColorScheme(), et stocké danswidget:isDark widgetTaskHandler.tsgère le rendu headless (quand l'app n'est pas ouverte) en lisant les deux clés AsyncStorage- Les couleurs du widget suivent la même palette que l'app (voir
LIGHT_COLORS/DARK_COLORSdansTaskListWidget.tsx)
Clés AsyncStorage utilisées par le widget
| Clé | Contenu |
|---|---|
widget:tasks |
WidgetTask[] sérialisé JSON |
widget:isDark |
boolean sérialisé JSON |
simpl-liste-settings |
Store Zustand persisté (contient state.theme) |
Build & déploiement
Profiles EAS dans eas.json :
- development — APK avec dev client
- preview — APK de distribution directe (hors Play Store)
- production — AAB pour le Play Store,
autoIncrement: truesurversionCode
Commandes de build
npx eas-cli build --platform android --profile preview --non-interactive # APK
npx eas-cli build --platform android --profile production --non-interactive # AAB
Important : eas n'est pas installé globalement, utiliser npx --yes eas-cli (pas npx eas).
Processus de release
- Bumper
versiondansapp.jsonETpackage.json - Le
versionCodeAndroid est auto-incrémenté par EAS (autoIncrement: true) - Build preview (APK) + production (AAB)
- Créer la release sur Forgejo via API :
# Créer la release curl -X POST ".../api/v1/repos/maximus/simpl-liste/releases" -d '{"tag_name":"vX.Y.Z",...}' # Attacher l'APK curl -X POST ".../releases/{id}/assets?name=simpl-liste-vX.Y.Z.apk" -F "attachment=@fichier.apk" - Le bouton « Vérifier les mises à jour » dans l'app utilise l'endpoint
/releases/latestet propose le téléchargement de l'asset.apk
Repo Forgejo
- URL :
https://git.lacompagniemaximus.com/maximus/simpl-liste - Remote git :
origin(push via HTTPS avec token dans~/.git-credentials) - Issues : utilisées pour le suivi des bugs/features
- Releases : distribution APK avec assets attachés
Mises à jour in-app
Le bouton dans Paramètres > À propos appelle GET /api/v1/repos/maximus/simpl-liste/releases/latest (repo public, pas d'auth nécessaire). Compare release.tag_name (ex: v1.0.1) avec Constants.expoConfig.version. Si différent, affiche une Alert avec le changelog (release.body) et un lien vers le premier asset .apk trouvé.