feat: add refresh button on web + swipe-to-refresh on mobile #61

Closed
opened 2026-04-09 00:47:21 +00:00 by maximus · 1 comment
Owner

Contexte

Ref #59

Actuellement le web se rafraîchit uniquement via WebSocket (useSync.ts). Il n'y a aucun moyen de forcer un refresh manuel. Sur mobile, le polling 500ms recharge les données, mais il n'y a pas de geste pull-to-refresh.

Travail à faire

Web

  • Ajouter un bouton "Rafraîchir" dans TaskList.tsx (icône RefreshCw de lucide)
  • Le bouton appelle router.refresh() avec un état de loading visuel

Mobile

  • Ajouter RefreshControl sur les FlatList/DraggableFlatList dans app/(tabs)/index.tsx et app/list/[id].tsx
  • Le swipe-to-refresh déclenche loadData() avec indicateur visuel

Fichiers concernés

  • web/src/components/TaskList.tsx — bouton refresh
  • app/(tabs)/index.tsx — RefreshControl mobile
  • app/list/[id].tsx — RefreshControl mobile

Critères d'acceptation

  • L'utilisateur web peut rafraîchir manuellement via un bouton
  • L'utilisateur mobile peut tirer vers le bas pour rafraîchir
  • Indicateur visuel de chargement pendant le refresh

Complexité

Simple

## Contexte Ref #59 Actuellement le web se rafraîchit uniquement via WebSocket (`useSync.ts`). Il n'y a aucun moyen de forcer un refresh manuel. Sur mobile, le polling 500ms recharge les données, mais il n'y a pas de geste pull-to-refresh. ## Travail à faire ### Web - [ ] Ajouter un bouton "Rafraîchir" dans `TaskList.tsx` (icône `RefreshCw` de lucide) - [ ] Le bouton appelle `router.refresh()` avec un état de loading visuel ### Mobile - [ ] Ajouter `RefreshControl` sur les `FlatList`/`DraggableFlatList` dans `app/(tabs)/index.tsx` et `app/list/[id].tsx` - [ ] Le swipe-to-refresh déclenche `loadData()` avec indicateur visuel ## Fichiers concernés - `web/src/components/TaskList.tsx` — bouton refresh - `app/(tabs)/index.tsx` — RefreshControl mobile - `app/list/[id].tsx` — RefreshControl mobile ## Critères d'acceptation - [ ] L'utilisateur web peut rafraîchir manuellement via un bouton - [ ] L'utilisateur mobile peut tirer vers le bas pour rafraîchir - [ ] Indicateur visuel de chargement pendant le refresh ## Complexité Simple
maximus added the
status:ready
type:feature
labels 2026-04-09 00:48:09 +00:00
maximus added
status:review
and removed
status:ready
labels 2026-04-09 01:08:01 +00:00
maximus added
status:approved
and removed
status:review
labels 2026-04-09 01:23:14 +00:00
maximus reopened this issue 2026-04-09 13:20:08 +00:00
Author
Owner

Réouverture — swipe-to-refresh ne fonctionne pas

Le RefreshControl ajouté dans la PR #64 ne déclenche aucun feedback visuel sur mobile. Cause : conflit de gesture entre DraggableFlatList et RefreshControl.

Diagnostic

react-native-draggable-flatlist enveloppe le FlatList interne dans un GestureDetector avec Gesture.Pan() (DraggableFlatList.tsx:376). Même si la prop refreshControl est correctement forwardée vers le FlatList sous-jacent ({...props} ligne 386), le GestureDetector extérieur intercepte les mouvements verticaux avant que le RefreshControl puisse les détecter — particulièrement quand activationDistance={0} (mode drag actif en sort position, qui est le défaut).

Changements à faire

Mobile — remplacer le swipe par un bouton dans la toolbar

  • Retirer le RefreshControl et le state refreshing de app/(tabs)/index.tsx (dead code, ne fonctionne pas)
  • Retirer l'import de RefreshControl dans app/(tabs)/index.tsx
  • Retirer le RefreshControl et le state refreshing de app/list/[id].tsx
  • Retirer l'import de RefreshControl dans app/list/[id].tsx
  • Ajouter un bouton refresh (icône RefreshCw de lucide-react-native) dans la toolbar de l'inbox (app/(tabs)/index.tsx), au même endroit que les boutons Search/Download/Sort/Filter
  • Ajouter le même bouton dans la toolbar du détail de liste (app/list/[id].tsx)
  • Le bouton appelle loadTasks() / loadData() avec une animation spin pendant le refresh (cohérent avec le bouton refresh web)

