MM-14958 Scroll to bottom when posting a new message (#2761)
This commit is contained in:
parent
963adcdc9f
commit
5ce9208d32
11 changed files with 196 additions and 360 deletions
51
NOTICE.txt
51
NOTICE.txt
|
|
@ -725,7 +725,7 @@ Common code (API client, Redux stores, logic, utility functions) for building a
|
|||
|
||||
* LICENSE: Apache-2.0
|
||||
|
||||
Copyright 2015-present Mattermost, Inc.
|
||||
Copyright 2015-present Mattermost, Inc.
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
|
@ -1401,7 +1401,7 @@ SOFTWARE.
|
|||
|
||||
This product contains 'react-native-doc-viewer' by Philipp Hecht.
|
||||
|
||||
React Native Native Module Bridge Quicklock Document Viewer for IOS + Android supports pdf, png, jpg, xls, ppt, doc, docx, pptx, xlx + Video Player mp4 supported
|
||||
React Native Native Module Bridge Quicklock Document Viewer for IOS + Android supports pdf, png, jpg, xls, ppt, doc, docx, pptx, xlx + Video Player mp4 supported
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/philipphecht/react-native-doc-viewer/blob/master/README.md
|
||||
|
|
@ -1859,41 +1859,6 @@ SOFTWARE.
|
|||
|
||||
---
|
||||
|
||||
## react-native-recyclerview-list
|
||||
|
||||
This product contains 'react-native-recyclerview-list' by GitHub user "godness84".
|
||||
|
||||
A RecyclerView implementation for React Native
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/godness84/react-native-recyclerview-list#readme
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Marc Shilling
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-safe-area
|
||||
|
||||
This product contains 'react-native-safe-area' by Masayuki Iwai.
|
||||
|
|
@ -1942,16 +1907,16 @@ This package simplifies constructing the getItemLayout prop for react native Sec
|
|||
|
||||
Copyright (c) 2017 Jan Soendermann
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -229,7 +229,6 @@ dependencies {
|
|||
implementation project(':react-native-sentry')
|
||||
implementation project(':react-native-exception-handler')
|
||||
implementation project(':rn-fetch-blob')
|
||||
implementation project(':react-native-recyclerview-list')
|
||||
implementation project(':react-native-webview')
|
||||
implementation project(':react-native-gesture-handler')
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import com.masteratul.exceptionhandler.ReactNativeExceptionHandlerPackage;
|
|||
import com.RNFetchBlob.RNFetchBlobPackage;
|
||||
import com.gantix.JailMonkey.JailMonkeyPackage;
|
||||
import io.tradle.react.LocalAuthPackage;
|
||||
import com.github.godness84.RNRecyclerViewList.RNRecyclerviewListPackage;
|
||||
import com.reactnativecommunity.webview.RNCWebViewPackage;
|
||||
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
|
||||
|
||||
|
|
@ -84,7 +83,6 @@ public class MainApplication extends NavigationApplication implements INotificat
|
|||
new SharePackage(this),
|
||||
new KeychainPackage(),
|
||||
new InitializationPackage(this),
|
||||
new RNRecyclerviewListPackage(),
|
||||
new RNCWebViewPackage(),
|
||||
new RNGestureHandlerPackage()
|
||||
);
|
||||
|
|
|
|||
|
|
@ -41,7 +41,5 @@ include ':react-native-svg'
|
|||
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
|
||||
include ':react-native-linear-gradient'
|
||||
project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android')
|
||||
include ':react-native-recyclerview-list'
|
||||
project(':react-native-recyclerview-list').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-recyclerview-list/android')
|
||||
include ':react-native-webview'
|
||||
project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android')
|
||||
|
|
|
|||
|
|
@ -1,125 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import RecyclerViewList, {DataSource, RecyclerRefreshControl} from 'react-native-recyclerview-list';
|
||||
|
||||
import {ListTypes} from 'app/constants';
|
||||
|
||||
import PostListBase from './post_list_base';
|
||||
|
||||
const SCROLL_UP_MULTIPLIER = 3.5;
|
||||
|
||||
export default class PostList extends PostListBase {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.contentOffsetY = 0;
|
||||
|
||||
this.state = {
|
||||
refreshing: false,
|
||||
dataSource: new DataSource(props.postIds, this.keyExtractor),
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.channelId !== nextProps.channelId) {
|
||||
this.contentOffsetY = 0;
|
||||
this.contentHeight = 0;
|
||||
}
|
||||
|
||||
if (nextProps.postIds !== this.props.postIds) {
|
||||
this.setState({
|
||||
dataSource: new DataSource(nextProps.postIds, this.keyExtractor),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleLayout = (layoutHeight, contentWidth, contentHeight) => {
|
||||
if (layoutHeight && contentHeight < layoutHeight) {
|
||||
// We still have less than 1 screen of posts loaded with more to get, so load more
|
||||
this.props.onLoadMoreUp();
|
||||
}
|
||||
};
|
||||
|
||||
handleScroll = (event) => {
|
||||
const {layoutMeasurement, contentOffset, contentSize} = event.nativeEvent;
|
||||
|
||||
if (!this.postListHeight) {
|
||||
this.handleLayout(layoutMeasurement.height, contentSize.width, contentSize.height);
|
||||
}
|
||||
|
||||
this.postListHeight = layoutMeasurement.height;
|
||||
|
||||
if (contentOffset.y >= 0) {
|
||||
const definedHeight = Math.round(this.postListHeight) * SCROLL_UP_MULTIPLIER;
|
||||
const pageOffsetY = contentOffset.y;
|
||||
const direction = (this.contentOffsetY < pageOffsetY) ?
|
||||
ListTypes.VISIBILITY_SCROLL_DOWN :
|
||||
ListTypes.VISIBILITY_SCROLL_UP;
|
||||
this.contentOffsetY = contentOffset.y;
|
||||
|
||||
switch (direction) {
|
||||
case ListTypes.VISIBILITY_SCROLL_UP:
|
||||
if (Math.round(pageOffsetY) < definedHeight) {
|
||||
this.props.onLoadMoreUp();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
channelId,
|
||||
initialIndex,
|
||||
postIds,
|
||||
} = this.props;
|
||||
|
||||
const otherProps = {};
|
||||
const footer = typeof this.props.renderFooter === 'object' ? this.props.renderFooter : this.props.renderFooter();
|
||||
if (postIds.length) {
|
||||
otherProps.ListFooterComponent = footer;
|
||||
} else {
|
||||
otherProps.ListEmptyComponent = footer;
|
||||
}
|
||||
|
||||
const hasPostsKey = postIds.length ? 'true' : 'false';
|
||||
return (
|
||||
<RecyclerRefreshControl
|
||||
key={`recyclerFor-${channelId}-${hasPostsKey}`}
|
||||
colors={['red', 'blue']}
|
||||
direction='bottom'
|
||||
enabled={Boolean(channelId)}
|
||||
onRefresh={this.handleRefresh}
|
||||
refreshing={this.state.refreshing}
|
||||
style={styles.flex}
|
||||
>
|
||||
<RecyclerViewList
|
||||
key={`${channelId}-${hasPostsKey}`}
|
||||
ref={'list'}
|
||||
style={[styles.flex, styles.postListContent]}
|
||||
dataSource={this.state.dataSource}
|
||||
renderItem={this.renderItem}
|
||||
initialScrollIndex={initialIndex}
|
||||
initialScrollPosition={RecyclerViewList.Constants.ScrollPosition.CENTER}
|
||||
inverted={true}
|
||||
onScroll={this.handleScroll}
|
||||
onContentSizeChange={this.onContentSizeChange}
|
||||
windowSize={60}
|
||||
{...otherProps}
|
||||
/>
|
||||
</RecyclerRefreshControl>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
postListContent: {
|
||||
paddingTop: 5,
|
||||
},
|
||||
});
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {FlatList, StyleSheet} from 'react-native';
|
||||
|
||||
import {ListTypes} from 'app/constants';
|
||||
import {makeExtraData} from 'app/utils/list_view';
|
||||
|
||||
import PostListBase from './post_list_base';
|
||||
|
||||
const INITIAL_BATCH_TO_RENDER = 15;
|
||||
const SCROLL_UP_MULTIPLIER = 3.5;
|
||||
const SCROLL_POSITION_CONFIG = {
|
||||
|
||||
// To avoid scrolling the list when new messages arrives
|
||||
// if the user is not at the bottom
|
||||
minIndexForVisible: 0,
|
||||
|
||||
// If the user is at the bottom or 60px from the bottom
|
||||
// auto scroll show the new message
|
||||
autoscrollToTopThreshold: 60,
|
||||
};
|
||||
|
||||
export default class PostList extends PostListBase {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.hasDoneInitialScroll = false;
|
||||
this.contentOffsetY = 0;
|
||||
this.makeExtraData = makeExtraData();
|
||||
|
||||
this.state = {
|
||||
postListHeight: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.channelId !== nextProps.channelId) {
|
||||
this.contentOffsetY = 0;
|
||||
this.hasDoneInitialScroll = false;
|
||||
}
|
||||
}
|
||||
|
||||
handleContentSizeChange = (contentWidth, contentHeight) => {
|
||||
this.scrollToInitialIndexIfNeeded(contentWidth, contentHeight);
|
||||
|
||||
if (this.state.postListHeight && contentHeight < this.state.postListHeight && this.props.extraData) {
|
||||
// We still have less than 1 screen of posts loaded with more to get, so load more
|
||||
this.props.onLoadMoreUp();
|
||||
}
|
||||
};
|
||||
|
||||
handleLayout = (event) => {
|
||||
const {height} = event.nativeEvent.layout;
|
||||
this.setState({postListHeight: height});
|
||||
};
|
||||
|
||||
handleScroll = (event) => {
|
||||
const pageOffsetY = event.nativeEvent.contentOffset.y;
|
||||
if (pageOffsetY > 0) {
|
||||
const contentHeight = event.nativeEvent.contentSize.height;
|
||||
const direction = (this.contentOffsetY < pageOffsetY) ?
|
||||
ListTypes.VISIBILITY_SCROLL_UP :
|
||||
ListTypes.VISIBILITY_SCROLL_DOWN;
|
||||
|
||||
this.contentOffsetY = pageOffsetY;
|
||||
if (
|
||||
direction === ListTypes.VISIBILITY_SCROLL_UP &&
|
||||
(contentHeight - pageOffsetY) < (this.state.postListHeight * SCROLL_UP_MULTIPLIER)
|
||||
) {
|
||||
this.props.onLoadMoreUp();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleScrollToIndexFailed = () => {
|
||||
requestAnimationFrame(() => {
|
||||
this.hasDoneInitialScroll = false;
|
||||
this.scrollToInitialIndexIfNeeded(1, 1);
|
||||
});
|
||||
};
|
||||
|
||||
scrollToInitialIndexIfNeeded = (width, height) => {
|
||||
if (
|
||||
width > 0 &&
|
||||
height > 0 &&
|
||||
this.props.initialIndex > 0 &&
|
||||
!this.hasDoneInitialScroll
|
||||
) {
|
||||
this.refs.list.scrollToIndex({
|
||||
animated: false,
|
||||
index: this.props.initialIndex,
|
||||
viewPosition: 0.5,
|
||||
});
|
||||
this.hasDoneInitialScroll = true;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
channelId,
|
||||
highlightPostId,
|
||||
postIds,
|
||||
refreshing,
|
||||
} = this.props;
|
||||
|
||||
const refreshControl = {refreshing};
|
||||
|
||||
if (channelId) {
|
||||
refreshControl.onRefresh = this.handleRefresh;
|
||||
}
|
||||
|
||||
const hasPostsKey = postIds.length ? 'true' : 'false';
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
key={`recyclerFor-${channelId}-${hasPostsKey}`}
|
||||
ref='list'
|
||||
contentContainerStyle={styles.postListContent}
|
||||
data={postIds}
|
||||
extraData={this.makeExtraData(channelId, highlightPostId, this.props.extraData)}
|
||||
initialNumToRender={INITIAL_BATCH_TO_RENDER}
|
||||
inverted={true}
|
||||
keyExtractor={this.keyExtractor}
|
||||
ListFooterComponent={this.props.renderFooter}
|
||||
maintainVisibleContentPosition={SCROLL_POSITION_CONFIG}
|
||||
maxToRenderPerBatch={INITIAL_BATCH_TO_RENDER + 1}
|
||||
onContentSizeChange={this.handleContentSizeChange}
|
||||
onLayout={this.handleLayout}
|
||||
onScroll={this.handleScroll}
|
||||
onScrollToIndexFailed={this.handleScrollToIndexFailed}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={this.renderItem}
|
||||
scrollEventThrottle={60}
|
||||
{...refreshControl}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
postListContent: {
|
||||
paddingTop: 5,
|
||||
},
|
||||
});
|
||||
|
|
@ -3,20 +3,36 @@
|
|||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {FlatList, StyleSheet} from 'react-native';
|
||||
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
import * as PostListUtils from 'mattermost-redux/utils/post_list';
|
||||
|
||||
import CombinedUserActivityPost from 'app/components/combined_user_activity_post';
|
||||
import Post from 'app/components/post';
|
||||
import {DeepLinkTypes} from 'app/constants';
|
||||
import {DeepLinkTypes, ListTypes} from 'app/constants';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import {makeExtraData} from 'app/utils/list_view';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
import {matchDeepLink} from 'app/utils/url';
|
||||
|
||||
import DateHeader from './date_header';
|
||||
import NewMessagesDivider from './new_messages_divider';
|
||||
|
||||
export default class PostListBase extends PureComponent {
|
||||
const INITIAL_BATCH_TO_RENDER = 15;
|
||||
const SCROLL_UP_MULTIPLIER = 3.5;
|
||||
const SCROLL_POSITION_CONFIG = {
|
||||
|
||||
// To avoid scrolling the list when new messages arrives
|
||||
// if the user is not at the bottom
|
||||
minIndexForVisible: 0,
|
||||
|
||||
// If the user is at the bottom or 60px from the bottom
|
||||
// auto scroll show the new message
|
||||
autoscrollToTopThreshold: 60,
|
||||
};
|
||||
|
||||
export default class PostList extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
handleSelectChannelByName: PropTypes.func.isRequired,
|
||||
|
|
@ -58,6 +74,30 @@ export default class PostListBase extends PureComponent {
|
|||
siteURL: '',
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.hasDoneInitialScroll = false;
|
||||
this.contentOffsetY = 0;
|
||||
this.makeExtraData = makeExtraData();
|
||||
this.flatListRef = React.createRef();
|
||||
|
||||
this.state = {
|
||||
postListHeight: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
EventEmitter.on('scroll-to-bottom', this.scrollToBottom);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.channelId !== nextProps.channelId) {
|
||||
this.contentOffsetY = 0;
|
||||
this.hasDoneInitialScroll = false;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.props.deepLinkURL) {
|
||||
this.handleDeepLink(this.props.deepLinkURL);
|
||||
|
|
@ -65,12 +105,25 @@ export default class PostListBase extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.off('scroll-to-bottom', this.scrollToBottom);
|
||||
}
|
||||
|
||||
handleClosePermalink = () => {
|
||||
const {actions} = this.props;
|
||||
actions.selectFocusedPostId('');
|
||||
this.showingPermalink = false;
|
||||
};
|
||||
|
||||
handleContentSizeChange = (contentWidth, contentHeight) => {
|
||||
this.scrollToInitialIndexIfNeeded(contentWidth, contentHeight);
|
||||
|
||||
if (this.state.postListHeight && contentHeight < this.state.postListHeight && this.props.extraData) {
|
||||
// We still have less than 1 screen of posts loaded with more to get, so load more
|
||||
this.props.onLoadMoreUp();
|
||||
}
|
||||
};
|
||||
|
||||
handleDeepLink = (url) => {
|
||||
const {serverURL, siteURL} = this.props;
|
||||
|
||||
|
|
@ -84,6 +137,22 @@ export default class PostListBase extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleLayout = (event) => {
|
||||
const {height} = event.nativeEvent.layout;
|
||||
this.setState({postListHeight: height});
|
||||
};
|
||||
|
||||
handlePermalinkPress = (postId, teamName) => {
|
||||
const {actions, onPermalinkPress} = this.props;
|
||||
|
||||
if (onPermalinkPress) {
|
||||
onPermalinkPress(postId, true);
|
||||
} else {
|
||||
actions.loadChannelsByTeamName(teamName);
|
||||
this.showPermalinkView(postId);
|
||||
}
|
||||
};
|
||||
|
||||
handleRefresh = () => {
|
||||
const {
|
||||
actions,
|
||||
|
|
@ -100,17 +169,31 @@ export default class PostListBase extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handlePermalinkPress = (postId, teamName) => {
|
||||
const {actions, onPermalinkPress} = this.props;
|
||||
handleScroll = (event) => {
|
||||
const pageOffsetY = event.nativeEvent.contentOffset.y;
|
||||
if (pageOffsetY > 0) {
|
||||
const contentHeight = event.nativeEvent.contentSize.height;
|
||||
const direction = (this.contentOffsetY < pageOffsetY) ?
|
||||
ListTypes.VISIBILITY_SCROLL_UP :
|
||||
ListTypes.VISIBILITY_SCROLL_DOWN;
|
||||
|
||||
if (onPermalinkPress) {
|
||||
onPermalinkPress(postId, true);
|
||||
} else {
|
||||
actions.loadChannelsByTeamName(teamName);
|
||||
this.showPermalinkView(postId);
|
||||
this.contentOffsetY = pageOffsetY;
|
||||
if (
|
||||
direction === ListTypes.VISIBILITY_SCROLL_UP &&
|
||||
(contentHeight - pageOffsetY) < (this.state.postListHeight * SCROLL_UP_MULTIPLIER)
|
||||
) {
|
||||
this.props.onLoadMoreUp();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleScrollToIndexFailed = () => {
|
||||
requestAnimationFrame(() => {
|
||||
this.hasDoneInitialScroll = false;
|
||||
this.scrollToInitialIndexIfNeeded(1, 1);
|
||||
});
|
||||
};
|
||||
|
||||
keyExtractor = (item) => {
|
||||
// All keys are strings (either post IDs or special keys)
|
||||
return item;
|
||||
|
|
@ -178,6 +261,27 @@ export default class PostListBase extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
scrollToBottom = () => {
|
||||
this.flatListRef.current.scrollToOffset({offset: 0, animated: true});
|
||||
};
|
||||
|
||||
scrollToInitialIndexIfNeeded = (width, height) => {
|
||||
if (
|
||||
width > 0 &&
|
||||
height > 0 &&
|
||||
this.props.initialIndex > 0 &&
|
||||
!this.hasDoneInitialScroll
|
||||
) {
|
||||
this.flatListRef.current.scrollToIndex({
|
||||
animated: false,
|
||||
index: this.props.initialIndex,
|
||||
viewOffset: 50,
|
||||
viewPosition: 0.5,
|
||||
});
|
||||
this.hasDoneInitialScroll = true;
|
||||
}
|
||||
};
|
||||
|
||||
showPermalinkView = (postId) => {
|
||||
const {actions, navigator} = this.props;
|
||||
|
||||
|
|
@ -204,5 +308,51 @@ export default class PostListBase extends PureComponent {
|
|||
navigator.showModal(options);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
channelId,
|
||||
highlightPostId,
|
||||
postIds,
|
||||
refreshing,
|
||||
} = this.props;
|
||||
|
||||
const refreshControl = {refreshing};
|
||||
|
||||
if (channelId) {
|
||||
refreshControl.onRefresh = this.handleRefresh;
|
||||
}
|
||||
|
||||
const hasPostsKey = postIds.length ? 'true' : 'false';
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
key={`recyclerFor-${channelId}-${hasPostsKey}`}
|
||||
ref={this.flatListRef}
|
||||
contentContainerStyle={styles.postListContent}
|
||||
data={postIds}
|
||||
extraData={this.makeExtraData(channelId, highlightPostId, this.props.extraData)}
|
||||
initialNumToRender={INITIAL_BATCH_TO_RENDER}
|
||||
inverted={true}
|
||||
keyExtractor={this.keyExtractor}
|
||||
ListFooterComponent={this.props.renderFooter}
|
||||
maintainVisibleContentPosition={SCROLL_POSITION_CONFIG}
|
||||
maxToRenderPerBatch={INITIAL_BATCH_TO_RENDER + 1}
|
||||
onContentSizeChange={this.handleContentSizeChange}
|
||||
onLayout={this.handleLayout}
|
||||
onScroll={this.handleScroll}
|
||||
onScrollToIndexFailed={this.handleScrollToIndexFailed}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={this.renderItem}
|
||||
scrollEventThrottle={60}
|
||||
{...refreshControl}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
postListContent: {
|
||||
paddingTop: 5,
|
||||
},
|
||||
});
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import PostList from './post_list.ios.js';
|
||||
import PostList from './post_list';
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
|
|
|||
|
|
@ -383,6 +383,8 @@ export default class PostTextbox extends PureComponent {
|
|||
|
||||
this.setState(nextState, callback);
|
||||
}
|
||||
|
||||
EventEmitter.emit('scroll-to-bottom');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
56
package-lock.json
generated
56
package-lock.json
generated
|
|
@ -1974,7 +1974,7 @@
|
|||
},
|
||||
"array-equal": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
|
||||
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
|
||||
"dev": true
|
||||
},
|
||||
|
|
@ -3116,7 +3116,7 @@
|
|||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "http://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
|
||||
},
|
||||
"fbjs": {
|
||||
|
|
@ -3147,7 +3147,7 @@
|
|||
},
|
||||
"css-select": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
||||
"integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
|
@ -4168,7 +4168,7 @@
|
|||
},
|
||||
"external-editor": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
|
||||
"integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
|
||||
"requires": {
|
||||
"chardet": "^0.4.0",
|
||||
|
|
@ -4359,7 +4359,7 @@
|
|||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
|
||||
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
|
||||
"dev": true
|
||||
}
|
||||
|
|
@ -9536,7 +9536,7 @@
|
|||
},
|
||||
"load-json-file": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
|
||||
"integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
|
|
@ -9677,7 +9677,7 @@
|
|||
},
|
||||
"lodash.isempty": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "http://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
|
||||
"integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4="
|
||||
},
|
||||
"lodash.isequal": {
|
||||
|
|
@ -10261,7 +10261,7 @@
|
|||
},
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
||||
},
|
||||
"mixin-deep": {
|
||||
|
|
@ -10285,7 +10285,7 @@
|
|||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
|
|
@ -10293,7 +10293,7 @@
|
|||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||
}
|
||||
}
|
||||
|
|
@ -11942,7 +11942,7 @@
|
|||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
|
||||
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
|
||||
},
|
||||
"wordwrap": {
|
||||
|
|
@ -11997,14 +11997,14 @@
|
|||
},
|
||||
"get-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
|
||||
}
|
||||
}
|
||||
},
|
||||
"os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
|
||||
},
|
||||
"output-file-sync": {
|
||||
|
|
@ -12150,7 +12150,7 @@
|
|||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"path-is-inside": {
|
||||
|
|
@ -12200,7 +12200,7 @@
|
|||
},
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
|
||||
},
|
||||
"pirates": {
|
||||
|
|
@ -12811,10 +12811,6 @@
|
|||
"resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-1.1.1.tgz",
|
||||
"integrity": "sha512-t0Ujm177bagjUOSzhpmkSz+LqFW04HnY9TeZFavDCmV521fQvFz82aD+POXqWsAdsJVOK3umJYBNNqCjC3g0hQ=="
|
||||
},
|
||||
"react-native-recyclerview-list": {
|
||||
"version": "github:mattermost/react-native-recyclerview-list#a2d26bc7b2623cae25e947ba3905f702434717bf",
|
||||
"from": "github:mattermost/react-native-recyclerview-list#a2d26bc7b2623cae25e947ba3905f702434717bf"
|
||||
},
|
||||
"react-native-safe-area": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area/-/react-native-safe-area-0.5.0.tgz",
|
||||
|
|
@ -12926,7 +12922,7 @@
|
|||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "http://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
|
||||
},
|
||||
"fbjs": {
|
||||
|
|
@ -13091,7 +13087,7 @@
|
|||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
|
|
@ -14089,7 +14085,7 @@
|
|||
},
|
||||
"safe-regex": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
|
||||
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
|
||||
"requires": {
|
||||
"ret": "~0.1.10"
|
||||
|
|
@ -14438,7 +14434,7 @@
|
|||
"dependencies": {
|
||||
"async": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "http://registry.npmjs.org/async/-/async-2.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.0.0.tgz",
|
||||
"integrity": "sha1-0JAK04WvE4BFQKEJxCFm4657K50=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
|
@ -14537,7 +14533,7 @@
|
|||
},
|
||||
"serialize-error": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "http://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz",
|
||||
"integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go="
|
||||
},
|
||||
"serve-static": {
|
||||
|
|
@ -14658,7 +14654,7 @@
|
|||
},
|
||||
"xmlbuilder": {
|
||||
"version": "8.2.2",
|
||||
"resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz",
|
||||
"integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M="
|
||||
}
|
||||
}
|
||||
|
|
@ -14880,7 +14876,7 @@
|
|||
},
|
||||
"inquirer": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "http://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz",
|
||||
"integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
|
@ -15283,7 +15279,7 @@
|
|||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
|
|
@ -15296,7 +15292,7 @@
|
|||
},
|
||||
"strip-eof": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
|
||||
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
|
||||
},
|
||||
"strip-json-comments": {
|
||||
|
|
@ -15501,7 +15497,7 @@
|
|||
},
|
||||
"through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
|
||||
},
|
||||
"through2": {
|
||||
|
|
@ -16237,7 +16233,7 @@
|
|||
},
|
||||
"wrap-ansi": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
|
||||
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
|
||||
"requires": {
|
||||
"string-width": "^1.0.1",
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@
|
|||
"react-native-notifications": "github:mattermost/react-native-notifications#c58499aedf9bd4c9a405196ec807019ed316f2bb",
|
||||
"react-native-passcode-status": "1.1.1",
|
||||
"react-native-permissions": "1.1.1",
|
||||
"react-native-recyclerview-list": "mattermost/react-native-recyclerview-list#a2d26bc7b2623cae25e947ba3905f702434717bf",
|
||||
"react-native-safe-area": "0.5.0",
|
||||
"react-native-section-list-get-item-layout": "2.2.3",
|
||||
"react-native-sentry": "0.42.0",
|
||||
|
|
|
|||
Loading…
Reference in a new issue