react-native-gifted-chat 2.8.0 → 2.8.2-alpha.0

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 (47) hide show
  1. package/README.md +18 -5
  2. package/lib/Bubble/index.d.ts +3 -27
  3. package/lib/Bubble/index.js +138 -125
  4. package/lib/Bubble/index.js.map +1 -1
  5. package/lib/Bubble/types.d.ts +3 -3
  6. package/lib/GiftedChat/index.js +19 -4
  7. package/lib/GiftedChat/index.js.map +1 -1
  8. package/lib/GiftedChat/types.d.ts +5 -10
  9. package/lib/Message/index.js +7 -12
  10. package/lib/Message/index.js.map +1 -1
  11. package/lib/Message/types.d.ts +2 -2
  12. package/lib/MessageContainer/components/Item/index.js +3 -1
  13. package/lib/MessageContainer/components/Item/index.js.map +1 -1
  14. package/lib/MessageContainer/index.js +69 -46
  15. package/lib/MessageContainer/index.js.map +1 -1
  16. package/lib/MessageContainer/styles.d.ts +3 -2
  17. package/lib/MessageContainer/styles.js +3 -2
  18. package/lib/MessageContainer/styles.js.map +1 -1
  19. package/lib/MessageContainer/types.d.ts +6 -9
  20. package/lib/MessageText.d.ts +11 -7
  21. package/lib/MessageText.js +57 -96
  22. package/lib/MessageText.js.map +1 -1
  23. package/lib/SystemMessage.d.ts +2 -1
  24. package/lib/SystemMessage.js +3 -2
  25. package/lib/SystemMessage.js.map +1 -1
  26. package/lib/utils.d.ts +2 -0
  27. package/lib/utils.js +66 -0
  28. package/lib/utils.js.map +1 -1
  29. package/package.json +37 -30
  30. package/src/Bubble/index.tsx +171 -172
  31. package/src/Bubble/types.ts +3 -3
  32. package/src/GiftedChat/index.tsx +23 -3
  33. package/src/GiftedChat/types.ts +6 -4
  34. package/src/Message/index.tsx +10 -16
  35. package/src/Message/types.ts +2 -2
  36. package/src/MessageContainer/components/Item/index.tsx +1 -0
  37. package/src/MessageContainer/index.tsx +93 -58
  38. package/src/MessageContainer/styles.ts +3 -2
  39. package/src/MessageContainer/types.ts +6 -9
  40. package/src/MessageText.tsx +86 -121
  41. package/src/SystemMessage.tsx +4 -1
  42. package/src/__tests__/DayAnimated.test.tsx +54 -0
  43. package/src/__tests__/GiftedChat.test.tsx +25 -0
  44. package/src/__tests__/__snapshots__/DayAnimated.test.tsx.snap +5 -0
  45. package/src/__tests__/__snapshots__/GiftedChat.test.tsx.snap +25 -0
  46. package/src/__tests__/__snapshots__/MessageContainer.test.tsx.snap +11 -9
  47. package/src/utils.ts +77 -1
@@ -3,7 +3,7 @@ import { AvatarProps } from '../Avatar'
3
3
  import { SystemMessageProps } from '../SystemMessage'
4
4
  import { DayProps } from '../Day'
5
5
  import { IMessage, User, LeftRightStyle } from '../types'
6
- import Bubble from '../Bubble'
6
+ import { BubbleProps } from '../Bubble'
7
7
 
8
8
  export interface MessageProps<TMessage extends IMessage> {
9
9
  showUserAvatar?: boolean
@@ -14,7 +14,7 @@ export interface MessageProps<TMessage extends IMessage> {
14
14
  user: User
15
15
  inverted?: boolean
16
16
  containerStyle?: LeftRightStyle<ViewStyle>
17
- renderBubble?(props: Bubble['props']): React.ReactNode
17
+ renderBubble?(props: BubbleProps<TMessage>): React.ReactNode
18
18
  renderDay?(props: DayProps): React.ReactNode
19
19
  renderSystemMessage?(props: SystemMessageProps<TMessage>): React.ReactNode
20
20
  renderAvatar?(props: AvatarProps<TMessage>): React.ReactNode
@@ -134,6 +134,7 @@ const Item = <TMessage extends IMessage>(props: ItemProps<TMessage>) => {
134
134
  }), [relativeScrolledPositionToBottomOfDay, dayContainerHeight, dayTopOffset])
