Commit graph

43 commits

Author SHA1 Message Date
Yanis af767879e3 feat(admin/users): bannedReason textarea on user detail page
Adds a Textarea below the ban Switch that lets the admin write the reason
shown to the mobile user in the BannedModal. The reason is persisted on
blur via PATCH /api/users/[id] (existing route), and only rendered when
the user is currently banned to keep the UI tight.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:10:23 +02:00
Yanis 792e969c00 feat(api/mobile): auth sync/me/sign-out + scans + bearer plugin
Connect the React Native app to the admin backend so users and scans flow
into the panel and bans take effect on the device.

- Activate the bearer() plugin in lib/auth.ts so mobile clients can pass
  the better-auth session token via Authorization: Bearer header
- Add requireMobileAuth() helper in lib/auth-guard.ts that resolves the
  session, re-fetches the user from DB (banned flag is on User, not in
  the Session payload) and returns 403 with banned/bannedReason for
  banned accounts
- Extend CORS in middleware.ts to allow POST + Authorization header on
  /api/mobile/* (preflight was failing before)
- New routes:
  POST /api/mobile/auth/sync       — passwordless mobile auth via
    deterministic password derived from sha256(email + deviceId + pepper).
    Tries signIn first, falls back to signUp on USER_NOT_FOUND. Returns
    409 when the email exists with a different deviceId.
  GET  /api/mobile/auth/me         — current user enriched with
    banned/bannedReason/role/xp/level
  POST /api/mobile/auth/sign-out   — best-effort session revocation
  POST /api/mobile/scans           — create a scan, resolves diseaseSlug
    to diseaseId, never accepts an imageUrl from the device (V1 keeps
    photos local-only)
  GET  /api/mobile/scans           — own scans, 50 most recent

Validated end-to-end via curl: signUp → me → repeat sync (idempotent) →
post scan → ban via DB → me reflects banned: true → POST scans returns
403 + banned/bannedReason.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:02:45 +02:00
Yanis 4d9c1a0a28 fix(nav): tab bar hitSlop + pointerEvents on inner badge
Touch targets on the bottom tabs were too small near the top edge of the
bar, making it easy to miss the tap when reaching for an icon. Add
hitSlop on the Pressable and pointerEvents="none" on the active-state
badge so taps always land on the parent button.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:31:27 +02:00
Yanis 086de7c05c feat(scanner,ml): real TFLite inference + preload + flip camera + analyzing skeleton
ML
- Reinstall react-native-fast-tflite + react-native-nitro-modules and
  register the fast-tflite Expo plugin in app.json
- Wire model.ts to the real native module: dynamic require + lazy
  loadTensorflowModel (cached), softmax/argmax on output, build Detection
  with the project 0-100 confidence convention. Falls back to mockDetection
  on any load/inference failure so the app never breaks.
- Align preprocessing input size to 256x256 to match the Python
  MobileNetV2 export.

Scanner UX
- Preload the TFLite model on Scanner mount to avoid the ~1-2s decode hit
  on first capture
- Add a flip-front/back camera control with a toast warning that the rear
  camera gives better results
- Show a full-screen analyzing skeleton overlay while inference runs
- Memoize ConfidenceMeter color into a single computed value

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:31:17 +02:00
Yanis f247748adc chore(android): propagate CMake fix to native subprojects
The withCmakeFix plugin now also modifies the root android/build.gradle
via withProjectBuildGradle, iterating over subprojects with
plugins.withId('com.android.library') / plugins.withId('com.android.application').
Using plugins.withId (vs subprojects { afterEvaluate {} }) avoids the
"Cannot run Project.afterEvaluate when the project is already evaluated"
error caused by gradle-plugins (kotlin, expo-gradle-plugin, ...) being
evaluated before the closure runs.

This unblocks the native CMake build of react-native-fast-tflite,
react-native-nitro-modules, react-native-screens, expo-modules-core, etc.
on Windows where the path-too-long issue affected subproject .o files.

Build verified: BUILD SUCCESSFUL in 15m 17s, 842 tasks, 0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:30:55 +02:00
Yanis a25295e186 feat(settings): account section + reset modal + language picker (Tailwind)
SettingsScreen :
- Section "Compte" : ligne user (avatar + nom + email si non-guest +
  badge "Invité" orange si isGuest) + ligne "Recommencer avec un nouveau
  compte" (icône RefreshCw rouge)
- Reset account : remplace Alert.alert natif par ConfirmDialog stylé
  (variant destructive). Au confirm, resetAccount() puis
  navigation.reset({ index: 0, routes: [{ name: 'Onboarding' }] }) après
  un setTimeout(50) pour laisser RootNavigator re-render avec le screen
  Onboarding monté
- Language picker : remplace le toggle inline (clic = swap FR/EN) par
  l'ouverture d'un LanguagePickerModal stylé

LanguagePickerModal (composant ui réutilisable) :
- Tailwind only : Modal RN + backdrop noir 50% + card rounded-3xl + shadow
- Header icône Globe verte + titre + subtitle
- 2 options Francais/English avec drapeau emoji 28px + label 16px
  font-semibold ; option active : bg vert pâle + border verte + cercle
  vert avec checkmark
- Bouton Annuler ghost grisé en bas

Messages i18n explicites :
- 'Vous serez redirigé vers l'écran de connexion pour créer un nouveau
  compte ou continuer en invité'
- CTA destructive : 'Oui, me déconnecter'

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 09:33:46 +02:00
Yanis 05e28b3ebc feat(auth): onboarding flow + local auth (no backend)
Full local onboarding + auth stack (AsyncStorage only) :

types/auth.ts : User + AuthState
services/auth/
  - authStorage.ts : AsyncStorage wrapper avec clés vineye:auth:{user,onboarding-done,terms-accepted-at}
  - randomUser.ts : generateGuestUser() avec préfixes localisés FR/EN
    (Sommelier/Vendangeur/Caviste/... + suffix Anonyme/Anonymous + #XXXX)
  - authValidation.ts : Zod schema factory pour name/email
contexts/AuthContext.tsx : Provider + hook useAuth() avec login(),
  loginAsGuest(), logout(), resetAccount(), acceptTerms(),
  completeOnboarding(), isLoading hydraté au mount

components/onboarding/ :
  - OnboardingButton : variants primary/secondary, loading, disabled
  - TermsCheckbox : checkbox custom Reanimated avec scale + opacity anim
  - EmailNameForm : form contrôlé avec validation Zod live + erreurs
    affichées sous chaque champ après touch

screens/onboarding/ :
  - WelcomeScreen : logo + 3 feature cards (Camera/Leaf/Brain) FadeInDown
    stagger + CTA "Commencer"
  - TermsScreen : 5 sections CGU (usage, dataCollected avec mention
    explicite de la géoloc, responsibility, intellectualProperty, contact)
    + footer fixe avec checkbox + bouton continuer disabled tant que pas
    coché
  - AuthChoiceScreen : EmailNameForm + séparateur "ou" + bouton secondary
    "Continuer en invité"

navigation/ :
  - OnboardingNavigator : Stack Welcome -> Terms -> AuthChoice (animation
    fade)
  - RootNavigator : switch dynamique via useAuth().isOnboardingComplete ;
    isLoading -> ActivityIndicator centré ; sinon render conditionnel
    Onboarding ou Main

App.tsx : AuthProvider wrap autour de NetworkProvider

deps : zod ^4.4.1, expo-crypto ~15.0.9 (pour Crypto.randomUUID())

i18n FR + EN : blocs onboarding.{welcome,terms,authChoice} + auth.errors
+ settings.{account,language} (utilisés par le commit suivant)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 09:33:27 +02:00
Yanis bdbdcd7b85 feat(scan-detail): wire 'Ajouter ma position' button to GPS persist
useScanDetail :
- Nouvelle méthode setLocation(coords) qui persiste latitude / longitude
  (top-level pour MapScreen.hasLocation), locationCapturedAt et
  l'objet location.{latitude, longitude} dans AsyncStorage + state local

ScanDetailScreen :
- handleAddLocation appelle requestAndGetLocation() (useScanLocation hook
  existant) puis setLocation()
- État addingLocation : bouton désactivé + opacity 0.6 + ActivityIndicator
  spinner à la place de l'icône MapPin + label 'Localisation en cours...'
- Toast success / error + hapticSuccess en cas de succès
- La carte location bascule automatiquement vers l'affichage des coords
  une fois persisté → la plante apparaît aussi sur MapScreen et
  SearchScreen mode Map

i18n :
- myPlants.toasts.locationAdded
- myPlants.detail.locating

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 01:06:10 +02:00
Yanis 05ea1df6ff feat(my-plants): skeleton + animations + ConfirmDialog (fix double modal)
ConfirmDialog (nouveau composant ui réutilisable) :
- Modal RN avec backdrop noir 50%, card rounded-3xl + shadow
- Variant 'destructive' (icône AlertTriangle rouge) ou 'default' (Check vert)
- Boutons côte-à-côte avec icônes X/Trash2/Check, minHeight 52px,
  ghost grisé bordé + primary/destructive avec shadow
- Tap backdrop = cancel, tap dialog = no-op via stopPropagation

ScanListItem :
- Remplace Alert.alert natif par ConfirmDialog stylé pour la suppression
- Le swipe gauche → bouton Supprimer → un seul modal ConfirmDialog → confirm

MyPlantsScreen :
- Suppression du DOUBLE modal (Alert dans handleDeleteScan était redondant
  avec celui de ScanListItem → 2 modals successifs avant suppression)
  → handleDeleteScan appelle directement deleteScan(id)
- FadeInDown.springify().damping(18) sur header / SearchBar / chaque
  date group avec stagger index*60
- Skeleton loading state : 2 groupes simulés (header bar + 3
  ScanListItemSkeleton dans une card-loading sans elevation)

DateGroupAccordion :
- Retrait de l'elevation Android sur styles.card → fix le flash "rectangle
  blanc + ombre" pendant l'animation FadeInDown du parent. iOS shadow
  conservée (composite layer respecte l'opacité)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 01:05:54 +02:00
Yanis 07c42ed40e feat(home): skeleton loading states + cascade entrance animations
Animations FadeInDown.springify().damping(16) avec stagger 60ms entre
sections du HomeScreen :
- SearchHeader (delay 0)
- SearchSection (delay 60)
- RecentScans (delay 120)
- FrequentDiseasesHorizontal (delay 200)
- PracticalGuides (delay 280)

Skeleton loading states :
- LargeDiseaseCardCompactSkeleton : nouveau, simule la structure compact
  (badge + icon + title + desc + footer) — utilisé dans
  FrequentDiseasesHorizontal en remplacement de CarouselCardSkeleton
- ScanListItemSkeleton : nouveau, simule image 64x64 + name + status pill
  + time + confidence tile — utilisé dans RecentScans
- RecentScans / PracticalGuides : nouveau style cardLoading sans
  shadow/elevation Android (qui ne respecte pas l'opacité de FadeInDown
  → flash "rectangle blanc + ombre" pendant l'anim). iOS shadow conservé.

isLoading propagé depuis useDiseases / useGuides / useHistory vers les
sections concernées.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 01:05:37 +02:00
Yanis 25b56092d5 feat(search): centralized search modal with categories + map plant distance
Une seule SearchScreen modale gère la recherche depuis Home, MyPlants,
Guides et Map. La SearchBar partagée passe en mode trigger sur ces pages,
ouvrant la modal au tap.

Animation par contexte (native-stack) :
- Home/MyPlants/Guides → 'fade_from_bottom' (fade + léger glissement)
- Map → 'fade' pur (la barre est déjà en haut, pas de mouvement vertical)
  via param de route { fromMap: true }

SearchScreen — mode global :
- 3 catégories : Maladies (red), Guides pratiques (blue), Mes plantes (green)
- Filter chips scrollable horizontal : Tout / Maladies / Guides / Plantes
  avec count par catégorie
- Sections en accordion (chevron) avec count par section
- Tag coloré sur chaque résultat indiquant la catégorie
- Plantes scannées incluses (recherche par customName / cépage)
- Tap résultat : DiseaseDetail / GuideDetail / Map+focusScanId / ScanDetail

SearchScreen — mode Map (fromMap=true) :
- Liste plate des plantes localisées uniquement
- Distance haversine depuis position courante (formatDistance helper)
- Tri par distance croissante
- Tap → navigate Main/Map avec focusScanId

MapScreen :
- useEffect sur route.params?.focusScanId : animation 2 étapes (zoom large
  450ms → zoom serré 500ms) + ouverture du preview
- Caméra décalée vers le sud (lat - delta * 0.18) pour que le marker soit
  visible AU-DESSUS du bottom sheet, pas masqué dessous
- Reset du param après usage via setParams

Recents :
- Hook useRecentSearches (AsyncStorage @vineye:recent_searches)
- Max 10, déduplication insensible à la casse
- Affichés quand pas de query, avec clear all + remove individuel

SearchBar partagée :
- Refactorisée 100% Tailwind (sauf 2-3 props RN-spécifiques sur TextInput)
- Nouvelle prop onTriggerPress : devient un Pressable avec Text placeholder
  qui navigate vers Search au lieu d'être un input local

Wirings :
- SearchSection (Home + Guides) : trigger
- MyPlantsScreen : trigger (suppression de l'inline filtering, plus utilisé)
- FloatingSearch (Map) : trigger avec fromMap: true
- RootNavigator : Stack.Screen Search avec options dynamique selon param

i18n FR + EN :
- search.{placeholder, placeholderMap, recentTitle, clearAll, noRecent,
  resultsTitle, noResults, nearbyPlantsTitle, noPlants}
- search.filter.{all, diseases, guides, plants}
- search.section.{diseases, guides, plants}
- search.tag.{disease, guide, plant}

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 00:37:12 +02:00
Yanis 07f25769e3 refactor(bottom-sheets): polish edit sheets (buttons, keyboard, layout)
EditNameBottomSheet :
- Render conditionnel (mounted seulement quand editingName=true) pour ne plus
  ouvrir le clavier auto à l'arrivée sur ScanDetail
- BottomSheetScrollView avec contentContainerStyle inline (Tailwind pour le
  reste) + insets.bottom pour padding bas
- isDirty disable du Save quand le nom n'a pas changé
- Boutons Annuler/Enregistrer en row via inner View avec icônes X/Check,
  font-bold, minHeight 56px

EditProfileModal :
- BottomSheetScrollView : actions déplacées DANS le scroll (juste après le
  dernier input email) → toujours visibles sous le formulaire, jamais
  poussées en bas du sheet
- snap 95% + topInset safe-area
- Boutons même style (icônes + ghost grisé bordé + primary shadow)

MapBottomSheet rename inline :
- useImperativeHandle pour forwarder le ref correctement et avoir un
  internalRef accessible côté composant
- snapToIndex(2) à l'ouverture du form (85%) puis snapToIndex(0) après
  save/cancel pour redonner la map
- BottomSheetScrollView pour le rename form avec keyboardShouldPersistTaps
- keyboardBehavior 'interactive' + android_keyboardInputMode 'adjustResize'
- containerStyle { zIndex:100, elevation:100 } pour passer au-dessus des
  FloatingActions

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 00:36:44 +02:00
Yanis 28d4fc176e chore(i18n,ui): keys for home/map/scan-detail + minor polish
i18n FR + EN :
- home.recentScans : 'Mes plantes récentes' / 'My recent plants'
- map.preview.{title,tapHint}
- myPlants.detail.rename{Title,Subtitle,Placeholder,Save}
- myPlants.toasts.renamed

App.tsx : retire NavigationBar.setBackgroundColorAsync + setPositionAsync
(navigation bar déjà bien gérée par expo-navigation-bar par défaut).

BottomTabNavigator + SettingsScreen : petits ajustements layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 00:03:55 +02:00
Yanis 0ce7b3a718 refactor(profile): edit modal as keyboard-aware bottom sheet
Remplace le Modal+KeyboardAvoidingView+ScrollView par un BottomSheet
(@gorhom/bottom-sheet) avec snap 95% + topInset safe-area +
keyboardBehavior 'interactive' + android_keyboardInputMode 'adjustResize'.

L'API publique du composant (visible/onClose/onSave) est inchangée.

Boutons Cancel/Save dans le BottomSheetScrollView (juste après le dernier
input email) avec icônes X/Check, layout row via inner View, ghost grisé
bordé + primary shadow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 00:03:43 +02:00
Yanis 0d97be422e feat(scan-detail): edit plant name via bottom sheet
- Nouveau composant EditNameBottomSheet (gorhom BottomSheet +
  BottomSheetTextInput + BottomSheetScrollView) avec snap 92%, topInset
  safe-area, keyboardBehavior interactive, autoFocus
- Mounted conditionnellement (state editingName) pour éviter que l'autoFocus
  ouvre le clavier dès l'arrivée sur ScanDetail
- Boutons Annuler / Enregistrer avec icônes X / Check, ghost grisé bordé +
  primary shadow, alignés en row via inner View, isDirty disable du Save si
  le nom n'a pas changé
- ScanDetailScreen : bouton Pencil flottant à côté du favori, heroTitle
  utilise customName en priorité
- useScanDetail : nouvelle méthode renameScan(newName) avec persist storage

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 00:03:32 +02:00
Yanis 98c446cd35 feat(map): preview mode + inline rename + floating actions tweaks
Comportement 2-clicks sur la liste des scans :
- 1er clic : map zoom sur le scan + sheet snap au plus bas + tile preview
  unique avec hint 'Appuyez à nouveau pour voir les détails'
- 2e clic même scan : navigate vers ScanDetail
- Clic autre scan en preview : switch preview + zoom

Rename inline dans le BottomSheet existant (plus de 2e sheet superposé) :
- Bouton retour (ChevronLeft) au lieu de croix
- Form (BottomSheetTextInput) avec keyboardBehavior='interactive' +
  android_keyboardInputMode='adjustResize' pour ne pas masquer l'input
- Boutons Annuler/Enregistrer avec icônes (X / Check), border 1.5px primary,
  shadow primary[900] sur le Save, minHeight 56px
- snapToIndex(2) à l'ouverture du form (85%) puis snapToIndex(0) après
  save/cancel pour redonner la map à voir
- containerStyle { zIndex:100, elevation:100 } pour passer au-dessus des
  FloatingActions quand le sheet s'expand

FloatingActions : couleur d'icône via prop color (className n'est pas
supporté pour la couleur sur lucide-react-native sans cssInterop).

actionsSlot : zIndex/elevation 1 pour passer derrière le sheet expanded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 00:03:18 +02:00
Yanis 7457e64996 feat(home): recent scans + horizontal disease cards
Refonte HomeScreen :
- 'Commencer votre collection' (HomeCta) → conditionnellement remplacé par
  les 3 derniers scans en mode \"grouped card\" via le nouveau composant
  RecentScans (fallback HomeCta si historique vide)
- 'Maladies fréquentes' → SmallDiseaseCard remplacé par LargeDiseaseCard
  en mode compact, en scroll horizontal (FrequentDiseasesHorizontal)
- Comment temporairement <SeasonAlert /> en attendant la page Notifications

LargeDiseaseCard : nouvelle prop compact qui réduit hauteur (260→220),
padding, font-sizes et lignes de description (3→2). Border + radius +
shadow Android forcés en style inline pour clip elevation correctement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 00:03:00 +02:00
Yanis 4ebbc692ff feat(my-plants): grouped card style for date groups
Aligne le rendu des date groups sur le pattern PracticalGuides : items
encapsulés dans une card blanche rounded-16, séparés par une ligne grise
indentée.

- ScanListItem : nouvelles props grouped + showSeparator → désactive
  borderRadius/margins/border individuels et active la ligne séparatrice
  alignée sous le texte
- DateGroupAccordion : wrappe les ScanListItem dans une View card avec
  shadow iOS / elevation Android

Le même pattern est réutilisé par RecentScans (Home) — voir commit suivant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 00:02:40 +02:00
Yanis 3781b1c0f4 feat(ui): shared SearchBar reused on Home, Map and MyPlants
Extrait la barre de recherche en composant partagé
(components/shared/SearchBar.tsx) avec props placeholder/value/onChangeText/
showFilter.

- Home (SearchSection) : utilise le composant partagé
- Map (FloatingSearch) : remplace l'input custom + ajuste les chips (border
  primary, font-size 15→12, MapPin couleur primary)
- MyPlants : remplace l'input custom + son bouton clear

Bonus : SearchBar gère proprement le clavier Android via numberOfLines={1},
multiline={false}, scrollEnabled={false}, lineHeight 20 + textAlignVertical
center + includeFontPadding false → le placeholder ne wrappe plus sur 2 lignes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 00:02:28 +02:00
Yanis c2b04757a4 chore(notifications): defer Notifications page references
Commente (sans supprimer) les liens et imports de NotificationsScreen :
- HeaderActionButtons : bouton cloche commenté → seul Settings reste
- RootNavigator : import + Stack.Screen commentés
- linking : deep-link 'notifications' commenté
- types/navigation : route param 'Notifications' commenté

À réactiver via la recherche de \"// TODO: réactiver quand la page Notifications\".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 00:02:14 +02:00
Yanis 036bc83618 fix(scan): confidence percentage display × 100 bug
ConfidenceTile multipliait par 100 une valeur déjà 0-100 (résultat 7000%).
Aligne le composant sur la convention 0-100 utilisée partout dans le projet
(useGameProgress, ScanCard, ScanDetail, ResultScreen, model.ts).

Corrige aussi mockSeed (0.94 → 94, etc.) pour matcher la convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 00:02:05 +02:00
Yanis 6232068208 chore(ml,android): retire react-native-fast-tflite + nitro, mock JS only
Contexte
- Build Android C++ instable sur Windows (CMake/Ninja path too long, Nitro
  headers manquants au clean). Modèle .tflite final pas encore prêt.
- Désinstall temporaire des deux libs natives, le mock JS dans model.ts
  continue de servir les détections simulées pondérées.

Changements
- package.json : retire react-native-fast-tflite (3.0.1)
- pnpm-lock.yaml : régénéré, -72 packages dont nitro-modules
- src/services/tflite/model.ts : refactor pur mock, interface publique
  inchangée (loadModel + runInference), procédure de réintégration
  documentée en tête du fichier
- plugins/withCmakeFix.js : plugin Expo config qui injecte les flags
  CMake (response files + ninja 1.12.1 + OBJECT_PATH_MAX) à chaque
  prebuild — dormant tant que fast-tflite n'est pas réintégré
- app.json : référence le plugin
- CLAUDE.md + .claude/notes/android-build : doc de l'état actuel et
  des étapes de réintégration (idéalement via EAS Build)

Reste
- src/assets/models/grapevine_v1.tflite conservé pour la réintégration
- metro.config.js continue de déclarer .tflite dans assetExts
- TypeScript check: 1 erreur préexistante (homeheader.tsx, palette[50]),
  non liée à ce changement

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 21:14:38 +02:00
Yanis a8b84472e6 feat(mobile): UI overhaul + ML pipeline + Android build fixes
UI/screens
- Refonte ProfileScreen, SettingsScreen, ResultScreen, ScanDetailScreen,
  GuidesScreen, MapScreen, MyPlantsScreen
- Nouveaux composants : LargeDiseaseCard, ConfidenceTile, StatusTag,
  EditProfileModal, HeaderActionButtons
- useUserProfile hook + types/user.ts pour le profil utilisateur
- i18n FR/EN enrichi pour les nouveaux écrans

ML
- src/services/ml/classes.ts (mapping ML → slugs Prisma)
- src/services/ml/preprocessing.ts (resize 224x224 + decode JPEG + norm /255)
- model.ts adapté + fallback mock quand le module natif est absent

Build Android (notes)
- .claude/notes/android-build/README.md : fixes CMake/Ninja "path too long"
  (response files + ninja 1.12.1 + CMAKE_OBJECT_PATH_MAX=1024)
- Note du blocage Nitro Modules headers + pistes (EAS Build, inférence
  serveur, fallback Expo Go mock)
- ⚠️ Le bloc externalNativeBuild dans android/app/build.gradle n'est pas
  versionné (android/ gitignored par expo prebuild) — à porter dans un
  plugin Expo config si on garde fast-tflite local

Admin
- vineye-admin/prisma/seed.ts : seed mock pour tester la Map sans scanner

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 21:00:03 +02:00
Yanis 06be3483d7 chore(dev): mock seed for testing the Map without scanning
Adds a "Add mock plants" entry under a new Developer section in the
Settings screen, gated by __DEV__ so it never ships in release. It
calls useHistory.seedTestData() which prepends 5 fake ScanRecords
spread across Bordeaux / Bourgogne / Champagne so all the map
features (region chips, markers, rename) can be exercised without
having to actually walk into a vineyard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 12:32:25 +02:00
Yanis d30f4f250c feat(map): rebuild Map screen on WebView+Leaflet with scan markers
Replace the Google-Maps-backed react-native-maps screen with a self-
contained WebView running Leaflet + Carto/OSM tiles. No API key, no
native compilation surface. The map is now driven by the real scan
history (useHistory) instead of mock parcels.

What's on the screen now:
- Markers for every ScanRecord that carries lat/lng, colored by
  status (healthy / infected / uncertain) derived from diseaseClass.
- Tapping a marker animates the camera and opens ScanDetail.
- Bottom sheet lists the same located scans with rename support: a
  pencil opens a modal-input that calls renameScan() to set
  ScanRecord.customName (empty value clears it). When the history is
  empty the sheet auto-snaps higher and shows a CTA to the Scanner.
- Region chips (Bordeaux/Bourgogne/Champagne) animate the camera and
  draw the actual department polygon as a dashed green outline. The
  GeoJSON is fetched on the React Native side (avoids the opaque
  origin CORS issue inside `source={{ html }}`) and cached in a
  useRef Map.
- "Ma position" filter + Locate FAB drop a circular green pin with a
  smiley SVG and a pulse halo at the user's GPS coords.
- FloatingActions and FloatingSearch tags restyled to match the
  Apple-inspired Bento spec (rounded-full FABs, 56x56, soft shadows,
  primary[900] active state).

VineyardMarker (orphan since markers are SVG inside the WebView) and
the data/mockScans.ts file were removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 12:32:14 +02:00
Yanis 3be2d3b531 feat(scan): persist GPS coordinates with each scan
Capture the user's location in parallel with TFLite inference so saving
the scan doesn't slow down the camera flow. Permission is requested
just-in-time on the first capture (not at app boot) and refusals are
surfaced once via toast — repeat refusals stay silent (flag persisted
in AsyncStorage under @vineye:location-permission-asked).

- ScanRecord gains optional latitude / longitude / locationCapturedAt
  (plus customName + getScanStatus helper used by the Map screen).
  All fields optional so older scans keep working unchanged.
- New useScanLocation hook: requestForegroundPermissionsAsync +
  getCurrentPositionAsync(Balanced) with a 5s timeout. On any failure
  returns null so the scan still saves without coordinates.
- ScannerScreen runs analyze() and requestAndGetLocation() through
  Promise.all so GPS acquisition does not block inference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 12:31:54 +02:00
Yanis 08c4eba940 chore(deps,infra): pnpm hoisted mode + dependency cleanup for Map rebuild
- Add VinEye/.npmrc with node-linker=hoisted to avoid Windows MAX_PATH
  crashes with .pnpm/<hash>/ deep paths during native compilation
- Replace react-native-maps (Google Maps SDK requires API key) with
  react-native-webview (renders Leaflet + OSM tiles, no key needed)
- Add expo-location for GPS capture during scans
- Add expo-dev-client for proper HMR + deep-link launch in dev builds
- Configure expo-location plugin in app.json with FR permission strings

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 12:31:39 +02:00
Yanis bf1a1bba13 merge: integrate ML model 0.99 acc + docs into mobile/admin branch 2026-04-29 21:45:01 +02:00
jhodi.avizara 283a745568 modif readme 2026-04-14 18:42:29 +02:00
jhodi.avizara 4faba481dc maj 0.99 acc 2026-04-14 18:38:32 +02:00
jhodi.avizara a8cd5ad450 maj 0.99 acc 2026-04-14 18:24:32 +02:00
Yanis 720dd34fdd add MyPlantsScreen + ScanDetailScreen + enriched admin + API mobile + project summary
Mobile:
- Replace LibraryScreen with MyPlantsScreen (date-grouped scan list, swipe actions, search, pull-to-refresh)
- Add ScanDetailScreen (immersive hero, confidence bar, cepage card, share/delete)
- Add DiseaseDetailScreen + GuideDetailScreen (hero pattern, animated entry)
- Add useScanDetail, useHistory (useCallback fix), dateGrouping utility
- Connect diseases/guides to admin API with cache + offline fallback
- Add NetworkContext, ToastContext, Skeleton loading components
- Extend ScanRecord type (isFavorite, location)
- Full i18n FR/EN for all new screens

Admin (vineye-admin):
- Enrich Disease/Guide Prisma schema (timeline, conditions, actions, sections)
- Enriched disease-form (7 sections) + guide-form (structured sections editor)
- Add mobile public API endpoints (diseases, guides by slug)
- Add Prisma migration + enriched seed data
- UI polish: sidebar, login, layout updates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 03:19:39 +02:00
Yanis fe70005a86 add vineye-admin dashboard (Next.js)
Admin panel for VinEye with dashboard, users, diseases, guides, alerts management.
Stack: Next.js App Router + Prisma + PostgreSQL + better-auth.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:22:01 +02:00
Yanis 269cc55c77 update home UI tweaks + add tab labels + vineye-admin setup + gitignore
- SearchHeader: settings icon, smaller brand title, rounded buttons
- BottomTabNavigator: re-enable tab labels, adjust FAB offset
- homeheader: remove bottom margin
- Add .claude config, root package.json, updated .gitignore

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:20:51 +02:00
Yanis af299e816a add new screens + home components + replace colors with theme tokens
New screens: Guides, Library, Map, Notifications, Settings.
Home refactored with modular components (SearchHeader, HomeCta, FrequentDiseases, SeasonAlert, PracticalGuides).
Replaced hardcoded colors with theme tokens in SeasonAlert and HomeCta.
Updated navigation, i18n, and CLAUDE.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:05:19 +02:00
Yanis 001658898e refactor navigation: classic bottom tab bar with FAB + header icons
- Replace floating pill tab bar with classic anchored bottom bar (Home | FAB Scan | Map)
- Add central FAB button (green, elevated) for Scanner
- Move History → Notifications and Profile → Settings (accessible via header icons)
- Add SearchHeader with bell (notifications) and settings icons
- Add MapScreen placeholder
- Extract SearchHeader component from HomeScreen
- Switch to lucide-react-native icons for bottom tab bar
- Fix react-dom version mismatch (19.2.4 → 19.1.0)
- Clean up unused imports in homeheader.tsx
- Update navigation types, deep links, and i18n keys

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 20:14:27 +02:00
Yanis a964cc3836 add VinEye frontend app + fix hardcoded paths + gitignore
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 10:30:05 +02:00
jhodi.avizara bb9df73b5f update 2026-03-23 16:49:29 +01:00
jhodi.avizara 26b13a424e update 2026-03-23 16:45:09 +01:00
jhodi.avizara 4e276c16ca update 2026-03-23 16:39:14 +01:00
jhodi.avizara a452705c36 maj 2026-03-23 00:57:43 +01:00
jhodi.avizara 5dd5b93f9f maj 2026-03-23 00:53:40 +01:00
jhodi.avizara c7a7f46865 first commit 2026-03-23 00:36:57 +01:00