react-native-gifted-chat 3.0.0-alpha.0 → 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 +71 -95
- package/package.json +2 -2
- package/src/Actions.tsx +27 -18
- package/src/Composer.tsx +21 -84
- package/src/Constant.ts +0 -9
- package/src/GiftedChat/index.tsx +52 -163
- package/src/GiftedChat/types.ts +11 -12
- package/src/InputToolbar.tsx +6 -11
- package/src/MessageImage.tsx +135 -65
- 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 +18 -14
- package/src/{MessageContainer → MessagesContainer}/styles.ts +12 -5
- 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__/GiftedChat.test.tsx +0 -28
- 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__/GiftedChat.test.tsx.snap +31 -54
- package/src/__tests__/__snapshots__/InputToolbar.test.tsx.snap +102 -31
- package/src/__tests__/__snapshots__/MessageImage.test.tsx.snap +252 -1
- 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/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
|
@@ -8,46 +8,101 @@ import {
|
|
|
8
8
|
StyleProp,
|
|
9
9
|
ImageStyle,
|
|
10
10
|
ImageURISource,
|
|
11
|
-
Modal,
|
|
12
11
|
TouchableOpacity,
|
|
13
12
|
LayoutChangeEvent,
|
|
14
13
|
useWindowDimensions,
|
|
14
|
+
StatusBar,
|
|
15
15
|
} from 'react-native'
|
|
16
16
|
import { BaseButton, GestureHandlerRootView, Text } from 'react-native-gesture-handler'
|
|
17
|
-
import {
|
|
17
|
+
import { OverKeyboardView } from 'react-native-keyboard-controller'
|
|
18
|
+
import Animated, {
|
|
19
|
+
useAnimatedStyle,
|
|
20
|
+
useSharedValue,
|
|
21
|
+
withTiming,
|
|
22
|
+
Easing,
|
|
23
|
+
runOnJS,
|
|
24
|
+
} from 'react-native-reanimated'
|
|
25
|
+
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
18
26
|
import Zoom from 'react-native-zoom-reanimated'
|
|
19
27
|
import { IMessage } from './Models'
|
|
20
28
|
import commonStyles from './styles'
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
},
|
|
30
|
-
modalContent: {
|
|
31
|
-
backgroundColor: '#000',
|
|
32
|
-
},
|
|
33
|
-
modalImageContainer: {
|
|
34
|
-
width: '100%',
|
|
35
|
-
height: '100%',
|
|
36
|
-
},
|
|
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
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
})
|
|
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
|
+
}
|
|
51
106
|
|
|
52
107
|
export interface MessageImageProps<TMessage extends IMessage> {
|
|
53
108
|
currentMessage: TMessage
|
|
@@ -68,8 +123,6 @@ export function MessageImage<TMessage extends IMessage = IMessage> ({
|
|
|
68
123
|
const [imageDimensions, setImageDimensions] = useState<{ width: number, height: number }>()
|
|
69
124
|
const windowDimensions = useWindowDimensions()
|
|
70
125
|
|
|
71
|
-
const insets = useSafeAreaInsets()
|
|
72
|
-
|
|
73
126
|
const imageSource = useMemo(() => ({
|
|
74
127
|
...imageSourceProps,
|
|
75
128
|
uri: currentMessage?.image,
|
|
@@ -140,42 +193,59 @@ export function MessageImage<TMessage extends IMessage = IMessage> ({
|
|
|
140
193
|
style={computedImageStyle}
|
|
141
194
|
source={imageSource}
|
|
142
195
|
onLayout={handleImageLayout}
|
|
196
|
+
resizeMode='cover'
|
|
143
197
|
/>
|
|
144
198
|
</TouchableOpacity>
|
|
145
199
|
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
<BaseButton onPress={handleModalClose}>
|
|
158
|
-
<View style={styles.closeButtonContent}>
|
|
159
|
-
<Text style={styles.closeButtonIcon}>
|
|
160
|
-
{'X'}
|
|
161
|
-
</Text>
|
|
162
|
-
</View>
|
|
163
|
-
</BaseButton>
|
|
164
|
-
</View>
|
|
165
|
-
|
|
166
|
-
<View style={[commonStyles.fill, commonStyles.centerItems]}>
|
|
167
|
-
<Zoom>
|
|
168
|
-
<Image
|
|
169
|
-
style={modalImageDimensions}
|
|
170
|
-
source={imageSource}
|
|
171
|
-
resizeMode='contain'
|
|
172
|
-
{...imageProps}
|
|
173
|
-
/>
|
|
174
|
-
</Zoom>
|
|
175
|
-
</View>
|
|
176
|
-
</View>
|
|
177
|
-
</GestureHandlerRootView>
|
|
178
|
-
</Modal>
|
|
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>
|
|
179
211
|
</View>
|
|
180
212
|
)
|
|
181
213
|
}
|
|
214
|
+
|
|
215
|
+
const styles = StyleSheet.create({
|
|
216
|
+
image: {
|
|
217
|
+
width: 150,
|
|
218
|
+
height: 100,
|
|
219
|
+
borderRadius: 13,
|
|
220
|
+
margin: 3,
|
|
221
|
+
},
|
|
222
|
+
modalOverlay: {
|
|
223
|
+
position: 'absolute',
|
|
224
|
+
top: 0,
|
|
225
|
+
left: 0,
|
|
226
|
+
right: 0,
|
|
227
|
+
bottom: 0,
|
|
228
|
+
zIndex: 1000,
|
|
229
|
+
},
|
|
230
|
+
modalContent: {
|
|
231
|
+
backgroundColor: '#000',
|
|
232
|
+
overflow: 'hidden',
|
|
233
|
+
},
|
|
234
|
+
modalImageContainer: {
|
|
235
|
+
width: '100%',
|
|
236
|
+
height: '100%',
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
closeButtonContainer: {
|
|
240
|
+
flexDirection: 'row',
|
|
241
|
+
justifyContent: 'flex-end',
|
|
242
|
+
},
|
|
243
|
+
closeButtonContent: {
|
|
244
|
+
padding: 20,
|
|
245
|
+
},
|
|
246
|
+
closeButtonIcon: {
|
|
247
|
+
fontSize: 20,
|
|
248
|
+
lineHeight: 20,
|
|
249
|
+
color: 'white',
|
|
250
|
+
},
|
|
251
|
+
})
|
|
@@ -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)
|
|
@@ -401,20 +401,24 @@ export const MessageContainer = <TMessage extends IMessage>(props: MessageContai
|
|
|
401
401
|
scrollEventThrottle={1}
|
|
402
402
|
onEndReached={onEndReached}
|
|
403
403
|
onEndReachedThreshold={0.1}
|
|
404
|
+
keyboardDismissMode='interactive'
|
|
405
|
+
keyboardShouldPersistTaps='handled'
|
|
404
406
|
{...listProps}
|
|
405
407
|
onScroll={scrollHandler}
|
|
406
408
|
onLayout={onLayoutList}
|
|
407
409
|
CellRendererComponent={renderCell}
|
|
408
410
|
/>
|
|
409
411
|
<ScrollToBottomWrapper />
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
+
)}
|
|
418
422
|
</View>
|
|
419
423
|
)
|
|
420
424
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StyleSheet } from 'react-native'
|
|
1
|
+
import { Platform, StyleSheet } from 'react-native'
|
|
2
2
|
import { Color } from '../Color'
|
|
3
3
|
|
|
4
4
|
export default StyleSheet.create({
|
|
@@ -24,9 +24,16 @@ export default StyleSheet.create({
|
|
|
24
24
|
width: 40,
|
|
25
25
|
borderRadius: 20,
|
|
26
26
|
backgroundColor: Color.white,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
...Platform.select({
|
|
28
|
+
ios: {
|
|
29
|
+
shadowColor: Color.black,
|
|
30
|
+
shadowOpacity: 0.5,
|
|
31
|
+
shadowOffset: { width: 0, height: 0 },
|
|
32
|
+
shadowRadius: 1,
|
|
33
|
+
},
|
|
34
|
+
android: {
|
|
35
|
+
elevation: 5,
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
31
38
|
},
|
|
32
39
|
})
|
|
@@ -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 {
|
package/src/Send.tsx
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import React, { useMemo, useCallback } from 'react'
|
|
1
|
+
import React, { useMemo, useCallback, useEffect } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
StyleSheet,
|
|
4
4
|
Text,
|
|
5
|
-
View,
|
|
6
5
|
StyleProp,
|
|
7
6
|
ViewStyle,
|
|
8
7
|
TextStyle,
|
|
9
8
|
useColorScheme,
|
|
10
9
|
} from 'react-native'
|
|
10
|
+
import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated'
|
|
11
11
|
import { Color } from './Color'
|
|
12
12
|
|
|
13
13
|
import { TouchableOpacity, TouchableOpacityProps } from './components/TouchableOpacity'
|
|
14
14
|
import { TEST_ID } from './Constant'
|
|
15
15
|
import { IMessage } from './Models'
|
|
16
|
+
import { getColorSchemeStyle } from './styles'
|
|
16
17
|
|
|
17
18
|
export interface SendProps<TMessage extends IMessage> {
|
|
18
19
|
text?: string
|
|
@@ -39,6 +40,7 @@ export const Send = <TMessage extends IMessage = IMessage>({
|
|
|
39
40
|
onSend,
|
|
40
41
|
}: SendProps<TMessage>) => {
|
|
41
42
|
const colorScheme = useColorScheme()
|
|
43
|
+
const opacity = useSharedValue(0)
|
|
42
44
|
|
|
43
45
|
const handleOnPress = useCallback(() => {
|
|
44
46
|
const message = { text: text?.trim() } as Partial<TMessage>
|
|
@@ -47,34 +49,51 @@ export const Send = <TMessage extends IMessage = IMessage>({
|
|
|
47
49
|
onSend(message, true)
|
|
48
50
|
}, [text, onSend])
|
|
49
51
|
|
|
50
|
-
const
|
|
52
|
+
const isVisible = useMemo(
|
|
51
53
|
() => isSendButtonAlwaysVisible || !!text?.trim().length,
|
|
52
54
|
[isSendButtonAlwaysVisible, text]
|
|
53
55
|
)
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
opacity.value = withTiming(isVisible ? 1 : 0, { duration: 200 })
|
|
59
|
+
}, [isVisible, opacity])
|
|
60
|
+
|
|
61
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
62
|
+
opacity: opacity.value,
|
|
63
|
+
}), [opacity])
|
|
57
64
|
|
|
58
65
|
return (
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
{
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
<Animated.View style={[styles.container, containerStyle, animatedStyle]} pointerEvents={isVisible ? 'auto' : 'none'}>
|
|
67
|
+
<TouchableOpacity
|
|
68
|
+
testID={TEST_ID.SEND_TOUCHABLE}
|
|
69
|
+
style={styles.touchable}
|
|
70
|
+
onPress={handleOnPress}
|
|
71
|
+
accessible
|
|
72
|
+
accessibilityLabel='send'
|
|
73
|
+
accessibilityRole='button'
|
|
74
|
+
{...sendButtonProps}
|
|
75
|
+
>
|
|
76
|
+
{
|
|
77
|
+
children ||
|
|
78
|
+
<Text
|
|
79
|
+
style={[
|
|
80
|
+
getColorSchemeStyle(styles, 'text', colorScheme),
|
|
81
|
+
textStyle,
|
|
82
|
+
]}
|
|
83
|
+
>
|
|
84
|
+
{label}
|
|
85
|
+
</Text>
|
|
86
|
+
}
|
|
87
|
+
</TouchableOpacity>
|
|
88
|
+
</Animated.View>
|
|
72
89
|
)
|
|
73
90
|
}
|
|
74
91
|
|
|
75
92
|
const styles = StyleSheet.create({
|
|
76
93
|
container: {
|
|
77
|
-
|
|
94
|
+
justifyContent: 'flex-end',
|
|
95
|
+
},
|
|
96
|
+
touchable: {
|
|
78
97
|
justifyContent: 'flex-end',
|
|
79
98
|
},
|
|
80
99
|
text: {
|
|
@@ -82,9 +101,8 @@ const styles = StyleSheet.create({
|
|
|
82
101
|
fontWeight: '600',
|
|
83
102
|
fontSize: 17,
|
|
84
103
|
backgroundColor: Color.backgroundTransparent,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
marginRight: 10,
|
|
104
|
+
paddingVertical: 10,
|
|
105
|
+
paddingHorizontal: 10,
|
|
88
106
|
},
|
|
89
107
|
text_dark: {
|
|
90
108
|
color: '#4da6ff',
|
|
@@ -2,7 +2,7 @@ import React from 'react'
|
|
|
2
2
|
import { View, Text } from 'react-native'
|
|
3
3
|
import { render } from '@testing-library/react-native'
|
|
4
4
|
import { DayProps } from '../Day'
|
|
5
|
-
import { DayAnimated } from '../
|
|
5
|
+
import { DayAnimated } from '../MessagesContainer/components/DayAnimated'
|
|
6
6
|
import { DEFAULT_TEST_MESSAGE } from './data'
|
|
7
7
|
|
|
8
8
|
const mockDaysPositions = { value: {} }
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { render } from '@testing-library/react-native'
|
|
3
3
|
|
|
4
|
-
import { useReanimatedKeyboardAnimation } from 'react-native-keyboard-controller'
|
|
5
4
|
import { GiftedChat } from '..'
|
|
6
5
|
|
|
7
6
|
const messages = [
|
|
@@ -17,32 +16,6 @@ const messages = [
|
|
|
17
16
|
]
|
|
18
17
|
|
|
19
18
|
it('should render <GiftedChat/> and compare with snapshot', () => {
|
|
20
|
-
(useReanimatedKeyboardAnimation as jest.Mock).mockReturnValue({
|
|
21
|
-
height: {
|
|
22
|
-
value: 0,
|
|
23
|
-
},
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
const { toJSON } = render(
|
|
27
|
-
<GiftedChat
|
|
28
|
-
messages={messages}
|
|
29
|
-
onSend={() => {}}
|
|
30
|
-
user={{
|
|
31
|
-
_id: 1,
|
|
32
|
-
}}
|
|
33
|
-
/>
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
expect(toJSON()).toMatchSnapshot()
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('should render <GiftedChat/> with isKeyboardInternallyHandled=false', () => {
|
|
40
|
-
(useReanimatedKeyboardAnimation as jest.Mock).mockReturnValue({
|
|
41
|
-
height: {
|
|
42
|
-
value: 0,
|
|
43
|
-
},
|
|
44
|
-
})
|
|
45
|
-
|
|
46
19
|
const { toJSON } = render(
|
|
47
20
|
<GiftedChat
|
|
48
21
|
messages={messages}
|
|
@@ -50,7 +23,6 @@ it('should render <GiftedChat/> with isKeyboardInternallyHandled=false', () => {
|
|
|
50
23
|
user={{
|
|
51
24
|
_id: 1,
|
|
52
25
|
}}
|
|
53
|
-
isKeyboardInternallyHandled={false}
|
|
54
26
|
/>
|
|
55
27
|
)
|
|
56
28
|
|