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.
- package/README.md +450 -156
- package/package.json +9 -8
- package/src/Bubble/index.tsx +34 -2
- package/src/Bubble/types.ts +17 -4
- package/src/Composer.tsx +1 -2
- package/src/Day/index.tsx +2 -2
- package/src/Day/types.ts +3 -2
- package/src/GiftedAvatar.tsx +1 -1
- package/src/GiftedChat/index.tsx +109 -23
- package/src/GiftedChat/types.ts +9 -3
- package/src/InputToolbar.tsx +62 -8
- package/src/Message/index.tsx +181 -21
- package/src/Message/types.ts +4 -0
- package/src/MessageReply.tsx +160 -0
- package/src/MessageText.tsx +2 -2
- package/src/MessagesContainer/components/DayAnimated/index.tsx +5 -1
- package/src/MessagesContainer/components/Item/index.tsx +82 -47
- package/src/MessagesContainer/index.tsx +30 -19
- package/src/MessagesContainer/styles.ts +2 -0
- package/src/MessagesContainer/types.ts +30 -3
- package/src/Models.ts +3 -0
- package/src/Reply/index.ts +1 -0
- package/src/Reply/types.ts +80 -0
- package/src/ReplyPreview.tsx +132 -0
- package/src/Send.tsx +8 -3
- package/src/SystemMessage.tsx +22 -16
- package/src/__tests__/MessageReply.test.tsx +54 -0
- package/src/__tests__/ReplyPreview.test.tsx +41 -0
- package/src/__tests__/__snapshots__/GiftedChat.test.tsx.snap +69 -42
- package/src/__tests__/__snapshots__/InputToolbar.test.tsx.snap +11 -15
- package/src/__tests__/__snapshots__/MessageImage.test.tsx.snap +24 -18
- package/src/__tests__/__snapshots__/MessageReply.test.tsx.snap +181 -0
- package/src/__tests__/__snapshots__/ReplyPreview.test.tsx.snap +403 -0
- package/src/__tests__/__snapshots__/Send.test.tsx.snap +3 -0
- package/src/__tests__/__snapshots__/SystemMessage.test.tsx.snap +36 -25
- package/src/components/MessageReply.tsx +156 -0
- package/src/components/ReplyPreview.tsx +230 -0
- package/src/index.ts +6 -1
- package/src/types.ts +17 -16
- package/src/utils.ts +11 -3
- package/CHANGELOG.md +0 -364
- package/src/reanimatedCompat.ts +0 -27
package/src/Message/index.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
+
})
|
package/src/Message/types.ts
CHANGED
|
@@ -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
|
+
})
|
package/src/MessageText.tsx
CHANGED
|
@@ -35,7 +35,7 @@ export type MessageTextProps<TMessage extends IMessage> = {
|
|
|
35
35
|
stripPrefix?: boolean
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export
|
|
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) =>
|
|
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
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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(
|
|
158
|
-
: <Message<TMessage> {...
|
|
192
|
+
? renderMessageProp(messageProps as MessageProps<TMessage>)
|
|
193
|
+
: <Message<TMessage> {...messageProps as MessageProps<TMessage>} />
|
|
159
194
|
}
|
|
160
195
|
</View>
|
|
161
196
|
)
|