1 | import { ActionSheetProvider, } from '@expo/react-native-action-sheet';
|
2 | import dayjs from 'dayjs';
|
3 | import localizedFormat from 'dayjs/plugin/localizedFormat';
|
4 | import PropTypes from 'prop-types';
|
5 | import React, { createRef, useEffect, useMemo, useRef, useState } from 'react';
|
6 | import { KeyboardAvoidingView, Platform, StyleSheet, View, } from 'react-native';
|
7 | import uuid from 'uuid';
|
8 | import { Actions } from './Actions';
|
9 | import { Avatar } from './Avatar';
|
10 | import Bubble from './Bubble';
|
11 | import { Composer } from './Composer';
|
12 | import { MAX_COMPOSER_HEIGHT, MIN_COMPOSER_HEIGHT, TEST_ID } from './Constant';
|
13 | import { Day } from './Day';
|
14 | import GiftedAvatar from './GiftedAvatar';
|
15 | import { GiftedChatContext } from './GiftedChatContext';
|
16 | import { InputToolbar } from './InputToolbar';
|
17 | import { LoadEarlier } from './LoadEarlier';
|
18 | import Message from './Message';
|
19 | import MessageContainer from './MessageContainer';
|
20 | import { MessageImage } from './MessageImage';
|
21 | import { MessageText } from './MessageText';
|
22 | import { Send } from './Send';
|
23 | import { SystemMessage } from './SystemMessage';
|
24 | import { Time } from './Time';
|
25 | import * as utils from './utils';
|
26 | dayjs.extend(localizedFormat);
|
27 | function 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 |
|
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 |
|
72 |
|
73 |
|
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 |
|
86 |
|
87 | const getBasicMessagesContainerHeight = (composerHeight = state.composerHeight) => {
|
88 | return maxHeightRef.current - calculateInputToolbarHeight(composerHeight);
|
89 | };
|
90 | |
91 |
|
92 |
|
93 | const getMessagesContainerHeightWithKeyboard = (composerHeight = state.composerHeight) => {
|
94 | return (getBasicMessagesContainerHeight(composerHeight) -
|
95 | getKeyboardHeight() +
|
96 | bottomOffsetRef.current);
|
97 | };
|
98 | |
99 |
|
100 |
|
101 |
|
102 |
|
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 |
|
112 |
|
113 |
|
114 | const handleTextInputFocusWhenKeyboardShow = () => {
|
115 | if (textInputRef.current &&
|
116 | _isTextInputWasFocused &&
|
117 | !textInputRef.current.isFocused()) {
|
118 | textInputRef.current.focus();
|
119 | }
|
120 |
|
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 | }
|
358 | GiftedChat.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 | };
|
421 | GiftedChat.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 | };
|
429 | GiftedChat.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 | };
|
437 | const styles = StyleSheet.create({
|
438 | container: {
|
439 | flex: 1,
|
440 | },
|
441 | wrapper: {
|
442 | flex: 1,
|
443 | },
|
444 | });
|
445 | export * from './Models';
|
446 | export { 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 |