UNPKG

12.7 kBJavaScriptView Raw
1import PropTypes from 'prop-types';
2import React from 'react';
3import { Text, Clipboard, StyleSheet, TouchableWithoutFeedback, View, } from 'react-native';
4import QuickReplies from './QuickReplies';
5import MessageText from './MessageText';
6import MessageImage from './MessageImage';
7import MessageVideo from './MessageVideo';
8import MessageAudio from './MessageAudio';
9import Time from './Time';
10import Color from './Color';
11import { StylePropType, isSameUser, isSameDay } from './utils';
12const styles = {
13 left: StyleSheet.create({
14 container: {
15 flex: 1,
16 alignItems: 'flex-start',
17 },
18 wrapper: {
19 borderRadius: 15,
20 backgroundColor: Color.leftBubbleBackground,
21 marginRight: 60,
22 minHeight: 20,
23 justifyContent: 'flex-end',
24 },
25 containerToNext: {
26 borderBottomLeftRadius: 3,
27 },
28 containerToPrevious: {
29 borderTopLeftRadius: 3,
30 },
31 bottom: {
32 flexDirection: 'row',
33 justifyContent: 'flex-start',
34 },
35 }),
36 right: StyleSheet.create({
37 container: {
38 flex: 1,
39 alignItems: 'flex-end',
40 },
41 wrapper: {
42 borderRadius: 15,
43 backgroundColor: Color.defaultBlue,
44 marginLeft: 60,
45 minHeight: 20,
46 justifyContent: 'flex-end',
47 },
48 containerToNext: {
49 borderBottomRightRadius: 3,
50 },
51 containerToPrevious: {
52 borderTopRightRadius: 3,
53 },
54 bottom: {
55 flexDirection: 'row',
56 justifyContent: 'flex-end',
57 },
58 }),
59 content: StyleSheet.create({
60 tick: {
61 fontSize: 10,
62 backgroundColor: Color.backgroundTransparent,
63 color: Color.white,
64 },
65 tickView: {
66 flexDirection: 'row',
67 marginRight: 10,
68 },
69 username: {
70 top: -3,
71 left: 0,
72 fontSize: 12,
73 backgroundColor: 'transparent',
74 color: '#aaa',
75 },
76 usernameView: {
77 flexDirection: 'row',
78 marginHorizontal: 10,
79 },
80 }),
81};
82const DEFAULT_OPTION_TITLES = ['Copy Text', 'Cancel'];
83export default class Bubble extends React.Component {
84 constructor() {
85 super(...arguments);
86 this.onLongPress = () => {
87 const { currentMessage } = this.props;
88 if (this.props.onLongPress) {
89 this.props.onLongPress(this.context, this.props.currentMessage);
90 }
91 else if (currentMessage && currentMessage.text) {
92 const { optionTitles } = this.props;
93 const options = optionTitles && optionTitles.length > 0
94 ? optionTitles.slice(0, 2)
95 : DEFAULT_OPTION_TITLES;
96 const cancelButtonIndex = options.length - 1;
97 this.context.actionSheet().showActionSheetWithOptions({
98 options,
99 cancelButtonIndex,
100 }, (buttonIndex) => {
101 switch (buttonIndex) {
102 case 0:
103 Clipboard.setString(currentMessage.text);
104 break;
105 default:
106 break;
107 }
108 });
109 }
110 };
111 }
112 styledBubbleToNext() {
113 const { currentMessage, nextMessage, position, containerToNextStyle, } = this.props;
114 if (currentMessage &&
115 nextMessage &&
116 position &&
117 isSameUser(currentMessage, nextMessage) &&
118 isSameDay(currentMessage, nextMessage)) {
119 return [
120 styles[position].containerToNext,
121 containerToNextStyle && containerToNextStyle[position],
122 ];
123 }
124 return null;
125 }
126 styledBubbleToPrevious() {
127 const { currentMessage, previousMessage, position, containerToPreviousStyle, } = this.props;
128 if (currentMessage &&
129 previousMessage &&
130 position &&
131 isSameUser(currentMessage, previousMessage) &&
132 isSameDay(currentMessage, previousMessage)) {
133 return [
134 styles[position].containerToPrevious,
135 containerToPreviousStyle && containerToPreviousStyle[position],
136 ];
137 }
138 return null;
139 }
140 renderQuickReplies() {
141 const { currentMessage, onQuickReply, nextMessage, renderQuickReplySend, quickReplyStyle, } = this.props;
142 if (currentMessage && currentMessage.quickReplies) {
143 const { containerStyle, wrapperStyle, ...quickReplyProps } = this.props;
144 if (this.props.renderQuickReplies) {
145 return this.props.renderQuickReplies(quickReplyProps);
146 }
147 return (<QuickReplies {...{
148 currentMessage,
149 onQuickReply,
150 nextMessage,
151 renderQuickReplySend,
152 quickReplyStyle,
153 }}/>);
154 }
155 return null;
156 }
157 renderMessageText() {
158 if (this.props.currentMessage && this.props.currentMessage.text) {
159 const { containerStyle, wrapperStyle, optionTitles, ...messageTextProps } = this.props;
160 if (this.props.renderMessageText) {
161 return this.props.renderMessageText(messageTextProps);
162 }
163 return <MessageText {...messageTextProps}/>;
164 }
165 return null;
166 }
167 renderMessageImage() {
168 if (this.props.currentMessage && this.props.currentMessage.image) {
169 const { containerStyle, wrapperStyle, ...messageImageProps } = this.props;
170 if (this.props.renderMessageImage) {
171 return this.props.renderMessageImage(messageImageProps);
172 }
173 return <MessageImage {...messageImageProps}/>;
174 }
175 return null;
176 }
177 renderMessageVideo() {
178 if (this.props.currentMessage && this.props.currentMessage.video) {
179 const { containerStyle, wrapperStyle, ...messageVideoProps } = this.props;
180 if (this.props.renderMessageVideo) {
181 return this.props.renderMessageVideo(messageVideoProps);
182 }
183 return <MessageVideo {...messageVideoProps}/>;
184 }
185 return null;
186 }
187 renderMessageAudio() {
188 if (this.props.currentMessage && this.props.currentMessage.audio) {
189 const { containerStyle, wrapperStyle, ...messageAudioProps } = this.props;
190 if (this.props.renderMessageAudio) {
191 return this.props.renderMessageAudio(messageAudioProps);
192 }
193 return <MessageAudio {...messageAudioProps}/>;
194 }
195 return null;
196 }
197 renderTicks() {
198 const { currentMessage, renderTicks, user } = this.props;
199 if (renderTicks && currentMessage) {
200 return renderTicks(currentMessage);
201 }
202 if (currentMessage &&
203 user &&
204 currentMessage.user &&
205 currentMessage.user._id !== user._id) {
206 return null;
207 }
208 if (currentMessage &&
209 (currentMessage.sent || currentMessage.received || currentMessage.pending)) {
210 return (<View style={styles.content.tickView}>
211 {!!currentMessage.sent && (<Text style={[styles.content.tick, this.props.tickStyle]}>✓</Text>)}
212 {!!currentMessage.received && (<Text style={[styles.content.tick, this.props.tickStyle]}>✓</Text>)}
213 {!!currentMessage.pending && (<Text style={[styles.content.tick, this.props.tickStyle]}>🕓</Text>)}
214 </View>);
215 }
216 return null;
217 }
218 renderTime() {
219 if (this.props.currentMessage && this.props.currentMessage.createdAt) {
220 const { containerStyle, wrapperStyle, textStyle, ...timeProps } = this.props;
221 if (this.props.renderTime) {
222 return this.props.renderTime(timeProps);
223 }
224 return <Time {...timeProps}/>;
225 }
226 return null;
227 }
228 renderUsername() {
229 const { currentMessage, user } = this.props;
230 if (this.props.renderUsernameOnMessage && currentMessage) {
231 if (user && currentMessage.user._id === user._id) {
232 return null;
233 }
234 return (<View style={styles.content.usernameView}>
235 <Text style={[styles.content.username, this.props.usernameStyle]}>
236 ~ {currentMessage.user.name}
237 </Text>
238 </View>);
239 }
240 return null;
241 }
242 renderCustomView() {
243 if (this.props.renderCustomView) {
244 return this.props.renderCustomView(this.props);
245 }
246 return null;
247 }
248 renderBubbleContent() {
249 return this.props.isCustomViewBottom ? (<View>
250 {this.renderMessageImage()}
251 {this.renderMessageVideo()}
252 {this.renderMessageAudio()}
253 {this.renderMessageText()}
254 {this.renderCustomView()}
255 </View>) : (<View>
256 {this.renderCustomView()}
257 {this.renderMessageImage()}
258 {this.renderMessageVideo()}
259 {this.renderMessageAudio()}
260 {this.renderMessageText()}
261 </View>);
262 }
263 render() {
264 const { position, containerStyle, wrapperStyle, bottomContainerStyle, } = this.props;
265 return (<View style={[
266 styles[position].container,
267 containerStyle && containerStyle[position],
268 ]}>
269 <View style={[
270 styles[position].wrapper,
271 this.styledBubbleToNext(),
272 this.styledBubbleToPrevious(),
273 wrapperStyle && wrapperStyle[position],
274 ]}>
275 <TouchableWithoutFeedback onLongPress={this.onLongPress} accessibilityTraits='text' {...this.props.touchableProps}>
276 <View>
277 {this.renderBubbleContent()}
278 <View style={[
279 styles[position].bottom,
280 bottomContainerStyle && bottomContainerStyle[position],
281 ]}>
282 {this.renderUsername()}
283 {this.renderTime()}
284 {this.renderTicks()}
285 </View>
286 </View>
287 </TouchableWithoutFeedback>
288 </View>
289 {this.renderQuickReplies()}
290 </View>);
291 }
292}
293Bubble.contextTypes = {
294 actionSheet: PropTypes.func,
295};
296Bubble.defaultProps = {
297 touchableProps: {},
298 onLongPress: null,
299 renderMessageImage: null,
300 renderMessageVideo: null,
301 renderMessageAudio: null,
302 renderMessageText: null,
303 renderCustomView: null,
304 renderUsername: null,
305 renderTicks: null,
306 renderTime: null,
307 renderQuickReplies: null,
308 onQuickReply: null,
309 position: 'left',
310 optionTitles: DEFAULT_OPTION_TITLES,
311 currentMessage: {
312 text: null,
313 createdAt: null,
314 image: null,
315 },
316 nextMessage: {},
317 previousMessage: {},
318 containerStyle: {},
319 wrapperStyle: {},
320 bottomContainerStyle: {},
321 tickStyle: {},
322 usernameStyle: {},
323 containerToNextStyle: {},
324 containerToPreviousStyle: {},
325};
326Bubble.propTypes = {
327 user: PropTypes.object.isRequired,
328 touchableProps: PropTypes.object,
329 onLongPress: PropTypes.func,
330 renderMessageImage: PropTypes.func,
331 renderMessageVideo: PropTypes.func,
332 renderMessageAudio: PropTypes.func,
333 renderMessageText: PropTypes.func,
334 renderCustomView: PropTypes.func,
335 isCustomViewBottom: PropTypes.bool,
336 renderUsernameOnMessage: PropTypes.bool,
337 renderUsername: PropTypes.func,
338 renderTime: PropTypes.func,
339 renderTicks: PropTypes.func,
340 renderQuickReplies: PropTypes.func,
341 onQuickReply: PropTypes.func,
342 position: PropTypes.oneOf(['left', 'right']),
343 optionTitles: PropTypes.arrayOf(PropTypes.string),
344 currentMessage: PropTypes.object,
345 nextMessage: PropTypes.object,
346 previousMessage: PropTypes.object,
347 containerStyle: PropTypes.shape({
348 left: StylePropType,
349 right: StylePropType,
350 }),
351 wrapperStyle: PropTypes.shape({
352 left: StylePropType,
353 right: StylePropType,
354 }),
355 bottomContainerStyle: PropTypes.shape({
356 left: StylePropType,
357 right: StylePropType,
358 }),
359 tickStyle: StylePropType,
360 usernameStyle: StylePropType,
361 containerToNextStyle: PropTypes.shape({
362 left: StylePropType,
363 right: StylePropType,
364 }),
365 containerToPreviousStyle: PropTypes.shape({
366 left: StylePropType,
367 right: StylePropType,
368 }),
369};
370//# sourceMappingURL=Bubble.js.map
\No newline at end of file