Unread and date separator layout improved (#319)
<!-- INSTRUCTION: Keep the line below to notify all core developers about this new PR --> @RocketChat/ReactNative <!-- INSTRUCTION: Inform the issue number that this PR closes, or remove the line below --> - [x] Unread and date separator layout - [x] "Start of conversation"/"Loading messages" label ![screen shot 2018-05-30 at 18 10 43](https://user-images.githubusercontent.com/804994/40747867-0424964a-6435-11e8-9293-31cc43c110ab.png) ![screen shot 2018-05-30 at 18 09 05](https://user-images.githubusercontent.com/804994/40747868-04484784-6435-11e8-8c31-92e0776276f0.png) <!-- INSTRUCTION: Tell us more about your PR with screen shots if you can -->
This commit is contained in:
parent
466a57e6b1
commit
22cbcf0b40
|
@ -29,20 +29,6 @@ async function loadMessagesForRoomDDP(...args) {
|
||||||
console.warn('loadMessagesForRoomDDP', e);
|
console.warn('loadMessagesForRoomDDP', e);
|
||||||
return loadMessagesForRoomRest.call(this, ...args);
|
return loadMessagesForRoomRest.call(this, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// }
|
|
||||||
// if (cb) {
|
|
||||||
// cb({ end: data && data.messages.length < 20 });
|
|
||||||
// }
|
|
||||||
// return data.message;
|
|
||||||
// }, (err) => {
|
|
||||||
// if (err) {
|
|
||||||
// if (cb) {
|
|
||||||
// cb({ end: true });
|
|
||||||
// }
|
|
||||||
// return Promise.reject(err);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function loadMessagesForRoom(...args) {
|
export default async function loadMessagesForRoom(...args) {
|
||||||
|
@ -52,13 +38,14 @@ export default async function loadMessagesForRoom(...args) {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const data = (await (false && this.ddp.status ? loadMessagesForRoomDDP.call(this, ...args) : loadMessagesForRoomRest.call(this, ...args))).map(buildMessage);
|
const data = (await (false && this.ddp.status ? loadMessagesForRoomDDP.call(this, ...args) : loadMessagesForRoomRest.call(this, ...args))).map(buildMessage);
|
||||||
if (data) {
|
if (data && data.length) {
|
||||||
InteractionManager.runAfterInteractions(() => {
|
InteractionManager.runAfterInteractions(() => {
|
||||||
db.write(() => data.forEach(message => db.create('messages', message, true)));
|
db.write(() => data.forEach(message => db.create('messages', message, true)));
|
||||||
return resolve(data);
|
return resolve(data);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
return resolve([]);
|
||||||
}
|
}
|
||||||
return resolve([]);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('loadMessagesForRoom', e);
|
log('loadMessagesForRoom', e);
|
||||||
reject(e);
|
reject(e);
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { View, StyleSheet, Text } from 'react-native';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
dateSeparator: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginVertical: -5
|
|
||||||
},
|
|
||||||
dateSeparatorLine: {
|
|
||||||
borderTopColor: '#eaeaea',
|
|
||||||
borderTopWidth: StyleSheet.hairlineWidth,
|
|
||||||
flex: 1
|
|
||||||
},
|
|
||||||
dateSeparatorBadge: {
|
|
||||||
color: '#444444',
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
fontSize: 11,
|
|
||||||
paddingHorizontal: 10,
|
|
||||||
transform: [{ scaleY: -1 }]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const DateSeparator = ({ ts }) => {
|
|
||||||
const text = moment(ts).format('MMMM DD, YYYY');
|
|
||||||
return (
|
|
||||||
<View style={styles.dateSeparator}>
|
|
||||||
<View style={styles.dateSeparatorLine} />
|
|
||||||
<Text style={styles.dateSeparatorBadge}>{text}</Text>
|
|
||||||
<View style={styles.dateSeparatorLine} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
DateSeparator.propTypes = {
|
|
||||||
ts: PropTypes.instanceOf(Date)
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DateSeparator;
|
|
|
@ -6,8 +6,7 @@ import moment from 'moment';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import DateSeparator from './DateSeparator';
|
import Separator from './Separator';
|
||||||
import UnreadSeparator from './UnreadSeparator';
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Typing from '../../containers/Typing';
|
import Typing from '../../containers/Typing';
|
||||||
import database from '../../lib/realm';
|
import database from '../../lib/realm';
|
||||||
|
@ -72,7 +71,7 @@ export class List extends React.Component {
|
||||||
onEndReachedThreshold={100}
|
onEndReachedThreshold={100}
|
||||||
renderFooter={this.props.renderFooter}
|
renderFooter={this.props.renderFooter}
|
||||||
renderHeader={() => <Typing />}
|
renderHeader={() => <Typing />}
|
||||||
onEndReached={() => this.props.onEndReached(this.data)}
|
onEndReached={() => this.props.onEndReached(this.data[this.data.length - 1])}
|
||||||
dataSource={this.dataSource}
|
dataSource={this.dataSource}
|
||||||
renderRow={(item, previousItem) => this.props.renderRow(item, previousItem)}
|
renderRow={(item, previousItem) => this.props.renderRow(item, previousItem)}
|
||||||
initialListSize={20}
|
initialListSize={20}
|
||||||
|
@ -128,17 +127,21 @@ export class ListView extends OldList2 {
|
||||||
|
|
||||||
|
|
||||||
if (!previousMessage) {
|
if (!previousMessage) {
|
||||||
|
bodyComponents.push(<Separator key={message.ts.toISOString()} ts={message.ts} />);
|
||||||
continue; // eslint-disable-line
|
continue; // eslint-disable-line
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.lastOpen &&
|
const showUnreadSeparator = this.props.lastOpen &&
|
||||||
moment(message.ts).isAfter(this.props.lastOpen) &&
|
moment(message.ts).isAfter(this.props.lastOpen) &&
|
||||||
moment(previousMessage.ts).isBefore(this.props.lastOpen)
|
moment(previousMessage.ts).isBefore(this.props.lastOpen);
|
||||||
) {
|
const showDateSeparator = !moment(message.ts).isSame(previousMessage.ts, 'day');
|
||||||
bodyComponents.push(<UnreadSeparator key='unread-separator' />);
|
|
||||||
}
|
if (showUnreadSeparator || showDateSeparator) {
|
||||||
if (!moment(message.ts).isSame(previousMessage.ts, 'day')) {
|
bodyComponents.push(<Separator
|
||||||
bodyComponents.push(<DateSeparator key={message.ts.toISOString()} ts={message.ts} />);
|
key={message.ts.toISOString()}
|
||||||
|
ts={showDateSeparator ? message.ts : null}
|
||||||
|
unread={showUnreadSeparator}
|
||||||
|
/>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View, StyleSheet, Text } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import moment from 'moment';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginVertical: 10
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
borderTopColor: '#eaeaea',
|
||||||
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: '#444444',
|
||||||
|
fontSize: 11,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
transform: [{ scaleY: -1 }]
|
||||||
|
},
|
||||||
|
unreadLine: {
|
||||||
|
borderTopColor: 'red'
|
||||||
|
},
|
||||||
|
unreadText: {
|
||||||
|
color: 'red'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const DateSeparator = ({ ts, unread }) => {
|
||||||
|
const date = ts ? moment(ts).format('MMMM DD, YYYY') : null;
|
||||||
|
if (ts && unread) {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text style={[styles.text, styles.unreadText]}>{date}</Text>
|
||||||
|
<View style={[styles.line, styles.unreadLine]} />
|
||||||
|
<Text style={[styles.text, styles.unreadText]}>{I18n.t('unread_messages')}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (ts) {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.line} />
|
||||||
|
<Text style={styles.text}>{date}</Text>
|
||||||
|
<View style={styles.line} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={[styles.line, styles.unreadLine]} />
|
||||||
|
<Text style={[styles.text, styles.unreadText]}>{I18n.t('unread_messages')}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
DateSeparator.propTypes = {
|
||||||
|
ts: PropTypes.instanceOf(Date),
|
||||||
|
unread: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DateSeparator;
|
|
@ -1,38 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { View, StyleSheet, Text, LayoutAnimation } from 'react-native';
|
|
||||||
import I18n from '../../i18n';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
firstUnread: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginVertical: -7
|
|
||||||
},
|
|
||||||
firstUnreadLine: {
|
|
||||||
borderTopColor: 'red',
|
|
||||||
borderTopWidth: StyleSheet.hairlineWidth,
|
|
||||||
flex: 1
|
|
||||||
},
|
|
||||||
firstUnreadBadge: {
|
|
||||||
color: 'red',
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
fontSize: 11,
|
|
||||||
paddingHorizontal: 10,
|
|
||||||
transform: [{ scaleY: -1 }]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default class UnreadSeparator extends React.PureComponent {
|
|
||||||
componentWillUnmount() {
|
|
||||||
LayoutAnimation.linear();
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<View style={styles.firstUnread}>
|
|
||||||
<View style={styles.firstUnreadLine} />
|
|
||||||
<Text style={styles.firstUnreadBadge}>{I18n.t('unread_messages')}</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -70,7 +70,8 @@ export default class RoomView extends LoggedView {
|
||||||
this.state = {
|
this.state = {
|
||||||
loaded: true,
|
loaded: true,
|
||||||
joined: typeof props.rid === 'undefined',
|
joined: typeof props.rid === 'undefined',
|
||||||
room: {}
|
room: {},
|
||||||
|
end: false
|
||||||
};
|
};
|
||||||
this.onReactionPress = this.onReactionPress.bind(this);
|
this.onReactionPress = this.onReactionPress.bind(this);
|
||||||
}
|
}
|
||||||
|
@ -87,20 +88,15 @@ export default class RoomView extends LoggedView {
|
||||||
this.props.editCancel();
|
this.props.editCancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
onEndReached = (data) => {
|
onEndReached = (lastRowData) => {
|
||||||
if (this.props.loading || this.state.end) {
|
if (!lastRowData) {
|
||||||
|
this.setState({ end: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(async() => {
|
||||||
const lastRowData = data[data.length - 1];
|
const result = await RocketChat.loadMessagesForRoom({ rid: this.rid, t: this.state.room.t, latest: lastRowData.ts });
|
||||||
if (!lastRowData) {
|
this.setState({ end: result < 20 });
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO: fix
|
|
||||||
RocketChat.loadMessagesForRoom({ rid: this.rid, t: this.state.room.t, latest: lastRowData.ts }, ({ end }) => end && this.setState({
|
|
||||||
end
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue