MM-22379 & MM-22598 Boost perf by using FastImage cache in favor of ImageCacheManager (#3971)

* MM-22379 & MM-22598 Boost perf by using FastImage cache in favor of ImageCacheManager

* Code review
This commit is contained in:
Elias Nahum 2020-02-27 16:29:51 -03:00 committed by GitHub
parent ff4a771a92
commit 0504a962a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 230 additions and 229 deletions

View file

@ -4,14 +4,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Image,
Platform,
StyleSheet,
Text,
} from 'react-native';
import FastImage from 'react-native-fast-image';
import CustomPropTypes from 'app/constants/custom_prop_types';
import ImageCacheManager from 'app/utils/image_cache_manager';
export default class Emoji extends React.PureComponent {
static propTypes = {
@ -49,56 +48,15 @@ export default class Emoji extends React.PureComponent {
isCustomEmoji: false,
};
constructor(props) {
super(props);
this.state = {
imageUrl: null,
};
}
componentDidMount() {
const {displayTextOnly, emojiName, imageUrl} = this.props;
this.mounted = true;
if (!displayTextOnly && imageUrl) {
ImageCacheManager.cache(`emoji-${emojiName}`, imageUrl, this.setImageUrl);
}
}
componentWillReceiveProps(nextProps) {
const {displayTextOnly, emojiName, imageUrl} = nextProps;
if (emojiName !== this.props.emojiName && this.mounted) {
this.setState({
imageUrl: null,
});
}
if (!displayTextOnly && imageUrl &&
imageUrl !== this.props.imageUrl) {
ImageCacheManager.cache(`emoji-${emojiName}`, imageUrl, this.setImageUrl);
}
}
componentWillUnmount() {
this.mounted = false;
}
setImageUrl = (imageUrl) => {
if (this.mounted) {
this.setState({
imageUrl,
});
}
};
render() {
const {
literal,
textStyle,
displayTextOnly,
customEmojiStyle,
displayTextOnly,
imageUrl,
literal,
unicode,
textStyle,
} = this.props;
const {imageUrl} = this.state;
let size = this.props.size;
let fontSize = size;
@ -117,36 +75,32 @@ export default class Emoji extends React.PureComponent {
// Android can't change the size of an image after its first render, so
// force a new image to be rendered when the size changes
const key = Platform.OS === 'android' ? (height + '-' + width) : null;
const key = Platform.OS === 'android' ? (`${imageUrl}-${height}-${width}`) : null;
if (this.props.unicode && !this.props.imageUrl) {
const codeArray = this.props.unicode.split('-');
if (unicode && !imageUrl) {
const codeArray = unicode.split('-');
const code = codeArray.reduce((acc, c) => {
return acc + String.fromCodePoint(parseInt(c, 16));
}, '');
return (
<Text style={[this.props.textStyle, {fontSize: size}]}>
<Text style={[textStyle, {fontSize: size}]}>
{code}
</Text>
);
}
if (!imageUrl) {
return (
<Image
key={key}
style={{width, height}}
/>
);
return null;
}
return (
<Image
<FastImage
key={key}
style={[customEmojiStyle, {width, height}]}
source={{uri: imageUrl}}
onError={this.onError}
resizeMode={FastImage.resizeMode.contain}
/>
);
}

View file

@ -146,7 +146,7 @@ export default class FileAttachmentDocument extends PureComponent {
this.setState({downloading: true});
this.downloadTask = RNFetchBlob.config(options).fetch('GET', getFileUrl(data.id));
this.downloadTask.progress((received, total) => {
const progress = (received / total) * 100;
const progress = Math.round((received / total) * 100);
if (this.mounted) {
this.setState({progress});
}
@ -216,7 +216,9 @@ export default class FileAttachmentDocument extends PureComponent {
};
onDonePreviewingFile = () => {
this.setState({preview: false});
if (this.mounted) {
this.setState({preview: false});
}
this.setStatusBarColor();
};
@ -262,7 +264,9 @@ export default class FileAttachmentDocument extends PureComponent {
RNFetchBlob.fs.unlink(path);
}
this.setState({downloading: false, progress: 0});
if (this.mounted) {
this.setState({downloading: false, progress: 0});
}
});
// Android does not trigger the event for DoneButtonEvent
@ -281,7 +285,11 @@ export default class FileAttachmentDocument extends PureComponent {
didCancel: true,
}, () => {
// need to wait a bit for the progress circle UI to update to the give progress
setTimeout(() => this.setState({downloading: false}), 2000);
setTimeout(() => {
if (this.mounted) {
this.setState({downloading: false});
}
}, 2000);
});
}
};

View file

@ -8,13 +8,12 @@ import {
View,
StyleSheet,
} from 'react-native';
import FastImage from 'react-native-fast-image';
import {Client4} from 'mattermost-redux/client';
import ProgressiveImage from 'app/components/progressive_image';
import {isGif} from 'app/utils/file';
import {emptyFunction} from 'app/utils/general';
import ImageCacheManager from 'app/utils/image_cache_manager';
import {changeOpacity} from 'app/utils/theme';
import thumb from 'assets/images/thumb.png';
@ -58,11 +57,14 @@ export default class FileAttachmentImage extends PureComponent {
const {file} = props;
if (file && file.id) {
ImageCacheManager.cache(file.name, Client4.getFileThumbnailUrl(file.id), emptyFunction);
const headers = {Authorization: `Bearer ${Client4.getToken()}`};
const preloadImages = [{uri: Client4.getFileThumbnailUrl(file.id), headers}];
if (isGif(file)) {
ImageCacheManager.cache(file.name, Client4.getFileUrl(file.id), emptyFunction);
preloadImages.push({uri: Client4.getFileUrl(file.id), headers});
}
FastImage.preload(preloadImages);
}
this.state = {

View file

@ -13,10 +13,8 @@ import {TABLET_WIDTH} from 'app/components/sidebars/drawer_layout';
import {DeviceTypes} from 'app/constants';
import mattermostManaged from 'app/mattermost_managed';
import {isDocument, isGif, isVideo} from 'app/utils/file';
import ImageCacheManager from 'app/utils/image_cache_manager';
import {previewImageAtIndex} from 'app/utils/images';
import {preventDoubleTap} from 'app/utils/tap';
import {emptyFunction} from 'app/utils/general';
import FileAttachment from './file_attachment';
@ -123,9 +121,9 @@ export default class FileAttachmentList extends PureComponent {
if (file.localPath) {
uri = file.localPath;
} else if (isGif(file)) {
uri = await ImageCacheManager.cache(file.name, Client4.getFileUrl(file.id), emptyFunction); // eslint-disable-line no-await-in-loop
uri = Client4.getFileUrl(file.id);
} else {
uri = await ImageCacheManager.cache(file.name, Client4.getFilePreviewUrl(file.id), emptyFunction); // eslint-disable-line no-await-in-loop
uri = Client4.getFilePreviewUrl(file.id);
}
results.push({

View file

@ -21,7 +21,6 @@ import CustomPropTypes from 'app/constants/custom_prop_types';
import EphemeralStore from 'app/store/ephemeral_store';
import mattermostManaged from 'app/mattermost_managed';
import BottomSheet from 'app/utils/bottom_sheet';
import ImageCacheManager from 'app/utils/image_cache_manager';
import {previewImageAtIndex, calculateDimensions, isGifTooLarge} from 'app/utils/images';
import {normalizeProtocol} from 'app/utils/url';
@ -32,7 +31,7 @@ const ANDROID_MAX_WIDTH = 4096;
const VIEWPORT_IMAGE_OFFSET = 66;
const VIEWPORT_IMAGE_REPLY_OFFSET = 13;
export default class MarkdownImage extends React.Component {
export default class MarkdownImage extends React.PureComponent {
static propTypes = {
children: PropTypes.node,
deviceHeight: PropTypes.number.isRequired,
@ -65,7 +64,7 @@ export default class MarkdownImage extends React.Component {
componentDidMount() {
this.mounted = true;
ImageCacheManager.cache(null, this.getSource(), this.setImageUrl);
this.setImageUrl(this.getSource());
}
static getDerivedStateFromProps(props) {
@ -84,7 +83,7 @@ export default class MarkdownImage extends React.Component {
componentDidUpdate(prevProps) {
if (this.props.source !== prevProps.source) {
// getSource also depends on serverURL, but that shouldn't change while this is mounted
ImageCacheManager.cache(null, this.getSource(), this.setImageUrl);
this.setImageUrl(this.getSource());
}
}

View file

@ -8,7 +8,6 @@ import {Image, View} from 'react-native';
import ProgressiveImage from 'app/components/progressive_image';
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
import {isGifTooLarge, previewImageAtIndex, calculateDimensions} from 'app/utils/images';
import ImageCacheManager from 'app/utils/image_cache_manager';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
const VIEWPORT_IMAGE_OFFSET = 100;
@ -42,13 +41,13 @@ export default class AttachmentImage extends PureComponent {
}
if (imageUrl) {
ImageCacheManager.cache(null, imageUrl, this.setImageUrl);
this.setImageUrl(imageUrl);
}
}
componentDidUpdate(prevProps) {
if (this.props.imageUrl && (prevProps.imageUrl !== this.props.imageUrl)) {
ImageCacheManager.cache(null, this.props.imageUrl, this.setImageUrl);
this.setImageUrl(this.props.imageUrl);
}
}

View file

@ -1,12 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import ImageCacheManager from 'app/utils/image_cache_manager';
import {Image} from 'react-native';
import {shallow} from 'enzyme';
import React from 'react';
const originalCacheFn = ImageCacheManager.cache;
const originalGetSizeFn = Image.getSize;
import Preferences from 'mattermost-redux/constants/preferences';
@ -24,57 +22,35 @@ describe('AttachmentImage', () => {
afterEach(() => {
Image.getSize = originalGetSizeFn;
ImageCacheManager.cache = originalCacheFn;
});
test('it matches snapshot', () => {
const cacheFn = jest.fn((_, url, callback) => {
callback(url);
});
ImageCacheManager.cache = cacheFn;
const wrapper = shallow(<AttachmentImage {...baseProps}/>);
expect(wrapper).toMatchSnapshot();
});
test('it sets state based on props', () => {
const cacheFn = jest.fn((_, url, callback) => {
callback(url);
});
ImageCacheManager.cache = cacheFn;
const wrapper = shallow(<AttachmentImage {...baseProps}/>);
const state = wrapper.state();
expect(state.hasImage).toBe(true);
expect(state.imageUri).toBe('https://images.com/image.png');
expect(state.originalWidth).toBe(32);
expect(cacheFn).toHaveBeenCalled();
});
test('it does not render image if no imageUrl is provided', () => {
const cacheFn = jest.fn((_, url, callback) => {
callback(url);
});
ImageCacheManager.cache = cacheFn;
const props = {...baseProps, imageUrl: null, imageMetadata: null};
const wrapper = shallow(<AttachmentImage {...props}/>);
const state = wrapper.state();
expect(state.hasImage).toBe(false);
expect(state.imageUri).toBe(null);
expect(cacheFn).not.toHaveBeenCalled();
});
test('it calls Image.getSize if metadata is not present', () => {
const cacheFn = jest.fn((_, url, callback) => {
callback(url);
});
const getSizeFn = jest.fn((_, callback) => {
callback(64, 64);
});
ImageCacheManager.cache = cacheFn;
Image.getSize = getSizeFn;
const props = {...baseProps, imageMetadata: null};
@ -84,16 +60,10 @@ describe('AttachmentImage', () => {
expect(state.hasImage).toBe(true);
expect(state.imageUri).toBe('https://images.com/image.png');
expect(state.originalWidth).toBe(64);
expect(cacheFn).toHaveBeenCalled();
expect(getSizeFn).toHaveBeenCalled();
});
test('it updates image when imageUrl prop changes', () => {
const cacheFn = jest.fn((_, url, callback) => {
callback(url);
});
ImageCacheManager.cache = cacheFn;
const wrapper = shallow(<AttachmentImage {...baseProps}/>);
wrapper.setProps({
@ -108,15 +78,9 @@ describe('AttachmentImage', () => {
expect(state.hasImage).toBe(true);
expect(state.imageUri).toBe('https://someothersite.com/picture.png');
expect(state.originalWidth).toBe(96);
expect(cacheFn).toHaveBeenCalledTimes(2);
});
test('it does not update image when an unrelated prop changes', () => {
const cacheFn = jest.fn((_, url, callback) => {
callback(url);
});
ImageCacheManager.cache = cacheFn;
const wrapper = shallow(<AttachmentImage {...baseProps}/>);
wrapper.setProps({
@ -127,6 +91,5 @@ describe('AttachmentImage', () => {
expect(state.hasImage).toBe(true);
expect(state.imageUri).toBe('https://images.com/image.png');
expect(state.originalWidth).toBe(32);
expect(cacheFn).toHaveBeenCalledTimes(1);
});
});

View file

@ -259,7 +259,7 @@ exports[`PostAttachmentOpenGraph should match state and snapshot, on renderImage
"marginTop": 5,
},
Object {
"height": 112.56666666666666,
"height": 69.83261802575107,
"width": 307,
},
]
@ -277,7 +277,7 @@ exports[`PostAttachmentOpenGraph should match state and snapshot, on renderImage
"borderRadius": 3,
},
Object {
"height": 112.56666666666666,
"height": 69.83261802575107,
"width": 307,
},
]

View file

@ -14,7 +14,6 @@ import {TABLET_WIDTH} from 'app/components/sidebars/drawer_layout';
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
import {DeviceTypes} from 'app/constants';
import ImageCacheManager from 'app/utils/image_cache_manager';
import {previewImageAtIndex, calculateDimensions} from 'app/utils/images';
import {getNearestPoint} from 'app/utils/opengraph';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
@ -47,6 +46,10 @@ export default class PostAttachmentOpenGraph extends PureComponent {
this.mounted = true;
this.fetchData(this.props.link, this.props.openGraphData);
if (this.state.openGraphImageUrl) {
this.getImageSize(this.state.openGraphImageUrl);
}
}
componentWillReceiveProps(nextProps) {
@ -56,7 +59,9 @@ export default class PostAttachmentOpenGraph extends PureComponent {
}
if (this.props.openGraphData !== nextProps.openGraphData) {
this.setState(this.getBestImageUrl(nextProps.openGraphData));
this.setState(this.getBestImageUrl(nextProps.openGraphData), () => {
this.getImageSize(this.state.openGraphImageUrl);
});
}
}
@ -110,10 +115,6 @@ export default class PostAttachmentOpenGraph extends PureComponent {
dimensions = calculateDimensions(ogImage.height, ogImage.width, this.getViewPostWidth());
}
if (imageUrl) {
ImageCacheManager.cache(this.getFilename(imageUrl), imageUrl, this.getImageSize);
}
return {
hasImage: true,
...dimensions,
@ -143,7 +144,7 @@ export default class PostAttachmentOpenGraph extends PureComponent {
}
if (!ogImage) {
ogImage = openGraphData.images.find((i) => i.url === openGraphImageUrl || i.secure_url === openGraphImageUrl);
ogImage = openGraphData?.images?.find((i) => i.url === openGraphImageUrl || i.secure_url === openGraphImageUrl);
}
// Fallback when the ogImage does not have dimensions but there is a metaImage defined

View file

@ -23,7 +23,6 @@ import TouchableWithFeedback from 'app/components/touchable_with_feedback';
import {DeviceTypes} from 'app/constants';
import CustomPropTypes from 'app/constants/custom_prop_types';
import ImageCacheManager from 'app/utils/image_cache_manager';
import {previewImageAtIndex, calculateDimensions} from 'app/utils/images';
import {getYouTubeVideoId, isImageLink, isYoutubeLink} from 'app/utils/url';
@ -126,7 +125,6 @@ export default class PostBodyAdditionalContent extends PureComponent {
} else if (isYoutubeLink(link)) {
const videoId = getYouTubeVideoId(link);
imageUrl = `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`;
ImageCacheManager.cache(null, `https://i.ytimg.com/vi/${videoId}/default.jpg`, () => true);
} else {
const {data} = await this.props.actions.getRedirectLocation(link);
@ -141,7 +139,6 @@ export default class PostBodyAdditionalContent extends PureComponent {
} else if (isYoutubeLink(shortenedLink)) {
const videoId = getYouTubeVideoId(shortenedLink);
imageUrl = `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`;
ImageCacheManager.cache(null, `https://i.ytimg.com/vi/${videoId}/default.jpg`, () => true);
}
if (this.mounted) {
this.setState({shortenedLink});
@ -150,7 +147,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
}
if (imageUrl) {
ImageCacheManager.cache(null, imageUrl, this.getImageSize);
this.getImageSize(imageUrl);
}
}
};

View file

@ -9,8 +9,6 @@ import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome';
import {Client4} from 'mattermost-redux/client';
import UserStatus from 'app/components/user_status';
import ImageCacheManager from 'app/utils/image_cache_manager';
import {emptyFunction} from 'app/utils/general';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import placeholder from 'assets/images/profile.jpg';
@ -63,7 +61,8 @@ export default class ProfilePicture extends PureComponent {
} else if (edit && imageUri) {
this.setImageURL(imageUri);
} else if (user) {
ImageCacheManager.cache('', Client4.getProfilePictureUrl(user.id, user.last_picture_update), this.setImageURL).then(this.clearProfileImageUri).catch(emptyFunction);
this.setImageURL(Client4.getProfilePictureUrl(user.id, user.last_picture_update));
this.clearProfileImageUri();
}
}
@ -101,7 +100,8 @@ export default class ProfilePicture extends PureComponent {
if (nextUrl && url !== nextUrl) {
// empty function is so that promise unhandled is not triggered in dev mode
ImageCacheManager.cache('', nextUrl, this.setImageURL).then(this.clearProfileImageUri).catch(emptyFunction);
this.setImageURL(nextUrl);
this.clearProfileImageUri();
}
}
}

View file

@ -3,5 +3,50 @@
exports[`ProgressiveImage should match snapshot 1`] = `
<View
style={Object {}}
/>
>
<AnimatedComponent
blurRadius={5}
onError={[MockFunction]}
onLoadEnd={[Function]}
resizeMethod="auto"
resizeMode="contain"
source={
Object {
"headers": Object {
"Authorization": "Bearer ",
},
"uri": "https://images.com/image.png",
}
}
style={
Array [
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
},
undefined,
]
}
/>
<AnimatedComponent
style={
Array [
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
},
Object {
"backgroundColor": "#ffffff",
"opacity": 0.8,
},
]
}
/>
</View>
`;

View file

@ -6,8 +6,9 @@ import PropTypes from 'prop-types';
import {Animated, Image, ImageBackground, Platform, View, StyleSheet} from 'react-native';
import FastImage from 'react-native-fast-image';
import {Client4} from 'mattermost-redux/client';
import CustomPropTypes from 'app/constants/custom_prop_types';
import ImageCacheManager from 'app/utils/image_cache_manager';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
const AnimatedImageBackground = Animated.createAnimatedComponent(ImageBackground);
@ -73,15 +74,24 @@ export default class ProgressiveImage extends PureComponent {
}
load = () => {
const {filename, imageUri, thumbnailUri} = this.props;
const {imageUri, thumbnailUri} = this.props;
const headers = {Authorization: `Bearer ${Client4.getToken()}`};
if (thumbnailUri) {
ImageCacheManager.cache(filename, thumbnailUri, this.setThumbnail);
if (thumbnailUri && imageUri) {
FastImage.preload([{uri: imageUri, headers}]);
this.setThumbnail(thumbnailUri);
} else if (imageUri) {
ImageCacheManager.cache(filename, imageUri, this.setImage);
this.setImage(imageUri);
}
};
loadFullImage = () => {
if (!this.state.uri) {
const {imageUri} = this.props;
this.setImage(imageUri);
}
}
setImage = (uri) => {
if (this.subscribedToCache) {
this.setState({uri});
@ -94,12 +104,7 @@ export default class ProgressiveImage extends PureComponent {
this.load();
this.setState({failedImageLoad: true});
} else {
const {filename, imageUri} = this.props;
this.setState({thumb}, () => {
setTimeout(() => {
ImageCacheManager.cache(filename, imageUri, this.setImage);
}, 300);
});
this.setState({thumb, uri: null});
}
}
};
@ -167,28 +172,36 @@ export default class ProgressiveImage extends PureComponent {
);
}
if (hasDefaultSource && !hasPreview && !hasURI) {
return (
<View style={style}>
{defaultImage}
{hasPreview &&
<Animated.View style={[StyleSheet.absoluteFill, {backgroundColor: theme.centerChannelBg, opacity}]}/>
}
</View>
);
}
let source;
const headers = {Authorization: `Bearer ${Client4.getToken()}`};
if (hasPreview && !isImageReady) {
source = {uri: thumb, headers};
} else if (isImageReady) {
source = {uri, headers};
}
return (
<View style={style}>
{(hasDefaultSource && !hasPreview && !hasURI) && defaultImage}
{hasPreview && !isImageReady &&
{source &&
<ImageComponent
resizeMode={resizeMode}
resizeMethod={resizeMethod}
onError={onError}
source={{uri: thumb}}
style={[StyleSheet.absoluteFill, imageStyle]}
blurRadius={5}
>
{this.props.children}
</ImageComponent>
}
{isImageReady &&
<ImageComponent
resizeMode={resizeMode}
resizeMethod={resizeMethod}
onError={onError}
source={{uri}}
source={source}
style={[StyleSheet.absoluteFill, imageStyle]}
blurRadius={isImageReady ? null : 5}
onLoadEnd={this.loadFullImage}
>
{this.props.children}
</ImageComponent>

View file

@ -8,6 +8,10 @@ import Preferences from 'mattermost-redux/constants/preferences';
import ProgressiveImage from './progressive_image';
jest.mock('react-native-fast-image', () => ({
preload: jest.fn(),
}));
jest.useFakeTimers();
describe('ProgressiveImage', () => {
@ -68,15 +72,15 @@ describe('ProgressiveImage', () => {
expect(instance.setImage).toHaveBeenCalledTimes(1);
});
test('should set thumbnail when thumbnailUri is set', () => {
test('should set thumbnail when thumbnailUri and imageUri are set', () => {
const wrapper = shallow(
<ProgressiveImage
{...baseProps}
imageUri={null}
/>,
);
const instance = wrapper.instance();
jest.spyOn(instance, 'setThumbnail');
instance.load();
jest.runAllTimers();
expect(instance.setThumbnail).toHaveBeenCalledTimes(1);

View file

@ -12,7 +12,6 @@ import {
import {Client4} from 'mattermost-redux/client';
import ImageCacheManager from 'app/utils/image_cache_manager';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
export default class TeamIcon extends React.PureComponent {
@ -29,34 +28,38 @@ export default class TeamIcon extends React.PureComponent {
constructor(props) {
super(props);
const {lastIconUpdate, teamId} = props;
if (lastIconUpdate) {
ImageCacheManager.cache('', Client4.getTeamIconUrl(teamId, lastIconUpdate), this.setImageURL);
}
this.state = {
teamIcon: null,
imageError: false,
};
}
componentWillReceiveProps(nextProps) {
const newState = {imageError: false};
componentDidMount() {
const {lastIconUpdate, teamId} = this.props;
this.mounted = true;
if (this.props.teamId !== nextProps.teamId) {
newState.teamIcon = null;
if (lastIconUpdate) {
this.setImageURL(Client4.getTeamIconUrl(teamId, lastIconUpdate));
}
}
if (nextProps.lastIconUpdate && this.props.lastIconUpdate !== nextProps.lastIconUpdate) {
const {lastIconUpdate, teamId} = nextProps;
ImageCacheManager.cache('', Client4.getTeamIconUrl(teamId, lastIconUpdate), this.setImageURL);
componentDidUpdate(prevProps) {
if (this.props.teamId !== prevProps.teamId) {
this.setImageURL(null);
} else if (this.props.lastIconUpdate && this.props.lastIconUpdate !== prevProps.lastIconUpdate) {
const {lastIconUpdate, teamId} = this.props;
this.setImageURL(Client4.getTeamIconUrl(teamId, lastIconUpdate));
}
}
this.setState(newState);
componentWillUnmount() {
this.mounted = false;
}
setImageURL = (teamIcon) => {
this.setState({teamIcon});
if (this.mounted) {
this.setState({imageError: false, teamIcon});
}
};
render() {

View file

@ -8,7 +8,6 @@ import RNFetchBlob from 'rn-fetch-blob';
import semver from 'semver/preload';
import {setAppState, setServerVersion} from 'mattermost-redux/actions/general';
import {loadMe, logout} from 'mattermost-redux/actions/users';
import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone';
import {close as closeWebSocket} from 'mattermost-redux/actions/websocket';
import {Client4} from 'mattermost-redux/client';
@ -22,6 +21,7 @@ import {setDeviceDimensions, setDeviceOrientation, setDeviceAsTablet, setStatusB
import {selectDefaultChannel} from 'app/actions/views/channel';
import {showOverlay} from 'app/actions/navigation';
import {loadConfigAndLicense, setDeepLinkURL, startDataCleanup} from 'app/actions/views/root';
import {loadMe, logout} from 'app/actions/views/user';
import {NavigationTypes, ViewTypes} from 'app/constants';
import {getTranslations, resetMomentLocale} from 'app/i18n';
import mattermostBucket from 'app/mattermost_bucket';

View file

@ -7,11 +7,8 @@ import mimeDB from 'mime-db';
import {lookupMimeType} from 'mattermost-redux/utils/file_utils';
import {DeviceTypes} from 'app/constants/';
const EXTRACT_TYPE_REGEXP = /^\s*([^;\s]*)(?:;|\s|$)/;
const CONTENT_DISPOSITION_REGEXP = /inline;filename=".*\.([a-z]+)";/i;
const {DOCUMENTS_PATH, IMAGES_PATH, VIDEOS_PATH} = DeviceTypes;
const DEFAULT_SERVER_MAX_FILE_SIZE = 50 * 1024 * 1024;// 50 Mb
export const SUPPORTED_DOCS_FORMAT = [
@ -58,50 +55,49 @@ export function generateId() {
return 'uid' + id;
}
const dirsToExclude = ['Cache.db', 'WebKit', 'WebView'];
async function getDirectorySize(fileStats) {
if (fileStats?.length) {
const total = await fileStats.reduce(async (previousPromise, stat) => {
const value = await previousPromise;
const exclude = dirsToExclude.find((f) => stat.path.includes(f));
if (exclude) {
return value;
}
if (stat.type === 'directory') {
const stats = await RNFetchBlob.fs.lstat(stat.path);
const dirSize = await getDirectorySize(stats, 0);
return value + dirSize;
}
return value + parseInt(stat.size, 10);
}, Promise.resolve(0));
return total;
}
return 0;
}
export async function getFileCacheSize() {
const isDocsDir = await RNFetchBlob.fs.isDir(DOCUMENTS_PATH);
const isImagesDir = await RNFetchBlob.fs.isDir(IMAGES_PATH);
const isVideosDir = await RNFetchBlob.fs.isDir(VIDEOS_PATH);
let size = 0;
if (isDocsDir) {
const docsStats = await RNFetchBlob.fs.lstat(DOCUMENTS_PATH);
size = docsStats.reduce((accumulator, stat) => {
return accumulator + parseInt(stat.size, 10);
}, size);
}
if (isImagesDir) {
const imagesStats = await RNFetchBlob.fs.lstat(IMAGES_PATH);
size = imagesStats.reduce((accumulator, stat) => {
return accumulator + parseInt(stat.size, 10);
}, size);
}
if (isVideosDir) {
const videoStats = await RNFetchBlob.fs.lstat(VIDEOS_PATH);
size = videoStats.reduce((accumulator, stat) => {
return accumulator + parseInt(stat.size, 10);
}, size);
}
const cacheStats = await RNFetchBlob.fs.lstat(RNFetchBlob.fs.dirs.CacheDir);
const size = await getDirectorySize(cacheStats);
return size;
}
export async function deleteFileCache() {
const isDocsDir = await RNFetchBlob.fs.isDir(DOCUMENTS_PATH);
const isImagesDir = await RNFetchBlob.fs.isDir(IMAGES_PATH);
const isVideosDir = await RNFetchBlob.fs.isDir(VIDEOS_PATH);
if (isDocsDir) {
await RNFetchBlob.fs.unlink(DOCUMENTS_PATH);
}
if (isImagesDir) {
await RNFetchBlob.fs.unlink(IMAGES_PATH);
}
if (isVideosDir) {
await RNFetchBlob.fs.unlink(VIDEOS_PATH);
const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
const isCacheDir = await RNFetchBlob.fs.isDir(cacheDir);
if (isCacheDir) {
try {
await RNFetchBlob.fs.unlink(cacheDir);
await RNFetchBlob.fs.mkdir(cacheDir);
} catch (e) {
// do nothing
}
}
return true;

41
package-lock.json generated
View file

@ -7065,7 +7065,8 @@
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true
"bundled": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -7083,11 +7084,13 @@
},
"balanced-match": {
"version": "1.0.0",
"bundled": true
"bundled": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -7100,15 +7103,18 @@
},
"code-point-at": {
"version": "1.1.0",
"bundled": true
"bundled": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true
"bundled": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true
"bundled": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -7211,7 +7217,8 @@
},
"inherits": {
"version": "2.0.3",
"bundled": true
"bundled": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -7221,6 +7228,7 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -7233,17 +7241,20 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"bundled": true
"bundled": true,
"optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -7260,6 +7271,7 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -7332,7 +7344,8 @@
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true
"bundled": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -7342,6 +7355,7 @@
"once": {
"version": "1.4.0",
"bundled": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -7417,7 +7431,8 @@
},
"safe-buffer": {
"version": "5.1.2",
"bundled": true
"bundled": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -7447,6 +7462,7 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -7464,6 +7480,7 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -7502,11 +7519,13 @@
},
"wrappy": {
"version": "1.0.2",
"bundled": true
"bundled": true,
"optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true
"bundled": true,
"optional": true
}
}
},