react-native-gifted-chat 2.4.1 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +1 -3
  2. package/lib/Actions.d.ts +5 -6
  3. package/lib/Actions.js +16 -13
  4. package/lib/Actions.js.map +1 -1
  5. package/lib/Avatar.d.ts +9 -25
  6. package/lib/Avatar.js +12 -18
  7. package/lib/Avatar.js.flow +1 -1
  8. package/lib/Avatar.js.map +1 -1
  9. package/lib/Bubble.d.ts +29 -29
  10. package/lib/Bubble.js +88 -70
  11. package/lib/Bubble.js.flow +1 -1
  12. package/lib/Bubble.js.map +1 -1
  13. package/lib/Composer.d.ts +1 -1
  14. package/lib/Composer.js +30 -32
  15. package/lib/Composer.js.map +1 -1
  16. package/lib/Constant.js +1 -0
  17. package/lib/Constant.js.map +1 -1
  18. package/lib/Day.d.ts +3 -15
  19. package/lib/Day.js +2 -14
  20. package/lib/Day.js.flow +1 -1
  21. package/lib/Day.js.map +1 -1
  22. package/lib/GiftedAvatar.d.ts +7 -7
  23. package/lib/GiftedAvatar.js +30 -29
  24. package/lib/GiftedAvatar.js.map +1 -1
  25. package/lib/GiftedChat.d.ts +15 -89
  26. package/lib/GiftedChat.js +204 -350
  27. package/lib/GiftedChat.js.flow +1 -3
  28. package/lib/GiftedChat.js.map +1 -1
  29. package/lib/GiftedChatContext.d.ts +2 -1
  30. package/lib/GiftedChatContext.js.map +1 -1
  31. package/lib/InputToolbar.d.ts +7 -5
  32. package/lib/InputToolbar.js +41 -34
  33. package/lib/InputToolbar.js.map +1 -1
  34. package/lib/LoadEarlier.d.ts +4 -4
  35. package/lib/LoadEarlier.js +8 -6
  36. package/lib/LoadEarlier.js.map +1 -1
  37. package/lib/Message.d.ts +8 -9
  38. package/lib/Message.js +47 -40
  39. package/lib/Message.js.flow +1 -1
  40. package/lib/Message.js.map +1 -1
  41. package/lib/MessageAudio.d.ts +2 -1
  42. package/lib/MessageAudio.js +4 -4
  43. package/lib/MessageAudio.js.flow +1 -1
  44. package/lib/MessageAudio.js.map +1 -1
  45. package/lib/MessageContainer.d.ts +17 -17
  46. package/lib/MessageContainer.js +33 -51
  47. package/lib/MessageContainer.js.map +1 -1
  48. package/lib/MessageImage.d.ts +5 -4
  49. package/lib/MessageImage.js +4 -5
  50. package/lib/MessageImage.js.flow +1 -1
  51. package/lib/MessageImage.js.map +1 -1
  52. package/lib/MessageText.d.ts +11 -10
  53. package/lib/MessageText.js +5 -10
  54. package/lib/MessageText.js.flow +1 -1
  55. package/lib/MessageText.js.map +1 -1
  56. package/lib/MessageVideo.d.ts +2 -1
  57. package/lib/MessageVideo.js +4 -4
  58. package/lib/MessageVideo.js.flow +1 -1
  59. package/lib/MessageVideo.js.map +1 -1
  60. package/lib/Models.d.ts +7 -7
  61. package/lib/QuickReplies.d.ts +3 -3
  62. package/lib/QuickReplies.js +8 -14
  63. package/lib/QuickReplies.js.flow +1 -1
  64. package/lib/QuickReplies.js.map +1 -1
  65. package/lib/Send.d.ts +4 -4
  66. package/lib/Send.js +6 -9
  67. package/lib/Send.js.map +1 -1
  68. package/lib/SystemMessage.d.ts +6 -5
  69. package/lib/SystemMessage.js +1 -2
  70. package/lib/SystemMessage.js.flow +1 -1
  71. package/lib/SystemMessage.js.map +1 -1
  72. package/lib/Time.d.ts +7 -6
  73. package/lib/Time.js +1 -3
  74. package/lib/Time.js.flow +1 -1
  75. package/lib/Time.js.map +1 -1
  76. package/lib/TypingIndicator.d.ts +2 -1
  77. package/lib/TypingIndicator.js +5 -5
  78. package/lib/TypingIndicator.js.map +1 -1
  79. package/lib/hooks/useUpdateLayoutEffect.js +2 -4
  80. package/lib/hooks/useUpdateLayoutEffect.js.map +1 -1
  81. package/lib/index.d.ts +1 -0
  82. package/lib/index.js +1 -0
  83. package/lib/index.js.map +1 -1
  84. package/lib/logging.d.ts +2 -2
  85. package/lib/logging.js.map +1 -1
  86. package/lib/types.js.flow +1 -1
  87. package/lib/utils.d.ts +1 -1
  88. package/lib/utils.js +2 -4
  89. package/lib/utils.js.map +1 -1
  90. package/package.json +83 -53
  91. package/src/Actions.tsx +114 -0
  92. package/src/Avatar.tsx +178 -0
  93. package/src/Bubble.tsx +601 -0
  94. package/src/Color.ts +17 -0
  95. package/src/Composer.tsx +147 -0
  96. package/src/Constant.ts +18 -0
  97. package/src/Day.tsx +71 -0
  98. package/src/GiftedAvatar.tsx +205 -0
  99. package/src/GiftedChat.tsx +670 -0
  100. package/src/GiftedChatContext.ts +23 -0
  101. package/src/InputToolbar.tsx +113 -0
  102. package/src/LoadEarlier.tsx +108 -0
  103. package/src/Message.tsx +229 -0
  104. package/src/MessageAudio.tsx +19 -0
  105. package/src/MessageContainer.tsx +362 -0
  106. package/src/MessageImage.tsx +78 -0
  107. package/src/MessageText.tsx +187 -0
  108. package/src/MessageVideo.tsx +19 -0
  109. package/src/Models.ts +84 -0
  110. package/src/QuickReplies.tsx +186 -0
  111. package/src/Send.tsx +102 -0
  112. package/src/SystemMessage.tsx +61 -0
  113. package/src/Time.tsx +97 -0
  114. package/src/TypingIndicator.tsx +108 -0
  115. package/src/__tests__/Actions.test.tsx +10 -0
  116. package/src/__tests__/Avatar.test.tsx +13 -0
  117. package/src/__tests__/Bubble.test.tsx +23 -0
  118. package/src/__tests__/Color.test.tsx +5 -0
  119. package/src/__tests__/Composer.test.tsx +11 -0
  120. package/src/__tests__/Constant.test.tsx +5 -0
  121. package/src/__tests__/Day.test.tsx +23 -0
  122. package/src/__tests__/GiftedAvatar.test.tsx +11 -0
  123. package/src/__tests__/GiftedChat.test.tsx +36 -0
  124. package/src/__tests__/InputToolbar.test.tsx +11 -0
  125. package/src/__tests__/LoadEarlier.test.tsx +11 -0
  126. package/src/__tests__/Message.test.tsx +77 -0
  127. package/src/__tests__/MessageContainer.test.tsx +11 -0
  128. package/src/__tests__/MessageImage.test.tsx +27 -0
  129. package/src/__tests__/MessageText.test.tsx +11 -0
  130. package/src/__tests__/Send.test.tsx +22 -0
  131. package/src/__tests__/SystemMessage.test.tsx +27 -0
  132. package/src/__tests__/Time.test.tsx +29 -0
  133. package/src/__tests__/__snapshots__/Actions.test.tsx.snap +76 -0
  134. package/src/__tests__/__snapshots__/Avatar.test.tsx.snap +17 -0
  135. package/src/__tests__/__snapshots__/Bubble.test.tsx.snap +145 -0
  136. package/src/__tests__/__snapshots__/Color.test.tsx.snap +21 -0
  137. package/src/__tests__/__snapshots__/Composer.test.tsx.snap +35 -0
  138. package/src/__tests__/__snapshots__/Constant.test.tsx.snap +16 -0
  139. package/src/__tests__/__snapshots__/Day.test.tsx.snap +37 -0
  140. package/src/__tests__/__snapshots__/GiftedAvatar.test.tsx.snap +22 -0
  141. package/src/__tests__/__snapshots__/GiftedChat.test.tsx.snap +15 -0
  142. package/src/__tests__/__snapshots__/InputToolbar.test.tsx.snap +60 -0
  143. package/src/__tests__/__snapshots__/LoadEarlier.test.tsx.snap +74 -0
  144. package/src/__tests__/__snapshots__/Message.test.tsx.snap +628 -0
  145. package/src/__tests__/__snapshots__/MessageContainer.test.tsx.snap +127 -0
  146. package/src/__tests__/__snapshots__/MessageImage.test.tsx.snap +38 -0
  147. package/src/__tests__/__snapshots__/MessageText.test.tsx.snap +30 -0
  148. package/src/__tests__/__snapshots__/Send.test.tsx.snap +129 -0
  149. package/src/__tests__/__snapshots__/SystemMessage.test.tsx.snap +38 -0
  150. package/src/__tests__/__snapshots__/Time.test.tsx.snap +33 -0
  151. package/src/__tests__/data.ts +8 -0
  152. package/src/__tests__/utils.test.ts +31 -0
  153. package/src/hooks/useUpdateLayoutEffect.ts +21 -0
  154. package/src/index.ts +4 -0
  155. package/src/logging.ts +8 -0
  156. package/src/utils.ts +39 -0
  157. package/.eslintignore +0 -2
  158. package/.eslintrc.js +0 -21
  159. package/jest.config.js +0 -15
