Created
June 30, 2016 19:51
-
-
Save nuxlli/ba52f72c78ebf63d3a13791189a2e357 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { PropTypes, Component } from 'react'; | |
import KeyboardSpacer from 'react-native-keyboard-spacer'; | |
import { | |
Animated, | |
Dimensions, | |
Image, | |
ListView, | |
Platform, | |
StyleSheet, | |
Text, | |
TextInput, | |
View, | |
Navigator, | |
TouchableHighlight, | |
} from 'react-native'; | |
import ExtraDimensions from 'react-native-extra-dimensions-android'; | |
import moment from 'moment'; | |
import ChatNavBar from './ChatNavBar'; | |
import ChatTimeBar from './ChatTimeBar'; | |
import ChatAlertBar from './ChatAlertBar'; | |
import ChatMessage from './ChatMessage'; | |
import styles from './styles'; | |
import { chatMock as chatMockMessages } from './data/chatMock'; | |
import { chatMockEarlier as chatMockEarlierMessages } from './data/chatMock-earlier'; | |
const myUserId = 'a5cc7d41-390b-47dc-aa4e-e9eae7fa8110'; | |
export default class Chat extends Component { | |
constructor(props) { | |
super(props); | |
this.onLayout = this.onLayout.bind(this); | |
this.onFooterLayout = this.onFooterLayout.bind(this); | |
this.renderMessage = this.renderMessage.bind(this); | |
this.preLoadEarlierMessages = this.preLoadEarlierMessages.bind(this); | |
this.renderLoadEarlierMessages = this.renderLoadEarlierMessages.bind(this); | |
this.renderMessagesListView = this.renderMessagesListView.bind(this); | |
this.renderMessageInput = this.renderMessageInput.bind(this); | |
this.renderFooter = this.renderFooter.bind(this); | |
this.renderDate = this.renderDate.bind(this); | |
this.setTimer = this.setTimer.bind(this); | |
this.updateTimer = this.updateTimer.bind(this); | |
// keyboard event handlers | |
this.onKeyboardWillShow = this.onKeyboardWillShow.bind(this); | |
this.onKeyboardDidShow = this.onKeyboardDidShow.bind(this); | |
this.onKeyboardWillHide = this.onKeyboardWillHide.bind(this); | |
this.onKeyboardDidHide = this.onKeyboardDidHide.bind(this) | |
// chat screen is composed by ChatNavBar, ChatTimeBar, ChatAlertBar | |
// screen components' dimensions to calc listview height | |
// when keyboad is open | |
const screenHeight = Dimensions.get('window').height; | |
const navBarHeight = StyleSheet.flatten(styles.navBar).height; | |
const alertBarHeight = StyleSheet.flatten(styles.alertBar).height; | |
const timerBarHeight = StyleSheet.flatten(styles.timerBar).height; | |
const textInputHeight = StyleSheet.flatten(styles.messageContainer).height; | |
// sum of components' height | |
let barsHeightSum = navBarHeight + timerBarHeight + textInputHeight; | |
if (this.props.showAlertBar) { | |
barsHeightSum += alertBarHeight; | |
} | |
let statusBarHeight = Navigator.NavigationBar.Styles.General.StatusBarHeight; | |
if (Platform.OS === 'android') { | |
statusBarHeight = ExtraDimensions.get('SOFT_MENU_BAR_HEIGHT'); | |
} | |
// listView final height | |
// this.listViewMaxHeight = screenHeight - statusBarHeight - barsHeightSum; | |
this.listViewMaxHeight = screenHeight - statusBarHeight; | |
console.log('screenHeight ', screenHeight ); | |
console.log('navBarHeight ', navBarHeight ); | |
console.log('statusBarHeight ', statusBarHeight ); | |
// console.log('alertBarHeight ', alertBarHeight ); | |
console.log('timerBarHeight ', timerBarHeight ); | |
console.log('textInputHeight', textInputHeight); | |
console.log('listViewMaxHeight', this.listViewMaxHeight); | |
this._messages = this.getInitialData(); | |
this._listHeight = 0; | |
this._footerY = 0; | |
this._firstDisplay = true; | |
this._footerY = 0; | |
this._scrollToBottomOnNextRender = true; | |
this._scrollToPreviousPosition = false; | |
this.state = { | |
isTimerPaused: true, | |
timer: "00:00", | |
// chat session data | |
messages: this._messages, | |
// all chat session data loaded? | |
allLoaded: false, | |
// loading earlier status | |
isLoadingEarlierMessages: false, | |
// is user typing message / message itself | |
typingMessage: '', | |
// listview height | |
// height: new Animated.Value(this.listViewMaxHeight), | |
height: this.listViewMaxHeight, | |
// textInput message | |
message: null, | |
bottom: 0, | |
top: 0, | |
// first chat session data load | |
dataLoaded: false, | |
dataSource: new ListView.DataSource({ | |
rowHasChanged : (row1, row2) => row1 !== row2, | |
sectionHeaderHasChanged : (s1, s2) => s1 !== s2 | |
}).cloneWithRows([]), | |
} | |
} | |
componentDidMount() { | |
this.scrollResponder = this._listView.getScrollResponder(); | |
this.setMessages(this.state.messages); | |
// simulating a user typing message... | |
setTimeout(() => { | |
this.setState({ | |
typingMessage: '[User] is typing a message...', | |
}); | |
}, 2000); | |
setTimeout(() => { | |
this.scrollToBottom(true); | |
}, 4000); | |
} | |
getInitialData() { | |
return chatMockMessages.data.reverse(); | |
} | |
setMessages(messages) { | |
const rows = {}; | |
const identities = []; | |
for (let i = 0; i < messages.length; i++) { | |
if (typeof messages[i].id === 'undefined') { | |
console.warn('messages['+i+'].id is missing'); | |
} | |
rows[messages[i].id] = Object.assign({}, messages[i]); | |
rows[messages[i].id].position = (myUserId == messages[i].relationships.user.data.id) ? 'right' : 'left'; | |
identities.push(messages[i].id); | |
} | |
this.setState({ | |
dataSource: this.state.dataSource.cloneWithRows(rows, identities), | |
}); | |
// if (isAppended === true) { | |
// this._scrollToBottomOnNextRender = true; | |
// } else if (isAppended === false) { | |
// this._scrollToPreviousPosition = true; | |
// } | |
} | |
onLayout(event) { | |
const layout = event.nativeEvent.layout; | |
this._listHeight = layout.height; | |
if (this._firstDisplay === true) { | |
requestAnimationFrame(() => { | |
this._firstDisplay = false; | |
this.scrollToBottom(false); | |
}); | |
} | |
} | |
onFooterLayout(event) { | |
const layout = event.nativeEvent.layout; | |
const oldFooterY = this._footerY; | |
this._footerY = layout.y; | |
if (this._scrollToBottomOnNextRender === true) { | |
this._scrollToBottomOnNextRender = false; | |
this.scrollToBottom(); | |
} | |
if (this._scrollToPreviousPosition === true) { | |
this._scrollToPreviousPosition = false; | |
this.scrollResponder.scrollTo({ | |
y: this._footerY - oldFooterY, | |
x: 0, | |
animated: false, | |
}); | |
} | |
} | |
onKeyboardWillHide(e) { | |
// this.setState({top: 0, bottom: 0}); | |
// this._keyboardspacer.resetKeyboardSpace(e); | |
// this.setState({height: this.listViewMaxHeight}); | |
// Animated.timing(this.state.height, { | |
// toValue: this.listViewMaxHeight, | |
// duration: 150, | |
// }).start(); | |
} | |
onKeyboardDidHide(e) { | |
if (Platform.OS === 'android') { | |
this.onKeyboardWillHide(e); | |
} | |
// TODO test in android | |
// if (this.props.keyboardShouldPersistTaps === false) { | |
// if (this.isLastMessageVisible()) { | |
this.scrollToBottom(); | |
// } | |
// } | |
} | |
onKeyboardWillShow(e) { | |
// this._keyboardspacer.updateKeyboardSpace(e); | |
// console.log('e.endCoordinates:', e.endCoordinates); | |
// console.log('e.endCoordinates:', this.listViewMaxHeight - e.endCoordinates.height); | |
// if (Platform.OS === 'ios') { | |
// this.setState({bottom: e.endCoordinates.height}); | |
// } else { | |
// this.setState({top: e.endCoordinates.height}); | |
// } | |
// this.setState({bottom: e.endCoordinates.height}); | |
// this.setState({height: this.listViewMaxHeight - e.endCoordinates.height}); | |
// Animated.timing(this.state.height, { | |
// toValue: this.listViewMaxHeight - e.endCoordinates.height, | |
// duration: 200, | |
// }).start(); | |
} | |
onKeyboardDidShow(e) { | |
if (Platform.OS === 'android') { | |
this.onKeyboardWillShow(e); | |
} | |
setTimeout(() => { | |
this.scrollToBottom(); | |
}, (Platform.OS === 'android' ? 200 : 100)); | |
} | |
scrollToBottom(animated = null) { | |
if (this._listHeight && this._footerY && this._footerY > this._listHeight) { | |
let scrollDistance = this._listHeight - this._footerY; | |
if (this.state.typingMessage) { | |
scrollDistance -= 44; | |
} | |
this.scrollResponder.scrollTo({ | |
y: -scrollDistance, | |
x: 0, | |
animated: typeof animated === 'boolean' ? animated : true, | |
}); | |
} | |
} | |
preLoadEarlierMessages() { | |
this.setState({ | |
isLoadingEarlierMessages: true, | |
}); | |
const earlierMockMessages = chatMockEarlierMessages.data; | |
this.setMessages(earlierMockMessages.concat(this._messages)) | |
} | |
renderLoadEarlierMessages() { | |
return ( | |
<View style={styles.loadEarlierMessages}> | |
<TouchableHighlight style={styles.btnWrapper} | |
onPress={ this.preLoadEarlierMessages }> | |
<Text style={styles.loadEarlierMessagesButton}>Render Earlier messages</Text> | |
</TouchableHighlight> | |
</View> | |
); | |
} | |
renderMessage(message = {}) { | |
return ( | |
<View> | |
{this.renderDate(message)} | |
<ChatMessage | |
messageData={message} | |
/> | |
</View> | |
); | |
} | |
renderDate(message = {}) { | |
return ( | |
<Text style={styles.messageDate}> | |
{moment(message.attributes.created_at).calendar()} | |
</Text> | |
); | |
return null; | |
} | |
renderTypingMessage() { | |
if (this.state.typingMessage) { | |
return ( | |
<View | |
style={{ | |
height: 44, | |
justifyContent: 'center', | |
}}> | |
<Text | |
style={{ | |
marginLeft: 10, | |
marginRight: 10, | |
color: '#aaaaaa', | |
}}> | |
{this.state.typingMessage} | |
</Text> | |
</View> | |
); | |
} | |
return null; | |
} | |
renderMessagesListView() { | |
return ( | |
<ListView | |
ref={(component) => { this._listView = component }} | |
dataSource={this.state.dataSource} | |
renderHeader={this.renderLoadEarlierMessages} | |
renderRow={this.renderMessage} | |
renderFooter={this.renderFooter} | |
onLayout={this.onLayout} | |
enableEmptySections={true} | |
// not supported in Android - to fix this issue in Android, onKeyboardWillShow is called inside onKeyboardDidShow | |
onKeyboardWillShow={this.onKeyboardWillShow} | |
onKeyboardDidShow={this.onKeyboardDidShow} | |
// not supported in Android - to fix this issue in Android, onKeyboardWillHide is called inside onKeyboardDidHide | |
onKeyboardWillHide={this.onKeyboardWillHide} | |
onKeyboardDidHide={this.onKeyboardDidHide} | |
/> | |
); | |
} | |
renderFooter() { | |
return( | |
<View onLayout={this.onFooterLayout}> | |
{this.renderTypingMessage()} | |
</View> | |
) | |
} | |
setTimer(status) { | |
this.setState({isTimerPaused: status}); | |
} | |
updateTimer(obj) { | |
this.setState({timer: obj.time}); | |
} | |
renderMessageInput() { | |
return ( | |
<View style={styles.messageContainer}> | |
<TextInput | |
style={styles.textInput} | |
onChangeText={(message) => this.setState({message})} | |
value={this.state.message} | |
placeholder={'Escreva aqui...'} | |
onChangeText={this.onChangeText} | |
onFocus={() => this.setTimer(false)} | |
onBlur={() => this.setTimer(true)} | |
value={this.state.message} | |
autoFocus={false} | |
returnKeyType={'send'} | |
onSubmitEditing={this.onSendMessage} | |
enablesReturnKeyAutomatically={true} | |
blurOnSubmit={this.props.blurOnSubmit} | |
/> | |
<TouchableHighlight style={styles.btnWrapper} | |
onPress={() => alert('Sending message...')}> | |
<Text style={styles.button}>ENVIAR</Text> | |
</TouchableHighlight> | |
</View> | |
) | |
} | |
// <View style={[styles.container, { bottom: this.state.bottom }]}> | |
render() { | |
return ( | |
<View style={[styles.container]}> | |
<ChatNavBar | |
user={{name: 'Alberto Alcantara', avatar: null}} | |
isOnline={true} | |
/> | |
<ChatAlertBar | |
visible={this.props.showAlertBar} | |
message={"Seu tempo de chat é contado a partir do momento em que começa a escrever, as respostas do guia não são contabilizadas do seu tempo."} | |
/> | |
<ChatTimeBar | |
ref={(component) => this._timerBar = component } | |
time={"0:00"} | |
paused={this.state.isTimerPaused} | |
retrieveTimer={this.updateTimer} | |
/> | |
{this.renderMessagesListView()} | |
{this.renderMessageInput()} | |
</View> | |
); | |
} | |
} | |
Chat.defaultProps = { | |
showAlertBar: false, | |
}; | |
Chat.propTypes = { | |
showAlertBar: React.PropTypes.boolean, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment