react-gifted-chat
Version:
React Native Gifted Chat for React.JS
490 lines (436 loc) • 11.6 kB
JavaScript
/* eslint
no-param-reassign: 0,
no-use-before-define: ["error", { "variables": false }],
no-return-assign: 0,
no-mixed-operators: 0,
react/sort-comp: 0
*/
import PropTypes from 'prop-types';
import React from 'react';
import { Animated, StyleSheet, View } from 'react-native';
import moment from 'moment';
import uuid from 'uuid';
import * as utils from './utils';
import Actions from './Actions';
import Avatar from './Avatar';
import Bubble from './Bubble';
import SystemMessage from './SystemMessage';
import MessageImage from './MessageImage';
import MessageText from './MessageText';
import Composer from './Composer';
import Day from './Day';
import InputToolbar from './InputToolbar';
import LoadEarlier from './LoadEarlier';
import Message from './Message';
import MessageContainer from './MessageContainer';
import Send from './Send';
import Time from './Time';
import GiftedAvatar from './GiftedAvatar';
import {
MIN_COMPOSER_HEIGHT,
MAX_COMPOSER_HEIGHT,
DEFAULT_PLACEHOLDER,
TIME_FORMAT,
DATE_FORMAT,
} from './Constant';
class GiftedChat extends React.Component {
constructor(props) {
super(props);
// default values
this._isMounted = false;
this._keyboardHeight = 0;
this._bottomOffset = 0;
this._maxHeight = null;
this._isFirstLayout = true;
this._locale = 'en';
this._messages = [];
this.state = {
isInitialized: false, // initialization will calculate maxHeight before rendering the chat
composerHeight: 40,
typingDisabled: false,
};
this.onSend = this.onSend.bind(this);
this.getLocale = this.getLocale.bind(this);
this.onInputSizeChanged = this.onInputSizeChanged.bind(this);
this.onInputTextChanged = this.onInputTextChanged.bind(this);
this.onMainViewLayout = this.onMainViewLayout.bind(this);
this.onInitialLayoutViewLayout = this.onInitialLayoutViewLayout.bind(this);
this.invertibleScrollViewProps = {
inverted: this.props.inverted,
};
}
static append(currentMessages = [], messages, inverted = true) {
if (!Array.isArray(messages)) {
messages = [messages];
}
return inverted ? messages.concat(currentMessages) : currentMessages.concat(messages);
}
static prepend(currentMessages = [], messages, inverted = true) {
if (!Array.isArray(messages)) {
messages = [messages];
}
return inverted ? currentMessages.concat(messages) : messages.concat(currentMessages);
}
getChildContext() {
return {
getLocale: this.getLocale,
};
}
componentWillMount() {
const { messages, text } = this.props;
this.setIsMounted(true);
this.initLocale();
this.setMessages(messages || []);
this.setTextFromProp(text);
}
componentWillUnmount() {
this.setIsMounted(false);
}
componentWillReceiveProps(nextProps = {}) {
const { messages, text } = nextProps;
this.setMessages(messages || []);
this.setTextFromProp(text);
}
initLocale() {
if (this.props.locale === null || moment.locales().indexOf(this.props.locale) === -1) {
this.setLocale('en');
} else {
this.setLocale(this.props.locale);
}
}
setLocale(locale) {
this._locale = locale;
}
getLocale() {
return this._locale;
}
setTextFromProp(textProp) {
// Text prop takes precedence over state.
if (textProp !== undefined && textProp !== this.state.text) {
this.setState({ text: textProp });
}
}
getTextFromProp(fallback) {
if (this.props.text === undefined) {
return fallback;
}
return this.props.text;
}
setMessages(messages) {
this._messages = messages;
}
getMessages() {
return this._messages;
}
setBottomOffset(value) {
this._bottomOffset = value;
}
getBottomOffset() {
return this._bottomOffset;
}
setIsFirstLayout(value) {
this._isFirstLayout = value;
}
getIsFirstLayout() {
return this._isFirstLayout;
}
setIsTypingDisabled(value) {
this.setState({
typingDisabled: value,
});
}
getIsTypingDisabled() {
return this.state.typingDisabled;
}
setIsMounted(value) {
this._isMounted = value;
}
getIsMounted() {
return this._isMounted;
}
scrollToBottom(animated = true) {
if (this._messageContainerRef === null) {
return;
}
this._messageContainerRef.scrollTo({
y: 0,
animated,
});
}
renderMessages() {
return (
<div style={{
height: `calc(100% - ${this.state.composerHeight}px)`,
display: 'flex',
}}
>
<MessageContainer
{...this.props}
invertibleScrollViewProps={this.invertibleScrollViewProps}
messages={this.getMessages()}
ref={component => this._messageContainerRef = component}
/>
{this.renderChatFooter()}
</div>
);
}
onSend(messages = [], shouldResetInputToolbar = false) {
if (!Array.isArray(messages)) {
messages = [messages];
}
messages = messages.map(message => ({
...message,
user: this.props.user,
createdAt: new Date(),
_id: this.props.messageIdGenerator(),
}));
if (shouldResetInputToolbar === true) {
this.setIsTypingDisabled(true);
this.resetInputToolbar();
}
this.props.onSend(messages);
this.scrollToBottom();
if (shouldResetInputToolbar === true) {
setTimeout(() => {
if (this.getIsMounted() === true) {
this.setIsTypingDisabled(false);
}
}, 100);
}
}
resetInputToolbar() {
if (this.textInput) {
this.textInput.clear();
}
this.notifyInputTextReset();
this.setState({
text: this.getTextFromProp(''),
});
}
focusTextInput() {
if (this.textInput) {
this.textInput.focus();
}
}
onInputSizeChanged(size) {
}
onInputTextChanged(text) {
if (this.getIsTypingDisabled()) {
return;
}
if (this.props.onInputTextChanged) {
this.props.onInputTextChanged(text);
}
// Only set state if it's not being overridden by a prop.
if (this.props.text === undefined) {
this.setState({ text });
}
}
notifyInputTextReset() {
if (this.props.onInputTextChanged) {
this.props.onInputTextChanged('');
}
}
onInitialLayoutViewLayout(e) {
const { layout } = e.nativeEvent;
if (layout.height <= 0) {
return;
}
this.notifyInputTextReset();
this.setState({
isInitialized: true,
text: this.getTextFromProp(''),
});
}
onMainViewLayout(e) {
}
renderInputToolbar() {
const inputToolbarProps = {
...this.props,
text: this.getTextFromProp(this.state.text),
composerHeight: this.state.composerHeight,
onSend: this.onSend,
onInputSizeChanged: this.onInputSizeChanged,
onTextChanged: this.onInputTextChanged,
textInputProps: {
...this.props.textInputProps,
ref: textInput => (this.textInput = textInput),
maxLength: this.getIsTypingDisabled() ? 0 : this.props.maxInputLength,
},
};
if (this.props.renderInputToolbar) {
return this.props.renderInputToolbar(inputToolbarProps);
}
return (
<InputToolbar
{...inputToolbarProps}
/>
);
}
renderChatFooter() {
if (this.props.renderChatFooter) {
const footerProps = {
...this.props,
};
return this.props.renderChatFooter(footerProps);
}
return null;
}
renderLoading() {
if (this.props.renderLoading) {
return this.props.renderLoading();
}
return null;
}
render() {
if (this.state.isInitialized === true) {
return (
<View style={styles.container} onLayout={this.onMainViewLayout}>
{this.renderMessages()}
{this.renderInputToolbar()}
</View>
);
}
return (
<View style={styles.container} onLayout={this.onInitialLayoutViewLayout}>
{this.renderLoading()}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
height: '100%',
},
});
GiftedChat.childContextTypes = {
getLocale: PropTypes.func,
};
GiftedChat.defaultProps = {
messages: [],
text: undefined,
placeholder: DEFAULT_PLACEHOLDER,
messageIdGenerator: () => uuid.v4(),
user: {},
onSend: () => { },
locale: null,
timeFormat: TIME_FORMAT,
dateFormat: DATE_FORMAT,
isAnimated: false,
loadEarlier: false,
onLoadEarlier: () => { },
isLoadingEarlier: false,
renderLoading: null,
renderLoadEarlier: null,
renderAvatar: undefined,
showUserAvatar: false,
onPressAvatar: null,
renderUsernameOnMessage: false,
renderAvatarOnTop: false,
renderBubble: null,
renderSystemMessage: null,
onLongPress: null,
renderMessage: null,
renderMessageText: null,
renderMessageImage: null,
imageProps: {},
videoProps: {},
lightboxProps: {},
textInputProps: {},
listViewProps: {},
renderCustomView: null,
renderDay: null,
renderTime: null,
renderFooter: null,
renderChatFooter: null,
renderInputToolbar: null,
renderComposer: null,
renderActions: null,
renderSend: null,
renderAccessory: null,
onPressActionButton: null,
bottomOffset: 0,
minInputToolbarHeight: 44,
keyboardShouldPersistTaps: 'never',
onInputTextChanged: null,
maxInputLength: null,
forceGetKeyboardHeight: false,
inverted: true,
extraData: null,
minComposerHeight: MIN_COMPOSER_HEIGHT,
maxComposerHeight: MAX_COMPOSER_HEIGHT,
};
GiftedChat.propTypes = {
messages: PropTypes.arrayOf(PropTypes.object),
text: PropTypes.string,
placeholder: PropTypes.string,
messageIdGenerator: PropTypes.func,
user: PropTypes.object,
onSend: PropTypes.func,
locale: PropTypes.string,
timeFormat: PropTypes.string,
dateFormat: PropTypes.string,
isAnimated: PropTypes.bool,
loadEarlier: PropTypes.bool,
onLoadEarlier: PropTypes.func,
isLoadingEarlier: PropTypes.bool,
renderLoading: PropTypes.func,
renderLoadEarlier: PropTypes.func,
renderAvatar: PropTypes.func,
showUserAvatar: PropTypes.bool,
onPressAvatar: PropTypes.func,
renderUsernameOnMessage: PropTypes.bool,
renderAvatarOnTop: PropTypes.bool,
renderBubble: PropTypes.func,
renderSystemMessage: PropTypes.func,
onLongPress: PropTypes.func,
renderMessage: PropTypes.func,
renderMessageText: PropTypes.func,
renderMessageImage: PropTypes.func,
imageProps: PropTypes.object,
videoProps: PropTypes.object,
lightboxProps: PropTypes.object,
renderCustomView: PropTypes.func,
renderDay: PropTypes.func,
renderTime: PropTypes.func,
renderFooter: PropTypes.func,
renderChatFooter: PropTypes.func,
renderInputToolbar: PropTypes.func,
renderComposer: PropTypes.func,
renderActions: PropTypes.func,
renderSend: PropTypes.func,
renderAccessory: PropTypes.func,
onPressActionButton: PropTypes.func,
bottomOffset: PropTypes.number,
minInputToolbarHeight: PropTypes.number,
listViewProps: PropTypes.object,
keyboardShouldPersistTaps: PropTypes.oneOf(['always', 'never', 'handled']),
onInputTextChanged: PropTypes.func,
maxInputLength: PropTypes.number,
forceGetKeyboardHeight: PropTypes.bool,
inverted: PropTypes.bool,
textInputProps: PropTypes.object,
extraData: PropTypes.object,
minComposerHeight: PropTypes.number,
maxComposerHeight: PropTypes.number,
};
export {
GiftedChat,
Actions,
Avatar,
Bubble,
SystemMessage,
MessageImage,
MessageText,
Composer,
Day,
InputToolbar,
LoadEarlier,
Message,
MessageContainer,
Send,
Time,
GiftedAvatar,
utils,
};