mattermost-mobile/app/components/progressive_image/progressive_image.js
Elias Nahum 5ea470a235
V1 dependencies and bump to RN 0.67.2 (#5908)
* update dependencies

* eslint fixes

* Upgrade to RN 67

* update other deps

* Update to RN 0.67.2

* fix Android build (mmkv)

* Fix crash when root message is deleted from the thread screen

* Fix gif emoji playing at high speed on iOS ProMotion capable devices
2022-02-02 15:28:57 -03:00

233 lines
7.7 KiB
JavaScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import PropTypes from 'prop-types';
import React, {PureComponent} from 'react';
import {Animated, ImageBackground, Image, Platform, View, StyleSheet} from 'react-native';
import thumb from '@assets/images/thumb.png';
import RetriableFastImage from '@components/retriable_fast_image';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
const AnimatedImageBackground = Animated.createAnimatedComponent(ImageBackground);
const AnimatedFastImage = Animated.createAnimatedComponent(RetriableFastImage);
export default class ProgressiveImage extends PureComponent {
static propTypes = {
id: PropTypes.string,
isBackgroundImage: PropTypes.bool,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
defaultSource: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.number]), // this should be provided by the component
imageUri: PropTypes.string,
imageStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
inViewPort: PropTypes.bool,
onError: PropTypes.func,
resizeMethod: PropTypes.string,
resizeMode: PropTypes.string,
style: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
theme: PropTypes.object.isRequired,
thumbnailUri: PropTypes.string,
tintDefaultSource: PropTypes.bool,
};
static defaultProps = {
style: {},
defaultSource: undefined,
resizeMode: 'contain',
};
constructor(props) {
super(props);
this.state = {
intensity: new Animated.Value(0),
showHighResImage: false,
};
}
componentDidUpdate(prevProps) {
if (prevProps.inViewPort !== this.props.inViewPort && this.props.inViewPort) {
this.startLoadingOriginalImage();
}
}
startLoadingOriginalImage = () => {
this.setState({
showHighResImage: true,
});
};
onLoadImageEnd = () => {
Animated.timing(this.state.intensity, {
duration: 300,
toValue: 100,
useNativeDriver: true,
}).start();
};
render() {
const {
defaultSource,
id,
imageStyle,
imageUri,
isBackgroundImage,
onError,
resizeMode,
resizeMethod,
style,
theme,
thumbnailUri,
tintDefaultSource,
} = this.props;
const {showHighResImage} = this.state;
let DefaultComponent;
let ImageComponent;
if (isBackgroundImage) {
DefaultComponent = ImageBackground;
ImageComponent = AnimatedImageBackground;
} else {
DefaultComponent = AnimatedFastImage;
ImageComponent = AnimatedFastImage;
}
const styles = getStyleSheet(theme);
if (isBackgroundImage) {
return (
<View style={[styles.defaultImageContainer, style]}>
<DefaultComponent
source={{uri: imageUri}}
resizeMode={'cover'}
style={[StyleSheet.absoluteFill, imageStyle]}
>
{this.props.children}
</DefaultComponent>
</View>
);
}
if (defaultSource) {
return (
<View style={[styles.defaultImageContainer, style]}>
<DefaultComponent
source={defaultSource}
style={[StyleSheet.absoluteFill, imageStyle, tintDefaultSource ? styles.defaultImageTint : null]}
resizeMode={resizeMode}
resizeMethod={resizeMethod}
onError={onError}
nativeID={`image-${id}`}
>
{this.props.children}
</DefaultComponent>
</View>
);
}
const opacity = this.state.intensity.interpolate({
inputRange: [20, 100],
outputRange: [0.2, 1],
});
const defaultOpacity = this.state.intensity.interpolate({
inputRange: [0, 100],
outputRange: [0.5, 0],
});
const containerStyle = {
backgroundColor: changeOpacity(theme.centerChannelColor, defaultOpacity),
};
let thumbnail;
let image;
if (thumbnailUri) {
const ImageElement = thumbnailUri.startsWith('data:') ? Image : ImageComponent;
thumbnail = (
<ImageElement
resizeMode={resizeMode}
resizeMethod={resizeMethod}
onError={onError}
source={{uri: thumbnailUri}}
style={[
StyleSheet.absoluteFill,
imageStyle,
]}
blurRadius={Platform.OS === 'android' ? 0.4 : 1}
testID='progressive_image.miniPreview'
>
{this.props.children}
</ImageElement>
);
if (showHighResImage) {
image = (
<ImageComponent
nativeID={`image-${id}`}
resizeMode={resizeMode}
resizeMethod={resizeMethod}
onError={onError}
source={{uri: imageUri}}
style={[
StyleSheet.absoluteFill,
imageStyle,
{opacity}]
}
testID='progressive_image.highResImage'
onLoadEnd={this.onLoadImageEnd}
>
{this.props.children}
</ImageComponent>
);
}
} else {
thumbnail = (
<DefaultComponent
resizeMode={resizeMode}
resizeMethod={resizeMethod}
onError={onError}
source={thumb}
style={[imageStyle, {tintColor: theme.centerChannelColor, opacity: defaultOpacity}]}
testID='progressive_image.thumbnail'
/>
);
image = (
<ImageComponent
nativeID={`image-${id}`}
resizeMode={resizeMode}
resizeMethod={resizeMethod}
onError={onError}
source={{uri: imageUri}}
style={[StyleSheet.absoluteFill, imageStyle, {opacity}]}
onLoadEnd={this.onLoadImageEnd}
testID='progressive_image.highResImage'
>
{this.props.children}
</ImageComponent>
);
}
return (
<Animated.View style={[styles.defaultImageContainer, style, containerStyle]}>
{thumbnail}
{image}
</Animated.View>
);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
defaultImageContainer: {
alignItems: 'center',
justifyContent: 'center',
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
},
defaultImageTint: {
flex: 1,
tintColor: changeOpacity(theme.centerChannelColor, 0.2),
},
};
});