Skip to content

Instantly share code, notes, and snippets.

@nuxlli
Created June 30, 2016 19:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nuxlli/ba52f72c78ebf63d3a13791189a2e357 to your computer and use it in GitHub Desktop.
Save nuxlli/ba52f72c78ebf63d3a13791189a2e357 to your computer and use it in GitHub Desktop.
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