-
Notifications
You must be signed in to change notification settings - Fork 8
/
ChatView.js
265 lines (216 loc) · 8.11 KB
/
ChatView.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
import React, { Component } from 'react';
import { Text, View, StyleSheet, ScrollView, KeyboardAvoidingView, TextInput, TouchableHighlight, Keyboard } from 'react-native';
import KeyboardSpacer from 'react-native-keyboard-spacer';
import AutogrowInput from 'react-native-autogrow-input';
//used to make random-sized messages
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// The actual chat view itself- a ScrollView of BubbleMessages, with an InputBar at the bottom, which moves with the keyboard
export default class ChatView extends Component {
constructor(props) {
super(props);
var loremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras ac orci augue. Sed fringilla nec magna id hendrerit. Proin posuere, tortor ut dignissim consequat, ante nibh ultrices tellus, in facilisis nunc nibh rutrum nibh.';
//create a set number of texts with random lengths. Also randomly put them on the right (user) or left (other person).
var numberOfMessages = 20;
var messages = [];
for(var i = 0; i < numberOfMessages; i++) {
var messageLength = getRandomInt(10, 120);
var direction = getRandomInt(1, 2) === 1 ? 'right' : 'left';
message = loremIpsum.substring(0, messageLength);
messages.push({
direction: direction,
text: message
})
}
this.state = {
messages: messages,
inputBarText: ''
}
}
static navigationOptions = {
title: 'Chat',
};
//fun keyboard stuff- we use these to get the end of the ScrollView to "follow" the top of the InputBar as the keyboard rises and falls
componentWillMount () {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide.bind(this));
}
componentWillUnmount() {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
//When the keyboard appears, this gets the ScrollView to move the end back "up" so the last message is visible with the keyboard up
//Without this, whatever message is the keyboard's height from the bottom will look like the last message.
keyboardDidShow (e) {
this.scrollView.scrollToEnd();
}
//When the keyboard dissapears, this gets the ScrollView to move the last message back down.
keyboardDidHide (e) {
this.scrollView.scrollToEnd();
}
//scroll to bottom when first showing the view
componentDidMount() {
setTimeout(function() {
this.scrollView.scrollToEnd();
}.bind(this))
}
//this is a bit sloppy: this is to make sure it scrolls to the bottom when a message is added, but
//the component could update for other reasons, for which we wouldn't want it to scroll to the bottom.
componentDidUpdate() {
setTimeout(function() {
this.scrollView.scrollToEnd();
}.bind(this))
}
_sendMessage() {
this.state.messages.push({direction: "right", text: this.state.inputBarText});
this.setState({
messages: this.state.messages,
inputBarText: ''
});
}
_onChangeInputBarText(text) {
this.setState({
inputBarText: text
});
}
//This event fires way too often.
//We need to move the last message up if the input bar expands due to the user's new message exceeding the height of the box.
//We really only need to do anything when the height of the InputBar changes, but AutogrowInput can't tell us that.
//The real solution here is probably a fork of AutogrowInput that can provide this information.
_onInputSizeChange() {
setTimeout(function() {
this.scrollView.scrollToEnd({animated: false});
}.bind(this))
}
render() {
var messages = [];
this.state.messages.forEach(function(message, index) {
messages.push(
<MessageBubble key={index} direction={message.direction} text={message.text}/>
);
});
return (
<View style={styles.outer}>
<ScrollView ref={(ref) => { this.scrollView = ref }} style={styles.messages}>
{messages}
</ScrollView>
<InputBar onSendPressed={() => this._sendMessage()}
onSizeChange={() => this._onInputSizeChange()}
onChangeText={(text) => this._onChangeInputBarText(text)}
text={this.state.inputBarText}/>
<KeyboardSpacer/>
</View>
);
}
}
//The bubbles that appear on the left or the right for the messages.
class MessageBubble extends Component {
render() {
//These spacers make the message bubble stay to the left or the right, depending on who is speaking, even if the message is multiple lines.
var leftSpacer = this.props.direction === 'left' ? null : <View style={{width: 70}}/>;
var rightSpacer = this.props.direction === 'left' ? <View style={{width: 70}}/> : null;
var bubbleStyles = this.props.direction === 'left' ? [styles.messageBubble, styles.messageBubbleLeft] : [styles.messageBubble, styles.messageBubbleRight];
var bubbleTextStyle = this.props.direction === 'left' ? styles.messageBubbleTextLeft : styles.messageBubbleTextRight;
return (
<View style={{justifyContent: 'space-between', flexDirection: 'row'}}>
{leftSpacer}
<View style={bubbleStyles}>
<Text style={bubbleTextStyle}>
{this.props.text}
</Text>
</View>
{rightSpacer}
</View>
);
}
}
//The bar at the bottom with a textbox and a send button.
class InputBar extends Component {
//AutogrowInput doesn't change its size when the text is changed from the outside.
//Thus, when text is reset to zero, we'll call it's reset function which will take it back to the original size.
//Another possible solution here would be if InputBar kept the text as state and only reported it when the Send button
//was pressed. Then, resetInputText() could be called when the Send button is pressed. However, this limits the ability
//of the InputBar's text to be set from the outside.
componentWillReceiveProps(nextProps) {
if(nextProps.text === '') {
this.autogrowInput.resetInputText();
}
}
render() {
return (
<View style={styles.inputBar}>
<AutogrowInput style={styles.textBox}
ref={(ref) => { this.autogrowInput = ref }}
multiline={true}
defaultHeight={30}
onChangeText={(text) => this.props.onChangeText(text)}
onContentSizeChange={this.props.onSizeChange}
value={this.props.text}/>
<TouchableHighlight style={styles.sendButton} onPress={() => this.props.onSendPressed()}>
<Text style={{color: 'white'}}>Send</Text>
</TouchableHighlight>
</View>
);
}
}
//TODO: separate these out. This is what happens when you're in a hurry!
const styles = StyleSheet.create({
//ChatView
outer: {
flex: 1,
flexDirection: 'column',
justifyContent: 'space-between',
backgroundColor: 'white'
},
messages: {
flex: 1
},
//InputBar
inputBar: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 5,
paddingVertical: 3,
},
textBox: {
borderRadius: 5,
borderWidth: 1,
borderColor: 'gray',
flex: 1,
fontSize: 16,
paddingHorizontal: 10
},
sendButton: {
justifyContent: 'center',
alignItems: 'center',
paddingLeft: 15,
marginLeft: 5,
paddingRight: 15,
borderRadius: 5,
backgroundColor: '#66db30'
},
//MessageBubble
messageBubble: {
borderRadius: 5,
marginTop: 8,
marginRight: 10,
marginLeft: 10,
paddingHorizontal: 10,
paddingVertical: 5,
flexDirection:'row',
flex: 1
},
messageBubbleLeft: {
backgroundColor: '#d5d8d4',
},
messageBubbleTextLeft: {
color: 'black'
},
messageBubbleRight: {
backgroundColor: '#66db30'
},
messageBubbleTextRight: {
color: 'white'
},
})