// 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 {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 {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; const AnimatedImageBackground = Animated.createAnimatedComponent(ImageBackground); const AnimatedImage = Animated.createAnimatedComponent(FastImage); export default class ProgressiveImage extends PureComponent { static propTypes = { isBackgroundImage: PropTypes.bool, children: CustomPropTypes.Children, defaultSource: PropTypes.oneOfType([PropTypes.object, PropTypes.number]), // this should be provided by the component filename: PropTypes.string, imageUri: PropTypes.string, imageStyle: CustomPropTypes.Style, onError: PropTypes.func, resizeMethod: PropTypes.string, resizeMode: PropTypes.string, style: CustomPropTypes.Style, theme: PropTypes.object.isRequired, thumbnailUri: PropTypes.string, tintDefaultSource: PropTypes.bool, }; static defaultProps = { style: {}, }; constructor(props) { super(props); this.subscribedToCache = true; this.state = { intensity: new Animated.Value(80), thumb: null, uri: null, failedImageLoad: false, }; } componentDidMount() { this.load(); } componentDidUpdate(prevProps, prevState) { if (this.props.filename !== prevProps.filename || this.props.imageUri !== prevProps.imageUri || this.props.thumbnailUri !== prevProps.thumbnailUri) { this.load(); } const {intensity, thumb, uri} = this.state; if (uri && thumb && uri !== thumb && prevState.uri !== uri) { Animated.timing(intensity, { duration: 300, toValue: 0, useNativeDriver: Platform.OS === 'android', }).start(); } } componentWillUnmount() { this.subscribedToCache = false; } load = () => { const {imageUri, thumbnailUri} = this.props; const headers = {Authorization: `Bearer ${Client4.getToken()}`}; if (thumbnailUri && imageUri) { FastImage.preload([{uri: imageUri, headers}]); this.setThumbnail(thumbnailUri); } else if (imageUri) { this.setImage(imageUri); } }; loadFullImage = () => { if (!this.state.uri) { const {imageUri} = this.props; this.setImage(imageUri); } } setImage = (uri) => { if (this.subscribedToCache) { this.setState({uri}); } }; setThumbnail = (thumb) => { if (this.subscribedToCache) { if (!thumb && !this.state.failedImageLoad) { this.load(); this.setState({failedImageLoad: true}); } else { this.setState({thumb, uri: null}); } } }; render() { const { defaultSource, imageStyle, isBackgroundImage, onError, resizeMode, resizeMethod, style, theme, tintDefaultSource, } = this.props; const {uri, intensity, thumb} = this.state; const hasDefaultSource = Boolean(defaultSource); const hasPreview = Boolean(thumb); const hasURI = Boolean(uri); const isImageReady = uri && uri !== thumb; const opacity = intensity.interpolate({ inputRange: [50, 100], outputRange: [0.5, 1], }); let DefaultComponent; let ImageComponent; if (isBackgroundImage) { DefaultComponent = ImageBackground; ImageComponent = AnimatedImageBackground; } else { DefaultComponent = Image; ImageComponent = AnimatedImage; } const styles = getStyleSheet(theme); let defaultImage; if (hasDefaultSource && tintDefaultSource) { defaultImage = ( {this.props.children} ); } else { defaultImage = ( {this.props.children} ); } if (hasDefaultSource && !hasPreview && !hasURI) { return ( {defaultImage} {hasPreview && } ); } let source; const headers = {Authorization: `Bearer ${Client4.getToken()}`}; if (hasPreview && !isImageReady) { source = {uri: thumb, headers}; } else if (isImageReady) { source = {uri, headers}; } return ( {source && {this.props.children} } {hasPreview && } ); } } const getStyleSheet = makeStyleSheetFromTheme((theme) => { return { defaultImageContainer: { flex: 1, position: 'absolute', height: 80, width: 80, alignItems: 'center', justifyContent: 'center', }, defaultImageTint: { flex: 1, tintColor: changeOpacity(theme.centerChannelColor, 0.2), }, }; });