Pourquoi cette approche

  • Toujours fonctionnel : pas de conflit gesture, indépendant du sort mode et du drag-and-drop
  • Cohérent avec le web : même UX (bouton avec icône RefreshCw)
  • Simple : aucune dépendance ou refactor de la stack gesture
  • Pas de dead code : on retire le RefreshControl qui ne marche pas plutôt que de le laisser en place

Alternatives écartées :

  • Conditional rendering FlatList/DraggableFlatList : refresh dispo uniquement hors sort position → incohérent
  • Custom Gesture.Pan au-dessus : trop complexe pour le bénéfice

Critères d'acceptation

  • Bouton refresh visible dans la toolbar de l'inbox et du détail de liste
  • Le bouton recharge les données et affiche un feedback visuel (spin) pendant le chargement
  • Aucune trace de RefreshControl ou state refreshing lié au swipe (dead code retiré)
  • L'icône utilise RefreshCw de lucide-react-native avec la couleur cohérente (isDark ? '#A0A0A0' : '#6B6B6B')
## Réouverture — swipe-to-refresh ne fonctionne pas Le `RefreshControl` ajouté dans la PR #64 ne déclenche aucun feedback visuel sur mobile. Cause : conflit de gesture entre `DraggableFlatList` et `RefreshControl`. ### Diagnostic `react-native-draggable-flatlist` enveloppe le `FlatList` interne dans un `GestureDetector` avec `Gesture.Pan()` (`DraggableFlatList.tsx:376`). Même si la prop `refreshControl` est correctement forwardée vers le FlatList sous-jacent (`{...props}` ligne 386), le `GestureDetector` extérieur intercepte les mouvements verticaux avant que le `RefreshControl` puisse les détecter — particulièrement quand `activationDistance={0}` (mode drag actif en sort `position`, qui est le défaut). ### Changements à faire #### Mobile — remplacer le swipe par un bouton dans la toolbar - [ ] **Retirer** le `RefreshControl` et le state `refreshing` de `app/(tabs)/index.tsx` (dead code, ne fonctionne pas) - [ ] **Retirer** l'import de `RefreshControl` dans `app/(tabs)/index.tsx` - [ ] **Retirer** le `RefreshControl` et le state `refreshing` de `app/list/[id].tsx` - [ ] **Retirer** l'import de `RefreshControl` dans `app/list/[id].tsx` - [ ] **Ajouter** un bouton refresh (icône `RefreshCw` de `lucide-react-native`) dans la toolbar de l'inbox (`app/(tabs)/index.tsx`), au même endroit que les boutons Search/Download/Sort/Filter - [ ] **Ajouter** le même bouton dans la toolbar du détail de liste (`app/list/[id].tsx`) - [ ] Le bouton appelle `loadTasks()` / `loadData()` avec une animation `spin` pendant le refresh (cohérent avec le bouton refresh web) ### Pourquoi cette approche - **Toujours fonctionnel** : pas de conflit gesture, indépendant du sort mode et du drag-and-drop - **Cohérent avec le web** : même UX (bouton avec icône `RefreshCw`) - **Simple** : aucune dépendance ou refactor de la stack gesture - **Pas de dead code** : on retire le `RefreshControl` qui ne marche pas plutôt que de le laisser en place Alternatives écartées : - *Conditional rendering FlatList/DraggableFlatList* : refresh dispo uniquement hors sort `position` → incohérent - *Custom Gesture.Pan au-dessus* : trop complexe pour le bénéfice ### Critères d'acceptation - [ ] Bouton refresh visible dans la toolbar de l'inbox et du détail de liste - [ ] Le bouton recharge les données et affiche un feedback visuel (spin) pendant le chargement - [ ] Aucune trace de `RefreshControl` ou state `refreshing` lié au swipe (dead code retiré) - [ ] L'icône utilise `RefreshCw` de `lucide-react-native` avec la couleur cohérente (`isDark ? '#A0A0A0' : '#6B6B6B'`)
maximus added
status:ready
type:bug
and removed
status:approved
type:feature
labels 2026-04-09 13:30:25 +00:00
maximus added
status:review
and removed
status:ready
labels 2026-04-09 13:40:06 +00:00
maximus added
status:approved
and removed
status:review
labels 2026-04-09 13:42:48 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/simpl-liste#61
No description provided.