From 0bf26e95b083d2001220252d34fdf6221705901b Mon Sep 17 00:00:00 2001 From: Chris Duarte Date: Mon, 20 Feb 2017 13:05:16 -0800 Subject: [PATCH] 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 --- app/components/post/post.js | 2 +- app/components/post_list/date_header.js | 2 +- .../post_list/new_messages_divider.js | 50 +++++++++++++++++++ app/components/post_list/post_list.js | 26 +++++++--- .../channel_post_list/channel_post_list.js | 3 ++ .../channel_post_list_container.js | 1 + service/constants/constants.js | 2 + service/utils/post_utils.js | 10 +++- 8 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 app/components/post_list/new_messages_divider.js diff --git a/app/components/post/post.js b/app/components/post/post.js index 5163c3f12..55f7636a0 100644 --- a/app/components/post/post.js +++ b/app/components/post/post.js @@ -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, diff --git a/app/components/post_list/date_header.js b/app/components/post_list/date_header.js index 8f18e0d2e..99203ca07 100644 --- a/app/components/post_list/date_header.js +++ b/app/components/post_list/date_header.js @@ -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() { diff --git a/app/components/post_list/new_messages_divider.js b/app/components/post_list/new_messages_divider.js new file mode 100644 index 000000000..e2ca9436a --- /dev/null +++ b/app/components/post_list/new_messages_divider.js @@ -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 ( + + + + + + + + ); +} + +NewMessagesDivider.propTypes = { + style: View.propTypes.style +}; + +export default NewMessagesDivider; diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js index d7fe47c36..966976eea 100644 --- a/app/components/post_list/post_list.js +++ b/app/components/post_list/post_list.js @@ -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 ( + + ); + } return this.renderPost(row); }; diff --git a/app/scenes/channel/channel_post_list/channel_post_list.js b/app/scenes/channel/channel_post_list/channel_post_list.js index 52b8eac69..3bc5c4841 100644 --- a/app/scenes/channel/channel_post_list/channel_post_list.js +++ b/app/scenes/channel/channel_post_list/channel_post_list.js @@ -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} /> ); } diff --git a/app/scenes/channel/channel_post_list/channel_post_list_container.js b/app/scenes/channel/channel_post_list/channel_post_list_container.js index 402054611..cd483b8db 100644 --- a/app/scenes/channel/channel_post_list/channel_post_list_container.js +++ b/app/scenes/channel/channel_post_list/channel_post_list_container.js @@ -65,6 +65,7 @@ const getPostsInCurrentChannelWithReplyProps = createSelector( function mapStateToProps(state, ownProps) { return { ...ownProps, + myMember: state.entities.channels.myMembers[ownProps.channel.id], posts: getPostsInCurrentChannelWithReplyProps(state) }; } diff --git a/service/constants/constants.js b/service/constants/constants.js index f966a1625..591c5779f 100644 --- a/service/constants/constants.js +++ b/service/constants/constants.js @@ -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' }; diff --git a/service/utils/post_utils.js b/service/utils/post_utils.js index 8c7adff4e..c3d84a288 100644 --- a/service/utils/post_utils.js +++ b/service/utils/post_utils.js @@ -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