UNPKG

17.4 kBJavaScriptView Raw
1import { ActionSheetProvider, } from '@expo/react-native-action-sheet';
2import dayjs from 'dayjs';
3import localizedFormat from 'dayjs/plugin/localizedFormat';
4import PropTypes from 'prop-types';
5import React, { createRef, useEffect, useMemo, useRef, useState } from 'react';
6import { KeyboardAvoidingView, Platform, StyleSheet, View, } from 'react-native';
7import uuid from 'uuid';
8import { Actions } from './Actions';
9import { Avatar } from './Avatar';
10import Bubble from './Bubble';
11import { Composer } from './Composer';
12import { MAX_COMPOSER_HEIGHT, MIN_COMPOSER_HEIGHT, TEST_ID } from './Constant';
13import { Day } from './Day';
14import GiftedAvatar from './GiftedAvatar';
15import { GiftedChatContext } from './GiftedChatContext';
16import { InputToolbar } from './InputToolbar';
17import { LoadEarlier } from './LoadEarlier';
18import Message from './Message';
19import MessageContainer from './MessageContainer';
20import { MessageImage } from './MessageImage';
21import { MessageText } from './MessageText';
22import { Send } from './Send';
23import { SystemMessage } from './SystemMessage';
24import { Time } from './Time';
25import * as utils from './utils';
26dayjs.extend(localizedFormat);
27function GiftedChat(props) {
28 const { messages = [], text = undefined, initialText = '', isTyping, messageIdGenerator = () => uuid.v4(), user = {}, onSend = () => { }, locale = 'en', renderLoading = null, actionSheet = null, textInputProps = {}, renderChatFooter = null, renderInputToolbar = null, renderAccessory = null, isKeyboardInternallyHandled = true, bottomOffset = null, minInputToolbarHeight = 44, keyboardShouldPersistTaps = Platform.select({
29 ios: 'never',
30 android: 'always',
31 default: 'never',
32 }), onInputTextChanged = null, maxInputLength = null, forceGetKeyboardHeight = false, inverted = true, minComposerHeight = MIN_COMPOSER_HEIGHT, maxComposerHeight = MAX_COMPOSER_HEIGHT, messageContainerRef = createRef(), textInputRef = createRef(), } = props;
33 const isMountedRef = useRef(false);
34 const keyboardHeightRef = useRef(0);
35 const bottomOffsetRef = useRef(0);
36 const maxHeightRef = useRef(undefined);
37 const isFirstLayoutRef = useRef(true);
38 const actionSheetRef = useRef(null);
39 let _isTextInputWasFocused = false;
40 const [state, setState] = useState({
41 isInitialized: false,
42 composerHeight: minComposerHeight,
43 messagesContainerHeight: undefined,
44 typingDisabled: false,
45 text: undefined,
46 messages: undefined,
47 });
48 useEffect(() => {
49 isMountedRef.current = true;
50 setState({
51 ...state,
52 messages,
53 // Text prop takes precedence over state.
54 ...(text !== undefined && text !== state.text && { text: text }),
55 });
56 if (inverted === false && (messages === null || messages === void 0 ? void 0 : messages.length)) {
57 setTimeout(() => scrollToBottom(false), 200);
58 }
59 return () => {
60 isMountedRef.current = false;
61 };
62 }, [messages, text]);
63 const getTextFromProp = (fallback) => {
64 if (text === undefined) {
65 return fallback;
66 }
67 return text;
68 };
69 const getKeyboardHeight = () => {
70 if (Platform.OS === 'android' && !forceGetKeyboardHeight) {
71 // For android: on-screen keyboard resized main container and has own height.
72 // @see https://developer.android.com/training/keyboard-input/visibility.html
73 // So for calculate the messages container height ignore keyboard height.
74 return 0;
75 }
76 return keyboardHeightRef.current;
77 };
78 const calculateInputToolbarHeight = (composerHeight) => {
79 const getMinInputToolbarHeight = renderAccessory
80 ? minInputToolbarHeight * 2
81 : minInputToolbarHeight;
82 return composerHeight + (getMinInputToolbarHeight - minComposerHeight);
83 };
84 /**
85 * Returns the height, based on current window size, without taking the keyboard into account.
86 */
87 const getBasicMessagesContainerHeight = (composerHeight = state.composerHeight) => {
88 return maxHeightRef.current - calculateInputToolbarHeight(composerHeight);
89 };
90 /**
91 * Returns the height, based on current window size, taking the keyboard into account.
92 */
93 const getMessagesContainerHeightWithKeyboard = (composerHeight = state.composerHeight) => {
94 return (getBasicMessagesContainerHeight(composerHeight) -
95 getKeyboardHeight() +
96 bottomOffsetRef.current);
97 };
98 /**
99 * Store text input focus status when keyboard hide to retrieve
100 * it after wards if needed.
101 * `onKeyboardWillHide` may be called twice in sequence so we
102 * make a guard condition (eg. showing image picker)
103 */
104 const handleTextInputFocusWhenKeyboardHide = () => {
105 var _a;
106 if (!_isTextInputWasFocused) {
107 _isTextInputWasFocused = ((_a = textInputRef.current) === null || _a === void 0 ? void 0 : _a.isFocused()) || false;
108 }
109 };
110 /**
111 * Refocus the text input only if it was focused before showing keyboard.
112 * This is needed in some cases (eg. showing image picker).
113 */
114 const handleTextInputFocusWhenKeyboardShow = () => {
115 if (textInputRef.current &&
116 _isTextInputWasFocused &&
117 !textInputRef.current.isFocused()) {
118 textInputRef.current.focus();
119 }
120 // Reset the indicator since the keyboard is shown
121 _isTextInputWasFocused = false;
122 };
123 const onKeyboardWillShow = (e) => {
124 handleTextInputFocusWhenKeyboardShow();
125 if (isKeyboardInternallyHandled) {
126 keyboardHeightRef.current = e.endCoordinates
127 ? e.endCoordinates.height
128 : e.end.height;
129 bottomOffsetRef.current = bottomOffset != null ? bottomOffset : 1;
130 const newMessagesContainerHeight = getMessagesContainerHeightWithKeyboard();
131 setState({
132 ...state,
133 typingDisabled: true,
134 messagesContainerHeight: newMessagesContainerHeight,
135 });
136 }
137 };
138 const onKeyboardWillHide = (_e) => {
139 handleTextInputFocusWhenKeyboardHide();
140 if (isKeyboardInternallyHandled) {
141 keyboardHeightRef.current = 0;
142 bottomOffsetRef.current = 0;
143 const newMessagesContainerHeight = getBasicMessagesContainerHeight();
144 setState({
145 ...state,
146 typingDisabled: true,
147 messagesContainerHeight: newMessagesContainerHeight,
148 });
149 }
150 };
151 const onKeyboardDidShow = (e) => {
152 if (Platform.OS === 'android') {
153 onKeyboardWillShow(e);
154 }
155 setState({
156 ...state,
157 typingDisabled: false,
158 });
159 };
160 const onKeyboardDidHide = (e) => {
161 if (Platform.OS === 'android') {
162 onKeyboardWillHide(e);
163 }
164 setState({
165 ...state,
166 typingDisabled: false,
167 });
168 };
169 const scrollToBottom = (animated = true) => {
170 if (messageContainerRef === null || messageContainerRef === void 0 ? void 0 : messageContainerRef.current) {
171 if (!inverted) {
172 messageContainerRef.current.scrollToEnd({ animated });
173 }
174 else {
175 messageContainerRef.current.scrollToOffset({
176 offset: 0,
177 animated,
178 });
179 }
180 }
181 };
182 const renderMessages = () => {
183 const { messagesContainerStyle, ...messagesContainerProps } = props;
184 const fragment = (<View style={[
185 typeof state.messagesContainerHeight === 'number' && {
186 height: state.messagesContainerHeight,
187 },
188 messagesContainerStyle,
189 ]}>
190 <MessageContainer {...messagesContainerProps} invertibleScrollViewProps={{
191 inverted: inverted,
192 keyboardShouldPersistTaps: keyboardShouldPersistTaps,
193 onKeyboardWillShow: onKeyboardWillShow,
194 onKeyboardWillHide: onKeyboardWillHide,
195 onKeyboardDidShow: onKeyboardDidShow,
196 onKeyboardDidHide: onKeyboardDidHide,
197 }} messages={state.messages} forwardRef={messageContainerRef} isTyping={isTyping}/>
198 {_renderChatFooter()}
199 </View>);
200 return isKeyboardInternallyHandled ? (<KeyboardAvoidingView enabled>{fragment}</KeyboardAvoidingView>) : (fragment);
201 };
202 const _onSend = (messages = [], shouldResetInputToolbar = false) => {
203 if (!Array.isArray(messages)) {
204 messages = [messages];
205 }
206 const newMessages = messages.map(message => {
207 return {
208 ...message,
209 user: user,
210 createdAt: new Date(),
211 _id: messageIdGenerator && messageIdGenerator(),
212 };
213 });
214 if (shouldResetInputToolbar === true) {
215 setState({
216 ...state,
217 typingDisabled: true,
218 });
219 resetInputToolbar();
220 }
221 if (onSend) {
222 onSend(newMessages);
223 }
224 // if (shouldResetInputToolbar === true) {
225 // setTimeout(() => {
226 // if (isMountedRef.current === true) {
227 // setState({
228 // ...state,
229 // typingDisabled: false,
230 // })
231 // }
232 // }, 100)
233 // }
234 };
235 const resetInputToolbar = () => {
236 if (textInputRef.current) {
237 textInputRef.current.clear();
238 }
239 notifyInputTextReset();
240 const newMessagesContainerHeight = getMessagesContainerHeightWithKeyboard(minComposerHeight);
241 setState({
242 ...state,
243 text: getTextFromProp(''),
244 composerHeight: minComposerHeight,
245 messagesContainerHeight: newMessagesContainerHeight,
246 });
247 };
248 const onInputSizeChanged = (size) => {
249 const newComposerHeight = Math.max(minComposerHeight, Math.min(maxComposerHeight, size.height));
250 const newMessagesContainerHeight = getMessagesContainerHeightWithKeyboard(newComposerHeight);
251 setState({
252 ...state,
253 composerHeight: newComposerHeight,
254 messagesContainerHeight: newMessagesContainerHeight,
255 });
256 };
257 const _onInputTextChanged = (_text) => {
258 if (state.typingDisabled) {
259 return;
260 }
261 if (onInputTextChanged) {
262 onInputTextChanged(_text);
263 }
264 // Only set state if it's not being overridden by a prop.
265 if (text === undefined) {
266 setState({ ...state, text: _text });
267 }
268 };
269 const notifyInputTextReset = () => {
270 if (onInputTextChanged) {
271 onInputTextChanged('');
272 }
273 };
274 const onInitialLayoutViewLayout = (e) => {
275 const { layout } = e.nativeEvent;
276 if (layout.height <= 0) {
277 return;
278 }
279 notifyInputTextReset();
280 maxHeightRef.current = layout.height;
281 const newMessagesContainerHeight = getMessagesContainerHeightWithKeyboard(minComposerHeight);
282 setState({
283 ...state,
284 isInitialized: true,
285 text: getTextFromProp(initialText),
286 composerHeight: minComposerHeight,
287 messagesContainerHeight: newMessagesContainerHeight,
288 });
289 };
290 const onMainViewLayout = (e) => {
291 // TODO: fix an issue when keyboard is dismissing during the initialization
292 const { layout } = e.nativeEvent;
293 if (maxHeightRef.current !== layout.height ||
294 isFirstLayoutRef.current === true) {
295 maxHeightRef.current = layout.height;
296 setState({
297 ...state,
298 messagesContainerHeight: keyboardHeightRef.current > 0
299 ? getMessagesContainerHeightWithKeyboard()
300 : getBasicMessagesContainerHeight(),
301 });
302 }
303 if (isFirstLayoutRef.current === true) {
304 isFirstLayoutRef.current = false;
305 }
306 };
307 const _renderInputToolbar = () => {
308 const inputToolbarProps = {
309 ...props,
310 text: getTextFromProp(state.text),
311 composerHeight: Math.max(minComposerHeight, state.composerHeight),
312 onSend: _onSend,
313 onInputSizeChanged: onInputSizeChanged,
314 onTextChanged: _onInputTextChanged,
315 textInputProps: {
316 ...textInputProps,
317 ref: textInputRef,
318 maxLength: state.typingDisabled ? 0 : maxInputLength,
319 },
320 };
321 if (renderInputToolbar) {
322 return renderInputToolbar(inputToolbarProps);
323 }
324 return <InputToolbar {...inputToolbarProps}/>;
325 };
326 const _renderChatFooter = () => {
327 if (renderChatFooter) {
328 return renderChatFooter();
329 }
330 return null;
331 };
332 const _renderLoading = () => {
333 if (renderLoading) {
334 return renderLoading();
335 }
336 return null;
337 };
338 const contextValues = useMemo(() => ({
339 actionSheet: actionSheet || (() => { var _a; return (_a = actionSheetRef.current) === null || _a === void 0 ? void 0 : _a.getContext(); }),
340 getLocale: () => locale,
341 }), [actionSheet, locale]);
342 if (state.isInitialized === true) {
343 return (<GiftedChatContext.Provider value={contextValues}>
344 <View testID={TEST_ID.WRAPPER} style={styles.wrapper}>
345 <ActionSheetProvider ref={actionSheetRef}>
346 <View style={styles.container} onLayout={onMainViewLayout}>
347 {renderMessages()}
348 {_renderInputToolbar()}
349 </View>
350 </ActionSheetProvider>
351 </View>
352 </GiftedChatContext.Provider>);
353 }
354 return (<View testID={TEST_ID.LOADING_WRAPPER} style={styles.container} onLayout={onInitialLayoutViewLayout}>
355 {_renderLoading()}
356 </View>);
357}
358GiftedChat.propTypes = {
359 messages: PropTypes.arrayOf(PropTypes.object),
360 messagesContainerStyle: utils.StylePropType,
361 text: PropTypes.string,
362 initialText: PropTypes.string,
363 placeholder: PropTypes.string,
364 disableComposer: PropTypes.bool,
365 messageIdGenerator: PropTypes.func,
366 user: PropTypes.object,
367 onSend: PropTypes.func,
368 locale: PropTypes.string,
369 timeFormat: PropTypes.string,
370 dateFormat: PropTypes.string,
371 isKeyboardInternallyHandled: PropTypes.bool,
372 loadEarlier: PropTypes.bool,
373 onLoadEarlier: PropTypes.func,
374 isLoadingEarlier: PropTypes.bool,
375 renderLoading: PropTypes.func,
376 renderLoadEarlier: PropTypes.func,
377 renderAvatar: PropTypes.func,
378 showUserAvatar: PropTypes.bool,
379 actionSheet: PropTypes.func,
380 onPressAvatar: PropTypes.func,
381 onLongPressAvatar: PropTypes.func,
382 renderUsernameOnMessage: PropTypes.bool,
383 renderAvatarOnTop: PropTypes.bool,
384 isCustomViewBottom: PropTypes.bool,
385 renderBubble: PropTypes.func,
386 renderSystemMessage: PropTypes.func,
387 onLongPress: PropTypes.func,
388 renderMessage: PropTypes.func,
389 renderMessageText: PropTypes.func,
390 renderMessageImage: PropTypes.func,
391 imageProps: PropTypes.object,
392 videoProps: PropTypes.object,
393 audioProps: PropTypes.object,
394 lightboxProps: PropTypes.object,
395 renderCustomView: PropTypes.func,
396 renderDay: PropTypes.func,
397 renderTime: PropTypes.func,
398 renderFooter: PropTypes.func,
399 renderChatEmpty: PropTypes.func,
400 renderChatFooter: PropTypes.func,
401 renderInputToolbar: PropTypes.func,
402 renderComposer: PropTypes.func,
403 renderActions: PropTypes.func,
404 renderSend: PropTypes.func,
405 renderAccessory: PropTypes.func,
406 onPressActionButton: PropTypes.func,
407 bottomOffset: PropTypes.number,
408 minInputToolbarHeight: PropTypes.number,
409 listViewProps: PropTypes.object,
410 keyboardShouldPersistTaps: PropTypes.oneOf(['always', 'never', 'handled']),
411 onInputTextChanged: PropTypes.func,
412 maxInputLength: PropTypes.number,
413 forceGetKeyboardHeight: PropTypes.bool,
414 inverted: PropTypes.bool,
415 textInputProps: PropTypes.object,
416 extraData: PropTypes.object,
417 minComposerHeight: PropTypes.number,
418 maxComposerHeight: PropTypes.number,
419 alignTop: PropTypes.bool,
420};
421GiftedChat.append = (currentMessages = [], messages, inverted = true) => {
422 if (!Array.isArray(messages)) {
423 messages = [messages];
424 }
425 return inverted
426 ? messages.concat(currentMessages)
427 : currentMessages.concat(messages);
428};
429GiftedChat.prepend = (currentMessages = [], messages, inverted = true) => {
430 if (!Array.isArray(messages)) {
431 messages = [messages];
432 }
433 return inverted
434 ? currentMessages.concat(messages)
435 : messages.concat(currentMessages);
436};
437const styles = StyleSheet.create({
438 container: {
439 flex: 1,
440 },
441 wrapper: {
442 flex: 1,
443 },
444});
445export * from './Models';
446export { GiftedChat, Actions, Avatar, Bubble, SystemMessage, MessageImage, MessageText, Composer, Day, InputToolbar, LoadEarlier, Message, MessageContainer, Send, Time, GiftedAvatar, utils, };
447//# sourceMappingURL=GiftedChat.js.map
\No newline at end of file