135
135
 
136
136
  return (
137
+ // do not remove key. it helps to get correct position of the day container
137
138
  <View key={props.currentMessage._id.toString()}>
138
139
  <Animated.View
139
140
  style={style}
@@ -1,7 +1,7 @@
1
1
  import React, { useCallback, useEffect, useMemo, useState } from 'react'
2
2
  import {
3
3
  View,
4
- TouchableOpacity,
4
+ Pressable,
5
5
  Text,
6
6
  Platform,
7
7
  LayoutChangeEvent,
@@ -23,11 +23,12 @@ import { ItemProps } from './components/Item/types'
23
23
  import { warning } from '../logging'
24
24
  import stylesCommon from '../styles'
25
25
  import styles from './styles'
26
- import { isSameDay } from '../utils'
26
+ import { isSameDay, useCallbackThrottled } from '../utils'
27
27
 
28
28
  export * from './types'
29
29
 
30
- const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ const AnimatedFlatList = Animated.createAnimatedComponent(FlatList) as React.ComponentType<any>
31
32
 
32
33
  function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageContainerProps<TMessage>) {
33
34
  const {
@@ -53,9 +54,12 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
53
54
  forwardRef,
54
55
  handleOnScroll: handleOnScrollProp,
55
56
  scrollToBottomComponent: scrollToBottomComponentProp,
57
+ renderDay: renderDayProp,
56
58
  } = props
57
59
 
58
60
  const scrollToBottomOpacity = useSharedValue(0)
61
+ const isScrollingDown = useSharedValue(false)
62
+ const lastScrolledY = useSharedValue(0)
59
63
  const [isScrollToBottomVisible, setIsScrollToBottomVisible] = useState(false)
60
64
  const scrollToBottomStyleAnim = useAnimatedStyle(() => ({
61
65
  opacity: scrollToBottomOpacity.value,
@@ -74,9 +78,9 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
74
78
 
75
79
  const ListFooterComponent = useMemo(() => {
76
80
  if (renderFooterProp)
77
- return <>{renderFooterProp(props)}</>
81
+ return renderFooterProp(props)
78
82
 
79
- return <>{renderTypingIndicator()}</>
83
+ return renderTypingIndicator()
80
84
  }, [renderFooterProp, renderTypingIndicator, props])
81
85
 
82
86
  const renderLoadEarlier = useCallback(() => {
@@ -90,17 +94,33 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
90
94
  return null
91
95
  }, [loadEarlier, renderLoadEarlierProp, props])
92
96
 
97
+ const changeScrollToBottomVisibility: (isVisible: boolean) => void = useCallbackThrottled((isVisible: boolean) => {
98
+ if (isScrollingDown.value && isVisible)
99
+ return
100
+
101
+ if (isVisible)
102
+ setIsScrollToBottomVisible(true)
103
+
104
+ scrollToBottomOpacity.value = withTiming(isVisible ? 1 : 0, { duration: 250 }, isFinished => {
105
+ if (isFinished && !isVisible)
106
+ runOnJS(setIsScrollToBottomVisible)(false)
107
+ })
108
+ }, [scrollToBottomOpacity, isScrollingDown], 50)
109
+
93
110
  const scrollTo = useCallback((options: { animated?: boolean, offset: number }) => {
94
- if (forwardRef?.current && options)
95
- forwardRef.current.scrollToOffset(options)
111
+ if (options)
112
+ forwardRef?.current?.scrollToOffset(options)
96
113
  }, [forwardRef])
97
114
 
98
115
  const doScrollToBottom = useCallback((animated: boolean = true) => {
116
+ isScrollingDown.value = true
117
+ changeScrollToBottomVisibility(false)
118
+
99
119
  if (inverted)
100
120
  scrollTo({ offset: 0, animated })
101
121
  else if (forwardRef?.current)
102
122
  forwardRef.current.scrollToEnd({ animated })
103
- }, [forwardRef, inverted, scrollTo])
123
+ }, [forwardRef, inverted, scrollTo, isScrollingDown, changeScrollToBottomVisibility])
104
124
 
105
125
  const handleOnScroll = useCallback((event: ReanimatedScrollEvent) => {
106
126
  handleOnScrollProp?.(event)
@@ -111,33 +131,25 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
111
131
  layoutMeasurement: { height: layoutMeasurementHeight },
112
132
  } = event
113
133
 
114
- const duration = 250
134
+ isScrollingDown.value =
135
+ (inverted && lastScrolledY.value > contentOffsetY) ||
136
+ (!inverted && lastScrolledY.value < contentOffsetY)
115
137
 
116
- const makeScrollToBottomVisible = () => {
117
- setIsScrollToBottomVisible(true)
118
- scrollToBottomOpacity.value = withTiming(1, { duration })
119
- }
120
-
121
- const makeScrollToBottomHidden = () => {
122
- scrollToBottomOpacity.value = withTiming(0, { duration }, isFinished => {
123
- if (isFinished)
124
- runOnJS(setIsScrollToBottomVisible)(false)
125
- })
126
- }
138
+ lastScrolledY.value = contentOffsetY
127
139
 
128
140
  if (inverted)
129
141
  if (contentOffsetY > scrollToBottomOffset!)
130
- makeScrollToBottomVisible()
142
+ changeScrollToBottomVisibility(true)
131
143
  else
132
- makeScrollToBottomHidden()
144
+ changeScrollToBottomVisibility(false)
133
145
  else if (
134
146
  contentOffsetY < scrollToBottomOffset! &&
135
147
  contentSizeHeight - layoutMeasurementHeight > scrollToBottomOffset!
136
148
  )
137
- makeScrollToBottomVisible()
149
+ changeScrollToBottomVisibility(false)
138
150
  else
139
- makeScrollToBottomHidden()
140
- }, [handleOnScrollProp, inverted, scrollToBottomOffset, scrollToBottomOpacity])
151
+ changeScrollToBottomVisibility(false)
152
+ }, [handleOnScrollProp, inverted, scrollToBottomOffset, changeScrollToBottomVisibility, isScrollingDown, lastScrolledY])
141
153
 
