react-native-gifted-chat 3.2.2 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +450 -156
  2. package/package.json +9 -8
  3. package/src/Bubble/index.tsx +34 -2
  4. package/src/Bubble/types.ts +17 -4
  5. package/src/Composer.tsx +1 -2
  6. package/src/Day/index.tsx +2 -2
  7. package/src/Day/types.ts +3 -2
  8. package/src/GiftedAvatar.tsx +1 -1
  9. package/src/GiftedChat/index.tsx +109 -23
  10. package/src/GiftedChat/types.ts +9 -3
  11. package/src/InputToolbar.tsx +62 -8
  12. package/src/Message/index.tsx +181 -21
  13. package/src/Message/types.ts +4 -0
  14. package/src/MessageReply.tsx +160 -0
  15. package/src/MessageText.tsx +2 -2
  16. package/src/MessagesContainer/components/DayAnimated/index.tsx +5 -1
  17. package/src/MessagesContainer/components/Item/index.tsx +82 -47
  18. package/src/MessagesContainer/index.tsx +30 -19
  19. package/src/MessagesContainer/styles.ts +2 -0
  20. package/src/MessagesContainer/types.ts +30 -3
  21. package/src/Models.ts +3 -0
  22. package/src/Reply/index.ts +1 -0
  23. package/src/Reply/types.ts +80 -0
  24. package/src/ReplyPreview.tsx +132 -0
  25. package/src/Send.tsx +8 -3
  26. package/src/SystemMessage.tsx +22 -16
  27. package/src/__tests__/MessageReply.test.tsx +54 -0
  28. package/src/__tests__/ReplyPreview.test.tsx +41 -0
  29. package/src/__tests__/__snapshots__/GiftedChat.test.tsx.snap +69 -42
  30. package/src/__tests__/__snapshots__/InputToolbar.test.tsx.snap +11 -15
  31. package/src/__tests__/__snapshots__/MessageImage.test.tsx.snap +24 -18
  32. package/src/__tests__/__snapshots__/MessageReply.test.tsx.snap +181 -0
  33. package/src/__tests__/__snapshots__/ReplyPreview.test.tsx.snap +403 -0
  34. package/src/__tests__/__snapshots__/Send.test.tsx.snap +3 -0
  35. package/src/__tests__/__snapshots__/SystemMessage.test.tsx.snap +36 -25
  36. package/src/components/MessageReply.tsx +156 -0
  37. package/src/components/ReplyPreview.tsx +230 -0
  38. package/src/index.ts +6 -1
  39. package/src/types.ts +17 -16
  40. package/src/utils.ts +11 -3
  41. package/CHANGELOG.md +0 -364
  42. package/src/reanimatedCompat.ts +0 -27
@@ -1,9 +1,13 @@
1
- import React, { useCallback } from 'react'
2
- import { View } from 'react-native'
1
+ import React, { useCallback, useMemo, useRef } from 'react'
2
+ import { View, StyleSheet } from 'react-native'
3
+ import ReanimatedSwipeable, { SwipeableMethods } from 'react-native-gesture-handler/ReanimatedSwipeable'
4
+ import Animated, { SharedValue, useAnimatedStyle } from 'react-native-reanimated'
3
5
 
4
6
  import { Avatar } from '../Avatar'
5
7
  import { Bubble } from '../Bubble'
8
+ import { Color } from '../Color'
6
9
  import { IMessage } from '../Models'
10
+ import { SwipeToReplyProps } from '../Reply'
7
11
  import { getStyleWithPosition } from '../styles'
8
12
  import { SystemMessage } from '../SystemMessage'
9
13
  import { isSameUser, renderComponentOrElement } from '../utils'
@@ -12,6 +16,44 @@ import { MessageProps } from './types'
12
16
 
13
17
  export * from './types'
14
18
 