@@ -0,0 +1,362 @@
1
+ import React, { RefObject } from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import {
5
+ FlatList,
6
+ View,
7
+ StyleSheet,
8
+ TouchableOpacity,
9
+ Text,
10
+ ListRenderItemInfo,
11
+ NativeSyntheticEvent,
12
+ NativeScrollEvent,
13
+ StyleProp,
14
+ ViewStyle,
15
+ Platform,
16
+ } from 'react-native'
17
+
18
+ import { LoadEarlier, LoadEarlierProps } from './LoadEarlier'
19
+ import Message from './Message'
20
+ import Color from './Color'
21
+ import { User, IMessage, Reply } from './Models'
22
+ import TypingIndicator from './TypingIndicator'
23
+
24
+ import { StylePropType } from './utils'
25
+ import { warning } from './logging'
26
+
27
+ const styles = StyleSheet.create({
28
+ container: {
29
+ flex: 1,
30
+ },
31
+ containerAlignTop: {
32
+ flexDirection: 'row',
33
+ alignItems: 'flex-start',
34
+ },
35
+ contentContainerStyle: {
36
+ flexGrow: 1,
37
+ justifyContent: 'flex-start',
38
+ },
39
+ emptyChatContainer: {
40
+ flex: 1,
41
+ transform: [{ scaleY: -1 }],
42
+ },
43
+ headerWrapper: {
44
+ flex: 1,
45
+ },
46
+ listStyle: {
47
+ flex: 1,
48
+ },
49
+ scrollToBottomStyle: {
50
+ opacity: 0.8,
51
+ position: 'absolute',
52
+ right: 10,
53
+ bottom: 30,
54
+ zIndex: 999,
55
+ height: 40,
56
+ width: 40,
57
+ borderRadius: 20,
58
+ backgroundColor: Color.white,
59
+ alignItems: 'center',
60
+ justifyContent: 'center',
61
+ shadowColor: Color.black,
62
+ shadowOpacity: 0.5,
63
+ shadowOffset: { width: 0, height: 0 },
64
+ shadowRadius: 1,
65
+ },
66
+ })
67
+
68
+ export interface MessageContainerProps<TMessage extends IMessage> {
69
+ messages?: TMessage[]
70
+ isTyping?: boolean
71
+ user?: User
72
+ listViewProps: object
73
+ inverted?: boolean
74
+ loadEarlier?: boolean
75
+ alignTop?: boolean
76
+ scrollToBottom?: boolean
77
+ scrollToBottomStyle?: StyleProp<ViewStyle>
78
+ invertibleScrollViewProps?: object
79
+ extraData?: object
80
+ scrollToBottomOffset?: number
81
+ forwardRef?: RefObject<FlatList<TMessage>>
82
+ renderChatEmpty?(): React.ReactNode
83
+ renderFooter?(props: MessageContainerProps<TMessage>): React.ReactNode
84
+ renderMessage?(props: Message['props']): React.ReactElement
85
+ renderLoadEarlier?(props: LoadEarlierProps): React.ReactNode
86
+ scrollToBottomComponent?(): React.ReactNode
87
+ onLoadEarlier?(): void
88
+ onQuickReply?(replies: Reply[]): void
89
+ infiniteScroll?: boolean
90
+ isLoadingEarlier?: boolean
91
+ }
92
+
93
+ interface State {
94
+ showScrollBottom: boolean
95
+ hasScrolled: boolean
96
+ }
97
+
98
+ export default class MessageContainer<
99
+ TMessage extends IMessage = IMessage,
100
+ > extends React.PureComponent<MessageContainerProps<TMessage>, State> {
101
+ static defaultProps = {
102
+ messages: [],
103
+ user: {},
104
+ isTyping: false,
105
+ renderChatEmpty: null,
106
+ renderFooter: null,
107
+ renderMessage: null,
108
+ onLoadEarlier: () => {},
109
+ onQuickReply: () => {},
110
+ inverted: true,
111
+ loadEarlier: false,
112
+ listViewProps: {},
113
+ invertibleScrollViewProps: {},
114
+ extraData: null,
115
+ scrollToBottom: false,
116
+ scrollToBottomOffset: 200,
117
+ alignTop: false,
118
+ scrollToBottomStyle: {},
119
+ infiniteScroll: false,
120
+ isLoadingEarlier: false,
121
+ }
122
+
123
+ static propTypes = {
124
+ messages: PropTypes.arrayOf(PropTypes.object),
125
+ isTyping: PropTypes.bool,
126
+ user: PropTypes.object,
127
+ renderChatEmpty: PropTypes.func,
128
+ renderFooter: PropTypes.func,
129
+ renderMessage: PropTypes.func,
130
+ renderLoadEarlier: PropTypes.func,
131
+ onLoadEarlier: PropTypes.func,
132
+ listViewProps: PropTypes.object,
133
+ inverted: PropTypes.bool,
134
+ loadEarlier: PropTypes.bool,
135
+ invertibleScrollViewProps: PropTypes.object,
136
+ extraData: PropTypes.object,
137
+ scrollToBottom: PropTypes.bool,
138
+ scrollToBottomOffset: PropTypes.number,
139
+ scrollToBottomComponent: PropTypes.func,
140
+ alignTop: PropTypes.bool,
141
+ scrollToBottomStyle: StylePropType,
142
+ infiniteScroll: PropTypes.bool,
143
+ }
144
+
145
+ state = {
146
+ showScrollBottom: false,
147
+ hasScrolled: false,
148
+ }
149
+
150
+ renderTypingIndicator = () => {
151
+ if (Platform.OS === 'web')
152
+ return null
153
+
154
+ return <TypingIndicator isTyping={this.props.isTyping || false} />
155
+ }
156
+
157
+ renderFooter = () => {
158
+ if (this.props.renderFooter)
159
+ return this.props.renderFooter(this.props)
160
+
161
+ return this.renderTypingIndicator()
162
+ }
163
+
164
+ renderLoadEarlier = () => {
165
+ if (this.props.loadEarlier === true) {
166
+ const loadEarlierProps = {
167
+ ...this.props,
168
+ }
169
+ if (this.props.renderLoadEarlier)
170
+ return this.props.renderLoadEarlier(loadEarlierProps)
171
+
172
+ return <LoadEarlier {...loadEarlierProps} />
173
+ }
174
+ return null
175
+ }
176
+
177
+ scrollTo (options: { animated?: boolean, offset: number }) {
178
+ if (this.props.forwardRef?.current && options)
179
+ this.props.forwardRef.current.scrollToOffset(options)
180
+ }
181
+
182
+ scrollToBottom = (animated: boolean = true) => {
183
+ const { inverted } = this.props
184
+ if (inverted)
185
+ this.scrollTo({ offset: 0, animated })
186
+ else if (this.props.forwardRef?.current)
187
+ this.props.forwardRef.current.scrollToEnd({ animated })
188
+ }
189
+
190
+ handleOnScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
191
+ const {
192
+ nativeEvent: {
193
+ contentOffset: { y: contentOffsetY },
194
+ contentSize: { height: contentSizeHeight },
195
+ layoutMeasurement: { height: layoutMeasurementHeight },
196
+ },
197
+ } = event
198
+ const { scrollToBottomOffset } = this.props
199
+ if (this.props.inverted)
200
+ if (contentOffsetY > scrollToBottomOffset!)
201
+ this.setState({ showScrollBottom: true, hasScrolled: true })
202
+ else
203
+ this.setState({ showScrollBottom: false, hasScrolled: true })
204
+ else if (
205
+ contentOffsetY < scrollToBottomOffset! &&
206
+ contentSizeHeight - layoutMeasurementHeight > scrollToBottomOffset!
207
+ )
208
+ this.setState({ showScrollBottom: true, hasScrolled: true })
209
+ else
210
+ this.setState({ showScrollBottom: false, hasScrolled: true })
211
+ }
212
+
213
+ renderRow = ({ item, index }: ListRenderItemInfo<TMessage>): React.ReactElement | null => {
214
+ if (!item._id && item._id !== 0)
215
+ warning('GiftedChat: `_id` is missing for message', JSON.stringify(item))
216
+
217
+ if (!item.user) {
218
+ if (!item.system)
219
+ warning(
220
+ 'GiftedChat: `user` is missing for message',
221
+ JSON.stringify(item)
222
+ )
223
+
224
+ item.user = { _id: 0 }
225
+ }
226
+ const { messages, user, inverted, ...restProps } = this.props
227
+ if (messages && user) {
228
+ const previousMessage =
229
+ (inverted ? messages[index + 1] : messages[index - 1]) || {}
230
+ const nextMessage =
231
+ (inverted ? messages[index - 1] : messages[index + 1]) || {}
232
+
233
+ const messageProps: Message['props'] = {
234
+ ...restProps,
235
+ user,
236
+ currentMessage: item,
237
+ previousMessage,
238
+ inverted,
239
+ nextMessage,
240
+ position: item.user._id === user._id ? 'right' : 'left',
241
+ }
242
+
243
+ if (this.props.renderMessage)
244
+ return this.props.renderMessage(messageProps)
245
+
246
+ return <Message key={item._id.toString()} {...messageProps} />
247
+ }
248
+ return null
249
+ }
250
+
251
+ renderChatEmpty = () => {
252
+ if (this.props.renderChatEmpty)
253
+ return this.props.inverted
254
+ ? (
255
+ this.props.renderChatEmpty()
256
+ )
257
+ : (
258
+ <View style={styles.emptyChatContainer}>
259
+ {this.props.renderChatEmpty()}
260
+ </View>
261
+ )
262
+
263
+ return <View style={styles.container} />
264
+ }
265
+
266
+ renderHeaderWrapper = () => (
267
+ <View style={styles.headerWrapper}>{this.renderLoadEarlier()}</View>
268
+ )
269
+
270
+ renderScrollBottomComponent () {
271
+ const { scrollToBottomComponent } = this.props
272
+
273
+ if (scrollToBottomComponent)
274
+ return scrollToBottomComponent()
275
+
276
+ return <Text>{'V'}</Text>
277
+ }
278
+
279
+ renderScrollToBottomWrapper () {
280
+ const propsStyle = this.props.scrollToBottomStyle || {}
281
+ return (
282
+ <View style={[styles.scrollToBottomStyle, propsStyle]}>
283
+ <TouchableOpacity
284
+ onPress={() => this.scrollToBottom()}
285
+ hitSlop={{ top: 5, left: 5, right: 5, bottom: 5 }}
286
+ >
287
+ {this.renderScrollBottomComponent()}
288
+ </TouchableOpacity>
289
+ </View>
290
+ )
291
+ }
292
+
293
+ onLayoutList = () => {
294
+ if (
295
+ !this.props.inverted &&
296
+ !!this.props.messages &&
297
+ this.props.messages!.length
298
+ )
299
+ setTimeout(
300
+ () => this.scrollToBottom && this.scrollToBottom(false),
301
+ 15 * this.props.messages!.length
302
+ )
303
+ }
304
+
305
+ onEndReached = ({ distanceFromEnd }: { distanceFromEnd: number }) => {
306
+ const { loadEarlier, onLoadEarlier, infiniteScroll, isLoadingEarlier } =
307
+ this.props
308
+ if (
309
+ infiniteScroll &&
310
+ (this.state.hasScrolled || distanceFromEnd > 0) &&
311
+ distanceFromEnd <= 100 &&
312
+ loadEarlier &&
313
+ onLoadEarlier &&
314
+ !isLoadingEarlier &&
315
+ Platform.OS !== 'web'
316
+ )
317
+ onLoadEarlier()
318
+ }
319
+
320
+ keyExtractor = (item: TMessage) => `${item._id}`
321
+
322
+ render () {
323
+ const { inverted } = this.props
324
+
325
+ return (
326
+ <View
327
+ style={
328
+ this.props.alignTop ? styles.containerAlignTop : styles.container
329
+ }
330
+ >
331
+ <FlatList
332
+ ref={this.props.forwardRef}
333
+ extraData={[this.props.extraData, this.props.isTyping]}
334
+ keyExtractor={this.keyExtractor}
335
+ automaticallyAdjustContentInsets={false}
336
+ inverted={inverted}
337
+ data={this.props.messages}
338
+ style={styles.listStyle}
339
+ contentContainerStyle={styles.contentContainerStyle}
340
+ renderItem={this.renderRow}
341
+ {...this.props.invertibleScrollViewProps}
342
+ ListEmptyComponent={this.renderChatEmpty}
343
+ ListFooterComponent={
344
+ inverted ? this.renderHeaderWrapper : this.renderFooter
345
+ }
346
+ ListHeaderComponent={
347
+ inverted ? this.renderFooter : this.renderHeaderWrapper
348
+ }
349
+ onScroll={this.handleOnScroll}
350
+ scrollEventThrottle={100}
351
+ onLayout={this.onLayoutList}
352
+ onEndReached={this.onEndReached}
353
+ onEndReachedThreshold={0.1}
354
+ {...this.props.listViewProps}
355
+ />
356
+ {this.state.showScrollBottom && this.props.scrollToBottom
357
+ ? this.renderScrollToBottomWrapper()
358
+ : null}
359
+ </View>
360
+ )
361
+ }
362
+ }
@@ -0,0 +1,78 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+ import {
4
+ Image,
5
+ StyleSheet,
6
+ View,
7
+ ImageProps,
8
+ ViewStyle,
9
+ StyleProp,
10
+ ImageStyle,
11
+ ImageURISource,
12
+ } from 'react-native'
13
+ // TODO: support web
14
+ import Lightbox, { LightboxProps } from 'react-native-lightbox-v2'
15
+ import { IMessage } from './Models'
16
+ import { StylePropType } from './utils'
17
+
18
+ const styles = StyleSheet.create({
19
+ image: {
20
+ width: 150,
21
+ height: 100,
22
+ borderRadius: 13,
23
+ margin: 3,
24
+ resizeMode: 'cover',
25
+ },
26
+ imageActive: {
27
+ flex: 1,
28
+ resizeMode: 'contain',
29
+ },
30
+ })
31
+
32
+ export interface MessageImageProps<TMessage extends IMessage> {
33
+ currentMessage: TMessage
34
+ containerStyle?: StyleProp<ViewStyle>
35
+ imageSourceProps?: Partial<ImageURISource>
36
+ imageStyle?: StyleProp<ImageStyle>
37
+ imageProps?: Partial<ImageProps>
38
+ lightboxProps?: LightboxProps
39
+ }
40
+
41
+ export function MessageImage<TMessage extends IMessage = IMessage> ({
42
+ containerStyle,
43
+ lightboxProps,
44
+ imageProps,
45
+ imageSourceProps,
46
+ imageStyle,
47
+ currentMessage,
48
+ }: MessageImageProps<TMessage>) {
49
+ if (currentMessage == null)
50
+ return null
51
+
52
+ return (
53
+ <View style={containerStyle}>
54
+ {/* @ts-expect-error: Lightbox types are not fully compatible */}
55
+ <Lightbox
56
+ activeProps={{
57
+ style: styles.imageActive,
58
+ }}
59
+ {...lightboxProps}
60
+ >
61
+ <Image
62
+ {...imageProps}
63
+ style={[styles.image, imageStyle]}
64
+ source={{ ...imageSourceProps, uri: currentMessage.image }}
65
+ />
66
+ </Lightbox>
67
+ </View>
68
+ )
69
+ }
70
+
71
+ MessageImage.propTypes = {
72
+ currentMessage: PropTypes.object,
73
+ containerStyle: StylePropType,
74
+ imageSourceProps: PropTypes.object,
75
+ imageStyle: StylePropType,
76
+ imageProps: PropTypes.object,
77
+ lightboxProps: PropTypes.object,
78
+ }
@@ -0,0 +1,187 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+ import {
4
+ Linking,
5
+ StyleSheet,
6
+ View,
7
+ TextProps,
8
+ StyleProp,
9
+ ViewStyle,
10
+ TextStyle,
11
+ } from 'react-native'
12
+
13
+ import ParsedText from 'react-native-parsed-text'
14
+ import { LeftRightStyle, IMessage } from './Models'
15
+ import { StylePropType } from './utils'
16
+ import { useChatContext } from './GiftedChatContext'
17
+ import { error } from './logging'
18
+
19
+ const WWW_URL_PATTERN = /^www\./i
20
+
21
+ const { textStyle } = StyleSheet.create({
22
+ textStyle: {
23
+ fontSize: 16,
24
+ lineHeight: 20,
25
+ marginTop: 5,
26
+ marginBottom: 5,
27
+ marginLeft: 10,
28
+ marginRight: 10,
29
+ },
30
+ })
31
+
32
+ const styles = {
33
+ left: StyleSheet.create({
34
+ container: {},
35
+ text: {
36
+ color: 'black',
37
+ ...textStyle,
38
+ },
39
+ link: {
40
+ color: 'black',
41
+ textDecorationLine: 'underline',
42
+ },
43
+ }),
44
+ right: StyleSheet.create({
45
+ container: {},
46
+ text: {
47
+ color: 'white',
48
+ ...textStyle,
49
+ },
50
+ link: {
51
+ color: 'white',
52
+ textDecorationLine: 'underline',
53
+ },
54
+ }),
55
+ }
56
+
57
+ const DEFAULT_OPTION_TITLES = ['Call', 'Text', 'Cancel']
58
+
59
+ export interface MessageTextProps<TMessage extends IMessage> {
60
+ position?: 'left' | 'right'
61
+ optionTitles?: string[]
62
+ currentMessage: TMessage
63
+ containerStyle?: LeftRightStyle<ViewStyle>
64
+ textStyle?: LeftRightStyle<TextStyle>
65
+ linkStyle?: LeftRightStyle<TextStyle>
66
+ textProps?: TextProps
67
+ customTextStyle?: StyleProp<TextStyle>
68
+ parsePatterns?: (linkStyle: TextStyle) => []
69
+ }
70
+
71
+ export function MessageText<TMessage extends IMessage = IMessage> ({
72
+ currentMessage = {} as TMessage,
73
+ optionTitles = DEFAULT_OPTION_TITLES,
74
+ position = 'left',
75
+ containerStyle,
76
+ textStyle,
77
+ linkStyle: linkStyleProp,
78
+ customTextStyle,
79
+ parsePatterns,
80
+ textProps,
81
+ }: MessageTextProps<TMessage>) {
82
+ const { actionSheet } = useChatContext()
83
+
84
+ // TODO: React.memo
85
+ // const shouldComponentUpdate = (nextProps: MessageTextProps<TMessage>) => {
86
+ // return (
87
+ // !!currentMessage &&
88
+ // !!nextProps.currentMessage &&
89
+ // currentMessage.text !== nextProps.currentMessage.text
90
+ // )
91
+ // }
92
+
93
+ const onUrlPress = (url: string) => {
94
+ // When someone sends a message that includes a website address beginning with "www." (omitting the scheme),
95
+ // react-native-parsed-text recognizes it as a valid url, but Linking fails to open due to the missing scheme.
96
+ if (WWW_URL_PATTERN.test(url))
97
+ onUrlPress(`https://${url}`)
98
+ else
99
+ Linking.openURL(url).catch(e => {
100
+ error(e, 'No handler for URL:', url)
101
+ })
102
+ }
103
+
104
+ const onPhonePress = (phone: string) => {
105
+ const options =
106
+ optionTitles && optionTitles.length > 0
107
+ ? optionTitles.slice(0, 3)
108
+ : DEFAULT_OPTION_TITLES
109
+ const cancelButtonIndex = options.length - 1
110
+ actionSheet().showActionSheetWithOptions(
111
+ {
112
+ options,
113
+ cancelButtonIndex,
114
+ },
115
+ (buttonIndex?: number) => {
116
+ switch (buttonIndex) {
117
+ case 0:
118
+ Linking.openURL(`tel:${phone}`).catch(e => {
119
+ error(e, 'No handler for telephone')
120
+ })
121
+ break
122
+ case 1:
123
+ Linking.openURL(`sms:${phone}`).catch(e => {
124
+ error(e, 'No handler for text')
125
+ })
126
+ break
127
+ }
128
+ }
129
+ )
130
+ }
131
+
132
+ const onEmailPress = (email: string) =>
133
+ Linking.openURL(`mailto:${email}`).catch(e =>
134
+ error(e, 'No handler for mailto')
135
+ )
136
+
137
+ const linkStyle = [
138
+ styles[position].link,
139
+ linkStyleProp?.[position],
140
+ ]
141
+ return (
142
+ <View
143
+ style={[
144
+ styles[position].container,
145
+ containerStyle && containerStyle[position],
146
+ ]}
147
+ >
148
+ <ParsedText
149
+ style={[
150
+ styles[position].text,
151
+ textStyle && textStyle[position],
152
+ customTextStyle,
153
+ ]}
154
+ parse={[
155
+ ...(parsePatterns ? parsePatterns(linkStyle as TextStyle) : []),
156
+ { type: 'url', style: linkStyle, onPress: onUrlPress },
157
+ { type: 'phone', style: linkStyle, onPress: onPhonePress },
158
+ { type: 'email', style: linkStyle, onPress: onEmailPress },
159
+ ]}
160
+ childrenProps={{ ...textProps }}
161
+ >
162
+ {currentMessage!.text}
163
+ </ParsedText>
164
+ </View>
165
+ )
166
+ }
167
+
168
+ MessageText.propTypes = {
169
+ position: PropTypes.oneOf(['left', 'right']),
170
+ optionTitles: PropTypes.arrayOf(PropTypes.string),
171
+ currentMessage: PropTypes.object,
172
+ containerStyle: PropTypes.shape({
173
+ left: StylePropType,
174
+ right: StylePropType,
175
+ }),
176
+ textStyle: PropTypes.shape({
177
+ left: StylePropType,
178
+ right: StylePropType,
179
+ }),
180
+ linkStyle: PropTypes.shape({
181
+ left: StylePropType,
182
+ right: StylePropType,
183
+ }),
184
+ parsePatterns: PropTypes.func,
185
+ textProps: PropTypes.object,
186
+ customTextStyle: StylePropType,
187
+ }
@@ -0,0 +1,19 @@
1
+ import React from 'react'
2
+ import Color from './Color'
3
+ import { View, Text } from 'react-native'
4
+
5
+ export function MessageVideo () {
6
+ return (
7
+ <View style={{ padding: 20 }}>
8
+ <Text style={{ color: Color.alizarin, fontWeight: '600' }}>
9
+ {'Video is not implemented by GiftedChat.'}
10
+ </Text>
11
+ <Text style={{ color: Color.alizarin, fontWeight: '600' }}>
12
+ {
13
+ 'You need to provide your own implementation by using renderMessageVideo'
14
+ }
15
+ {'prop.'}
16
+ </Text>
17
+ </View>
18
+ )
19
+ }