// CommentsBottomSheet.jsx
import {
BottomSheetBackdrop,
BottomSheetModal,
BottomSheetScrollView,
BottomSheetTextInput,
} from '@gorhom/bottom-sheet';
import { BlurView } from 'expo-blur';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {
Image,
Platform,
Pressable,
Text,
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 { getArtistDisplayName } from '../../utils/artistName';
import { Palette } from '../../styles';
import { FONT_FAMILY } from '../../styles/Fonts';
import ProfilePicture from '../ProfilePicture';
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 currentUserDisplayName = useMemo(
() => getArtistDisplayName(currentUserData, ''),
[currentUserData]
);
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 INPUT_HEIGHT = 54;
const INPUT_MARGIN_VERTICAL = 12;
const bottomPadding = useMemo(
() => INPUT_HEIGHT + INPUT_MARGIN_VERTICAL + (insets?.bottom || 0) + 12,
[insets?.bottom]
);
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: currentUserDisplayName,
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: currentUserDisplayName,
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: bottomPadding,
}}
showsVerticalScrollIndicator>
{Array.isArray(comments) && comments.length > 0 ? (
comments.map((c) => (
{c?.userId === currentUID
? 'vous'
: c?.userName || ''}
• {formatRelativeTime(c?.createdAt)}
{c?.text}
))
) : (
Aucun commentaire pour le moment
)}
{
setText(t);
}}
onFocus={() => {
setTyping(true);
}}
onBlur={() => setTyping(false)}
style={{
flex: 1,
color: Palette.white,
fontSize: 14,
fontFamily: FONT_FAMILY.InterRegular,
}}
/>
);
};
export default CommentsBottomSheet;