react-native-gifted-chat 2.8.2-alpha.2 → 2.8.2-alpha.4
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 +25 -8
- package/package.json +1 -1
- package/src/Actions.tsx +13 -15
- package/src/GiftedChat/index.tsx +9 -6
- package/src/GiftedChat/types.ts +2 -2
- package/src/InputToolbar.tsx +8 -8
- package/src/LoadEarlier.tsx +5 -5
- package/src/LoadEarlierMessages.tsx +97 -0
- package/src/MessageContainer/components/DayAnimated/index.tsx +7 -7
- package/src/MessageContainer/components/DayAnimated/types.ts +1 -1
- package/src/MessageContainer/index.tsx +14 -19
- package/src/MessageContainer/types.ts +4 -10
- package/src/__tests__/DayAnimated.test.tsx +2 -2
- package/src/__tests__/LoadEarlier.test.tsx +3 -3
- package/src/__tests__/MessageContainer.test.tsx +0 -7
- package/src/__tests__/__snapshots__/LoadEarlier.test.tsx.snap +1 -1
- package/src/types.ts +1 -1
- package/src/utils.ts +0 -2
package/README.md
CHANGED
|
@@ -26,10 +26,7 @@
|
|
|
26
26
|
</p>
|
|
27
27
|
|
|
28
28
|
<p align="center">
|
|
29
|
-
<a href="https://
|
|
30
|
-
</p>
|
|
31
|
-
<p align="center">
|
|
32
|
-
<a href="https://snack.expo.io/@xcarpentier/giftedchat-playground" target="_blank">Snack GiftedChat playground</a>
|
|
29
|
+
<a href="https://snack.expo.dev/@kesha-antonov/gifted-chat-playground" target="_blank">Snack GiftedChat playground</a>
|
|
33
30
|
<img height="18" src="https://snack.expo.io/favicon.ico" />
|
|
34
31
|
</p>
|
|
35
32
|
|
|
@@ -358,10 +355,12 @@ interface QuickReplies {
|
|
|
358
355
|
- **`messageContainerRef`** _(FlatList ref)_ - Ref to the flatlist
|
|
359
356
|
- **`textInputRef`** _(TextInput ref)_ - Ref to the text input
|
|
360
357
|
- **`messages`** _(Array)_ - Messages to display
|
|
358
|
+
- **`messagesContainerStyle`** _(Object)_ - Custom style for the messages container
|
|
361
359
|
- **`isTyping`** _(Bool)_ - Typing Indicator state; default `false`. If you use`renderFooter` it will override this.
|
|
362
360
|
- **`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.
|
|
363
361
|
- **`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`
|
|
364
362
|
- **`text`** _(String)_ - Input text; default is `undefined`, but if specified, it will override GiftedChat's internal state (e.g. for redux; [see notes below](#notes-for-redux))
|
|
363
|
+
- **`initialText`** _(String)_ - Initial text to display in the input field
|
|
365
364
|
- **`onInputTextChanged`** _(Function)_ - Callback when the input text changes
|
|
366
365
|
- **`messageIdGenerator`** _(Function)_ - Generate an id for new messages. Defaults to UUID v4, generated by [uuid](https://github.com/kelektiv/node-uuid)
|
|
367
366
|
- **`user`** _(Object)_ - User sending the messages: `{ _id, name, avatar }`
|
|
@@ -371,9 +370,18 @@ interface QuickReplies {
|
|
|
371
370
|
- **`timeFormat`** _(String)_ - Format to use for rendering times; default is `'LT'` (see [Day.js Format](https://day.js.org/docs/en/display/format))
|
|
372
371
|
- **`dateFormat`** _(String)_ - Format to use for rendering dates; default is `'D MMMM'` (see [Day.js Format](https://day.js.org/docs/en/display/format))
|
|
373
372
|
- **`dateFormatCalendar`** _(Object)_ - Format to use for rendering relative times; default is `{ sameDay: '[Today]' }` (see [Day.js Calendar](https://day.js.org/docs/en/plugin/calendar))
|
|
374
|
-
- **`
|
|
375
|
-
-
|
|
376
|
-
-
|
|
373
|
+
- **`loadEarlierMessagesProps`** _(Object)_ - Props to pass to the LoadEarlierMessages component. The button is only visible when `isAvailable` is `true`. Supports the following props:
|
|
374
|
+
- `isAvailable` - Controls button visibility (default: false)
|
|
375
|
+
- `onPress` - Callback when button is pressed
|
|
376
|
+
- `isLoading` - Display loading indicator (default: false)
|
|
377
|
+
- `isInfiniteScrollEnabled` - Enable infinite scroll up when reaching the top of messages container, automatically calls `onPress` (not yet supported for web)
|
|
378
|
+
- `label` - Override the default "Load earlier messages" text
|
|
379
|
+
- `containerStyle` - Custom style for the button container
|
|
380
|
+
- `wrapperStyle` - Custom style for the button wrapper
|
|
381
|
+
- `textStyle` - Custom style for the button text
|
|
382
|
+
- `activityIndicatorStyle` - Custom style for the loading indicator
|
|
383
|
+
- `activityIndicatorColor` - Color of the loading indicator (default: 'white')
|
|
384
|
+
- `activityIndicatorSize` - Size of the loading indicator (default: 'small')
|
|
377
385
|
- **`renderLoading`** _(Function)_ - Render a loading view when initializing
|
|
378
386
|
- **`renderLoadEarlier`** _(Function)_ - Custom "Load earlier messages" button
|
|
379
387
|
- **`renderAvatar`** _(Function)_ - Custom message avatar; set to `null` to not render any avatar for the message
|
|
@@ -394,13 +402,17 @@ interface QuickReplies {
|
|
|
394
402
|
- **`renderMessageText`** _(Function)_ - Custom message text
|
|
395
403
|
- **`renderMessageImage`** _(Function)_ - Custom message image
|
|
396
404
|
- **`renderMessageVideo`** _(Function)_ - Custom message video
|
|
405
|
+
- **`renderMessageAudio`** _(Function)_ - Custom message audio
|
|
397
406
|
- **`imageProps`** _(Object)_ - Extra props to be passed to the [`<Image>`](https://reactnative.dev/docs/image.html) component created by the default `renderMessageImage`
|
|
407
|
+
- **`imageStyle`** _(Object)_ - Custom style for message images
|
|
398
408
|
- **`videoProps`** _(Object)_ - Extra props to be passed to the video component created by the required `renderMessageVideo`
|
|
399
409
|
- **`isCustomViewBottom`** _(Bool)_ - Determine whether renderCustomView is displayed before or after the text, image and video views; default is `false`
|
|
400
410
|
- **`renderCustomView`** _(Function)_ - Custom view inside the bubble
|
|
401
411
|
- **`renderDay`** _(Function)_ - Custom day above a message
|
|
402
412
|
- **`renderTime`** _(Function)_ - Custom time inside a message
|
|
413
|
+
- **`timeTextStyle`** _(Object)_ - Custom text style for time inside messages (supports left/right styles)
|
|
403
414
|
- **`renderFooter`** _(Function)_ - Custom footer component on the ListView, e.g. `'User is typing...'`; see [App.tsx](/example/App.tsx) for an example. Overrides default typing indicator that triggers when `isTyping` is true.
|
|
415
|
+
- **`renderTypingIndicator`** _(Function)_ - Custom typing indicator component
|
|
404
416
|
- **`renderChatEmpty`** _(Function)_ - Custom component to render in the ListView when messages are empty
|
|
405
417
|
- **`renderChatFooter`** _(Function)_ - Custom component to render below the MessageContainer (separate from the ListView)
|
|
406
418
|
- **`renderInputToolbar`** _(Function)_ - Custom message composer container
|
|
@@ -409,6 +421,9 @@ interface QuickReplies {
|
|
|
409
421
|
- **`renderSend`** _(Function)_ - Custom send button; you can pass children to the original `Send` component quite easily, for example, to use a custom icon ([example](https://github.com/FaridSafi/react-native-gifted-chat/pull/487))
|
|
410
422
|
- **`renderAccessory`** _(Function)_ - Custom second line of actions below the message composer
|
|
411
423
|
- **`onPressActionButton`** _(Function)_ - Callback when the Action button is pressed (if set, the default `actionSheet` will not be used)
|
|
424
|
+
- **`actionSheet`** _(Function)_ - Custom action sheet interface for showing action options
|
|
425
|
+
- **`actions`** _(Array)_ - Custom action options for the input toolbar action button; array of objects with `title` (string) and `action` (function) properties
|
|
426
|
+
- **`actionSheetOptionTintColor`** _(String)_ - Tint color for action sheet options
|
|
412
427
|
- **`focusOnInputWhenOpeningKeyboard`** _(Bool)_ - Focus on <TextInput> automatically when opening the keyboard; default `true`
|
|
413
428
|
- **`minInputToolbarHeight`** _(Integer)_ - Minimum height of the input toolbar; default is `44`
|
|
414
429
|
- **`listProps`** _(Object)_ - Extra props to be passed to the messages [`<FlatList>`](https://reactnative.dev/docs/flatlist.html); some props can't be overridden, see the code in `MessageContainer.render()` for details
|
|
@@ -457,10 +472,12 @@ Example:
|
|
|
457
472
|
* **`onQuickReply`** _(Function)_ - Callback when sending a quick reply (to backend server)
|
|
458
473
|
* **`renderQuickReplies`** _(Function)_ - Custom all quick reply view
|
|
459
474
|
* **`quickReplyStyle`** _(StyleProp<ViewStyle>)_ - Custom quick reply view style
|
|
475
|
+
* **`quickReplyTextStyle`** _(StyleProp<TextStyle>)_ - Custom text style for quick reply buttons
|
|
476
|
+
* **`quickReplyContainerStyle`** _(StyleProp<ViewStyle>)_ - Custom container style for quick replies
|
|
460
477
|
* **`renderQuickReplySend`** _(Function)_ - Custom quick reply **send** view
|
|
461
478
|
* **`shouldUpdateMessage`** _(Function)_ - Lets the message component know when to update outside of normal cases.
|
|
462
|
-
* **`infiniteScroll`** _(Bool)_ - infinite scroll up when reach the top of messages container, automatically call onLoadEarlier function if exist (not yet supported for the web). You need to add `loadEarlier` prop too.
|
|
463
479
|
* **`typingIndicatorStyle`** _(StyleProp<ViewStyle>)_ - Custom style for the TypingIndicator component.
|
|
480
|
+
* **`handleOnScroll`** _(Function)_ - Custom scroll event handler for the message list
|
|
464
481
|
|
|
465
482
|
## Notes for [Redux](https://github.com/reactjs/redux)
|
|
466
483
|
|
package/package.json
CHANGED
package/src/Actions.tsx
CHANGED
|
@@ -14,8 +14,8 @@ import { useChatContext } from './GiftedChatContext'
|
|
|
14
14
|
import stylesCommon from './styles'
|
|
15
15
|
|
|
16
16
|
export interface ActionsProps {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
actions?: Array<{ title: string, action: () => void }>
|
|
18
|
+
actionSheetOptionTintColor?: string
|
|
19
19
|
icon?: () => ReactNode
|
|
20
20
|
wrapperStyle?: StyleProp<ViewStyle>
|
|
21
21
|
iconTextStyle?: StyleProp<TextStyle>
|
|
@@ -24,8 +24,8 @@ export interface ActionsProps {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export function Actions ({
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
actions,
|
|
28
|
+
actionSheetOptionTintColor = Color.optionTintColor,
|
|
29
29
|
icon,
|
|
30
30
|
wrapperStyle,
|
|
31
31
|
iconTextStyle,
|
|
@@ -35,28 +35,26 @@ export function Actions ({
|
|
|
35
35
|
const { actionSheet } = useChatContext()
|
|
36
36
|
|
|
37
37
|
const onActionsPress = useCallback(() => {
|
|
38
|
-
if (!
|
|
38
|
+
if (!actions?.length)
|
|
39
39
|
return
|
|
40
40
|
|
|
41
|
-
const
|
|
42
|
-
const cancelButtonIndex = optionKeys.indexOf('Cancel')
|
|
41
|
+
const titles = actions.map(item => item.title)
|
|
43
42
|
|
|
44
43
|
actionSheet().showActionSheetWithOptions(
|
|
45
44
|
{
|
|
46
|
-
options:
|
|
47
|
-
cancelButtonIndex,
|
|
48
|
-
tintColor:
|
|
45
|
+
options: titles,
|
|
46
|
+
cancelButtonIndex: titles.length - 1,
|
|
47
|
+
tintColor: actionSheetOptionTintColor,
|
|
49
48
|
},
|
|
50
|
-
(buttonIndex
|
|
49
|
+
(buttonIndex?: number) => {
|
|
51
50
|
if (buttonIndex === undefined)
|
|
52
51
|
return
|
|
53
52
|
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
options[key]()
|
|
53
|
+
const item = actions[buttonIndex]
|
|
54
|
+
item.action?.()
|
|
57
55
|
}
|
|
58
56
|
)
|
|
59
|
-
}, [actionSheet,
|
|
57
|
+
}, [actionSheet, actions, actionSheetOptionTintColor])
|
|
60
58
|
|
|
61
59
|
const renderIcon = useCallback(() => {
|
|
62
60
|
if (icon)
|
package/src/GiftedChat/index.tsx
CHANGED
|
@@ -27,6 +27,7 @@ import Animated, {
|
|
|
27
27
|
withTiming,
|
|
28
28
|
runOnJS,
|
|
29
29
|
} from 'react-native-reanimated'
|
|
30
|
+
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
|
30
31
|
import { Actions } from '../Actions'
|
|
31
32
|
import { Avatar } from '../Avatar'
|
|
32
33
|
import Bubble from '../Bubble'
|
|
@@ -36,7 +37,7 @@ import { Day } from '../Day'
|
|
|
36
37
|
import { GiftedAvatar } from '../GiftedAvatar'
|
|
37
38
|
import { GiftedChatContext } from '../GiftedChatContext'
|
|
38
39
|
import { InputToolbar } from '../InputToolbar'
|
|
39
|
-
import {
|
|
40
|
+
import { LoadEarlierMessages } from '../LoadEarlierMessages'
|
|
40
41
|
import Message from '../Message'
|
|
41
42
|
import MessageContainer, { AnimatedList } from '../MessageContainer'
|
|
42
43
|
import { MessageImage } from '../MessageImage'
|
|
@@ -45,10 +46,10 @@ import { Send } from '../Send'
|
|
|
45
46
|
import stylesCommon from '../styles'
|
|
46
47
|
import { SystemMessage } from '../SystemMessage'
|
|
47
48
|
import { Time } from '../Time'
|
|
49
|
+
|
|
48
50
|
import {
|
|
49
51
|
IMessage,
|
|
50
52
|
} from '../types'
|
|
51
|
-
|
|
52
53
|
import * as utils from '../utils'
|
|
53
54
|
import styles from './styles'
|
|
54
55
|
import { GiftedChatProps } from './types'
|
|
@@ -421,9 +422,11 @@ function GiftedChatWrapper<TMessage extends IMessage = IMessage> (props: GiftedC
|
|
|
421
422
|
|
|
422
423
|
return (
|
|
423
424
|
<GestureHandlerRootView style={styles.fill}>
|
|
424
|
-
<
|
|
425
|
-
<
|
|
426
|
-
|
|
425
|
+
<SafeAreaProvider>
|
|
426
|
+
<KeyboardProvider>
|
|
427
|
+
<GiftedChat<TMessage> {...props} />
|
|
428
|
+
</KeyboardProvider>
|
|
429
|
+
</SafeAreaProvider>
|
|
427
430
|
</GestureHandlerRootView>
|
|
428
431
|
)
|
|
429
432
|
}
|
|
@@ -467,7 +470,7 @@ export {
|
|
|
467
470
|
Composer,
|
|
468
471
|
Day,
|
|
469
472
|
InputToolbar,
|
|
470
|
-
|
|
473
|
+
LoadEarlierMessages,
|
|
471
474
|
Message,
|
|
472
475
|
MessageContainer,
|
|
473
476
|
Send,
|
package/src/GiftedChat/types.ts
CHANGED
|
@@ -77,8 +77,8 @@ export interface GiftedChatProps<TMessage extends IMessage> extends Partial<Mess
|
|
|
77
77
|
minComposerHeight?: number
|
|
78
78
|
/* composer min Height */
|
|
79
79
|
maxComposerHeight?: number
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
actions?: Array<{ title: string, action: () => void }>
|
|
81
|
+
actionSheetOptionTintColor?: string
|
|
82
82
|
quickReplyStyle?: StyleProp<ViewStyle>
|
|
83
83
|
quickReplyTextStyle?: StyleProp<TextStyle>
|
|
84
84
|
quickReplyContainerStyle?: StyleProp<ViewStyle>
|
package/src/InputToolbar.tsx
CHANGED
|
@@ -8,8 +8,8 @@ import { Send, SendProps } from './Send'
|
|
|
8
8
|
import { IMessage } from './types'
|
|
9
9
|
|
|
10
10
|
export interface InputToolbarProps<TMessage extends IMessage> {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
actions?: Array<{ title: string, action: () => void }>
|
|
12
|
+
actionSheetOptionTintColor?: string
|
|
13
13
|
containerStyle?: StyleProp<ViewStyle>
|
|
14
14
|
primaryStyle?: StyleProp<ViewStyle>
|
|
15
15
|
accessoryStyle?: StyleProp<ViewStyle>
|
|
@@ -31,8 +31,8 @@ export function InputToolbar<TMessage extends IMessage = IMessage> (
|
|
|
31
31
|
renderComposer,
|
|
32
32
|
renderSend,
|
|
33
33
|
renderAccessory,
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
actions,
|
|
35
|
+
actionSheetOptionTintColor,
|
|
36
36
|
icon,
|
|
37
37
|
wrapperStyle,
|
|
38
38
|
containerStyle,
|
|
@@ -43,8 +43,8 @@ export function InputToolbar<TMessage extends IMessage = IMessage> (
|
|
|
43
43
|
const actionsFragment = useMemo(() => {
|
|
44
44
|
const props = {
|
|
45
45
|
onPressActionButton,
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
actions,
|
|
47
|
+
actionSheetOptionTintColor,
|
|
48
48
|
icon,
|
|
49
49
|
wrapperStyle,
|
|
50
50
|
containerStyle,
|
|
@@ -56,8 +56,8 @@ export function InputToolbar<TMessage extends IMessage = IMessage> (
|
|
|
56
56
|
}, [
|
|
57
57
|
renderActions,
|
|
58
58
|
onPressActionButton,
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
actions,
|
|
60
|
+
actionSheetOptionTintColor,
|
|
61
61
|
icon,
|
|
62
62
|
wrapperStyle,
|
|
63
63
|
containerStyle,
|
package/src/LoadEarlier.tsx
CHANGED
|
@@ -40,7 +40,7 @@ const styles = StyleSheet.create({
|
|
|
40
40
|
},
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
-
export interface
|
|
43
|
+
export interface LoadEarlierMessagesProps {
|
|
44
44
|
isLoadingEarlier?: boolean
|
|
45
45
|
label?: string
|
|
46
46
|
containerStyle?: StyleProp<ViewStyle>
|
|
@@ -49,12 +49,12 @@ export interface LoadEarlierProps {
|
|
|
49
49
|
activityIndicatorStyle?: StyleProp<ViewStyle>
|
|
50
50
|
activityIndicatorColor?: string
|
|
51
51
|
activityIndicatorSize?: number | 'small' | 'large'
|
|
52
|
-
|
|
52
|
+
onPress: () => void
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export const
|
|
55
|
+
export const LoadEarlierMessages: React.FC<LoadEarlierMessagesProps> = ({
|
|
56
56
|
isLoadingEarlier = false,
|
|
57
|
-
|
|
57
|
+
onPress,
|
|
58
58
|
label = 'Load earlier messages',
|
|
59
59
|
containerStyle,
|
|
60
60
|
wrapperStyle,
|
|
@@ -83,7 +83,7 @@ export const LoadEarlier: React.FC<LoadEarlierProps> = ({
|
|
|
83
83
|
return (
|
|
84
84
|
<TouchableOpacity
|
|
85
85
|
style={[styles.container, containerStyle]}
|
|
86
|
-
onPress={
|
|
86
|
+
onPress={onPress}
|
|
87
87
|
disabled={isLoadingEarlier}
|
|
88
88
|
accessibilityRole='button'
|
|
89
89
|
>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
Platform,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
View,
|
|
8
|
+
StyleProp,
|
|
9
|
+
ViewStyle,
|
|
10
|
+
TextStyle,
|
|
11
|
+
} from 'react-native'
|
|
12
|
+
import Color from './Color'
|
|
13
|
+
import { TouchableOpacity } from './components/TouchableOpacity'
|
|
14
|
+
import stylesCommon from './styles'
|
|
15
|
+
|
|
16
|
+
const styles = StyleSheet.create({
|
|
17
|
+
container: {
|
|
18
|
+
alignItems: 'center',
|
|
19
|
+
marginTop: 5,
|
|
20
|
+
marginBottom: 10,
|
|
21
|
+
},
|
|
22
|
+
wrapper: {
|
|
23
|
+
backgroundColor: Color.defaultColor,
|
|
24
|
+
borderRadius: 15,
|
|
25
|
+
height: 30,
|
|
26
|
+
paddingLeft: 10,
|
|
27
|
+
paddingRight: 10,
|
|
28
|
+
},
|
|
29
|
+
text: {
|
|
30
|
+
backgroundColor: Color.backgroundTransparent,
|
|
31
|
+
color: Color.white,
|
|
32
|
+
fontSize: 12,
|
|
33
|
+
},
|
|
34
|
+
activityIndicator: {
|
|
35
|
+
marginTop: Platform.select({
|
|
36
|
+
ios: -14,
|
|
37
|
+
android: -16,
|
|
38
|
+
default: -15,
|
|
39
|
+
}),
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export interface LoadEarlierMessagesProps {
|
|
44
|
+
isAvailable?: boolean
|
|
45
|
+
isLoading?: boolean
|
|
46
|
+
isInfiniteScrollEnabled?: boolean
|
|
47
|
+
label?: string
|
|
48
|
+
containerStyle?: StyleProp<ViewStyle>
|
|
49
|
+
wrapperStyle?: StyleProp<ViewStyle>
|
|
50
|
+
textStyle?: StyleProp<TextStyle>
|
|
51
|
+
activityIndicatorStyle?: StyleProp<ViewStyle>
|
|
52
|
+
activityIndicatorColor?: string
|
|
53
|
+
activityIndicatorSize?: number | 'small' | 'large'
|
|
54
|
+
onPress: () => void
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const LoadEarlierMessages: React.FC<LoadEarlierMessagesProps> = ({
|
|
58
|
+
isLoading = false,
|
|
59
|
+
onPress,
|
|
60
|
+
label = 'Load earlier messages',
|
|
61
|
+
containerStyle,
|
|
62
|
+
wrapperStyle,
|
|
63
|
+
textStyle,
|
|
64
|
+
activityIndicatorColor = 'white',
|
|
65
|
+
activityIndicatorSize = 'small',
|
|
66
|
+
activityIndicatorStyle,
|
|
67
|
+
}) => {
|
|
68
|
+
const loadingContent = useMemo(() => (
|
|
69
|
+
<View>
|
|
70
|
+
<Text style={[styles.text, textStyle, { opacity: 0 }]}>
|
|
71
|
+
{label}
|
|
72
|
+
</Text>
|
|
73
|
+
<ActivityIndicator
|
|
74
|
+
color={activityIndicatorColor!}
|
|
75
|
+
size={activityIndicatorSize!}
|
|
76
|
+
style={[styles.activityIndicator, activityIndicatorStyle]}
|
|
77
|
+
/>
|
|
78
|
+
</View>
|
|
79
|
+
), [label, textStyle, activityIndicatorColor, activityIndicatorSize, activityIndicatorStyle])
|
|
80
|
+
|
|
81
|
+
const labelContent = useMemo(() => (
|
|
82
|
+
<Text style={[styles.text, textStyle]}>{label}</Text>
|
|
83
|
+
), [label, textStyle])
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<TouchableOpacity
|
|
87
|
+
style={[styles.container, containerStyle]}
|
|
88
|
+
onPress={onPress}
|
|
89
|
+
disabled={isLoading}
|
|
90
|
+
accessibilityRole='button'
|
|
91
|
+
>
|
|
92
|
+
<View style={[stylesCommon.centerItems, styles.wrapper, wrapperStyle]}>
|
|
93
|
+
{isLoading ? loadingContent : labelContent}
|
|
94
|
+
</View>
|
|
95
|
+
</TouchableOpacity>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
@@ -11,13 +11,13 @@ import { DayAnimatedProps } from './types'
|
|
|
11
11
|
|
|
12
12
|
export * from './types'
|
|
13
13
|
|
|
14
|
-
const DayAnimated = ({ scrolledY, daysPositions, listHeight, renderDay, messages,
|
|
14
|
+
const DayAnimated = ({ scrolledY, daysPositions, listHeight, renderDay, messages, isLoading, ...rest }: DayAnimatedProps) => {
|
|
15
15
|
const opacity = useSharedValue(0)
|
|
16
16
|
const fadeOutOpacityTimeoutId = useSharedValue<ReturnType<typeof setTimeout> | undefined>(undefined)
|
|
17
17
|
const containerHeight = useSharedValue(0)
|
|
18
18
|
|
|
19
19
|
const isScrolledOnMount = useSharedValue(false)
|
|
20
|
-
const
|
|
20
|
+
const isLoadingAnim = useSharedValue(isLoading)
|
|
21
21
|
|
|
22
22
|
const daysPositionsArray = useDerivedValue(() => Object.values(daysPositions.value).sort((a, b) => a.y - b.y))
|
|
23
23
|
|
|
@@ -57,11 +57,11 @@ const DayAnimated = ({ scrolledY, daysPositions, listHeight, renderDay, messages
|
|
|
57
57
|
const style = useAnimatedStyle(() => ({
|
|
58
58
|
top: interpolate(
|
|
59
59
|
relativeScrolledPositionToBottomOfDay.value,
|
|
60
|
-
[-dayTopOffset, -0.0001, 0,
|
|
61
|
-
[dayTopOffset, dayTopOffset, -containerHeight.value,
|
|
60
|
+
[-dayTopOffset, -0.0001, 0, isLoadingAnim.value ? 0 : containerHeight.value + dayTopOffset],
|
|
61
|
+
[dayTopOffset, dayTopOffset, -containerHeight.value, isLoadingAnim.value ? -containerHeight.value : dayTopOffset],
|
|
62
62
|
'clamp'
|
|
63
63
|
),
|
|
64
|
-
}), [relativeScrolledPositionToBottomOfDay, containerHeight, dayTopOffset,
|
|
64
|
+
}), [relativeScrolledPositionToBottomOfDay, containerHeight, dayTopOffset, isLoadingAnim])
|
|
65
65
|
|
|
66
66
|
const contentStyle = useAnimatedStyle(() => ({
|
|
67
67
|
opacity: opacity.value,
|
|
@@ -111,8 +111,8 @@ const DayAnimated = ({ scrolledY, daysPositions, listHeight, renderDay, messages
|
|
|
111
111
|
)
|
|
112
112
|
|
|
113
113
|
useEffect(() => {
|
|
114
|
-
|
|
115
|
-
}, [
|
|
114
|
+
isLoadingAnim.value = isLoading
|
|
115
|
+
}, [isLoadingAnim, isLoading])
|
|
116
116
|
|
|
117
117
|
const dayContent = useMemo(() => {
|
|
118
118
|
if (!createdAt)
|
|
@@ -3,14 +3,13 @@ import {
|
|
|
3
3
|
View,
|
|
4
4
|
Pressable,
|
|
5
5
|
Text,
|
|
6
|
-
Platform,
|
|
7
6
|
LayoutChangeEvent,
|
|
8
7
|
ListRenderItemInfo,
|
|
9
8
|
CellRendererProps,
|
|
10
9
|
} from 'react-native'
|
|
11
10
|
import { FlatList } from 'react-native-gesture-handler'
|
|
12
11
|
import Animated, { runOnJS, useAnimatedScrollHandler, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'
|
|
13
|
-
import {
|
|
12
|
+
import { LoadEarlierMessages } from '../LoadEarlierMessages'
|
|
14
13
|
import { warning } from '../logging'
|
|
15
14
|
import { ReanimatedScrollEvent } from '../reanimatedCompat'
|
|
16
15
|
|
|
@@ -27,7 +26,7 @@ import { MessageContainerProps, DaysPositions } from './types'
|
|
|
27
26
|
|
|
28
27
|
export * from './types'
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
|
|
31
30
|
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList) as React.ComponentType<any>
|
|
32
31
|
|
|
33
32
|
function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageContainerProps<TMessage>) {
|
|
@@ -36,17 +35,14 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
|
|
|
36
35
|
user,
|
|
37
36
|
isTyping = false,
|
|
38
37
|
renderChatEmpty: renderChatEmptyProp,
|
|
39
|
-
onLoadEarlier,
|
|
40
38
|
inverted = true,
|
|
41
|
-
loadEarlier = false,
|
|
42
39
|
listProps,
|
|
43
40
|
extraData,
|
|
44
41
|
isScrollToBottomEnabled = false,
|
|
45
42
|
scrollToBottomOffset = 200,
|
|
46
43
|
alignTop = false,
|
|
47
44
|
scrollToBottomStyle,
|
|
48
|
-
|
|
49
|
-
isLoadingEarlier = false,
|
|
45
|
+
loadEarlierMessagesProps,
|
|
50
46
|
renderTypingIndicator: renderTypingIndicatorProp,
|
|
51
47
|
renderFooter: renderFooterProp,
|
|
52
48
|
renderLoadEarlier: renderLoadEarlierProp,
|
|
@@ -83,15 +79,15 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
|
|
|
83
79
|
}, [renderFooterProp, renderTypingIndicator, props])
|
|
84
80
|
|
|
85
81
|
const renderLoadEarlier = useCallback(() => {
|
|
86
|
-
if (
|
|
82
|
+
if (loadEarlierMessagesProps?.onPress && loadEarlierMessagesProps?.isAvailable) {
|
|
87
83
|
if (renderLoadEarlierProp)
|
|
88
|
-
return renderLoadEarlierProp(
|
|
84
|
+
return renderLoadEarlierProp(loadEarlierMessagesProps)
|
|
89
85
|
|
|
90
|
-
return <
|
|
86
|
+
return <LoadEarlierMessages {...loadEarlierMessagesProps} />
|
|
91
87
|
}
|
|
92
88
|
|
|
93
89
|
return null
|
|
94
|
-
}, [
|
|
90
|
+
}, [loadEarlierMessagesProps, renderLoadEarlierProp])
|
|
95
91
|
|
|
96
92
|
const changeScrollToBottomVisibility: (isVisible: boolean) => void = useCallbackThrottled((isVisible: boolean) => {
|
|
97
93
|
if (isScrollingDown.value && isVisible)
|
|
@@ -290,14 +286,13 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
|
|
|
290
286
|
|
|
291
287
|
const onEndReached = useCallback(() => {
|
|
292
288
|
if (
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
!
|
|
297
|
-
Platform.OS !== 'web'
|
|
289
|
+
loadEarlierMessagesProps?.isInfiniteScrollEnabled &&
|
|
290
|
+
loadEarlierMessagesProps?.onPress &&
|
|
291
|
+
loadEarlierMessagesProps?.isAvailable &&
|
|
292
|
+
!loadEarlierMessagesProps?.isLoading
|
|
298
293
|
)
|
|
299
|
-
|
|
300
|
-
}, [
|
|
294
|
+
loadEarlierMessagesProps.onPress()
|
|
295
|
+
}, [loadEarlierMessagesProps])
|
|
301
296
|
|
|
302
297
|
const keyExtractor = useCallback((item: unknown) => (item as TMessage)._id.toString(), [])
|
|
303
298
|
|
|
@@ -420,7 +415,7 @@ function MessageContainer<TMessage extends IMessage = IMessage> (props: MessageC
|
|
|
420
415
|
listHeight={listHeight}
|
|
421
416
|
renderDay={renderDayProp}
|
|
422
417
|
messages={messages}
|
|
423
|
-
|
|
418
|
+
isLoading={loadEarlierMessagesProps?.isLoading ?? false}
|
|
424
419
|
/>
|
|
425
420
|
</View>
|
|
426
421
|
)
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from 'react-native'
|
|
7
7
|
import { FlatList } from 'react-native-gesture-handler'
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { LoadEarlierMessagesProps } from '../LoadEarlierMessages'
|
|
10
10
|
import { MessageProps } from '../Message'
|
|
11
11
|
import { ReanimatedScrollEvent } from '../reanimatedCompat'
|
|
12
12
|
import { User, IMessage, Reply, DayProps } from '../types'
|
|
@@ -28,8 +28,6 @@ export interface MessageContainerProps<TMessage extends IMessage = IMessage>
|
|
|
28
28
|
listProps?: ListProps<TMessage>
|
|
29
29
|
/** Reverses display order of messages; default is true */
|
|
30
30
|
inverted?: boolean
|
|
31
|
-
/** Enables the "Load earlier messages" button */
|
|
32
|
-
loadEarlier?: boolean
|
|
33
31
|
/** Controls whether or not the message bubbles appear at the top of the chat */
|
|
34
32
|
alignTop?: boolean
|
|
35
33
|
/** Enables the isScrollToBottomEnabled Component */
|
|
@@ -49,19 +47,15 @@ export interface MessageContainerProps<TMessage extends IMessage = IMessage>
|
|
|
49
47
|
/** Custom day above a message */
|
|
50
48
|
renderDay?(props: DayProps): React.ReactNode
|
|
51
49
|
/** Custom "Load earlier messages" button */
|
|
52
|
-
renderLoadEarlier?(props:
|
|
50
|
+
renderLoadEarlier?(props: LoadEarlierMessagesProps): React.ReactNode
|
|
53
51
|
/** Custom typing indicator */
|
|
54
52
|
renderTypingIndicator?(): React.ReactNode
|
|
55
53
|
/** Scroll to bottom custom component */
|
|
56
54
|
scrollToBottomComponent?(): React.ReactNode
|
|
57
|
-
/** Callback when loading earlier messages */
|
|
58
|
-
onLoadEarlier?(): void
|
|
59
55
|
/** Callback when quick reply is sent */
|
|
60
56
|
onQuickReply?(replies: Reply[]): void
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
/** Display an ActivityIndicator when loading earlier messages */
|
|
64
|
-
isLoadingEarlier?: boolean
|
|
57
|
+
/** Props to pass to the LoadEarlierMessages component. The LoadEarlierMessages button is only visible when isAvailable is true. Includes isAvailable (controls button visibility), isInfiniteScrollEnabled (infinite scroll up when reach the top of messages container, automatically call onPress function if it exists - not yet supported for web), onPress (callback when button is pressed), isLoading (display loading indicator), label (override default "Load earlier messages" text), and styling props (containerStyle, wrapperStyle, textStyle, activityIndicatorStyle, activityIndicatorColor, activityIndicatorSize). */
|
|
58
|
+
loadEarlierMessagesProps?: LoadEarlierMessagesProps
|
|
65
59
|
/** Custom scroll event handler */
|
|
66
60
|
handleOnScroll?(event: ReanimatedScrollEvent): void
|
|
67
61
|
/** Style for TypingIndicator component */
|
|
@@ -23,7 +23,7 @@ describe('DayAnimated', () => {
|
|
|
23
23
|
daysPositions={mockDaysPositions}
|
|
24
24
|
listHeight={mockListHeight}
|
|
25
25
|
messages={[mockMessage]}
|
|
26
|
-
|
|
26
|
+
isLoading={false}
|
|
27
27
|
/>
|
|
28
28
|
)
|
|
29
29
|
expect(toJSON()).toMatchSnapshot()
|
|
@@ -42,7 +42,7 @@ describe('DayAnimated', () => {
|
|
|
42
42
|
daysPositions={mockDaysPositions}
|
|
43
43
|
listHeight={mockListHeight}
|
|
44
44
|
messages={[mockMessage]}
|
|
45
|
-
|
|
45
|
+
isLoading={false}
|
|
46
46
|
renderDay={customRenderDay}
|
|
47
47
|
/>
|
|
48
48
|
)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { render } from '@testing-library/react-native'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { LoadEarlierMessages } from '../GiftedChat'
|
|
5
5
|
|
|
6
|
-
it('should render <
|
|
7
|
-
const { toJSON } = render(<
|
|
6
|
+
it('should render <LoadEarlierMessages /> and compare with snapshot', () => {
|
|
7
|
+
const { toJSON } = render(<LoadEarlierMessages />)
|
|
8
8
|
|
|
9
9
|
expect(toJSON()).toMatchSnapshot()
|
|
10
10
|
})
|
|
@@ -5,13 +5,6 @@ import { MessageContainer } from '../GiftedChat'
|
|
|
5
5
|
import { DEFAULT_TEST_MESSAGE } from './data'
|
|
6
6
|
|
|
7
7
|
it('should render <MessageContainer /> without crashing', () => {
|
|
8
|
-
render(
|
|
9
|
-
<MessageContainer
|
|
10
|
-
messages={[DEFAULT_TEST_MESSAGE]}
|
|
11
|
-
user={{ _id: 'test' }}
|
|
12
|
-
/>
|
|
13
|
-
)
|
|
14
|
-
|
|
15
8
|
// Just verify it renders without throwing
|
|
16
9
|
expect(() => render(
|
|
17
10
|
<MessageContainer
|
package/src/types.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { ComposerProps } from './Composer'
|
|
|
13
13
|
export { DayProps } from './Day'
|
|
14
14
|
export { GiftedAvatarProps } from './GiftedAvatar'
|
|
15
15
|
export { InputToolbarProps } from './InputToolbar'
|
|
16
|
-
export {
|
|
16
|
+
export { LoadEarlierMessagesProps } from './LoadEarlierMessages'
|
|
17
17
|
export { MessageProps } from './Message'
|
|
18
18
|
export { MessageContainerProps } from './MessageContainer'
|
|
19
19
|
export { MessageImageProps } from './MessageImage'
|
package/src/utils.ts
CHANGED
|
@@ -46,7 +46,6 @@ function processCallbackArguments (args: unknown[]): unknown[] {
|
|
|
46
46
|
return params
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
49
|
export function useCallbackDebounced<T extends (...args: any[]) => any>(callbackFunc: T, deps: React.DependencyList = [], time: number): (...args: Parameters<T>) => void {
|
|
51
50
|
const timeoutId = useRef<ReturnType<typeof setTimeout>>(undefined)
|
|
52
51
|
|
|
@@ -70,7 +69,6 @@ export function useCallbackDebounced<T extends (...args: any[]) => any>(callback
|
|
|
70
69
|
return savedFunc
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
|
|
74
72
|
export function useCallbackThrottled<T extends (...args: any[]) => any>(callbackFunc: T, deps: React.DependencyList = [], time: number): (...args: Parameters<T>) => void {
|
|
75
73
|
const lastExecution = useRef<number>(0)
|
|
76
74
|
const timeoutId = useRef<ReturnType<typeof setTimeout>>(undefined)
|