react-native-gifted-chat
Version:
The most complete chat UI for React Native
120 lines • 6.09 kB
JavaScript
import React, { useCallback, useMemo } from 'react';
import { View } from 'react-native';
import Animated, { interpolate, useAnimatedStyle, useDerivedValue, useSharedValue } from 'react-native-reanimated';
import { Day } from '../../../Day';
import { Message } from '../../../Message';
import { isSameDay } from '../../../utils';
export * from './types';
// y-position of current scroll position relative to the bottom of the day container. (since we have inverted list it is bottom)
export const useAbsoluteScrolledPositionToBottomOfDay = (listHeight, scrolledY, containerHeight, dayBottomMargin, dayTopOffset) => {
const absoluteScrolledPositionToBottomOfDay = useDerivedValue(() => listHeight.value + scrolledY.value - containerHeight.value - dayBottomMargin - dayTopOffset, [listHeight, scrolledY, containerHeight, dayBottomMargin, dayTopOffset]);
return absoluteScrolledPositionToBottomOfDay;
};
export const useRelativeScrolledPositionToBottomOfDay = (listHeight, scrolledY, daysPositions, containerHeight, dayBottomMargin, dayTopOffset, createdAt) => {
const dayMarginTop = useMemo(() => 5, []);
const absoluteScrolledPositionToBottomOfDay = useAbsoluteScrolledPositionToBottomOfDay(listHeight, scrolledY, containerHeight, dayBottomMargin, dayTopOffset);
// find current day position by scrolled position
const currentDayPosition = useDerivedValue(() => {
'worklet';
// When createdAt is provided (called from AnimatedDayWrapper for a specific message),
// directly find the day position by createdAt without sorting the entire array.
// This avoids O(n log n) sorting and O(n) search for each message item.
if (createdAt != null) {
const values = Object.values(daysPositions.value);
for (let i = 0; i < values.length; i++)
if (values[i].createdAt === createdAt)
return values[i];
}
// Fallback: sort and search when createdAt is not provided (e.g., from DayAnimated)
const sortedArray = Object.values(daysPositions.value).sort((a, b) => {
'worklet';
return a.y - b.y;
});
for (let i = 0; i < sortedArray.length; i++) {
const day = sortedArray[i];
const dayPosition = day.y + day.height;
if (absoluteScrolledPositionToBottomOfDay.value < dayPosition || i === sortedArray.length - 1)
return day;
}
return undefined;
}, [daysPositions, absoluteScrolledPositionToBottomOfDay, createdAt]);
const relativeScrolledPositionToBottomOfDay = useDerivedValue(() => {
const scrolledBottomY = listHeight.value + scrolledY.value - ((currentDayPosition.value?.y ?? 0) +
(currentDayPosition.value?.height ?? 0) +
dayMarginTop);
return scrolledBottomY;
}, [listHeight, scrolledY, currentDayPosition, dayMarginTop]);
return relativeScrolledPositionToBottomOfDay;
};
const DayWrapper = (props) => {
const { renderDay: renderDayProp, currentMessage, previousMessage, } = props;
if (!currentMessage?.createdAt || isSameDay(currentMessage, previousMessage))
return null;
const {
/* eslint-disable @typescript-eslint/no-unused-vars */
containerStyle, onMessageLayout,
/* eslint-enable @typescript-eslint/no-unused-vars */
...rest } = props;
return (<View>
{renderDayProp
? renderDayProp({ ...rest, createdAt: currentMessage.createdAt })
: <Day {...rest} createdAt={currentMessage.createdAt}/>}
</View>);
};
const AnimatedDayWrapper = (props) => {
const { scrolledY, daysPositions, listHeight, ...rest } = props;
const dayContainerHeight = useSharedValue(0);
const dayTopOffset = useMemo(() => 10, []);
const dayBottomMargin = useMemo(() => 10, []);
const createdAt = useMemo(() => new Date(props.currentMessage.createdAt).getTime(), [props.currentMessage.createdAt]);
const relativeScrolledPositionToBottomOfDay = useRelativeScrolledPositionToBottomOfDay(listHeight, scrolledY, daysPositions, dayContainerHeight, dayBottomMargin, dayTopOffset, createdAt);
const handleLayoutDayContainer = useCallback(({ nativeEvent }) => {
dayContainerHeight.value = nativeEvent.layout.height;
}, [dayContainerHeight]);
const style = useAnimatedStyle(() => ({
opacity: interpolate(relativeScrolledPositionToBottomOfDay.value, [
-dayTopOffset,
-0.0001,
0,
dayContainerHeight.value + dayTopOffset,
], [
0,
0,
1,
1,
], 'clamp'),
}), [relativeScrolledPositionToBottomOfDay, dayContainerHeight, dayTopOffset]);
return (<Animated.View style={style} onLayout={handleLayoutDayContainer}>
<DayWrapper {...rest}/>
</Animated.View>);
};
export const Item = (props) => {
const { renderMessage: renderMessageProp, isDayAnimationEnabled, reply,
/* eslint-disable @typescript-eslint/no-unused-vars */
scrolledY: _scrolledY, daysPositions: _daysPositions, listHeight: _listHeight,
/* eslint-enable @typescript-eslint/no-unused-vars */
...rest } = props;
// Transform reply props for Message and Bubble
const messageProps = useMemo(() => ({
...rest,
// Swipe to reply for Message component
swipeToReply: reply?.swipe,
// Message reply styling for Bubble component
messageReply: reply ? {
renderMessageReply: reply.renderMessageReply,
onPress: reply.onPress,
...reply.messageStyle,
} : undefined,
}), [rest, reply]);
return (
// do not remove key. it helps to get correct position of the day container
<View key={props.currentMessage._id.toString()}>
{isDayAnimationEnabled
? <AnimatedDayWrapper {...props}/>
: <DayWrapper {...messageProps}/>}
{renderMessageProp
? renderMessageProp(messageProps)
: <Message {...messageProps}/>}
</View>);
};
//# sourceMappingURL=index.js.map