UNPKG

11.3 kBJavaScriptView Raw
1import PropTypes from 'prop-types';
2import React from 'react';
3import { FlatList, View, StyleSheet, Keyboard, TouchableOpacity, Text, Platform, } from 'react-native';
4import LoadEarlier from './LoadEarlier';
5import Message from './Message';
6import Color from './Color';
7import { warning, StylePropType } from './utils';
8import TypingIndicator from './TypingIndicator';
9const styles = StyleSheet.create({
10 container: {
11 flex: 1,
12 },
13 containerAlignTop: {
14 flexDirection: 'row',
15 alignItems: 'flex-start',
16 },
17 contentContainerStyle: {
18 flexGrow: 1,
19 justifyContent: 'flex-start',
20 },
21 emptyChatContainer: {
22 flex: 1,
23 transform: [{ scaleY: -1 }],
24 },
25 headerWrapper: {
26 flex: 1,
27 },
28 listStyle: {
29 flex: 1,
30 },
31 scrollToBottomStyle: {
32 opacity: 0.8,
33 position: 'absolute',
34 right: 10,
35 bottom: 30,
36 zIndex: 999,
37 height: 40,
38 width: 40,
39 borderRadius: 20,
40 backgroundColor: Color.white,
41 alignItems: 'center',
42 justifyContent: 'center',
43 shadowColor: Color.black,
44 shadowOpacity: 0.5,
45 shadowOffset: { width: 0, height: 0 },
46 shadowRadius: 1,
47 },
48});
49export default class MessageContainer extends React.PureComponent {
50 constructor() {
51 super(...arguments);
52 this.state = {
53 showScrollBottom: false,
54 };
55 this.attachKeyboardListeners = () => {
56 const { invertibleScrollViewProps: invertibleProps } = this.props;
57 if (invertibleProps) {
58 Keyboard.addListener('keyboardWillShow', invertibleProps.onKeyboardWillShow);
59 Keyboard.addListener('keyboardDidShow', invertibleProps.onKeyboardDidShow);
60 Keyboard.addListener('keyboardWillHide', invertibleProps.onKeyboardWillHide);
61 Keyboard.addListener('keyboardDidHide', invertibleProps.onKeyboardDidHide);
62 }
63 };
64 this.detachKeyboardListeners = () => {
65 const { invertibleScrollViewProps: invertibleProps } = this.props;
66 Keyboard.removeListener('keyboardWillShow', invertibleProps.onKeyboardWillShow);
67 Keyboard.removeListener('keyboardDidShow', invertibleProps.onKeyboardDidShow);
68 Keyboard.removeListener('keyboardWillHide', invertibleProps.onKeyboardWillHide);
69 Keyboard.removeListener('keyboardDidHide', invertibleProps.onKeyboardDidHide);
70 };
71 this.renderTypingIndicator = () => {
72 if (Platform.OS === 'web') {
73 return null;
74 }
75 return <TypingIndicator isTyping={this.props.isTyping || false}/>;
76 };
77 this.renderFooter = () => {
78 if (this.props.renderFooter) {
79 return this.props.renderFooter(this.props);
80 }
81 return this.renderTypingIndicator();
82 };
83 this.renderLoadEarlier = () => {
84 if (this.props.loadEarlier === true) {
85 const loadEarlierProps = {
86 ...this.props,
87 };
88 if (this.props.renderLoadEarlier) {
89 return this.props.renderLoadEarlier(loadEarlierProps);
90 }
91 return <LoadEarlier {...loadEarlierProps}/>;
92 }
93 return null;
94 };
95 this.scrollToBottom = (animated = true) => {
96 const { inverted } = this.props;
97 if (inverted) {
98 this.scrollTo({ offset: 0, animated });
99 }
100 else if (this.props.forwardRef && this.props.forwardRef.current) {
101 this.props.forwardRef.current.scrollToEnd({ animated });
102 }
103 };
104 this.handleOnScroll = (event) => {
105 const { nativeEvent: { contentOffset: { y: contentOffsetY }, contentSize: { height: contentSizeHeight }, layoutMeasurement: { height: layoutMeasurementHeight }, }, } = event;
106 const { scrollToBottomOffset } = this.props;
107 if (this.props.inverted) {
108 if (contentOffsetY > scrollToBottomOffset) {
109 this.setState({ showScrollBottom: true });
110 }
111 else {
112 this.setState({ showScrollBottom: false });
113 }
114 }
115 else {
116 if (contentOffsetY < scrollToBottomOffset &&
117 contentSizeHeight - layoutMeasurementHeight > scrollToBottomOffset) {
118 this.setState({ showScrollBottom: true });
119 }
120 else {
121 this.setState({ showScrollBottom: false });
122 }
123 }
124 };
125 this.renderRow = ({ item, index }) => {
126 if (!item._id && item._id !== 0) {
127 warning('GiftedChat: `_id` is missing for message', JSON.stringify(item));
128 }
129 if (!item.user) {
130 if (!item.system) {
131 warning('GiftedChat: `user` is missing for message', JSON.stringify(item));
132 }
133 item.user = { _id: 0 };
134 }
135 const { messages, user, inverted, ...restProps } = this.props;
136 if (messages && user) {
137 const previousMessage = (inverted ? messages[index + 1] : messages[index - 1]) || {};
138 const nextMessage = (inverted ? messages[index - 1] : messages[index + 1]) || {};
139 const messageProps = {
140 ...restProps,
141 user,
142 key: item._id,
143 currentMessage: item,
144 previousMessage,
145 inverted,
146 nextMessage,
147 position: item.user._id === user._id ? 'right' : 'left',
148 };
149 if (this.props.renderMessage) {
150 return this.props.renderMessage(messageProps);
151 }
152 return <Message {...messageProps}/>;
153 }
154 return null;
155 };
156 this.renderChatEmpty = () => {
157 if (this.props.renderChatEmpty) {
158 return this.props.inverted ? (this.props.renderChatEmpty()) : (<View style={styles.emptyChatContainer}>
159 {this.props.renderChatEmpty()}
160 </View>);
161 }
162 return <View style={styles.container}/>;
163 };
164 this.renderHeaderWrapper = () => (<View style={styles.headerWrapper}>{this.renderLoadEarlier()}</View>);
165 this.onLayoutList = () => {
166 if (!this.props.inverted &&
167 !!this.props.messages &&
168 this.props.messages.length) {
169 setTimeout(() => this.scrollToBottom && this.scrollToBottom(false), 15 * this.props.messages.length);
170 }
171 };
172 this.onEndReached = ({ distanceFromEnd }) => {
173 const { loadEarlier, onLoadEarlier, infiniteScroll, isLoadingEarlier, } = this.props;
174 if (infiniteScroll &&
175 distanceFromEnd > 0 &&
176 distanceFromEnd <= 100 &&
177 loadEarlier &&
178 onLoadEarlier &&
179 !isLoadingEarlier &&
180 Platform.OS !== 'web') {
181 onLoadEarlier();
182 }
183 };
184 this.keyExtractor = (item) => `${item._id}`;
185 }
186 componentDidMount() {
187 if (this.props.messages && this.props.messages.length === 0) {
188 this.attachKeyboardListeners();
189 }
190 }
191 componentWillUnmount() {
192 this.detachKeyboardListeners();
193 }
194 componentDidUpdate(prevProps) {
195 if (prevProps.messages &&
196 prevProps.messages.length === 0 &&
197 this.props.messages &&
198 this.props.messages.length > 0) {
199 this.detachKeyboardListeners();
200 }
201 else if (prevProps.messages &&
202 this.props.messages &&
203 prevProps.messages.length > 0 &&
204 this.props.messages.length === 0) {
205 this.attachKeyboardListeners();
206 }
207 }
208 scrollTo(options) {
209 if (this.props.forwardRef && this.props.forwardRef.current && options) {
210 this.props.forwardRef.current.scrollToOffset(options);
211 }
212 }
213 renderScrollBottomComponent() {
214 const { scrollToBottomComponent } = this.props;
215 if (scrollToBottomComponent) {
216 return scrollToBottomComponent();
217 }
218 return <Text>V</Text>;
219 }
220 renderScrollToBottomWrapper() {
221 const propsStyle = this.props.scrollToBottomStyle || {};
222 return (<View style={[styles.scrollToBottomStyle, propsStyle]}>
223 <TouchableOpacity onPress={() => this.scrollToBottom()} hitSlop={{ top: 5, left: 5, right: 5, bottom: 5 }}>
224 {this.renderScrollBottomComponent()}
225 </TouchableOpacity>
226 </View>);
227 }
228 render() {
229 const { inverted } = this.props;
230 return (<View style={this.props.alignTop ? styles.containerAlignTop : styles.container}>
231 {this.state.showScrollBottom && this.props.scrollToBottom
232 ? this.renderScrollToBottomWrapper()
233 : null}
234 <FlatList ref={this.props.forwardRef} extraData={[this.props.extraData, this.props.isTyping]} keyExtractor={this.keyExtractor} enableEmptySections automaticallyAdjustContentInsets={false} inverted={inverted} data={this.props.messages} style={styles.listStyle} contentContainerStyle={styles.contentContainerStyle} renderItem={this.renderRow} {...this.props.invertibleScrollViewProps} ListEmptyComponent={this.renderChatEmpty} ListFooterComponent={inverted ? this.renderHeaderWrapper : this.renderFooter} ListHeaderComponent={inverted ? this.renderFooter : this.renderHeaderWrapper} onScroll={this.handleOnScroll} scrollEventThrottle={100} onLayout={this.onLayoutList} onEndReached={this.onEndReached} onEndReachedThreshold={0.1} {...this.props.listViewProps}/>
235 </View>);
236 }
237}
238MessageContainer.defaultProps = {
239 messages: [],
240 user: {},
241 isTyping: false,
242 renderChatEmpty: null,
243 renderFooter: null,
244 renderMessage: null,
245 onLoadEarlier: () => { },
246 onQuickReply: () => { },
247 inverted: true,
248 loadEarlier: false,
249 listViewProps: {},
250 invertibleScrollViewProps: {},
251 extraData: null,
252 scrollToBottom: false,
253 scrollToBottomOffset: 200,
254 alignTop: false,
255 scrollToBottomStyle: {},
256 infiniteScroll: false,
257 isLoadingEarlier: false,
258};
259MessageContainer.propTypes = {
260 messages: PropTypes.arrayOf(PropTypes.object),
261 isTyping: PropTypes.bool,
262 user: PropTypes.object,
263 renderChatEmpty: PropTypes.func,
264 renderFooter: PropTypes.func,
265 renderMessage: PropTypes.func,
266 renderLoadEarlier: PropTypes.func,
267 onLoadEarlier: PropTypes.func,
268 listViewProps: PropTypes.object,
269 inverted: PropTypes.bool,
270 loadEarlier: PropTypes.bool,
271 invertibleScrollViewProps: PropTypes.object,
272 extraData: PropTypes.array,
273 scrollToBottom: PropTypes.bool,
274 scrollToBottomOffset: PropTypes.number,
275 scrollToBottomComponent: PropTypes.func,
276 alignTop: PropTypes.bool,
277 scrollToBottomStyle: StylePropType,
278 infiniteScroll: PropTypes.bool,
279};
280//# sourceMappingURL=MessageContainer.js.map
\No newline at end of file