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