as-app/src/containers/ChatMessages/index.js

210 lines
5.6 KiB
JavaScript

import React, { useCallback, useEffect, useState, useMemo } from "react";
import { View, ScrollView } from "react-native";
import { createStyles, useTheme } from "~/theme";
import { Button } from "react-native-paper";
import { AntDesign } from "@expo/vector-icons";
import { alertActions, aggregatedMessagesActions } from "~/stores";
import { getVisibleIndexes, isScrollAtBottom } from "./utils";
import MessageRow from "./MessageRow";
import MessageWelcome from "./MessageWelcome";
const ChatMessages = React.memo(function ChatMessages({
scrollViewRef,
messages = [],
loading,
}) {
const styles = useStyles();
const { colors } = useTheme();
const [messageLayouts, setMessageLayouts] = useState([]);
const [layoutsReady, setLayoutsReady] = useState(false);
const [isAtBottom, setIsAtBottom] = useState(true);
const [lastMessageId, setLastMessageId] = useState(null);
const [lastViewedIndex, setLastViewedIndex] = useState(messages.length - 1);
useEffect(() => {
alertActions.setHasMessages(messages.length > 0);
}, [messages.length]);
// Reset layouts when messages change
useEffect(() => {
setMessageLayouts([]);
setLayoutsReady(false);
}, [messages]);
const scrollToBottom = useCallback(() => {
scrollViewRef.current?.scrollToEnd({ animated: true });
}, [scrollViewRef]);
// Handle new messages and auto-scrolling
useEffect(() => {
if (messages.length === 0) return;
const lastMessage = messages[messages.length - 1];
// Only scroll if it's a new message and we're at the bottom
if (lastMessage.id !== lastMessageId) {
setLastMessageId(lastMessage.id);
if (isAtBottom) {
scrollToBottom();
}
}
}, [messages, isAtBottom, scrollToBottom, lastMessageId]);
const messagesLength = messages.length;
const onScroll = useCallback(
({ nativeEvent }) => {
const wasAtBottom = isAtBottom;
const nowAtBottom = isScrollAtBottom(nativeEvent);
setIsAtBottom(nowAtBottom);
if (!wasAtBottom && nowAtBottom) {
setLastViewedIndex(messages.length - 1);
return;
}
if (!layoutsReady) return;
const visibleIndexes = getVisibleIndexes(nativeEvent, messageLayouts);
if (messagesLength === 0 || messageLayouts.length === 0) {
return;
}
// Mark visible messages as read in the aggregated messages store
// aggregatedMessagesActions.markMultipleMessagesAsRead(
// visibleIndexes
// .filter((index) => !messages[index].isRead)
// .map((index) => messages[index].id),
// );
const maxVisibleIndex = Math.max(...visibleIndexes);
if (maxVisibleIndex > lastViewedIndex) {
setLastViewedIndex(maxVisibleIndex);
}
},
[
messagesLength,
messageLayouts,
messages,
layoutsReady,
isAtBottom,
lastViewedIndex,
],
);
const onContentSizeChange = useCallback(() => {
if (isAtBottom && lastMessageId) {
scrollToBottom();
}
}, [isAtBottom, scrollToBottom, lastMessageId]);
const onMessageLayout = useCallback(
(index, event) => {
const { layout } = event.nativeEvent;
setMessageLayouts((prev) => {
const next = [...prev];
next[index] = layout;
if (next.filter(Boolean).length === messagesLength) {
setLayoutsReady(true);
}
return next;
});
},
[messagesLength],
);
const newMessagesCount = Math.max(0, messages.length - 1 - lastViewedIndex);
const hasNewMessages = newMessagesCount > 0 && !isAtBottom;
const newMessagesPlural = newMessagesCount > 1;
const newMessagesText = hasNewMessages
? `${newMessagesCount} nouveau${newMessagesPlural ? "x" : ""} message${
newMessagesPlural ? "s" : ""
}`
: "";
if (loading) {
return null;
}
return (
<View style={styles.wrapper}>
{hasNewMessages && (
<View style={{ zIndex: 10 }}>
<Button
style={styles.newMessageButton}
labelStyle={styles.newMessageButtonLabel}
contentStyle={styles.newMessageButtonContent}
icon={() => (
<AntDesign name="arrowdown" size={14} color={colors.onPrimary} />
)}
onPress={scrollToBottom}
>
{newMessagesText}
</Button>
</View>
)}
<ScrollView
onContentSizeChange={onContentSizeChange}
onScroll={onScroll}
scrollEventThrottle={16}
ref={scrollViewRef}
>
<View style={styles.container}>
<MessageWelcome />
{messages.map((row, index) => {
const sameUserAsPrevious =
row.userId === messages[index - 1]?.userId;
return (
<MessageRow
key={row.id}
row={row}
index={index}
sameUserAsPrevious={sameUserAsPrevious}
onLayout={(event) => onMessageLayout(index, event)}
/>
);
})}
</View>
</ScrollView>
</View>
);
});
const useStyles = createStyles(({ wp, hp, scaleText, theme: { colors } }) => ({
wrapper: {
flex: 1,
},
container: {
display: "flex",
flex: 1,
flexDirection: "column",
width: "100%",
paddingTop: 20,
paddingBottom: 50,
paddingHorizontal: 5,
},
newMessageButton: {
position: "absolute",
top: 0,
width: "100%",
backgroundColor: colors.blueLight,
borderRadius: 0,
},
newMessageButtonContent: {
// alignItems: "flex-start",
},
newMessageButtonLabel: {
top: 0,
marginVertical: 0,
color: colors.surface,
},
}));
ChatMessages.displayName = "ChatMessages";
export default ChatMessages;