diff --git a/app/components/post_list/index.js b/app/components/post_list/index.js
index 96c09e38e..59cb7cc65 100644
--- a/app/components/post_list/index.js
+++ b/app/components/post_list/index.js
@@ -5,7 +5,7 @@ import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {refreshChannelWithRetry} from 'app/actions/views/channel';
-import {makePreparePostIdsForPostList} from 'app/selectors/post_list';
+import {makePreparePostIdsForPostList, START_OF_NEW_MESSAGES} from 'app/selectors/post_list';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
@@ -13,10 +13,16 @@ import PostList from './post_list';
function makeMapStateToProps() {
const preparePostIds = makePreparePostIdsForPostList();
-
return (state, ownProps) => {
+ const postIds = preparePostIds(state, ownProps);
+ const measureCellLayout = postIds.indexOf(START_OF_NEW_MESSAGES) > -1 || Boolean(ownProps.highlightPostId);
+
+ const {deviceHeight} = state.device.dimension;
+
return {
- postIds: preparePostIds(state, ownProps),
+ deviceHeight,
+ measureCellLayout,
+ postIds,
theme: getTheme(state)
};
};
diff --git a/app/components/post_list/new_messages_divider.js b/app/components/post_list/new_messages_divider.js
index 4725b2f28..204a25869 100644
--- a/app/components/post_list/new_messages_divider.js
+++ b/app/components/post_list/new_messages_divider.js
@@ -14,15 +14,29 @@ import {makeStyleSheetFromTheme} from 'app/utils/theme';
function NewMessagesDivider(props) {
const style = getStyleFromTheme(props.theme);
+ let text = (
+
+ );
+
+ if (props.moreMessages) {
+ text = (
+
+ );
+ }
+
return (
-
+ {text}
@@ -30,6 +44,7 @@ function NewMessagesDivider(props) {
}
NewMessagesDivider.propTypes = {
+ moreMessages: PropTypes.bool,
style: ViewPropTypes.style,
theme: PropTypes.object
};
diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js
index d095ee57e..17718e3e7 100644
--- a/app/components/post_list/post_list.js
+++ b/app/components/post_list/post_list.js
@@ -19,6 +19,13 @@ import {makeExtraData} from 'app/utils/list_view';
import DateHeader from './date_header';
import LoadMorePosts from './load_more_posts';
import NewMessagesDivider from './new_messages_divider';
+import withLayout from './with_layout';
+
+const DateHeaderWithLayout = withLayout(DateHeader);
+const NewMessagesDividerWithLayout = withLayout(NewMessagesDivider);
+const PostWithLayout = withLayout(Post);
+
+const INITAL_BATCH_TO_RENDER = 15;
export default class PostList extends PureComponent {
static propTypes = {
@@ -27,11 +34,13 @@ export default class PostList extends PureComponent {
}).isRequired,
channelId: PropTypes.string,
currentUserId: PropTypes.string,
+ deviceHeight: PropTypes.number.isRequired,
highlightPostId: PropTypes.string,
indicateNewMessages: PropTypes.bool,
isSearchResult: PropTypes.bool,
lastViewedAt: PropTypes.number, // Used by container // eslint-disable-line no-unused-prop-types
loadMore: PropTypes.func,
+ measureCellLayout: PropTypes.bool,
navigator: PropTypes.object,
onPostPress: PropTypes.func,
onRefresh: PropTypes.func,
@@ -49,9 +58,11 @@ export default class PostList extends PureComponent {
newMessagesIndex = -1;
scrollToMessageTries = 0;
makeExtraData = makeExtraData();
+ itemMeasurements = {};
state = {
- managedConfig: {}
+ managedConfig: {},
+ scrollToMessage: false
};
componentWillMount() {
@@ -60,20 +71,24 @@ export default class PostList extends PureComponent {
componentDidMount() {
this.setManagedConfig();
- this.scrollList();
}
componentWillReceiveProps(nextProps) {
if (nextProps.postIds !== this.props.postIds) {
this.newMessagesIndex = -1;
}
+ if (this.props.channelId !== nextProps.channelId) {
+ this.itemMeasurements = {};
+ this.newMessageScrolledTo = false;
+ }
}
componentDidUpdate(prevProps) {
const initialPosts = !prevProps.postIds.length && prevProps.postIds !== this.props.postIds;
- if ((prevProps.channelId !== this.props.channelId || initialPosts || this.props.isSearchResult) && this.refs.list) {
- this.scrollToMessageTries = 0;
- this.scrollList();
+ if ((prevProps.channelId !== this.props.channelId || initialPosts) && this.refs.list) {
+ this.scrollToBottomOffset();
+ } else if ((this.props.measureCellLayout || this.props.isSearchResult) && this.state.scrollToMessage) {
+ this.scrollListToMessageOffset();
}
}
@@ -81,30 +96,47 @@ export default class PostList extends PureComponent {
mattermostManaged.removeEventListener(this.listenerId);
}
- scrollList = () => {
+ scrollToBottomOffset = () => {
InteractionManager.runAfterInteractions(() => {
- if (this.props.postIds.length && this.newMessagesIndex !== -1) {
- if (this.refs.list) {
- this.refs.list.scrollToIndex({
- index: this.newMessagesIndex,
- viewPosition: 1,
- viewOffset: -10,
- animated: true
- });
- }
- } else if (this.refs.list) {
- this.refs.list.scrollToOffset({y: 0, animated: false});
+ if (this.refs.list) {
+ this.refs.list.scrollToOffset({offset: 0, animated: false});
}
});
- };
+ }
- scrollListFailed = ({index}) => {
- if (this.scrollToMessageTries < 3) {
- this.scrollToMessageTries++;
- setTimeout(() => {
- this.newMessagesIndex = index;
- this.scrollList();
- }, 300);
+ getMeasurementOffset = (index) => {
+ const orderedKeys = Object.keys(this.itemMeasurements).sort().slice(0, index);
+ return orderedKeys.map((i) => this.itemMeasurements[i]).reduce((a, b) => a + b, 0);
+ }
+
+ scrollListToMessageOffset = () => {
+ const index = this.moreNewMessages ? this.props.postIds.length : this.newMessagesIndex;
+ if (index !== -1) {
+ let offset = this.getMeasurementOffset(index);
+
+ const windowHeight = this.state.postListHeight;
+ if (index !== this.props.postIds.length - 1) {
+ if (offset < windowHeight) {
+ return; // no need to scroll since item is in view
+ } else if (offset > windowHeight) {
+ offset = (offset - (windowHeight / 2)) + this.itemMeasurements[index];
+ }
+ }
+
+ InteractionManager.runAfterInteractions(() => {
+ if (this.refs.list) {
+ if (!this.moreNewMessages) {
+ this.newMessageScrolledTo = true;
+ }
+
+ this.refs.list.scrollToOffset({offset, animated: true});
+ this.newMessagesIndex = -1;
+ this.moreNewMessages = false;
+ this.setState({
+ scrollToMessage: false
+ });
+ }
+ });
}
}
@@ -140,17 +172,37 @@ export default class PostList extends PureComponent {
}
};
+ measureItem = (index, height) => {
+ this.itemMeasurements[index] = height;
+ if (this.props.postIds.length === Object.values(this.itemMeasurements).length) {
+ if (this.newMessagesIndex !== -1 && !this.newMessageScrolledTo) {
+ this.setState({
+ scrollToMessage: true
+ });
+ }
+ }
+ }
+
renderItem = ({item, index}) => {
if (item === START_OF_NEW_MESSAGES) {
this.newMessagesIndex = index;
+
+ // postIds includes a date item after the new message indicator so 2
+ // needs to be added to the index for the length check to be correct.
+ this.moreNewMessages = this.props.postIds.length === index + 2;
+
return (
-
);
} else if (item.indexOf(DATE_LINE) === 0) {
const date = item.substring(DATE_LINE.length);
- return this.renderDateHeader(new Date(date));
+ return this.renderDateHeader(new Date(date), index);
}
const postId = item;
@@ -163,9 +215,14 @@ export default class PostList extends PureComponent {
return this.renderPost(postId, previousPostId, nextPostId, index);
};
- renderDateHeader = (date) => {
+ renderDateHeader = (date, index) => {
return (
-
+
);
};
@@ -186,17 +243,20 @@ export default class PostList extends PureComponent {
}
return (
-
);
};
@@ -223,6 +283,13 @@ export default class PostList extends PureComponent {
);
};
+ onLayout = (event) => {
+ const {height} = event.nativeEvent.layout;
+ this.setState({
+ postListHeight: height
+ });
+ }
+
render() {
const {
channelId,
@@ -241,16 +308,17 @@ export default class PostList extends PureComponent {
return (
{
+ const {height} = event.nativeEvent.layout;
+ this.props.onLayoutCalled(this.props.index, height);
+ };
+
+ render() {
+ const {index, onLayoutCalled, shouldCallOnLayout, ...otherProps} = this.props; //eslint-disable-line no-unused-vars
+
+ if (shouldCallOnLayout) {
+ return (
+
+
+
+ );
+ }
+
+ return ;
+ }
+ };
+}
+
+export default withLayout;
diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index d5ba2d273..a9d76f65d 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -2098,6 +2098,7 @@
"mobile.post_textbox.empty.message": "You are trying to send an empty message.\nPlease make sure you have a message or at least one attached file.",
"mobile.post_textbox.empty.ok": "OK",
"mobile.post_textbox.empty.title": "Empty Message",
+ "mobile.posts_view.moreMsg": "More New Messages Above",
"mobile.rename_channel.display_name_maxLength": "Channel name must be less than {maxLength, number} characters",
"mobile.rename_channel.display_name_minLength": "Channel name must be {minLength, number} or more characters",
"mobile.rename_channel.display_name_required": "Channel name is required",