MM-36881 Fix scroll perf degradation with pull to refresh enabled (#5525) (#5535)

* Fix scroll perf degradation with pull to refresh enabled

* Update app/components/post_list/post_list_refresh_control.tsx

Co-authored-by: Anurag Shivarathri <anurag6713@gmail.com>

Co-authored-by: Anurag Shivarathri <anurag6713@gmail.com>
(cherry picked from commit 58d9c2e9f0)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
Mattermost Build 2021-07-12 22:13:42 +02:00 committed by GitHub
parent 50f616f5d7
commit 962a1759a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 141 additions and 58 deletions

View file

@ -3,10 +3,7 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Text,
View,
} from 'react-native';
import {Platform, Text, View} from 'react-native';
import {injectIntl, intlShape} from 'react-intl';
import {displayUsername} from '@mm-redux/utils/user_utils';
@ -386,6 +383,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
marginHorizontal: 12,
marginBottom: 12,
overflow: 'hidden',
...Platform.select({
android: {
scaleY: -1,
},
}),
},
displayName: {
color: theme.centerChannelColor,

View file

@ -3,7 +3,7 @@
import React from 'react';
import {injectIntl, intlShape} from 'react-intl';
import {Keyboard, View} from 'react-native';
import {Keyboard, StyleProp, View, ViewStyle} from 'react-native';
import {showModalOverCurrentContext} from '@actions/navigation';
import Markdown from '@components/markdown';
@ -31,6 +31,7 @@ type Props = {
testID?: string;
theme: Theme;
usernamesById: Record<string, string>;
style?: StyleProp<ViewStyle>;
}
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
@ -87,7 +88,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
const CombinedUserActivity = ({
canDelete, currentUserId, currentUsername, intl, post,
showJoinLeave, testID, theme, usernamesById,
showJoinLeave, testID, theme, usernamesById, style,
}: Props) => {
const itemTestID = `${testID}.${post.id}`;
const textStyles = getMarkdownTextStyles(theme);
@ -215,6 +216,7 @@ const CombinedUserActivity = ({
return (
<View
style={style}
testID={testID}
>
<TouchableWithFeedback

View file

@ -15,6 +15,7 @@ import {isPostFlagged, isSystemMessage} from '@mm-redux/utils/post_utils';
import {canDeletePost} from '@selectors/permissions';
import {areConsecutivePosts, postUserDisplayName} from '@utils/post';
import type {StyleProp, ViewStyle} from 'react-native';
import type {Post as PostType} from '@mm-redux/types/posts';
import type {Theme} from '@mm-redux/types/preferences';
import type {GlobalState} from '@mm-redux/types/store';
@ -27,6 +28,7 @@ type OwnProps = {
post?: PostType;
previousPostId?: string;
nextPostId?: string;
style?: StyleProp<ViewStyle>;
testID: string;
theme: Theme;
}

View file

@ -44,6 +44,7 @@ type PostProps = {
showPermalink: (intl: typeof intlShape, teamName: string, postId: string) => null;
skipFlaggedHeader?: boolean;
skipPinnedHeader?: boolean;
style?: StyleProp<ViewStyle>;
teammateNameDisplay: string;
testID?: string;
theme: Theme
@ -92,10 +93,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
const Post = ({
canDelete, enablePostUsernameOverride, highlight, highlightPinnedOrFlagged = true, intl, isConsecutivePost, isFirstReply, isFlagged, isLastReply,
location, post, removePost, rootPostAuthor, shouldRenderReplyButton, skipFlaggedHeader, skipPinnedHeader, showAddReaction = true, showPermalink,
teammateNameDisplay, testID, theme,
teammateNameDisplay, testID, theme, style,
}: PostProps) => {
const pressDetected = useRef(false);
const style = getStyleSheet(theme);
const styles = getStyleSheet(theme);
const handlePress = preventDoubleTap(() => {
pressDetected.current = true;
@ -159,23 +160,23 @@ const Post = ({
const highlightFlagged = isFlagged && !skipFlaggedHeader;
const hightlightPinned = post.is_pinned && !skipPinnedHeader;
const itemTestID = `${testID}.${post.id}`;
const rightColumnStyle = [style.rightColumn, (post.root_id && isLastReply && style.rightColumnPadding)];
const pendingPostStyle: StyleProp<ViewStyle> | undefined = isPostPendingOrFailed(post) ? style.pendingPost : undefined;
const rightColumnStyle = [styles.rightColumn, (post.root_id && isLastReply && styles.rightColumnPadding)];
const pendingPostStyle: StyleProp<ViewStyle> | undefined = isPostPendingOrFailed(post) ? styles.pendingPost : undefined;
const isAutoResponder = fromAutoResponder(post);
let highlightedStyle: StyleProp<ViewStyle>;
if (highlight) {
highlightedStyle = style.highlight;
highlightedStyle = styles.highlight;
} else if ((highlightFlagged || hightlightPinned) && highlightPinnedOrFlagged) {
highlightedStyle = style.highlightPinnedOrFlagged;
highlightedStyle = styles.highlightPinnedOrFlagged;
}
let header: ReactNode;
let postAvatar: ReactNode;
let consecutiveStyle: StyleProp<ViewStyle>;
if (isConsecutivePost) {
consecutiveStyle = style.consective;
postAvatar = <View style={style.consecutivePostContainer}/>;
consecutiveStyle = styles.consective;
postAvatar = <View style={styles.consecutivePostContainer}/>;
} else {
postAvatar = isAutoResponder ? (
<SystemAvatar theme={theme}/>
@ -235,7 +236,7 @@ const Post = ({
return (
<View
testID={testID}
style={[style.postStyle, highlightedStyle]}
style={[styles.postStyle, style, highlightedStyle]}
>
<TouchableWithFeedback
testID={itemTestID}
@ -254,7 +255,7 @@ const Post = ({
skipPinnedHeader={skipPinnedHeader}
theme={theme}
/>
<View style={[style.container, consecutiveStyle]}>
<View style={[styles.container, consecutiveStyle]}>
{postAvatar}
<View style={rightColumnStyle}>
{header}

View file

@ -1,9 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {ReactElement, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react';
import React, {ReactElement, useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react';
import {injectIntl, intlShape} from 'react-intl';
import {DeviceEventEmitter, FlatList, Platform, RefreshControl, StyleSheet, ViewToken} from 'react-native';
import {DeviceEventEmitter, FlatList, NativeScrollEvent, NativeSyntheticEvent, Platform, StyleSheet, ViewToken} from 'react-native';
import {DeepLinkTypes, NavigationTypes} from '@constants';
import * as Screens from '@constants/screen';
@ -27,6 +27,7 @@ import DateSeparator from './date_separator';
import MoreMessagesButton from './more_messages_button';
import NewMessagesLine from './new_message_line';
import Post from './post';
import PostListRefreshControl from './post_list_refresh_control';
type PostListProps = {
channelId?: string;
@ -79,6 +80,13 @@ const styles = StyleSheet.create({
postListContent: {
paddingTop: 5,
},
scale: {
...Platform.select({
android: {
scaleY: -1,
},
}),
},
});
const buildExtraData = makeExtraData();
@ -95,6 +103,7 @@ const PostList = ({
const onScrollEndIndexListener = useRef<onScrollEndIndexListenerEvent>();
const onViewableItemsChangedListener = useRef<ViewableItemsChangedListenerEvent>();
const [refreshing, setRefreshing] = useState(false);
const [offsetY, setOffsetY] = useState(0);
const registerViewableItemsListener = useCallback((listener) => {
onViewableItemsChangedListener.current = listener;
@ -181,6 +190,7 @@ const PostList = ({
theme={theme}
moreMessages={moreNewMessages && checkForPostId}
testID={`${testID}.new_messages_line`}
style={styles.scale}
/>
);
} else if (isDateLine(item)) {
@ -188,6 +198,7 @@ const PostList = ({
<DateSeparator
date={getDateForDateLine(item)}
theme={theme}
style={styles.scale}
/>
);
}
@ -195,6 +206,7 @@ const PostList = ({
if (isCombinedUserActivityPost(item)) {
const postProps = {
postId: item,
style: styles.scale,
testID: `${testID}.combined_user_activity`,
theme,
};
@ -232,6 +244,7 @@ const PostList = ({
<Post
highlight={highlightPostId === item}
postId={item}
style={styles.scale}
testID={`${testID}.post`}
{...postProps}
/>
@ -247,6 +260,17 @@ const PostList = ({
});
}, []);
const onScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
if (Platform.OS === 'android') {
const {y} = event.nativeEvent.contentOffset;
if (y === 0) {
setOffsetY(y);
} else if (offsetY === 0 && y !== 0) {
setOffsetY(y);
}
}
}, [offsetY]);
useResetNativeScrollView(scrollViewNativeID, postIds);
useEffect(() => {
@ -300,46 +324,47 @@ const PostList = ({
}
}, [initialIndex, highlightPostId]);
const refreshControl = useMemo(() => (
<RefreshControl
refreshing={refreshing}
colors={[theme.onlineIndicator, theme.awayIndicator, theme.dndIndicator]}
tintColor={theme.centerChannelColor}
onRefresh={onRefresh}
const list = (
<FlatList
contentContainerStyle={styles.postListContent}
data={postIds}
extraData={buildExtraData(channelId, highlightPostId, extraData, loadMorePostsVisible)}
initialNumToRender={INITIAL_BATCH_TO_RENDER}
key={`recyclerFor-${channelId}-${hasPostsKey}`}
keyboardDismissMode={'interactive'}
keyboardShouldPersistTaps={'handled'}
keyExtractor={keyExtractor}
ListFooterComponent={renderFooter}
listKey={`recyclerFor-${channelId}`}
maintainVisibleContentPosition={SCROLL_POSITION_CONFIG}
maxToRenderPerBatch={Platform.select({android: 5})}
nativeID={scrollViewNativeID}
onEndReached={onLoadMoreUp}
onEndReachedThreshold={2}
onScroll={onScroll}
onScrollToIndexFailed={onScrollToIndexFailed}
onViewableItemsChanged={onViewableItemsChanged}
ref={flatListRef}
removeClippedSubviews={true}
renderItem={renderItem}
scrollEventThrottle={60}
style={styles.flex}
windowSize={Posts.POST_CHUNK_SIZE / 2}
viewabilityConfig={VIEWABILITY_CONFIG}
testID={testID}
/>
), [refreshing, theme]);
);
return (
<>
<FlatList
contentContainerStyle={styles.postListContent}
data={postIds}
extraData={buildExtraData(channelId, highlightPostId, extraData, loadMorePostsVisible)}
initialNumToRender={INITIAL_BATCH_TO_RENDER}
inverted={true}
key={`recyclerFor-${channelId}-${hasPostsKey}`}
keyboardDismissMode={'interactive'}
keyboardShouldPersistTaps={'handled'}
keyExtractor={keyExtractor}
ListFooterComponent={renderFooter}
listKey={`recyclerFor-${channelId}`}
maintainVisibleContentPosition={SCROLL_POSITION_CONFIG}
maxToRenderPerBatch={Platform.select({android: 5})}
nativeID={scrollViewNativeID}
onScrollToIndexFailed={onScrollToIndexFailed}
ref={flatListRef}
onEndReached={onLoadMoreUp}
onEndReachedThreshold={2}
removeClippedSubviews={true}
renderItem={renderItem}
scrollEventThrottle={60}
style={styles.flex}
windowSize={Posts.POST_CHUNK_SIZE / 2}
viewabilityConfig={VIEWABILITY_CONFIG}
onViewableItemsChanged={onViewableItemsChanged}
refreshControl={refreshControl}
testID={testID}
/>
<PostListRefreshControl
enabled={offsetY === 0}
refreshing={refreshing}
onRefresh={onRefresh}
theme={theme}
>
{list}
</PostListRefreshControl>
{showMoreMessagesButton &&
<MoreMessagesButton
channelId={channelId}

View file

@ -0,0 +1,52 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {ReactElement} from 'react';
import {Platform, RefreshControl, StyleSheet} from 'react-native';
import type {Theme} from '@mm-redux/types/preferences';
type Props = {
children: ReactElement;
enabled: boolean;
onRefresh: () => void;
refreshing: boolean;
theme: Theme;
}
const style = StyleSheet.create({
container: {
flex: 1,
scaleY: -1,
},
});
const PostListRefreshControl = ({children, enabled, onRefresh, refreshing, theme}: Props) => {
const props = {
colors: [theme.onlineIndicator, theme.awayIndicator, theme.dndIndicator],
onRefresh,
refreshing,
tintColor: theme.centerChannelColor,
};
if (Platform.OS === 'android') {
return (
<RefreshControl
{...props}
enabled={enabled}
style={style.container}
>
{children}
</RefreshControl>
);
}
const refreshControl = <RefreshControl {...props}/>;
return React.cloneElement(
children,
{refreshControl, inverted: true},
);
};
export default PostListRefreshControl;

View file

@ -693,7 +693,7 @@ SPEC CHECKSUMS:
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b
FBReactNativeSpec: 74185edd6e8e5cb35512747bd840f8f9ad2381ff
FBReactNativeSpec: cef0cc6d50abc92e8cf52f140aa22b5371cfec0b
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
jail-monkey: 01cd0a75aa1034d08fd851869e6e6c3b063242d7
libwebp: e90b9c01d99205d03b6bb8f2c8c415e5a4ef66f0

5
package-lock.json generated
View file

@ -5,7 +5,7 @@
"requires": true,
"packages": {
"": {
"version": "1.44.1",
"version": "1.45.0",
"hasInstallScript": true,
"license": "Apache 2.0",
"dependencies": {
@ -62143,8 +62143,7 @@
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz",
"integrity": "sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw==",
"requires": {
}
"requires": {}
},
"xcode": {
"version": "3.0.1",