diff --git a/src/CONST.js b/src/CONST.js index fe0876996e66..e1fbe4c194d3 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -862,8 +862,6 @@ const CONST = { }, TFA_CODE_LENGTH: 6, - - CHAT_ATTACHMENT_TOKEN_KEY: 'X-Chat-Attachment-Token', }; export default CONST; diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 05f818f337a7..7895b119b726 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -115,7 +115,7 @@ class AttachmentModal extends PureComponent { * @param {String} sourceURL */ downloadAttachment(sourceURL) { - fileDownload(this.props.isAuthTokenRequired ? addEncryptedAuthTokenToURL(sourceURL) : sourceURL, this.props.originalFileName); + fileDownload(sourceURL, this.props.originalFileName); // At ios, if the keyboard is open while opening the attachment, then after downloading // the attachment keyboard will show up. So, to fix it we need to dismiss the keyboard. @@ -229,7 +229,9 @@ class AttachmentModal extends PureComponent { } render() { - const sourceURL = this.state.sourceURL; + const sourceURL = this.props.isAuthTokenRequired + ? addEncryptedAuthTokenToURL(this.state.sourceURL) + : this.state.sourceURL; const {fileName, fileExtension} = FileUtils.splitExtensionFromFileName(this.props.originalFileName || lodashGet(this.state, 'file.name', '')); @@ -264,7 +266,6 @@ class AttachmentModal extends PureComponent { {this.state.sourceURL && ( { // will appear with a sourceURL that is a blob if (Str.isPDF(props.sourceURL) || (props.file && Str.isPDF(props.file.name || props.translate('attachmentView.unknownFilename')))) { - const sourceURL = props.isAuthTokenRequired - ? addEncryptedAuthTokenToURL(props.sourceURL) - : props.sourceURL; return ( @@ -70,7 +61,7 @@ const AttachmentView = (props) => { // both PDFs and images will appear as images when pasted into the the text field if (Str.isImage(props.sourceURL) || (props.file && Str.isImage(props.file.name))) { return ( - + ); } diff --git a/src/components/Avatar.js b/src/components/Avatar.js index bfdc2f8c20e1..05caa431c970 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -1,5 +1,5 @@ import React, {PureComponent} from 'react'; -import {View} from 'react-native'; +import {Image, View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import stylePropTypes from '../styles/stylePropTypes'; @@ -10,7 +10,6 @@ import * as StyleUtils from '../styles/StyleUtils'; import * as Expensicons from './Icon/Expensicons'; import getAvatarDefaultSource from '../libs/getAvatarDefaultSource'; import styles from '../styles/styles'; -import FastImage from './FastImage'; const propTypes = { /** Source for the avatar. Can be a URL or an icon. */ @@ -81,7 +80,7 @@ class Avatar extends PureComponent { ) : ( - { - this.props.onLoad({nativeEvent: {width, height}}); - }); - } - - render() { - // eslint-disable-next-line - const { source, onLoad, ...rest } = this.props; - - // eslint-disable-next-line - return ; - } -} - -FastImage.propTypes = Image.propTypes; -FastImage.resizeMode = RESIZE_MODES; -export default FastImage; diff --git a/src/components/FastImage/index.native.js b/src/components/FastImage/index.native.js deleted file mode 100644 index bb9501df53b7..000000000000 --- a/src/components/FastImage/index.native.js +++ /dev/null @@ -1,9 +0,0 @@ -import RNFastImage from '@pieter-pot/react-native-fast-image'; - -// eslint-disable-next-line -const FastImage = (props) => ; - -FastImage.displayName = 'FastImage'; -FastImage.propTypes = RNFastImage.propTypes; -FastImage.resizeMode = RNFastImage.resizeMode; -export default FastImage; diff --git a/src/components/ImageView/index.js b/src/components/ImageView/index.js index ebf8d9faee95..2356ffee8b5f 100644 --- a/src/components/ImageView/index.js +++ b/src/components/ImageView/index.js @@ -1,33 +1,20 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { - View, Pressable, + View, Image, Pressable, } from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import FastImage from '../FastImage'; import styles from '../../styles/styles'; import * as StyleUtils from '../../styles/StyleUtils'; import canUseTouchScreen from '../../libs/canUseTouchscreen'; import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions'; import FullscreenLoadingIndicator from '../FullscreenLoadingIndicator'; -import compose from '../../libs/compose'; -import ONYXKEYS from '../../ONYXKEYS'; -import chatAttachmentTokenHeaders from '../../libs/chatAttachmentTokenHeaders'; const propTypes = { - - /** Do the urls require an authToken? */ - isAuthTokenRequired: PropTypes.bool, - /** URL to full-sized image */ url: PropTypes.string.isRequired, ...windowDimensionsPropTypes, }; -const defaultProps = { - isAuthTokenRequired: false, -}; - class ImageView extends PureComponent { constructor(props) { super(props); @@ -36,7 +23,6 @@ class ImageView extends PureComponent { this.onContainerLayoutChanged = this.onContainerLayoutChanged.bind(this); this.onContainerPressIn = this.onContainerPressIn.bind(this); this.onContainerPress = this.onContainerPress.bind(this); - this.imageLoad = this.imageLoad.bind(this); this.imageLoadingStart = this.imageLoadingStart.bind(this); this.imageLoadingEnd = this.imageLoadingEnd.bind(this); this.trackMovement = this.trackMovement.bind(this); @@ -60,6 +46,9 @@ class ImageView extends PureComponent { } componentDidMount() { + Image.getSize(this.props.url, (width, height) => { + this.setImageRegion(width, height); + }); if (this.canUseTouchScreen) { return; } @@ -218,10 +207,6 @@ class ImageView extends PureComponent { this.setState(prevState => ({isDragging: prevState.isMouseDown})); } - imageLoad({nativeEvent}) { - this.setImageRegion(nativeEvent.width, nativeEvent.height); - } - imageLoadingStart() { this.setState({isLoading: true}); } @@ -231,18 +216,14 @@ class ImageView extends PureComponent { } render() { - const headers = this.props.isAuthTokenRequired ? chatAttachmentTokenHeaders() : undefined; if (this.canUseTouchScreen) { return ( - 1 ? FastImage.resizeMode.center : FastImage.resizeMode.contain} + resizeMode={this.state.zoomScale > 1 ? 'center' : 'contain'} onLoadStart={this.imageLoadingStart} onLoadEnd={this.imageLoadingEnd} - onLoad={this.imageLoad} /> {this.state.isLoading && ( - @@ -312,7 +288,4 @@ class ImageView extends PureComponent { } ImageView.propTypes = propTypes; -ImageView.defaultProps = defaultProps; -export default compose(withWindowDimensions, withOnyx({ - session: {key: ONYXKEYS.SESSION}, -}))(ImageView); +export default withWindowDimensions(ImageView); diff --git a/src/components/ImageView/index.native.js b/src/components/ImageView/index.native.js index 1736af4c53cf..e3b0a6ef0e07 100644 --- a/src/components/ImageView/index.native.js +++ b/src/components/ImageView/index.native.js @@ -1,46 +1,35 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { - View, PanResponder, InteractionManager, + View, InteractionManager, PanResponder, } from 'react-native'; +import Image from '@pieter-pot/react-native-fast-image'; import ImageZoom from 'react-native-image-pan-zoom'; +import ImageSize from 'react-native-image-size'; import _ from 'underscore'; import styles from '../../styles/styles'; import variables from '../../styles/variables'; import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions'; import FullscreenLoadingIndicator from '../FullscreenLoadingIndicator'; -import FastImage from '../FastImage'; -import chatAttachmentTokenHeaders from '../../libs/chatAttachmentTokenHeaders'; /** * On the native layer, we use a image library to handle zoom functionality */ const propTypes = { - - /** Do the urls require an authToken? */ - isAuthTokenRequired: PropTypes.bool, - /** URL to full-sized image */ url: PropTypes.string.isRequired, ...windowDimensionsPropTypes, }; -const defaultProps = { - isAuthTokenRequired: false, -}; - class ImageView extends PureComponent { constructor(props) { super(props); this.state = { isLoading: false, - - // Default to large image width and height to prevent - // small, blurry image being present by react-native-image-pan-zoom - imageWidth: props.windowWidth, - imageHeight: props.windowHeight, + imageWidth: undefined, + imageHeight: undefined, interactionPromise: undefined, containerHeight: undefined, }; @@ -58,7 +47,12 @@ class ImageView extends PureComponent { }); this.imageLoadingStart = this.imageLoadingStart.bind(this); - this.imageLoad = this.imageLoad.bind(this); + this.imageLoadingEnd = this.imageLoadingEnd.bind(this); + } + + componentDidMount() { + // Wait till animations are over to prevent stutter in navigation animation + this.state.interactionPromise = InteractionManager.runAfterInteractions(() => this.calculateImageSize()); } componentWillUnmount() { @@ -68,27 +62,13 @@ class ImageView extends PureComponent { this.state.interactionPromise.cancel(); } - /** - * Updates the amount of active touches on the PanResponder on our ImageZoom overlay View - * - * @param {Event} e - * @param {GestureState} gestureState - * @returns {Boolean} - */ - updatePanResponderTouches(e, gestureState) { - if (_.isNumber(gestureState.numberActiveTouches)) { - this.amountOfTouches = gestureState.numberActiveTouches; + calculateImageSize() { + if (!this.props.url) { + return; } - - // We don't need to set the panResponder since all we care about is checking the gestureState, so return false - return false; - } - - imageLoad({nativeEvent}) { - // Wait till animations are over to prevent stutter in navigation animation - this.state.interactionPromise = InteractionManager.runAfterInteractions(() => { - let imageWidth = nativeEvent.width; - let imageHeight = nativeEvent.height; + ImageSize.getSize(this.props.url).then(({width, height}) => { + let imageWidth = width; + let imageHeight = height; const containerWidth = Math.round(this.props.windowWidth); const containerHeight = Math.round(this.state.containerHeight); @@ -104,18 +84,63 @@ class ImageView extends PureComponent { const maxDimensionsScale = 11; imageHeight = Math.min(imageHeight, (this.props.windowHeight * maxDimensionsScale)); imageWidth = Math.min(imageWidth, (this.props.windowWidth * maxDimensionsScale)); - this.setState({imageHeight, imageWidth, isLoading: false}); + this.setState({imageHeight, imageWidth}); }); } + /** + * Updates the amount of active touches on the PanResponder on our ImageZoom overlay View + * + * @param {Event} e + * @param {GestureState} gestureState + * @returns {Boolean} + */ + updatePanResponderTouches(e, gestureState) { + if (_.isNumber(gestureState.numberActiveTouches)) { + this.amountOfTouches = gestureState.numberActiveTouches; + } + + // We don't need to set the panResponder since all we care about is checking the gestureState, so return false + return false; + } + imageLoadingStart() { this.setState({isLoading: true}); } + imageLoadingEnd() { + this.setState({isLoading: false}); + } + render() { // Default windowHeight accounts for the modal header height const windowHeight = this.props.windowHeight - variables.contentHeaderHeight; - const headers = this.props.isAuthTokenRequired ? chatAttachmentTokenHeaders() : undefined; + + // Display thumbnail until Image size calculation is complete + if (!this.state.imageWidth || !this.state.imageHeight) { + return ( + { + const layout = event.nativeEvent.layout; + this.setState({ + containerHeight: layout.height, + }); + }} + > + + + ); + } // Zoom view should be loaded only after measuring actual image dimensions, otherwise it causes blurred images on Android return ( @@ -127,12 +152,6 @@ class ImageView extends PureComponent { styles.justifyContentCenter, styles.overflowHidden, ]} - onLayout={(event) => { - const layout = event.nativeEvent.layout; - this.setState({ - containerHeight: layout.height, - }); - }} > this.zoom = el} @@ -167,24 +186,16 @@ class ImageView extends PureComponent { this.imageZoomScale = scale; }} > - {/** Create an invisible view on top of the image so we can capture and set the amount of touches before @@ -212,6 +223,5 @@ class ImageView extends PureComponent { } ImageView.propTypes = propTypes; -ImageView.defaultProps = defaultProps; export default withWindowDimensions(ImageView); diff --git a/src/components/ImageWithSizeCalculation.js b/src/components/ImageWithSizeCalculation.js index f7a32be95368..3b6eee240d11 100644 --- a/src/components/ImageWithSizeCalculation.js +++ b/src/components/ImageWithSizeCalculation.js @@ -1,14 +1,10 @@ import React, {PureComponent} from 'react'; -import {View} from 'react-native'; +import {View, Image} from 'react-native'; import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; -import lodashGet from 'lodash/get'; import Log from '../libs/Log'; import styles from '../styles/styles'; +import makeCancellablePromise from '../libs/MakeCancellablePromise'; import FullscreenLoadingIndicator from './FullscreenLoadingIndicator'; -import ONYXKEYS from '../ONYXKEYS'; -import chatAttachmentTokenHeaders from '../libs/chatAttachmentTokenHeaders'; -import FastImage from './FastImage'; const propTypes = { /** Url for image to display */ @@ -20,25 +16,11 @@ const propTypes = { /** Callback fired when the image has been measured. */ onMeasure: PropTypes.func, - - /** Does the image require an authToken? */ - isAuthTokenRequired: PropTypes.bool, - - /* Onyx props */ - /** Session object */ - session: PropTypes.shape({ - /** An error message to display to the user */ - encryptedAuthToken: PropTypes.string, - }), }; const defaultProps = { style: {}, onMeasure: () => {}, - isAuthTokenRequired: false, - session: { - encryptedAuthToken: false, - }, }; /** @@ -57,33 +39,71 @@ class ImageWithSizeCalculation extends PureComponent { this.imageLoadingStart = this.imageLoadingStart.bind(this); this.imageLoadingEnd = this.imageLoadingEnd.bind(this); - this.onError = this.onError.bind(this); - this.imageLoadedSuccessfuly = this.imageLoadedSuccessfuly.bind(this); } - onError() { - Log.hmmm('Unable to fetch image to calculate size', {url: this.props.url}); + componentDidMount() { + this.calculateImageSize(); } - imageLoadingStart() { - this.setState({isLoading: true}); + componentDidUpdate(prevProps) { + if (prevProps.url === this.props.url) { + return; + } + + this.calculateImageSize(); } - imageLoadingEnd() { - this.setState({isLoading: false}); + componentWillUnmount() { + if (!this.getImageSizePromise) { + return; + } + + this.getImageSizePromise.cancel(); + } + + /** + * @param {String} url + * @returns {Promise} + */ + getImageSize(url) { + return new Promise((resolve, reject) => { + Image.getSize(url, (width, height) => { + resolve({width, height}); + }, (error) => { + reject(error); + }); + }); } - imageLoadedSuccessfuly(event) { - if (!lodashGet(event, 'nativeEvent.width', false) || !lodashGet(event, 'nativeEvent.height', false)) { - // Image didn't load properly + calculateImageSize() { + if (!this.props.url) { return; } - this.props.onMeasure({width: event.nativeEvent.width, height: event.nativeEvent.height}); + this.getImageSizePromise = makeCancellablePromise(this.getImageSize(this.props.url)); + this.getImageSizePromise.promise + .then(({width, height}) => { + if (!width || !height) { + // Image didn't load properly + return; + } + + this.props.onMeasure({width, height}); + }) + .catch((error) => { + Log.hmmm('Unable to fetch image to calculate size', {error, url: this.props.url}); + }); + } + + imageLoadingStart() { + this.setState({isLoading: true}); + } + + imageLoadingEnd() { + this.setState({isLoading: false}); } render() { - const headers = this.props.isAuthTokenRequired ? chatAttachmentTokenHeaders() : undefined; return ( - {this.state.isLoading && ( @@ -100,7 +103,6 @@ class ThumbnailImage extends PureComponent { diff --git a/src/libs/chatAttachmentTokenHeaders.js b/src/libs/chatAttachmentTokenHeaders.js deleted file mode 100644 index 60c4d5dc8505..000000000000 --- a/src/libs/chatAttachmentTokenHeaders.js +++ /dev/null @@ -1,21 +0,0 @@ -import lodashGet from 'lodash/get'; -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '../ONYXKEYS'; -import CONST from '../CONST'; - -let encryptedAuthToken = ''; -Onyx.connect({ - key: ONYXKEYS.SESSION, - callback: session => encryptedAuthToken = lodashGet(session, 'encryptedAuthToken', ''), -}); - -/** - * Create a header object with the encryptedAuthToken for image caching via headers - * - * @returns {String} - */ -export default function () { - return { - [CONST.CHAT_ATTACHMENT_TOKEN_KEY]: encryptedAuthToken, - }; -}