react-native-gifted-chat
Version:
The most complete chat UI for React Native
180 lines • 7.29 kB
JavaScript
import React, { useCallback, useMemo, useRef } from 'react';
import { View, StyleSheet } from 'react-native';
import ReanimatedSwipeable from 'react-native-gesture-handler/ReanimatedSwipeable';
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
import { Avatar } from '../Avatar';
import { Bubble } from '../Bubble';
import { Color } from '../Color';
import { getStyleWithPosition } from '../styles';
import { SystemMessage } from '../SystemMessage';
import { isSameUser, renderComponentOrElement } from '../utils';
import styles from './styles';
export * from './types';
const ReplyIcon = ({ progress, direction, position, style }) => {
const animatedStyle = useAnimatedStyle(() => {
'worklet';
const scale = Math.min(progress.value, 1);
// When swiping left (icon on right), icon should move left (negative)
// When swiping right (icon on left), icon should move right (positive)
const translateX = direction === 'left'
? Math.min(progress.value * -12, -12)
: Math.max(progress.value * 12, 12);
return {
transform: [{ scale }, { translateX }],
marginLeft: position === 'left' ? 0 : 16,
marginRight: position === 'right' ? 0 : 16,
};
});
return (<Animated.View style={[localStyles.swipeActionContainer, animatedStyle, style]}>
<View style={localStyles.replyIconContainer}>
<View style={localStyles.replyIcon}>
<View style={localStyles.replyIconArrow}/>
<View style={localStyles.replyIconLine}/>
</View>
</View>
</Animated.View>);
};
export const Message = (props) => {
const { currentMessage, renderBubble: renderBubbleProp, renderSystemMessage: renderSystemMessageProp, onMessageLayout, nextMessage, position, containerStyle, user, isUserAvatarVisible, swipeToReply, } = props;
// Extract swipe props
const isSwipeToReplyEnabled = swipeToReply?.isEnabled ?? false;
const swipeToReplyDirection = swipeToReply?.direction ?? 'left';
const onSwipeToReply = swipeToReply?.onSwipe;
const renderSwipeToReplyActionProp = swipeToReply?.renderAction;
const swipeToReplyActionContainerStyle = swipeToReply?.actionContainerStyle;
const swipeableRef = useRef(null);
const renderBubble = useCallback(() => {
const {
/* eslint-disable @typescript-eslint/no-unused-vars */
containerStyle, onMessageLayout, swipeToReply,
/* eslint-enable @typescript-eslint/no-unused-vars */
...rest } = props;
if (renderBubbleProp)
return renderComponentOrElement(renderBubbleProp, rest);
return <Bubble {...rest}/>;
}, [props, renderBubbleProp]);
const renderSystemMessage = useCallback(() => {
const {
/* eslint-disable @typescript-eslint/no-unused-vars */
containerStyle, onMessageLayout, swipeToReply,
/* eslint-enable @typescript-eslint/no-unused-vars */
...rest } = props;
if (renderSystemMessageProp)
return renderComponentOrElement(renderSystemMessageProp, rest);
return <SystemMessage {...rest}/>;
}, [props, renderSystemMessageProp]);
const renderAvatar = useCallback(() => {
if (user?._id &&
currentMessage?.user &&
user._id === currentMessage.user._id &&
!isUserAvatarVisible)
return null;
if (currentMessage?.user?.avatar === null)
return null;
const {
/* eslint-disable @typescript-eslint/no-unused-vars */
containerStyle, onMessageLayout, swipeToReply,
/* eslint-enable @typescript-eslint/no-unused-vars */
...rest } = props;
return <Avatar {...rest}/>;
}, [
props,
user,
currentMessage,
isUserAvatarVisible,
]);
const renderSwipeAction = useCallback((progress, translation) => {
if (renderSwipeToReplyActionProp)
return renderSwipeToReplyActionProp(progress, translation, position);
return (<ReplyIcon progress={progress} translation={translation} direction={swipeToReplyDirection} position={position} style={swipeToReplyActionContainerStyle}/>);
}, [position, renderSwipeToReplyActionProp, swipeToReplyDirection, swipeToReplyActionContainerStyle]);
const handleSwipeableOpen = useCallback(() => {
swipeableRef.current?.close();
}, []);
const handleSwipeableWillOpen = useCallback(() => {
if (onSwipeToReply && currentMessage)
onSwipeToReply(currentMessage);
}, [onSwipeToReply, currentMessage]);
const sameUser = useMemo(() => isSameUser(currentMessage, nextMessage), [currentMessage, nextMessage]);
const messageContent = useMemo(() => {
if (currentMessage?.system)
return renderSystemMessage();
return (<View style={[
getStyleWithPosition(styles, 'container', position),
{ marginBottom: sameUser ? 2 : 10 },
!props.isInverted && { marginBottom: 2 },
containerStyle?.[position],
]}>
{position === 'left' && renderAvatar()}
{renderBubble()}
{position === 'right' && renderAvatar()}
</View>);
}, [
currentMessage?.system,
renderSystemMessage,
position,
sameUser,
props.isInverted,
containerStyle,
renderAvatar,
renderBubble,
]);
if (!currentMessage)
return null;
// Don't wrap system messages in Swipeable
if (currentMessage.system || !isSwipeToReplyEnabled)
return (<View onLayout={onMessageLayout}>
{messageContent}
</View>);
return (<View onLayout={onMessageLayout}>
<ReanimatedSwipeable ref={swipeableRef} friction={2} overshootFriction={8} renderRightActions={swipeToReplyDirection === 'left' ? renderSwipeAction : undefined} renderLeftActions={swipeToReplyDirection === 'right' ? renderSwipeAction : undefined} onSwipeableOpen={handleSwipeableOpen} onSwipeableWillOpen={handleSwipeableWillOpen}>
{messageContent}
</ReanimatedSwipeable>
</View>);
};
const localStyles = StyleSheet.create({
swipeActionContainer: {
width: 40,
justifyContent: 'center',
alignItems: 'center',
},
replyIconContainer: {
width: 28,
height: 28,
borderRadius: 14,
backgroundColor: Color.defaultBlue,
justifyContent: 'center',
alignItems: 'center',
},
replyIcon: {
width: 14,
height: 10,
transform: [{ scaleX: -1 }],
},
replyIconArrow: {
position: 'absolute',
top: 0,
left: 0,
width: 0,
height: 0,
borderTopWidth: 5,
borderTopColor: 'transparent',
borderBottomWidth: 5,
borderBottomColor: 'transparent',
borderRightWidth: 6,
borderRightColor: Color.white,
},
replyIconLine: {
position: 'absolute',
top: 3,
left: 5,
width: 9,
height: 4,
borderTopWidth: 2,
borderRightWidth: 2,
borderTopColor: Color.white,
borderRightColor: Color.white,
borderTopRightRadius: 4,
},
});
//# sourceMappingURL=index.js.map