// CommentsBottomSheet.jsx import { BottomSheetBackdrop, BottomSheetModal, BottomSheetScrollView, } from '@gorhom/bottom-sheet'; import { BlurView } from 'expo-blur'; import { Image as ExpoImage } from 'expo-image'; import React, { useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import { Image, Platform, Pressable, Text, TextInput, View, KeyboardAvoidingView, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { icons } from '../../assets'; import { increment, projectsRef, serverTimestamp } from '../../config/firebase'; import useDataFromRef from '../../hooks/useDataFromRef'; import { useUser } from '../../providers/UserDataProvider'; import { Palette } from '../../styles'; import { FONT_FAMILY } from '../../styles/Fonts'; import { size } from '../../styles/Style'; let externalOpen; let externalClose; export const openComments = (projectId) => { if (typeof externalOpen === 'function') externalOpen(projectId); }; export const closeComments = () => { if (typeof externalClose === 'function') externalClose(); }; const formatRelativeTime = (date) => { try { const d = date instanceof Date ? date : date?.toDate?.() || null; if (!d) return ''; const diff = Math.max(0, Date.now() - d.getTime()); const sec = Math.floor(diff / 1000); if (sec < 60) return `${sec}s`; const min = Math.floor(sec / 60); if (min < 60) return `${min}min`; const h = Math.floor(min / 60); if (h < 24) return `${h}h`; const dA = Math.floor(h / 24); return `${dA}j`; } catch (e) { return ''; } }; const CommentsBottomSheet = () => { const modalRef = useRef(null); const insets = useSafeAreaInsets(); const { currentUID, currentUserData } = useUser(); const scrollRef = useRef(null); const [projectId, setProjectId] = useState(null); const [text, setText] = useState(''); const [typing, setTyping] = useState(false); useEffect(() => { externalOpen = (pid) => { setProjectId(pid || null); requestAnimationFrame(() => modalRef.current?.present()); }; externalClose = () => { try { modalRef.current?.dismiss?.(); } catch {} }; return () => { externalOpen = undefined; externalClose = undefined; }; }, []); const snapPoints = useMemo(() => ['90%'], []); const commentsRef = useMemo(() => { try { return projectId ? projectsRef .doc(projectId) .collection('comments') .orderBy('createdAt', 'desc') : null; } catch { return null; } }, [projectId]); const { data: comments = [], setData: setComments, loadMore, loading: loadingComments, } = useDataFromRef({ ref: commentsRef, simpleRef: false, listener: false, condition: !!commentsRef, refreshArray: [projectId], usePagination: true, batchSize: 20, }); const onSend = async () => { const value = (text || '').trim(); if (!value || !projectId || !currentUID) return; try { setText(''); const docRef = await projectsRef .doc(projectId) .collection('comments') .add({ userId: currentUID, userName: currentUserData?.userName || '', profilePicture: currentUserData?.profilePictureURL || '', text: value, createdAt: serverTimestamp(), }); try { await projectsRef .doc(projectId) .set({ commentsCount: increment(1) }, { merge: true }); } catch {} // Optimistic: ajouter immédiatement le commentaire en tête de liste const optimistic = { id: docRef?.id || Math.random().toString(36).slice(2), userId: currentUID, userName: currentUserData?.userName || '', profilePicture: currentUserData?.profilePictureURL || '', text: value, createdAt: new Date(), }; setComments((prev = []) => [ optimistic, ...prev.filter((x) => x?.id !== optimistic.id), ]); } catch { // ignore } }; const renderBackdrop = useCallback( (props) => ( ), [] ); const handleScroll = useCallback( ({ nativeEvent }) => { try { const { layoutMeasurement, contentOffset, contentSize } = nativeEvent || {}; const paddingToBottom = 80; if ( layoutMeasurement?.height + contentOffset?.y >= (contentSize?.height || 0) - paddingToBottom ) { loadMore?.(); } } catch {} }, [loadMore] ); return ( { setProjectId(null); setTyping(false); setText(''); }}> modalRef.current?.dismiss?.()} style={{ position: 'absolute', right: 10, top: 8, padding: 6 }} hitSlop={8}> Commentaires { if (typing) { try { scrollRef.current?.scrollToEnd?.({ animated: true }); } catch {} } }} onScroll={handleScroll} scrollEventThrottle={100} contentContainerStyle={{ paddingHorizontal: 14, paddingBottom: (insets?.bottom || 0) + 40, }} showsVerticalScrollIndicator> {Array.isArray(comments) && comments.length > 0 ? ( comments.map((c) => ( {c?.profilePicture ? ( ) : ( )} {c?.userId === currentUID ? 'vous' : c?.userName || ''} • {formatRelativeTime(c?.createdAt)} {c?.text} )) ) : ( Aucun commentaire pour le moment )} {/* INPUT en bas du contenu */} { setText(t); }} onFocus={() => { setTyping(true); setTimeout(() => { try { scrollRef.current?.scrollToEnd?.({ animated: true }); } catch {} }, 50); }} onBlur={() => setTyping(false)} style={{ flex: 1, color: Palette.white, fontSize: 14, fontFamily: FONT_FAMILY.InterRegular, }} /> ); }; export default CommentsBottomSheet;