278 lines
7.8 KiB
JavaScript
278 lines
7.8 KiB
JavaScript
import { ListView as OldList } from 'realm/react-native';
|
|
import React from 'react';
|
|
import {
|
|
TouchableOpacity, ScrollView, ListView as OldList2, ImageBackground, ActivityIndicator
|
|
} from 'react-native';
|
|
import moment from 'moment';
|
|
import { connect } from 'react-redux';
|
|
import PropTypes from 'prop-types';
|
|
|
|
import Separator from './Separator';
|
|
import styles from './styles';
|
|
import database from '../../lib/realm';
|
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
|
import debounce from '../../utils/debounce';
|
|
import RocketChat from '../../lib/rocketchat';
|
|
import log from '../../utils/log';
|
|
import { CustomIcon } from '../../lib/Icons';
|
|
import { isIOS, isNotch } from '../../utils/deviceInfo';
|
|
|
|
const DEFAULT_SCROLL_CALLBACK_THROTTLE = 100;
|
|
|
|
export class DataSource extends OldList.DataSource {
|
|
getRowData(sectionIndex: number, rowIndex: number): any {
|
|
const sectionID = this.sectionIdentities[sectionIndex];
|
|
const rowID = this.rowIdentities[sectionIndex][rowIndex];
|
|
return this._getRowData(this._dataBlob, sectionID, rowID);
|
|
}
|
|
_calculateDirtyArrays() { // eslint-disable-line
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const ds = new DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id });
|
|
|
|
export class List extends React.Component {
|
|
static propTypes = {
|
|
onEndReached: PropTypes.func,
|
|
renderFooter: PropTypes.func,
|
|
renderRow: PropTypes.func,
|
|
room: PropTypes.object
|
|
};
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
this.data = database
|
|
.objects('messages')
|
|
.filtered('rid = $0', props.room.rid)
|
|
.sorted('ts', true);
|
|
this.state = {
|
|
loading: true,
|
|
loadingMore: false,
|
|
end: false,
|
|
showScollToBottomButton: false
|
|
};
|
|
this.dataSource = ds.cloneWithRows(this.data);
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.updateState();
|
|
this.data.addListener(this.updateState);
|
|
}
|
|
|
|
shouldComponentUpdate(nextProps, nextState) {
|
|
const { loadingMore, loading, end, showScollToBottomButton } = this.state;
|
|
return end !== nextState.end || loadingMore !== nextState.loadingMore || loading !== nextState.loading || showScollToBottomButton !== nextState.showScollToBottomButton;
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.data.removeAllListeners();
|
|
this.updateState.stop();
|
|
}
|
|
|
|
// eslint-disable-next-line react/sort-comp
|
|
updateState = debounce(() => {
|
|
this.setState({ loading: true });
|
|
this.dataSource = this.dataSource.cloneWithRows(this.data);
|
|
this.setState({ loading: false });
|
|
}, 300);
|
|
|
|
onEndReached = async() => {
|
|
const { loadingMore, end } = this.state;
|
|
if (loadingMore || end || this.data.length < 50) {
|
|
return;
|
|
}
|
|
|
|
this.setState({ loadingMore: true });
|
|
const { room } = this.props;
|
|
try {
|
|
const result = await RocketChat.loadMessagesForRoom({ rid: room.rid, t: room.t, latest: this.data[this.data.length - 1].ts });
|
|
this.setState({ end: result.length < 50, loadingMore: false });
|
|
} catch (e) {
|
|
this.setState({ loadingMore: false });
|
|
log('ListView.onEndReached', e);
|
|
}
|
|
}
|
|
|
|
scrollToBottom = () => {
|
|
this.listView.scrollTo({ x: 0, y: 0, animated: true });
|
|
}
|
|
|
|
handleScroll= (event) => {
|
|
if (event.nativeEvent.contentOffset.y > 0) {
|
|
this.setState({ showScollToBottomButton: true });
|
|
} else {
|
|
this.setState({ showScollToBottomButton: false });
|
|
}
|
|
}
|
|
|
|
renderFooter = () => {
|
|
const { loadingMore, loading } = this.state;
|
|
if (loadingMore || loading) {
|
|
return <ActivityIndicator style={styles.loadingMore} />;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
getScrollButtonStyle = () => {
|
|
let right = 30;
|
|
if (isIOS) {
|
|
right = isNotch ? 45 : 30;
|
|
}
|
|
return ({
|
|
position: 'absolute',
|
|
width: 42,
|
|
height: 42,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
right,
|
|
bottom: 70,
|
|
backgroundColor: '#EAF2FE',
|
|
borderRadius: 20
|
|
})
|
|
}
|
|
|
|
render() {
|
|
const { renderRow } = this.props;
|
|
const { showScollToBottomButton } = this.state;
|
|
const scrollButtonStyle = this.getScrollButtonStyle();
|
|
return (
|
|
<React.Fragment>
|
|
<ListView
|
|
enableEmptySections
|
|
ref={ref => this.listView = ref}
|
|
style={styles.list}
|
|
data={this.data}
|
|
keyExtractor={item => item._id}
|
|
onEndReachedThreshold={100}
|
|
renderFooter={this.renderFooter}
|
|
onEndReached={this.onEndReached}
|
|
dataSource={this.dataSource}
|
|
renderRow={(item, previousItem) => renderRow(item, previousItem)}
|
|
initialListSize={1}
|
|
pageSize={20}
|
|
onScroll={this.handleScroll}
|
|
testID='room-view-messages'
|
|
{...scrollPersistTaps}
|
|
/>
|
|
{showScollToBottomButton ? (
|
|
<TouchableOpacity activeOpacity={0.5} style={scrollButtonStyle} onPress={this.scrollToBottom}>
|
|
<CustomIcon name='arrow-down' color='white' size={30} />
|
|
</TouchableOpacity>
|
|
) : null}
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
}
|
|
|
|
@connect(state => ({
|
|
lastOpen: state.room.lastOpen
|
|
}), null, null, { forwardRef: true })
|
|
export class ListView extends OldList2 {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
curRenderedRowsCount: 10
|
|
// highlightedRow: ({}: Object)
|
|
};
|
|
}
|
|
|
|
getInnerViewNode() {
|
|
return this.refs.listView.getInnerViewNode();
|
|
}
|
|
|
|
scrollTo(...args) {
|
|
this.refs.listView.scrollTo(...args);
|
|
}
|
|
|
|
setNativeProps(props) {
|
|
this.refs.listView.setNativeProps(props);
|
|
}
|
|
|
|
static DataSource = DataSource;
|
|
|
|
render() {
|
|
const bodyComponents = [];
|
|
|
|
// const stickySectionHeaderIndices = [];
|
|
|
|
// const { renderSectionHeader } = this.props;
|
|
|
|
const header = this.props.renderHeader ? this.props.renderHeader() : null;
|
|
const footer = this.props.renderFooter ? this.props.renderFooter() : null;
|
|
// let totalIndex = header ? 1 : 0;
|
|
|
|
const { data } = this.props;
|
|
let count = 0;
|
|
|
|
for (let i = 0; i < this.state.curRenderedRowsCount && i < data.length; i += 1, count += 1) {
|
|
const message = data[i];
|
|
const previousMessage = data[i + 1];
|
|
bodyComponents.push(this.props.renderRow(message, previousMessage));
|
|
|
|
|
|
if (!previousMessage) {
|
|
bodyComponents.push(<Separator key={message.ts.toISOString()} ts={message.ts} />);
|
|
continue; // eslint-disable-line
|
|
}
|
|
|
|
const showUnreadSeparator = this.props.lastOpen
|
|
&& moment(message.ts).isAfter(this.props.lastOpen)
|
|
&& moment(previousMessage.ts).isBefore(this.props.lastOpen);
|
|
const showDateSeparator = !moment(message.ts).isSame(previousMessage.ts, 'day');
|
|
|
|
if (showUnreadSeparator || showDateSeparator) {
|
|
bodyComponents.push(<Separator
|
|
key={message.ts.toISOString()}
|
|
ts={showDateSeparator ? message.ts : null}
|
|
unread={showUnreadSeparator}
|
|
/>);
|
|
}
|
|
}
|
|
|
|
const { ...props } = this.props;
|
|
if (!props.scrollEventThrottle) {
|
|
props.scrollEventThrottle = DEFAULT_SCROLL_CALLBACK_THROTTLE;
|
|
}
|
|
if (props.removeClippedSubviews === undefined) {
|
|
props.removeClippedSubviews = true;
|
|
}
|
|
/* $FlowFixMe(>=0.54.0 site=react_native_fb,react_native_oss) This comment
|
|
* suppresses an error found when Flow v0.54 was deployed. To see the error
|
|
* delete this comment and run Flow. */
|
|
Object.assign(props, {
|
|
onScroll: this._onScroll,
|
|
/* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This
|
|
* comment suppresses an error when upgrading Flow's support for React.
|
|
* To see the error delete this comment and run Flow. */
|
|
// stickyHeaderIndices: this.props.stickyHeaderIndices.concat(stickySectionHeaderIndices,),
|
|
|
|
// Do not pass these events downstream to ScrollView since they will be
|
|
// registered in ListView's own ScrollResponder.Mixin
|
|
onKeyboardWillShow: undefined,
|
|
onKeyboardWillHide: undefined,
|
|
onKeyboardDidShow: undefined,
|
|
onKeyboardDidHide: undefined
|
|
});
|
|
|
|
const image = data.length === 0 ? { uri: 'message_empty' } : null;
|
|
return (
|
|
[
|
|
<ImageBackground key='listview-background' source={image} style={styles.imageBackground} />,
|
|
<ScrollView
|
|
key='listview-scroll'
|
|
ref={this._setScrollComponentRef}
|
|
onContentSizeChange={this._onContentSizeChange}
|
|
onLayout={this._onLayout}
|
|
{...props}
|
|
>
|
|
{header}
|
|
{bodyComponents}
|
|
{footer}
|
|
</ScrollView>
|
|
]
|
|
);
|
|
}
|
|
}
|
|
ListView.DataSource = DataSource;
|