react-native-lifetime-livechat
Version:
LiveChat implementation for LifeTime application
374 lines (317 loc) • 10 kB
JavaScript
import React, {Component} from 'react';
import {
View, Dimensions, StyleSheet, Image, Platform, Text, TouchableOpacity, Keyboard, Animated, Easing
} from 'react-native';
import PropTypes from 'prop-types';
import moment from 'moment';
import {
PanGestureHandler, State,
} from 'react-native-gesture-handler';
import {init} from './sdk/livechat-visitor-sdk.min';
import {GiftedChat, Bubble} from './components/GiftedChat';
import InputField from "./components/InputField";
const {height, width} = Dimensions.get('window');
const sliderImage = require('./assets/slider.png');
const chatHeights = [height * 0.18, height * 0.4, height];
const animationTime = 300;
const initialMessage = [{
_id: '1', text: 'How can we help you today?', createdAt: new Date(), user: {
_id: 'LifeTime', name: 'LifeTime',
},
}];
function flatten(arr) {
return arr.reduce(function (flat, toFlatten) {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}
export default class LiveChat extends Component {
state = {
visitorSDK: null,
chatState: 0,
chatHeight: new Animated.Value(0),
keyboardHeight: new Animated.Value(0),
messages: []
};
componentWillUpdate(nextProps) {
const messages = flatten([initialMessage, ...this.extractMessagesFromChatData(nextProps.chats)]);
if (this.state.messages.length !== messages.length) {
this.setState({
messages,
});
}
}
componentDidMount() {
const {license, group, chats} = this.props;
if (Platform.OS === 'ios') {
this.keyboardShowListener = Keyboard.addListener('keyboardWillShow', this._keyboardDidShow);
this.keyboardHideListener = Keyboard.addListener('keyboardWillHide', this._keyboardDidHide);
} else {
this.keyboardShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
this.keyboardHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
}
GLOBAL.cookie = '';
const visitorSDK = init({
license, group
});
visitorSDK.on('new_message', this.handleNewMessage);
this.setState({
visitorSDK, messages: flatten([initialMessage, ...this.extractMessagesFromChatData(chats)])
});
this.animateChatHeight();
}
componentWillUnmount() {
this.keyboardShowListener.remove();
this.keyboardHideListener.remove();
this.state.visitorSDK.closeChat();
this.state.visitorSDK.destroy();
}
scrollToGivenChat = (chatId) => {
const {chats} = this.props;
let index = {
found: false,
index: 0
};
chats.forEach(data => {
if (!index.found) {
if (data.chat.id === chatId) {
index.found = true;
} else {
index.index += data.chat.messages.length
}
}
});
this.chat._messageContainerRef.scrollTo(index.index);
};
extractMessagesFromChatData = (chats) => {
return chats.map(chatData => {
const chat = chatData.chat || {};
const messages = chat.messages || [];
return messages.slice(0).reverse().map(message => ({
_id: Math.random().toString().substring(2),
text: message.text,
createdAt: message.timestamp,
user: {
_id: message.user_type === 'agent' ? 'LifeTime' : message.author_name,
name: message.user_type === 'agent' ? 'LifeTime' : message.author_name,
}
}));
})
};
animateChatHeight = () => {
const {chatHeight, chatState} = this.state;
Animated.timing(chatHeight, {
toValue: chatHeights[chatState], easing: Easing.out(Easing.quad), duration: animationTime,
}).start();
};
animateKeyboardBottom = (height) => {
const {keyboardHeight} = this.state;
Animated.timing(keyboardHeight, {
toValue: height, easing: Easing.out(Easing.quad), duration: animationTime,
}).start();
};
_keyboardDidShow = (e) => {
const {chatState} = this.state;
this.setState({
chatState: chatState === 0 ? 1 : chatState
}, () => {
this.animateChatHeight();
this.animateKeyboardBottom(e.endCoordinates.height)
})
};
_keyboardDidHide = () => {
this.animateKeyboardBottom(0)
};
onSendClicked = async (message) => {
const {visitorSDK, chatId, keyboardHeight, chatState} = this.state;
if (keyboardHeight._value > 0) {
Keyboard.dismiss();
}
if (chatState === 0) {
this.smallChat();
}
if (message) {
try {
this.addMessageToChat(message, 'User');
if (!chatId) {
const data = await visitorSDK.startChat();
this.setState({
chatId: data.chatId
});
}
} catch (e) {
console.log(e);
}
visitorSDK.sendMessage({
text: message, customId: Math.random().toString().substring(1),
});
}
};
addMessageToChat = (message, user) => {
this.setState({
chatId: message.chatId, messages: [{
text: message, _id: Math.random().toString().substring(1), createdAt: new Date(), user: {
_id: user, name: user,
}
}, ...this.state.messages],
});
};
handleNewMessage = (message) => {
if (message.id === 'welcome_message') return;
if (message.authorId.toString().indexOf('.') > -1) return;
this.setState({
messages: [{
text: message.text, _id: message.id.toString(), createdAt: message.timestamp, user: {
_id: 'LifeTime', name: 'LifeTime',
},
}, ...this.state.messages],
});
};
openChat = () => {
const {chatState} = this.state;
if (chatState === 0) {
this.setState({
chatState: 1
}, this.animateChatHeight);
}
};
makeChatFullScreen = () => {
this.setState({chatState: 2}, this.animateChatHeight);
};
closeChat = () => {
this.setState({chatState: 0}, this.animateChatHeight);
Keyboard.dismiss();
};
smallChat = () => {
this.setState({chatState: 1}, this.animateChatHeight);
};
onSliderClicked = () => {
const {chatState} = this.state;
if (chatState === 2) {
this.closeChat();
} else {
this.makeChatFullScreen();
}
};
renderBubble = props => {
return (
<Bubble
key={props.id}
{...props}
wrapperStyle={{
left: {backgroundColor: '#f2cca2'}, right: {backgroundColor: '#dce9d5'}
}}
textStyle={{
right: {color: '#444'}, left: {color: '#444'},
}}
/>
);
};
_onGestureEvent = (event) => {
const {chatHeight, keyboardHeight} = this.state;
chatHeight.setValue(height - event.nativeEvent.absoluteY + width / 20 - keyboardHeight._value);
};
_onHandlerStateChange = event => {
const {chatState} = this.state;
if (event.nativeEvent.state === State.BEGAN) {
this.dragStartY = event.nativeEvent.absoluteY;
} else if (event.nativeEvent.state === State.END) {
this.dragEndY = event.nativeEvent.absoluteY;
if (this.dragEndY - this.dragStartY < 0) {
this.makeChatFullScreen();
} else {
if (chatState === 2) {
this.smallChat();
} else {
this.closeChat(event);
}
}
}
};
render() {
const {chatState, messages, keyboardHeight, chatHeight} = this.state;
const {container, title, slider, gap, time} = styles;
const {onLoadEarlier, isLoadingEarlier, loadEarlier, scrollToBottom,
scrollToBottomOffset, onLoadAfter, isLoadingAfter, loadAfter, onJumpBackClicked} = this.props;
return (
<Animated.View style={[container, {
bottom: keyboardHeight, height: chatHeight,
}]}>
{chatState > 0 && <PanGestureHandler
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}
>
<View>
<TouchableOpacity onPress={this.onSliderClicked}>
<Image
source={sliderImage}
style={slider}
/>
</TouchableOpacity>
</View>
</PanGestureHandler>}
{chatState === 0 && <Text style={title}>
Lifetime Chat (Available 9a - 9p PST)
</Text>}
<GiftedChat
renderAvatar={null}
messages={chatState > 0 ? messages : []}
ref={ref => this.chat = ref}
renderBubble={this.renderBubble}
scrollToBottom={scrollToBottom}
scrollToBottomOffset={scrollToBottomOffset}
loadEarlier={loadEarlier}
onLoadEarlier={onLoadEarlier}
isLoadingEarlier={isLoadingEarlier}
onLoadAfter={onLoadAfter}
isLoadingAfter={isLoadingAfter}
loadAfter={loadAfter}
onJumpBackClicked={onJumpBackClicked}
renderDay={day => (
<Text style={time}>
{moment(day.currentMessage.createdAt)
.format(day.timeFormat)}
</Text>
)}
renderInputToolbar={() => (
<InputField
chatState={chatState}
onSend={this.onSendClicked}
openChat={this.openChat}
/>
)}
onSend={messages => this.onSend(messages)}
user={{
_id: "LifeTime",
}}
/>
<View style={gap}/>
</Animated.View>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#9dc284',
position: 'absolute',
left: 0,
right: 0,
width: '100%',
zIndex: 999999
}, title: {
color: 'white', fontSize: 16, padding: 14,
}, slider: {
alignSelf: 'center', marginTop: 24, width: width / 7, height: width / 11,
}, gap: {
height: 25,
}, time: {
color: 'white', width: '100%', textAlign: 'center', fontSize: 12, paddingVertical: 10,
}
});
LiveChat.propTypes = {
license: PropTypes.string.isRequired,
chats: PropTypes.array.isRequired,
onLoadEarlier: PropTypes.func.isRequired
};
LiveChat.defaultProps = {
group: 0, chats: []
};