Refactor postlist to include Android Pie fixes and smoother scrolling (#2161)
This commit is contained in:
parent
f7c7e70a37
commit
f522df7a2e
16 changed files with 446 additions and 402 deletions
|
|
@ -212,6 +212,7 @@ dependencies {
|
|||
implementation project(':react-native-sentry')
|
||||
implementation project(':react-native-exception-handler')
|
||||
implementation project(':rn-fetch-blob')
|
||||
implementation project(':react-native-recyclerview-list')
|
||||
|
||||
// For animated GIF support
|
||||
implementation 'com.facebook.fresco:animated-base-support:1.3.0'
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ 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.facebook.react.ReactPackage;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
|
@ -75,7 +76,8 @@ public class MainApplication extends NavigationApplication implements INotificat
|
|||
new ReactNativeDocumentPicker(),
|
||||
new SharePackage(this),
|
||||
new KeychainPackage(),
|
||||
new InitializationPackage(this)
|
||||
new InitializationPackage(this),
|
||||
new RNRecyclerviewListPackage()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,3 +39,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')
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ function makeMapStateToProps() {
|
|||
|
||||
return {
|
||||
...getDimensions(state),
|
||||
config,
|
||||
googleDeveloperKey: config.GoogleDeveloperKey,
|
||||
link,
|
||||
openGraphData: getOpenGraphMetadataForUrl(state, link),
|
||||
showLinkPreviews: previewsEnabled && config.EnableLinkPreviews === 'true',
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
}).isRequired,
|
||||
baseTextStyle: CustomPropTypes.Style,
|
||||
blockStyles: PropTypes.object,
|
||||
config: PropTypes.object,
|
||||
googleDeveloperKey: PropTypes.string,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
isReplyPost: PropTypes.bool,
|
||||
|
|
@ -198,7 +198,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.imageContainer, {height: imgHeight}]}
|
||||
style={[styles.imageContainer, {height: imgHeight || MAX_YOUTUBE_IMAGE_HEIGHT}]}
|
||||
{...this.responder}
|
||||
onPress={this.playYouTubeVideo}
|
||||
>
|
||||
|
|
@ -225,13 +225,13 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
return (
|
||||
<TouchableWithoutFeedback
|
||||
onPress={this.handlePreviewImage}
|
||||
style={[styles.imageContainer, {height: imgHeight}]}
|
||||
style={[styles.imageContainer, {height: imgHeight || MAX_YOUTUBE_IMAGE_HEIGHT}]}
|
||||
{...this.responder}
|
||||
>
|
||||
<View ref='item'>
|
||||
<ProgressiveImage
|
||||
ref='image'
|
||||
style={[styles.image, {width, height: imgHeight}]}
|
||||
style={[styles.image, {width, height: imgHeight || MAX_YOUTUBE_IMAGE_HEIGHT}]}
|
||||
defaultSource={{uri}}
|
||||
resizeMode='contain'
|
||||
onError={this.handleLinkLoadError}
|
||||
|
|
@ -394,11 +394,11 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
playVideo(videoId, startTime).
|
||||
catch(this.playYouTubeVideoError);
|
||||
} else {
|
||||
const {config} = this.props;
|
||||
const {googleDeveloperKey} = this.props;
|
||||
|
||||
if (config.GoogleDeveloperKey) {
|
||||
if (googleDeveloperKey) {
|
||||
YouTubeStandaloneAndroid.playVideo({
|
||||
apiKey: config.GoogleDeveloperKey,
|
||||
apiKey: googleDeveloperKey,
|
||||
videoId,
|
||||
autoplay: true,
|
||||
startTime,
|
||||
|
|
|
|||
|
|
@ -18,15 +18,15 @@ 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;
|
||||
let initialIndex = postIds.indexOf(START_OF_NEW_MESSAGES);
|
||||
if (ownProps.highlightPostId) {
|
||||
initialIndex = postIds.indexOf(ownProps.highlightPostId);
|
||||
}
|
||||
|
||||
return {
|
||||
deepLinkURL: state.views.root.deepLinkURL,
|
||||
deviceHeight,
|
||||
measureCellLayout,
|
||||
postIds,
|
||||
initialIndex,
|
||||
serverURL: getCurrentUrl(state),
|
||||
siteURL: getConfig(state).SiteURL,
|
||||
theme: getTheme(state),
|
||||
|
|
|
|||
125
app/components/post_list/post_list.android.js
Normal file
125
app/components/post_list/post_list.android.js
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
// 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,
|
||||
managedConfig: {},
|
||||
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 = {};
|
||||
if (postIds.length) {
|
||||
otherProps.ListFooterComponent = this.props.renderFooter();
|
||||
} else {
|
||||
otherProps.ListEmptyComponent = this.props.renderFooter();
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
137
app/components/post_list/post_list.ios.js
Normal file
137
app/components/post_list/post_list.ios.js
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
// 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;
|
||||
|
||||
export default class PostList extends PostListBase {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.hasDoneInitialScroll = false;
|
||||
this.contentOffsetY = 0;
|
||||
this.makeExtraData = makeExtraData();
|
||||
|
||||
this.state = {
|
||||
managedConfig: {},
|
||||
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,
|
||||
} = this.props;
|
||||
|
||||
const refreshControl = {
|
||||
refreshing: false,
|
||||
};
|
||||
|
||||
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}
|
||||
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,34 +3,18 @@
|
|||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
FlatList,
|
||||
InteractionManager,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import Post from 'app/components/post';
|
||||
import {START_OF_NEW_MESSAGES} from 'app/selectors/post_list';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import {makeExtraData} from 'app/utils/list_view';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
import {matchPermalink} from 'app/utils/url';
|
||||
|
||||
import DateHeader from './date_header';
|
||||
import {isDateLine} from './date_header/utils';
|
||||
import NewMessagesDivider from './new_messages_divider';
|
||||
import withLayout from './with_layout';
|
||||
|
||||
const PostWithLayout = withLayout(Post);
|
||||
|
||||
const INITIAL_BATCH_TO_RENDER = 15;
|
||||
const NEW_MESSAGES_HEIGHT = 28;
|
||||
const DATE_HEADER_HEIGHT = 28;
|
||||
|
||||
export default class PostList extends PureComponent {
|
||||
export default class PostListBase extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
loadChannelsByTeamName: PropTypes.func.isRequired,
|
||||
|
|
@ -39,19 +23,14 @@ export default class PostList extends PureComponent {
|
|||
setDeepLinkURL: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
channelId: PropTypes.string,
|
||||
currentUserId: PropTypes.string,
|
||||
deepLinkURL: PropTypes.string,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
extraData: PropTypes.any,
|
||||
highlightPostId: PropTypes.string,
|
||||
indicateNewMessages: PropTypes.bool,
|
||||
initialIndex: PropTypes.number,
|
||||
isSearchResult: PropTypes.bool,
|
||||
lastViewedAt: PropTypes.number, // Used by container // eslint-disable-line no-unused-prop-types
|
||||
measureCellLayout: PropTypes.bool,
|
||||
navigator: PropTypes.object,
|
||||
onContentSizeChange: PropTypes.func,
|
||||
onEndReached: PropTypes.func,
|
||||
onHashtagPress: PropTypes.func,
|
||||
onLoadMoreUp: PropTypes.func,
|
||||
onPermalinkPress: PropTypes.func,
|
||||
onPostPress: PropTypes.func,
|
||||
onRefresh: PropTypes.func,
|
||||
|
|
@ -65,47 +44,20 @@ export default class PostList extends PureComponent {
|
|||
};
|
||||
|
||||
static defaultProps = {
|
||||
loadMore: () => true,
|
||||
onLoadMoreUp: () => true,
|
||||
renderFooter: () => null,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.newMessagesIndex = -1;
|
||||
this.makeExtraData = makeExtraData();
|
||||
this.itemMeasurements = {};
|
||||
|
||||
this.state = {
|
||||
managedConfig: {},
|
||||
scrollToMessage: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.listenerId = mattermostManaged.addEventListener('change', this.setManagedConfig);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
EventEmitter.on('reset_channel', this.scrollToBottomOffset);
|
||||
this.mounted = true;
|
||||
this.setManagedConfig();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.postIds !== this.props.postIds) {
|
||||
this.newMessagesIndex = -1;
|
||||
}
|
||||
if (this.props.channelId !== nextProps.channelId) {
|
||||
this.itemMeasurements = {};
|
||||
this.newMessageScrolledTo = false;
|
||||
this.scrollToBottomOffset();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if ((this.props.measureCellLayout || this.props.isSearchResult) && this.state.scrollToMessage) {
|
||||
this.scrollListToMessageOffset();
|
||||
}
|
||||
|
||||
if (this.props.deepLinkURL) {
|
||||
this.handleDeepLink(this.props.deepLinkURL);
|
||||
this.props.actions.setDeepLinkURL('');
|
||||
|
|
@ -113,7 +65,7 @@ export default class PostList extends PureComponent {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.off('reset_channel', this.scrollToBottomOffset);
|
||||
this.mounted = false;
|
||||
mattermostManaged.removeEventListener(this.listenerId);
|
||||
}
|
||||
|
||||
|
|
@ -135,6 +87,22 @@ export default class PostList extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleRefresh = () => {
|
||||
const {
|
||||
actions,
|
||||
channelId,
|
||||
onRefresh,
|
||||
} = this.props;
|
||||
|
||||
if (channelId) {
|
||||
actions.refreshChannelWithRetry(channelId);
|
||||
}
|
||||
|
||||
if (onRefresh) {
|
||||
onRefresh();
|
||||
}
|
||||
};
|
||||
|
||||
handlePermalinkPress = (postId, teamName) => {
|
||||
const {actions, onPermalinkPress} = this.props;
|
||||
|
||||
|
|
@ -146,12 +114,87 @@ export default class PostList extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
showPermalinkView = (postId) => {
|
||||
keyExtractor = (item) => {
|
||||
// All keys are strings (either post IDs or special keys)
|
||||
return item;
|
||||
};
|
||||
|
||||
renderItem = ({item, index}) => {
|
||||
if (item === START_OF_NEW_MESSAGES) {
|
||||
// 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.
|
||||
const moreNewMessages = this.props.postIds.length === index + 2;
|
||||
|
||||
return (
|
||||
<NewMessagesDivider
|
||||
index={index}
|
||||
theme={this.props.theme}
|
||||
moreMessages={moreNewMessages}
|
||||
/>
|
||||
);
|
||||
} else if (isDateLine(item)) {
|
||||
return (
|
||||
<DateHeader
|
||||
dateLineString={item}
|
||||
index={index}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const postId = item;
|
||||
|
||||
// Remember that the list is rendered with item 0 at the bottom so the "previous" post
|
||||
// comes after this one in the list
|
||||
const previousPostId = index < this.props.postIds.length - 1 ? this.props.postIds[index + 1] : null;
|
||||
const nextPostId = index > 0 ? this.props.postIds[index - 1] : null;
|
||||
|
||||
return this.renderPost(postId, previousPostId, nextPostId);
|
||||
};
|
||||
|
||||
renderPost = (postId, previousPostId, nextPostId) => {
|
||||
const {
|
||||
actions,
|
||||
highlightPostId,
|
||||
isSearchResult,
|
||||
navigator,
|
||||
onHashtagPress,
|
||||
onPostPress,
|
||||
renderReplies,
|
||||
shouldRenderReplyButton,
|
||||
} = this.props;
|
||||
const {managedConfig} = this.state;
|
||||
|
||||
const highlight = highlightPostId === postId;
|
||||
return (
|
||||
<Post
|
||||
postId={postId}
|
||||
previousPostId={previousPostId}
|
||||
nextPostId={nextPostId}
|
||||
onPermalinkPress={this.handlePermalinkPress}
|
||||
highlight={highlight}
|
||||
renderReplies={renderReplies}
|
||||
isSearchResult={isSearchResult}
|
||||
shouldRenderReplyButton={shouldRenderReplyButton}
|
||||
onPress={onPostPress}
|
||||
navigator={navigator}
|
||||
managedConfig={managedConfig}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
setManagedConfig = async (config) => {
|
||||
let nextConfig = config;
|
||||
if (!nextConfig) {
|
||||
nextConfig = await mattermostManaged.getLocalConfig();
|
||||
}
|
||||
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
managedConfig: nextConfig,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
showPermalinkView = (postId) => {
|
||||
const {actions, navigator} = this.props;
|
||||
|
||||
actions.selectFocusedPostId(postId);
|
||||
|
||||
|
|
@ -169,7 +212,6 @@ export default class PostList extends PureComponent {
|
|||
passProps: {
|
||||
isPermalink: true,
|
||||
onClose: this.handleClosePermalink,
|
||||
onHashtagPress,
|
||||
onPermalinkPress: this.handlePermalinkPress,
|
||||
},
|
||||
};
|
||||
|
|
@ -178,229 +220,5 @@ export default class PostList extends PureComponent {
|
|||
navigator.showModal(options);
|
||||
}
|
||||
};
|
||||
|
||||
scrollToBottomOffset = () => {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
if (this.refs.list) {
|
||||
this.refs.list.scrollToOffset({offset: 0, animated: false});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
getMeasurementOffset = (index) => {
|
||||
const orderedKeys = Object.keys(this.itemMeasurements).sort((a, b) => {
|
||||
const numA = Number(a);
|
||||
const numB = Number(b);
|
||||
|
||||
if (numA > numB) {
|
||||
return 1;
|
||||
} else if (numA < numB) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}).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 - 1 : this.newMessagesIndex;
|
||||
if (index !== -1) {
|
||||
let offset = this.getMeasurementOffset(index) + this.itemMeasurements[index];
|
||||
const windowHeight = this.state.postListHeight;
|
||||
|
||||
if (offset < windowHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
const upperBound = offset + windowHeight;
|
||||
offset = offset - ((upperBound - offset) / 2);
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
setManagedConfig = async (config) => {
|
||||
let nextConfig = config;
|
||||
if (!nextConfig) {
|
||||
nextConfig = await mattermostManaged.getLocalConfig();
|
||||
}
|
||||
|
||||
if (Object.keys(nextConfig).length) {
|
||||
this.setState({
|
||||
managedConfig: nextConfig,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
keyExtractor = (item) => {
|
||||
// All keys are strings (either post IDs or special keys)
|
||||
return item;
|
||||
};
|
||||
|
||||
onRefresh = () => {
|
||||
const {
|
||||
actions,
|
||||
channelId,
|
||||
onRefresh,
|
||||
} = this.props;
|
||||
|
||||
if (channelId) {
|
||||
actions.refreshChannelWithRetry(channelId);
|
||||
}
|
||||
|
||||
if (onRefresh) {
|
||||
onRefresh();
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
this.itemMeasurements[index] = NEW_MESSAGES_HEIGHT;
|
||||
return (
|
||||
<NewMessagesDivider
|
||||
index={index}
|
||||
theme={this.props.theme}
|
||||
moreMessages={this.moreNewMessages}
|
||||
/>
|
||||
);
|
||||
} else if (isDateLine(item)) {
|
||||
this.itemMeasurements[index] = DATE_HEADER_HEIGHT;
|
||||
return (
|
||||
<DateHeader
|
||||
dateLineString={item}
|
||||
index={index}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const postId = item;
|
||||
|
||||
// Remember that the list is rendered with item 0 at the bottom so the "previous" post
|
||||
// comes after this one in the list
|
||||
const previousPostId = index < this.props.postIds.length - 1 ? this.props.postIds[index + 1] : null;
|
||||
const nextPostId = index > 0 ? this.props.postIds[index - 1] : null;
|
||||
|
||||
return this.renderPost(postId, previousPostId, nextPostId, index);
|
||||
};
|
||||
|
||||
renderPost = (postId, previousPostId, nextPostId, index) => {
|
||||
const {
|
||||
highlightPostId,
|
||||
isSearchResult,
|
||||
navigator,
|
||||
onHashtagPress,
|
||||
onPostPress,
|
||||
renderReplies,
|
||||
shouldRenderReplyButton,
|
||||
} = this.props;
|
||||
const {managedConfig} = this.state;
|
||||
|
||||
const highlight = highlightPostId === postId;
|
||||
if (highlight) {
|
||||
this.newMessagesIndex = index;
|
||||
}
|
||||
|
||||
return (
|
||||
<PostWithLayout
|
||||
postId={postId}
|
||||
previousPostId={previousPostId}
|
||||
nextPostId={nextPostId}
|
||||
highlight={highlight}
|
||||
index={index}
|
||||
renderReplies={renderReplies}
|
||||
isSearchResult={isSearchResult}
|
||||
shouldRenderReplyButton={shouldRenderReplyButton}
|
||||
onHashtagPress={onHashtagPress}
|
||||
onPermalinkPress={this.handlePermalinkPress}
|
||||
onPress={onPostPress}
|
||||
navigator={navigator}
|
||||
managedConfig={managedConfig}
|
||||
onLayoutCalled={this.measureItem}
|
||||
shouldCallOnLayout={this.props.measureCellLayout && !this.newMessageScrolledTo}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
onLayout = (event) => {
|
||||
const {height} = event.nativeEvent.layout;
|
||||
this.setState({
|
||||
postListHeight: height,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
channelId,
|
||||
highlightPostId,
|
||||
onEndReached,
|
||||
postIds,
|
||||
} = this.props;
|
||||
|
||||
const refreshControl = {
|
||||
refreshing: false,
|
||||
};
|
||||
|
||||
if (channelId) {
|
||||
refreshControl.onRefresh = this.onRefresh;
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
onContentSizeChange={this.props.onContentSizeChange}
|
||||
onLayout={this.onLayout}
|
||||
ref='list'
|
||||
data={postIds}
|
||||
extraData={this.makeExtraData(channelId, highlightPostId, this.props.extraData)}
|
||||
initialNumToRender={10}
|
||||
maxToRenderPerBatch={INITIAL_BATCH_TO_RENDER + 1}
|
||||
inverted={true}
|
||||
keyExtractor={this.keyExtractor}
|
||||
ListFooterComponent={this.props.renderFooter}
|
||||
onEndReached={onEndReached}
|
||||
onEndReachedThreshold={Platform.OS === 'ios' ? 0 : 1}
|
||||
removeClippedSubviews={true}
|
||||
{...refreshControl}
|
||||
renderItem={this.renderItem}
|
||||
contentContainerStyle={styles.postListContent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
postListContent: {
|
||||
paddingTop: 5,
|
||||
},
|
||||
});
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
|
||||
function withLayout(WrappedComponent) {
|
||||
return class WithLayoutComponent extends PureComponent {
|
||||
static propTypes = {
|
||||
index: PropTypes.number.isRequired,
|
||||
onLayoutCalled: PropTypes.func,
|
||||
shouldCallOnLayout: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onLayoutCalled: emptyFunction,
|
||||
};
|
||||
|
||||
onLayout = (event) => {
|
||||
const {height} = event.nativeEvent.layout;
|
||||
const {shouldCallOnLayout} = this.props;
|
||||
if (shouldCallOnLayout) {
|
||||
this.props.onLayoutCalled(this.props.index, height);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {index, onLayoutCalled, shouldCallOnLayout, ...otherProps} = this.props; //eslint-disable-line no-unused-vars
|
||||
return (
|
||||
<View onLayout={this.onLayout}>
|
||||
<WrappedComponent {...otherProps}/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default withLayout;
|
||||
|
|
@ -7,15 +7,13 @@ import {
|
|||
Platform,
|
||||
StyleSheet,
|
||||
View,
|
||||
InteractionManager,
|
||||
} from 'react-native';
|
||||
|
||||
import {debounce} from 'mattermost-redux/actions/helpers';
|
||||
|
||||
import AnnouncementBanner from 'app/components/announcement_banner';
|
||||
import PostList from 'app/components/post_list';
|
||||
import PostListRetry from 'app/components/post_list_retry';
|
||||
import RetryBarIndicator from 'app/components/retry_bar_indicator';
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import tracker from 'app/utils/time_tracker';
|
||||
|
||||
let ChannelIntro = null;
|
||||
|
|
@ -34,7 +32,6 @@ export default class ChannelPostList extends PureComponent {
|
|||
channelId: PropTypes.string.isRequired,
|
||||
channelRefreshingFailed: PropTypes.bool,
|
||||
currentUserId: PropTypes.string,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
lastViewedAt: PropTypes.number,
|
||||
loadMorePostsVisible: PropTypes.bool.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
|
|
@ -44,7 +41,7 @@ export default class ChannelPostList extends PureComponent {
|
|||
};
|
||||
|
||||
static defaultProps = {
|
||||
postVisibility: 15,
|
||||
postVisibility: ViewTypes.POST_VISIBILITY_CHUNK_SIZE,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -52,39 +49,20 @@ export default class ChannelPostList extends PureComponent {
|
|||
|
||||
this.state = {
|
||||
visiblePostIds: this.getVisiblePostIds(props),
|
||||
loading: true,
|
||||
};
|
||||
|
||||
this.contentHeight = 0;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
if (this.mounted === true) {
|
||||
this.setState({loading: false});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {postIds: nextPostIds} = nextProps;
|
||||
|
||||
let visiblePostIds = this.state.visiblePostIds;
|
||||
const channelSwitch = nextProps.channelId !== this.props.channelId;
|
||||
|
||||
if (nextPostIds !== this.props.postIds || nextProps.postVisibility !== this.props.postVisibility) {
|
||||
visiblePostIds = this.getVisiblePostIds(nextProps);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
visiblePostIds,
|
||||
loading: channelSwitch,
|
||||
}, () => InteractionManager.runAfterInteractions(() => {
|
||||
if (channelSwitch) {
|
||||
this.setState({loading: false});
|
||||
}
|
||||
}));
|
||||
this.setState({visiblePostIds});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
|
|
@ -93,10 +71,6 @@ export default class ChannelPostList extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
getVisiblePostIds = (props) => {
|
||||
return props.postIds.slice(0, props.postVisibility);
|
||||
};
|
||||
|
|
@ -131,21 +105,15 @@ export default class ChannelPostList extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleContentSizeChange = (contentWidth, contentHeight) => {
|
||||
this.contentHeight = contentHeight;
|
||||
};
|
||||
|
||||
loadMorePosts = debounce(() => {
|
||||
if (this.props.loadMorePostsVisible) {
|
||||
const {actions, channelId} = this.props;
|
||||
loadMorePostsTop = async () => {
|
||||
const {actions, channelId} = this.props;
|
||||
if (!this.isLoadingMoreTop) {
|
||||
this.isLoadingMoreTop = true;
|
||||
actions.increasePostVisibility(channelId).then((hasMore) => {
|
||||
if (hasMore && this.contentHeight < this.props.deviceHeight) {
|
||||
// We still have less than 1 screen of posts loaded with more to get, so load more
|
||||
this.loadMorePosts();
|
||||
}
|
||||
this.isLoadingMoreTop = !hasMore;
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
loadPostsRetry = () => {
|
||||
const {actions, channelId} = this.props;
|
||||
|
|
@ -165,7 +133,7 @@ export default class ChannelPostList extends PureComponent {
|
|||
return (
|
||||
<LoadMorePosts
|
||||
channelId={this.props.channelId}
|
||||
loadMore={this.loadMorePosts}
|
||||
loadMore={this.loadMorePostsTop}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
);
|
||||
|
|
@ -196,15 +164,7 @@ export default class ChannelPostList extends PureComponent {
|
|||
theme,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
visiblePostIds,
|
||||
loading,
|
||||
} = this.state;
|
||||
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {visiblePostIds} = this.state;
|
||||
let component;
|
||||
|
||||
if (!postIds.length && channelRefreshingFailed) {
|
||||
|
|
@ -219,8 +179,7 @@ export default class ChannelPostList extends PureComponent {
|
|||
<PostList
|
||||
postIds={visiblePostIds}
|
||||
extraData={loadMorePostsVisible}
|
||||
onContentSizeChange={this.handleContentSizeChange}
|
||||
onEndReached={this.loadMorePosts}
|
||||
onLoadMoreUp={this.loadMorePostsTop}
|
||||
onPostPress={this.goToThread}
|
||||
onRefresh={actions.setChannelRefreshing}
|
||||
renderReplies={true}
|
||||
|
|
|
|||
|
|
@ -246,8 +246,8 @@ export default class Permalink extends PureComponent {
|
|||
let focusChannelId = channelId;
|
||||
|
||||
const post = await actions.getPostThread(focusedPostId, false);
|
||||
if (this.mounted && post.error && (!postIds || !postIds.length)) {
|
||||
if (isPermalink && post.error.message.toLowerCase() !== 'network request failed') {
|
||||
if (post.error && (!postIds || !postIds.length)) {
|
||||
if (this.mounted && isPermalink && post.error.message.toLowerCase() !== 'network request failed') {
|
||||
this.setState({
|
||||
error: formatMessage({
|
||||
id: 'permalink.error.access',
|
||||
|
|
@ -258,7 +258,7 @@ export default class Permalink extends PureComponent {
|
|||
defaultMessage: 'No Results Found',
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
} else if (this.mounted) {
|
||||
this.setState({error: post.error.message, retry: true});
|
||||
}
|
||||
|
||||
|
|
@ -281,7 +281,9 @@ export default class Permalink extends PureComponent {
|
|||
actions.getPostsAfter(focusChannelId, focusedPostId, 0, 10),
|
||||
]);
|
||||
|
||||
this.setState({loading: false});
|
||||
if (this.mounted) {
|
||||
this.setState({loading: false});
|
||||
}
|
||||
};
|
||||
|
||||
onNavigatorEvent = (event) => {
|
||||
|
|
|
|||
7
package-lock.json
generated
7
package-lock.json
generated
|
|
@ -14382,6 +14382,13 @@
|
|||
"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:enahum/react-native-recyclerview-list#a05429439c49c41c73f42bb1d8ddc86eb8af5d35",
|
||||
"from": "github:enahum/react-native-recyclerview-list#a05429439c49c41c73f42bb1d8ddc86eb8af5d35",
|
||||
"requires": {
|
||||
"prop-types": "^15.6.0"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
"react-native-notifications": "github:enahum/react-native-notifications#e5bf431716613b6abf312698dde194537420f966",
|
||||
"react-native-passcode-status": "1.1.1",
|
||||
"react-native-permissions": "1.1.1",
|
||||
"react-native-recyclerview-list": "enahum/react-native-recyclerview-list.git#a05429439c49c41c73f42bb1d8ddc86eb8af5d35",
|
||||
"react-native-safe-area": "0.5.0",
|
||||
"react-native-section-list-get-item-layout": "2.2.3",
|
||||
"react-native-sentry": "0.38.3",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -2,6 +2,8 @@
|
|||
// See LICENSE.txt for license information.
|
||||
module.exports = ['./node_modules/app/app.js',
|
||||
'./node_modules/app/components/app_icon.js',
|
||||
'./node_modules/app/components/at_mention/at_mention.js',
|
||||
'./node_modules/app/components/at_mention/index.js',
|
||||
'./node_modules/app/components/attachment_button.js',
|
||||
'./node_modules/app/components/badge.js',
|
||||
'./node_modules/app/components/channel_icon.js',
|
||||
|
|
@ -25,6 +27,7 @@ module.exports = ['./node_modules/app/app.js',
|
|||
'./node_modules/app/components/layout/keyboard_layout/index.js',
|
||||
'./node_modules/app/components/layout/keyboard_layout/keyboard_layout.js',
|
||||
'./node_modules/app/components/loading.js',
|
||||
'./node_modules/app/components/markdown/hashtag.js',
|
||||
'./node_modules/app/components/markdown/index.js',
|
||||
'./node_modules/app/components/markdown/markdown.js',
|
||||
'./node_modules/app/components/markdown/markdown_block_quote.js',
|
||||
|
|
@ -55,8 +58,8 @@ module.exports = ['./node_modules/app/app.js',
|
|||
'./node_modules/app/components/post_list/date_header/index.js',
|
||||
'./node_modules/app/components/post_list/index.js',
|
||||
'./node_modules/app/components/post_list/new_messages_divider.js',
|
||||
'./node_modules/app/components/post_list/post_list.js',
|
||||
'./node_modules/app/components/post_list/with_layout.js',
|
||||
'./node_modules/app/components/post_list/post_list.android.js',
|
||||
'./node_modules/app/components/post_list/post_list_base.js',
|
||||
'./node_modules/app/components/post_list_retry.js',
|
||||
'./node_modules/app/components/post_profile_picture/index.js',
|
||||
'./node_modules/app/components/post_profile_picture/post_profile_picture.js',
|
||||
|
|
@ -68,7 +71,6 @@ module.exports = ['./node_modules/app/app.js',
|
|||
'./node_modules/app/components/profile_picture/profile_picture.js',
|
||||
'./node_modules/app/components/progressive_image/index.js',
|
||||
'./node_modules/app/components/progressive_image/progressive_image.js',
|
||||
'./node_modules/app/components/quick_text_input.js',
|
||||
'./node_modules/app/components/reply_icon.js',
|
||||
'./node_modules/app/components/safe_area_view/index.js',
|
||||
'./node_modules/app/components/safe_area_view/safe_area_view.android.js',
|
||||
|
|
@ -115,6 +117,7 @@ module.exports = ['./node_modules/app/app.js',
|
|||
'./node_modules/app/reducers/views/i18n.js',
|
||||
'./node_modules/app/reducers/views/index.js',
|
||||
'./node_modules/app/reducers/views/login.js',
|
||||
'./node_modules/app/reducers/views/post.js',
|
||||
'./node_modules/app/reducers/views/recent_emojis.js',
|
||||
'./node_modules/app/reducers/views/root.js',
|
||||
'./node_modules/app/reducers/views/search.js',
|
||||
|
|
@ -145,14 +148,35 @@ module.exports = ['./node_modules/app/app.js',
|
|||
'./node_modules/app/utils/avoid_native_bridge.js',
|
||||
'./node_modules/app/utils/client_upgrade.js',
|
||||
'./node_modules/app/utils/general.js',
|
||||
'./node_modules/app/utils/i18n.js',
|
||||
'./node_modules/app/utils/image_cache_manager.js',
|
||||
'./node_modules/app/utils/network.js',
|
||||
'./node_modules/app/utils/push_notifications.js',
|
||||
'./node_modules/app/utils/sentry/middleware.js',
|
||||
'./node_modules/app/utils/i18n.js',
|
||||
'./node_modules/app/utils/theme.js',
|
||||
'./node_modules/app/utils/time_tracker.js',
|
||||
'./node_modules/index.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/arrayWithHoles.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/arrayWithoutHoles.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/assertThisInitialized.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/classCallCheck.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/createClass.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/defineProperty.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/extends.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/get.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/getPrototypeOf.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/inherits.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/interopRequireDefault.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/interopRequireWildcard.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/objectSpread.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/objectWithoutProperties.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/objectWithoutPropertiesLoose.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/possibleConstructorReturn.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/setPrototypeOf.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/slicedToArray.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/toConsumableArray.js',
|
||||
'./node_modules/node_modules/@babel/runtime/helpers/typeof.js',
|
||||
'./node_modules/node_modules/@babel/runtime/regenerator/index.js',
|
||||
'./node_modules/node_modules/base-64/base64.js',
|
||||
'./node_modules/node_modules/clamp/index.js',
|
||||
'./node_modules/node_modules/color-convert/conversions.js',
|
||||
|
|
@ -408,6 +432,11 @@ module.exports = ['./node_modules/app/app.js',
|
|||
'./node_modules/node_modules/react-native-notifications/notification.android.js',
|
||||
'./node_modules/node_modules/react-native-permissions/index.js',
|
||||
'./node_modules/node_modules/react-native-permissions/lib/permissions.android.js',
|
||||
'./node_modules/node_modules/react-native-recyclerview-list/index.js',
|
||||
'./node_modules/node_modules/react-native-recyclerview-list/src/Constants.js',
|
||||
'./node_modules/node_modules/react-native-recyclerview-list/src/DataSource.js',
|
||||
'./node_modules/node_modules/react-native-recyclerview-list/src/RecyclerRefresh.js',
|
||||
'./node_modules/node_modules/react-native-recyclerview-list/src/RecyclerViewList.js',
|
||||
'./node_modules/node_modules/react-native-svg/elements/Circle.js',
|
||||
'./node_modules/node_modules/react-native-svg/elements/ClipPath.js',
|
||||
'./node_modules/node_modules/react-native-svg/elements/Defs.js',
|
||||
|
|
@ -416,7 +445,9 @@ module.exports = ['./node_modules/app/app.js',
|
|||
'./node_modules/node_modules/react-native-svg/elements/Image.js',
|
||||
'./node_modules/node_modules/react-native-svg/elements/Line.js',
|
||||
'./node_modules/node_modules/react-native-svg/elements/LinearGradient.js',
|
||||
'./node_modules/node_modules/react-native-svg/elements/Mask.js',
|
||||
'./node_modules/node_modules/react-native-svg/elements/Path.js',
|
||||
'./node_modules/node_modules/react-native-svg/elements/Pattern.js',
|
||||
'./node_modules/node_modules/react-native-svg/elements/Polygon.js',
|
||||
'./node_modules/node_modules/react-native-svg/elements/Polyline.js',
|
||||
'./node_modules/node_modules/react-native-svg/elements/RadialGradient.js',
|
||||
|
|
@ -668,6 +699,8 @@ module.exports = ['./node_modules/app/app.js',
|
|||
'./node_modules/node_modules/redux-persist/lib/utils/isStatePlainEnough.js',
|
||||
'./node_modules/node_modules/redux-persist/lib/utils/setImmediate.js',
|
||||
'./node_modules/node_modules/redux/lib/redux.js',
|
||||
'./node_modules/node_modules/regenerator-runtime/runtime-module.js',
|
||||
'./node_modules/node_modules/regenerator-runtime/runtime.js',
|
||||
'./node_modules/node_modules/reselect/lib/index.js',
|
||||
'./node_modules/node_modules/rn-fetch-blob/android.js',
|
||||
'./node_modules/node_modules/rn-fetch-blob/cba/index.js',
|
||||
|
|
@ -713,8 +746,6 @@ module.exports = ['./node_modules/app/app.js',
|
|||
'./node_modules/node_modules/schedule/tracking.js',
|
||||
'./node_modules/node_modules/semver/semver.js',
|
||||
'./node_modules/node_modules/shallow-equals/index.js',
|
||||
'./node_modules/node_modules/stacktrace-parser/index.js',
|
||||
'./node_modules/node_modules/stacktrace-parser/lib/stacktrace-parser.js',
|
||||
'./node_modules/node_modules/symbol-observable/lib/index.js',
|
||||
'./node_modules/node_modules/symbol-observable/lib/ponyfill.js',
|
||||
'./node_modules/node_modules/tinycolor2/tinycolor.js',
|
||||
|
|
|
|||
Loading…
Reference in a new issue