react-native-gifted-chat 2.4.1 → 2.6.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 (159) hide show
  1. package/README.md +34 -14
  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 -30
  10. package/lib/Bubble.js +99 -92
  11. package/lib/Bubble.js.flow +2 -2
  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 +86 -59
  91. package/src/Actions.tsx +114 -0
  92. package/src/Avatar.tsx +178 -0
  93. package/src/Bubble.tsx +596 -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,670 @@
1
+ import React, {
2
+ createRef,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ useCallback,
8
+ MutableRefObject,
9
+ } from 'react'
10
+ import 'react-native-get-random-values' // NOTE: FOR "uuid" SUPPORT
11
+ import {
12
+ ActionSheetOptions,
13
+ ActionSheetProvider,
14
+ ActionSheetProviderRef,
15
+ } from '@expo/react-native-action-sheet'
16
+ import dayjs from 'dayjs'
17
+ import localizedFormat from 'dayjs/plugin/localizedFormat'
18
+ import {
19
+ FlatList,
20
+ Platform,
21
+ StyleProp,
22
+ StyleSheet,
23
+ TextInput,
24
+ TextStyle,
25
+ View,
26
+ ViewStyle,
27
+ LayoutChangeEvent,
28
+ } from 'react-native'
29
+ import { LightboxProps } from 'react-native-lightbox-v2'
30
+ import { v4 as uuidv4 } from 'uuid'
31
+ import { Actions, ActionsProps } from './Actions'
32
+ import { Avatar, AvatarProps } from './Avatar'
33
+ import Bubble from './Bubble'
34
+ import { Composer, ComposerProps } from './Composer'
35
+ import { MAX_COMPOSER_HEIGHT, MIN_COMPOSER_HEIGHT, TEST_ID } from './Constant'
36
+ import { Day, DayProps } from './Day'
37
+ import GiftedAvatar from './GiftedAvatar'
38
+ import { GiftedChatContext } from './GiftedChatContext'
39
+ import { InputToolbar, InputToolbarProps } from './InputToolbar'
40
+ import { LoadEarlier, LoadEarlierProps } from './LoadEarlier'
41
+ import Message from './Message'
42
+ import MessageContainer from './MessageContainer'
43
+ import { MessageImage, MessageImageProps } from './MessageImage'
44
+ import { MessageText, MessageTextProps } from './MessageText'
45
+ import {
46
+ IMessage,
47
+ LeftRightStyle,
48
+ MessageAudioProps,
49
+ MessageVideoProps,
50
+ Reply,
51
+ User,
52
+ } from './Models'
53
+ import { QuickRepliesProps } from './QuickReplies'
54
+ import { Send, SendProps } from './Send'
55
+ import { SystemMessage, SystemMessageProps } from './SystemMessage'
56
+ import { Time, TimeProps } from './Time'
57
+ import * as utils from './utils'
58
+ import Animated, {
59
+ useAnimatedKeyboard,
60
+ useAnimatedStyle,
61
+ useAnimatedReaction,
62
+ useSharedValue,
63
+ withTiming,
64
+ runOnJS,
65
+ } from 'react-native-reanimated'
66
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
67
+
68
+ dayjs.extend(localizedFormat)
69
+
70
+ export interface GiftedChatProps<TMessage extends IMessage = IMessage> {
71
+ /* Message container ref */
72
+ messageContainerRef?: React.RefObject<FlatList<IMessage>>
73
+ /* text input ref */
74
+ textInputRef?: React.RefObject<TextInput>
75
+ /* Messages to display */
76
+ messages?: TMessage[]
77
+ /* Typing Indicator state */
78
+ isTyping?: boolean
79
+ /* Messages container style */
80
+ messagesContainerStyle?: StyleProp<ViewStyle>
81
+ /* Input text; default is undefined, but if specified, it will override GiftedChat's internal state */
82
+ text?: string
83
+ /* Controls whether or not the message bubbles appear at the top of the chat */
84
+ alignTop?: boolean
85
+ /* enables the scrollToBottom Component */
86
+ scrollToBottom?: boolean
87
+ /* Scroll to bottom wrapper style */
88
+ scrollToBottomStyle?: StyleProp<ViewStyle>
89
+ initialText?: string
90
+ /* Placeholder when text is empty; default is 'Type a message...' */
91
+ placeholder?: string
92
+ /* Makes the composer not editable */
93
+ disableComposer?: boolean
94
+ /* User sending the messages: { _id, name, avatar } */
95
+ user?: User
96
+ /* Locale to localize the dates */
97
+ locale?: string
98
+ /* Format to use for rendering times; default is 'LT' */
99
+ timeFormat?: string
100
+ /* Format to use for rendering dates; default is 'll' */
101
+ dateFormat?: string
102
+ /* Enables the "Load earlier messages" button */
103
+ loadEarlier?: boolean
104
+ /* Display an ActivityIndicator when loading earlier messages */
105
+ isLoadingEarlier?: boolean
106
+ /* Whether to render an avatar for the current user; default is false, only show avatars for other users */
107
+ showUserAvatar?: boolean
108
+ /* When false, avatars will only be displayed when a consecutive message is from the same user on the same day; default is false */
109
+ showAvatarForEveryMessage?: boolean
110
+ /* Render the message avatar at the top of consecutive messages, rather than the bottom; default is false */
111
+ renderAvatarOnTop?: boolean
112
+ inverted?: boolean
113
+ /* Extra props to be passed to the <Image> component created by the default renderMessageImage */
114
+ imageProps?: Message<TMessage>['props']
115
+ /* Extra props to be passed to the MessageImage's Lightbox */
116
+ lightboxProps?: LightboxProps
117
+ /* Minimum height of the input toolbar; default is 44 */
118
+ minInputToolbarHeight?: number
119
+ /* Extra props to be passed to the messages <ListView>; some props can't be overridden, see the code in MessageContainer.render() for details */
120
+ listViewProps?: object
121
+ /* Extra props to be passed to the <TextInput> */
122
+ textInputProps?: object
123
+ /* Determines whether the keyboard should stay visible after a tap; see <ScrollView> docs */
124
+ keyboardShouldPersistTaps?: boolean
125
+ /* Max message composer TextInput length */
126
+ maxInputLength?: number
127
+ /* Force send button */
128
+ alwaysShowSend?: boolean
129
+ /* Image style */
130
+ imageStyle?: StyleProp<ViewStyle>
131
+ /* This can be used to pass unknown data which needs to be re-rendered */
132
+ extraData?: object
133
+ /* composer min Height */
134
+ minComposerHeight?: number
135
+ /* composer min Height */
136
+ maxComposerHeight?: number
137
+ options?: { [key: string]: () => void }
138
+ optionTintColor?: string
139
+ quickReplyStyle?: StyleProp<ViewStyle>
140
+ quickReplyTextStyle?: StyleProp<TextStyle>
141
+ quickReplyContainerStyle?: StyleProp<ViewStyle>
142
+ /* optional prop used to place customView below text, image and video views; default is false */
143
+ isCustomViewBottom?: boolean
144
+ /* infinite scroll up when reach the top of messages container, automatically call onLoadEarlier function if exist */
145
+ infiniteScroll?: boolean
146
+ timeTextStyle?: LeftRightStyle<TextStyle>
147
+ /* Custom action sheet */
148
+ actionSheet?(): {
149
+ showActionSheetWithOptions: (
150
+ options: ActionSheetOptions,
151
+ callback: (buttonIndex: number) => void | Promise<void>,
152
+ ) => void
153
+ }
154
+ /* Callback when a message avatar is tapped */
155
+ onPressAvatar?(user: User): void
156
+ /* Callback when a message avatar is tapped */
157
+ onLongPressAvatar?(user: User): void
158
+ /* Generate an id for new messages. Defaults to UUID v4, generated by uuid */
159
+ messageIdGenerator?(message?: TMessage): string
160
+ /* Callback when sending a message */
161
+ onSend?(messages: TMessage[]): void
162
+ /* Callback when loading earlier messages */
163
+ onLoadEarlier?(): void
164
+ /* Render a loading view when initializing */
165
+ renderLoading?(): React.ReactNode
166
+ /* Custom "Load earlier messages" button */
167
+ renderLoadEarlier?(props: LoadEarlierProps): React.ReactNode
168
+ /* Custom message avatar; set to null to not render any avatar for the message */
169
+ renderAvatar?: null | ((props: AvatarProps<TMessage>) => React.ReactNode)
170
+ /* Custom message bubble */
171
+ renderBubble?(props: Bubble<TMessage>['props']): React.ReactNode
172
+ /* Custom system message */
173
+ renderSystemMessage?(props: SystemMessageProps<TMessage>): React.ReactNode
174
+ /* Callback when a message bubble is pressed; default is to do nothing */
175
+ onPress?(context: unknown, message: TMessage): void
176
+ /* Callback when a message bubble is long-pressed; default is to show an ActionSheet with "Copy Text" (see example using showActionSheetWithOptions()) */
177
+ onLongPress?(context: unknown, message: TMessage): void
178
+ /* Custom Username container */
179
+ renderUsername?(user: User): React.ReactNode
180
+ /* Reverses display order of messages; default is true */
181
+ /* Custom message container */
182
+ renderMessage?(message: Message<TMessage>['props']): React.ReactElement
183
+ /* Custom message text */
184
+ renderMessageText?(messageText: MessageTextProps<TMessage>): React.ReactNode
185
+ /* Custom message image */
186
+ renderMessageImage?(props: MessageImageProps<TMessage>): React.ReactNode
187
+ /* Custom message video */
188
+ renderMessageVideo?(props: MessageVideoProps<TMessage>): React.ReactNode
189
+ /* Custom message video */
190
+ renderMessageAudio?(props: MessageAudioProps<TMessage>): React.ReactNode
191
+ /* Custom view inside the bubble */
192
+ renderCustomView?(props: Bubble<TMessage>['props']): React.ReactNode
193
+ /* Custom day above a message */
194
+ renderDay?(props: DayProps<TMessage>): React.ReactNode
195
+ /* Custom time inside a message */
196
+ renderTime?(props: TimeProps<TMessage>): React.ReactNode
197
+ /* Custom footer component on the ListView, e.g. 'User is typing...' */
198
+ renderFooter?(): React.ReactNode
199
+ /* Custom component to render in the ListView when messages are empty */
200
+ renderChatEmpty?(): React.ReactNode
201
+ /* Custom component to render below the MessageContainer (separate from the ListView) */
202
+ renderChatFooter?(): React.ReactNode
203
+ /* Custom message composer container */
204
+ renderInputToolbar?(props: InputToolbarProps<TMessage>): React.ReactNode
205
+ /* Custom text input message composer */
206
+ renderComposer?(props: ComposerProps): React.ReactNode
207
+ /* Custom action button on the left of the message composer */
208
+ renderActions?(props: ActionsProps): React.ReactNode
209
+ /* Custom send button; you can pass children to the original Send component quite easily, for example to use a custom icon (example) */
210
+ renderSend?(props: SendProps<TMessage>): React.ReactNode
211
+ /* Custom second line of actions below the message composer */
212
+ renderAccessory?(props: InputToolbarProps<TMessage>): React.ReactNode
213
+ /* Callback when the Action button is pressed (if set, the default actionSheet will not be used) */
214
+ onPressActionButton?(): void
215
+ /* Callback when the input text changes */
216
+ onInputTextChanged?(text: string): void
217
+ /* Custom parse patterns for react-native-parsed-text used to linking message content (like URLs and phone numbers) */
218
+ parsePatterns?: (linkStyle: TextStyle) => []
219
+ onQuickReply?(replies: Reply[]): void
220
+ renderQuickReplies?(
221
+ quickReplies: QuickRepliesProps<TMessage>,
222
+ ): React.ReactNode
223
+ renderQuickReplySend?(): React.ReactNode
224
+ /* Scroll to bottom custom component */
225
+ scrollToBottomComponent?(): React.ReactNode
226
+ shouldUpdateMessage?(
227
+ props: Message<TMessage>['props'],
228
+ nextProps: Message<TMessage>['props'],
229
+ ): boolean
230
+ }
231
+
232
+ function GiftedChat<TMessage extends IMessage = IMessage> (
233
+ props: GiftedChatProps
234
+ ) {
235
+ const {
236
+ messages = [],
237
+ initialText = '',
238
+ isTyping,
239
+ messageIdGenerator = () => uuidv4(),
240
+ user = {},
241
+ onSend,
242
+ locale = 'en',
243
+ renderLoading,
244
+ actionSheet = null,
245
+ textInputProps,
246
+ renderChatFooter = null,
247
+ renderInputToolbar = null,
248
+ keyboardShouldPersistTaps = Platform.select({
249
+ ios: 'never',
250
+ android: 'always',
251
+ default: 'never',
252
+ }),
253
+ onInputTextChanged = null,
254
+ maxInputLength = null,
255
+ inverted = true,
256
+ minComposerHeight = MIN_COMPOSER_HEIGHT,
257
+ maxComposerHeight = MAX_COMPOSER_HEIGHT,
258
+ } = props
259
+
260
+ const actionSheetRef = useRef<ActionSheetProviderRef>(null)
261
+
262
+ const messageContainerRef = useMemo(
263
+ () => props.messageContainerRef || createRef<FlatList<IMessage>>(),
264
+ [props.messageContainerRef]
265
+ )
266
+
267
+ const textInputRef = useMemo(
268
+ () => props.textInputRef || createRef<TextInput>(),
269
+ [props.textInputRef]
270
+ )
271
+
272
+ const isTextInputWasFocused: MutableRefObject<boolean> = useRef(false)
273
+
274
+ const [isInitialized, setIsInitialized] = useState<boolean>(false)
275
+ const [composerHeight, setComposerHeight] = useState<number>(
276
+ minComposerHeight!
277
+ )
278
+ const [text, setText] = useState<string | undefined>(() => props.text || '')
279
+ const [isTypingDisabled, setIsTypingDisabled] = useState<boolean>(false)
280
+
281
+ const keyboard = useAnimatedKeyboard()
282
+ const trackingKeyboardMovement = useSharedValue(false)
283
+ const debounceEnableTypingTimeoutId = useRef<ReturnType<typeof setTimeout>>()
284
+ const insets = useSafeAreaInsets()
285
+ const keyboardOffsetBottom = useSharedValue(0)
286
+
287
+ const contentStyleAnim = useAnimatedStyle(
288
+ () => ({
289
+ transform: [
290
+ { translateY: -keyboard.height.value + keyboardOffsetBottom.value },
291
+ ],
292
+ }),
293
+ [keyboard, keyboardOffsetBottom]
294
+ )
295
+
296
+ const getTextFromProp = useCallback(
297
+ (fallback: string) => {
298
+ if (props.text === undefined)
299
+ return fallback
300
+
301
+ return props.text
302
+ },
303
+ [props.text]
304
+ )
305
+
306
+ /**
307
+ * Store text input focus status when keyboard hide to retrieve
308
+ * it afterwards if needed.
309
+ * `onKeyboardWillHide` may be called twice in sequence so we
310
+ * make a guard condition (eg. showing image picker)
311
+ */
312
+ const handleTextInputFocusWhenKeyboardHide = useCallback(() => {
313
+ if (!isTextInputWasFocused.current)
314
+ isTextInputWasFocused.current =
315
+ textInputRef.current?.isFocused() || false
316
+ }, [textInputRef])
317
+
318
+ /**
319
+ * Refocus the text input only if it was focused before showing keyboard.
320
+ * This is needed in some cases (eg. showing image picker).
321
+ */
322
+ const handleTextInputFocusWhenKeyboardShow = useCallback(() => {
323
+ if (
324
+ textInputRef.current &&
325
+ isTextInputWasFocused &&
326
+ !textInputRef.current.isFocused()
327
+ )
328
+ textInputRef.current.focus()
329
+
330
+ // Reset the indicator since the keyboard is shown
331
+ isTextInputWasFocused.current = false
332
+ }, [textInputRef])
333
+
334
+ const disableTyping = useCallback(() => {
335
+ clearTimeout(debounceEnableTypingTimeoutId.current)
336
+ setIsTypingDisabled(true)
337
+ }, [])
338
+
339
+ const enableTyping = useCallback(() => {
340
+ clearTimeout(debounceEnableTypingTimeoutId.current)
341
+ setIsTypingDisabled(false)
342
+ }, [])
343
+
344
+ const debounceEnableTyping = useCallback(() => {
345
+ clearTimeout(debounceEnableTypingTimeoutId.current)
346
+ debounceEnableTypingTimeoutId.current = setTimeout(() => {
347
+ enableTyping()
348
+ }, 50)
349
+ }, [enableTyping])
350
+
351
+ const scrollToBottom = useCallback(
352
+ (isAnimated = true) => {
353
+ if (!messageContainerRef?.current)
354
+ return
355
+
356
+ if (inverted) {
357
+ messageContainerRef.current.scrollToOffset({
358
+ offset: 0,
359
+ animated: isAnimated,
360
+ })
361
+ return
362
+ }
363
+
364
+ messageContainerRef.current.scrollToEnd({ animated: isAnimated })
365
+ },
366
+ [inverted, messageContainerRef]
367
+ )
368
+
369
+ const renderMessages = useMemo(() => {
370
+ if (!isInitialized)
371
+ return null
372
+
373
+ const { messagesContainerStyle, ...messagesContainerProps } = props
374
+
375
+ const fragment = (
376
+ <View style={[styles.fill, messagesContainerStyle]}>
377
+ <MessageContainer
378
+ {...messagesContainerProps}
379
+ invertibleScrollViewProps={{
380
+ inverted,
381
+ keyboardShouldPersistTaps,
382
+ }}
383
+ messages={messages}
384
+ forwardRef={messageContainerRef}
385
+ isTyping={isTyping}
386
+ />
387
+ {renderChatFooter?.()}
388
+ </View>
389
+ )
390
+
391
+ return fragment
392
+ }, [
393
+ isInitialized,
394
+ isTyping,
395
+ messages,
396
+ props,
397
+ inverted,
398
+ keyboardShouldPersistTaps,
399
+ messageContainerRef,
400
+ renderChatFooter,
401
+ ])
402
+
403
+ const notifyInputTextReset = useCallback(() => {
404
+ onInputTextChanged?.('')
405
+ }, [onInputTextChanged])
406
+
407
+ const resetInputToolbar = useCallback(() => {
408
+ textInputRef.current?.clear()
409
+
410
+ notifyInputTextReset()
411
+
412
+ setComposerHeight(minComposerHeight!)
413
+ setText(getTextFromProp(''))
414
+ enableTyping()
415
+ }, [
416
+ minComposerHeight,
417
+ getTextFromProp,
418
+ textInputRef,
419
+ notifyInputTextReset,
420
+ enableTyping,
421
+ ])
422
+
423
+ const _onSend = useCallback(
424
+ (messages: TMessage[] = [], shouldResetInputToolbar = false) => {
425
+ if (!Array.isArray(messages))
426
+ messages = [messages]
427
+
428
+ const newMessages: TMessage[] = messages.map(message => {
429
+ return {
430
+ ...message,
431
+ user: user!,
432
+ createdAt: new Date(),
433
+ _id: messageIdGenerator?.(),
434
+ }
435
+ })
436
+
437
+ if (shouldResetInputToolbar === true) {
438
+ disableTyping()
439
+
440
+ resetInputToolbar()
441
+ }
442
+
443
+ onSend?.(newMessages)
444
+ },
445
+ [messageIdGenerator, onSend, user, resetInputToolbar, disableTyping]
446
+ )
447
+
448
+ const onInputSizeChanged = useCallback(
449
+ (size: { height: number }) => {
450
+ const newComposerHeight = Math.max(
451
+ minComposerHeight!,
452
+ Math.min(maxComposerHeight!, size.height)
453
+ )
454
+
455
+ setComposerHeight(newComposerHeight)
456
+ },
457
+ [maxComposerHeight, minComposerHeight]
458
+ )
459
+
460
+ const _onInputTextChanged = useCallback(
461
+ (_text: string) => {
462
+ if (isTypingDisabled)
463
+ return
464
+
465
+ onInputTextChanged?.(_text)
466
+
467
+ // Only set state if it's not being overridden by a prop.
468
+ if (props.text === undefined)
469
+ setText(_text)
470
+ },
471
+ [onInputTextChanged, isTypingDisabled, props.text]
472
+ )
473
+
474
+ const onInitialLayoutViewLayout = useCallback(
475
+ (e: LayoutChangeEvent) => {
476
+ const { layout } = e.nativeEvent
477
+
478
+ if (layout.height <= 0)
479
+ return
480
+
481
+ notifyInputTextReset()
482
+
483
+ setIsInitialized(true)
484
+ setComposerHeight(minComposerHeight!)
485
+ setText(getTextFromProp(initialText))
486
+ },
487
+ [initialText, minComposerHeight, notifyInputTextReset, getTextFromProp]
488
+ )
489
+
490
+ const inputToolbarFragment = useMemo(() => {
491
+ if (!isInitialized)
492
+ return null
493
+
494
+ const inputToolbarProps = {
495
+ ...props,
496
+ text: getTextFromProp(text!),
497
+ composerHeight: Math.max(minComposerHeight!, composerHeight),
498
+ onSend: _onSend,
499
+ onInputSizeChanged,
500
+ onTextChanged: _onInputTextChanged,
501
+ textInputProps: {
502
+ ...textInputProps,
503
+ ref: textInputRef,
504
+ maxLength: isTypingDisabled ? 0 : maxInputLength,
505
+ },
506
+ }
507
+
508
+ if (renderInputToolbar)
509
+ return renderInputToolbar(inputToolbarProps)
510
+
511
+ return <InputToolbar {...inputToolbarProps} />
512
+ }, [
513
+ isInitialized,
514
+ _onSend,
515
+ getTextFromProp,
516
+ maxInputLength,
517
+ minComposerHeight,
518
+ onInputSizeChanged,
519
+ props,
520
+ text,
521
+ renderInputToolbar,
522
+ composerHeight,
523
+ isTypingDisabled,
524
+ textInputRef,
525
+ textInputProps,
526
+ _onInputTextChanged,
527
+ ])
528
+
529
+ const contextValues = useMemo(
530
+ () => ({
531
+ actionSheet:
532
+ actionSheet ||
533
+ (() => ({
534
+ showActionSheetWithOptions:
535
+ actionSheetRef.current!.showActionSheetWithOptions,
536
+ })),
537
+ getLocale: () => locale,
538
+ }),
539
+ [actionSheet, locale]
540
+ )
541
+
542
+ useEffect(() => {
543
+ if (props.text != null)
544
+ setText(props.text)
545
+ }, [props.text])
546
+
547
+ useEffect(() => {
548
+ if (!inverted && messages?.length)
549
+ setTimeout(() => scrollToBottom(false), 200)
550
+ }, [messages?.length, inverted, scrollToBottom])
551
+
552
+ useAnimatedReaction(
553
+ () => keyboard.height.value,
554
+ (value, prevValue) => {
555
+ if (prevValue && value !== prevValue) {
556
+ const isKeyboardMovingUp = value > prevValue
557
+ if (isKeyboardMovingUp !== trackingKeyboardMovement.value) {
558
+ trackingKeyboardMovement.value = isKeyboardMovingUp
559
+ keyboardOffsetBottom.value = withTiming(
560
+ isKeyboardMovingUp ? insets.bottom : 0,
561
+ {
562
+ duration: 400,
563
+ }
564
+ )
565
+
566
+ if (isKeyboardMovingUp)
567
+ runOnJS(handleTextInputFocusWhenKeyboardShow)()
568
+ else
569
+ runOnJS(handleTextInputFocusWhenKeyboardHide)()
570
+
571
+ if (value === 0) {
572
+ runOnJS(enableTyping)()
573
+ } else {
574
+ runOnJS(disableTyping)()
575
+ runOnJS(debounceEnableTyping)()
576
+ }
577
+ }
578
+ }
579
+ },
580
+ [
581
+ keyboard,
582
+ trackingKeyboardMovement,
583
+ insets,
584
+ handleTextInputFocusWhenKeyboardHide,
585
+ handleTextInputFocusWhenKeyboardShow,
586
+ enableTyping,
587
+ disableTyping,
588
+ debounceEnableTyping,
589
+ ]
590
+ )
591
+
592
+ return (
593
+ <GiftedChatContext.Provider value={contextValues}>
594
+ <ActionSheetProvider ref={actionSheetRef}>
595
+ <View
596
+ testID={TEST_ID.WRAPPER}
597
+ style={[styles.fill, styles.contentContainer]}
598
+ onLayout={onInitialLayoutViewLayout}
599
+ >
600
+ {isInitialized
601
+ ? (
602
+ <Animated.View style={[styles.fill, contentStyleAnim]}>
603
+ {renderMessages}
604
+ {inputToolbarFragment}
605
+ </Animated.View>
606
+ )
607
+ : (
608
+ renderLoading?.()
609
+ )}
610
+ </View>
611
+ </ActionSheetProvider>
612
+ </GiftedChatContext.Provider>
613
+ )
614
+ }
615
+
616
+ GiftedChat.append = <TMessage extends IMessage>(
617
+ currentMessages: TMessage[] = [],
618
+ messages: TMessage[],
619
+ inverted = true
620
+ ) => {
621
+ if (!Array.isArray(messages))
622
+ messages = [messages]
623
+
624
+ return inverted
625
+ ? messages.concat(currentMessages)
626
+ : currentMessages.concat(messages)
627
+ }
628
+
629
+ GiftedChat.prepend = <TMessage extends IMessage>(
630
+ currentMessages: TMessage[] = [],
631
+ messages: TMessage[],
632
+ inverted = true
633
+ ) => {
634
+ if (!Array.isArray(messages))
635
+ messages = [messages]
636
+
637
+ return inverted
638
+ ? currentMessages.concat(messages)
639
+ : messages.concat(currentMessages)
640
+ }
641
+
642
+ const styles = StyleSheet.create({
643
+ fill: {
644
+ flex: 1,
645
+ },
646
+ contentContainer: {
647
+ overflow: 'hidden',
648
+ },
649
+ })
650
+
651
+ export * from './Models'
652
+ export {
653
+ GiftedChat,
654
+ Actions,
655
+ Avatar,
656
+ Bubble,
657
+ SystemMessage,
658
+ MessageImage,
659
+ MessageText,
660
+ Composer,
661
+ Day,
662
+ InputToolbar,
663
+ LoadEarlier,
664
+ Message,
665
+ MessageContainer,
666
+ Send,
667
+ Time,
668
+ GiftedAvatar,
669
+ utils
670
+ }
@@ -0,0 +1,23 @@
1
+ import * as React from 'react'
2
+ import {
3
+ ActionSheetOptions,
4
+ } from '@expo/react-native-action-sheet'
5
+
6
+ export interface IGiftedChatContext {
7
+ actionSheet(): {
8
+ showActionSheetWithOptions: (
9
+ options: ActionSheetOptions,
10
+ callback: (buttonIndex?: number) => void | Promise<void>
11
+ ) => void
12
+ }
13
+ getLocale(): string
14
+ }
15
+
16
+ export const GiftedChatContext = React.createContext<IGiftedChatContext>({
17
+ getLocale: () => 'en',
18
+ actionSheet: () => ({
19
+ showActionSheetWithOptions: () => {},
20
+ }),
21
+ })
22
+
23
+ export const useChatContext = () => React.useContext(GiftedChatContext)