PLT-5499 Indicate start of new messages in posts list (#279)

* Indicate start of new messages in posts list

* Use flag prop to indicate start of new messages in posts list

* Declare style props with View.propTypes.style
This commit is contained in:
Chris Duarte 2017-02-20 13:05:16 -08:00 committed by enahum
parent 584096aea8
commit 0bf26e95b0
8 changed files with 87 additions and 9 deletions

View file

@ -91,7 +91,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
export default class Post extends React.Component {
static propTypes = {
style: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.number]),
style: View.propTypes.style,
post: React.PropTypes.object.isRequired,
user: React.PropTypes.object,
displayName: React.PropTypes.string,

View file

@ -35,7 +35,7 @@ export default class DateHeader extends React.Component {
static propTypes = {
date: React.PropTypes.object.isRequired,
theme: React.PropTypes.object.isRequired,
style: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.number])
style: View.propTypes.style
};
render() {

View file

@ -0,0 +1,50 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {StyleSheet, View} from 'react-native';
import FormattedText from 'app/components/formatted_text';
const style = StyleSheet.create({
container: {
alignItems: 'center',
flexDirection: 'row'
},
textContainer: {
marginHorizontal: 15
},
line: {
flex: 1,
height: 1,
backgroundColor: '#ffaf53',
opacity: 0.2
},
text: {
fontSize: 14,
fontWeight: '600',
color: '#ffaf53'
}
});
function NewMessagesDivider(props) {
return (
<View style={[style.container, props.style]}>
<View style={style.line}/>
<View style={style.textContainer}>
<FormattedText
id='post_list.newMsg'
defaultMessage='New Messages'
style={style.text}
/>
</View>
<View style={style.line}/>
</View>
);
}
NewMessagesDivider.propTypes = {
style: View.propTypes.style
};
export default NewMessagesDivider;

View file

@ -9,9 +9,11 @@ import {
import Post from 'app/components/post';
import {Constants} from 'service/constants';
import {addDatesToPostList} from 'service/utils/post_utils';
import DateHeader from './date_header';
import NewMessagesDivider from './new_messages_divider';
const style = StyleSheet.create({
container: {
@ -27,24 +29,28 @@ export default class PostList extends React.Component {
posts: React.PropTypes.array.isRequired,
theme: React.PropTypes.object.isRequired,
onPostPress: React.PropTypes.func,
renderReplies: React.PropTypes.bool
renderReplies: React.PropTypes.bool,
indicateNewMessages: React.PropTypes.bool,
lastViewedAt: React.PropTypes.number
};
constructor(props) {
super(props);
const {posts, indicateNewMessages, lastViewedAt} = this.props;
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (a, b) => a !== b
}).cloneWithRows(addDatesToPostList(this.props.posts))
}).cloneWithRows(addDatesToPostList(posts, indicateNewMessages, lastViewedAt))
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.posts !== this.props.posts) {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(addDatesToPostList(nextProps.posts))
});
const {posts, indicateNewMessages, lastViewedAt} = nextProps;
const dataSource = this.state.dataSource.cloneWithRows(
addDatesToPostList(posts, indicateNewMessages, lastViewedAt)
);
this.setState({dataSource});
}
}
@ -52,6 +58,14 @@ export default class PostList extends React.Component {
if (row instanceof Date) {
return this.renderDateHeader(row);
}
if (row === Constants.START_OF_NEW_MESSAGES) {
return (
<NewMessagesDivider
theme={this.props.theme}
style={style.row}
/>
);
}
return this.renderPost(row);
};

View file

@ -12,6 +12,7 @@ export default class ChannelPostList extends PureComponent {
goToThread: PropTypes.func.isRequired
}).isRequired,
channel: PropTypes.object.isRequired,
myMember: PropTypes.object.isRequired,
posts: PropTypes.array.isRequired
};
@ -39,6 +40,8 @@ export default class ChannelPostList extends PureComponent {
posts={this.props.posts}
onPostPress={this.goToThread}
renderReplies={true}
indicateNewMessages={true}
lastViewedAt={this.props.myMember.last_viewed_at}
/>
);
}

View file

@ -65,6 +65,7 @@ const getPostsInCurrentChannelWithReplyProps = createSelector(
function mapStateToProps(state, ownProps) {
return {
...ownProps,
myMember: state.entities.channels.myMembers[ownProps.channel.id],
posts: getPostsInCurrentChannelWithReplyProps(state)
};
}

View file

@ -33,6 +33,8 @@ const Constants = {
DISPLAY_PREFER_NICKNAME: 'nickname_full_name',
DISPLAY_PREFER_FULL_NAME: 'full_name',
START_OF_NEW_MESSAGES: 'start-of-new-messages',
POST_HEADER_CHANGE: 'system_header_change',
POST_PURPOSE_CHANGE: 'system_purpose_change'
};

View file

@ -7,10 +7,12 @@ export function isSystemMessage(post) {
return post.type && post.type.startsWith(Constants.SYSTEM_MESSAGE_PREFIX);
}
export function addDatesToPostList(posts) {
export function addDatesToPostList(posts, indicateNewMessages, lastViewedAt) {
const out = [];
let lastDate = null;
let subsequentPostIsUnread = false;
let postIsUnread;
for (const post of posts) {
const postDate = new Date(post.create_at);
@ -21,6 +23,12 @@ export function addDatesToPostList(posts) {
lastDate = postDate;
out.push(post);
postIsUnread = post.create_at > lastViewedAt;
if (indicateNewMessages && subsequentPostIsUnread && !postIsUnread) {
out.push(Constants.START_OF_NEW_MESSAGES);
}
subsequentPostIsUnread = postIsUnread;
}
// Push on the date header for the oldest post