142
154
  const renderItem = useCallback(({ item, index }: ListRenderItemInfo<unknown>): React.ReactElement | null => {
143
155
  const messageItem = item as TMessage
@@ -215,42 +227,46 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
215
227
  return <Text>{'V'}</Text>
216
228
  }, [scrollToBottomComponentProp])
217
229
 
218
- const renderScrollToBottomWrapper = useCallback(() => {
230
+ const ScrollToBottomWrapper = useCallback(() => {
231
+ if (!isScrollToBottomEnabled)
232
+ return null
233
+
219
234
  if (!isScrollToBottomVisible)
220
235
  return null
221
236
 
222
237
  return (
223
- <Animated.View
224
- style={[
225
- stylesCommon.centerItems,
226
- styles.scrollToBottomStyle,
227
- scrollToBottomStyle,
228
- scrollToBottomStyleAnim,
229
- ]}
238
+ <Pressable
239
+ style={styles.scrollToBottom}
240
+ onPress={() => doScrollToBottom()}
230
241
  >
231
- <TouchableOpacity
232
- onPress={() => doScrollToBottom()}
233
- hitSlop={{ top: 5, left: 5, right: 5, bottom: 5 }}
242
+ <Animated.View
243
+ style={[
244
+ stylesCommon.centerItems,
245
+ styles.scrollToBottomContent,
246
+ scrollToBottomStyle,
247
+ scrollToBottomStyleAnim,
248
+ ]}
234
249
  >
235
250
  {renderScrollBottomComponent()}
236
- </TouchableOpacity>
237
- </Animated.View>
251
+ </Animated.View>
252
+ </Pressable>
238
253
  )
239
- }, [scrollToBottomStyle, renderScrollBottomComponent, doScrollToBottom, scrollToBottomStyleAnim, isScrollToBottomVisible])
254
+ }, [scrollToBottomStyle, renderScrollBottomComponent, doScrollToBottom, scrollToBottomStyleAnim, isScrollToBottomEnabled, isScrollToBottomVisible])
240
255
 
241
256
  const onLayoutList = useCallback((event: LayoutChangeEvent) => {
242
257
  listHeight.value = event.nativeEvent.layout.height
243
258
 
244
259
  if (
245
260
  !inverted &&
246
- messages?.length
261
+ messages?.length &&
262
+ isScrollToBottomEnabled
247
263
  )
248
264
  setTimeout(() => {
249
265
  doScrollToBottom(false)
250
266
  }, 500)
251
267
 
252
268
  listViewProps?.onLayout?.(event)
253
- }, [inverted, messages, doScrollToBottom, listHeight, listViewProps])
269
+ }, [inverted, messages, doScrollToBottom, listHeight, listViewProps, isScrollToBottomEnabled])
254
270
 