19
+ interface ReplyIconProps {
20
+ progress: SharedValue<number>
21
+ translation: SharedValue<number>
22
+ direction: 'left' | 'right'
23
+ position: 'left' | 'right'
24
+ style?: SwipeToReplyProps<IMessage>['actionContainerStyle']
25
+ }
26
+
27
+ const ReplyIcon = ({ progress, direction, position, style }: ReplyIconProps) => {
28
+ const animatedStyle = useAnimatedStyle(() => {
29
+ 'worklet'
30
+
31
+ const scale = Math.min(progress.value, 1)
32
+ // When swiping left (icon on right), icon should move left (negative)
33
+ // When swiping right (icon on left), icon should move right (positive)
34
+ const translateX = direction === 'left'
35
+ ? Math.min(progress.value * -12, -12)
36
+ : Math.max(progress.value * 12, 12)
37
+
38
+ return {
39
+ transform: [{ scale }, { translateX }],
40
+ marginLeft: position === 'left' ? 0 : 16,
41
+ marginRight: position === 'right' ? 0 : 16,
42
+ }
43
+ })
44
+
45
+ return (
46
+ <Animated.View style={[localStyles.swipeActionContainer, animatedStyle, style]}>
47
+ <View style={localStyles.replyIconContainer}>
48
+ <View style={localStyles.replyIcon}>
49
+ <View style={localStyles.replyIconArrow} />
50
+ <View style={localStyles.replyIconLine} />
51
+ </View>
52
+ </View>
53
+ </Animated.View>
54
+ )
55
+ }
56
+
15
57
  export const Message = <TMessage extends IMessage = IMessage>(props: MessageProps<TMessage>) => {
16
58
  const {
17
59
  currentMessage,
@@ -23,13 +65,24 @@ export const Message = <TMessage extends IMessage = IMessage>(props: MessageProp
23
65
  containerStyle,
24
66
  user,
25
67
  isUserAvatarVisible,
68
+ swipeToReply,
26
69
  } = props
27
70
 
71
+ // Extract swipe props
72
+ const isSwipeToReplyEnabled = swipeToReply?.isEnabled ?? false
73
+ const swipeToReplyDirection = swipeToReply?.direction ?? 'left'
74
+ const onSwipeToReply = swipeToReply?.onSwipe
75
+ const renderSwipeToReplyActionProp = swipeToReply?.renderAction
76
+ const swipeToReplyActionContainerStyle = swipeToReply?.actionContainerStyle
77
+
78
+ const swipeableRef = useRef<SwipeableMethods>(null)
79
+
28
80
  const renderBubble = useCallback(() => {
29
81
  const {
30
82
  /* eslint-disable @typescript-eslint/no-unused-vars */
31
83
  containerStyle,
32
84
  onMessageLayout,
85
+ swipeToReply,
33
86
  /* eslint-enable @typescript-eslint/no-unused-vars */
34
87
  ...rest
35
88
  } = props
@@ -45,6 +98,7 @@ export const Message = <TMessage extends IMessage = IMessage>(props: MessageProp
45
98
  /* eslint-disable @typescript-eslint/no-unused-vars */
46
99
  containerStyle,
47
100
  onMessageLayout,
101
+ swipeToReply,
48
102
  /* eslint-enable @typescript-eslint/no-unused-vars */
49
103
  ...rest
50
104
  } = props
@@ -71,6 +125,7 @@ export const Message = <TMessage extends IMessage = IMessage>(props: MessageProp
71
125
  /* eslint-disable @typescript-eslint/no-unused-vars */
72
126
  containerStyle,
73
127
  onMessageLayout,
128
+ swipeToReply,
74
129
  /* eslint-enable @typescript-eslint/no-unused-vars */
75
130
  ...rest
76
131
  } = props
@@ -83,31 +138,136 @@ export const Message = <TMessage extends IMessage = IMessage>(props: MessageProp
83
138
  isUserAvatarVisible,
84
139
  ])
85
140
 
141
+ const renderSwipeAction = useCallback((
142
+ progress: SharedValue<number>,
143
+ translation: SharedValue<number>
144
+ ) => {
145
+ if (renderSwipeToReplyActionProp)
146
+ return renderSwipeToReplyActionProp(progress, translation, position)
147
+
148
+ return (
149
+ <ReplyIcon
150
+ progress={progress}
151
+ translation={translation}
152
+ direction={swipeToReplyDirection}
153
+ position={position}
154
+ style={swipeToReplyActionContainerStyle}
155
+ />
156
+ )
157
+ }, [position, renderSwipeToReplyActionProp, swipeToReplyDirection, swipeToReplyActionContainerStyle])
158
+
159
+ const handleSwipeableOpen = useCallback(() => {
160
+ swipeableRef.current?.close()
161
+ }, [])
162
+
163
+ const handleSwipeableWillOpen = useCallback(() => {
164
+ if (onSwipeToReply && currentMessage)
165
+ onSwipeToReply(currentMessage)
166
+ }, [onSwipeToReply, currentMessage])
167
+
168
+ const sameUser = useMemo(() =>
169
+ isSameUser(currentMessage, nextMessage!)
170
+ , [currentMessage, nextMessage])
171
+
172
+ const messageContent = useMemo(() => {
173
+ if (currentMessage?.system)
174
+ return renderSystemMessage()
175
+
176
+ return (
177
+ <View
178
+ style={[
179
+ getStyleWithPosition(styles, 'container', position),
180
+ { marginBottom: sameUser ? 2 : 10 },
181
+ !props.isInverted && { marginBottom: 2 },
182
+ containerStyle?.[position],
183
+ ]}
184
+ >
185
+ {position === 'left' && renderAvatar()}
186
+ {renderBubble()}
187
+ {position === 'right' && renderAvatar()}
188
+ </View>
189
+ )
190
+ }, [
191
+ currentMessage?.system,
192
+ renderSystemMessage,
193
+ position,
194
+ sameUser,
195
+ props.isInverted,
196
+ containerStyle,
197
+ renderAvatar,
198
+ renderBubble,
199
+ ])
200
+
86
201
  if (!currentMessage)
87
202
  return null
88
203
 
89
- const sameUser = isSameUser(currentMessage, nextMessage!)
204
+ // Don't wrap system messages in Swipeable
205
+ if (currentMessage.system || !isSwipeToReplyEnabled)
206
+ return (
207
+ <View onLayout={onMessageLayout}>
208
+ {messageContent}
209
+ </View>
210
+ )
90
211
 
91
212
  return (
92
213
  <View onLayout={onMessageLayout}>
93
- {currentMessage.system
94
- ? (
95
- renderSystemMessage()
96
- )
97
- : (
98
- <View
99
- style={[
100
- getStyleWithPosition(styles, 'container', position),
101
- { marginBottom: sameUser ? 2 : 10 },
102
- !props.isInverted && { marginBottom: 2 },
103
- containerStyle?.[position],
104
- ]}
105
- >
106
- {position === 'left' && renderAvatar()}
107
- {renderBubble()}
108
- {position === 'right' && renderAvatar()}
109
- </View>
110
- )}
214
+ <ReanimatedSwipeable
215
+ ref={swipeableRef}
216
+ friction={2}
217
+ overshootFriction={8}
218
+ renderRightActions={swipeToReplyDirection === 'left' ? renderSwipeAction : undefined}
219
+ renderLeftActions={swipeToReplyDirection === 'right' ? renderSwipeAction : undefined}
220
+ onSwipeableOpen={handleSwipeableOpen}
221
+ onSwipeableWillOpen={handleSwipeableWillOpen}
222
+ >
223
+ {messageContent}
224
+ </ReanimatedSwipeable>
111
225
  </View>
112
226
  )
113
227
  }
228
+
229
+ const localStyles = StyleSheet.create({
230
+ swipeActionContainer: {
231
+ width: 40,
232
+ justifyContent: 'center',
233
+ alignItems: 'center',
234
+ },
235
+ replyIconContainer: {
236
+ width: 28,
237
+ height: 28,
238
+ borderRadius: 14,
239
+ backgroundColor: Color.defaultBlue,
240
+ justifyContent: 'center',
241
+ alignItems: 'center',
242
+ },
243
+ replyIcon: {
244
+ width: 14,
245
+ height: 10,
246
+ transform: [{ scaleX: -1 }],
247
+ },
248
+ replyIconArrow: {
249
+ position: 'absolute',
250
+ top: 0,
251
+ left: 0,
252
+ width: 0,
253
+ height: 0,
254
+ borderTopWidth: 5,
255
+ borderTopColor: 'transparent',
256
+ borderBottomWidth: 5,
257
+ borderBottomColor: 'transparent',
258
+ borderRightWidth: 6,
259
+ borderRightColor: Color.white,
260
+ },
261
+ replyIconLine: {
262
+ position: 'absolute',
263
+ top: 3,
264
+ left: 5,
265
+ width: 9,
266
+ height: 4,
267
+ borderTopWidth: 2,
268
+ borderRightWidth: 2,
269
+ borderTopColor: Color.white,
270
+ borderRightColor: Color.white,
271
+ borderTopRightRadius: 4,
272
+ },
273
+ })
@@ -1,8 +1,10 @@
1
1
  import { ViewStyle, LayoutChangeEvent } from 'react-native'
2
+
2
3
  import { AvatarProps } from '../Avatar'
3
4
  import { BubbleProps } from '../Bubble'
4
5
  import { DayProps } from '../Day'
5
6
  import { IMessage, User, LeftRightStyle } from '../Models'
7
+ import { SwipeToReplyProps } from '../Reply'
6
8
  import { SystemMessageProps } from '../SystemMessage'
7
9
 
8
10
  export interface MessageProps<TMessage extends IMessage> {
@@ -19,4 +21,6 @@ export interface MessageProps<TMessage extends IMessage> {
19
21
  renderSystemMessage?: (props: SystemMessageProps<TMessage>) => React.ReactNode
20
22
  renderAvatar?: (props: AvatarProps<TMessage>) => React.ReactNode
21
23
  onMessageLayout?: (event: LayoutChangeEvent) => void
24
+ /** Swipe to reply configuration */
25
+ swipeToReply?: SwipeToReplyProps<TMessage>
22
26
  }
@@ -0,0 +1,160 @@
1
+ import React, { useMemo, useCallback } from 'react'
2
+ import {
3
+ StyleSheet,
4
+ ViewStyle,
5
+ View,
6
+ Pressable,
7
+ Image,
8
+ TextStyle,
9
+ StyleProp,
10
+ ImageStyle,
11
+ } from 'react-native'
12
+
13
+ import { Text } from 'react-native-gesture-handler'
14
+ import { Color } from './Color'
15
+ import { LeftRightStyle, IMessage, ReplyMessage } from './Models'
16
+ import { getStyleWithPosition } from './styles'
17
+
18
+ export interface MessageReplyProps<TMessage extends IMessage> {
19
+ position?: 'left' | 'right'
20
+ currentMessage: TMessage
21
+ containerStyle?: LeftRightStyle<ViewStyle>
22
+ contentContainerStyle?: LeftRightStyle<ViewStyle>
23
+ imageStyle?: StyleProp<ImageStyle>
24
+ usernameStyle?: StyleProp<TextStyle>
25
+ textStyle?: StyleProp<TextStyle>
26
+ onPress?: (replyMessage: ReplyMessage) => void
27
+ }
28
+
29
+ export function MessageReply<TMessage extends IMessage = IMessage> ({
30
+ currentMessage,
31
+ position = 'left',
32
+ containerStyle,
33
+ contentContainerStyle,
34
+ imageStyle,
35
+ usernameStyle,
36
+ textStyle,
37
+ onPress: onPressProp,
38
+ }: MessageReplyProps<TMessage>) {
39
+ const handlePress = useCallback(() => {
40
+ if (!onPressProp || !currentMessage.replyMessage)
41
+ return
42
+
43
+ onPressProp(currentMessage.replyMessage)
44
+ }, [onPressProp, currentMessage.replyMessage])
45
+
46
+ const containerStyleMemo = useMemo(() => [
47
+ styles.container,
48
+ getStyleWithPosition(styles, 'container', position),
49
+ containerStyle?.[position],
50
+ ], [position, containerStyle])
51
+
52
+ const contentContainerStyleMemo = useMemo(() => [
53
+ styles.contentContainer,
54
+ contentContainerStyle?.[position],
55
+ ], [position, contentContainerStyle])
56
+
57
+ const imageStyleMemo = useMemo(() => [
58
+ styles.image,
59
+ imageStyle,
60
+ ], [imageStyle])
61
+
62
+ const usernameStyleMemo = useMemo(() => [
63
+ styles.username,
64
+ getStyleWithPosition(styles, 'username', position),
65
+ usernameStyle,
66
+ ], [position, usernameStyle])
67
+
68
+ const textStyleMemo = useMemo(() => [
69
+ styles.text,
70
+ getStyleWithPosition(styles, 'text', position),
71
+ textStyle,
72
+ ], [position, textStyle])
73
+
74
+ if (!currentMessage.replyMessage)
75
+ return null
76
+
77
+ const { replyMessage } = currentMessage
78
+
79
+ return (
80
+ <Pressable
81
+ onPress={handlePress}
82
+ style={containerStyleMemo}
83
+ >
84
+ <View style={contentContainerStyleMemo}>
85
+ {replyMessage.image && (
86
+ <Image
87
+ source={{ uri: replyMessage.image }}
88
+ style={imageStyleMemo}
89
+ />
90
+ )}
91
+ <View style={styles.textContainer}>
92
+ <Text
93
+ style={usernameStyleMemo}
94
+ numberOfLines={1}
95
+ >
96
+ {replyMessage.user?.name || 'User'}
97
+ </Text>
98
+ <Text
99
+ numberOfLines={1}
100
+ style={textStyleMemo}
101
+ >
102
+ {replyMessage.text || (replyMessage.image ? 'Photo' : (replyMessage.audio ? 'Audio' : 'Message'))}
103
+ </Text>
104
+ </View>
105
+ </View>
106
+ </Pressable>
107
+ )
108
+ }
109
+
110
+ const styles = StyleSheet.create({
111
+ container: {
112
+ marginHorizontal: 10,
113
+ marginTop: 5,
114
+ marginBottom: 2,
115
+ padding: 8,
116
+ borderRadius: 8,
117
+ borderLeftWidth: 3,
118
+ borderLeftColor: Color.defaultBlue,
119
+ minWidth: 150,
120
+ },
121
+ container_left: {
122
+ backgroundColor: 'rgba(0, 0, 0, 0.05)',
123
+ },
124
+ container_right: {
125
+ backgroundColor: 'rgba(255, 255, 255, 0.15)',
126
+ },
127
+ contentContainer: {
128
+ flexDirection: 'row',
129
+ alignItems: 'center',
130
+ },
131
+ image: {
132
+ width: 36,
133
+ height: 36,
134
+ borderRadius: 4,
135
+ marginRight: 8,
136
+ },
137
+ textContainer: {
138
+ flex: 1,
139
+ },
140
+ username: {
141
+ fontSize: 13,
142
+ fontWeight: '600',
143
+ marginBottom: 2,
144
+ },
145
+ username_left: {
146
+ color: Color.defaultBlue,
147
+ },
148
+ username_right: {
149
+ color: Color.white,
150
+ },
151
+ text: {
152
+ fontSize: 13,
153
+ },
154
+ text_left: {
155
+ color: Color.black,
156
+ },
157
+ text_right: {
158
+ color: 'rgba(255, 255, 255, 0.8)',
159
+ },
160
+ })
@@ -35,7 +35,7 @@ export type MessageTextProps<TMessage extends IMessage> = {
35
35
  stripPrefix?: boolean
36
36
  }
37
37
 
38
- export const MessageText: React.FC<MessageTextProps<IMessage>> = ({
38
+ export function MessageText<TMessage extends IMessage>({
39
39
  currentMessage,
40
40
  position = 'left',
41
41
  containerStyle,
@@ -52,7 +52,7 @@ export const MessageText: React.FC<MessageTextProps<IMessage>> = ({
52
52
  hashtagUrl,
53
53
  mentionUrl,
54
54
  stripPrefix = false,
55
- }) => {
55
+ }: MessageTextProps<TMessage>) {
56
56
  const linkStyle = useMemo(() => StyleSheet.flatten([
57
57
  styles.link,
58
58
  linkStyleProp?.[position],
@@ -19,7 +19,11 @@ export const DayAnimated = ({ scrolledY, daysPositions, listHeight, renderDay, m
19
19
  const isScrolledOnMount = useSharedValue(false)
20
20
  const isLoadingAnim = useSharedValue(isLoading)
21
21
 
22
- const daysPositionsArray = useDerivedValue(() => Object.values(daysPositions.value).sort((a, b) => a.y - b.y))
22
+ const daysPositionsArray = useDerivedValue(() => Object.values(daysPositions.value).sort((a, b) => {
23
+ 'worklet'
24
+
25
+ return a.y - b.y
26
+ }))
23
27
 
24
28
  const [createdAt, setCreatedAt] = useState<number | undefined>()
25
29
 
@@ -32,28 +32,35 @@ export const useRelativeScrolledPositionToBottomOfDay = (
32
32
 
33
33
  const absoluteScrolledPositionToBottomOfDay = useAbsoluteScrolledPositionToBottomOfDay(listHeight, scrolledY, containerHeight, dayBottomMargin, dayTopOffset)
34
34
 
35
- // sorted array of days positions by y
36
- const daysPositionsArray = useDerivedValue(() => {
37
- return Object.values(daysPositions.value).sort((a, b) => {
38
- 'worklet'
39
-
40
- return a.y - b.y
41
- })
42
- })
43
-
44
35
  // find current day position by scrolled position
45
36
  const currentDayPosition = useDerivedValue(() => {
37
+ 'worklet'
38
+
39
+ // When createdAt is provided (called from AnimatedDayWrapper for a specific message),
40
+ // directly find the day position by createdAt without sorting the entire array.
41
+ // This avoids O(n log n) sorting and O(n) search for each message item.
46
42
  if (createdAt != null) {
47
- const currentDayPosition = daysPositionsArray.value.find(day => day.createdAt === createdAt)
48
- if (currentDayPosition)
49
- return currentDayPosition
43
+ const values = Object.values(daysPositions.value)
44
+ for (let i = 0; i < values.length; i++)
45
+ if (values[i].createdAt === createdAt)
46
+ return values[i]
50
47
  }
51
48
 
52
- return daysPositionsArray.value.find((day, index) => {
53
- const dayPosition = day.y + day.height
54
- return (absoluteScrolledPositionToBottomOfDay.value < dayPosition) || index === daysPositionsArray.value.length - 1
49
+ // Fallback: sort and search when createdAt is not provided (e.g., from DayAnimated)
50
+ const sortedArray = Object.values(daysPositions.value).sort((a, b) => {
51
+ 'worklet'
52
+
53
+ return a.y - b.y
55
54
  })
56
- }, [daysPositionsArray, absoluteScrolledPositionToBottomOfDay, createdAt])
55
+ for (let i = 0; i < sortedArray.length; i++) {
56
+ const day = sortedArray[i]
57
+ const dayPosition = day.y + day.height
58
+ if (absoluteScrolledPositionToBottomOfDay.value < dayPosition || i === sortedArray.length - 1)
59
+ return day
60
+ }
61
+
62
+ return undefined
63
+ }, [daysPositions, absoluteScrolledPositionToBottomOfDay, createdAt])
57
64
 
58
65
  const relativeScrolledPositionToBottomOfDay = useDerivedValue(() => {
59
66
  const scrolledBottomY = listHeight.value + scrolledY.value - (
@@ -97,13 +104,11 @@ const DayWrapper = <TMessage extends IMessage>(props: MessageProps<TMessage>) =>
97
104
  )
98
105
  }
99
106
 
100
- export const Item = <TMessage extends IMessage>(props: ItemProps<TMessage>) => {
107
+ const AnimatedDayWrapper = <TMessage extends IMessage>(props: ItemProps<TMessage>) => {
101
108
  const {
102
- renderMessage: renderMessageProp,
103
109
  scrolledY,
104
110
  daysPositions,
105
111
  listHeight,
106
- isDayAnimationEnabled,
107
112
  ...rest
108
113
  } = props
109
114
 
@@ -122,40 +127,70 @@ export const Item = <TMessage extends IMessage>(props: ItemProps<TMessage>) => {
122
127
  }, [dayContainerHeight])
123
128
 
124
129
  const style = useAnimatedStyle(() => ({
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])
130
+ opacity: interpolate(
131
+ relativeScrolledPositionToBottomOfDay.value,
132
+ [
133
+ -dayTopOffset,
134
+ -0.0001,
135
+ 0,
136
+ dayContainerHeight.value + dayTopOffset,
137
+ ],
138
+ [
139
+ 0,
140
+ 0,
141
+ 1,
142
+ 1,
143
+ ],
144
+ 'clamp'
145
+ ),
146
+ }), [relativeScrolledPositionToBottomOfDay, dayContainerHeight, dayTopOffset])
147
+
148
+ return (
149
+ <Animated.View
150
+ style={style}
151
+ onLayout={handleLayoutDayContainer}
152
+ >
153
+ <DayWrapper<TMessage> {...rest as MessageProps<TMessage>} />
154
+ </Animated.View>
155
+ )
156
+ }
157
+
158
+ export const Item = <TMessage extends IMessage>(props: ItemProps<TMessage>) => {
159
+ const {
160
+ renderMessage: renderMessageProp,
161
+ isDayAnimationEnabled,
162
+ reply,
163
+ /* eslint-disable @typescript-eslint/no-unused-vars */
164
+ scrolledY: _scrolledY,
165
+ daysPositions: _daysPositions,
166
+ listHeight: _listHeight,
167
+ /* eslint-enable @typescript-eslint/no-unused-vars */
168
+ ...rest
169
+ } = props
170
+
171
+ // Transform reply props for Message and Bubble
172
+ const messageProps = useMemo(() => ({
173
+ ...rest,
174
+ // Swipe to reply for Message component
175
+ swipeToReply: reply?.swipe,
176
+ // Message reply styling for Bubble component
177
+ messageReply: reply ? {
178
+ renderMessageReply: reply.renderMessageReply,
179
+ onPress: reply.onPress,
180
+ ...reply.messageStyle,
181
+ } : undefined,
182
+ }), [rest, reply])
145
183
 
146
184
  return (
147
185
  // do not remove key. it helps to get correct position of the day container
148
186
  <View key={props.currentMessage._id.toString()}>
149
- <Animated.View
150
- style={style}
151
- onLayout={handleLayoutDayContainer}
152
- >
153
- <DayWrapper<TMessage> {...rest as MessageProps<TMessage>} />
154
- </Animated.View>
187
+ {isDayAnimationEnabled
188
+ ? <AnimatedDayWrapper<TMessage> {...props} />
189
+ : <DayWrapper<TMessage> {...messageProps as MessageProps<TMessage>} />}
155
190
  {
156
191
  renderMessageProp
157
- ? renderMessageProp(rest as MessageProps<TMessage>)
158
- : <Message<TMessage> {...rest as MessageProps<TMessage>} />
192
+ ? renderMessageProp(messageProps as MessageProps<TMessage>)
193
+ : <Message<TMessage> {...messageProps as MessageProps<TMessage>} />
159
194
  }
160
195
  </View>
161
196
  )