1 | import PropTypes from 'prop-types';
|
2 | import React from 'react';
|
3 | import { Platform, StyleSheet, View, SafeAreaView, KeyboardAvoidingView, } from 'react-native';
|
4 | import { ActionSheetProvider, } from '@expo/react-native-action-sheet';
|
5 | import uuid from 'uuid';
|
6 | import { getBottomSpace } from 'react-native-iphone-x-helper';
|
7 | import dayjs from 'dayjs';
|
8 | import localizedFormat from 'dayjs/plugin/localizedFormat';
|
9 | import * as utils from './utils';
|
10 | import Actions from './Actions';
|
11 | import Avatar from './Avatar';
|
12 | import Bubble from './Bubble';
|
13 | import SystemMessage from './SystemMessage';
|
14 | import MessageImage from './MessageImage';
|
15 | import MessageText from './MessageText';
|
16 | import Composer from './Composer';
|
17 | import Day from './Day';
|
18 | import InputToolbar from './InputToolbar';
|
19 | import LoadEarlier from './LoadEarlier';
|
20 | import Message from './Message';
|
21 | import MessageContainer from './MessageContainer';
|
22 | import Send from './Send';
|
23 | import Time from './Time';
|
24 | import GiftedAvatar from './GiftedAvatar';
|
25 | import { MIN_COMPOSER_HEIGHT, MAX_COMPOSER_HEIGHT, DEFAULT_PLACEHOLDER, TIME_FORMAT, DATE_FORMAT, } from './Constant';
|
26 | dayjs.extend(localizedFormat);
|
27 | class GiftedChat extends React.Component {
|
28 | constructor(props) {
|
29 | super(props);
|
30 | this._isMounted = false;
|
31 | this._keyboardHeight = 0;
|
32 | this._bottomOffset = 0;
|
33 | this._maxHeight = undefined;
|
34 | this._isFirstLayout = true;
|
35 | this._locale = 'en';
|
36 | this.invertibleScrollViewProps = undefined;
|
37 | this._actionSheetRef = undefined;
|
38 | this._messageContainerRef = React.createRef();
|
39 | this.state = {
|
40 | isInitialized: false,
|
41 | composerHeight: this.props.minComposerHeight,
|
42 | messagesContainerHeight: undefined,
|
43 | typingDisabled: false,
|
44 | text: undefined,
|
45 | messages: undefined,
|
46 | };
|
47 | this.getLocale = () => this._locale;
|
48 | this.safeAreaSupport = (bottomOffset) => {
|
49 | return bottomOffset === this._bottomOffset
|
50 | ? this.getBottomOffset()
|
51 | ? this.getBottomOffset()
|
52 | : getBottomSpace()
|
53 | : bottomOffset;
|
54 | };
|
55 | this.onKeyboardWillShow = (e) => {
|
56 | if (this.props.isKeyboardInternallyHandled) {
|
57 | this.setIsTypingDisabled(true);
|
58 | this.setKeyboardHeight(e.endCoordinates ? e.endCoordinates.height : e.end.height);
|
59 | this.setBottomOffset(this.safeAreaSupport(this.props.bottomOffset));
|
60 | const newMessagesContainerHeight = this.getMessagesContainerHeightWithKeyboard();
|
61 | this.setState({
|
62 | messagesContainerHeight: newMessagesContainerHeight,
|
63 | });
|
64 | }
|
65 | };
|
66 | this.onKeyboardWillHide = (_e) => {
|
67 | if (this.props.isKeyboardInternallyHandled) {
|
68 | this.setIsTypingDisabled(true);
|
69 | this.setKeyboardHeight(0);
|
70 | this.setBottomOffset(0);
|
71 | const newMessagesContainerHeight = this.getBasicMessagesContainerHeight();
|
72 | this.setState({
|
73 | messagesContainerHeight: newMessagesContainerHeight,
|
74 | });
|
75 | }
|
76 | };
|
77 | this.onKeyboardDidShow = (e) => {
|
78 | if (Platform.OS === 'android') {
|
79 | this.onKeyboardWillShow(e);
|
80 | }
|
81 | this.setIsTypingDisabled(false);
|
82 | };
|
83 | this.onKeyboardDidHide = (e) => {
|
84 | if (Platform.OS === 'android') {
|
85 | this.onKeyboardWillHide(e);
|
86 | }
|
87 | this.setIsTypingDisabled(false);
|
88 | };
|
89 | this.onSend = (messages = [], shouldResetInputToolbar = false) => {
|
90 | if (!Array.isArray(messages)) {
|
91 | messages = [messages];
|
92 | }
|
93 | const newMessages = messages.map(message => {
|
94 | return {
|
95 | ...message,
|
96 | user: this.props.user,
|
97 | createdAt: new Date(),
|
98 | _id: this.props.messageIdGenerator && this.props.messageIdGenerator(),
|
99 | };
|
100 | });
|
101 | if (shouldResetInputToolbar === true) {
|
102 | this.setIsTypingDisabled(true);
|
103 | this.resetInputToolbar();
|
104 | }
|
105 | if (this.props.onSend) {
|
106 | this.props.onSend(newMessages);
|
107 | }
|
108 | if (shouldResetInputToolbar === true) {
|
109 | setTimeout(() => {
|
110 | if (this.getIsMounted() === true) {
|
111 | this.setIsTypingDisabled(false);
|
112 | }
|
113 | }, 100);
|
114 | }
|
115 | };
|
116 | this.onInputSizeChanged = (size) => {
|
117 | const newComposerHeight = Math.max(this.props.minComposerHeight, Math.min(this.props.maxComposerHeight, size.height));
|
118 | const newMessagesContainerHeight = this.getMessagesContainerHeightWithKeyboard(newComposerHeight);
|
119 | this.setState({
|
120 | composerHeight: newComposerHeight,
|
121 | messagesContainerHeight: newMessagesContainerHeight,
|
122 | });
|
123 | };
|
124 | this.onInputTextChanged = (text) => {
|
125 | if (this.getIsTypingDisabled()) {
|
126 | return;
|
127 | }
|
128 | if (this.props.onInputTextChanged) {
|
129 | this.props.onInputTextChanged(text);
|
130 | }
|
131 |
|
132 | if (this.props.text === undefined) {
|
133 | this.setState({ text });
|
134 | }
|
135 | };
|
136 | this.onInitialLayoutViewLayout = (e) => {
|
137 | const { layout } = e.nativeEvent;
|
138 | if (layout.height <= 0) {
|
139 | return;
|
140 | }
|
141 | this.notifyInputTextReset();
|
142 | this.setMaxHeight(layout.height);
|
143 | const newComposerHeight = this.props.minComposerHeight;
|
144 | const newMessagesContainerHeight = this.getMessagesContainerHeightWithKeyboard(newComposerHeight);
|
145 | const initialText = this.props.initialText || '';
|
146 | this.setState({
|
147 | isInitialized: true,
|
148 | text: this.getTextFromProp(initialText),
|
149 | composerHeight: newComposerHeight,
|
150 | messagesContainerHeight: newMessagesContainerHeight,
|
151 | });
|
152 | };
|
153 | this.onMainViewLayout = (e) => {
|
154 |
|
155 | const { layout } = e.nativeEvent;
|
156 | if (this.getMaxHeight() !== layout.height ||
|
157 | this.getIsFirstLayout() === true) {
|
158 | this.setMaxHeight(layout.height);
|
159 | this.setState({
|
160 | messagesContainerHeight: this._keyboardHeight > 0
|
161 | ? this.getMessagesContainerHeightWithKeyboard()
|
162 | : this.getBasicMessagesContainerHeight(),
|
163 | });
|
164 | }
|
165 | if (this.getIsFirstLayout() === true) {
|
166 | this.setIsFirstLayout(false);
|
167 | }
|
168 | };
|
169 | this.invertibleScrollViewProps = {
|
170 | inverted: this.props.inverted,
|
171 | keyboardShouldPersistTaps: this.props.keyboardShouldPersistTaps,
|
172 | onKeyboardWillShow: this.onKeyboardWillShow,
|
173 | onKeyboardWillHide: this.onKeyboardWillHide,
|
174 | onKeyboardDidShow: this.onKeyboardDidShow,
|
175 | onKeyboardDidHide: this.onKeyboardDidHide,
|
176 | };
|
177 | }
|
178 | static append(currentMessages = [], messages, inverted = true) {
|
179 | if (!Array.isArray(messages)) {
|
180 | messages = [messages];
|
181 | }
|
182 | return inverted
|
183 | ? messages.concat(currentMessages)
|
184 | : currentMessages.concat(messages);
|
185 | }
|
186 | static prepend(currentMessages = [], messages, inverted = true) {
|
187 | if (!Array.isArray(messages)) {
|
188 | messages = [messages];
|
189 | }
|
190 | return inverted
|
191 | ? currentMessages.concat(messages)
|
192 | : messages.concat(currentMessages);
|
193 | }
|
194 | getChildContext() {
|
195 | return {
|
196 | actionSheet: this.props.actionSheet || (() => this._actionSheetRef.getContext()),
|
197 | getLocale: this.getLocale,
|
198 | };
|
199 | }
|
200 | componentDidMount() {
|
201 | const { messages, text } = this.props;
|
202 | this.setIsMounted(true);
|
203 | this.initLocale();
|
204 | this.setMessages(messages || []);
|
205 | this.setTextFromProp(text);
|
206 | }
|
207 | componentWillUnmount() {
|
208 | this.setIsMounted(false);
|
209 | }
|
210 | componentDidUpdate(prevProps = {}) {
|
211 | const { messages, text, inverted } = this.props;
|
212 | if (this.props !== prevProps) {
|
213 | this.setMessages(messages || []);
|
214 | }
|
215 | if (inverted === false &&
|
216 | messages &&
|
217 | prevProps.messages &&
|
218 | messages.length !== prevProps.messages.length) {
|
219 | setTimeout(() => this.scrollToBottom(false), 200);
|
220 | }
|
221 | if (text !== prevProps.text) {
|
222 | this.setTextFromProp(text);
|
223 | }
|
224 | }
|
225 | initLocale() {
|
226 | if (this.props.locale === null) {
|
227 | this.setLocale('en');
|
228 | }
|
229 | else {
|
230 | this.setLocale(this.props.locale || 'en');
|
231 | }
|
232 | }
|
233 | setLocale(locale) {
|
234 | this._locale = locale;
|
235 | }
|
236 | setTextFromProp(textProp) {
|
237 |
|
238 | if (textProp !== undefined && textProp !== this.state.text) {
|
239 | this.setState({ text: textProp });
|
240 | }
|
241 | }
|
242 | getTextFromProp(fallback) {
|
243 | if (this.props.text === undefined) {
|
244 | return fallback;
|
245 | }
|
246 | return this.props.text;
|
247 | }
|
248 | setMessages(messages) {
|
249 | this.setState({ messages });
|
250 | }
|
251 | getMessages() {
|
252 | return this.state.messages;
|
253 | }
|
254 | setMaxHeight(height) {
|
255 | this._maxHeight = height;
|
256 | }
|
257 | getMaxHeight() {
|
258 | return this._maxHeight;
|
259 | }
|
260 | setKeyboardHeight(height) {
|
261 | this._keyboardHeight = height;
|
262 | }
|
263 | getKeyboardHeight() {
|
264 | if (Platform.OS === 'android' && !this.props.forceGetKeyboardHeight) {
|
265 |
|
266 |
|
267 |
|
268 | return 0;
|
269 | }
|
270 | return this._keyboardHeight;
|
271 | }
|
272 | setBottomOffset(value) {
|
273 | this._bottomOffset = value;
|
274 | }
|
275 | getBottomOffset() {
|
276 | return this._bottomOffset;
|
277 | }
|
278 | setIsFirstLayout(value) {
|
279 | this._isFirstLayout = value;
|
280 | }
|
281 | getIsFirstLayout() {
|
282 | return this._isFirstLayout;
|
283 | }
|
284 | setIsTypingDisabled(value) {
|
285 | this.setState({
|
286 | typingDisabled: value,
|
287 | });
|
288 | }
|
289 | getIsTypingDisabled() {
|
290 | return this.state.typingDisabled;
|
291 | }
|
292 | setIsMounted(value) {
|
293 | this._isMounted = value;
|
294 | }
|
295 | getIsMounted() {
|
296 | return this._isMounted;
|
297 | }
|
298 | getMinInputToolbarHeight() {
|
299 | return this.props.renderAccessory
|
300 | ? this.props.minInputToolbarHeight * 2
|
301 | : this.props.minInputToolbarHeight;
|
302 | }
|
303 | calculateInputToolbarHeight(composerHeight) {
|
304 | return (composerHeight +
|
305 | (this.getMinInputToolbarHeight() - this.props.minComposerHeight));
|
306 | }
|
307 | |
308 |
|
309 |
|
310 | getBasicMessagesContainerHeight(composerHeight = this.state.composerHeight) {
|
311 | return (this.getMaxHeight() - this.calculateInputToolbarHeight(composerHeight));
|
312 | }
|
313 | |
314 |
|
315 |
|
316 | getMessagesContainerHeightWithKeyboard(composerHeight = this.state.composerHeight) {
|
317 | return (this.getBasicMessagesContainerHeight(composerHeight) -
|
318 | this.getKeyboardHeight() +
|
319 | this.getBottomOffset());
|
320 | }
|
321 | scrollToBottom(animated = true) {
|
322 | if (this._messageContainerRef && this._messageContainerRef.current) {
|
323 | const { inverted } = this.props;
|
324 | if (!inverted) {
|
325 | this._messageContainerRef.current.scrollToEnd({ animated });
|
326 | }
|
327 | else {
|
328 | this._messageContainerRef.current.scrollToOffset({
|
329 | offset: 0,
|
330 | animated,
|
331 | });
|
332 | }
|
333 | }
|
334 | }
|
335 | renderMessages() {
|
336 | const { messagesContainerStyle, ...messagesContainerProps } = this.props;
|
337 | const fragment = (<View style={[
|
338 | {
|
339 | height: this.state.messagesContainerHeight,
|
340 | },
|
341 | messagesContainerStyle,
|
342 | ]}>
|
343 | <MessageContainer {...messagesContainerProps} invertibleScrollViewProps={this.invertibleScrollViewProps} messages={this.getMessages()} forwardRef={this._messageContainerRef} isTyping={this.props.isTyping}/>
|
344 | {this.renderChatFooter()}
|
345 | </View>);
|
346 | return this.props.isKeyboardInternallyHandled ? (<KeyboardAvoidingView enabled>{fragment}</KeyboardAvoidingView>) : (fragment);
|
347 | }
|
348 | resetInputToolbar() {
|
349 | if (this.textInput) {
|
350 | this.textInput.clear();
|
351 | }
|
352 | this.notifyInputTextReset();
|
353 | const newComposerHeight = this.props.minComposerHeight;
|
354 | const newMessagesContainerHeight = this.getMessagesContainerHeightWithKeyboard(newComposerHeight);
|
355 | this.setState({
|
356 | text: this.getTextFromProp(''),
|
357 | composerHeight: newComposerHeight,
|
358 | messagesContainerHeight: newMessagesContainerHeight,
|
359 | });
|
360 | }
|
361 | focusTextInput() {
|
362 | if (this.textInput) {
|
363 | this.textInput.focus();
|
364 | }
|
365 | }
|
366 | notifyInputTextReset() {
|
367 | if (this.props.onInputTextChanged) {
|
368 | this.props.onInputTextChanged('');
|
369 | }
|
370 | }
|
371 | renderInputToolbar() {
|
372 | const inputToolbarProps = {
|
373 | ...this.props,
|
374 | text: this.getTextFromProp(this.state.text),
|
375 | composerHeight: Math.max(this.props.minComposerHeight, this.state.composerHeight),
|
376 | onSend: this.onSend,
|
377 | onInputSizeChanged: this.onInputSizeChanged,
|
378 | onTextChanged: this.onInputTextChanged,
|
379 | textInputProps: {
|
380 | ...this.props.textInputProps,
|
381 | ref: (textInput) => (this.textInput = textInput),
|
382 | maxLength: this.getIsTypingDisabled() ? 0 : this.props.maxInputLength,
|
383 | },
|
384 | };
|
385 | if (this.props.renderInputToolbar) {
|
386 | return this.props.renderInputToolbar(inputToolbarProps);
|
387 | }
|
388 | return <InputToolbar {...inputToolbarProps}/>;
|
389 | }
|
390 | renderChatFooter() {
|
391 | if (this.props.renderChatFooter) {
|
392 | return this.props.renderChatFooter();
|
393 | }
|
394 | return null;
|
395 | }
|
396 | renderLoading() {
|
397 | if (this.props.renderLoading) {
|
398 | return this.props.renderLoading();
|
399 | }
|
400 | return null;
|
401 | }
|
402 | render() {
|
403 | if (this.state.isInitialized === true) {
|
404 | const { wrapInSafeArea } = this.props;
|
405 | const Wrapper = wrapInSafeArea ? SafeAreaView : View;
|
406 | return (<Wrapper style={styles.safeArea}>
|
407 | <ActionSheetProvider ref={(component) => (this._actionSheetRef = component)}>
|
408 | <View style={styles.container} onLayout={this.onMainViewLayout}>
|
409 | {this.renderMessages()}
|
410 | {this.renderInputToolbar()}
|
411 | </View>
|
412 | </ActionSheetProvider>
|
413 | </Wrapper>);
|
414 | }
|
415 | return (<View style={styles.container} onLayout={this.onInitialLayoutViewLayout}>
|
416 | {this.renderLoading()}
|
417 | </View>);
|
418 | }
|
419 | }
|
420 | GiftedChat.childContextTypes = {
|
421 | actionSheet: PropTypes.func,
|
422 | getLocale: PropTypes.func,
|
423 | };
|
424 | GiftedChat.defaultProps = {
|
425 | messages: [],
|
426 | messagesContainerStyle: undefined,
|
427 | text: undefined,
|
428 | placeholder: DEFAULT_PLACEHOLDER,
|
429 | disableComposer: false,
|
430 | messageIdGenerator: () => uuid.v4(),
|
431 | user: {},
|
432 | onSend: () => { },
|
433 | locale: null,
|
434 | timeFormat: TIME_FORMAT,
|
435 | dateFormat: DATE_FORMAT,
|
436 | loadEarlier: false,
|
437 | onLoadEarlier: () => { },
|
438 | isLoadingEarlier: false,
|
439 | renderLoading: null,
|
440 | renderLoadEarlier: null,
|
441 | renderAvatar: undefined,
|
442 | showUserAvatar: false,
|
443 | actionSheet: null,
|
444 | onPressAvatar: null,
|
445 | onLongPressAvatar: null,
|
446 | renderUsernameOnMessage: false,
|
447 | renderAvatarOnTop: false,
|
448 | renderBubble: null,
|
449 | renderSystemMessage: null,
|
450 | onLongPress: null,
|
451 | renderMessage: null,
|
452 | renderMessageText: null,
|
453 | renderMessageImage: null,
|
454 | imageProps: {},
|
455 | videoProps: {},
|
456 | audioProps: {},
|
457 | lightboxProps: {},
|
458 | textInputProps: {},
|
459 | listViewProps: {},
|
460 | renderCustomView: null,
|
461 | isCustomViewBottom: false,
|
462 | renderDay: null,
|
463 | renderTime: null,
|
464 | renderFooter: null,
|
465 | renderChatEmpty: null,
|
466 | renderChatFooter: null,
|
467 | renderInputToolbar: null,
|
468 | renderComposer: null,
|
469 | renderActions: null,
|
470 | renderSend: null,
|
471 | renderAccessory: null,
|
472 | isKeyboardInternallyHandled: true,
|
473 | onPressActionButton: null,
|
474 | bottomOffset: 0,
|
475 | minInputToolbarHeight: 44,
|
476 | keyboardShouldPersistTaps: Platform.select({
|
477 | ios: 'never',
|
478 | android: 'always',
|
479 | default: 'never',
|
480 | }),
|
481 | onInputTextChanged: null,
|
482 | maxInputLength: null,
|
483 | forceGetKeyboardHeight: false,
|
484 | inverted: true,
|
485 | extraData: null,
|
486 | minComposerHeight: MIN_COMPOSER_HEIGHT,
|
487 | maxComposerHeight: MAX_COMPOSER_HEIGHT,
|
488 | wrapInSafeArea: true,
|
489 | };
|
490 | GiftedChat.propTypes = {
|
491 | messages: PropTypes.arrayOf(PropTypes.object),
|
492 | messagesContainerStyle: utils.StylePropType,
|
493 | text: PropTypes.string,
|
494 | initialText: PropTypes.string,
|
495 | placeholder: PropTypes.string,
|
496 | disableComposer: PropTypes.bool,
|
497 | messageIdGenerator: PropTypes.func,
|
498 | user: PropTypes.object,
|
499 | onSend: PropTypes.func,
|
500 | locale: PropTypes.string,
|
501 | timeFormat: PropTypes.string,
|
502 | dateFormat: PropTypes.string,
|
503 | isKeyboardInternallyHandled: PropTypes.bool,
|
504 | loadEarlier: PropTypes.bool,
|
505 | onLoadEarlier: PropTypes.func,
|
506 | isLoadingEarlier: PropTypes.bool,
|
507 | renderLoading: PropTypes.func,
|
508 | renderLoadEarlier: PropTypes.func,
|
509 | renderAvatar: PropTypes.func,
|
510 | showUserAvatar: PropTypes.bool,
|
511 | actionSheet: PropTypes.func,
|
512 | onPressAvatar: PropTypes.func,
|
513 | onLongPressAvatar: PropTypes.func,
|
514 | renderUsernameOnMessage: PropTypes.bool,
|
515 | renderAvatarOnTop: PropTypes.bool,
|
516 | isCustomViewBottom: PropTypes.bool,
|
517 | renderBubble: PropTypes.func,
|
518 | renderSystemMessage: PropTypes.func,
|
519 | onLongPress: PropTypes.func,
|
520 | renderMessage: PropTypes.func,
|
521 | renderMessageText: PropTypes.func,
|
522 | renderMessageImage: PropTypes.func,
|
523 | imageProps: PropTypes.object,
|
524 | videoProps: PropTypes.object,
|
525 | audioProps: PropTypes.object,
|
526 | lightboxProps: PropTypes.object,
|
527 | renderCustomView: PropTypes.func,
|
528 | renderDay: PropTypes.func,
|
529 | renderTime: PropTypes.func,
|
530 | renderFooter: PropTypes.func,
|
531 | renderChatEmpty: PropTypes.func,
|
532 | renderChatFooter: PropTypes.func,
|
533 | renderInputToolbar: PropTypes.func,
|
534 | renderComposer: PropTypes.func,
|
535 | renderActions: PropTypes.func,
|
536 | renderSend: PropTypes.func,
|
537 | renderAccessory: PropTypes.func,
|
538 | onPressActionButton: PropTypes.func,
|
539 | bottomOffset: PropTypes.number,
|
540 | minInputToolbarHeight: PropTypes.number,
|
541 | listViewProps: PropTypes.object,
|
542 | keyboardShouldPersistTaps: PropTypes.oneOf(['always', 'never', 'handled']),
|
543 | onInputTextChanged: PropTypes.func,
|
544 | maxInputLength: PropTypes.number,
|
545 | forceGetKeyboardHeight: PropTypes.bool,
|
546 | inverted: PropTypes.bool,
|
547 | textInputProps: PropTypes.object,
|
548 | extraData: PropTypes.object,
|
549 | minComposerHeight: PropTypes.number,
|
550 | maxComposerHeight: PropTypes.number,
|
551 | alignTop: PropTypes.bool,
|
552 | wrapInSafeArea: PropTypes.bool,
|
553 | };
|
554 | const styles = StyleSheet.create({
|
555 | container: {
|
556 | flex: 1,
|
557 | },
|
558 | safeArea: {
|
559 | flex: 1,
|
560 | },
|
561 | });
|
562 | export { GiftedChat, Actions, Avatar, Bubble, SystemMessage, MessageImage, MessageText, Composer, Day, InputToolbar, LoadEarlier, Message, MessageContainer, Send, Time, GiftedAvatar, utils, };
|
563 | //# sourceMappingURL=GiftedChat.js.map |
\ | No newline at end of file |