UNPKG

react-native-gifted-chat

Version:
241 lines 10.2 kB
import React, { createRef, useEffect, useMemo, useRef, useState, useCallback, } from 'react'; import { View, useColorScheme, } from 'react-native'; import { ActionSheetProvider, } from '@expo/react-native-action-sheet'; import dayjs from 'dayjs'; import localizedFormat from 'dayjs/plugin/localizedFormat'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { KeyboardAvoidingView, KeyboardProvider } from 'react-native-keyboard-controller'; import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'; import { TEST_ID } from '../Constant'; import { GiftedChatContext } from '../GiftedChatContext'; import { InputToolbar } from '../InputToolbar'; import { MessagesContainer } from '../MessagesContainer'; import stylesCommon from '../styles'; import { renderComponentOrElement } from '../utils'; import styles from './styles'; dayjs.extend(localizedFormat); function GiftedChat(props) { const { messages = [], initialText = '', isTyping, // "random" function from here: https://stackoverflow.com/a/8084248/3452513 // we do not use uuid since it would add extra native dependency (https://www.npmjs.com/package/react-native-get-random-values) // lib's user can decide which algorithm to use and pass it as a prop messageIdGenerator = () => (Math.random() + 1).toString(36).substring(7), user = {}, onSend, locale = 'en', colorScheme: colorSchemeProp, renderLoading, actionSheet, textInputProps, renderChatFooter, renderInputToolbar, isInverted = true, // Reply props reply, } = props; // Extract reply props for internal use const replyMessageProp = reply?.message; const onClearReply = reply?.onClear; const onSwipeToReply = reply?.swipe?.onSwipe; const renderReplyPreview = reply?.renderPreview; const replyPreviewContainerStyle = reply?.previewStyle?.containerStyle; const replyPreviewTextStyle = reply?.previewStyle?.textStyle; const systemColorScheme = useColorScheme(); const colorScheme = colorSchemeProp !== undefined ? colorSchemeProp : systemColorScheme; const actionSheetRef = useRef(null); const insets = useSafeAreaInsets(); const messagesContainerRef = useMemo(() => props.messagesContainerRef || createRef(), [props.messagesContainerRef]); const textInputRef = useMemo(() => props.textInputRef || createRef(), [props.textInputRef]); const [isInitialized, setIsInitialized] = useState(false); const [text, setText] = useState(() => props.text || ''); const [internalReplyMessage, setInternalReplyMessage] = useState(null); // Use controlled or uncontrolled reply state const replyMessage = replyMessageProp !== undefined ? replyMessageProp : internalReplyMessage; const getTextFromProp = useCallback((fallback) => { if (props.text === undefined) return fallback; return props.text; }, [props.text]); const scrollToBottom = useCallback((isAnimated = true) => { if (!messagesContainerRef?.current) return; if (isInverted) { messagesContainerRef.current.scrollToOffset({ offset: 0, animated: isAnimated, }); return; } messagesContainerRef.current.scrollToEnd({ animated: isAnimated }); }, [isInverted, messagesContainerRef]); const handleSwipeToReply = useCallback((message) => { if (replyMessageProp === undefined) // Uncontrolled mode: manage state internally setInternalReplyMessage({ _id: message._id, text: message.text, user: message.user, image: message.image, audio: message.audio, }); onSwipeToReply?.(message); }, [replyMessageProp, onSwipeToReply]); const clearReply = useCallback(() => { if (replyMessageProp === undefined) // Uncontrolled mode: manage state internally setInternalReplyMessage(null); onClearReply?.(); }, [replyMessageProp, onClearReply]); const renderMessages = useMemo(() => { if (!isInitialized) return null; const { messagesContainerStyle, ...messagesContainerProps } = props; return (<View style={[stylesCommon.fill, messagesContainerStyle]}> <MessagesContainer {...messagesContainerProps} isInverted={isInverted} messages={messages} forwardRef={messagesContainerRef} isTyping={isTyping} reply={{ ...reply, swipe: reply?.swipe ? { ...reply.swipe, onSwipe: handleSwipeToReply, } : undefined, }}/> {renderComponentOrElement(renderChatFooter, {})} </View>); }, [ isInitialized, isTyping, messages, props, isInverted, messagesContainerRef, renderChatFooter, reply, handleSwipeToReply, ]); const notifyInputTextReset = useCallback(() => { props.textInputProps?.onChangeText?.(''); }, [props.textInputProps]); const resetInputToolbar = useCallback(() => { textInputRef.current?.clear(); notifyInputTextReset(); setText(getTextFromProp('')); }, [ getTextFromProp, textInputRef, notifyInputTextReset, ]); const _onSend = useCallback((messages = [], shouldResetInputToolbar = false) => { if (!Array.isArray(messages)) messages = [messages]; const newMessages = messages.map(message => { return { ...message, user: user, createdAt: new Date(), _id: messageIdGenerator?.(), // Attach reply message if exists ...(replyMessage ? { replyMessage } : {}), }; }); if (shouldResetInputToolbar === true) resetInputToolbar(); // Clear reply after sending clearReply(); onSend?.(newMessages); setTimeout(() => scrollToBottom(), 10); }, [messageIdGenerator, onSend, user, resetInputToolbar, scrollToBottom, replyMessage, clearReply]); const _onChangeText = useCallback((text) => { props.textInputProps?.onChangeText?.(text); // Only set state if it's not being overridden by a prop. if (props.text === undefined) setText(text); }, [props.text, props.textInputProps]); const onInitialLayoutViewLayout = useCallback((e) => { if (isInitialized) return; const { layout } = e.nativeEvent; if (layout.height <= 0) return; notifyInputTextReset(); setIsInitialized(true); setText(getTextFromProp(initialText)); }, [isInitialized, initialText, notifyInputTextReset, getTextFromProp]); const inputToolbarFragment = useMemo(() => { if (!isInitialized) return null; const inputToolbarProps = { ...props, text: getTextFromProp(text), onSend: _onSend, textInputProps: { ...textInputProps, onChangeText: _onChangeText, ref: textInputRef, }, // Reply preview props replyMessage, onClearReply: clearReply, renderReplyPreview, replyPreviewContainerStyle, replyPreviewTextStyle, }; if (renderInputToolbar) return renderComponentOrElement(renderInputToolbar, inputToolbarProps); return <InputToolbar {...inputToolbarProps}/>; }, [ isInitialized, _onSend, getTextFromProp, props, text, renderInputToolbar, textInputRef, textInputProps, _onChangeText, replyMessage, clearReply, renderReplyPreview, replyPreviewContainerStyle, replyPreviewTextStyle, ]); const contextValues = useMemo(() => ({ actionSheet: actionSheet || (() => ({ showActionSheetWithOptions: actionSheetRef.current.showActionSheetWithOptions, })), getLocale: () => locale, getColorScheme: () => colorScheme, }), [actionSheet, locale, colorScheme]); useEffect(() => { if (props.text != null) setText(props.text); }, [props.text]); return (<GiftedChatContext.Provider value={contextValues}> <ActionSheetProvider ref={actionSheetRef}> <View testID={TEST_ID.WRAPPER} style={[stylesCommon.fill, styles.contentContainer]} onLayout={onInitialLayoutViewLayout}> {/* @ts-expect-error */} <KeyboardAvoidingView behavior='translate-with-padding' keyboardVerticalOffset={insets.top} style={stylesCommon.fill} {...props.keyboardAvoidingViewProps}> <View style={[stylesCommon.fill, !isInitialized && styles.hidden]}> {renderMessages} {inputToolbarFragment} </View> {!isInitialized && renderComponentOrElement(renderLoading, {})} </KeyboardAvoidingView> </View> </ActionSheetProvider> </GiftedChatContext.Provider>); } function GiftedChatWrapper(props) { const { keyboardProviderProps, ...rest } = props; return (<GestureHandlerRootView style={styles.fill}> <SafeAreaProvider> <KeyboardProvider statusBarTranslucent navigationBarTranslucent {...keyboardProviderProps}> <GiftedChat {...rest}/> </KeyboardProvider> </SafeAreaProvider> </GestureHandlerRootView>); } GiftedChatWrapper.append = (currentMessages = [], messages, isInverted = true) => { if (!Array.isArray(messages)) messages = [messages]; return isInverted ? messages.concat(currentMessages) : currentMessages.concat(messages); }; GiftedChatWrapper.prepend = (currentMessages = [], messages, isInverted = true) => { if (!Array.isArray(messages)) messages = [messages]; return isInverted ? currentMessages.concat(messages) : messages.concat(currentMessages); }; export { GiftedChatWrapper as GiftedChat }; //# sourceMappingURL=index.js.map