UNPKG

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