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:
Diego Mello 2018-06-01 14:56:59 -03:00 committed by Guilherme Gazzo
parent 466a57e6b1
commit 22cbcf0b40
6 changed files with 90 additions and 118 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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}
/>);
} }
} }

View File

@ -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;

View File

@ -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>
);
}
}

View File

@ -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
}));
}); });
} }