react-native-gifted-chat 3.0.0-alpha.0 → 3.0.0-alpha.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.
package/README.md CHANGED
@@ -1,11 +1,3 @@
1
- <p align="center">
2
- <p align="center">
3
- <a href="https://reactnative.gallery/FaridSafi/gifted-chat">
4
-    <img alt="react-native-gifted-chat" src="https://thumbs.gfycat.com/AbsoluteSadDobermanpinscher-size_restricted.gif" width="260" height="510" />
5
- </a>
6
-
7
- </p>
8
-
9
1
  <h3 align="center">
10
2
  💬 Gifted Chat
11
3
  </h3>
@@ -16,26 +8,59 @@
16
8
  <a href="https://www.npmjs.com/package/react-native-gifted-chat">
17
9
  <img alt="npm downloads" src="https://img.shields.io/npm/dm/react-native-gifted-chat.svg"/></a>
18
10
  <a href="https://www.npmjs.com/package/react-native-gifted-chat"><img alt="npm version" src="https://badge.fury.io/js/react-native-gifted-chat.svg"/></a>
11
+  <a href="https://circleci.com/gh/FaridSafi/react-native-gifted-chat"><img src="https://circleci.com/gh/FaridSafi/react-native-gifted-chat.svg?style=shield" alt="build"></a>
19
12
  </p>
13
+
14
+ ---
15
+
16
+ <h3 align="center">
17
+ 🚀 Try it now in your browser!
18
+ </h3>
20
19
  <p align="center">
21
-  <a href="https://circleci.com/gh/FaridSafi/react-native-gifted-chat"><img src="https://circleci.com/gh/FaridSafi/react-native-gifted-chat.svg?style=shield" alt="build"></a>
22
- <a title='License' href="https://github.com/FaridSafi/react-native-gifted-chat/blob/master/LICENSE" height="18">
23
- <img src='https://img.shields.io/badge/license-MIT-blue.svg' />
20
+ <a href="https://snack.expo.dev/@kesha-antonov/gifted-chat-playground" target="_blank">
21
+ <img src="https://img.shields.io/badge/▶️_Try_GiftedChat_Playground-4630EB?style=for-the-badge&logo=expo&logoColor=white" alt="Try GiftedChat on Expo Snack"/>
24
22
  </a>
25
- <a href="#hire-an-expert"><img src="https://img.shields.io/badge/%F0%9F%92%AA-hire%20an%20expert-brightgreen"/></a>
26
23
  </p>
24
+ <p align="center">
25
+ <strong>No installation required • Interactive examples • Edit and run in real-time</strong>
26
+ </p>
27
+
28
+ <br />
29
+
30
+ <!-- previews -->
27
31
 
28
32
  <p align="center">
29
- <a href="https://snack.expo.dev/@kesha-antonov/gifted-chat-playground" target="_blank">Snack GiftedChat playground</a>
30
- <img height="18" src="https://snack.expo.io/favicon.ico" />
33
+ <img width="200" src="https://github.com/user-attachments/assets/c9da88f5-0b20-471c-8cd7-373bdb767517" />
34
+ &nbsp;&nbsp;&nbsp;&nbsp;
35
+ <img width="200" src="https://github.com/user-attachments/assets/f72b17f1-6c2e-43b5-87e7-477011aa3b07" />
36
+ &nbsp;&nbsp;&nbsp;&nbsp;
37
+ <img width="200" src="https://github.com/user-attachments/assets/86711e73-ee3c-4527-b38d-e4dab47a44fe" />
31
38
  </p>
32
39
 
40
+ ---
41
+
42
+ ## Features
43
+
44
+ - Fully customizable components
45
+ - Composer actions (to attach photos, etc.)
46
+ - Load earlier messages
47
+ - Copy messages to clipboard
48
+ - Touchable links using [react-native-autolink](https://github.com/joshswan/react-native-autolink)
49
+ - Avatar as user's initials
50
+ - Localized dates
51
+ - Multi-line TextInput
52
+ - InputToolbar avoiding keyboard
53
+ - System message
54
+ - Quick Reply messages (bot)
55
+ - Typing indicator
56
+ - react-native-web [web configuration](#react-native-web)
57
+
33
58
  ## Sponsor
34
59
 
35
60
  <p align="center">
36
61
  <br/>
37
62
  <a href="https://www.lereacteur.io" target="_blank">
38
- <img src="https://raw.githubusercontent.com/FaridSafi/react-native-gifted-chat/master/media/logo_sponsor.png">
63
+ <img src="https://raw.githubusercontent.com/FaridSafi/react-native-gifted-chat/master/media/logo_sponsor.png" height="60">
39
64
  </a>
40
65
  <br>
41
66
  <p align="center">
@@ -51,7 +76,7 @@
51
76
  <p align="center">
52
77
  <br/>
53
78
  <a href="https://getstream.io/chat/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_Chat&utm_term=react-native-gifted-chat" target="_blank">
54
- <img src="https://raw.githubusercontent.com/FaridSafi/react-native-gifted-chat/master/media/stream-logo.png" height="50">
79
+ <img src="https://raw.githubusercontent.com/FaridSafi/react-native-gifted-chat/master/media/stream-logo.png" height="40">
55
80
  </a>
56
81
  <br>
57
82
  <p align="center">
@@ -65,7 +90,7 @@
65
90
  <p align="center">
66
91
  <br/>
67
92
  <a href="https://www.ethora.com" target="_blank">
68
- <img src="https://www.dappros.com/wp-content/uploads/2023/12/Ethora-Logo.png" width="300px">
93
+ <img src="https://www.dappros.com/wp-content/uploads/2023/12/Ethora-Logo.png" height="60">
69
94
  </a>
70
95
  <br>
71
96
  <p align="center">
@@ -80,50 +105,26 @@
80
105
  <a href="https://amzn.to/3ZmTyb2" target="_blank">React Key Concepts: Consolidate your knowledge of React’s core features (2nd ed. Edition)</a>
81
106
  </p>
82
107
 
83
- ## Features
84
-
85
- - Fully customizable components
86
- - Composer actions (to attach photos, etc.)
87
- - Load earlier messages
88
- - Copy messages to clipboard
89
- - Touchable links using [react-native-autolink](https://github.com/joshswan/react-native-autolink)
90
- - Avatar as user's initials
91
- - Localized dates
92
- - Multi-line TextInput
93
- - InputToolbar avoiding keyboard
94
- - System message
95
- - Quick Reply messages (bot)
96
- - Typing indicator
97
- - react-native-web [web configuration](#react-native-web)
98
-
99
108
  # Getting started
100
109
 
101
- ## 🚧👷 Important notice
102
-
103
- There's currently WIP going on to make the library more performant, modern in terms of chat UI and easier to maintain. If you have any issues, please report them. If you want to contribute, please do so.
104
-
105
- The most stable version is `2.6.5`. If you want to use the latest version, please be aware that it's a work in progress.
106
-
107
- Readme for this version: [2.6.5 readme](https://github.com/FaridSafi/react-native-gifted-chat/blob/eebab3751fcbe07715135e6e7b2aa3f76a10d8ac/README.md)
108
-
109
110
  ## Installation
110
111
 
111
112
  ### Install dependencies
112
113
 
113
114
  Yarn:
114
115
  ```bash
115
- yarn add react-native-gifted-chat react-native-reanimated react-native-keyboard-controller react-native-gesture-handler react-native-safe-area-context
116
+ yarn add react-native-gifted-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
116
117
  ```
117
118
 
118
119
  Npm:
119
120
 
120
121
  ```bash
121
- npm install --save react-native-gifted-chat react-native-reanimated react-native-keyboard-controller react-native-gesture-handler react-native-safe-area-context
122
+ npm install --save react-native-gifted-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
122
123
  ```
123
124
 
124
125
  Expo
125
126
  ```bash
126
- npx expo install react-native-gifted-chat react-native-reanimated react-native-keyboard-controller react-native-gesture-handler react-native-safe-area-context
127
+ npx expo install react-native-gifted-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
127
128
  ```
128
129
 
129
130
  ### Non-expo users
@@ -136,7 +137,9 @@ npx pod-install
136
137
 
137
138
  Follow guide: [react-native-reanimated](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/#step-2-add-reanimateds-babel-plugin)
138
139
 
139
- ## Simple example
140
+ ## Examples
141
+
142
+ ### Basic usage
140
143
 
141
144
  ```jsx
142
145
  import React, { useState, useCallback, useEffect } from 'react'
@@ -149,7 +152,7 @@ export function Example() {
149
152
 
150
153
  // If you have a tab bar, include its height
151
154
  const tabbarHeight = 50
152
- const keyboardBottomOffset = insets.bottom + tabbarHeight
155
+ const keyboardVerticalOffset = insets.bottom + tabbarHeight
153
156
 
154
157
  useEffect(() => {
155
158
  setMessages([
@@ -179,13 +182,14 @@ export function Example() {
179
182
  user={{
180
183
  _id: 1,
181
184
  }}
182
- keyboardBottomOffset={keyboardBottomOffset}
185
+
186
+ keyboardAvoidingViewProps={{ keyboardVerticalOffset }}
183
187
  />
184
188
  )
185
189
  }
186
190
  ```
187
191
 
188
- ## Different examples
192
+ ### Other examples
189
193
 
190
194
  Check out code of [`examples`](example)
191
195
 
@@ -210,9 +214,8 @@ Messages, system messages, quick replies etc.: [data structure](src/Models.ts)
210
214
 
211
215
  ### Keyboard & Layout
212
216
 
213
- - **`keyboardBottomOffset`** _(Integer)_ - Distance between the bottom of the screen and bottom of the `GiftedChat` component. Useful when you have a tab bar or navigation bar; default is `0`. Needed for correct keyboard avoiding behavior. Without it you might see gap between the keyboard and the input toolbar if you have a tab bar, navigation bar, or safe area.
214
- - **`isKeyboardInternallyHandled`** _(Bool)_ - Determine whether to handle keyboard awareness inside the plugin. If you have your own keyboard handling outside the plugin set this to false; default is `true`
215
- - **`shouldFocusInputOnKeyboardOpen`** _(Bool)_ - Focus on <TextInput> automatically when opening the keyboard; default `true`
217
+ - **`keyboardProviderProps`** _(Object)_ - Props to be passed to the [`KeyboardProvider`](https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-provider) for keyboard handling.
218
+ - **`keyboardAvoidingViewProps`** _(Object)_ - Props to be passed to the [`KeyboardAvoidingView`](https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-avoiding-view). The `behavior` prop defaults to `'padding'`.
216
219
  - **`isAlignedTop`** _(Boolean)_ Controls whether or not the message bubbles appear at the top of the chat (Default is false - bubbles align to bottom)
217
220
  - **`isInverted`** _(Bool)_ - Reverses display order of `messages`; default is `true`
218
221
 
@@ -509,25 +512,16 @@ fireEvent(loadingWrapper, 'layout', {
509
512
  1. Find responses in existing issues
510
513
  1. Try to keep issues for issues
511
514
 
512
- ## License
515
+ ## Hire an expert
513
516
 
514
- - [MIT](LICENSE)
517
+ Looking for a React Native freelance expert with more than 14 years of experience? Contact Xavier from his [website](https://xaviercarpentier.com)
515
518
 
516
519
  ## Author
517
520
 
518
- Feel free to ask me questions on Twitter [@FaridSafi](https://www.twitter.com/FaridSafi)! or [@xcapetir](https://www.twitter.com/xcapetir)!
519
-
520
- ## Contributors
521
-
522
- - Kevin Cooper [cooperka](https://github.com/cooperka)
523
- - Kfir Golan [kfiroo](https://github.com/kfiroo)
524
- - Bruno Cascio [brunocascio](https://github.com/brunocascio)
525
- - Xavier Carpentier [xcarpentier](https://github.com/xcarpentier)
526
- - Kesha Antonov [kesha-antonov](https://github.com/kesha-antonov)
527
- - [more](https://github.com/FaridSafi/react-native-gifted-chat/graphs/contributors)
521
+ Feel free to ask me questions on Twitter [@FaridSafi](https://www.twitter.com/FaridSafi) or [@xcapetir](https://www.twitter.com/xcapetir)
528
522
 
529
- ## Hire an expert!
523
+ ## Maintainer
530
524
 
531
- Looking for a React Native freelance expert with more than 14 years of experience? Contact Xavier from his [website](https://xaviercarpentier.com)!
525
+ Have any questions? Reach out to [Kesha Antonov](https://github.com/kesha-antonov)
532
526
 
533
- Need expert help with React Native Gifted Chat? Reach out to [Kesha Antonov](mailto:innokenty.longway@gmail.com)
527
+ Please note that I'm maintaining this project in my free time for free. If you find any issues, feel free to open them, and I'll do my best to address them as time permits.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-gifted-chat",
3
- "version": "3.0.0-alpha.0",
3
+ "version": "3.0.0-alpha.1",
4
4
  "description": "The most complete chat UI for React Native",
5
5
  "keywords": [
6
6
  "android",
@@ -94,7 +94,7 @@
94
94
  "react-dom": "19.1.0",
95
95
  "react-native": "0.81.5",
96
96
  "react-native-gesture-handler": "^2.29.1",
97
- "react-native-keyboard-controller": "^1.19.5",
97
+ "react-native-keyboard-controller": "^1.19.6",
98
98
  "react-native-reanimated": "^3.19.4",
99
99
  "react-native-safe-area-context": "^5.6.2",
100
100
  "react-test-renderer": "19.1.0",
@@ -19,14 +19,7 @@ import {
19
19
  import dayjs from 'dayjs'
20
20
  import localizedFormat from 'dayjs/plugin/localizedFormat'
21
21
  import { GestureHandlerRootView } from 'react-native-gesture-handler'
22
- import { KeyboardProvider, useReanimatedKeyboardAnimation } from 'react-native-keyboard-controller'
23
- import Animated, {
24
- useAnimatedStyle,
25
- useAnimatedReaction,
26
- useSharedValue,
27
- withTiming,
28
- runOnJS,
29
- } from 'react-native-reanimated'
22
+ import { KeyboardAvoidingView, KeyboardProvider } from 'react-native-keyboard-controller'
30
23
  import { SafeAreaProvider } from 'react-native-safe-area-context'
31
24
  import { MAX_COMPOSER_HEIGHT, MIN_COMPOSER_HEIGHT, TEST_ID } from '../Constant'
32
25
  import { GiftedChatContext } from '../GiftedChatContext'
@@ -61,12 +54,9 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
61
54
  textInputProps,
62
55
  renderChatFooter,
63
56
  renderInputToolbar,
64
- keyboardBottomOffset = 0,
65
- shouldFocusInputOnKeyboardOpen = true,
66
57
  isInverted = true,
67
58
  minComposerHeight = MIN_COMPOSER_HEIGHT,
68
59
  maxComposerHeight = MAX_COMPOSER_HEIGHT,
69
- isKeyboardInternallyHandled = true,
70
60
  } = props
71
61
 
72
62
  const actionSheetRef = useRef<ActionSheetProviderRef>(null)
@@ -81,37 +71,12 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
81
71
  [props.textInputRef]
82
72
  )
83
73
 
84
- const isTextInputWasFocused: RefObject<boolean> = useRef(false)
85
-
86
74
  const [isInitialized, setIsInitialized] = useState<boolean>(false)
87
75
  const [composerHeight, setComposerHeight] = useState<number>(
88
76
  minComposerHeight!
89
77
  )
90
78
  const [text, setText] = useState<string | undefined>(() => props.text || '')
91
79
 
92
- // Always call the hook, but conditionally use its data
93
- const keyboardControllerData = useReanimatedKeyboardAnimation()
94
-
95
- // Create a mock keyboard object when keyboard is not internally handled
96
- const keyboard = useMemo(() => {
97
- if (!isKeyboardInternallyHandled)
98
- return { height: { value: 0 } }
99
-
100
- return keyboardControllerData
101
- }, [isKeyboardInternallyHandled, keyboardControllerData])
102
-
103
- const trackingKeyboardMovement = useSharedValue(false)
104
- const keyboardBottomOffsetAnim = useSharedValue(0)
105
-
106
- const contentStyleAnim = useAnimatedStyle(
107
- () => ({
108
- transform: [
109
- { translateY: keyboard.height.value + keyboardBottomOffsetAnim.value },
110
- ],
111
- }),
112
- [keyboard, keyboardBottomOffsetAnim]
113
- )
114
-
115
80
  const getTextFromProp = useCallback(
116
81
  (fallback: string) => {
117
82
  if (props.text === undefined)
@@ -122,34 +87,6 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
122
87
  [props.text]
123
88
  )
124
89
 
125
- /**
126
- * Store text input focus status when keyboard hide to retrieve
127
- * it afterwards if needed.
128
- * `onKeyboardWillHide` may be called twice in sequence so we
129
- * make a guard condition (eg. showing image picker)
130
- */
131
- const handleTextInputFocusWhenKeyboardHide = useCallback(() => {
132
- if (!isTextInputWasFocused.current)
133
- isTextInputWasFocused.current =
134
- textInputRef.current?.isFocused() || false
135
- }, [textInputRef])
136
-
137
- /**
138
- * Refocus the text input only if it was focused before showing keyboard.
139
- * This is needed in some cases (eg. showing image picker).
140
- */
141
- const handleTextInputFocusWhenKeyboardShow = useCallback(() => {
142
- if (
143
- textInputRef.current &&
144
- isTextInputWasFocused.current &&
145
- !textInputRef.current.isFocused()
146
- )
147
- textInputRef.current.focus()
148
-
149
- // Reset the indicator since the keyboard is shown
150
- isTextInputWasFocused.current = false
151
- }, [textInputRef])
152
-
153
90
  const scrollToBottom = useCallback(
154
91
  (isAnimated = true) => {
155
92
  if (!messageContainerRef?.current)
@@ -334,78 +271,55 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
334
271
  setText(props.text)
335
272
  }, [props.text])
336
273
 
337
- // Only set up keyboard animation when keyboard is internally handled
338
- useAnimatedReaction(
339
- () => isKeyboardInternallyHandled ? keyboard.height.value : 0,
340
- (value, prevValue) => {
341
- // Skip keyboard handling when not internally handled
342
- if (!isKeyboardInternallyHandled)
343
- return
344
-
345
- if (prevValue !== null && value !== prevValue) {
346
- const isKeyboardMovingUp = value < prevValue
347
- if (isKeyboardMovingUp !== trackingKeyboardMovement.value) {
348
- trackingKeyboardMovement.value = isKeyboardMovingUp
349
- keyboardBottomOffsetAnim.value = withTiming(
350
- isKeyboardMovingUp ? keyboardBottomOffset : 0,
351
- {
352
- // If `keyboardBottomOffset` exists, we change the duration to a smaller value to fix the delay in the keyboard animation speed
353
- duration: keyboardBottomOffset ? 150 : 400,
354
- }
355
- )
356
-
357
- if (shouldFocusInputOnKeyboardOpen)
358
- if (isKeyboardMovingUp)
359
- runOnJS(handleTextInputFocusWhenKeyboardShow)()
360
- else
361
- runOnJS(handleTextInputFocusWhenKeyboardHide)()
362
- }
363
- }
364
- },
365
- [
366
- keyboard,
367
- trackingKeyboardMovement,
368
- shouldFocusInputOnKeyboardOpen,
369
- handleTextInputFocusWhenKeyboardHide,
370
- handleTextInputFocusWhenKeyboardShow,
371
- keyboardBottomOffset,
372
- isKeyboardInternallyHandled,
373
- ]
374
- )
375
-
376
274
  return (
377
275
  <GiftedChatContext.Provider value={contextValues}>
378
276
  <ActionSheetProvider ref={actionSheetRef}>
379
- <View
380
- testID={TEST_ID.WRAPPER}
381
- style={[stylesCommon.fill, styles.contentContainer]}
382
- onLayout={onInitialLayoutViewLayout}
277
+ {/* @ts-expect-error */}
278
+ <KeyboardAvoidingView
279
+ behavior='padding'
280
+ style={stylesCommon.fill}
281
+ {...props.keyboardAvoidingViewProps}
383
282
  >
384
- {isInitialized
385
- ? (
386
- <Animated.View style={[stylesCommon.fill, isKeyboardInternallyHandled && contentStyleAnim]}>
387
- {renderMessages}
388
- {inputToolbarFragment}
389
- </Animated.View>
390
- )
391
- : (
392
- renderComponentOrElement(renderLoading, {})
393
- )}
394
- </View>
283
+ <View
284
+ testID={TEST_ID.WRAPPER}
285
+ style={[stylesCommon.fill, styles.contentContainer]}
286
+ onLayout={onInitialLayoutViewLayout}
287
+ >
288
+ {isInitialized
289
+ ? (
290
+ <>
291
+ {renderMessages}
292
+ {inputToolbarFragment}
293
+ </>
294
+ )
295
+ : (
296
+ renderComponentOrElement(renderLoading, {})
297
+ )}
298
+ </View>
299
+ </KeyboardAvoidingView>
395
300
  </ActionSheetProvider>
396
301
  </GiftedChatContext.Provider>
397
302
  )
398
303
  }
399
304
 
400
305
  function GiftedChatWrapper<TMessage extends IMessage = IMessage> (props: GiftedChatProps<TMessage>) {
306
+ const {
307
+ keyboardProviderProps,
308
+ ...rest
309
+ } = props
310
+
401
311
  return (
402
- <KeyboardProvider>
403
- <GestureHandlerRootView style={styles.fill}>
404
- <SafeAreaProvider>
405
- <GiftedChat<TMessage> {...props} />
406
- </SafeAreaProvider>
407
- </GestureHandlerRootView>
408
- </KeyboardProvider>
312
+ <GestureHandlerRootView style={styles.fill}>
313
+ <SafeAreaProvider>
314
+ <KeyboardProvider
315
+ statusBarTranslucent
316
+ navigationBarTranslucent
317
+ {...keyboardProviderProps}
318
+ >
319
+ <GiftedChat<TMessage> {...rest} />
320
+ </KeyboardProvider>
321
+ </SafeAreaProvider>
322
+ </GestureHandlerRootView>
409
323
  )
410
324
  }
411
325
 
@@ -8,6 +8,7 @@ import {
8
8
  import {
9
9
  ActionSheetOptions,
10
10
  } from '@expo/react-native-action-sheet'
11
+ import { KeyboardProvider, KeyboardAvoidingViewProps } from 'react-native-keyboard-controller'
11
12
  import { ActionsProps } from '../Actions'
12
13
  import { AvatarProps } from '../Avatar'
13
14
  import { BubbleProps } from '../Bubble'
@@ -50,8 +51,6 @@ export interface GiftedChatProps<TMessage extends IMessage> extends Partial<Mess
50
51
  dateFormat?: string
51
52
  /* Format to use for rendering relative times; Today - for now. See more: https://day.js.org/docs/en/plugin/calendar */
52
53
  dateFormatCalendar?: object
53
- /* Determine whether to handle keyboard awareness inside the plugin. If you have your own keyboard handling outside the plugin set this to false; default is `true` */
54
- isKeyboardInternallyHandled?: boolean
55
54
  /* Whether to render an avatar for the current user; default is false, only show avatars for other users */
56
55
  isUserAvatarVisible?: boolean
57
56
  /* When false, avatars will only be displayed when a consecutive message is from the same user on the same day; default is false */
@@ -60,10 +59,6 @@ export interface GiftedChatProps<TMessage extends IMessage> extends Partial<Mess
60
59
  isAvatarOnTop?: boolean
61
60
  /* Extra props to be passed to the <Image> component created by the default renderMessageImage */
62
61
  imageProps?: MessageImageProps<TMessage>
63
- /* Distance of the chat from the bottom of the screen (e.g. useful if you display a tab bar); default is 0 */
64
- keyboardBottomOffset?: number
65
- /* Focus on <TextInput> automatically when opening the keyboard; default is true */
66
- shouldFocusInputOnKeyboardOpen?: boolean
67
62
  /* Minimum height of the input toolbar; default is 44 */
68
63
  minInputToolbarHeight?: number
69
64
  /* Extra props to be passed to the <TextInput>. See https://reactnative.dev/docs/textinput */
@@ -146,4 +141,6 @@ export interface GiftedChatProps<TMessage extends IMessage> extends Partial<Mess
146
141
  quickReplies: QuickRepliesProps<TMessage>,
147
142
  ) => React.ReactNode
148
143
  renderQuickReplySend?: () => React.ReactNode
144
+ keyboardProviderProps?: React.ComponentProps<typeof KeyboardProvider>
145
+ keyboardAvoidingViewProps?: KeyboardAvoidingViewProps
149
146
  }
@@ -401,6 +401,8 @@ export const MessageContainer = <TMessage extends IMessage>(props: MessageContai
401
401
  scrollEventThrottle={1}
402
402
  onEndReached={onEndReached}
403
403
  onEndReachedThreshold={0.1}
404
+ keyboardDismissMode='interactive'
405
+ keyboardShouldPersistTaps='handled'
404
406
  {...listProps}
405
407
  onScroll={scrollHandler}
406
408
  onLayout={onLayoutList}
@@ -1,4 +1,4 @@
1
- import { StyleSheet } from 'react-native'
1
+ import { Platform, StyleSheet } from 'react-native'
2
2
  import { Color } from '../Color'
3
3
 
4
4
  export default StyleSheet.create({
@@ -24,9 +24,16 @@ export default StyleSheet.create({
24
24
  width: 40,
25
25
  borderRadius: 20,
26
26
  backgroundColor: Color.white,
27
- shadowColor: Color.black,
28
- shadowOpacity: 0.5,
29
- shadowOffset: { width: 0, height: 0 },
30
- shadowRadius: 1,
27
+ ...Platform.select({
28
+ ios: {
29
+ shadowColor: Color.black,
30
+ shadowOpacity: 0.5,
31
+ shadowOffset: { width: 0, height: 0 },
32
+ shadowRadius: 1,
33
+ },
34
+ android: {
35
+ elevation: 5,
36
+ },
37
+ }),
31
38
  },
32
39
  })
@@ -8,47 +8,25 @@ import {
8
8
  StyleProp,
9
9
  ImageStyle,
10
10
  ImageURISource,
11
- Modal,
12
11
  TouchableOpacity,
13
12
  LayoutChangeEvent,
14
13
  useWindowDimensions,
14
+ StatusBar,
15
15
  } from 'react-native'
16
16
  import { BaseButton, GestureHandlerRootView, Text } from 'react-native-gesture-handler'
17
+ import { OverKeyboardView } from 'react-native-keyboard-controller'
18
+ import Animated, {
19
+ useAnimatedStyle,
20
+ useSharedValue,
21
+ withTiming,
22
+ runOnJS,
23
+ Easing,
24
+ } from 'react-native-reanimated'
17
25
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
18
26
  import Zoom from 'react-native-zoom-reanimated'
19
27
  import { IMessage } from './Models'
20
28
  import commonStyles from './styles'
21
29
 
22
- const styles = StyleSheet.create({
23
- image: {
24
- width: 150,
25
- height: 100,
26
- borderRadius: 13,
27
- margin: 3,
28
- resizeMode: 'cover',
29
- },
30
- modalContent: {
31
- backgroundColor: '#000',
32
- },
33
- modalImageContainer: {
34
- width: '100%',
35
- height: '100%',
36
- },
37
-
38
- closeButtonContainer: {
39
- flexDirection: 'row',
40
- justifyContent: 'flex-end',
41
- },
42
- closeButtonContent: {
43
- padding: 10,
44
- },
45
- closeButtonIcon: {
46
- fontSize: 20,
47
- lineHeight: 20,
48
- color: 'white',
49
- },
50
- })
51
-
52
30
  export interface MessageImageProps<TMessage extends IMessage> {
53
31
  currentMessage: TMessage
54
32
  containerStyle?: StyleProp<ViewStyle>
@@ -70,6 +48,10 @@ export function MessageImage<TMessage extends IMessage = IMessage> ({
70
48
 
71
49
  const insets = useSafeAreaInsets()
72
50
 
51
+ // Animation values
52
+ const modalOpacity = useSharedValue(0)
53
+ const modalScale = useSharedValue(0.9)
54
+
73
55
  const imageSource = useMemo(() => ({
74
56
  ...imageSourceProps,
75
57
  uri: currentMessage?.image,
@@ -88,15 +70,23 @@ export function MessageImage<TMessage extends IMessage = IMessage> ({
88
70
 
89
71
  setIsModalVisible(true)
90
72
 
73
+ // Animate modal entrance
74
+ modalOpacity.value = withTiming(1, { duration: 300, easing: Easing.out(Easing.ease) })
75
+ modalScale.value = withTiming(1, { duration: 300, easing: Easing.out(Easing.ease) })
76
+
91
77
  if (isImageSourceChanged.current || !imageDimensions)
92
78
  Image.getSize(imageSource.uri, (width, height) => {
93
79
  setImageDimensions({ width, height })
94
80
  })
95
- }, [imageSource.uri, imageDimensions])
81
+ }, [imageSource.uri, imageDimensions, modalOpacity, modalScale])
96
82
 
97
83
  const handleModalClose = useCallback(() => {
98
- setIsModalVisible(false)
99
- }, [])
84
+ // Animate modal exit
85
+ modalOpacity.value = withTiming(0, { duration: 200, easing: Easing.in(Easing.ease) })
86
+ modalScale.value = withTiming(0.9, { duration: 200, easing: Easing.in(Easing.ease) }, () => {
87
+ runOnJS(setIsModalVisible)(false)
88
+ })
89
+ }, [modalOpacity, modalScale])
100
90
 
101
91
  const handleImageLayout = useCallback((e: LayoutChangeEvent) => {
102
92
  setImageDimensions({
@@ -125,6 +115,11 @@ export function MessageImage<TMessage extends IMessage = IMessage> ({
125
115
  }
126
116
  }, [imageDimensions, windowDimensions.height, windowDimensions.width])
127
117
 
118
+ const modalAnimatedStyle = useAnimatedStyle(() => ({
119
+ opacity: modalOpacity.value,
120
+ transform: [{ scale: modalScale.value }],
121
+ }), [modalOpacity, modalScale])
122
+
128
123
  useEffect(() => {
129
124
  isImageSourceChanged.current = true
130
125
  }, [imageSource.uri])
@@ -140,42 +135,80 @@ export function MessageImage<TMessage extends IMessage = IMessage> ({
140
135
  style={computedImageStyle}
141
136
  source={imageSource}
142
137
  onLayout={handleImageLayout}
138
+ resizeMode='cover'
143
139
  />
144
140
  </TouchableOpacity>
145
141
 
146
- <Modal
147
- visible={isModalVisible}
148
- onRequestClose={handleModalClose}
149
- animationType='slide'
150
- transparent={false}
151
- >
152
- <GestureHandlerRootView style={commonStyles.fill}>
153
- <View style={[commonStyles.fill, styles.modalContent, { paddingTop: insets.top, paddingBottom: insets.bottom }]}>
154
-
155
- {/* close button */}
156
- <View style={styles.closeButtonContainer}>
157
- <BaseButton onPress={handleModalClose}>
158
- <View style={styles.closeButtonContent}>
159
- <Text style={styles.closeButtonIcon}>
160
- {'X'}
161
- </Text>
142
+ {isModalVisible && (
143
+ <OverKeyboardView visible={isModalVisible}>
144
+ <StatusBar animated barStyle='dark-content' />
145
+ <Animated.View style={[styles.modalOverlay, modalAnimatedStyle]}>
146
+ <GestureHandlerRootView style={commonStyles.fill}>
147
+ <View style={[commonStyles.fill, styles.modalContent, { paddingTop: insets.top, paddingBottom: insets.bottom }]}>
148
+
149
+ {/* close button */}
150
+ <View style={styles.closeButtonContainer}>
151
+ <BaseButton onPress={handleModalClose}>
152
+ <View style={styles.closeButtonContent}>
153
+ <Text style={styles.closeButtonIcon}>
154
+ {'X'}
155
+ </Text>
156
+ </View>
157
+ </BaseButton>
158
+ </View>
159
+
160
+ <View style={[commonStyles.fill, commonStyles.centerItems]}>
161
+ <Zoom>
162
+ <Image
163
+ style={modalImageDimensions}
164
+ source={imageSource}
165
+ resizeMode='contain'
166
+ {...imageProps}
167
+ />
168
+ </Zoom>
162
169
  </View>
163
- </BaseButton>
164
- </View>
165
-
166
- <View style={[commonStyles.fill, commonStyles.centerItems]}>
167
- <Zoom>
168
- <Image
169
- style={modalImageDimensions}
170
- source={imageSource}
171
- resizeMode='contain'
172
- {...imageProps}
173
- />
174
- </Zoom>
175
- </View>
176
- </View>
177
- </GestureHandlerRootView>
178
- </Modal>
170
+ </View>
171
+ </GestureHandlerRootView>
172
+ </Animated.View>
173
+ </OverKeyboardView>
174
+ )}
179
175
  </View>
180
176
  )
181
177
  }
178
+
179
+ const styles = StyleSheet.create({
180
+ image: {
181
+ width: 150,
182
+ height: 100,
183
+ borderRadius: 13,
184
+ margin: 3,
185
+ },
186
+ modalOverlay: {
187
+ position: 'absolute',
188
+ top: 0,
189
+ left: 0,
190
+ right: 0,
191
+ bottom: 0,
192
+ zIndex: 1000,
193
+ },
194
+ modalContent: {
195
+ backgroundColor: '#000',
196
+ },
197
+ modalImageContainer: {
198
+ width: '100%',
199
+ height: '100%',
200
+ },
201
+
202
+ closeButtonContainer: {
203
+ flexDirection: 'row',
204
+ justifyContent: 'flex-end',
205
+ },
206
+ closeButtonContent: {
207
+ padding: 20,
208
+ },
209
+ closeButtonIcon: {
210
+ fontSize: 20,
211
+ lineHeight: 20,
212
+ color: 'white',
213
+ },
214
+ })
@@ -1,7 +1,6 @@
1
1
  import React from 'react'
2
2
  import { render } from '@testing-library/react-native'
3
3
 
4
- import { useReanimatedKeyboardAnimation } from 'react-native-keyboard-controller'
5
4
  import { GiftedChat } from '..'
6
5
 
7
6
  const messages = [
@@ -17,32 +16,6 @@ const messages = [
17
16
  ]
18
17
 
19
18
  it('should render <GiftedChat/> and compare with snapshot', () => {
20
- (useReanimatedKeyboardAnimation as jest.Mock).mockReturnValue({
21
- height: {
22
- value: 0,
23
- },
24
- })
25
-
26
- const { toJSON } = render(
27
- <GiftedChat
28
- messages={messages}
29
- onSend={() => {}}
30
- user={{
31
- _id: 1,
32
- }}
33
- />
34
- )
35
-
36
- expect(toJSON()).toMatchSnapshot()
37
- })
38
-
39
- it('should render <GiftedChat/> with isKeyboardInternallyHandled=false', () => {
40
- (useReanimatedKeyboardAnimation as jest.Mock).mockReturnValue({
41
- height: {
42
- value: 0,
43
- },
44
- })
45
-
46
19
  const { toJSON } = render(
47
20
  <GiftedChat
48
21
  messages={messages}
@@ -50,7 +23,6 @@ it('should render <GiftedChat/> with isKeyboardInternallyHandled=false', () => {
50
23
  user={{
51
24
  _id: 1,
52
25
  }}
53
- isKeyboardInternallyHandled={false}
54
26
  />
55
27
  )
56
28
 
@@ -1,48 +1,16 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`should render <GiftedChat/> and compare with snapshot 1`] = `
4
- <KeyboardProvider>
5
- <View
6
- style={
7
- {
8
- "flex": 1,
9
- }
10
- }
11
- >
12
- <View
13
- style={
14
- {
15
- "flex": 1,
16
- }
17
- }
18
- >
19
- <View
20
- onLayout={[Function]}
21
- style={
22
- [
23
- {
24
- "flex": 1,
25
- },
26
- {
27
- "overflow": "hidden",
28
- },
29
- ]
30
- }
31
- testID="GC_WRAPPER"
32
- />
33
- </View>
34
- </View>
35
- </KeyboardProvider>
36
- `;
37
-
38
- exports[`should render <GiftedChat/> with isKeyboardInternallyHandled=false 1`] = `
39
- <KeyboardProvider>
40
- <View
41
- style={
42
- {
43
- "flex": 1,
44
- }
4
+ <View
5
+ style={
6
+ {
7
+ "flex": 1,
45
8
  }
9
+ }
10
+ >
11
+ <KeyboardProvider
12
+ navigationBarTranslucent={true}
13
+ statusBarTranslucent={true}
46
14
  >
47
15
  <View
48
16
  style={
@@ -52,20 +20,29 @@ exports[`should render <GiftedChat/> with isKeyboardInternallyHandled=false 1`]
52
20
  }
53
21
  >
54
22
  <View
55
- onLayout={[Function]}
23
+ behavior="padding"
56
24
  style={
57
- [
58
- {
59
- "flex": 1,
60
- },
61
- {
62
- "overflow": "hidden",
63
- },
64
- ]
25
+ {
26
+ "flex": 1,
27
+ }
65
28
  }
66
- testID="GC_WRAPPER"
67
- />
29
+ >
30
+ <View
31
+ onLayout={[Function]}
32
+ style={
33
+ [
34
+ {
35
+ "flex": 1,
36
+ },
37
+ {
38
+ "overflow": "hidden",
39
+ },
40
+ ]
41
+ }
42
+ testID="GC_WRAPPER"
43
+ />
44
+ </View>
68
45
  </View>
69
- </View>
70
- </KeyboardProvider>
46
+ </KeyboardProvider>
47
+ </View>
71
48
  `;
@@ -38,6 +38,7 @@ exports[`MessageImage should render <MessageImage /> and compare with snapshot
38
38
  >
39
39
  <Image
40
40
  onLayout={[Function]}
41
+ resizeMode="cover"
41
42
  source={
42
43
  {
43
44
  "uri": "url://to/image.png",
@@ -49,7 +50,6 @@ exports[`MessageImage should render <MessageImage /> and compare with snapshot
49
50
  "borderRadius": 13,
50
51
  "height": 100,
51
52
  "margin": 3,
52
- "resizeMode": "cover",
53
53
  "width": 150,
54
54
  },
55
55
  undefined,