react-native-gifted-chat 3.0.0-alpha.1 → 3.0.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.
- package/CHANGELOG.md +295 -0
- package/README.md +12 -30
- package/package.json +1 -1
- package/src/Actions.tsx +27 -18
- package/src/Composer.tsx +21 -84
- package/src/Constant.ts +0 -9
- package/src/GiftedChat/index.tsx +13 -38
- package/src/GiftedChat/types.ts +8 -6
- package/src/InputToolbar.tsx +6 -11
- package/src/MessageImage.tsx +94 -57
- package/src/{MessageContainer → MessagesContainer}/components/Item/index.tsx +21 -17
- package/src/{MessageContainer → MessagesContainer}/components/Item/types.ts +3 -2
- package/src/{MessageContainer → MessagesContainer}/index.tsx +16 -14
- package/src/{MessageContainer → MessagesContainer}/types.ts +4 -2
- package/src/Send.tsx +40 -22
- package/src/__tests__/DayAnimated.test.tsx +1 -1
- package/src/__tests__/{MessageContainer.test.tsx → MessagesContainer.test.tsx} +7 -7
- package/src/__tests__/__snapshots__/Actions.test.tsx.snap +31 -23
- package/src/__tests__/__snapshots__/Composer.test.tsx.snap +29 -30
- package/src/__tests__/__snapshots__/Constant.test.tsx.snap +0 -2
- package/src/__tests__/__snapshots__/InputToolbar.test.tsx.snap +102 -31
- package/src/__tests__/__snapshots__/MessageImage.test.tsx.snap +251 -0
- package/src/__tests__/__snapshots__/Send.test.tsx.snap +189 -49
- package/src/index.ts +1 -1
- package/src/styles.ts +5 -0
- package/src/types.ts +1 -1
- package/CHANGELOG_2.8.1_to_2.8.2-alpha.5.md +0 -374
- /package/src/{MessageContainer → MessagesContainer}/components/DayAnimated/index.tsx +0 -0
- /package/src/{MessageContainer → MessagesContainer}/components/DayAnimated/styles.ts +0 -0
- /package/src/{MessageContainer → MessagesContainer}/components/DayAnimated/types.ts +0 -0
- /package/src/{MessageContainer → MessagesContainer}/styles.ts +0 -0
package/src/GiftedChat/index.tsx
CHANGED
|
@@ -21,10 +21,10 @@ import localizedFormat from 'dayjs/plugin/localizedFormat'
|
|
|
21
21
|
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
22
22
|
import { KeyboardAvoidingView, KeyboardProvider } from 'react-native-keyboard-controller'
|
|
23
23
|
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
|
24
|
-
import {
|
|
24
|
+
import { TEST_ID } from '../Constant'
|
|
25
25
|
import { GiftedChatContext } from '../GiftedChatContext'
|
|
26
26
|
import { InputToolbar } from '../InputToolbar'
|
|
27
|
-
import {
|
|
27
|
+
import { MessagesContainer, AnimatedList } from '../MessagesContainer'
|
|
28
28
|
import { IMessage } from '../Models'
|
|
29
29
|
import stylesCommon from '../styles'
|
|
30
30
|
import { renderComponentOrElement } from '../utils'
|
|
@@ -55,15 +55,13 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
55
55
|
renderChatFooter,
|
|
56
56
|
renderInputToolbar,
|
|
57
57
|
isInverted = true,
|
|
58
|
-
minComposerHeight = MIN_COMPOSER_HEIGHT,
|
|
59
|
-
maxComposerHeight = MAX_COMPOSER_HEIGHT,
|
|
60
58
|
} = props
|
|
61
59
|
|
|
62
60
|
const actionSheetRef = useRef<ActionSheetProviderRef>(null)
|
|
63
61
|
|
|
64
|
-
const
|
|
65
|
-
() => props.
|
|
66
|
-
[props.
|
|
62
|
+
const messagesContainerRef = useMemo(
|
|
63
|
+
() => props.messagesContainerRef || createRef<AnimatedList<TMessage>>(),
|
|
64
|
+
[props.messagesContainerRef]
|
|
67
65
|
) as RefObject<AnimatedList<TMessage>>
|
|
68
66
|
|
|
69
67
|
const textInputRef = useMemo(
|
|
@@ -72,9 +70,6 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
72
70
|
)
|
|
73
71
|
|
|
74
72
|
const [isInitialized, setIsInitialized] = useState<boolean>(false)
|
|
75
|
-
const [composerHeight, setComposerHeight] = useState<number>(
|
|
76
|
-
minComposerHeight!
|
|
77
|
-
)
|
|
78
73
|
const [text, setText] = useState<string | undefined>(() => props.text || '')
|
|
79
74
|
|
|
80
75
|
const getTextFromProp = useCallback(
|
|
@@ -89,20 +84,20 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
89
84
|
|
|
90
85
|
const scrollToBottom = useCallback(
|
|
91
86
|
(isAnimated = true) => {
|
|
92
|
-
if (!
|
|
87
|
+
if (!messagesContainerRef?.current)
|
|
93
88
|
return
|
|
94
89
|
|
|
95
90
|
if (isInverted) {
|
|
96
|
-
|
|
91
|
+
messagesContainerRef.current.scrollToOffset({
|
|
97
92
|
offset: 0,
|
|
98
93
|
animated: isAnimated,
|
|
99
94
|
})
|
|
100
95
|
return
|
|
101
96
|
}
|
|
102
97
|
|
|
103
|
-
|
|
98
|
+
messagesContainerRef.current.scrollToEnd({ animated: isAnimated })
|
|
104
99
|
},
|
|
105
|
-
[isInverted,
|
|
100
|
+
[isInverted, messagesContainerRef]
|
|
106
101
|
)
|
|
107
102
|
|
|
108
103
|
const renderMessages = useMemo(() => {
|
|
@@ -113,11 +108,11 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
113
108
|
|
|
114
109
|
return (
|
|
115
110
|
<View style={[stylesCommon.fill, messagesContainerStyle]}>
|
|
116
|
-
<
|
|
111
|
+
<MessagesContainer<TMessage>
|
|
117
112
|
{...messagesContainerProps}
|
|
118
113
|
isInverted={isInverted}
|
|
119
114
|
messages={messages}
|
|
120
|
-
forwardRef={
|
|
115
|
+
forwardRef={messagesContainerRef}
|
|
121
116
|
isTyping={isTyping}
|
|
122
117
|
/>
|
|
123
118
|
{renderComponentOrElement(renderChatFooter, {})}
|
|
@@ -129,7 +124,7 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
129
124
|
messages,
|
|
130
125
|
props,
|
|
131
126
|
isInverted,
|
|
132
|
-
|
|
127
|
+
messagesContainerRef,
|
|
133
128
|
renderChatFooter,
|
|
134
129
|
])
|
|
135
130
|
|
|
@@ -142,10 +137,8 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
142
137
|
|
|
143
138
|
notifyInputTextReset()
|
|
144
139
|
|
|
145
|
-
setComposerHeight(minComposerHeight!)
|
|
146
140
|
setText(getTextFromProp(''))
|
|
147
141
|
}, [
|
|
148
|
-
minComposerHeight,
|
|
149
142
|
getTextFromProp,
|
|
150
143
|
textInputRef,
|
|
151
144
|
notifyInputTextReset,
|
|
@@ -175,18 +168,6 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
175
168
|
[messageIdGenerator, onSend, user, resetInputToolbar, scrollToBottom]
|
|
176
169
|
)
|
|
177
170
|
|
|
178
|
-
const onInputSizeChanged = useCallback(
|
|
179
|
-
(size: { height: number }) => {
|
|
180
|
-
const newComposerHeight = Math.max(
|
|
181
|
-
minComposerHeight!,
|
|
182
|
-
Math.min(maxComposerHeight!, size.height)
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
setComposerHeight(newComposerHeight)
|
|
186
|
-
},
|
|
187
|
-
[maxComposerHeight, minComposerHeight]
|
|
188
|
-
)
|
|
189
|
-
|
|
190
171
|
const _onChangeText = useCallback(
|
|
191
172
|
(text: string) => {
|
|
192
173
|
props.textInputProps?.onChangeText?.(text)
|
|
@@ -211,10 +192,9 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
211
192
|
notifyInputTextReset()
|
|
212
193
|
|
|
213
194
|
setIsInitialized(true)
|
|
214
|
-
setComposerHeight(minComposerHeight!)
|
|
215
195
|
setText(getTextFromProp(initialText))
|
|
216
196
|
},
|
|
217
|
-
[isInitialized, initialText,
|
|
197
|
+
[isInitialized, initialText, notifyInputTextReset, getTextFromProp]
|
|
218
198
|
)
|
|
219
199
|
|
|
220
200
|
const inputToolbarFragment = useMemo(() => {
|
|
@@ -224,9 +204,7 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
224
204
|
const inputToolbarProps = {
|
|
225
205
|
...props,
|
|
226
206
|
text: getTextFromProp(text!),
|
|
227
|
-
composerHeight: Math.max(minComposerHeight!, composerHeight),
|
|
228
207
|
onSend: _onSend,
|
|
229
|
-
onInputSizeChanged,
|
|
230
208
|
textInputProps: {
|
|
231
209
|
...textInputProps,
|
|
232
210
|
onChangeText: _onChangeText,
|
|
@@ -242,12 +220,9 @@ function GiftedChat<TMessage extends IMessage = IMessage> (
|
|
|
242
220
|
isInitialized,
|
|
243
221
|
_onSend,
|
|
244
222
|
getTextFromProp,
|
|
245
|
-
minComposerHeight,
|
|
246
|
-
onInputSizeChanged,
|
|
247
223
|
props,
|
|
248
224
|
text,
|
|
249
225
|
renderInputToolbar,
|
|
250
|
-
composerHeight,
|
|
251
226
|
textInputRef,
|
|
252
227
|
textInputProps,
|
|
253
228
|
_onChangeText,
|
package/src/GiftedChat/types.ts
CHANGED
|
@@ -14,8 +14,8 @@ import { AvatarProps } from '../Avatar'
|
|
|
14
14
|
import { BubbleProps } from '../Bubble'
|
|
15
15
|
import { ComposerProps } from '../Composer'
|
|
16
16
|
import { InputToolbarProps } from '../InputToolbar'
|
|
17
|
-
import { AnimatedList, MessageContainerProps } from '../MessageContainer'
|
|
18
17
|
import { MessageImageProps } from '../MessageImage'
|
|
18
|
+
import { AnimatedList, MessagesContainerProps } from '../MessagesContainer'
|
|
19
19
|
import { MessageTextProps } from '../MessageText'
|
|
20
20
|
import {
|
|
21
21
|
IMessage,
|
|
@@ -29,9 +29,9 @@ import { SendProps } from '../Send'
|
|
|
29
29
|
import { SystemMessageProps } from '../SystemMessage'
|
|
30
30
|
import { TimeProps } from '../Time'
|
|
31
31
|
|
|
32
|
-
export interface GiftedChatProps<TMessage extends IMessage> extends Partial<
|
|
33
|
-
/*
|
|
34
|
-
|
|
32
|
+
export interface GiftedChatProps<TMessage extends IMessage> extends Partial<MessagesContainerProps<TMessage>> {
|
|
33
|
+
/* Messages container ref */
|
|
34
|
+
messagesContainerRef?: RefObject<AnimatedList<TMessage>>
|
|
35
35
|
/* text input ref */
|
|
36
36
|
textInputRef?: RefObject<TextInput>
|
|
37
37
|
/* Controls whether or not to show user.name property in the message bubble */
|
|
@@ -115,13 +115,13 @@ export interface GiftedChatProps<TMessage extends IMessage> extends Partial<Mess
|
|
|
115
115
|
renderMessageImage?: (props: MessageImageProps<TMessage>) => React.ReactNode
|
|
116
116
|
/* Custom message video */
|
|
117
117
|
renderMessageVideo?: (props: MessageVideoProps<TMessage>) => React.ReactNode
|
|
118
|
-
/* Custom message
|
|
118
|
+
/* Custom message audio */
|
|
119
119
|
renderMessageAudio?: (props: MessageAudioProps<TMessage>) => React.ReactNode
|
|
120
120
|
/* Custom view inside the bubble */
|
|
121
121
|
renderCustomView?: (props: BubbleProps<TMessage>) => React.ReactNode
|
|
122
122
|
/* Custom time inside a message */
|
|
123
123
|
renderTime?: (props: TimeProps<TMessage>) => React.ReactNode
|
|
124
|
-
/* Custom component to render below the
|
|
124
|
+
/* Custom component to render below the MessagesContainer */
|
|
125
125
|
renderChatFooter?: () => React.ReactNode
|
|
126
126
|
/* Custom message composer container. Can be a component, element, render function, or null */
|
|
127
127
|
renderInputToolbar?: React.ComponentType<InputToolbarProps<TMessage>> | React.ReactElement | ((props: InputToolbarProps<TMessage>) => React.ReactNode) | null
|
|
@@ -143,4 +143,6 @@ export interface GiftedChatProps<TMessage extends IMessage> extends Partial<Mess
|
|
|
143
143
|
renderQuickReplySend?: () => React.ReactNode
|
|
144
144
|
keyboardProviderProps?: React.ComponentProps<typeof KeyboardProvider>
|
|
145
145
|
keyboardAvoidingViewProps?: KeyboardAvoidingViewProps
|
|
146
|
+
/** Enable animated day label that appears on scroll; default is true */
|
|
147
|
+
isDayAnimationEnabled?: boolean
|
|
146
148
|
}
|
package/src/InputToolbar.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { Color } from './Color'
|
|
|
6
6
|
import { Composer, ComposerProps } from './Composer'
|
|
7
7
|
import { IMessage } from './Models'
|
|
8
8
|
import { Send, SendProps } from './Send'
|
|
9
|
+
import { getColorSchemeStyle } from './styles'
|
|
9
10
|
import { renderComponentOrElement } from './utils'
|
|
10
11
|
|
|
11
12
|
export interface InputToolbarProps<TMessage extends IMessage> {
|
|
@@ -13,7 +14,6 @@ export interface InputToolbarProps<TMessage extends IMessage> {
|
|
|
13
14
|
actionSheetOptionTintColor?: string
|
|
14
15
|
containerStyle?: StyleProp<ViewStyle>
|
|
15
16
|
primaryStyle?: StyleProp<ViewStyle>
|
|
16
|
-
accessoryStyle?: StyleProp<ViewStyle>
|
|
17
17
|
renderAccessory?: (props: InputToolbarProps<TMessage>) => React.ReactNode
|
|
18
18
|
renderActions?: (props: ActionsProps) => React.ReactNode
|
|
19
19
|
renderSend?: (props: SendProps<TMessage>) => React.ReactNode
|
|
@@ -88,16 +88,14 @@ export function InputToolbar<TMessage extends IMessage = IMessage> (
|
|
|
88
88
|
if (!renderAccessory)
|
|
89
89
|
return null
|
|
90
90
|
|
|
91
|
-
return (
|
|
92
|
-
<View style={[styles.accessory, props.accessoryStyle]}>
|
|
93
|
-
{renderComponentOrElement(renderAccessory, props)}
|
|
94
|
-
</View>
|
|
95
|
-
)
|
|
91
|
+
return renderComponentOrElement(renderAccessory, props)
|
|
96
92
|
}, [renderAccessory, props])
|
|
97
93
|
|
|
98
94
|
return (
|
|
99
|
-
<View
|
|
100
|
-
|
|
95
|
+
<View
|
|
96
|
+
style={[getColorSchemeStyle(styles, 'container', colorScheme), containerStyle]}
|
|
97
|
+
>
|
|
98
|
+
<View style={[getColorSchemeStyle(styles, 'primary', colorScheme), props.primaryStyle]}>
|
|
101
99
|
{actionsFragment}
|
|
102
100
|
{composerFragment}
|
|
103
101
|
{sendFragment}
|
|
@@ -121,7 +119,4 @@ const styles = StyleSheet.create({
|
|
|
121
119
|
flexDirection: 'row',
|
|
122
120
|
alignItems: 'flex-end',
|
|
123
121
|
},
|
|
124
|
-
accessory: {
|
|
125
|
-
height: 44,
|
|
126
|
-
},
|
|
127
122
|
})
|
package/src/MessageImage.tsx
CHANGED
|
@@ -19,14 +19,91 @@ import Animated, {
|
|
|
19
19
|
useAnimatedStyle,
|
|
20
20
|
useSharedValue,
|
|
21
21
|
withTiming,
|
|
22
|
-
runOnJS,
|
|
23
22
|
Easing,
|
|
23
|
+
runOnJS,
|
|
24
24
|
} from 'react-native-reanimated'
|
|
25
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
25
|
+
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
26
26
|
import Zoom from 'react-native-zoom-reanimated'
|
|
27
27
|
import { IMessage } from './Models'
|
|
28
28
|
import commonStyles from './styles'
|
|
29
29
|
|
|
30
|
+
interface ModalContentProps {
|
|
31
|
+
isVisible: boolean
|
|
32
|
+
imageSource: ImageURISource
|
|
33
|
+
modalImageDimensions: { width: number, height: number } | undefined
|
|
34
|
+
imageProps?: Partial<ImageProps>
|
|
35
|
+
onClose: () => void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ModalContent({ isVisible, imageSource, modalImageDimensions, imageProps, onClose }: ModalContentProps) {
|
|
39
|
+
const insets = useSafeAreaInsets()
|
|
40
|
+
|
|
41
|
+
// Animation values
|
|
42
|
+
const modalOpacity = useSharedValue(0)
|
|
43
|
+
const modalScale = useSharedValue(0.9)
|
|
44
|
+
const modalBorderRadius = useSharedValue(40)
|
|
45
|
+
|
|
46
|
+
const handleModalClose = useCallback(() => {
|
|
47
|
+
modalOpacity.value = withTiming(0, { duration: 200, easing: Easing.in(Easing.ease) })
|
|
48
|
+
modalScale.value = withTiming(0.9, { duration: 200, easing: Easing.in(Easing.ease) }, () => {
|
|
49
|
+
runOnJS(onClose)()
|
|
50
|
+
})
|
|
51
|
+
modalBorderRadius.value = withTiming(40, { duration: 200, easing: Easing.in(Easing.ease) })
|
|
52
|
+
}, [onClose, modalOpacity, modalScale, modalBorderRadius])
|
|
53
|
+
|
|
54
|
+
// Animate on visibility change
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (isVisible) {
|
|
57
|
+
modalOpacity.value = withTiming(1, { duration: 300, easing: Easing.out(Easing.ease) })
|
|
58
|
+
modalScale.value = withTiming(1, { duration: 300, easing: Easing.out(Easing.ease) })
|
|
59
|
+
modalBorderRadius.value = withTiming(0, { duration: 300, easing: Easing.out(Easing.ease) })
|
|
60
|
+
}
|
|
61
|
+
}, [isVisible, modalOpacity, modalScale, modalBorderRadius])
|
|
62
|
+
|
|
63
|
+
const modalAnimatedStyle = useAnimatedStyle(() => ({
|
|
64
|
+
opacity: modalOpacity.value,
|
|
65
|
+
transform: [{ scale: modalScale.value }],
|
|
66
|
+
}), [modalOpacity, modalScale])
|
|
67
|
+
|
|
68
|
+
const modalBorderRadiusStyle = useAnimatedStyle(() => ({
|
|
69
|
+
borderRadius: modalBorderRadius.value,
|
|
70
|
+
}), [modalBorderRadius])
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<>
|
|
74
|
+
<StatusBar animated barStyle='dark-content' />
|
|
75
|
+
<Animated.View style={[styles.modalOverlay, modalAnimatedStyle, modalBorderRadiusStyle]}>
|
|
76
|
+
<GestureHandlerRootView style={commonStyles.fill}>
|
|
77
|
+
<Animated.View style={[commonStyles.fill, styles.modalContent, modalBorderRadiusStyle, { paddingTop: insets.top, paddingBottom: insets.bottom }]}>
|
|
78
|
+
|
|
79
|
+
{/* close button */}
|
|
80
|
+
<View style={styles.closeButtonContainer}>
|
|
81
|
+
<BaseButton onPress={handleModalClose}>
|
|
82
|
+
<View style={styles.closeButtonContent}>
|
|
83
|
+
<Text style={styles.closeButtonIcon}>
|
|
84
|
+
{'X'}
|
|
85
|
+
</Text>
|
|
86
|
+
</View>
|
|
87
|
+
</BaseButton>
|
|
88
|
+
</View>
|
|
89
|
+
|
|
90
|
+
<View style={[commonStyles.fill, commonStyles.centerItems]}>
|
|
91
|
+
<Zoom>
|
|
92
|
+
<Image
|
|
93
|
+
style={modalImageDimensions}
|
|
94
|
+
source={imageSource}
|
|
95
|
+
resizeMode='contain'
|
|
96
|
+
{...imageProps}
|
|
97
|
+
/>
|
|
98
|
+
</Zoom>
|
|
99
|
+
</View>
|
|
100
|
+
</Animated.View>
|
|
101
|
+
</GestureHandlerRootView>
|
|
102
|
+
</Animated.View>
|
|
103
|
+
</>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
30
107
|
export interface MessageImageProps<TMessage extends IMessage> {
|
|
31
108
|
currentMessage: TMessage
|
|
32
109
|
containerStyle?: StyleProp<ViewStyle>
|
|
@@ -46,12 +123,6 @@ export function MessageImage<TMessage extends IMessage = IMessage> ({
|
|
|
46
123
|
const [imageDimensions, setImageDimensions] = useState<{ width: number, height: number }>()
|
|
47
124
|
const windowDimensions = useWindowDimensions()
|
|
48
125
|
|
|
49
|
-
const insets = useSafeAreaInsets()
|
|
50
|
-
|
|
51
|
-
// Animation values
|
|
52
|
-
const modalOpacity = useSharedValue(0)
|
|
53
|
-
const modalScale = useSharedValue(0.9)
|
|
54
|
-
|
|
55
126
|
const imageSource = useMemo(() => ({
|
|
56
127
|
...imageSourceProps,
|
|
57
128
|
uri: currentMessage?.image,
|
|
@@ -70,23 +141,15 @@ export function MessageImage<TMessage extends IMessage = IMessage> ({
|
|
|
70
141
|
|
|
71
142
|
setIsModalVisible(true)
|
|
72
143
|
|
|
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
|
-
|
|
77
144
|
if (isImageSourceChanged.current || !imageDimensions)
|
|
78
145
|
Image.getSize(imageSource.uri, (width, height) => {
|
|
79
146
|
setImageDimensions({ width, height })
|
|
80
147
|
})
|
|
81
|
-
}, [imageSource.uri, imageDimensions
|
|
148
|
+
}, [imageSource.uri, imageDimensions])
|
|
82
149
|
|
|
83
150
|
const handleModalClose = useCallback(() => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
modalScale.value = withTiming(0.9, { duration: 200, easing: Easing.in(Easing.ease) }, () => {
|
|
87
|
-
runOnJS(setIsModalVisible)(false)
|
|
88
|
-
})
|
|
89
|
-
}, [modalOpacity, modalScale])
|
|
151
|
+
setIsModalVisible(false)
|
|
152
|
+
}, [])
|
|
90
153
|
|
|
91
154
|
const handleImageLayout = useCallback((e: LayoutChangeEvent) => {
|
|
92
155
|
setImageDimensions({
|
|
@@ -115,11 +178,6 @@ export function MessageImage<TMessage extends IMessage = IMessage> ({
|
|
|
115
178
|
}
|
|
116
179
|
}, [imageDimensions, windowDimensions.height, windowDimensions.width])
|
|
117
180
|
|
|
118
|
-
const modalAnimatedStyle = useAnimatedStyle(() => ({
|
|
119
|
-
opacity: modalOpacity.value,
|
|
120
|
-
transform: [{ scale: modalScale.value }],
|
|
121
|
-
}), [modalOpacity, modalScale])
|
|
122
|
-
|
|
123
181
|
useEffect(() => {
|
|
124
182
|
isImageSourceChanged.current = true
|
|
125
183
|
}, [imageSource.uri])
|
|
@@ -139,39 +197,17 @@ export function MessageImage<TMessage extends IMessage = IMessage> ({
|
|
|
139
197
|
/>
|
|
140
198
|
</TouchableOpacity>
|
|
141
199
|
|
|
142
|
-
{isModalVisible
|
|
143
|
-
<
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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>
|
|
169
|
-
</View>
|
|
170
|
-
</View>
|
|
171
|
-
</GestureHandlerRootView>
|
|
172
|
-
</Animated.View>
|
|
173
|
-
</OverKeyboardView>
|
|
174
|
-
)}
|
|
200
|
+
<OverKeyboardView visible={isModalVisible}>
|
|
201
|
+
<SafeAreaProvider>
|
|
202
|
+
<ModalContent
|
|
203
|
+
isVisible={isModalVisible}
|
|
204
|
+
imageSource={imageSource}
|
|
205
|
+
modalImageDimensions={modalImageDimensions}
|
|
206
|
+
imageProps={imageProps}
|
|
207
|
+
onClose={handleModalClose}
|
|
208
|
+
/>
|
|
209
|
+
</SafeAreaProvider>
|
|
210
|
+
</OverKeyboardView>
|
|
175
211
|
</View>
|
|
176
212
|
)
|
|
177
213
|
}
|
|
@@ -193,6 +229,7 @@ const styles = StyleSheet.create({
|
|
|
193
229
|
},
|
|
194
230
|
modalContent: {
|
|
195
231
|
backgroundColor: '#000',
|
|
232
|
+
overflow: 'hidden',
|
|
196
233
|
},
|
|
197
234
|
modalImageContainer: {
|
|
198
235
|
width: '100%',
|
|
@@ -103,6 +103,7 @@ export const Item = <TMessage extends IMessage>(props: ItemProps<TMessage>) => {
|
|
|
103
103
|
scrolledY,
|
|
104
104
|
daysPositions,
|
|
105
105
|
listHeight,
|
|
106
|
+
isDayAnimationEnabled,
|
|
106
107
|
...rest
|
|
107
108
|
} = props
|
|
108
109
|
|
|
@@ -121,23 +122,26 @@ export const Item = <TMessage extends IMessage>(props: ItemProps<TMessage>) => {
|
|
|
121
122
|
}, [dayContainerHeight])
|
|
122
123
|
|
|
123
124
|
const style = useAnimatedStyle(() => ({
|
|
124
|
-
opacity:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
125
|
+
opacity:
|
|
126
|
+
isDayAnimationEnabled
|
|
127
|
+
? interpolate(
|
|
128
|
+
relativeScrolledPositionToBottomOfDay.value,
|
|
129
|
+
[
|
|
130
|
+
-dayTopOffset,
|
|
131
|
+
-0.0001,
|
|
132
|
+
0,
|
|
133
|
+
dayContainerHeight.value + dayTopOffset,
|
|
134
|
+
],
|
|
135
|
+
[
|
|
136
|
+
0,
|
|
137
|
+
0,
|
|
138
|
+
1,
|
|
139
|
+
1,
|
|
140
|
+
],
|
|
141
|
+
'clamp'
|
|
142
|
+
)
|
|
143
|
+
: 1,
|
|
144
|
+
}), [relativeScrolledPositionToBottomOfDay, dayContainerHeight, dayTopOffset, isDayAnimationEnabled])
|
|
141
145
|
|
|
142
146
|
return (
|
|
143
147
|
// do not remove key. it helps to get correct position of the day container
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IMessage } from '../../../Models'
|
|
2
|
-
import {
|
|
2
|
+
import { MessagesContainerProps, DaysPositions } from '../../types'
|
|
3
3
|
|
|
4
|
-
export interface ItemProps<TMessage extends IMessage> extends
|
|
4
|
+
export interface ItemProps<TMessage extends IMessage> extends MessagesContainerProps<TMessage> {
|
|
5
5
|
currentMessage: TMessage
|
|
6
6
|
previousMessage?: TMessage
|
|
7
7
|
nextMessage?: TMessage
|
|
@@ -9,4 +9,5 @@ export interface ItemProps<TMessage extends IMessage> extends MessageContainerPr
|
|
|
9
9
|
scrolledY: { value: number }
|
|
10
10
|
daysPositions: { value: DaysPositions }
|
|
11
11
|
listHeight: { value: number }
|
|
12
|
+
isDayAnimationEnabled?: boolean
|
|
12
13
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
|
-
Pressable,
|
|
5
4
|
Text,
|
|
6
5
|
LayoutChangeEvent,
|
|
7
6
|
ListRenderItemInfo,
|
|
8
7
|
CellRendererProps,
|
|
9
8
|
} from 'react-native'
|
|
10
|
-
import { FlatList } from 'react-native-gesture-handler'
|
|
9
|
+
import { FlatList, Pressable } from 'react-native-gesture-handler'
|
|
11
10
|
import Animated, { runOnJS, useAnimatedScrollHandler, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'
|
|
12
11
|
import { LoadEarlierMessages } from '../LoadEarlierMessages'
|
|
13
12
|
import { warning } from '../logging'
|
|
@@ -21,14 +20,13 @@ import { DayAnimated } from './components/DayAnimated'
|
|
|
21
20
|
import { Item } from './components/Item'
|
|
22
21
|
import { ItemProps } from './components/Item/types'
|
|
23
22
|
import styles from './styles'
|
|
24
|
-
import {
|
|
23
|
+
import { MessagesContainerProps, DaysPositions } from './types'
|
|
25
24
|
|
|
26
25
|
export * from './types'
|
|
27
26
|
|
|
28
|
-
|
|
29
27
|
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList) as React.ComponentType<any>
|
|
30
28
|
|
|
31
|
-
export const
|
|
29
|
+
export const MessagesContainer = <TMessage extends IMessage>(props: MessagesContainerProps<TMessage>) => {
|
|
32
30
|
const {
|
|
33
31
|
messages = [],
|
|
34
32
|
user,
|
|
@@ -47,6 +45,7 @@ export const MessageContainer = <TMessage extends IMessage>(props: MessageContai
|
|
|
47
45
|
forwardRef,
|
|
48
46
|
scrollToBottomComponent: scrollToBottomComponentProp,
|
|
49
47
|
renderDay: renderDayProp,
|
|
48
|
+
isDayAnimationEnabled = true,
|
|
50
49
|
} = props
|
|
51
50
|
|
|
52
51
|
const listPropsOnScrollProp = listProps?.onScroll
|
|
@@ -182,6 +181,7 @@ export const MessageContainer = <TMessage extends IMessage>(props: MessageContai
|
|
|
182
181
|
scrolledY,
|
|
183
182
|
daysPositions,
|
|
184
183
|
listHeight,
|
|
184
|
+
isDayAnimationEnabled,
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
return (
|
|
@@ -190,7 +190,7 @@ export const MessageContainer = <TMessage extends IMessage>(props: MessageContai
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
return null
|
|
193
|
-
}, [messages, restProps, isInverted, scrolledY, daysPositions, listHeight, user])
|
|
193
|
+
}, [messages, restProps, isInverted, scrolledY, daysPositions, listHeight, isDayAnimationEnabled, user])
|
|
194
194
|
|
|
195
195
|
const emptyContent = useMemo(() => {
|
|
196
196
|
if (!renderChatEmptyProp)
|
|
@@ -409,14 +409,16 @@ export const MessageContainer = <TMessage extends IMessage>(props: MessageContai
|
|
|
409
409
|
CellRendererComponent={renderCell}
|
|
410
410
|
/>
|
|
411
411
|
<ScrollToBottomWrapper />
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
412
|
+
{isDayAnimationEnabled && (
|
|
413
|
+
<DayAnimated
|
|
414
|
+
scrolledY={scrolledY}
|
|
415
|
+
daysPositions={daysPositions}
|
|
416
|
+
listHeight={listHeight}
|
|
417
|
+
renderDay={renderDayProp}
|
|
418
|
+
messages={messages}
|
|
419
|
+
isLoading={loadEarlierMessagesProps?.isLoading ?? false}
|
|
420
|
+
/>
|
|
421
|
+
)}
|
|
420
422
|
</View>
|
|
421
423
|
)
|
|
422
424
|
}
|
|
@@ -16,7 +16,7 @@ export type ListProps<TMessage extends IMessage = IMessage> = Partial<FlatListPr
|
|
|
16
16
|
|
|
17
17
|
export type AnimatedList<TMessage> = FlatList<TMessage>
|
|
18
18
|
|
|
19
|
-
export interface
|
|
19
|
+
export interface MessagesContainerProps<TMessage extends IMessage = IMessage>
|
|
20
20
|
extends Omit<TypingIndicatorProps, 'style'> {
|
|
21
21
|
/** Ref for the FlatList message container */
|
|
22
22
|
forwardRef?: RefObject<AnimatedList<TMessage>>
|
|
@@ -39,7 +39,7 @@ export interface MessageContainerProps<TMessage extends IMessage = IMessage>
|
|
|
39
39
|
/** Custom component to render when messages are empty */
|
|
40
40
|
renderChatEmpty?: () => React.ReactNode
|
|
41
41
|
/** Custom footer component on the ListView, e.g. 'User is typing...' */
|
|
42
|
-
renderFooter?: (props:
|
|
42
|
+
renderFooter?: (props: MessagesContainerProps<TMessage>) => React.ReactNode
|
|
43
43
|
/** Custom message container */
|
|
44
44
|
renderMessage?: (props: MessageProps<TMessage>) => React.ReactElement
|
|
45
45
|
/** Custom day above a message */
|
|
@@ -56,6 +56,8 @@ export interface MessageContainerProps<TMessage extends IMessage = IMessage>
|
|
|
56
56
|
loadEarlierMessagesProps?: LoadEarlierMessagesProps
|
|
57
57
|
/** Style for TypingIndicator component */
|
|
58
58
|
typingIndicatorStyle?: StyleProp<ViewStyle>
|
|
59
|
+
/** Enable animated day label that appears on scroll; default is true */
|
|
60
|
+
isDayAnimationEnabled?: boolean
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
export interface State {
|