255
271
  const onEndReached = useCallback(() => {
256
272
  if (
@@ -266,35 +282,55 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
266
282
  const keyExtractor = useCallback((item: unknown) => (item as TMessage)._id.toString(), [])
267
283
 
268
284
  const renderCell = useCallback((props: CellRendererProps<unknown>) => {
285
+ const { item, onLayout: onLayoutProp, children } = props
286
+ const id = (item as IMessage)._id.toString()
287
+
269
288
  const handleOnLayout = (event: LayoutChangeEvent) => {
270
- const prevMessage = messages[props.index + (inverted ? 1 : -1)]
271
- if (prevMessage && isSameDay(props.item as IMessage, prevMessage))
272
- return
289
+ onLayoutProp?.(event)
273
290
 
274
291
  const { y, height } = event.nativeEvent.layout
275
292
 
276
293
  const newValue = {
277
294
  y,
278
295
  height,
279
- createdAt: new Date((props.item as IMessage).createdAt).getTime(),
296
+ createdAt: new Date((item as IMessage).createdAt).getTime(),
280
297
  }
281
298
 
282
299
  daysPositions.modify(value => {
283
300
  'worklet'
284
301
 
302
+ const isSameDay = (date1: number, date2: number) => {
303
+ const d1 = new Date(date1)
304
+ const d2 = new Date(date2)
305
+
306
+ return (
307
+ d1.getDate() === d2.getDate() &&
308
+ d1.getMonth() === d2.getMonth() &&
309
+ d1.getFullYear() === d2.getFullYear()
310
+ )
311
+ }
312
+
313
+ for (const [key, item] of Object.entries(value))
314
+ if (isSameDay(newValue.createdAt, item.createdAt) && (inverted ? item.y <= newValue.y : item.y >= newValue.y)) {
315
+ delete value[key]
316
+ break
317
+ }
318
+
285
319
  // @ts-expect-error: https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue#remarks
286
- value[props.item._id] = newValue
320
+ value[id] = newValue
287
321
  return value
288
322
  })
289
323
  }
290
324
 
291
325
  return (
292
- <Animated.View
326
+ <View
293
327
  {...props}
294
328
  onLayout={handleOnLayout}
295
- />
329
+ >
330
+ {children}
331
+ </View>
296
332
  )
297
- }, [daysPositions, messages, inverted])
333
+ }, [daysPositions, inverted])
298
334
 
299
335
  const scrollHandler = useAnimatedScrollHandler({
300
336
  onScroll: event => {
@@ -307,7 +343,7 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
307
343
  // removes unrendered days positions when messages are added/removed
308
344
  useEffect(() => {
309
345
  Object.keys(daysPositions.value).forEach(key => {
310
- const messageIndex = messages.findIndex(m => m._id === key)
346
+ const messageIndex = messages.findIndex(m => m._id.toString() === key)
311
347
  let shouldRemove = messageIndex === -1
312
348
 
313
349
  if (!shouldRemove) {
@@ -334,14 +370,14 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
334
370
  ]}
335
371
  >
336
372
  <AnimatedFlatList
337
- ref={forwardRef as React.Ref<FlatList<unknown>>}
338
- extraData={[extraData, isTyping]}
373
+ extraData={extraData}
374
+ ref={forwardRef}
339
375
  keyExtractor={keyExtractor}
340
- automaticallyAdjustContentInsets={false}
341
- inverted={inverted}
342
376
  data={messages}
343
- style={stylesCommon.fill}
344
377
  renderItem={renderItem}
378
+ inverted={inverted}
379
+ automaticallyAdjustContentInsets={false}
380
+ style={stylesCommon.fill}
345
381
  {...invertibleScrollViewProps}
346
382
  ListEmptyComponent={renderChatEmpty}
347
383
  ListFooterComponent={
@@ -358,13 +394,12 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
358
394
  onLayout={onLayoutList}
359
395
  CellRendererComponent={renderCell}
360
396
  />
361
- {isScrollToBottomEnabled
362
- ? renderScrollToBottomWrapper()
363
- : null}
397
+ <ScrollToBottomWrapper />
364
398
  <DayAnimated
365
399
  scrolledY={scrolledY}
366
400
  daysPositions={daysPositions}
367
401
  listHeight={listHeight}
402
+ renderDay={renderDayProp}
368
403
  messages={messages}
369
404
  isLoadingEarlier={isLoadingEarlier}
370
405
  />
@@ -13,12 +13,13 @@ export default StyleSheet.create({
13
13
  emptyChatContainer: {
14
14
  transform: [{ scaleY: -1 }],
15
15
  },
16
- scrollToBottomStyle: {
17
- opacity: 0.8,
16
+ scrollToBottom: {
18
17
  position: 'absolute',
19
18
  right: 10,
20
19
  bottom: 30,
21
20
  zIndex: 999,
21
+ },
22
+ scrollToBottomContent: {
22
23
  height: 40,
23
24
  width: 40,
24
25
  borderRadius: 20,
@@ -1,23 +1,19 @@
1
- import React, { Component, RefObject } from 'react'
1
+ import React, { RefObject } from 'react'
2
2
  import {
3
3
  FlatListProps,
4
- LayoutChangeEvent,
5
4
  StyleProp,
6
5
  ViewStyle,
6
+ FlatList,
7
7
  } from 'react-native'
8
8
 
9
9
  import { LoadEarlierProps } from '../LoadEarlier'
10
10
  import { MessageProps } from '../Message'
11
- import { User, IMessage, Reply } from '../types'
11
+ import { User, IMessage, Reply, DayProps } from '../types'
12
12
  import { ReanimatedScrollEvent } from 'react-native-reanimated/lib/typescript/hook/commonTypes'
13
- import { FlatList } from 'react-native-reanimated/lib/typescript/Animated'
14
- import { AnimateProps } from 'react-native-reanimated'
15
13
 
16
- export type ListViewProps = {
17
- onLayout?: (event: LayoutChangeEvent) => void
18
- } & object
14
+ export type ListViewProps<TMessage extends IMessage = IMessage> = Partial<FlatListProps<TMessage>>
19
15
 
20
- export type AnimatedList<TMessage> = Component<AnimateProps<FlatListProps<TMessage>>, unknown, unknown> & FlatList<FlatListProps<TMessage>>
16
+ export type AnimatedList<TMessage> = FlatList<TMessage>
21
17
 
22
18
  export interface MessageContainerProps<TMessage extends IMessage = IMessage> {
23
19
  forwardRef?: RefObject<AnimatedList<TMessage>>
@@ -36,6 +32,7 @@ export interface MessageContainerProps<TMessage extends IMessage = IMessage> {
36
32
  renderChatEmpty?(): React.ReactNode
37
33
  renderFooter?(props: MessageContainerProps<TMessage>): React.ReactNode
38
34
  renderMessage?(props: MessageProps<TMessage>): React.ReactElement
35
+ renderDay?(props: DayProps): React.ReactNode
39
36
  renderLoadEarlier?(props: LoadEarlierProps): React.ReactNode
40
37
  renderTypingIndicator?(): React.ReactNode
41
38
  scrollToBottomComponent?(): React.ReactNode
@@ -1,84 +1,46 @@
1
- import React from 'react'
1
+ import React, { useMemo, useCallback } from 'react'
2
2
  import {
3
3
  Linking,
4
4
  StyleSheet,
5
- View,
6
- TextProps,
7
5
  StyleProp,
8
6
  ViewStyle,
9
7
  TextStyle,
10
8
  } from 'react-native'
11
9
 
12
- import ParsedText from 'react-native-parsed-text'
10
+ import Autolink, { AutolinkProps } from 'react-native-autolink'
11
+ import { Match } from 'autolinker/dist/es2015'
13
12
  import { LeftRightStyle, IMessage } from './types'
14
- import { useChatContext } from './GiftedChatContext'
15
13
  import { error } from './logging'
16
14
 
17
- const WWW_URL_PATTERN = /^www\./i
18
-
19
- const { textStyle } = StyleSheet.create({
20
- textStyle: {
21
- fontSize: 16,
22
- lineHeight: 20,
23
- marginTop: 5,
24
- marginBottom: 5,
25
- marginLeft: 10,
26
- marginRight: 10,
27
- },
28
- })
29
-
30
- const styles = {
31
- left: StyleSheet.create({
32
- container: {},
33
- text: {
34
- color: 'black',
35
- ...textStyle,
36
- },
37
- link: {
38
- color: 'black',
39
- textDecorationLine: 'underline',
40
- },
41
- }),
42
- right: StyleSheet.create({
43
- container: {},
44
- text: {
45
- color: 'white',
46
- ...textStyle,
47
- },
48
- link: {
49
- color: 'white',
50
- textDecorationLine: 'underline',
51
- },
52
- }),
15
+ export interface MessageOption {
16
+ title: string
17
+ action: (phone: string) => void
53
18
  }
54
19
 
55
- const DEFAULT_OPTION_TITLES = ['Call', 'Text', 'Cancel']
56
-
57
- export interface MessageTextProps<TMessage extends IMessage> {
20
+ export type MessageTextProps<TMessage extends IMessage> = {
58
21
  position?: 'left' | 'right'
59
- optionTitles?: string[]
60
22
  currentMessage: TMessage
61
23
  containerStyle?: LeftRightStyle<ViewStyle>
62
24
  textStyle?: LeftRightStyle<TextStyle>
63
25
  linkStyle?: LeftRightStyle<TextStyle>
64
- textProps?: TextProps
65
26
  customTextStyle?: StyleProp<TextStyle>
66
- parsePatterns?: (linkStyle: TextStyle) => []
67
- }
27
+ onPress?: (
28
+ message: TMessage,
29
+ url: string,
30
+ match: Match
31
+ ) => void
32
+ } & Omit<AutolinkProps, 'text' | 'onPress'>
68
33
 
69
- export function MessageText<TMessage extends IMessage = IMessage> ({
70
- currentMessage = {} as TMessage,
71
- optionTitles = DEFAULT_OPTION_TITLES,
34
+ export const MessageText: React.FC<MessageTextProps<IMessage>> = ({
35
+ currentMessage = {} as IMessage,
72
36
  position = 'left',
73
37
  containerStyle,
74
38
  textStyle,
75
39
  linkStyle: linkStyleProp,
76
40
  customTextStyle,
77
- parsePatterns,
78
- textProps,
79
- }: MessageTextProps<TMessage>) {
80
- const { actionSheet } = useChatContext()
81
-
41
+ onPress: onPressProp,
42
+ ...rest
43
+ }) => {
82
44
  // TODO: React.memo
83
45
  // const shouldComponentUpdate = (nextProps: MessageTextProps<TMessage>) => {
84
46
  // return (
@@ -88,77 +50,80 @@ export function MessageText<TMessage extends IMessage = IMessage> ({
88
50
  // )
89
51
  // }
90
52
 
91
- const onUrlPress = (url: string) => {
92
- // When someone sends a message that includes a website address beginning with "www." (omitting the scheme),
93
- // react-native-parsed-text recognizes it as a valid url, but Linking fails to open due to the missing scheme.
94
- if (WWW_URL_PATTERN.test(url))
95
- onUrlPress(`https://${url}`)
96
- else
97
- Linking.openURL(url).catch(e => {
98
- error(e, 'No handler for URL:', url)
99
- })
100
- }
53
+ const onUrlPress = useCallback((url: string) => {
54
+ if (/^www\./i.test(url))
55
+ url = `https://${url}`
56
+
57
+ Linking.openURL(url).catch(e => {
58
+ error(e, 'No handler for URL:', url)
59
+ })
60
+ }, [])
101
61
 
102
- const onPhonePress = (phone: string) => {
103
- const options =
104
- optionTitles && optionTitles.length > 0
105
- ? optionTitles.slice(0, 3)
106
- : DEFAULT_OPTION_TITLES
107
- const cancelButtonIndex = options.length - 1
108
- actionSheet().showActionSheetWithOptions(
109
- {
110
- options,
111
- cancelButtonIndex,
112
- },
113
- (buttonIndex?: number) => {
114
- switch (buttonIndex) {
115
- case 0:
116
- Linking.openURL(`tel:${phone}`).catch(e => {
117
- error(e, 'No handler for telephone')
118
- })
119
- break
120
- case 1:
121
- Linking.openURL(`sms:${phone}`).catch(e => {
122
- error(e, 'No handler for text')
123
- })
124
- break
125
- }
126
- }
127
- )
128
- }
62
+ const onPhonePress = useCallback((phone: string) => {
63
+ Linking.openURL(`tel:${phone}`).catch(e => {
64
+ error(e, 'No handler for telephone')
65
+ })
66
+ }, [])
129
67
 
130
- const onEmailPress = (email: string) =>
68
+ const onEmailPress = useCallback((email: string) =>
131
69
  Linking.openURL(`mailto:${email}`).catch(e =>
132
70
  error(e, 'No handler for mailto')
133
- )
71
+ ), [])
134
72
 
135
- const linkStyle = [
136
- styles[position].link,
73
+ const linkStyle = useMemo(() => StyleSheet.flatten([
74
+ styles.link,
137
75
  linkStyleProp?.[position],
138
- ]
76
+ ]), [position, linkStyleProp])
77
+
78
+ const handlePress = useCallback((url: string, match: Match) => {
79
+ const type = match.getType()
80
+
81
+ if (onPressProp)
82
+ onPressProp(currentMessage, url, match)
83
+ else if (type === 'url')
84
+ onUrlPress(url)
85
+ else if (type === 'phone')
86
+ onPhonePress(url)
87
+ else if (type === 'email')
88
+ onEmailPress(url)
89
+ }, [onUrlPress, onPhonePress, onEmailPress, onPressProp, currentMessage])
90
+
91
+ const style = useMemo(() => [
92
+ containerStyle?.[position],
93
+ styles[`text_${position}`],
94
+ textStyle?.[position],
95
+ customTextStyle,
96
+ ], [containerStyle, position, textStyle, customTextStyle])
97
+
139
98
  return (
140
- <View
141
- style={[
142
- styles[position].container,
143
- containerStyle && containerStyle[position],
144
- ]}
145
- >
146
- <ParsedText
147
- style={[
148
- styles[position].text,
149
- textStyle && textStyle[position],
150
- customTextStyle,
151
- ]}
152
- parse={[
153
- ...(parsePatterns ? parsePatterns(linkStyle as unknown as TextStyle) : []),
154
- { type: 'url', style: linkStyle, onPress: onUrlPress },
155
- { type: 'phone', style: linkStyle, onPress: onPhonePress },
156
- { type: 'email', style: linkStyle, onPress: onEmailPress },
157
- ]}
158
- childrenProps={{ ...textProps }}
159
- >
160
- {currentMessage!.text}
161
- </ParsedText>
162
- </View>
99
+ <Autolink
100
+ style={style}
101
+ {...rest}
102
+ text={currentMessage!.text}
103
+ email
104
+ link
105
+ linkStyle={linkStyle}
106
+ onPress={handlePress}
107
+ />
163
108
  )
164
109
  }
110
+
111
+ const styles = StyleSheet.create({
112
+ text: {
113
+ fontSize: 16,
114
+ lineHeight: 20,
115
+ marginTop: 5,
116
+ marginBottom: 5,
117
+ marginLeft: 10,
118
+ marginRight: 10,
119
+ },
120
+ text_left: {
121
+ color: 'black',
122
+ },
123
+ text_right: {
124
+ color: 'white',
125
+ },
126
+ link: {
127
+ textDecorationLine: 'underline',
128
+ },
129
+ })
@@ -29,6 +29,7 @@ export interface SystemMessageProps<TMessage extends IMessage> {
29
29
  containerStyle?: StyleProp<ViewStyle>
30
30
  wrapperStyle?: StyleProp<ViewStyle>
31
31
  textStyle?: StyleProp<TextStyle>
32
+ children?: React.ReactNode
32
33
  }
33
34
 
34
35
  export function SystemMessage<TMessage extends IMessage = IMessage> ({
@@ -36,6 +37,7 @@ export function SystemMessage<TMessage extends IMessage = IMessage> ({
36
37
  containerStyle,
37
38
  wrapperStyle,
38
39
  textStyle,
40
+ children,
39
41
  }: SystemMessageProps<TMessage>) {
40
42
  if (currentMessage == null || currentMessage.system === false)
41
43
  return null
@@ -43,7 +45,8 @@ export function SystemMessage<TMessage extends IMessage = IMessage> ({
43
45
  return (
44
46
  <View style={[stylesCommon.fill, stylesCommon.centerItems, styles.container, containerStyle]}>
45
47
  <View style={wrapperStyle}>
46
- <Text style={[styles.text, textStyle]}>{currentMessage.text}</Text>
48
+ {!!currentMessage.text && <Text style={[styles.text, textStyle]}>{currentMessage.text}</Text>}
49
+ {children}
47
50
  </View>
48
51
  </View>
49
52
  )