From e06e498a6be35c68b04314413ac2dc17ac576681 Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Wed, 26 Oct 2022 17:28:48 +0200 Subject: [PATCH 01/16] Pass encryptedAuthToken to image via header --- src/components/ImageWithSizeCalculation.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/ImageWithSizeCalculation.js b/src/components/ImageWithSizeCalculation.js index 3b6eee240d11..f660de518018 100644 --- a/src/components/ImageWithSizeCalculation.js +++ b/src/components/ImageWithSizeCalculation.js @@ -5,6 +5,10 @@ import Log from '../libs/Log'; import styles from '../styles/styles'; import makeCancellablePromise from '../libs/MakeCancellablePromise'; import FullscreenLoadingIndicator from './FullscreenLoadingIndicator'; +import {withOnyx} from 'react-native-onyx'; +import ONYXKEYS from '../ONYXKEYS'; +import compose from '../libs/compose'; +import lodashGet from 'lodash/get'; const propTypes = { /** Url for image to display */ @@ -117,7 +121,12 @@ class ImageWithSizeCalculation extends PureComponent { styles.w100, styles.h100, ]} - source={{uri: this.props.url}} + source={{ + uri: this.props.url, + headers: { + 'X-Chat-Img-Authorization': lodashGet(this.props.session, 'encryptedAuthToken', ''), + }, + }} resizeMode="contain" onLoadStart={this.imageLoadingStart} onLoadEnd={this.imageLoadingEnd} @@ -134,4 +143,8 @@ class ImageWithSizeCalculation extends PureComponent { ImageWithSizeCalculation.propTypes = propTypes; ImageWithSizeCalculation.defaultProps = defaultProps; -export default ImageWithSizeCalculation; +export default compose( + withOnyx({ + session: {key: ONYXKEYS.SESSION}, + }), +)(ImageWithSizeCalculation); From 2db5ed0bf3afc1fbc58138ebe27f8717e84f294b Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Wed, 26 Oct 2022 17:29:00 +0200 Subject: [PATCH 02/16] Stop sending authToken in URL --- src/components/ThumbnailImage.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 3088a13a425b..bedb4e432d53 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -3,7 +3,6 @@ import React, {PureComponent} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import ImageWithSizeCalculation from './ImageWithSizeCalculation'; -import addEncryptedAuthTokenToURL from '../libs/addEncryptedAuthTokenToURL'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; @@ -87,9 +86,7 @@ class ThumbnailImage extends PureComponent { } render() { - const url = this.props.isAuthTokenRequired - ? addEncryptedAuthTokenToURL(this.props.previewSourceURL) - : this.props.previewSourceURL; + const url = this.props.previewSourceURL; return ( From 47feb3ac2a79dd82cf908ba5e059faac78ea6365 Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Wed, 26 Oct 2022 17:47:23 +0200 Subject: [PATCH 03/16] Clean up lint errors --- src/components/ImageWithSizeCalculation.js | 34 +++++++++++++++------- src/components/ThumbnailImage.js | 3 +- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/components/ImageWithSizeCalculation.js b/src/components/ImageWithSizeCalculation.js index f660de518018..98d0dac5dd55 100644 --- a/src/components/ImageWithSizeCalculation.js +++ b/src/components/ImageWithSizeCalculation.js @@ -1,14 +1,13 @@ import React, {PureComponent} from 'react'; 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 {withOnyx} from 'react-native-onyx'; import ONYXKEYS from '../ONYXKEYS'; -import compose from '../libs/compose'; -import lodashGet from 'lodash/get'; const propTypes = { /** Url for image to display */ @@ -20,11 +19,25 @@ 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, + }, }; /** @@ -108,6 +121,9 @@ class ImageWithSizeCalculation extends PureComponent { } render() { + const headers = this.props.isAuthTokenRequired ? { + 'X-Chat-Img-Authorization': lodashGet(this.props.session, 'encryptedAuthToken', ''), + } : {}; return ( From 6df9720bf0373d8e8643f4bb58e5be42b28a0148 Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Wed, 26 Oct 2022 17:58:49 +0200 Subject: [PATCH 04/16] Fix header param passed to image source --- src/components/ImageWithSizeCalculation.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/ImageWithSizeCalculation.js b/src/components/ImageWithSizeCalculation.js index 98d0dac5dd55..f35a37a6bf74 100644 --- a/src/components/ImageWithSizeCalculation.js +++ b/src/components/ImageWithSizeCalculation.js @@ -121,9 +121,11 @@ class ImageWithSizeCalculation extends PureComponent { } render() { - const headers = this.props.isAuthTokenRequired ? { - 'X-Chat-Img-Authorization': lodashGet(this.props.session, 'encryptedAuthToken', ''), - } : {}; + const headers = this.props.isAuthTokenRequired ? ({ + headers: { + 'X-Chat-Img-Authorization': lodashGet(this.props.session, 'encryptedAuthToken', ''), + }, + }) : {}; return ( Date: Tue, 8 Nov 2022 18:54:20 +0200 Subject: [PATCH 05/16] Update header --- src/components/ImageWithSizeCalculation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ImageWithSizeCalculation.js b/src/components/ImageWithSizeCalculation.js index f35a37a6bf74..6e662e6ff395 100644 --- a/src/components/ImageWithSizeCalculation.js +++ b/src/components/ImageWithSizeCalculation.js @@ -123,7 +123,7 @@ class ImageWithSizeCalculation extends PureComponent { render() { const headers = this.props.isAuthTokenRequired ? ({ headers: { - 'X-Chat-Img-Authorization': lodashGet(this.props.session, 'encryptedAuthToken', ''), + 'X-Chat-Attachment-Token': lodashGet(this.props.session, 'encryptedAuthToken', ''), }, }) : {}; return ( From 508ba92f9d3b57fe36e10165c6bf3d3932095563 Mon Sep 17 00:00:00 2001 From: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> Date: Wed, 9 Nov 2022 16:36:04 +0000 Subject: [PATCH 06/16] feat: Add fast-image to thumbnails & avatar Signed-off-by: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> --- ios/NewExpensify.xcodeproj/project.pbxproj | 6 +- .../contents.xcworkspacedata | 10 +++ src/components/Avatar.js | 5 +- src/components/ImageWithSizeCalculation.js | 73 +++++-------------- 4 files changed, 35 insertions(+), 59 deletions(-) create mode 100644 ios/NewExpensify.xcworkspace/contents.xcworkspacedata diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 953179f1e1f8..2b3119b554f4 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -766,7 +766,7 @@ COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -791,6 +791,7 @@ ); MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; }; name = Debug; @@ -827,7 +828,7 @@ COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -844,6 +845,7 @@ "\"$(inherited)\"", ); MTL_ENABLE_DEBUG_INFO = NO; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; diff --git a/ios/NewExpensify.xcworkspace/contents.xcworkspacedata b/ios/NewExpensify.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..bf66f1bed435 --- /dev/null +++ b/ios/NewExpensify.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/components/Avatar.js b/src/components/Avatar.js index aa4f6dbf92fd..80bba25b40e6 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -1,7 +1,8 @@ import React, {PureComponent} from 'react'; -import {Image, View} from 'react-native'; +import {View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; +import FastImage from '@pieter-pot/react-native-fast-image'; import stylePropTypes from '../styles/stylePropTypes'; import Icon from './Icon'; import themeColors from '../styles/themes/default'; @@ -72,7 +73,7 @@ class Avatar extends PureComponent { /> ) : ( - { - Image.getSize(url, (width, height) => { - resolve({width, height}); - }, (error) => { - reject(error); - }); - }); + imageLoadingEnd() { + this.setState({isLoading: false}); } - calculateImageSize() { - if (!this.props.url) { + imageLoadedSuccessfuly(event) { + if (!event?.nativeEvent?.width || !event?.nativeEvent?.height) { + // Image didn't load properly return; } - 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}); + this.props.onMeasure({width: event.nativeEvent.width, height: event.nativeEvent.height}); } render() { @@ -134,7 +95,7 @@ class ImageWithSizeCalculation extends PureComponent { this.props.style, ]} > - {this.state.isLoading && ( Date: Thu, 10 Nov 2022 18:41:37 +0000 Subject: [PATCH 07/16] feat: Add fast image to attachments view Signed-off-by: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> --- src/components/ImageView/index.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/components/ImageView/index.js b/src/components/ImageView/index.js index 84cce4f29098..fd91b568351b 100644 --- a/src/components/ImageView/index.js +++ b/src/components/ImageView/index.js @@ -3,11 +3,16 @@ import PropTypes from 'prop-types'; import { View, Image, Pressable, } from 'react-native'; +import FastImage from '@pieter-pot/react-native-fast-image'; +import {withOnyx} from 'react-native-onyx'; +import lodashGet from 'lodash/get'; 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'; const propTypes = { /** URL to full-sized image */ @@ -205,8 +210,13 @@ class ImageView extends PureComponent { style={[styles.imageViewContainer, styles.overflowHidden]} onLayout={this.onContainerLayoutChanged} > - 1 ? 'center' : 'contain'} + resizeMode={this.state.zoomScale > 1 ? FastImage.resizeMode.center : FastImage.resizeMode.contain} onLoadStart={this.imageLoadingStart} onLoadEnd={this.imageLoadingEnd} /> @@ -248,13 +258,13 @@ class ImageView extends PureComponent { onPressIn={this.onContainerPressIn} onPress={this.onContainerPress} > - @@ -271,4 +281,6 @@ class ImageView extends PureComponent { } ImageView.propTypes = propTypes; -export default withWindowDimensions(ImageView); +export default compose(withWindowDimensions, withOnyx({ + session: {key: ONYXKEYS.SESSION}, +}))(ImageView); From 98c2590a550d0854c91e74c720aa5df7c3ec4a52 Mon Sep 17 00:00:00 2001 From: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> Date: Fri, 11 Nov 2022 11:40:51 +0000 Subject: [PATCH 08/16] Update src/components/ImageWithSizeCalculation.js Co-authored-by: Aldo Canepa Garay <87341702+aldo-expensify@users.noreply.github.com> Signed-off-by: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> --- src/components/ImageWithSizeCalculation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ImageWithSizeCalculation.js b/src/components/ImageWithSizeCalculation.js index d3167dcd2330..254dcf195579 100644 --- a/src/components/ImageWithSizeCalculation.js +++ b/src/components/ImageWithSizeCalculation.js @@ -73,7 +73,7 @@ class ImageWithSizeCalculation extends PureComponent { } imageLoadedSuccessfuly(event) { - if (!event?.nativeEvent?.width || !event?.nativeEvent?.height) { + if (!lodashGet(event, 'nativeEvent.width', false) || !lodashGet(event, 'nativeEvent.height', false)) { // Image didn't load properly return; } From 3ce4d4651c85314189b0cf8a074f50008ab3d34b Mon Sep 17 00:00:00 2001 From: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> Date: Wed, 16 Nov 2022 10:56:17 +0100 Subject: [PATCH 09/16] feat: Util function for getting chat attachment headers --- src/CONST.js | 2 ++ src/components/ImageView/index.js | 6 ++---- src/components/ImageWithSizeCalculation.js | 9 +++------ src/libs/chatAttachmentTokenHeaders.js | 21 +++++++++++++++++++++ 4 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 src/libs/chatAttachmentTokenHeaders.js diff --git a/src/CONST.js b/src/CONST.js index 5906c18b31bd..b89d2e671ea5 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -858,6 +858,8 @@ const CONST = { }, TFA_CODE_LENGTH: 6, + + CHAT_ATTACHMENT_TOKEN_KEY: 'X-Chat-Attachment-Token', }; export default CONST; diff --git a/src/components/ImageView/index.js b/src/components/ImageView/index.js index fd91b568351b..fbe4c5e0435e 100644 --- a/src/components/ImageView/index.js +++ b/src/components/ImageView/index.js @@ -5,7 +5,6 @@ import { } from 'react-native'; import FastImage from '@pieter-pot/react-native-fast-image'; import {withOnyx} from 'react-native-onyx'; -import lodashGet from 'lodash/get'; import styles from '../../styles/styles'; import * as StyleUtils from '../../styles/StyleUtils'; import canUseTouchScreen from '../../libs/canUseTouchscreen'; @@ -13,6 +12,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDime import FullscreenLoadingIndicator from '../FullscreenLoadingIndicator'; import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; +import chatAttachmentTokenHeaders from '../../libs/chatAttachmentTokenHeaders'; const propTypes = { /** URL to full-sized image */ @@ -213,9 +213,7 @@ class ImageView extends PureComponent { 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, + }; +} From 3c90aed789a6df3e958f8966f3d8f5ab44606e9d Mon Sep 17 00:00:00 2001 From: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> Date: Wed, 16 Nov 2022 12:35:52 +0100 Subject: [PATCH 10/16] fix: Use RNW's Image component directly in FastImage wrapper --- src/components/Avatar.js | 2 +- src/components/FastImage/index.js | 29 ++++++++++++++++++++++ src/components/FastImage/index.native.js | 9 +++++++ src/components/ImageView/index.js | 2 +- src/components/ImageWithSizeCalculation.js | 3 ++- 5 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 src/components/FastImage/index.js create mode 100644 src/components/FastImage/index.native.js diff --git a/src/components/Avatar.js b/src/components/Avatar.js index 80bba25b40e6..d0a6c648acfe 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -2,7 +2,6 @@ import React, {PureComponent} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; -import FastImage from '@pieter-pot/react-native-fast-image'; import stylePropTypes from '../styles/stylePropTypes'; import Icon from './Icon'; import themeColors from '../styles/themes/default'; @@ -10,6 +9,7 @@ import CONST from '../CONST'; import * as StyleUtils from '../styles/StyleUtils'; import * as Expensicons from './Icon/Expensicons'; import getAvatarDefaultSource from '../libs/getAvatarDefaultSource'; +import FastImage from './FastImage'; const propTypes = { /** Source for the avatar. Can be a URL or an icon. */ diff --git a/src/components/FastImage/index.js b/src/components/FastImage/index.js new file mode 100644 index 000000000000..6bb73494ccfa --- /dev/null +++ b/src/components/FastImage/index.js @@ -0,0 +1,29 @@ +import React from 'react'; +import {Image} from 'react-native'; +import addEncryptedAuthTokenToURL from '../../libs/addEncryptedAuthTokenToURL'; + +const RESIZE_MODES = { + contain: 'contain', + cover: 'cover', + stretch: 'stretch', + center: 'center', +}; + +const FastImage = (props) => { + // eslint-disable-next-line + const {source, ...rest} = props; + + // Check for headers - if it has them then we need to instead just add the + // encryptedAuthToken to the url and RNW's Image component headers do not work (or do they just not cache?) + if (typeof source === 'number' || props.source.headers == null) { + // eslint-disable-next-line + return ; + } + // eslint-disable-next-line + return ; +}; + +FastImage.displayName = 'FastImage'; +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 new file mode 100644 index 000000000000..bb9501df53b7 --- /dev/null +++ b/src/components/FastImage/index.native.js @@ -0,0 +1,9 @@ +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 fbe4c5e0435e..0c467642bffd 100644 --- a/src/components/ImageView/index.js +++ b/src/components/ImageView/index.js @@ -3,8 +3,8 @@ import PropTypes from 'prop-types'; import { View, Image, Pressable, } from 'react-native'; -import FastImage from '@pieter-pot/react-native-fast-image'; 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'; diff --git a/src/components/ImageWithSizeCalculation.js b/src/components/ImageWithSizeCalculation.js index e396b62b9517..688afea0a662 100644 --- a/src/components/ImageWithSizeCalculation.js +++ b/src/components/ImageWithSizeCalculation.js @@ -3,12 +3,12 @@ import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; -import FastImage from '@pieter-pot/react-native-fast-image'; import Log from '../libs/Log'; import styles from '../styles/styles'; import FullscreenLoadingIndicator from './FullscreenLoadingIndicator'; import ONYXKEYS from '../ONYXKEYS'; import chatAttachmentTokenHeaders from '../libs/chatAttachmentTokenHeaders'; +import FastImage from './FastImage'; const propTypes = { /** Url for image to display */ @@ -97,6 +97,7 @@ class ImageWithSizeCalculation extends PureComponent { styles.w100, styles.h100, ]} + fallback source={{ uri: this.props.url, headers, From 760e0c6859f6271a7cb3cd967ad6265987932fc0 Mon Sep 17 00:00:00 2001 From: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> Date: Fri, 18 Nov 2022 18:27:05 +0100 Subject: [PATCH 11/16] fix: Remove XCode 14 generated code --- ios/NewExpensify.xcodeproj/project.pbxproj | 5 ++--- ios/NewExpensify.xcworkspace/contents.xcworkspacedata | 10 ---------- 2 files changed, 2 insertions(+), 13 deletions(-) delete mode 100644 ios/NewExpensify.xcworkspace/contents.xcworkspacedata diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 2b3119b554f4..fed70c920d2c 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -766,7 +766,7 @@ COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -791,7 +791,6 @@ ); MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; }; name = Debug; @@ -828,7 +827,7 @@ COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; diff --git a/ios/NewExpensify.xcworkspace/contents.xcworkspacedata b/ios/NewExpensify.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index bf66f1bed435..000000000000 --- a/ios/NewExpensify.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - From c512849317cecb499931c6ae4fcfa9c3ab557ce7 Mon Sep 17 00:00:00 2001 From: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> Date: Sun, 20 Nov 2022 03:15:03 +0100 Subject: [PATCH 12/16] fix: Attachment modal image caching --- src/components/AttachmentModal.js | 7 +- src/components/AttachmentView.js | 13 ++- src/components/FastImage/index.js | 33 +++++-- src/components/ImageView/index.js | 29 ++++-- src/components/ImageView/index.native.js | 112 +++++++++++------------ 5 files changed, 113 insertions(+), 81 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 7895b119b726..05f818f337a7 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(sourceURL, this.props.originalFileName); + fileDownload(this.props.isAuthTokenRequired ? addEncryptedAuthTokenToURL(sourceURL) : 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,9 +229,7 @@ class AttachmentModal extends PureComponent { } render() { - const sourceURL = this.props.isAuthTokenRequired - ? addEncryptedAuthTokenToURL(this.state.sourceURL) - : this.state.sourceURL; + const sourceURL = this.state.sourceURL; const {fileName, fileExtension} = FileUtils.splitExtensionFromFileName(this.props.originalFileName || lodashGet(this.state, 'file.name', '')); @@ -266,6 +264,7 @@ 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 ( @@ -61,7 +70,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/FastImage/index.js b/src/components/FastImage/index.js index 6bb73494ccfa..49c4cec294d2 100644 --- a/src/components/FastImage/index.js +++ b/src/components/FastImage/index.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useEffect, useMemo} from 'react'; import {Image} from 'react-native'; import addEncryptedAuthTokenToURL from '../../libs/addEncryptedAuthTokenToURL'; @@ -11,16 +11,33 @@ const RESIZE_MODES = { const FastImage = (props) => { // eslint-disable-next-line - const {source, ...rest} = props; + const {source, onLoad, ...rest} = props; // Check for headers - if it has them then we need to instead just add the - // encryptedAuthToken to the url and RNW's Image component headers do not work (or do they just not cache?) - if (typeof source === 'number' || props.source.headers == null) { - // eslint-disable-next-line - return ; - } + // encryptedAuthToken to the url as RNW's Image component headers do not properly cache + const imageSource = useMemo(() => { + if (typeof source === 'number' || source.headers == null) { + return source; + } + return {uri: addEncryptedAuthTokenToURL(source.uri)}; + }, [source]); + + // Conform DOM onLoad event to return width and height to match RNFastImage + // https://github.com/DylanVann/react-native-fast-image#onload-event--void + useEffect(() => { + if (onLoad == null) { + return; + } + const uri = typeof imageSource === 'number' + ? Image.resolveAssetSource(imageSource).uri + : imageSource.uri; + Image.getSize(uri, (width, height) => { + onLoad({nativeEvent: {width, height}}); + }); + }, [imageSource, onLoad]); + // eslint-disable-next-line - return ; + return ; }; FastImage.displayName = 'FastImage'; diff --git a/src/components/ImageView/index.js b/src/components/ImageView/index.js index 51062ac3a844..ebf8d9faee95 100644 --- a/src/components/ImageView/index.js +++ b/src/components/ImageView/index.js @@ -1,7 +1,7 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { - View, Image, Pressable, + View, Pressable, } from 'react-native'; import {withOnyx} from 'react-native-onyx'; import FastImage from '../FastImage'; @@ -15,11 +15,19 @@ 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); @@ -28,6 +36,7 @@ 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); @@ -51,9 +60,6 @@ class ImageView extends PureComponent { } componentDidMount() { - Image.getSize(this.props.url, (width, height) => { - this.setImageRegion(width, height); - }); if (this.canUseTouchScreen) { return; } @@ -212,6 +218,10 @@ class ImageView extends PureComponent { this.setState(prevState => ({isDragging: prevState.isMouseDown})); } + imageLoad({nativeEvent}) { + this.setImageRegion(nativeEvent.width, nativeEvent.height); + } + imageLoadingStart() { this.setState({isLoading: true}); } @@ -221,6 +231,7 @@ class ImageView extends PureComponent { } render() { + const headers = this.props.isAuthTokenRequired ? chatAttachmentTokenHeaders() : undefined; if (this.canUseTouchScreen) { return ( 1 ? FastImage.resizeMode.center : FastImage.resizeMode.contain} onLoadStart={this.imageLoadingStart} onLoadEnd={this.imageLoadingEnd} + onLoad={this.imageLoad} /> {this.state.isLoading && ( @@ -296,6 +312,7 @@ class ImageView extends PureComponent { } ImageView.propTypes = propTypes; +ImageView.defaultProps = defaultProps; export default compose(withWindowDimensions, withOnyx({ session: {key: ONYXKEYS.SESSION}, }))(ImageView); diff --git a/src/components/ImageView/index.native.js b/src/components/ImageView/index.native.js index e3b0a6ef0e07..955ddfb49a13 100644 --- a/src/components/ImageView/index.native.js +++ b/src/components/ImageView/index.native.js @@ -1,35 +1,46 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { - View, InteractionManager, PanResponder, + View, PanResponder, InteractionManager, } 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, - imageWidth: undefined, - imageHeight: undefined, + + // 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, interactionPromise: undefined, containerHeight: undefined, }; @@ -46,15 +57,11 @@ class ImageView extends PureComponent { onStartShouldSetPanResponder: this.updatePanResponderTouches.bind(this), }); + this.imageLoad = this.imageLoad.bind(this); this.imageLoadingStart = this.imageLoadingStart.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() { if (!this.state.interactionPromise) { return; @@ -62,13 +69,27 @@ class ImageView extends PureComponent { this.state.interactionPromise.cancel(); } - calculateImageSize() { - if (!this.props.url) { - return; + /** + * 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; } - ImageSize.getSize(this.props.url).then(({width, height}) => { - let imageWidth = width; - let imageHeight = height; + + // 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; const containerWidth = Math.round(this.props.windowWidth); const containerHeight = Math.round(this.state.containerHeight); @@ -88,22 +109,6 @@ class ImageView extends PureComponent { }); } - /** - * 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}); } @@ -116,32 +121,6 @@ class ImageView extends PureComponent { // Default windowHeight accounts for the modal header height const windowHeight = this.props.windowHeight - variables.contentHeaderHeight; - // 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 ( { + const layout = event.nativeEvent.layout; + this.setState({ + containerHeight: layout.height, + }); + }} > this.zoom = el} @@ -186,16 +171,20 @@ 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 @@ -223,5 +212,6 @@ class ImageView extends PureComponent { } ImageView.propTypes = propTypes; +ImageView.defaultProps = defaultProps; export default withWindowDimensions(ImageView); From c71095a94d4a3316059c8d2cf0dd1806f4cc0287 Mon Sep 17 00:00:00 2001 From: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> Date: Sun, 20 Nov 2022 12:11:18 +0100 Subject: [PATCH 13/16] fix: Image layout shift --- src/components/ImageView/index.native.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/ImageView/index.native.js b/src/components/ImageView/index.native.js index 955ddfb49a13..e7060cb11c69 100644 --- a/src/components/ImageView/index.native.js +++ b/src/components/ImageView/index.native.js @@ -57,9 +57,8 @@ class ImageView extends PureComponent { onStartShouldSetPanResponder: this.updatePanResponderTouches.bind(this), }); - this.imageLoad = this.imageLoad.bind(this); this.imageLoadingStart = this.imageLoadingStart.bind(this); - this.imageLoadingEnd = this.imageLoadingEnd.bind(this); + this.imageLoad = this.imageLoad.bind(this); } componentWillUnmount() { @@ -105,7 +104,7 @@ 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}); + this.setState({imageHeight, imageWidth, isLoading: false}); }); } @@ -113,10 +112,6 @@ class ImageView extends PureComponent { 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; @@ -176,6 +171,11 @@ class ImageView extends PureComponent { styles.w100, styles.h100, this.props.style, + + // Hide image while loading so ImageZoom can get the image + // size before presenting - preventing visual glitches or shift + // due to ImageZoom + this.state.isLoading ? styles.opacity0 : styles.opacity1, ]} source={{ uri: this.props.url, @@ -183,7 +183,6 @@ class ImageView extends PureComponent { }} resizeMode={FastImage.resizeMode.contain} onLoadStart={this.imageLoadingStart} - onLoadEnd={this.imageLoadingEnd} onLoad={this.imageLoad} /> {/** From e8db5484cc8699ecafceae7fca37418b89fbd25a Mon Sep 17 00:00:00 2001 From: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> Date: Tue, 22 Nov 2022 13:58:13 +0100 Subject: [PATCH 14/16] fix: Remove fallback prop --- src/components/ImageView/index.native.js | 3 ++- src/components/ImageWithSizeCalculation.js | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ImageView/index.native.js b/src/components/ImageView/index.native.js index e7060cb11c69..1736af4c53cf 100644 --- a/src/components/ImageView/index.native.js +++ b/src/components/ImageView/index.native.js @@ -115,6 +115,7 @@ class ImageView extends PureComponent { render() { // Default windowHeight accounts for the modal header height const windowHeight = this.props.windowHeight - variables.contentHeaderHeight; + const headers = this.props.isAuthTokenRequired ? chatAttachmentTokenHeaders() : undefined; // Zoom view should be loaded only after measuring actual image dimensions, otherwise it causes blurred images on Android return ( @@ -179,7 +180,7 @@ class ImageView extends PureComponent { ]} source={{ uri: this.props.url, - headers: this.props.isAuthTokenRequired ? chatAttachmentTokenHeaders() : undefined, + headers, }} resizeMode={FastImage.resizeMode.contain} onLoadStart={this.imageLoadingStart} diff --git a/src/components/ImageWithSizeCalculation.js b/src/components/ImageWithSizeCalculation.js index 688afea0a662..f7a32be95368 100644 --- a/src/components/ImageWithSizeCalculation.js +++ b/src/components/ImageWithSizeCalculation.js @@ -97,7 +97,6 @@ class ImageWithSizeCalculation extends PureComponent { styles.w100, styles.h100, ]} - fallback source={{ uri: this.props.url, headers, From b0b2e6d61936675a0c789c77cfb9b023874accd6 Mon Sep 17 00:00:00 2001 From: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> Date: Tue, 22 Nov 2022 16:19:41 +0100 Subject: [PATCH 15/16] fix: Use lifecycles rather than hooks --- src/components/FastImage/index.js | 57 +++++++++++++++++++------------ 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/components/FastImage/index.js b/src/components/FastImage/index.js index 49c4cec294d2..4df8137bb526 100644 --- a/src/components/FastImage/index.js +++ b/src/components/FastImage/index.js @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo} from 'react'; +import React from 'react'; import {Image} from 'react-native'; import addEncryptedAuthTokenToURL from '../../libs/addEncryptedAuthTokenToURL'; @@ -9,38 +9,53 @@ const RESIZE_MODES = { center: 'center', }; -const FastImage = (props) => { - // eslint-disable-next-line - const {source, onLoad, ...rest} = props; +class FastImage extends React.Component { + constructor(props) { + super(props); - // Check for headers - if it has them then we need to instead just add the - // encryptedAuthToken to the url as RNW's Image component headers do not properly cache - const imageSource = useMemo(() => { - if (typeof source === 'number' || source.headers == null) { - return source; + this.state = { + imageSource: undefined, + }; + } + + componentDidMount() { + this.configureImageSource(); + } + + componentDidUpdate(prevProps) { + if (prevProps.source === this.props.source) { + return; } - return {uri: addEncryptedAuthTokenToURL(source.uri)}; - }, [source]); + this.configureImageSource(); + } - // Conform DOM onLoad event to return width and height to match RNFastImage - // https://github.com/DylanVann/react-native-fast-image#onload-event--void - useEffect(() => { - if (onLoad == null) { + configureImageSource() { + const source = this.props.source; + let imageSource = source; + if (typeof source !== 'number' && source.headers != null) { + imageSource = {uri: addEncryptedAuthTokenToURL(source.uri)}; + } + this.setState({imageSource}); + if (this.props.onLoad == null) { return; } const uri = typeof imageSource === 'number' ? Image.resolveAssetSource(imageSource).uri : imageSource.uri; Image.getSize(uri, (width, height) => { - onLoad({nativeEvent: {width, height}}); + this.props.onLoad({nativeEvent: {width, height}}); }); - }, [imageSource, onLoad]); + } - // eslint-disable-next-line - return ; -}; + render() { + // eslint-disable-next-line + const { source, onLoad, ...rest } = this.props; + + // eslint-disable-next-line + return ; + } +} -FastImage.displayName = 'FastImage'; FastImage.propTypes = Image.propTypes; FastImage.resizeMode = RESIZE_MODES; export default FastImage; From 973cbce986b17a2dccd03deb44fdccc72ca5f912 Mon Sep 17 00:00:00 2001 From: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> Date: Tue, 22 Nov 2022 17:09:40 +0100 Subject: [PATCH 16/16] fix: Remove XCode 14 generate line --- ios/NewExpensify.xcodeproj/project.pbxproj | 1 - 1 file changed, 1 deletion(-) diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index fed70c920d2c..953179f1e1f8 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -844,7 +844,6 @@ "\"$(inherited)\"", ); MTL_ENABLE_DEBUG_INFO = NO; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; };