ignite-boilerplate-andross-typescript
Version:
Customized version of ignite Andross With TypeScript Support.
627 lines (561 loc) • 17.3 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, Platform, StyleSheet, View } from 'react-native'
import ActionSheet from '@expo/react-native-action-sheet'
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: MIN_COMPOSER_HEIGHT,
messagesContainerHeight: null,
typingDisabled: false
}
this.onKeyboardWillShow = this.onKeyboardWillShow.bind(this)
this.onKeyboardWillHide = this.onKeyboardWillHide.bind(this)
this.onKeyboardDidShow = this.onKeyboardDidShow.bind(this)
this.onKeyboardDidHide = this.onKeyboardDidHide.bind(this)
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,
keyboardShouldPersistTaps: this.props.keyboardShouldPersistTaps,
onKeyboardWillShow: this.onKeyboardWillShow,
onKeyboardWillHide: this.onKeyboardWillHide,
onKeyboardDidShow: this.onKeyboardDidShow,
onKeyboardDidHide: this.onKeyboardDidHide
}
}
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 {
actionSheet: () => this._actionSheetRef,
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
}
setMaxHeight (height) {
this._maxHeight = height
}
getMaxHeight () {
return this._maxHeight
}
setKeyboardHeight (height) {
this._keyboardHeight = height
}
getKeyboardHeight () {
if (Platform.OS === 'android' && !this.props.forceGetKeyboardHeight) {
// For android: on-screen keyboard resized main container and has own height.
// @see https://developer.android.com/training/keyboard-input/visibility.html
// So for calculate the messages container height ignore keyboard height.
return 0
}
return this._keyboardHeight
}
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
}
// TODO: setMinInputToolbarHeight
getMinInputToolbarHeight () {
return this.props.renderAccessory
? this.props.minInputToolbarHeight * 2
: this.props.minInputToolbarHeight
}
calculateInputToolbarHeight (composerHeight) {
return composerHeight + (this.getMinInputToolbarHeight() - MIN_COMPOSER_HEIGHT)
}
/**
* Returns the height, based on current window size, without taking the keyboard into account.
*/
getBasicMessagesContainerHeight (composerHeight = this.state.composerHeight) {
return this.getMaxHeight() - this.calculateInputToolbarHeight(composerHeight)
}
/**
* Returns the height, based on current window size, taking the keyboard into account.
*/
getMessagesContainerHeightWithKeyboard (composerHeight = this.state.composerHeight) {
return this.getBasicMessagesContainerHeight(composerHeight) - this.getKeyboardHeight() + this.getBottomOffset()
}
prepareMessagesContainerHeight (value) {
if (this.props.isAnimated === true) {
return new Animated.Value(value)
}
return value
}
onKeyboardWillShow (e) {
this.setIsTypingDisabled(true)
this.setKeyboardHeight(e.endCoordinates ? e.endCoordinates.height : e.end.height)
this.setBottomOffset(this.props.bottomOffset)
const newMessagesContainerHeight = this.getMessagesContainerHeightWithKeyboard()
if (this.props.isAnimated === true) {
Animated.timing(this.state.messagesContainerHeight, {
toValue: newMessagesContainerHeight,
duration: 210
}).start()
} else {
this.setState({
messagesContainerHeight: newMessagesContainerHeight
})
}
}
onKeyboardWillHide () {
this.setIsTypingDisabled(true)
this.setKeyboardHeight(0)
this.setBottomOffset(0)
const newMessagesContainerHeight = this.getBasicMessagesContainerHeight()
if (this.props.isAnimated === true) {
Animated.timing(this.state.messagesContainerHeight, {
toValue: newMessagesContainerHeight,
duration: 210
}).start()
} else {
this.setState({
messagesContainerHeight: newMessagesContainerHeight
})
}
}
onKeyboardDidShow (e) {
if (Platform.OS === 'android') {
this.onKeyboardWillShow(e)
}
this.setIsTypingDisabled(false)
}
onKeyboardDidHide (e) {
if (Platform.OS === 'android') {
this.onKeyboardWillHide(e)
}
this.setIsTypingDisabled(false)
}
scrollToBottom (animated = true) {
if (this._messageContainerRef === null) {
return
}
this._messageContainerRef.scrollTo({ y: 0, animated })
}
renderMessages () {
const AnimatedView = this.props.isAnimated === true ? Animated.View : View
return (
<AnimatedView
style={{
height: this.state.messagesContainerHeight
}}
>
<MessageContainer
{...this.props}
invertibleScrollViewProps={this.invertibleScrollViewProps}
messages={this.getMessages()}
ref={(component) => (this._messageContainerRef = component)}
/>
{this.renderChatFooter()}
</AnimatedView>
)
}
onSend (messages = [], shouldResetInputToolbar = false) {
if (!Array.isArray(messages)) {
messages = [messages]
}
messages = messages.map((message) => {
return {
...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()
const newComposerHeight = MIN_COMPOSER_HEIGHT
const newMessagesContainerHeight = this.getMessagesContainerHeightWithKeyboard(newComposerHeight)
this.setState({
text: this.getTextFromProp(''),
composerHeight: newComposerHeight,
messagesContainerHeight: this.prepareMessagesContainerHeight(newMessagesContainerHeight)
})
}
focusTextInput () {
if (this.textInput) {
this.textInput.focus()
}
}
onInputSizeChanged (size) {
const newComposerHeight = Math.max(MIN_COMPOSER_HEIGHT, Math.min(MAX_COMPOSER_HEIGHT, size.height))
const newMessagesContainerHeight = this.getMessagesContainerHeightWithKeyboard(newComposerHeight)
this.setState({
composerHeight: newComposerHeight,
messagesContainerHeight: this.prepareMessagesContainerHeight(newMessagesContainerHeight)
})
}
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.setMaxHeight(layout.height)
const newComposerHeight = MIN_COMPOSER_HEIGHT
const newMessagesContainerHeight = this.getMessagesContainerHeightWithKeyboard(newComposerHeight)
this.setState({
isInitialized: true,
text: this.getTextFromProp(''),
composerHeight: newComposerHeight,
messagesContainerHeight: this.prepareMessagesContainerHeight(newMessagesContainerHeight)
})
}
onMainViewLayout (e) {
// fix an issue when keyboard is dismissing during the initialization
const { layout } = e.nativeEvent
if (this.getMaxHeight() !== layout.height || this.getIsFirstLayout() === true) {
this.setMaxHeight(layout.height)
this.setState({
messagesContainerHeight: this.prepareMessagesContainerHeight(this.getBasicMessagesContainerHeight())
})
}
if (this.getIsFirstLayout() === true) {
this.setIsFirstLayout(false)
}
}
renderInputToolbar () {
const inputToolbarProps = {
...this.props,
text: this.getTextFromProp(this.state.text),
composerHeight: Math.max(MIN_COMPOSER_HEIGHT, 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 (
<ActionSheet ref={(component) => (this._actionSheetRef = component)}>
<View style={styles.container} onLayout={this.onMainViewLayout}>
{this.renderMessages()}
{this.renderInputToolbar()}
</View>
</ActionSheet>
)
}
return (
<View style={styles.container} onLayout={this.onInitialLayoutViewLayout}>
{this.renderLoading()}
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1
}
})
GiftedChat.childContextTypes = {
actionSheet: PropTypes.func,
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: Platform.select({
ios: true,
android: false
}),
loadEarlier: false,
onLoadEarlier: () => { },
isLoadingEarlier: false,
renderLoading: null,
renderLoadEarlier: null,
renderAvatar: undefined,
showUserAvatar: false,
onPressAvatar: null,
renderAvatarOnTop: false,
renderBubble: null,
renderSystemMessage: null,
onLongPress: null,
renderMessage: null,
renderMessageText: null,
renderMessageImage: null,
imageProps: {},
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: Platform.select({
ios: 'never',
android: 'always'
}),
onInputTextChanged: null,
maxInputLength: null,
forceGetKeyboardHeight: false,
inverted: true
}
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,
renderAvatarOnTop: PropTypes.bool,
renderBubble: PropTypes.func,
renderSystemMessage: PropTypes.func,
onLongPress: PropTypes.func,
renderMessage: PropTypes.func,
renderMessageText: PropTypes.func,
renderMessageImage: PropTypes.func,
imageProps: 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
}
export {
GiftedChat,
Actions,
Avatar,
Bubble,
SystemMessage,
MessageImage,
MessageText,
Composer,
Day,
InputToolbar,
LoadEarlier,
Message,
MessageContainer,
Send,
Time,
GiftedAvatar,
utils
}