From d148a0d7110b4241768f47f2b4cfcb2efbb3c2fb Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Fri, 9 Oct 2020 17:57:03 -0700 Subject: [PATCH 01/39] add component and styles --- src/components/ImageModal/index.js | 73 +++++++++++++++++++ .../home/report/ReportActionItemFragment.js | 4 + src/style/StyleSheet.js | 23 ++++++ 3 files changed, 100 insertions(+) create mode 100644 src/components/ImageModal/index.js diff --git a/src/components/ImageModal/index.js b/src/components/ImageModal/index.js new file mode 100644 index 000000000000..11002544528d --- /dev/null +++ b/src/components/ImageModal/index.js @@ -0,0 +1,73 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { View, Image, Modal, TouchableOpacity, Text } from 'react-native'; +import styles from '../../style/StyleSheet'; +import ImageView from "react-native-image-viewing"; + +/** + * Text based component that is passed a URL to open onPress + */ + +const propTypes = { + // The preview of the image + previewSrc: PropTypes.string, + + // The full sized image + src: PropTypes.string, + + // Any additional styles to apply + // eslint-disable-next-line react/forbid-prop-types + style: PropTypes.any, +}; + +const defaultProps = { + previewSrc: '', + src: '', + style: {}, +}; + +class ImageModal extends React.Component { + constructor(props) { + super(props); + + this.state = { + visible: false, + } + } + + setModalVisiblity(visibility) { + this.setState({ visible: visibility }); + } + + render() { + return ( + + this.setModalVisiblity(true)}> + + + + this.setModalVisiblity(false)} + visible={this.state.visible} + > + + this.setModalVisiblity(false)}>X + + + + + + + + + ); + } + +} + +ImageModal.propTypes = propTypes; +ImageModal.defaultProps = defaultProps; +ImageModal.displayName = 'ImageModal'; + +export default ImageModal; diff --git a/src/page/home/report/ReportActionItemFragment.js b/src/page/home/report/ReportActionItemFragment.js index 18eea963ce86..bcda1bd7c803 100644 --- a/src/page/home/report/ReportActionItemFragment.js +++ b/src/page/home/report/ReportActionItemFragment.js @@ -9,6 +9,7 @@ import ReportActionFragmentPropTypes from './ReportActionFragmentPropTypes'; import styles, {webViewStyles, colors} from '../../../style/StyleSheet'; import Text from '../../../components/Text'; import AnchorForCommentsOnly from '../../../components/AnchorForCommentsOnly'; +import ImageModal from '../../../components/ImageModal'; import {getAuthToken} from '../../../lib/API'; import InlineCodeBlock from '../../../components/InlineCodeBlock'; @@ -62,6 +63,9 @@ class ReportActionItemFragment extends React.PureComponent { {children} ), + img: (htmlAttribs, children, convertedCSSStyles, passProps) => ( + + ), }; } diff --git a/src/style/StyleSheet.js b/src/style/StyleSheet.js index d29c55951c5a..b30cfca280b7 100644 --- a/src/style/StyleSheet.js +++ b/src/style/StyleSheet.js @@ -662,6 +662,29 @@ const styles = { backgroundColor: colors.blue, borderRadius: 8, }, + + imageModal: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#000000' + }, + + imageModalImage: { + width: '100%', + height: undefined, + aspectRatio: 1, + }, + + imageModalPlaceholder: { + backgroundColor: '#fff' + }, + + imageModalHeader: { + alignItems: 'flex-end', + height: 35, + backgroundColor: '#fff', + }, }; const baseCodeTagStyles = { From bcfd7ecc7561fbe611d0de881b0ceee9d75c2c0e Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Mon, 12 Oct 2020 13:30:55 -0700 Subject: [PATCH 02/39] add packages pdf viewer image viewer --- android/app/build.gradle | 9 +++++ ios/Podfile.lock | 24 ++++++++++++ package-lock.json | 80 +++++++++++++++++++++++++++++++++++----- package.json | 7 +++- 4 files changed, 110 insertions(+), 10 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index c80021e63819..bd3483200163 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -133,6 +133,15 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + packagingOptions { + pickFirst 'lib/x86/libc++_shared.so' + pickFirst 'lib/x86_64/libjsc.so' + pickFirst 'lib/arm64-v8a/libjsc.so' + pickFirst 'lib/arm64-v8a/libc++_shared.so' + pickFirst 'lib/x86_64/libc++_shared.so' + pickFirst 'lib/armeabi-v7a/libc++_shared.so' + } defaultConfig { applicationId "com.expensify.chat" diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 8c1457e58c0e..4cbd47fd2512 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -244,6 +244,12 @@ PODS: - React - react-native-netinfo (5.9.6): - React + - react-native-pdf (6.2.2): + - React-Core + - react-native-progress-bar-android (1.0.3): + - React + - react-native-progress-view (1.2.1): + - React - react-native-safe-area-context (3.1.4): - React - react-native-webview (10.6.0): @@ -308,6 +314,8 @@ PODS: - React-Core (= 0.63.3) - React-cxxreact (= 0.63.3) - React-jsi (= 0.63.3) + - rn-fetch-blob (0.12.0): + - React-Core - RNCAsyncStorage (1.11.0): - React - RNCPushNotificationIOS (1.5.0): @@ -356,6 +364,9 @@ DEPENDENCIES: - react-native-config (from `../node_modules/react-native-config`) - react-native-image-picker (from `../node_modules/react-native-image-picker`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" + - react-native-pdf (from `../node_modules/react-native-pdf`) + - "react-native-progress-bar-android (from `../node_modules/@react-native-community/progress-bar-android`)" + - "react-native-progress-view (from `../node_modules/@react-native-community/progress-view`)" - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - react-native-webview (from `../node_modules/react-native-webview`) - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) @@ -368,6 +379,7 @@ DEPENDENCIES: - React-RCTText (from `../node_modules/react-native/Libraries/Text`) - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - rn-fetch-blob (from `../node_modules/rn-fetch-blob`) - "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)" - "RNCPushNotificationIOS (from `../node_modules/@react-native-community/push-notification-ios`)" - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) @@ -424,6 +436,12 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-image-picker" react-native-netinfo: :path: "../node_modules/@react-native-community/netinfo" + react-native-pdf: + :path: "../node_modules/react-native-pdf" + react-native-progress-bar-android: + :path: "../node_modules/@react-native-community/progress-bar-android" + react-native-progress-view: + :path: "../node_modules/@react-native-community/progress-view" react-native-safe-area-context: :path: "../node_modules/react-native-safe-area-context" react-native-webview: @@ -448,6 +466,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/Libraries/Vibration" ReactCommon: :path: "../node_modules/react-native/ReactCommon" + rn-fetch-blob: + :path: "../node_modules/rn-fetch-blob" RNCAsyncStorage: :path: "../node_modules/@react-native-community/async-storage" RNCPushNotificationIOS: @@ -485,6 +505,9 @@ SPEC CHECKSUMS: react-native-config: 9a061347e0136fdb32d43a34d60999297d672361 react-native-image-picker: a6c3d644751a388b0fc8b56822ff7cbd398a3008 react-native-netinfo: d2c312fa4b151214e1d5c8456ddb5f28ff24a576 + react-native-pdf: 4b5a9e4465a6a3b399e91dc4838eb44ddf716d1f + react-native-progress-bar-android: d1b109b86c699d36f6a7099dab1a117dc1d66d3c + react-native-progress-view: 0b937488a5730aeec461b00c4eb438e1fe626015 react-native-safe-area-context: 0ed9288ed4409beabb0817b54efc047286fc84da react-native-webview: 797f50d16bb271c4270bc742040a64c79ec7147c React-RCTActionSheet: 53ea72699698b0b47a6421cb1c8b4ab215a774aa @@ -497,6 +520,7 @@ SPEC CHECKSUMS: React-RCTText: 65a6de06a7389098ce24340d1d3556015c38f746 React-RCTVibration: 8e9fb25724a0805107fc1acc9075e26f814df454 ReactCommon: 4167844018c9ed375cc01a843e9ee564399e53c3 + rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba RNCAsyncStorage: db711e29e5e0500d9bd21aa0c2e397efa45302b1 RNCPushNotificationIOS: 8025ff0b610d7b28d29ddc1b619cd55814362e4c Yoga: 7d13633d129fd179e01b8953d38d47be90db185a diff --git a/package-lock.json b/package-lock.json index 2387fb9dbf72..57683e55d5c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1822,6 +1822,16 @@ "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-5.9.6.tgz", "integrity": "sha512-cEkA1Apg8+VjnDdeDZRHI+2RqouiPKgYnewouRkvF4ettH9ZS4Cmi/nANQKIpIu2L+czboxM3fCZ44nc7IM9VQ==" }, + "@react-native-community/progress-bar-android": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@react-native-community/progress-bar-android/-/progress-bar-android-1.0.3.tgz", + "integrity": "sha512-Xy+nTCibCLQ6Jy0YNlVwrO+m/j2el6PN8nVQcdgVqp+Jm1XTguq7xjxUV5spzYmVDIVx+zW7WKfkeSbhUk91Tg==" + }, + "@react-native-community/progress-view": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@react-native-community/progress-view/-/progress-view-1.2.1.tgz", + "integrity": "sha512-x8YqINP5TsdKKTY3LV4QJT2gmFsRrrIbI3uJeq7T/np9jHtZLxJ85EZdaLyZGy/m0YZjP0fWxU1jLPgUtsYW4w==" + }, "@react-native-community/push-notification-ios": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@react-native-community/push-notification-ios/-/push-notification-ios-1.5.0.tgz", @@ -3553,6 +3563,11 @@ } } }, + "base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=" + }, "base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", @@ -5152,6 +5167,11 @@ "randomfill": "^1.0.3" } }, + "crypto-js": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", + "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + }, "crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -5328,11 +5348,6 @@ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.9.1.tgz", "integrity": "sha512-01NCTBg8cuMJG1OQc6PR7T66+AFYiPwgDvdJmvJBn29NGzIG+DIFxPLNjHzwz3cpFIvG+NcwIjP9hSaPVoOaDg==" }, - "debounce": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz", - "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==" - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -14807,16 +14822,40 @@ "resolved": "https://registry.npmjs.org/react-native-config/-/react-native-config-1.3.3.tgz", "integrity": "sha512-uc84IFVLqu2dC7Zutg3OlK6RZYvJlGbIdi0v1ZxjUhU379Nz9IkZR1t8J1L5QSnGV8885mtwWDPJRdQyVNFsAw==" }, + "react-native-image-pan-zoom": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz", + "integrity": "sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q==" + }, "react-native-image-picker": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-2.3.3.tgz", "integrity": "sha512-i/7JDnxKkUGSbFY2i7YqFNn3ncJm1YlcrPKXrXmJ/YUElz8tHkuwknuqBd9QCJivMfHX41cmq4XvdBDwIOtO+A==" }, + "react-native-image-zoom-viewer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/react-native-image-zoom-viewer/-/react-native-image-zoom-viewer-3.0.1.tgz", + "integrity": "sha512-la6s5DNSuq4GCRLsi5CZ29FPjgTpdCuGIRdO5T9rUrAtxrlpBPhhSnHrbmPVxsdtOUvxHacTh2Gfa9+RraMZQA==", + "requires": { + "react-native-image-pan-zoom": "^2.1.12" + } + }, "react-native-keyboard-spacer": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/react-native-keyboard-spacer/-/react-native-keyboard-spacer-0.4.1.tgz", "integrity": "sha1-RvGKMgQyCYol6p+on1FD3SVNMy0=" }, + "react-native-pdf": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.2.2.tgz", + "integrity": "sha512-ylRqbjf9BDSZpOV9eJpASZ+0FWZwEhEGyWJVhA6epr+HY8G2EOThr1HNXbf3nTIwL/t+Esfw4b60+w+2k614Pg==", + "requires": { + "@react-native-community/progress-bar-android": "^1.0.3", + "@react-native-community/progress-view": "^1.0.3", + "crypto-js": "^3.2.0", + "prop-types": "^15.7.2" + } + }, "react-native-render-html": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/react-native-render-html/-/react-native-render-html-4.2.3.tgz", @@ -14886,13 +14925,12 @@ } }, "react-native-web": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.13.5.tgz", - "integrity": "sha512-Z4hiqKZ0bFFVMlsc/6gQMkIiGYPrY8bH6SFpLrLljDGLousNZjD+H4hV7QQz72smo8786994UJEBC0bU+0kAMw==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.14.0.tgz", + "integrity": "sha512-jz7li7lgjZ2rJeH9HLBpAtLDzD1LHfK7TcmsrBes3za4hl5+Qn3UtlKbhiT/lcNGd2IDCP13Gj9DEWTRg/auNA==", "requires": { "array-find-index": "^1.0.2", "create-react-class": "^15.6.2", - "debounce": "^1.2.0", "deep-assign": "^3.0.0", "fbjs": "^1.0.0", "hyphenate-style-name": "^1.0.3", @@ -15755,6 +15793,30 @@ "inherits": "^2.0.1" } }, + "rn-fetch-blob": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/rn-fetch-blob/-/rn-fetch-blob-0.12.0.tgz", + "integrity": "sha512-+QnR7AsJ14zqpVVUbzbtAjq0iI8c9tCg49tIoKO2ezjzRunN7YL6zFSFSWZm6d+mE/l9r+OeDM3jmb2tBb2WbA==", + "requires": { + "base-64": "0.1.0", + "glob": "7.0.6" + }, + "dependencies": { + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, "roarr": { "version": "2.15.3", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.3.tgz", diff --git a/package.json b/package.json index ab95dfba6e7d..0fbd70c64d26 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "dependencies": { "@react-native-community/async-storage": "^1.11.0", "@react-native-community/netinfo": "^5.9.6", + "@react-native-community/progress-bar-android": "^1.0.3", + "@react-native-community/progress-view": "^1.2.1", "@react-native-community/push-notification-ios": "^1.5.0", "babel-plugin-transform-remove-console": "^6.9.4", "dotenv": "^8.2.0", @@ -46,14 +48,17 @@ "react-native": "0.63.3", "react-native-config": "^1.3.3", "react-native-image-picker": "^2.3.3", + "react-native-image-zoom-viewer": "^3.0.1", "react-native-keyboard-spacer": "^0.4.1", + "react-native-pdf": "^6.2.2", "react-native-render-html": "^4.2.3", "react-native-safe-area-context": "^3.1.4", - "react-native-web": "^0.13.5", + "react-native-web": "^0.14.0", "react-native-webview": "^10.6.0", "react-router-dom": "^5.2.0", "react-router-native": "^5.2.0", "react-web-config": "^1.0.0", + "rn-fetch-blob": "^0.12.0", "save": "^2.4.0", "underscore": "^1.10.2" }, From 3a34acf97f5b58c9a08a0c41b0976643f69e6a5b Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Mon, 12 Oct 2020 13:31:27 -0700 Subject: [PATCH 03/39] update native components for image viewers --- src/components/ImageModal/index.js | 33 +++++---- src/components/ImageModal/index.native.js | 85 +++++++++++++++++++++++ 2 files changed, 105 insertions(+), 13 deletions(-) create mode 100644 src/components/ImageModal/index.native.js diff --git a/src/components/ImageModal/index.js b/src/components/ImageModal/index.js index 11002544528d..22550339f0d3 100644 --- a/src/components/ImageModal/index.js +++ b/src/components/ImageModal/index.js @@ -1,19 +1,22 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, Image, Modal, TouchableOpacity, Text } from 'react-native'; +import { View, Image, Modal, TouchableOpacity, Text, Dimensions } from 'react-native'; import styles from '../../style/StyleSheet'; -import ImageView from "react-native-image-viewing"; +import _ from 'underscore'; /** * Text based component that is passed a URL to open onPress */ const propTypes = { - // The preview of the image - previewSrc: PropTypes.string, + // Object array of images + images: PropTypes.array, - // The full sized image - src: PropTypes.string, + // URL to image preview + previewSrcURL: PropTypes.string, + + // URL to full-sized image + srcURL: PropTypes.string, // Any additional styles to apply // eslint-disable-next-line react/forbid-prop-types @@ -21,8 +24,8 @@ const propTypes = { }; const defaultProps = { - previewSrc: '', - src: '', + previewSrcURL: '', + srcURL: '', style: {}, }; @@ -41,9 +44,9 @@ class ImageModal extends React.Component { render() { return ( - - this.setModalVisiblity(true)}> - + <> + this.setModalVisiblity(true)} > + this.setModalVisiblity(false)} visible={this.state.visible} > + this.setModalVisiblity(false)}>X + - + + - + + ); } diff --git a/src/components/ImageModal/index.native.js b/src/components/ImageModal/index.native.js new file mode 100644 index 000000000000..0366b38b0686 --- /dev/null +++ b/src/components/ImageModal/index.native.js @@ -0,0 +1,85 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { View, Image, Modal, TouchableOpacity, Text, Dimensions } from 'react-native'; +import styles from '../../style/StyleSheet'; +import ImageViewer from 'react-native-image-zoom-viewer'; +import _ from 'underscore'; +import Pdf from 'react-native-pdf'; +import Str from '../../lib/Str'; + +/** + * Text based component that is passed a URL to open onPress + */ + +const propTypes = { + // Object array of images + images: PropTypes.array, + + // URL to image preview + previewSrcURL: PropTypes.string, + + // URL to full-sized image + srcURL: PropTypes.string, + + // Any additional styles to apply + // eslint-disable-next-line react/forbid-prop-types + style: PropTypes.any, +}; + +const defaultProps = { + previewSrcURL: '', + srcURL: '', + style: {}, +}; + +class ImageModal extends React.Component { + constructor(props) { + super(props); + + this.state = { + visible: false, + } + } + + setModalVisiblity(visibility) { + this.setState({ visible: visibility }); + } + + render() { + let imageView; + + if (Str.isPDF(this.props.srcURL)) { + imageView = ; + } else { + imageView= this.setModalVisiblity(false)} />; + } + + return ( + <> + this.setModalVisiblity(true)} > + + + + this.setModalVisiblity(false)} + visible={this.state.visible} + > + + + this.setModalVisiblity(false)} style={{color: 'white'}}>X + + {imageView} + + + + ); + } + +} + +ImageModal.propTypes = propTypes; +ImageModal.defaultProps = defaultProps; +ImageModal.displayName = 'ImageModal'; + +export default ImageModal; From 0a5de9e5875f8ee4d004884dd2396a00dbc08120 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Mon, 12 Oct 2020 13:31:48 -0700 Subject: [PATCH 04/39] add utility function for checking pdf --- src/lib/Str.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/lib/Str.js b/src/lib/Str.js index 83ddfb02bae0..72def0cbf3db 100644 --- a/src/lib/Str.js +++ b/src/lib/Str.js @@ -94,7 +94,17 @@ const Str = { */ normalizeUrl(url) { return (typeof url === 'string' && url.startsWith('/')) ? url : `/${url}`; - } + }, + + /** + * Takes in a URL and checks if the file extension is PDF + * + * @param {mixed} url The URL to be checked + * @returns {Boolean} Whether file path is PDF or not + */ + isPDF(url) { + return _.first(_.last(url.split('.')).split('?')) === 'pdf'; + }, }; export default Str; From 66848fd75b5be0777d510d634e9a5538553b72b4 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Mon, 12 Oct 2020 13:32:54 -0700 Subject: [PATCH 05/39] update css and props passed in --- .../home/report/ReportActionItemFragment.js | 5 ++-- src/style/StyleSheet.js | 23 +++++++++++++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/page/home/report/ReportActionItemFragment.js b/src/page/home/report/ReportActionItemFragment.js index bcda1bd7c803..f5c368b3b249 100644 --- a/src/page/home/report/ReportActionItemFragment.js +++ b/src/page/home/report/ReportActionItemFragment.js @@ -64,7 +64,7 @@ class ReportActionItemFragment extends React.PureComponent { ), img: (htmlAttribs, children, convertedCSSStyles, passProps) => ( - + ), }; } @@ -81,7 +81,8 @@ class ReportActionItemFragment extends React.PureComponent { // We only want to attach auth tokens to images that come from Expensify attachments if (htmlNode.name === 'img' && htmlNode.attribs['data-expensify-source']) { - htmlNode.attribs.src = `${node.attribs.src}?authToken=${getAuthToken()}`; + htmlNode.attribs.preview = `${node.attribs.src}?authToken=${getAuthToken()}`; + htmlNode.attribs.src = htmlNode.attribs['data-expensify-source']; return htmlNode; } } diff --git a/src/style/StyleSheet.js b/src/style/StyleSheet.js index b30cfca280b7..8b00c9ad86da 100644 --- a/src/style/StyleSheet.js +++ b/src/style/StyleSheet.js @@ -1,4 +1,5 @@ // We place items a percentage to the safe area on the top or bottom of the screen +import {Dimensions} from 'react-native'; import fontFamily from './fontFamily'; import italic from './italic'; @@ -665,9 +666,9 @@ const styles = { imageModal: { flex: 1, - alignItems: 'center', + alignItems: 'center', justifyContent: 'center', - backgroundColor: '#000000' + backgroundColor: colors.black, }, imageModalImage: { @@ -676,14 +677,26 @@ const styles = { aspectRatio: 1, }, + imageModalPDF: { + flex: 1, + width: Dimensions.get('window').width, + height: Dimensions.get('window').height, + }, + imageModalPlaceholder: { - backgroundColor: '#fff' + alignItems: 'center', + justifyContent: 'center', + backgroundColor: colors.componentBG, }, imageModalHeader: { alignItems: 'flex-end', - height: 35, - backgroundColor: '#fff', + justifyContent: 'flex-end', + height: 65, + backgroundColor: colors.heading, + paddingRight: 25, + paddingBottom: 10, + overflow: 'hidden', }, }; From 2628bc31984a3259871202e89deca77cd6362994 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Mon, 26 Oct 2020 13:06:12 -0700 Subject: [PATCH 06/39] resolve conflicts and change image viewer --- .../java/com/expensify/chat/MainActivity.java | 13 +++- package-lock.json | 8 --- package.json | 2 +- src/components/ImageModal/index.js | 29 +++++--- src/components/ImageModal/index.native.js | 68 +++++++++++++------ .../home/report/ReportActionItemFragment.js | 2 +- src/styles/StyleSheet.js | 6 +- 7 files changed, 82 insertions(+), 46 deletions(-) diff --git a/android/app/src/main/java/com/expensify/chat/MainActivity.java b/android/app/src/main/java/com/expensify/chat/MainActivity.java index 3e5f8b2af479..818bdc96e476 100644 --- a/android/app/src/main/java/com/expensify/chat/MainActivity.java +++ b/android/app/src/main/java/com/expensify/chat/MainActivity.java @@ -1,7 +1,9 @@ package com.expensify.chat; import com.facebook.react.ReactActivity; - +import com.facebook.react.ReactActivityDelegate; +import com.facebook.react.ReactRootView; +import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView; public class MainActivity extends ReactActivity { /** @@ -12,4 +14,13 @@ public class MainActivity extends ReactActivity { protected String getMainComponentName() { return "ReactNativeChat"; } + @Override + protected ReactActivityDelegate createReactActivityDelegate() { + return new ReactActivityDelegate(this, getMainComponentName()) { + @Override + protected ReactRootView createRootView() { + return new RNGestureHandlerEnabledRootView(MainActivity.this); + } + }; + } } diff --git a/package-lock.json b/package-lock.json index 71e7c35b7230..2eb02d9a8cf8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14891,14 +14891,6 @@ "resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-2.3.3.tgz", "integrity": "sha512-i/7JDnxKkUGSbFY2i7YqFNn3ncJm1YlcrPKXrXmJ/YUElz8tHkuwknuqBd9QCJivMfHX41cmq4XvdBDwIOtO+A==" }, - "react-native-image-zoom-viewer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/react-native-image-zoom-viewer/-/react-native-image-zoom-viewer-3.0.1.tgz", - "integrity": "sha512-la6s5DNSuq4GCRLsi5CZ29FPjgTpdCuGIRdO5T9rUrAtxrlpBPhhSnHrbmPVxsdtOUvxHacTh2Gfa9+RraMZQA==", - "requires": { - "react-native-image-pan-zoom": "^2.1.12" - } - }, "react-native-keyboard-spacer": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/react-native-keyboard-spacer/-/react-native-keyboard-spacer-0.4.1.tgz", diff --git a/package.json b/package.json index a1caf2b4d4af..cdbbfbe37b89 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,8 @@ "@react-native-firebase/analytics": "^7.6.7", "@react-native-firebase/app": "^8.4.5", "@react-native-firebase/crashlytics": "^8.4.9", + "react-native-image-pan-zoom": "^2.1.12", "react-native-image-picker": "^2.3.3", - "react-native-image-zoom-viewer": "^3.0.1", "react-native-keyboard-spacer": "^0.4.1", "react-native-pdf": "^6.2.2", "react-native-render-html": "^4.2.3", diff --git a/src/components/ImageModal/index.js b/src/components/ImageModal/index.js index 22550339f0d3..24996ff9209d 100644 --- a/src/components/ImageModal/index.js +++ b/src/components/ImageModal/index.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, Image, Modal, TouchableOpacity, Text, Dimensions } from 'react-native'; +import { View, Image, Modal, TouchableOpacity, Text } from 'react-native'; import styles from '../../style/StyleSheet'; import _ from 'underscore'; @@ -9,9 +9,6 @@ import _ from 'underscore'; */ const propTypes = { - // Object array of images - images: PropTypes.array, - // URL to image preview previewSrcURL: PropTypes.string, @@ -38,11 +35,21 @@ class ImageModal extends React.Component { } } + componentWillMount() { + Image.getSize(this.props.srcURL, (width, height) => { + + }) + } + setModalVisiblity(visibility) { this.setState({ visible: visibility }); } render() { + + // Hack for achieving height: auto + + return ( <> this.setModalVisiblity(true)} > @@ -53,15 +60,15 @@ class ImageModal extends React.Component { animationType={"slide"} onRequestClose={() => this.setModalVisiblity(false)} visible={this.state.visible} + transparent={true} > + + + this.setModalVisiblity(false)}>X + - - this.setModalVisiblity(false)}>X - - - - - + + diff --git a/src/components/ImageModal/index.native.js b/src/components/ImageModal/index.native.js index 0366b38b0686..8e70da8a3ee4 100644 --- a/src/components/ImageModal/index.native.js +++ b/src/components/ImageModal/index.native.js @@ -1,8 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, Image, Modal, TouchableOpacity, Text, Dimensions } from 'react-native'; -import styles from '../../style/StyleSheet'; -import ImageViewer from 'react-native-image-zoom-viewer'; +import { View, Image, Modal, TouchableOpacity, Text, Dimensions, StatusBar } from 'react-native'; +import {SafeAreaInsetsContext, SafeAreaProvider} from 'react-native-safe-area-context'; +import styles, {getSafeAreaPadding} from '../../style/StyleSheet'; +import ImageZoom from 'react-native-image-pan-zoom'; import _ from 'underscore'; import Pdf from 'react-native-pdf'; import Str from '../../lib/Str'; @@ -38,6 +39,8 @@ class ImageModal extends React.Component { this.state = { visible: false, + width: 200, + height: 200, } } @@ -45,37 +48,60 @@ class ImageModal extends React.Component { this.setState({ visible: visibility }); } + componentDidMount() { + Image.getSize(this.props.srcURL, (width, height) => {this.setState({width, height})}); + } + render() { let imageView; if (Str.isPDF(this.props.srcURL)) { imageView = ; } else { - imageView= this.setModalVisiblity(false)} />; + imageView = + + ; + + + // this.setModalVisiblity(false)} />; } return ( <> - this.setModalVisiblity(true)} > - - - - this.setModalVisiblity(false)} - visible={this.state.visible} - > - - - this.setModalVisiblity(false)} style={{color: 'white'}}>X - - {imageView} - - + this.setModalVisiblity(true)} > + + + + + + { insets => ( + + this.setModalVisiblity(false)} + visible={this.state.visible} + > + + + Attachment + this.setModalVisiblity(false)} style={{color: 'white'}}>X + + {imageView} + + + + )} + + ); } - } ImageModal.propTypes = propTypes; diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 1b7c76468676..863d53325c5c 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -82,7 +82,7 @@ class ReportActionItemFragment extends React.PureComponent { // We only want to attach auth tokens to images that come from Expensify attachments if (htmlNode.name === 'img' && htmlNode.attribs['data-expensify-source']) { htmlNode.attribs.preview = `${node.attribs.src}?authToken=${getAuthToken()}`; - htmlNode.attribs.src = htmlNode.attribs['data-expensify-source']; + htmlNode.attribs.src = htmlNode.attribs['data-expensify-source'] + `?authToken=${getAuthToken()}`; return htmlNode; } } diff --git a/src/styles/StyleSheet.js b/src/styles/StyleSheet.js index c798772213e9..f6178ad137fb 100644 --- a/src/styles/StyleSheet.js +++ b/src/styles/StyleSheet.js @@ -713,9 +713,9 @@ const styles = { }, imageModalImage: { - width: '100%', - height: undefined, - aspectRatio: 1, + flex: 1, + width: 200, + height: 200, }, imageModalPDF: { From e33f16f877d23aee8aa62cb9a5d97fd7870f4976 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Tue, 27 Oct 2020 12:40:39 -0700 Subject: [PATCH 07/39] update broken filepaths --- src/components/ImageModal/index.js | 2 +- src/components/ImageModal/index.native.js | 4 ++-- src/pages/home/report/ReportActionItemFragment.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ImageModal/index.js b/src/components/ImageModal/index.js index 24996ff9209d..2196a80e83d6 100644 --- a/src/components/ImageModal/index.js +++ b/src/components/ImageModal/index.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { View, Image, Modal, TouchableOpacity, Text } from 'react-native'; -import styles from '../../style/StyleSheet'; +import styles from '../../styles/StyleSheet'; import _ from 'underscore'; /** diff --git a/src/components/ImageModal/index.native.js b/src/components/ImageModal/index.native.js index 8e70da8a3ee4..4d3e14eb2f69 100644 --- a/src/components/ImageModal/index.native.js +++ b/src/components/ImageModal/index.native.js @@ -2,11 +2,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import { View, Image, Modal, TouchableOpacity, Text, Dimensions, StatusBar } from 'react-native'; import {SafeAreaInsetsContext, SafeAreaProvider} from 'react-native-safe-area-context'; -import styles, {getSafeAreaPadding} from '../../style/StyleSheet'; +import styles, {getSafeAreaPadding} from '../../styles/StyleSheet'; import ImageZoom from 'react-native-image-pan-zoom'; import _ from 'underscore'; import Pdf from 'react-native-pdf'; -import Str from '../../lib/Str'; +import Str from '../../libs/Str'; /** * Text based component that is passed a URL to open onPress diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 863d53325c5c..0be17b698fb7 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -10,7 +10,7 @@ import styles, {webViewStyles, colors} from '../../../styles/StyleSheet'; import Text from '../../../components/Text'; import AnchorForCommentsOnly from '../../../components/AnchorForCommentsOnly'; import ImageModal from '../../../components/ImageModal'; -import {getAuthToken} from '../../../lib/API'; +import {getAuthToken} from '../../../libs/API'; import InlineCodeBlock from '../../../components/InlineCodeBlock'; const propTypes = { From 32389dcbd57cdc9a5d6af6269d1054c40096f356 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Tue, 27 Oct 2020 13:10:19 -0700 Subject: [PATCH 08/39] update image size and remove gesture handler imports --- .../main/java/com/expensify/chat/MainActivity.java | 13 +------------ src/components/ImageModal/index.native.js | 13 +++++++++---- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/android/app/src/main/java/com/expensify/chat/MainActivity.java b/android/app/src/main/java/com/expensify/chat/MainActivity.java index 818bdc96e476..9375274205e3 100644 --- a/android/app/src/main/java/com/expensify/chat/MainActivity.java +++ b/android/app/src/main/java/com/expensify/chat/MainActivity.java @@ -1,9 +1,6 @@ package com.expensify.chat; import com.facebook.react.ReactActivity; -import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.ReactRootView; -import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView; public class MainActivity extends ReactActivity { /** @@ -14,13 +11,5 @@ public class MainActivity extends ReactActivity { protected String getMainComponentName() { return "ReactNativeChat"; } - @Override - protected ReactActivityDelegate createReactActivityDelegate() { - return new ReactActivityDelegate(this, getMainComponentName()) { - @Override - protected ReactRootView createRootView() { - return new RNGestureHandlerEnabledRootView(MainActivity.this); - } - }; - } + } diff --git a/src/components/ImageModal/index.native.js b/src/components/ImageModal/index.native.js index 4d3e14eb2f69..56c8ab3968e2 100644 --- a/src/components/ImageModal/index.native.js +++ b/src/components/ImageModal/index.native.js @@ -49,7 +49,12 @@ class ImageModal extends React.Component { } componentDidMount() { - Image.getSize(this.props.srcURL, (width, height) => {this.setState({width, height})}); + Image.getSize(this.props.srcURL, (width, height) => { + const screenWidth = Dimensions.get('window').width; + const scaleFactor = width / screenWidth; + const imageHeight = height / scaleFactor; + this.setState({imgWidth: screenWidth, imgHeight: imageHeight}) + }); } render() { @@ -60,10 +65,10 @@ class ImageModal extends React.Component { } else { imageView = + imageWidth={this.state.imgWidth} + imageHeight={this.state.imageHeight}> ; From ec2fdcdd0542ada4558ad1a82a3032511b1e90a6 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Tue, 27 Oct 2020 13:10:49 -0700 Subject: [PATCH 09/39] remove commented out imageviewer --- src/components/ImageModal/index.native.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/ImageModal/index.native.js b/src/components/ImageModal/index.native.js index 56c8ab3968e2..a1611e8c44ef 100644 --- a/src/components/ImageModal/index.native.js +++ b/src/components/ImageModal/index.native.js @@ -72,9 +72,6 @@ class ImageModal extends React.Component { source={{ uri:this.props.srcURL }} /> ; - - - // this.setModalVisiblity(false)} />; } return ( From 28288b4d110e942ceddd4d0bd693e16930b0a9a2 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 29 Oct 2020 13:15:48 -0700 Subject: [PATCH 10/39] styling for web and mobile! --- package-lock.json | 10 +-- src/components/ImageModal/index.js | 75 +++++++++++++++++----- src/components/ImageModal/index.native.js | 76 ++++++++++++++--------- src/styles/StyleSheet.js | 25 ++++++-- 4 files changed, 132 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52ca6e740daa..d86bada7b544 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14881,16 +14881,16 @@ "resolved": "https://registry.npmjs.org/react-native-config/-/react-native-config-1.3.3.tgz", "integrity": "sha512-uc84IFVLqu2dC7Zutg3OlK6RZYvJlGbIdi0v1ZxjUhU379Nz9IkZR1t8J1L5QSnGV8885mtwWDPJRdQyVNFsAw==" }, - "react-native-image-pan-zoom": { - "version": "2.1.12", - "resolved": "https://registry.npmjs.org/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz", - "integrity": "sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q==" - }, "react-native-document-picker": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-4.0.0.tgz", "integrity": "sha512-tjIOBBcyjv4j5E1MDL2OvEKNpXxQybLNkjjfpTyDUzek7grZ5eOvSlp6i/Y3EfuIGLByeaw++9R1SZtOij6R7w==" }, + "react-native-image-pan-zoom": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz", + "integrity": "sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q==" + }, "react-native-image-picker": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-2.3.3.tgz", diff --git a/src/components/ImageModal/index.js b/src/components/ImageModal/index.js index 2196a80e83d6..321a752bed16 100644 --- a/src/components/ImageModal/index.js +++ b/src/components/ImageModal/index.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, Image, Modal, TouchableOpacity, Text } from 'react-native'; +import { View, Image, Modal, TouchableOpacity, Text, Dimensions, ImageBackground } from 'react-native'; +import exitIcon from '../../../assets/images/icon-x.png'; import styles from '../../styles/StyleSheet'; import _ from 'underscore'; @@ -32,13 +33,13 @@ class ImageModal extends React.Component { this.state = { visible: false, + imgWidth: 200, + imgHeight: 200, } } componentWillMount() { - Image.getSize(this.props.srcURL, (width, height) => { - - }) + Image.getSize(this.props.srcURL, (width, height) => {this.setState({imgWidth: width, imgHeight: height})}); } setModalVisiblity(visibility) { @@ -47,9 +48,6 @@ class ImageModal extends React.Component { render() { - // Hack for achieving height: auto - - return ( <> this.setModalVisiblity(true)} > @@ -57,22 +55,69 @@ class ImageModal extends React.Component { this.setModalVisiblity(false)} visible={this.state.visible} transparent={true} > - - - this.setModalVisiblity(false)}>X - - - - + + + + + + Attachment + + + this.setModalVisiblity(false)} + style={[styles.touchableButtonImage, styles.mr0]} + > + + + + + + + + + + + + ); diff --git a/src/components/ImageModal/index.native.js b/src/components/ImageModal/index.native.js index a1611e8c44ef..b397b8b6a159 100644 --- a/src/components/ImageModal/index.native.js +++ b/src/components/ImageModal/index.native.js @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, Image, Modal, TouchableOpacity, Text, Dimensions, StatusBar } from 'react-native'; -import {SafeAreaInsetsContext, SafeAreaProvider} from 'react-native-safe-area-context'; -import styles, {getSafeAreaPadding} from '../../styles/StyleSheet'; +import { View, Image, Modal, TouchableOpacity, Text, Dimensions } from 'react-native'; +import styles from '../../styles/StyleSheet'; +import exitIcon from '../../../assets/images/icon-x.png'; import ImageZoom from 'react-native-image-pan-zoom'; import _ from 'underscore'; import Pdf from 'react-native-pdf'; @@ -39,8 +39,8 @@ class ImageModal extends React.Component { this.state = { visible: false, - width: 200, - height: 200, + imgWidth: 200, + imgHeight: 200, } } @@ -63,13 +63,14 @@ class ImageModal extends React.Component { if (Str.isPDF(this.props.srcURL)) { imageView = ; } else { - imageView = + imageHeight={this.state.imgHeight}> ; } @@ -80,27 +81,46 @@ class ImageModal extends React.Component { - - - { insets => ( - - this.setModalVisiblity(false)} - visible={this.state.visible} - > - - + + + this.setModalVisiblity(false)} + visible={this.state.visible} + transparent={true} + > + + + + Attachment - this.setModalVisiblity(false)} style={{color: 'white'}}>X - {imageView} - - + + this.setModalVisiblity(false)} + style={[styles.touchableButtonImage, styles.mr0]} + > + + + + + - )} - - + {imageView} + + + ); } diff --git a/src/styles/StyleSheet.js b/src/styles/StyleSheet.js index 7a04989359a5..6a4252dec1e9 100644 --- a/src/styles/StyleSheet.js +++ b/src/styles/StyleSheet.js @@ -158,6 +158,10 @@ const styles = { backgroundColor: 'pink', }, + overflowHidden: { + overflow: 'hidden', + }, + h4: { fontFamily: fontFamily.GTA_BOLD, fontWeight: '700', @@ -545,6 +549,20 @@ const styles = { paddingRight: 20, }, + imageModalContentHeader: { + borderWidth: 1, + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + overflow: 'hidden', + borderColor: colors.border, + height: 87, + justifyContent: 'center', + display: 'flex', + paddingLeft: 20, + paddingRight: 20, + backgroundColor: colors.componentBG, + }, + appContentHeaderTitle: { alignItems: 'center', flexDirection: 'row', @@ -860,16 +878,11 @@ const styles = { backgroundColor: colors.black, }, - imageModalImage: { - flex: 1, - width: 200, - height: 200, - }, - imageModalPDF: { flex: 1, width: Dimensions.get('window').width, height: Dimensions.get('window').height, + backgroundColor: colors.componentBG, }, imageModalPlaceholder: { From 980dd8d50518a9b01f07f98114583a1c0fa80099 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Fri, 30 Oct 2020 13:51:53 -0700 Subject: [PATCH 11/39] working and styled modals! --- .../java/com/expensify/chat/MainActivity.java | 2 +- ios/Podfile.lock | 6 +- package-lock.json | 17 +++-- package.json | 5 +- src/components/ImageModal/index.js | 73 +++++++++++-------- src/components/ImageModal/index.native.js | 27 +++++-- src/styles/StyleSheet.js | 54 ++++++++------ webpack.common.js | 11 +++ 8 files changed, 121 insertions(+), 74 deletions(-) diff --git a/android/app/src/main/java/com/expensify/chat/MainActivity.java b/android/app/src/main/java/com/expensify/chat/MainActivity.java index 9375274205e3..3e5f8b2af479 100644 --- a/android/app/src/main/java/com/expensify/chat/MainActivity.java +++ b/android/app/src/main/java/com/expensify/chat/MainActivity.java @@ -1,6 +1,7 @@ package com.expensify.chat; import com.facebook.react.ReactActivity; + public class MainActivity extends ReactActivity { /** @@ -11,5 +12,4 @@ public class MainActivity extends ReactActivity { protected String getMainComponentName() { return "ReactNativeChat"; } - } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3fc4b32b4389..b3bcada5c194 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -338,8 +338,8 @@ PODS: - React - react-native-safe-area-context (3.1.4): - React - - react-native-webview (10.6.0): - - React + - react-native-webview (10.10.0): + - React-Core - React-RCTActionSheet (0.63.3): - React-Core/RCTActionSheetHeaders (= 0.63.3) - React-RCTAnimation (0.63.3): @@ -649,7 +649,7 @@ SPEC CHECKSUMS: react-native-progress-bar-android: d1b109b86c699d36f6a7099dab1a117dc1d66d3c react-native-progress-view: 0b937488a5730aeec461b00c4eb438e1fe626015 react-native-safe-area-context: 0ed9288ed4409beabb0817b54efc047286fc84da - react-native-webview: 797f50d16bb271c4270bc742040a64c79ec7147c + react-native-webview: 2e330b109bfd610e9818bf7865d1979f898960a7 React-RCTActionSheet: 53ea72699698b0b47a6421cb1c8b4ab215a774aa React-RCTAnimation: 1befece0b5183c22ae01b966f5583f42e69a83c2 React-RCTBlob: 0b284339cbe4b15705a05e2313a51c6d8b51fa40 diff --git a/package-lock.json b/package-lock.json index d86bada7b544..acd6382dcaa9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14453,8 +14453,7 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "query-string": { "version": "4.3.4", @@ -14996,10 +14995,18 @@ "react-timer-mixin": "^0.13.4" } }, + "react-native-web-webview": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/react-native-web-webview/-/react-native-web-webview-1.0.2.tgz", + "integrity": "sha512-oNAYNuqUqeqTuAAdIejzDqvUtYA+k5lrvhUYmASdUznZNmyIaoQFA6OKoA4K9F3wdMvark42vUXkUWIp875ewg==", + "requires": { + "qs": "^6.5.1" + } + }, "react-native-webview": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-10.6.0.tgz", - "integrity": "sha512-y1/zYx9LuoiPVSM3oxf77WQjRebaYG22Kya4lr2tmIESaR1GkKzwKSpKPyEZYfu8U/WNRSrrS/i78o+R1aQRTA==", + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-10.10.0.tgz", + "integrity": "sha512-T0AnZ0LVhaFBqZpl5attDDYo83zqdFRsFVINbrgHaIm6w5r0d/QK/dJRgXRNyFhn1fSONhe0ejdcnCYCT73B6g==", "requires": { "escape-string-regexp": "2.0.0", "invariant": "2.2.4" diff --git a/package.json b/package.json index 2d9006eb31fc..bac30ffc346a 100644 --- a/package.json +++ b/package.json @@ -50,15 +50,16 @@ "react-dom": "^16.13.1", "react-native": "0.63.3", "react-native-config": "^1.3.3", - "react-native-image-pan-zoom": "^2.1.12", "react-native-document-picker": "^4.0.0", + "react-native-image-pan-zoom": "^2.1.12", "react-native-image-picker": "^2.3.3", "react-native-keyboard-spacer": "^0.4.1", "react-native-pdf": "^6.2.2", "react-native-render-html": "^4.2.3", "react-native-safe-area-context": "^3.1.4", "react-native-web": "^0.14.0", - "react-native-webview": "^10.6.0", + "react-native-web-webview": "^1.0.2", + "react-native-webview": "^10.10.0", "react-router-dom": "^5.2.0", "react-router-native": "^5.2.0", "react-web-config": "^1.0.0", diff --git a/src/components/ImageModal/index.js b/src/components/ImageModal/index.js index 321a752bed16..f3fd14a54668 100644 --- a/src/components/ImageModal/index.js +++ b/src/components/ImageModal/index.js @@ -1,9 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, Image, Modal, TouchableOpacity, Text, Dimensions, ImageBackground } from 'react-native'; +import { View, Image, Modal, TouchableOpacity, Text, Dimensions, TouchableWithoutFeedback } from 'react-native'; import exitIcon from '../../../assets/images/icon-x.png'; import styles from '../../styles/StyleSheet'; import _ from 'underscore'; +import { WebView } from 'react-native-webview'; +import Str from '../../libs/Str'; /** * Text based component that is passed a URL to open onPress @@ -39,7 +41,19 @@ class ImageModal extends React.Component { } componentWillMount() { - Image.getSize(this.props.srcURL, (width, height) => {this.setState({imgWidth: width, imgHeight: height})}); + Image.getSize(this.props.srcURL, (width, height) => { + const screenWidth = Dimensions.get('window').width * 0.6; + const scaleFactor = width / screenWidth; + const imageHeight = height / scaleFactor; + this.setState({imgWidth: screenWidth, imgHeight: imageHeight}) + }); + + Image.getSize(this.props.previewSrcURL, (width, height) => { + const screenWidth = 300; + const scaleFactor = width / screenWidth; + const imageHeight = height / scaleFactor; + this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}) + }); } setModalVisiblity(visibility) { @@ -47,27 +61,38 @@ class ImageModal extends React.Component { } render() { + let imageView; + + if (Str.isPDF(this.props.srcURL)) { + const pdfViewUrl = `http://mozilla.github.com/pdf.js/web/viewer.html?file=${encodeURIComponent(this.props.srcURL)}`; + imageView = ; + } else { + imageView = + + ; + } return ( <> this.setModalVisiblity(true)} > - + {this.props.info} + + this.setModalVisiblity(false)} visible={this.state.visible} transparent={true} > - - - + + this.setModalVisiblity(false)}> + + + - - - - + + {imageView} - - + + diff --git a/src/components/ImageModal/index.native.js b/src/components/ImageModal/index.native.js index b397b8b6a159..1dd73a2aed4d 100644 --- a/src/components/ImageModal/index.native.js +++ b/src/components/ImageModal/index.native.js @@ -49,25 +49,37 @@ class ImageModal extends React.Component { } componentDidMount() { + // Generate image size for modal Image.getSize(this.props.srcURL, (width, height) => { const screenWidth = Dimensions.get('window').width; const scaleFactor = width / screenWidth; const imageHeight = height / scaleFactor; this.setState({imgWidth: screenWidth, imgHeight: imageHeight}) }); + + // Generate thumbnail size + Image.getSize(this.props.previewSrcURL, (width, height) => { + const screenWidth = 300; + const scaleFactor = width / screenWidth; + const imageHeight = height / scaleFactor; + this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}) + }); } render() { let imageView; - + if (Str.isPDF(this.props.srcURL)) { - imageView = ; + imageView = ; } else { imageView = + imageHeight={this.state.imgHeight + 87}> this.setModalVisiblity(true)} > - + - - this.setModalVisiblity(false)} visible={this.state.visible} transparent={true} > - - + + {imageView} + diff --git a/src/styles/StyleSheet.js b/src/styles/StyleSheet.js index 6a4252dec1e9..9065604b15e6 100644 --- a/src/styles/StyleSheet.js +++ b/src/styles/StyleSheet.js @@ -549,20 +549,6 @@ const styles = { paddingRight: 20, }, - imageModalContentHeader: { - borderWidth: 1, - borderTopLeftRadius: 20, - borderTopRightRadius: 20, - overflow: 'hidden', - borderColor: colors.border, - height: 87, - justifyContent: 'center', - display: 'flex', - paddingLeft: 20, - paddingRight: 20, - backgroundColor: colors.componentBG, - }, - appContentHeaderTitle: { alignItems: 'center', flexDirection: 'row', @@ -878,6 +864,23 @@ const styles = { backgroundColor: colors.black, }, + imageModalContainer: { + backgroundColor: colors.componentBG, + borderColor: colors.border, + borderWidth: 1, + borderRadius: 20, + height: '100%', + }, + + imageModalHeader: { + overflow: 'hidden', + height: 87, + justifyContent: 'center', + display: 'flex', + paddingLeft: 20, + paddingRight: 20, + }, + imageModalPDF: { flex: 1, width: Dimensions.get('window').width, @@ -885,19 +888,24 @@ const styles = { backgroundColor: colors.componentBG, }, - imageModalPlaceholder: { + imageModalCenterContainer: { + flex: 1, + flexDirection: 'column', + justifyContent: 'center', alignItems: 'center', + backgroundColor: '#00000080', + }, + + imageModalImageCenterContainer: { + flex: 1, + flexDirection: 'column', justifyContent: 'center', - backgroundColor: colors.componentBG, + alignItems: 'center', + overflow: 'hidden', }, - imageModalHeader: { - alignItems: 'flex-end', - justifyContent: 'flex-end', - height: 65, - backgroundColor: colors.heading, - paddingRight: 25, - paddingBottom: 10, + imageModalImageContainer: { + padding: 50, overflow: 'hidden', }, }; diff --git a/webpack.common.js b/webpack.common.js index 02f4efae73d6..92fa30866d11 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -64,6 +64,16 @@ module.exports = { emitWarning: true, }, }, + // Rule for react-native-web-webview + { + test: /postMock.html$/, + use: { + loader: 'file-loader', + options: { + name: '[name].[ext]', + }, + }, + }, // Gives the ability to load local images { @@ -80,6 +90,7 @@ module.exports = { alias: { 'react-native-config': 'react-web-config', 'react-native$': 'react-native-web', + 'react-native-webview': 'react-native-web-webview', }, // React Native libraries may have web-specific module implementations that appear with the extension `.web.js` From a4af55608f0f38cfa1abf9fe4705159787df941b Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Fri, 30 Oct 2020 14:54:27 -0700 Subject: [PATCH 12/39] ~ style ~ --- src/components/ImageModal/index.js | 118 ++++++++++-------- src/components/ImageModal/index.native.js | 104 ++++++++------- .../home/report/ReportActionItemFragment.js | 9 +- 3 files changed, 129 insertions(+), 102 deletions(-) diff --git a/src/components/ImageModal/index.js b/src/components/ImageModal/index.js index f3fd14a54668..620a2637654e 100644 --- a/src/components/ImageModal/index.js +++ b/src/components/ImageModal/index.js @@ -1,10 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, Image, Modal, TouchableOpacity, Text, Dimensions, TouchableWithoutFeedback } from 'react-native'; +import { + View, Image, Modal, TouchableOpacity, Text, Dimensions, TouchableWithoutFeedback +} from 'react-native'; +import _ from 'underscore'; +import {WebView} from 'react-native-webview'; import exitIcon from '../../../assets/images/icon-x.png'; import styles from '../../styles/StyleSheet'; -import _ from 'underscore'; -import { WebView } from 'react-native-webview'; import Str from '../../libs/Str'; /** @@ -37,27 +39,27 @@ class ImageModal extends React.Component { visible: false, imgWidth: 200, imgHeight: 200, - } + }; } - componentWillMount() { + componentDidMount() { Image.getSize(this.props.srcURL, (width, height) => { - const screenWidth = Dimensions.get('window').width * 0.6; + const screenWidth = Dimensions.get('window').width * 0.6; const scaleFactor = width / screenWidth; const imageHeight = height / scaleFactor; - this.setState({imgWidth: screenWidth, imgHeight: imageHeight}) + this.setState({imgWidth: screenWidth, imgHeight: imageHeight}); }); Image.getSize(this.props.previewSrcURL, (width, height) => { const screenWidth = 300; const scaleFactor = width / screenWidth; const imageHeight = height / scaleFactor; - this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}) + this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}); }); } setModalVisiblity(visibility) { - this.setState({ visible: visibility }); + this.setState({visible: visibility}); } render() { @@ -65,73 +67,81 @@ class ImageModal extends React.Component { if (Str.isPDF(this.props.srcURL)) { const pdfViewUrl = `http://mozilla.github.com/pdf.js/web/viewer.html?file=${encodeURIComponent(this.props.srcURL)}`; - imageView = ; + imageView = ; } else { - imageView = - - ; + imageView = ( + + + + ); } return ( <> - this.setModalVisiblity(true)} > - {this.props.info} - + this.setModalVisiblity(true)}> + - + this.setModalVisiblity(false)} visible={this.state.visible} - transparent={true} + transparent > - this.setModalVisiblity(false)}> - - - - - - Attachment - - - this.setModalVisiblity(false)} - style={[styles.touchableButtonImage, styles.mr0]} - > - - + this.setModalVisiblity(false)} + > + + + + + + Attachment + + + this.setModalVisiblity(false)} + style={[styles.touchableButtonImage, styles.mr0]} + > + + + - + + + {imageView} + - - {imageView} - - - + ); } - } ImageModal.propTypes = propTypes; diff --git a/src/components/ImageModal/index.native.js b/src/components/ImageModal/index.native.js index 1dd73a2aed4d..db6434eaa588 100644 --- a/src/components/ImageModal/index.native.js +++ b/src/components/ImageModal/index.native.js @@ -1,11 +1,13 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, Image, Modal, TouchableOpacity, Text, Dimensions } from 'react-native'; -import styles from '../../styles/StyleSheet'; -import exitIcon from '../../../assets/images/icon-x.png'; +import { + View, Image, Modal, TouchableOpacity, Text, Dimensions +} from 'react-native'; import ImageZoom from 'react-native-image-pan-zoom'; import _ from 'underscore'; import Pdf from 'react-native-pdf'; +import exitIcon from '../../../assets/images/icon-x.png'; +import styles from '../../styles/StyleSheet'; import Str from '../../libs/Str'; /** @@ -13,9 +15,6 @@ import Str from '../../libs/Str'; */ const propTypes = { - // Object array of images - images: PropTypes.array, - // URL to image preview previewSrcURL: PropTypes.string, @@ -41,20 +40,16 @@ class ImageModal extends React.Component { visible: false, imgWidth: 200, imgHeight: 200, - } - } - - setModalVisiblity(visibility) { - this.setState({ visible: visibility }); + }; } componentDidMount() { - // Generate image size for modal + // Generate image size for modal Image.getSize(this.props.srcURL, (width, height) => { const screenWidth = Dimensions.get('window').width; const scaleFactor = width / screenWidth; const imageHeight = height / scaleFactor; - this.setState({imgWidth: screenWidth, imgHeight: imageHeight}) + this.setState({imgWidth: screenWidth, imgHeight: imageHeight}); }); // Generate thumbnail size @@ -62,43 +57,59 @@ class ImageModal extends React.Component { const screenWidth = 300; const scaleFactor = width / screenWidth; const imageHeight = height / scaleFactor; - this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}) + this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}); }); } + setModalVisiblity(visibility) { + this.setState({visible: visibility}); + } + render() { let imageView; - + if (Str.isPDF(this.props.srcURL)) { - imageView = ; + imageView = ( + + ); } else { - imageView = - - ; + imageView = ( + + + + ); } return ( <> - this.setModalVisiblity(true)} > - - - - this.setModalVisiblity(false)} - visible={this.state.visible} - transparent={true} - > + this.setModalVisiblity(true)}> + + + + this.setModalVisiblity(false)} + visible={this.state.visible} + transparent + > + ]} + > Attachment - + this.setModalVisiblity(false)} style={[styles.touchableButtonImage, styles.mr0]} @@ -125,13 +137,13 @@ class ImageModal extends React.Component { - + {imageView} - - - - + + + + ); } diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 0be17b698fb7..a4fcf4138f8e 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -64,7 +64,12 @@ class ReportActionItemFragment extends React.PureComponent { ), img: (htmlAttribs, children, convertedCSSStyles, passProps) => ( - + ), }; } @@ -82,7 +87,7 @@ class ReportActionItemFragment extends React.PureComponent { // We only want to attach auth tokens to images that come from Expensify attachments if (htmlNode.name === 'img' && htmlNode.attribs['data-expensify-source']) { htmlNode.attribs.preview = `${node.attribs.src}?authToken=${getAuthToken()}`; - htmlNode.attribs.src = htmlNode.attribs['data-expensify-source'] + `?authToken=${getAuthToken()}`; + htmlNode.attribs.src = `${htmlNode.attribs['data-expensify-source']}?authToken=${getAuthToken()}`; return htmlNode; } } From 8a52f51853c787f71d341f36a2f380ab8c220c4c Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Fri, 30 Oct 2020 15:02:06 -0700 Subject: [PATCH 13/39] style (remove unused imports, spacing) --- src/components/ImageModal/index.js | 1 - src/components/ImageModal/index.native.js | 1 - webpack/webpack.common.js | 5 +++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/ImageModal/index.js b/src/components/ImageModal/index.js index 620a2637654e..8271ae97dc7a 100644 --- a/src/components/ImageModal/index.js +++ b/src/components/ImageModal/index.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import { View, Image, Modal, TouchableOpacity, Text, Dimensions, TouchableWithoutFeedback } from 'react-native'; -import _ from 'underscore'; import {WebView} from 'react-native-webview'; import exitIcon from '../../../assets/images/icon-x.png'; import styles from '../../styles/StyleSheet'; diff --git a/src/components/ImageModal/index.native.js b/src/components/ImageModal/index.native.js index db6434eaa588..b05e3d921597 100644 --- a/src/components/ImageModal/index.native.js +++ b/src/components/ImageModal/index.native.js @@ -4,7 +4,6 @@ import { View, Image, Modal, TouchableOpacity, Text, Dimensions } from 'react-native'; import ImageZoom from 'react-native-image-pan-zoom'; -import _ from 'underscore'; import Pdf from 'react-native-pdf'; import exitIcon from '../../../assets/images/icon-x.png'; import styles from '../../styles/StyleSheet'; diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index 3c0e2cc5b779..34a0a028a7e3 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -67,10 +67,11 @@ module.exports = { emitWarning: true, }, }, - // Rule for react-native-web-webview + + // Rule for react-native-web-webview { test: /postMock.html$/, - use: { + use: { loader: 'file-loader', options: { name: '[name].[ext]', From 254856c621c8dc51f948fc78c3bd5485db639cdc Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Fri, 30 Oct 2020 15:04:09 -0700 Subject: [PATCH 14/39] style in str.js --- src/libs/Str.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Str.js b/src/libs/Str.js index c57a7f7e3706..de3f7a22ed26 100644 --- a/src/libs/Str.js +++ b/src/libs/Str.js @@ -98,7 +98,7 @@ const Str = { /** * Takes in a URL and checks if the file extension is PDF - * + * * @param {mixed} url The URL to be checked * @returns {Boolean} Whether file path is PDF or not */ From 1836a4dbe07373de483fdb13cc9e3335028fc63d Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Mon, 2 Nov 2020 10:24:06 -0800 Subject: [PATCH 15/39] update comments and move rendering logic --- src/CONST.js | 2 + src/components/ImageModal/index.js | 66 ++++++++++++++--------- src/components/ImageModal/index.native.js | 60 ++++++++++----------- src/styles/StyleSheet.js | 3 +- 4 files changed, 73 insertions(+), 58 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 29bf133b1eab..aec83345c7d2 100644 --- a/src/CONST.js +++ b/src/CONST.js @@ -1,7 +1,9 @@ const CLOUDFRONT_URL = 'https://d2k5nsl2zxldvw.cloudfront.net'; +const MOZILLA_PDF_VIEWER_URL = 'http://mozilla.github.com/pdf.js/web/viewer.html'; const CONST = { CLOUDFRONT_URL, + MOZILLA_PDF_VIEWER_URL, EXPENSIFY_ICON_URL: `${CLOUDFRONT_URL}/images/favicon-2019.png`, }; diff --git a/src/components/ImageModal/index.js b/src/components/ImageModal/index.js index 8271ae97dc7a..3b2f58c902ad 100644 --- a/src/components/ImageModal/index.js +++ b/src/components/ImageModal/index.js @@ -7,9 +7,10 @@ import {WebView} from 'react-native-webview'; import exitIcon from '../../../assets/images/icon-x.png'; import styles from '../../styles/StyleSheet'; import Str from '../../libs/Str'; +import CONST from '../../CONST'; /** - * Text based component that is passed a URL to open onPress + * Image modal component that is triggered by an onpress on an image preview */ const propTypes = { @@ -38,10 +39,13 @@ class ImageModal extends React.Component { visible: false, imgWidth: 200, imgHeight: 200, + thumbnailWidth: 300, + thumbnailHeight: 150, }; } componentDidMount() { + // Scale image for modal view Image.getSize(this.props.srcURL, (width, height) => { const screenWidth = Dimensions.get('window').width * 0.6; const scaleFactor = width / screenWidth; @@ -49,6 +53,7 @@ class ImageModal extends React.Component { this.setState({imgWidth: screenWidth, imgHeight: imageHeight}); }); + // Scale image for thumbnail preview Image.getSize(this.props.previewSrcURL, (width, height) => { const screenWidth = 300; const scaleFactor = width / screenWidth; @@ -57,50 +62,47 @@ class ImageModal extends React.Component { }); } + /** + * Updates the visibility of the modal + * + * @param {bool} visibility + */ setModalVisiblity(visibility) { this.setState({visible: visibility}); } render() { - let imageView; - - if (Str.isPDF(this.props.srcURL)) { - const pdfViewUrl = `http://mozilla.github.com/pdf.js/web/viewer.html?file=${encodeURIComponent(this.props.srcURL)}`; - imageView = ; - } else { - imageView = ( - - - - ); - } + // Generate height/width for modal + const modalWidth = Dimensions.get('window').width * 0.8; + const modalHeight = Dimensions.get('window').height * 0.8; + + // URL for PDF file inside the mozilla viewer + const PDFUrl = `${CONST.MOZILLA_PDF_VIEWER_URL}?file=${encodeURIComponent(this.props.srcURL)}`; return ( <> this.setModalVisiblity(true)}> - this.setModalVisiblity(false)} visible={this.state.visible} transparent > - this.setModalVisiblity(false)} > - + - - {imageView} + { + (Str.isPDF(this.props.srcURL)) + ? ( + + ) + : ( + + + + ) + } - - ); } @@ -145,6 +162,5 @@ class ImageModal extends React.Component { ImageModal.propTypes = propTypes; ImageModal.defaultProps = defaultProps; -ImageModal.displayName = 'ImageModal'; export default ImageModal; diff --git a/src/components/ImageModal/index.native.js b/src/components/ImageModal/index.native.js index b05e3d921597..61387a6409ad 100644 --- a/src/components/ImageModal/index.native.js +++ b/src/components/ImageModal/index.native.js @@ -10,7 +10,7 @@ import styles from '../../styles/StyleSheet'; import Str from '../../libs/Str'; /** - * Text based component that is passed a URL to open onPress + * Image modal component that is triggered by an onpress on an image preview */ const propTypes = { @@ -39,11 +39,13 @@ class ImageModal extends React.Component { visible: false, imgWidth: 200, imgHeight: 200, + thumbnailWidth: 300, + thumbnailHeight: 150, }; } componentDidMount() { - // Generate image size for modal + // Scale image for modal view Image.getSize(this.props.srcURL, (width, height) => { const screenWidth = Dimensions.get('window').width; const scaleFactor = width / screenWidth; @@ -51,7 +53,7 @@ class ImageModal extends React.Component { this.setState({imgWidth: screenWidth, imgHeight: imageHeight}); }); - // Generate thumbnail size + // Scale image for thumbnail preview Image.getSize(this.props.previewSrcURL, (width, height) => { const screenWidth = 300; const scaleFactor = width / screenWidth; @@ -60,36 +62,16 @@ class ImageModal extends React.Component { }); } + /** + * Updates the visibility of the modal + * + * @param {bool} visibility + */ setModalVisiblity(visibility) { this.setState({visible: visibility}); } render() { - let imageView; - - if (Str.isPDF(this.props.srcURL)) { - imageView = ( - - ); - } else { - imageView = ( - - - - ); - } - return ( <> this.setModalVisiblity(true)}> @@ -138,11 +120,26 @@ class ImageModal extends React.Component { - {imageView} + {(Str.isPDF(this.props.srcURL)) ? ( + + ) : ( + + + + )} - - ); } @@ -150,6 +147,5 @@ class ImageModal extends React.Component { ImageModal.propTypes = propTypes; ImageModal.defaultProps = defaultProps; -ImageModal.displayName = 'ImageModal'; export default ImageModal; diff --git a/src/styles/StyleSheet.js b/src/styles/StyleSheet.js index 9065604b15e6..3795e3a1aa02 100644 --- a/src/styles/StyleSheet.js +++ b/src/styles/StyleSheet.js @@ -21,6 +21,7 @@ const colors = { textSupporting: '#7D8B8F', red: '#E84A3B', buttonBG: '#8A8A8A', + modalBackground: '#00000080', }; const styles = { @@ -893,7 +894,7 @@ const styles = { flexDirection: 'column', justifyContent: 'center', alignItems: 'center', - backgroundColor: '#00000080', + backgroundColor: colors.modalBackground, }, imageModalImageCenterContainer: { From 604560aae5bf81f98f86c71976e8f17008ef2ce6 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 5 Nov 2020 15:45:37 -0800 Subject: [PATCH 16/39] break down into components --- src/components/BaseModal.js | 123 +++++++++++++ src/components/ImageModal/index.js | 166 ------------------ src/components/ImageModal/index.native.js | 151 ---------------- src/components/ImageView/index.js | 43 +++++ src/components/ImageView/index.native.js | 56 ++++++ src/components/ModalWithImage/index.js | 112 ++++++++++++ src/components/ModalWithImage/index.native.js | 89 ++++++++++ src/components/PDFView/index.js | 40 +++++ src/components/PDFView/index.native.js | 47 +++++ .../home/report/ReportActionItemFragment.js | 6 +- src/styles/StyleSheet.js | 9 +- 11 files changed, 516 insertions(+), 326 deletions(-) create mode 100644 src/components/BaseModal.js delete mode 100644 src/components/ImageModal/index.js delete mode 100644 src/components/ImageModal/index.native.js create mode 100644 src/components/ImageView/index.js create mode 100644 src/components/ImageView/index.native.js create mode 100644 src/components/ModalWithImage/index.js create mode 100644 src/components/ModalWithImage/index.native.js create mode 100644 src/components/PDFView/index.js create mode 100644 src/components/PDFView/index.native.js diff --git a/src/components/BaseModal.js b/src/components/BaseModal.js new file mode 100644 index 000000000000..7a5ca305b3e3 --- /dev/null +++ b/src/components/BaseModal.js @@ -0,0 +1,123 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + View, Image, Modal, TouchableOpacity, Text, Dimensions, TouchableWithoutFeedback +} from 'react-native'; +import exitIcon from '../../assets/images/icon-x.png'; +import styles from '../styles/StyleSheet'; + +/** + * Modal component + */ + +const propTypes = { + // Should modal go full screen + pinToEdges: PropTypes.bool, + + // Title of the modal + title: PropTypes.string, + + // Children of modal component + children: PropTypes.func.isRequired, + +}; + +const defaultProps = { + pinToEdges: true, + title: 'Attachment', + visible: 'false', +}; + +class BaseModal extends React.Component { + constructor(props) { + super(props); + + this.state = { + visible: false, + }; + } + + /** + * Updates the visibility of the modal + * + * @param {Boolean} visibility + */ + setModalVisiblity(visibility) { + this.setState({visible: visibility}); + } + + render() { + // Generate height/width for modal + const modalWidth = Dimensions.get('window').width * 0.8; + const modalHeight = Dimensions.get('window').height * 0.8; + + const ModalHeader = ( + + + + {this.props.title} + + + this.setModalVisiblity(false)} + style={[styles.touchableButtonImage, styles.mr0]} + > + + + + + + ); + + return ( + <> + this.setModalVisiblity(false)} + visible={this.state.visible} + transparent + > + {(this.props.pinToEdges) ? ( + + {ModalHeader} + + {this.props.children} + + + ) : ( + this.setModalVisiblity(false)} + > + + + {ModalHeader} + + {this.props.children} + + + + + )} + + + ); + } +} + +BaseModal.propTypes = propTypes; +BaseModal.defaultProps = defaultProps; + +export default BaseModal; diff --git a/src/components/ImageModal/index.js b/src/components/ImageModal/index.js deleted file mode 100644 index 3b2f58c902ad..000000000000 --- a/src/components/ImageModal/index.js +++ /dev/null @@ -1,166 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { - View, Image, Modal, TouchableOpacity, Text, Dimensions, TouchableWithoutFeedback -} from 'react-native'; -import {WebView} from 'react-native-webview'; -import exitIcon from '../../../assets/images/icon-x.png'; -import styles from '../../styles/StyleSheet'; -import Str from '../../libs/Str'; -import CONST from '../../CONST'; - -/** - * Image modal component that is triggered by an onpress on an image preview - */ - -const propTypes = { - // URL to image preview - previewSrcURL: PropTypes.string, - - // URL to full-sized image - srcURL: PropTypes.string, - - // Any additional styles to apply - // eslint-disable-next-line react/forbid-prop-types - style: PropTypes.any, -}; - -const defaultProps = { - previewSrcURL: '', - srcURL: '', - style: {}, -}; - -class ImageModal extends React.Component { - constructor(props) { - super(props); - - this.state = { - visible: false, - imgWidth: 200, - imgHeight: 200, - thumbnailWidth: 300, - thumbnailHeight: 150, - }; - } - - componentDidMount() { - // Scale image for modal view - Image.getSize(this.props.srcURL, (width, height) => { - const screenWidth = Dimensions.get('window').width * 0.6; - const scaleFactor = width / screenWidth; - const imageHeight = height / scaleFactor; - this.setState({imgWidth: screenWidth, imgHeight: imageHeight}); - }); - - // Scale image for thumbnail preview - Image.getSize(this.props.previewSrcURL, (width, height) => { - const screenWidth = 300; - const scaleFactor = width / screenWidth; - const imageHeight = height / scaleFactor; - this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}); - }); - } - - /** - * Updates the visibility of the modal - * - * @param {bool} visibility - */ - setModalVisiblity(visibility) { - this.setState({visible: visibility}); - } - - render() { - // Generate height/width for modal - const modalWidth = Dimensions.get('window').width * 0.8; - const modalHeight = Dimensions.get('window').height * 0.8; - - // URL for PDF file inside the mozilla viewer - const PDFUrl = `${CONST.MOZILLA_PDF_VIEWER_URL}?file=${encodeURIComponent(this.props.srcURL)}`; - - return ( - <> - this.setModalVisiblity(true)}> - - - - this.setModalVisiblity(false)} - visible={this.state.visible} - transparent - > - this.setModalVisiblity(false)} - > - - - - - - Attachment - - - this.setModalVisiblity(false)} - style={[styles.touchableButtonImage, styles.mr0]} - > - - - - - - - { - (Str.isPDF(this.props.srcURL)) - ? ( - - ) - : ( - - - - ) - } - - - - - - - ); - } -} - -ImageModal.propTypes = propTypes; -ImageModal.defaultProps = defaultProps; - -export default ImageModal; diff --git a/src/components/ImageModal/index.native.js b/src/components/ImageModal/index.native.js deleted file mode 100644 index 61387a6409ad..000000000000 --- a/src/components/ImageModal/index.native.js +++ /dev/null @@ -1,151 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { - View, Image, Modal, TouchableOpacity, Text, Dimensions -} from 'react-native'; -import ImageZoom from 'react-native-image-pan-zoom'; -import Pdf from 'react-native-pdf'; -import exitIcon from '../../../assets/images/icon-x.png'; -import styles from '../../styles/StyleSheet'; -import Str from '../../libs/Str'; - -/** - * Image modal component that is triggered by an onpress on an image preview - */ - -const propTypes = { - // URL to image preview - previewSrcURL: PropTypes.string, - - // URL to full-sized image - srcURL: PropTypes.string, - - // Any additional styles to apply - // eslint-disable-next-line react/forbid-prop-types - style: PropTypes.any, -}; - -const defaultProps = { - previewSrcURL: '', - srcURL: '', - style: {}, -}; - -class ImageModal extends React.Component { - constructor(props) { - super(props); - - this.state = { - visible: false, - imgWidth: 200, - imgHeight: 200, - thumbnailWidth: 300, - thumbnailHeight: 150, - }; - } - - componentDidMount() { - // Scale image for modal view - Image.getSize(this.props.srcURL, (width, height) => { - const screenWidth = Dimensions.get('window').width; - const scaleFactor = width / screenWidth; - const imageHeight = height / scaleFactor; - this.setState({imgWidth: screenWidth, imgHeight: imageHeight}); - }); - - // Scale image for thumbnail preview - Image.getSize(this.props.previewSrcURL, (width, height) => { - const screenWidth = 300; - const scaleFactor = width / screenWidth; - const imageHeight = height / scaleFactor; - this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}); - }); - } - - /** - * Updates the visibility of the modal - * - * @param {bool} visibility - */ - setModalVisiblity(visibility) { - this.setState({visible: visibility}); - } - - render() { - return ( - <> - this.setModalVisiblity(true)}> - - - - this.setModalVisiblity(false)} - visible={this.state.visible} - transparent - > - - - - - Attachment - - - this.setModalVisiblity(false)} - style={[styles.touchableButtonImage, styles.mr0]} - > - - - - - - - {(Str.isPDF(this.props.srcURL)) ? ( - - ) : ( - - - - )} - - - - ); - } -} - -ImageModal.propTypes = propTypes; -ImageModal.defaultProps = defaultProps; - -export default ImageModal; diff --git a/src/components/ImageView/index.js b/src/components/ImageView/index.js new file mode 100644 index 000000000000..1c2abdaa5cc4 --- /dev/null +++ b/src/components/ImageView/index.js @@ -0,0 +1,43 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {View, Image} from 'react-native'; + +const propTypes = { + // URL to full-sized image + sourceURL: PropTypes.string, + + // Image height + imageHeight: PropTypes.number, + + // Image width + imageWidth: PropTypes.number, + + // Any additional styles to apply + // eslint-disable-next-line react/forbid-prop-types + style: PropTypes.any, +}; + +const defaultProps = { + sourceURL: '', + imageHeight: 300, + imageWidth: 300, + style: {}, +}; + +const ImageView = props => ( + + + +); + +ImageView.propTypes = propTypes; +ImageView.defaultProps = defaultProps; +ImageView.displayName = 'ImageView'; + +export default ImageView; diff --git a/src/components/ImageView/index.native.js b/src/components/ImageView/index.native.js new file mode 100644 index 000000000000..537215551baf --- /dev/null +++ b/src/components/ImageView/index.native.js @@ -0,0 +1,56 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {View, Dimensions, Image} from 'react-native'; +import ImgZoom from 'react-native-image-pan-zoom'; +import styles from '../../styles/StyleSheet'; + +/** + * On the native layer, we use a image library to handle zoom functionality + * + * @param props + * @returns {JSX.Element} + */ + +const propTypes = { + // URL to full-sized image + sourceURL: PropTypes.string, + + // Image height + imageHeight: PropTypes.number, + + // Image width + imageWidth: PropTypes.number, + + // Any additional styles to apply + // eslint-disable-next-line react/forbid-prop-types + style: PropTypes.any, +}; + +const defaultProps = { + sourceURL: '', + imageHeight: 300, + imageWidth: 300, + style: {}, +}; + +const ImageView = props => ( + + + + + +); + +ImageView.propTypes = propTypes; +ImageView.defaultProps = defaultProps; +ImageView.displayName = 'ImageView'; + +export default ImageView; diff --git a/src/components/ModalWithImage/index.js b/src/components/ModalWithImage/index.js new file mode 100644 index 000000000000..8a8d629ba3bd --- /dev/null +++ b/src/components/ModalWithImage/index.js @@ -0,0 +1,112 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + View, Image, Modal, TouchableOpacity, Text, Dimensions, TouchableWithoutFeedback +} from 'react-native'; +import {WebView} from 'react-native-webview'; +import exitIcon from '../../../assets/images/icon-x.png'; +import styles from '../../styles/StyleSheet'; +import Str from '../../libs/Str'; +import CONST from '../../CONST'; +import PDFView from '../PDFView'; +import ImageView from '../ImageView'; +import BaseModal from '../BaseModal'; + +/** + * Image modal component that is triggered when pressing on an image + */ + +const propTypes = { + // URL to image preview + previewSourceURL: PropTypes.string, + + // URL to full-sized image + sourceURL: PropTypes.string, + + // Any additional styles to apply + // eslint-disable-next-line react/forbid-prop-types + style: PropTypes.any, +}; + +const defaultProps = { + previewSourceURL: '', + sourceURL: '', + style: {}, +}; + +class ModalWithImage extends React.Component { + constructor(props) { + super(props); + + this.state = { + imgWidth: 200, + imgHeight: 200, + thumbnailWidth: 300, + thumbnailHeight: 150, + visible: false, + }; + } + + componentDidMount() { + // Scale image for modal view + Image.getSize(this.props.sourceURL, (width, height) => { + const screenWidth = Dimensions.get('window').width * 0.6; + const scaleFactor = width / screenWidth; + const imageHeight = height / scaleFactor; + this.setState({imgWidth: screenWidth, imgHeight: imageHeight}); + }); + + // Scale image for thumbnail preview + Image.getSize(this.props.previewSourceURL, (width, height) => { + const screenWidth = 300; + const scaleFactor = width / screenWidth; + const imageHeight = height / scaleFactor; + this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}); + }); + } + + /** + * Updates the visibility of the modal + * + * @param {Boolean} visibility + */ + setModalVisiblity(visibility) { + this.setState({visible: visibility}); + } + + render() { + return ( + <> + this.setModalVisiblity(true)}> + + + + + {(Str.isPDF(this.props.sourceURL)) ? ( + + ) : ( + + )} + + + ); + } +} + +ModalWithImage.propTypes = propTypes; +ModalWithImage.defaultProps = defaultProps; + +export default ModalWithImage; diff --git a/src/components/ModalWithImage/index.native.js b/src/components/ModalWithImage/index.native.js new file mode 100644 index 000000000000..fb88209ff406 --- /dev/null +++ b/src/components/ModalWithImage/index.native.js @@ -0,0 +1,89 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + View, Image, Modal, TouchableOpacity, Text, Dimensions +} from 'react-native'; +import styles from '../../styles/StyleSheet'; +import Str from '../../libs/Str'; +import PDFView from '../PDFView'; +import ImageView from '../ImageView'; +import BaseModal from '../BaseModal'; + +/** + * Image modal component that is triggered when pressing on an image + */ + +const propTypes = { + // URL to image preview + previewSourceURL: PropTypes.string, + + // URL to full-sized image + sourceURL: PropTypes.string, + + // Any additional styles to apply + // eslint-disable-next-line react/forbid-prop-types + style: PropTypes.any, +}; + +const defaultProps = { + previewSourceURL: '', + sourceURL: '', + style: {}, +}; + +class ModalWithImage extends React.Component { + constructor(props) { + super(props); + + this.state = { + imgWidth: 200, + imgHeight: 200, + }; + } + + componentDidMount() { + // Scale image for modal view + Image.getSize(this.props.sourceURL, (width, height) => { + const screenWidth = Dimensions.get('window').width; + const scaleFactor = width / screenWidth; + const imageHeight = height / scaleFactor; + this.setState({imgWidth: screenWidth, imgHeight: imageHeight}); + }); + } + + render() { + return ( + <> + this.setModalVisiblity(true)}> + + + + + {(Str.isPDF(this.props.sourceURL)) ? ( + + ) : ( + + )} + + + ); + } +} + +ModalWithImage.propTypes = propTypes; +ModalWithImage.defaultProps = defaultProps; + +export default ModalWithImage; diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js new file mode 100644 index 000000000000..d4e2f81c51cd --- /dev/null +++ b/src/components/PDFView/index.js @@ -0,0 +1,40 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {View} from 'react-native'; +import {WebView} from 'react-native-webview'; +import styles from '../../styles/StyleSheet'; +import CONST from '../../CONST'; + +/** + * On web, we use a WebView pointed to a pdf renderer + * + * @param props + * @returns {JSX.Element} + */ + +const propTypes = { + // URL to full-sized image + sourceURL: PropTypes.string, + + // Any additional styles to apply + // eslint-disable-next-line react/forbid-prop-types + style: PropTypes.any, +}; + +const defaultProps = { + sourceURL: '', + style: {}, +}; + +const PDFView = props => ( + +); + +PDFView.propTypes = propTypes; +PDFView.defaultProps = defaultProps; +PDFView.displayName = 'PDFView'; + +export default PDFView; diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js new file mode 100644 index 000000000000..6344d1cddedc --- /dev/null +++ b/src/components/PDFView/index.native.js @@ -0,0 +1,47 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {View, Dimensions} from 'react-native'; +import Pdf from 'react-native-pdf'; +import styles from '../../styles/StyleSheet'; + +/** + * On the native layer, we use a pdf library to display PDFs + * + * @param props + * @returns {JSX.Element} + */ + +const propTypes = { + // URL to full-sized image + sourceURL: PropTypes.string, + + // Any additional styles to apply + // eslint-disable-next-line react/forbid-prop-types + style: PropTypes.any, +}; + +const defaultProps = { + sourceURL: '', + style: {}, +}; + +const PDFView = props => ( + + + +); + +PDFView.propTypes = propTypes; +PDFView.defaultProps = defaultProps; +PDFView.displayName = 'PDFView'; + +export default PDFView; diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index a4fcf4138f8e..074be53c5c1f 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -9,7 +9,7 @@ import ReportActionFragmentPropTypes from './ReportActionFragmentPropTypes'; import styles, {webViewStyles, colors} from '../../../styles/StyleSheet'; import Text from '../../../components/Text'; import AnchorForCommentsOnly from '../../../components/AnchorForCommentsOnly'; -import ImageModal from '../../../components/ImageModal'; +import ImageModal from '../../../components/ModalWithImage'; import {getAuthToken} from '../../../libs/API'; import InlineCodeBlock from '../../../components/InlineCodeBlock'; @@ -65,8 +65,8 @@ class ReportActionItemFragment extends React.PureComponent { ), img: (htmlAttribs, children, convertedCSSStyles, passProps) => ( diff --git a/src/styles/StyleSheet.js b/src/styles/StyleSheet.js index 3795e3a1aa02..cfd5b44b495e 100644 --- a/src/styles/StyleSheet.js +++ b/src/styles/StyleSheet.js @@ -1,5 +1,4 @@ // We place items a percentage to the safe area on the top or bottom of the screen -import {Dimensions} from 'react-native'; import fontFamily from './fontFamily'; import italic from './italic'; @@ -21,7 +20,7 @@ const colors = { textSupporting: '#7D8B8F', red: '#E84A3B', buttonBG: '#8A8A8A', - modalBackground: '#00000080', + modalBackdrop: '#00000080', }; const styles = { @@ -884,8 +883,6 @@ const styles = { imageModalPDF: { flex: 1, - width: Dimensions.get('window').width, - height: Dimensions.get('window').height, backgroundColor: colors.componentBG, }, @@ -894,7 +891,7 @@ const styles = { flexDirection: 'column', justifyContent: 'center', alignItems: 'center', - backgroundColor: colors.modalBackground, + backgroundColor: colors.modalBackdrop, }, imageModalImageCenterContainer: { @@ -906,7 +903,7 @@ const styles = { }, imageModalImageContainer: { - padding: 50, + margin: 50, overflow: 'hidden', }, }; From 543997f81a6e79d1b0e5cb51b209432330a8047f Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Fri, 6 Nov 2020 06:23:33 -0800 Subject: [PATCH 17/39] clean up components and pass down method to update visibility --- src/components/BaseModal.js | 156 +++++++++--------- src/components/ImageView/index.native.js | 3 +- src/components/ModalWithImage/index.js | 28 ++-- src/components/ModalWithImage/index.native.js | 40 +++-- src/components/PDFView/index.js | 2 - 5 files changed, 117 insertions(+), 112 deletions(-) diff --git a/src/components/BaseModal.js b/src/components/BaseModal.js index 7a5ca305b3e3..48457357e2b9 100644 --- a/src/components/BaseModal.js +++ b/src/components/BaseModal.js @@ -17,104 +17,98 @@ const propTypes = { // Title of the modal title: PropTypes.string, + // Visibility of modal + visible: PropTypes.bool, + + // Width of the modal + modalWidth: PropTypes.number, + + // Height of the modal + modalHeight: PropTypes.number, + + // Method passed down to update visibility of the modal + setModalVisiblity: PropTypes.func.isRequired, + // Children of modal component children: PropTypes.func.isRequired, - }; const defaultProps = { pinToEdges: true, title: 'Attachment', - visible: 'false', + visible: false, + modalWidth: Dimensions.get('window').width * 0.8, + modalHeight: Dimensions.get('window').height * 0.8, }; -class BaseModal extends React.Component { - constructor(props) { - super(props); - - this.state = { - visible: false, - }; - } - - /** - * Updates the visibility of the modal - * - * @param {Boolean} visibility - */ - setModalVisiblity(visibility) { - this.setState({visible: visibility}); - } - - render() { - // Generate height/width for modal - const modalWidth = Dimensions.get('window').width * 0.8; - const modalHeight = Dimensions.get('window').height * 0.8; - - const ModalHeader = ( - - - - {this.props.title} - - - this.setModalVisiblity(false)} - style={[styles.touchableButtonImage, styles.mr0]} - > - - - +function BaseModal(props) { + const ModalHeader = ( + + + + {props.title} + + + props.setModalVisiblity(false)} + style={[styles.touchableButtonImage, styles.mr0]} + > + + - ); + + ); - return ( - <> - this.setModalVisiblity(false)} - visible={this.state.visible} - transparent + return ( + props.setModalVisiblity(false)} + visible={props.visible} + transparent + > + {(props.pinToEdges) ? ( + + {ModalHeader} + + {props.children} + + + ) : ( + props.setModalVisiblity(false)} > - {(this.props.pinToEdges) ? ( - + + {ModalHeader} - {this.props.children} + {props.children} - ) : ( - this.setModalVisiblity(false)} - > - - - {ModalHeader} - - {this.props.children} - - - - - )} - - - ); - } + + + )} + + ); } BaseModal.propTypes = propTypes; diff --git a/src/components/ImageView/index.native.js b/src/components/ImageView/index.native.js index 537215551baf..8c8ad96a2088 100644 --- a/src/components/ImageView/index.native.js +++ b/src/components/ImageView/index.native.js @@ -2,7 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import {View, Dimensions, Image} from 'react-native'; import ImgZoom from 'react-native-image-pan-zoom'; -import styles from '../../styles/StyleSheet'; /** * On the native layer, we use a image library to handle zoom functionality @@ -37,7 +36,7 @@ const ImageView = props => ( diff --git a/src/components/ModalWithImage/index.js b/src/components/ModalWithImage/index.js index 8a8d629ba3bd..2ce463f17bc0 100644 --- a/src/components/ModalWithImage/index.js +++ b/src/components/ModalWithImage/index.js @@ -1,19 +1,17 @@ import React from 'react'; import PropTypes from 'prop-types'; import { - View, Image, Modal, TouchableOpacity, Text, Dimensions, TouchableWithoutFeedback + Image, TouchableOpacity, Dimensions } from 'react-native'; -import {WebView} from 'react-native-webview'; -import exitIcon from '../../../assets/images/icon-x.png'; import styles from '../../styles/StyleSheet'; import Str from '../../libs/Str'; -import CONST from '../../CONST'; import PDFView from '../PDFView'; import ImageView from '../ImageView'; import BaseModal from '../BaseModal'; /** * Image modal component that is triggered when pressing on an image + * On web, we indicate that the modal isn't pinned to edges with props to BaseModal */ const propTypes = { @@ -22,16 +20,11 @@ const propTypes = { // URL to full-sized image sourceURL: PropTypes.string, - - // Any additional styles to apply - // eslint-disable-next-line react/forbid-prop-types - style: PropTypes.any, }; const defaultProps = { previewSourceURL: '', sourceURL: '', - style: {}, }; class ModalWithImage extends React.Component { @@ -70,24 +63,27 @@ class ModalWithImage extends React.Component { * * @param {Boolean} visibility */ - setModalVisiblity(visibility) { + setModalVisiblity = (visibility) => { this.setState({visible: visibility}); } + render() { return ( <> this.setModalVisiblity(true)}> - + {(Str.isPDF(this.props.sourceURL)) ? ( )} diff --git a/src/components/ModalWithImage/index.native.js b/src/components/ModalWithImage/index.native.js index fb88209ff406..5616d51a31c8 100644 --- a/src/components/ModalWithImage/index.native.js +++ b/src/components/ModalWithImage/index.native.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { - View, Image, Modal, TouchableOpacity, Text, Dimensions + Image, TouchableOpacity, Dimensions } from 'react-native'; import styles from '../../styles/StyleSheet'; import Str from '../../libs/Str'; @@ -11,6 +11,7 @@ import BaseModal from '../BaseModal'; /** * Image modal component that is triggered when pressing on an image + * On native, we indicate that the modal is fullscreen with props to BaseModal */ const propTypes = { @@ -19,16 +20,11 @@ const propTypes = { // URL to full-sized image sourceURL: PropTypes.string, - - // Any additional styles to apply - // eslint-disable-next-line react/forbid-prop-types - style: PropTypes.any, }; const defaultProps = { previewSourceURL: '', sourceURL: '', - style: {}, }; class ModalWithImage extends React.Component { @@ -38,6 +34,9 @@ class ModalWithImage extends React.Component { this.state = { imgWidth: 200, imgHeight: 200, + thumbnailWidth: 300, + thumbnailHeight: 150, + visible: false, }; } @@ -49,6 +48,23 @@ class ModalWithImage extends React.Component { const imageHeight = height / scaleFactor; this.setState({imgWidth: screenWidth, imgHeight: imageHeight}); }); + + // Scale image for thumbnail preview + Image.getSize(this.props.previewSourceURL, (width, height) => { + const screenWidth = 200; + const scaleFactor = width / screenWidth; + const imageHeight = height / scaleFactor; + this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}); + }); + } + + /** + * Updates the visibility of the modal + * + * @param {Boolean} visibility + */ + setModalVisiblity = (visibility) => { + this.setState({visible: visibility}); } render() { @@ -57,14 +73,16 @@ class ModalWithImage extends React.Component { this.setModalVisiblity(true)}> - + {(Str.isPDF(this.props.sourceURL)) ? ( Date: Fri, 6 Nov 2020 07:28:15 -0800 Subject: [PATCH 18/39] update component name to be more specific --- .../{ModalWithImage => ImageThumbnailWithModal}/index.js | 8 ++++---- .../index.native.js | 8 ++++---- src/pages/home/report/ReportActionItemFragment.js | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) rename src/components/{ModalWithImage => ImageThumbnailWithModal}/index.js (94%) rename src/components/{ModalWithImage => ImageThumbnailWithModal}/index.native.js (94%) diff --git a/src/components/ModalWithImage/index.js b/src/components/ImageThumbnailWithModal/index.js similarity index 94% rename from src/components/ModalWithImage/index.js rename to src/components/ImageThumbnailWithModal/index.js index 2ce463f17bc0..8d20658bae7d 100644 --- a/src/components/ModalWithImage/index.js +++ b/src/components/ImageThumbnailWithModal/index.js @@ -27,7 +27,7 @@ const defaultProps = { sourceURL: '', }; -class ModalWithImage extends React.Component { +class ImageThumbnailWithModal extends React.Component { constructor(props) { super(props); @@ -102,7 +102,7 @@ class ModalWithImage extends React.Component { } } -ModalWithImage.propTypes = propTypes; -ModalWithImage.defaultProps = defaultProps; +ImageThumbnailWithModal.propTypes = propTypes; +ImageThumbnailWithModal.defaultProps = defaultProps; -export default ModalWithImage; +export default ImageThumbnailWithModal; diff --git a/src/components/ModalWithImage/index.native.js b/src/components/ImageThumbnailWithModal/index.native.js similarity index 94% rename from src/components/ModalWithImage/index.native.js rename to src/components/ImageThumbnailWithModal/index.native.js index 5616d51a31c8..38d077470ed2 100644 --- a/src/components/ModalWithImage/index.native.js +++ b/src/components/ImageThumbnailWithModal/index.native.js @@ -27,7 +27,7 @@ const defaultProps = { sourceURL: '', }; -class ModalWithImage extends React.Component { +class ImageThumbnailWithModal extends React.Component { constructor(props) { super(props); @@ -101,7 +101,7 @@ class ModalWithImage extends React.Component { } } -ModalWithImage.propTypes = propTypes; -ModalWithImage.defaultProps = defaultProps; +ImageThumbnailWithModal.propTypes = propTypes; +ImageThumbnailWithModal.defaultProps = defaultProps; -export default ModalWithImage; +export default ImageThumbnailWithModal; diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 0c6f1b8288b8..01569855af39 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -9,7 +9,7 @@ import ReportActionFragmentPropTypes from './ReportActionFragmentPropTypes'; import styles, {webViewStyles, colors} from '../../../styles/StyleSheet'; import Text from '../../../components/Text'; import AnchorForCommentsOnly from '../../../components/AnchorForCommentsOnly'; -import ImageModal from '../../../components/ModalWithImage'; +import ImageThumbnailWithModal from '../../../components/ImageThumbnailWithModal'; import {getAuthToken} from '../../../libs/API'; import InlineCodeBlock from '../../../components/InlineCodeBlock'; @@ -69,7 +69,7 @@ class ReportActionItemFragment extends React.PureComponent { ), img: (htmlAttribs, children, convertedCSSStyles, passProps) => ( - Date: Fri, 6 Nov 2020 07:37:27 -0800 Subject: [PATCH 19/39] add display name --- src/components/BaseModal.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/BaseModal.js b/src/components/BaseModal.js index 48457357e2b9..21880314d9ea 100644 --- a/src/components/BaseModal.js +++ b/src/components/BaseModal.js @@ -113,5 +113,6 @@ function BaseModal(props) { BaseModal.propTypes = propTypes; BaseModal.defaultProps = defaultProps; +BaseModal.displayName = 'BaseModal'; export default BaseModal; From 7c113eab2d4abda6a66b4886c5eb144fdb9d7a25 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Fri, 6 Nov 2020 07:41:50 -0800 Subject: [PATCH 20/39] add mock so module passes unit test --- tests/unit/loginTest.js | 3 +++ tests/unit/mocks/react-native-pdf.js | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 tests/unit/mocks/react-native-pdf.js diff --git a/tests/unit/loginTest.js b/tests/unit/loginTest.js index 22805f158e71..840130a55455 100644 --- a/tests/unit/loginTest.js +++ b/tests/unit/loginTest.js @@ -22,6 +22,9 @@ jest.mock('../../node_modules/react-native-image-picker', () => require('./mocks/react-native-image-picker')); jest.mock('../../node_modules/urbanairship-react-native', () => require('./mocks/urbanairship-react-native')); +jest.mock('../../node_modules/react-native-pdf', + () => require('./mocks/react-native-pdf')); + describe('AppComponent', () => { it('renders correctly', () => { diff --git a/tests/unit/mocks/react-native-pdf.js b/tests/unit/mocks/react-native-pdf.js new file mode 100644 index 000000000000..4d179e730903 --- /dev/null +++ b/tests/unit/mocks/react-native-pdf.js @@ -0,0 +1,4 @@ +export default { + DocumentDir: jest.fn(), + ImageCache: jest.fn(), +}; From 82bab0047442950f149aabe3f5a2e1420da0bda8 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Mon, 9 Nov 2020 15:47:39 -0800 Subject: [PATCH 21/39] DRY it up --- src/components/AttachmentView.js | 45 ++++++ src/components/BaseImageModal.js | 141 ++++++++++++++++++ src/components/BaseModal.js | 118 --------------- src/components/BaseModalHeader.js | 53 +++++++ .../ImageThumbnailWithModal/index.js | 103 ++----------- .../ImageThumbnailWithModal/index.native.js | 102 ++----------- 6 files changed, 263 insertions(+), 299 deletions(-) create mode 100644 src/components/AttachmentView.js create mode 100644 src/components/BaseImageModal.js delete mode 100644 src/components/BaseModal.js create mode 100644 src/components/BaseModalHeader.js diff --git a/src/components/AttachmentView.js b/src/components/AttachmentView.js new file mode 100644 index 000000000000..aaa7d3100c6d --- /dev/null +++ b/src/components/AttachmentView.js @@ -0,0 +1,45 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from '../styles/StyleSheet'; +import Str from '../libs/Str'; +import PDFView from './PDFView'; +import ImageView from './ImageView'; + +const propTypes = { + // URL to full-sized attachment + sourceURL: PropTypes.string.isRequired, + + // Height of image + imageHeight: PropTypes.number, + + // Width of image + imageWidth: PropTypes.number, +}; + +const defaultProps = { + imageHeight: 200, + imageWidth: 200, +}; + +const AttachmentView = props => ( + <> + {(Str.isPDF(props.sourceURL)) ? ( + + ) : ( + + )} + +); + +AttachmentView.propTypes = propTypes; +AttachmentView.defaultProps = defaultProps; +AttachmentView.displayName = 'AttachmentView'; + +export default AttachmentView; diff --git a/src/components/BaseImageModal.js b/src/components/BaseImageModal.js new file mode 100644 index 000000000000..5b0decbf9689 --- /dev/null +++ b/src/components/BaseImageModal.js @@ -0,0 +1,141 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + View, Image, Modal, TouchableOpacity, Dimensions, TouchableWithoutFeedback +} from 'react-native'; +import BaseModalHeader from './BaseModalHeader'; +import AttachmentView from './AttachmentView'; +import styles from '../styles/StyleSheet'; + +/** + * Modal component + */ + +const propTypes = { + // Should modal go full screen + pinToEdges: PropTypes.bool, + + // Width of the modal + modalWidth: PropTypes.number, + + // Height of the modal + modalHeight: PropTypes.number, + + // URL to image preview + previewSourceURL: PropTypes.string, + + // URL to full-sized image + sourceURL: PropTypes.string, +}; + +const defaultProps = { + pinToEdges: true, + modalWidth: Dimensions.get('window').width * 0.8, + modalHeight: Dimensions.get('window').height * 0.8, + previewSourceURL: '', + sourceURL: '', +}; + +class BaseImageModal extends React.Component { + constructor(props) { + super(props); + + this.state = { + imageWidth: 200, + imageHeight: 200, + thumbnailWidth: 300, + thumbnailHeight: 150, + visible: false, + }; + } + + componentDidMount() { + // Scale image for modal view + Image.getSize(this.props.sourceURL, (width, height) => { + const screenWidth = Dimensions.get('window').width * 0.6; + const scaleFactor = width / screenWidth; + const newImageHeight = height / scaleFactor; + this.setState({imageWidth: screenWidth, imageHeight: newImageHeight}); + }); + + // Scale image for thumbnail preview + Image.getSize(this.props.previewSourceURL, (width, height) => { + const screenWidth = 300; + const scaleFactor = width / screenWidth; + const imageHeight = height / scaleFactor; + this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}); + }); + } + + /** + * Updates the visibility of the modal + * + * @param {Boolean} visibility + */ + setModalVisiblity = (visibility) => { + this.setState({visible: visibility}); + } + + render() { + return ( + <> + this.setModalVisiblity(true)}> + + + + this.setModalVisiblity(false)} + visible={this.state.visible} + transparent + > + {(this.props.pinToEdges) ? ( + + + + + + + ) : ( + this.setModalVisiblity(false)} + > + + + + + + + + + + )} + + + ); + } +} + +BaseImageModal.propTypes = propTypes; +BaseImageModal.defaultProps = defaultProps; +BaseImageModal.displayName = 'BaseModal'; + +export default BaseImageModal; diff --git a/src/components/BaseModal.js b/src/components/BaseModal.js deleted file mode 100644 index 21880314d9ea..000000000000 --- a/src/components/BaseModal.js +++ /dev/null @@ -1,118 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { - View, Image, Modal, TouchableOpacity, Text, Dimensions, TouchableWithoutFeedback -} from 'react-native'; -import exitIcon from '../../assets/images/icon-x.png'; -import styles from '../styles/StyleSheet'; - -/** - * Modal component - */ - -const propTypes = { - // Should modal go full screen - pinToEdges: PropTypes.bool, - - // Title of the modal - title: PropTypes.string, - - // Visibility of modal - visible: PropTypes.bool, - - // Width of the modal - modalWidth: PropTypes.number, - - // Height of the modal - modalHeight: PropTypes.number, - - // Method passed down to update visibility of the modal - setModalVisiblity: PropTypes.func.isRequired, - - // Children of modal component - children: PropTypes.func.isRequired, -}; - -const defaultProps = { - pinToEdges: true, - title: 'Attachment', - visible: false, - modalWidth: Dimensions.get('window').width * 0.8, - modalHeight: Dimensions.get('window').height * 0.8, -}; - -function BaseModal(props) { - const ModalHeader = ( - - - - {props.title} - - - props.setModalVisiblity(false)} - style={[styles.touchableButtonImage, styles.mr0]} - > - - - - - - ); - - return ( - props.setModalVisiblity(false)} - visible={props.visible} - transparent - > - {(props.pinToEdges) ? ( - - {ModalHeader} - - {props.children} - - - ) : ( - props.setModalVisiblity(false)} - > - - - {ModalHeader} - - {props.children} - - - - - )} - - ); -} - -BaseModal.propTypes = propTypes; -BaseModal.defaultProps = defaultProps; -BaseModal.displayName = 'BaseModal'; - -export default BaseModal; diff --git a/src/components/BaseModalHeader.js b/src/components/BaseModalHeader.js new file mode 100644 index 000000000000..4397edb4f437 --- /dev/null +++ b/src/components/BaseModalHeader.js @@ -0,0 +1,53 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {View, Image, TouchableOpacity, Text} from 'react-native'; +import styles from '../styles/StyleSheet'; +import exitIcon from '../../assets/images/icon-x.png'; + +const propTypes = { + // Title of the modal + title: PropTypes.string, + + // Method passed down to update visibility of the modal + setModalVisiblity: PropTypes.func.isRequired, +}; + +const defaultProps = { + title: '', +}; + +const BaseModalHeader = props => ( + + + + {props.title} + + + props.setModalVisiblity(false)} + style={[styles.touchableButtonImage, styles.mr0]} + > + + + + + +); + +BaseModalHeader.propTypes = propTypes; +BaseModalHeader.defaultProps = defaultProps; +BaseModalHeader.displayName = 'BaseModalHeader'; + +export default BaseModalHeader; diff --git a/src/components/ImageThumbnailWithModal/index.js b/src/components/ImageThumbnailWithModal/index.js index 8d20658bae7d..e6ee2b5d90c6 100644 --- a/src/components/ImageThumbnailWithModal/index.js +++ b/src/components/ImageThumbnailWithModal/index.js @@ -1,108 +1,29 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Image, TouchableOpacity, Dimensions -} from 'react-native'; -import styles from '../../styles/StyleSheet'; -import Str from '../../libs/Str'; -import PDFView from '../PDFView'; -import ImageView from '../ImageView'; -import BaseModal from '../BaseModal'; +import BaseImageModal from '../BaseImageModal'; /** * Image modal component that is triggered when pressing on an image - * On web, we indicate that the modal isn't pinned to edges with props to BaseModal + * On native, we indicate that the modal is fullscreen with props to BaseImageModal */ const propTypes = { // URL to image preview - previewSourceURL: PropTypes.string, + previewSourceURL: PropTypes.string.isRequired, // URL to full-sized image - sourceURL: PropTypes.string, + sourceURL: PropTypes.string.isRequired, }; -const defaultProps = { - previewSourceURL: '', - sourceURL: '', -}; - -class ImageThumbnailWithModal extends React.Component { - constructor(props) { - super(props); - - this.state = { - imgWidth: 200, - imgHeight: 200, - thumbnailWidth: 300, - thumbnailHeight: 150, - visible: false, - }; - } - - componentDidMount() { - // Scale image for modal view - Image.getSize(this.props.sourceURL, (width, height) => { - const screenWidth = Dimensions.get('window').width * 0.6; - const scaleFactor = width / screenWidth; - const imageHeight = height / scaleFactor; - this.setState({imgWidth: screenWidth, imgHeight: imageHeight}); - }); - - // Scale image for thumbnail preview - Image.getSize(this.props.previewSourceURL, (width, height) => { - const screenWidth = 300; - const scaleFactor = width / screenWidth; - const imageHeight = height / scaleFactor; - this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}); - }); - } - - /** - * Updates the visibility of the modal - * - * @param {Boolean} visibility - */ - setModalVisiblity = (visibility) => { - this.setState({visible: visibility}); - } - - - render() { - return ( - <> - this.setModalVisiblity(true)}> - - - - - {(Str.isPDF(this.props.sourceURL)) ? ( - - ) : ( - - )} - - - ); - } -} +const ImageThumbnailWithModal = props => ( + +); ImageThumbnailWithModal.propTypes = propTypes; -ImageThumbnailWithModal.defaultProps = defaultProps; +ImageThumbnailWithModal.displayName = 'ImageThumbnailWithModal'; export default ImageThumbnailWithModal; diff --git a/src/components/ImageThumbnailWithModal/index.native.js b/src/components/ImageThumbnailWithModal/index.native.js index 38d077470ed2..665ac1e123ef 100644 --- a/src/components/ImageThumbnailWithModal/index.native.js +++ b/src/components/ImageThumbnailWithModal/index.native.js @@ -1,107 +1,29 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Image, TouchableOpacity, Dimensions -} from 'react-native'; -import styles from '../../styles/StyleSheet'; -import Str from '../../libs/Str'; -import PDFView from '../PDFView'; -import ImageView from '../ImageView'; -import BaseModal from '../BaseModal'; +import BaseImageModal from '../BaseImageModal'; /** * Image modal component that is triggered when pressing on an image - * On native, we indicate that the modal is fullscreen with props to BaseModal + * On native, we indicate that the modal is fullscreen with props to BaseImageModal */ const propTypes = { // URL to image preview - previewSourceURL: PropTypes.string, + previewSourceURL: PropTypes.string.isRequired, // URL to full-sized image - sourceURL: PropTypes.string, + sourceURL: PropTypes.string.isRequired, }; -const defaultProps = { - previewSourceURL: '', - sourceURL: '', -}; - -class ImageThumbnailWithModal extends React.Component { - constructor(props) { - super(props); - - this.state = { - imgWidth: 200, - imgHeight: 200, - thumbnailWidth: 300, - thumbnailHeight: 150, - visible: false, - }; - } - - componentDidMount() { - // Scale image for modal view - Image.getSize(this.props.sourceURL, (width, height) => { - const screenWidth = Dimensions.get('window').width; - const scaleFactor = width / screenWidth; - const imageHeight = height / scaleFactor; - this.setState({imgWidth: screenWidth, imgHeight: imageHeight}); - }); - - // Scale image for thumbnail preview - Image.getSize(this.props.previewSourceURL, (width, height) => { - const screenWidth = 200; - const scaleFactor = width / screenWidth; - const imageHeight = height / scaleFactor; - this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}); - }); - } - - /** - * Updates the visibility of the modal - * - * @param {Boolean} visibility - */ - setModalVisiblity = (visibility) => { - this.setState({visible: visibility}); - } - - render() { - return ( - <> - this.setModalVisiblity(true)}> - - - - - {(Str.isPDF(this.props.sourceURL)) ? ( - - ) : ( - - )} - - - ); - } -} +const ImageThumbnailWithModal = props => ( + +); ImageThumbnailWithModal.propTypes = propTypes; -ImageThumbnailWithModal.defaultProps = defaultProps; +ImageThumbnailWithModal.displayName = 'ImageThumbnailWithModal'; export default ImageThumbnailWithModal; From 34162fef158f3c6f64b452367f54a5eb517c2fe0 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Tue, 10 Nov 2020 14:26:37 -0800 Subject: [PATCH 22/39] move authtoken into component, clean up components --- src/components/BaseImageModal.js | 78 ++++++++++++++----- src/components/BaseModalHeader.js | 4 +- .../ImageThumbnailWithModal/index.js | 10 +++ .../ImageThumbnailWithModal/index.native.js | 10 +++ src/components/ImageView/index.native.js | 12 ++- .../home/report/ReportActionItemFragment.js | 7 +- 6 files changed, 97 insertions(+), 24 deletions(-) diff --git a/src/components/BaseImageModal.js b/src/components/BaseImageModal.js index 5b0decbf9689..059e846e305f 100644 --- a/src/components/BaseImageModal.js +++ b/src/components/BaseImageModal.js @@ -6,6 +6,7 @@ import { import BaseModalHeader from './BaseModalHeader'; import AttachmentView from './AttachmentView'; import styles from '../styles/StyleSheet'; +import {getAuthToken} from '../libs/API'; /** * Modal component @@ -21,19 +22,32 @@ const propTypes = { // Height of the modal modalHeight: PropTypes.number, + // Width of image inside the modal + modalImageWidth: PropTypes.number, + // URL to image preview previewSourceURL: PropTypes.string, // URL to full-sized image sourceURL: PropTypes.string, + + // Is the image an expensify attachment + isExpensifyAttachment: PropTypes.bool, + + // Any additional styles to apply to the image thumbnail + // eslint-disable-next-line react/forbid-prop-types + style: PropTypes.any, }; const defaultProps = { pinToEdges: true, modalWidth: Dimensions.get('window').width * 0.8, modalHeight: Dimensions.get('window').height * 0.8, + modalImageWidth: Dimensions.get('window').width * 0.6, previewSourceURL: '', sourceURL: '', + isExpensifyAttachment: true, + style: {}, }; class BaseImageModal extends React.Component { @@ -41,32 +55,57 @@ class BaseImageModal extends React.Component { super(props); this.state = { - imageWidth: 200, - imageHeight: 200, - thumbnailWidth: 300, - thumbnailHeight: 150, + imageWidth: 300, + imageHeight: 300, + thumbnailWidth: 200, + thumbnailHeight: 200, visible: false, + calculatedImageSize: false, + previewSourceURL: this.props.previewSourceURL, + sourceURL: this.props.sourceURL, }; } componentDidMount() { - // Scale image for modal view - Image.getSize(this.props.sourceURL, (width, height) => { - const screenWidth = Dimensions.get('window').width * 0.6; - const scaleFactor = width / screenWidth; - const newImageHeight = height / scaleFactor; - this.setState({imageWidth: screenWidth, imageHeight: newImageHeight}); - }); + this._isMounted = true; + + // If the images are expensify attachments, add an authtoken so we can access them + if (this.props.isExpensifyAttachment) { + this.setState({ + previewSourceURL: `${this.props.previewSourceURL}?authToken=${getAuthToken()}`, + sourceURL: `${this.props.sourceURL}?authToken=${getAuthToken()}` + }); + } // Scale image for thumbnail preview - Image.getSize(this.props.previewSourceURL, (width, height) => { + Image.getSize(this.state.previewSourceURL, (width, height) => { const screenWidth = 300; const scaleFactor = width / screenWidth; const imageHeight = height / scaleFactor; - this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}); + if (this._isMounted) { + this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}); + } }); } + componentDidUpdate() { + // Only calculate image size if the modal is visible and if we haven't already done this + if (this.state.visible && !this.state.calculatedImageSize) { + Image.getSize(this.state.sourceURL, (width, height) => { + const screenWidth = this.props.pinToEdges ? Dimensions.get('window').width : this.props.modalImageWidth; + const scaleFactor = width / screenWidth; + const imageHeight = height / scaleFactor; + if (this._isMounted) { + this.setState({imageWidth: screenWidth, imageHeight}); + } + }); + } + } + + componentWillUnmount() { + this._isMounted = false; + } + /** * Updates the visibility of the modal * @@ -81,8 +120,12 @@ class BaseImageModal extends React.Component { <> this.setModalVisiblity(true)}> @@ -96,7 +139,7 @@ class BaseImageModal extends React.Component { @@ -119,7 +162,7 @@ class BaseImageModal extends React.Component { @@ -136,6 +179,5 @@ class BaseImageModal extends React.Component { BaseImageModal.propTypes = propTypes; BaseImageModal.defaultProps = defaultProps; -BaseImageModal.displayName = 'BaseModal'; export default BaseImageModal; diff --git a/src/components/BaseModalHeader.js b/src/components/BaseModalHeader.js index 4397edb4f437..e37f981f4ee7 100644 --- a/src/components/BaseModalHeader.js +++ b/src/components/BaseModalHeader.js @@ -1,6 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {View, Image, TouchableOpacity, Text} from 'react-native'; +import { + View, Image, TouchableOpacity, Text +} from 'react-native'; import styles from '../styles/StyleSheet'; import exitIcon from '../../assets/images/icon-x.png'; diff --git a/src/components/ImageThumbnailWithModal/index.js b/src/components/ImageThumbnailWithModal/index.js index e6ee2b5d90c6..de309b0ea184 100644 --- a/src/components/ImageThumbnailWithModal/index.js +++ b/src/components/ImageThumbnailWithModal/index.js @@ -13,6 +13,14 @@ const propTypes = { // URL to full-sized image sourceURL: PropTypes.string.isRequired, + + // Any additional styles to apply to the image thumbnail + // eslint-disable-next-line react/forbid-prop-types + style: PropTypes.any, +}; + +const defaultProps = { + style: {} }; const ImageThumbnailWithModal = props => ( @@ -20,10 +28,12 @@ const ImageThumbnailWithModal = props => ( pinToEdges={false} previewSourceURL={props.previewSourceURL} sourceURL={props.sourceURL} + style={props.style} /> ); ImageThumbnailWithModal.propTypes = propTypes; +ImageThumbnailWithModal.defaultProps = defaultProps; ImageThumbnailWithModal.displayName = 'ImageThumbnailWithModal'; export default ImageThumbnailWithModal; diff --git a/src/components/ImageThumbnailWithModal/index.native.js b/src/components/ImageThumbnailWithModal/index.native.js index 665ac1e123ef..f7a22cd88e43 100644 --- a/src/components/ImageThumbnailWithModal/index.native.js +++ b/src/components/ImageThumbnailWithModal/index.native.js @@ -13,6 +13,14 @@ const propTypes = { // URL to full-sized image sourceURL: PropTypes.string.isRequired, + + // Any additional styles to apply to the image thumbnail + // eslint-disable-next-line react/forbid-prop-types + style: PropTypes.any, +}; + +const defaultProps = { + style: {} }; const ImageThumbnailWithModal = props => ( @@ -20,10 +28,12 @@ const ImageThumbnailWithModal = props => ( pinToEdges previewSourceURL={props.previewSourceURL} sourceURL={props.sourceURL} + style={props.style} /> ); ImageThumbnailWithModal.propTypes = propTypes; +ImageThumbnailWithModal.defaultProps = defaultProps; ImageThumbnailWithModal.displayName = 'ImageThumbnailWithModal'; export default ImageThumbnailWithModal; diff --git a/src/components/ImageView/index.native.js b/src/components/ImageView/index.native.js index 8c8ad96a2088..47d713523d44 100644 --- a/src/components/ImageView/index.native.js +++ b/src/components/ImageView/index.native.js @@ -20,6 +20,12 @@ const propTypes = { // Image width imageWidth: PropTypes.number, + // Window Height + cropHeight: PropTypes.number, + + // Window Width + cropWidth: PropTypes.number, + // Any additional styles to apply // eslint-disable-next-line react/forbid-prop-types style: PropTypes.any, @@ -29,14 +35,16 @@ const defaultProps = { sourceURL: '', imageHeight: 300, imageWidth: 300, + cropHeight: Dimensions.get('window').height, + cropWidth: Dimensions.get('window').width, style: {}, }; const ImageView = props => ( diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 01569855af39..3f3815e2f9ca 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -10,7 +10,6 @@ import styles, {webViewStyles, colors} from '../../../styles/StyleSheet'; import Text from '../../../components/Text'; import AnchorForCommentsOnly from '../../../components/AnchorForCommentsOnly'; import ImageThumbnailWithModal from '../../../components/ImageThumbnailWithModal'; -import {getAuthToken} from '../../../libs/API'; import InlineCodeBlock from '../../../components/InlineCodeBlock'; const propTypes = { @@ -72,6 +71,7 @@ class ReportActionItemFragment extends React.PureComponent { @@ -91,8 +91,9 @@ class ReportActionItemFragment extends React.PureComponent { // We only want to attach auth tokens to images that come from Expensify attachments if (htmlNode.name === 'img' && htmlNode.attribs['data-expensify-source']) { - htmlNode.attribs.preview = `${node.attribs.src}?authToken=${getAuthToken()}`; - htmlNode.attribs.src = `${htmlNode.attribs['data-expensify-source']}?authToken=${getAuthToken()}`; + htmlNode.attribs.preview = node.attribs.src; + htmlNode.attribs.src = htmlNode.attribs['data-expensify-source']; + htmlNode.attribs.isExpensifyAttachment = true; return htmlNode; } } From 979df6f71e615ecf0efbe44e6f98da59383ff7a0 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Tue, 10 Nov 2020 14:32:27 -0800 Subject: [PATCH 23/39] add comments and eslint disable for instance property --- src/components/BaseImageModal.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/BaseImageModal.js b/src/components/BaseImageModal.js index 059e846e305f..7b5167151fed 100644 --- a/src/components/BaseImageModal.js +++ b/src/components/BaseImageModal.js @@ -67,10 +67,15 @@ class BaseImageModal extends React.Component { } componentDidMount() { + // If the component unmounts by the time getSize() is finished, it will throw a warning + // So this is to prevent setting state if the component isn't mounted + + // eslint-disable-next-line no-underscore-dangle this._isMounted = true; // If the images are expensify attachments, add an authtoken so we can access them - if (this.props.isExpensifyAttachment) { + // eslint-disable-next-line no-underscore-dangle + if (this.props.isExpensifyAttachment && this._isMounted) { this.setState({ previewSourceURL: `${this.props.previewSourceURL}?authToken=${getAuthToken()}`, sourceURL: `${this.props.sourceURL}?authToken=${getAuthToken()}` @@ -82,6 +87,8 @@ class BaseImageModal extends React.Component { const screenWidth = 300; const scaleFactor = width / screenWidth; const imageHeight = height / scaleFactor; + + // eslint-disable-next-line no-underscore-dangle if (this._isMounted) { this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}); } @@ -95,6 +102,8 @@ class BaseImageModal extends React.Component { const screenWidth = this.props.pinToEdges ? Dimensions.get('window').width : this.props.modalImageWidth; const scaleFactor = width / screenWidth; const imageHeight = height / scaleFactor; + + // eslint-disable-next-line no-underscore-dangle if (this._isMounted) { this.setState({imageWidth: screenWidth, imageHeight}); } @@ -103,6 +112,7 @@ class BaseImageModal extends React.Component { } componentWillUnmount() { + // eslint-disable-next-line no-underscore-dangle this._isMounted = false; } From e0e3e7498a1058d31ffbf110c875afcc94b64b62 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 12 Nov 2020 10:09:38 -0800 Subject: [PATCH 24/39] don't pass down image thumbnail style, remove _ from variable name --- src/components/BaseImageModal.js | 28 ++++++------------- .../ImageThumbnailWithModal/index.js | 5 ---- .../ImageThumbnailWithModal/index.native.js | 5 ---- .../home/report/ReportActionItemFragment.js | 1 - 4 files changed, 9 insertions(+), 30 deletions(-) diff --git a/src/components/BaseImageModal.js b/src/components/BaseImageModal.js index 7b5167151fed..532a9c958b8e 100644 --- a/src/components/BaseImageModal.js +++ b/src/components/BaseImageModal.js @@ -5,11 +5,12 @@ import { } from 'react-native'; import BaseModalHeader from './BaseModalHeader'; import AttachmentView from './AttachmentView'; -import styles from '../styles/StyleSheet'; +import styles, {webViewStyles} from '../styles/StyleSheet'; import {getAuthToken} from '../libs/API'; /** - * Modal component + * Modal component consisting of an image thumbnail which triggers a modal with a larger image display + * Used for smaller image previews that also need to be viewed full-sized like in report comments */ const propTypes = { @@ -33,10 +34,6 @@ const propTypes = { // Is the image an expensify attachment isExpensifyAttachment: PropTypes.bool, - - // Any additional styles to apply to the image thumbnail - // eslint-disable-next-line react/forbid-prop-types - style: PropTypes.any, }; const defaultProps = { @@ -47,7 +44,6 @@ const defaultProps = { previewSourceURL: '', sourceURL: '', isExpensifyAttachment: true, - style: {}, }; class BaseImageModal extends React.Component { @@ -69,13 +65,10 @@ class BaseImageModal extends React.Component { componentDidMount() { // If the component unmounts by the time getSize() is finished, it will throw a warning // So this is to prevent setting state if the component isn't mounted - - // eslint-disable-next-line no-underscore-dangle - this._isMounted = true; + this.isMounted = true; // If the images are expensify attachments, add an authtoken so we can access them - // eslint-disable-next-line no-underscore-dangle - if (this.props.isExpensifyAttachment && this._isMounted) { + if (this.props.isExpensifyAttachment) { this.setState({ previewSourceURL: `${this.props.previewSourceURL}?authToken=${getAuthToken()}`, sourceURL: `${this.props.sourceURL}?authToken=${getAuthToken()}` @@ -88,8 +81,7 @@ class BaseImageModal extends React.Component { const scaleFactor = width / screenWidth; const imageHeight = height / scaleFactor; - // eslint-disable-next-line no-underscore-dangle - if (this._isMounted) { + if (this.isMounted) { this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}); } }); @@ -103,8 +95,7 @@ class BaseImageModal extends React.Component { const scaleFactor = width / screenWidth; const imageHeight = height / scaleFactor; - // eslint-disable-next-line no-underscore-dangle - if (this._isMounted) { + if (this.isMounted) { this.setState({imageWidth: screenWidth, imageHeight}); } }); @@ -112,8 +103,7 @@ class BaseImageModal extends React.Component { } componentWillUnmount() { - // eslint-disable-next-line no-underscore-dangle - this._isMounted = false; + this.isMounted = false; } /** @@ -132,7 +122,7 @@ class BaseImageModal extends React.Component { ( pinToEdges={false} previewSourceURL={props.previewSourceURL} sourceURL={props.sourceURL} - style={props.style} /> ); diff --git a/src/components/ImageThumbnailWithModal/index.native.js b/src/components/ImageThumbnailWithModal/index.native.js index f7a22cd88e43..16fc0cc951dc 100644 --- a/src/components/ImageThumbnailWithModal/index.native.js +++ b/src/components/ImageThumbnailWithModal/index.native.js @@ -13,10 +13,6 @@ const propTypes = { // URL to full-sized image sourceURL: PropTypes.string.isRequired, - - // Any additional styles to apply to the image thumbnail - // eslint-disable-next-line react/forbid-prop-types - style: PropTypes.any, }; const defaultProps = { @@ -28,7 +24,6 @@ const ImageThumbnailWithModal = props => ( pinToEdges previewSourceURL={props.previewSourceURL} sourceURL={props.sourceURL} - style={props.style} /> ); diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 3f3815e2f9ca..086f90377bf5 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -72,7 +72,6 @@ class ReportActionItemFragment extends React.PureComponent { previewSourceURL={htmlAttribs.preview} sourceURL={htmlAttribs.src} isExpensifyAttachment={htmlAttribs.isExpensifyAttachment} - style={webViewStyles.tagStyles.img} key={passProps.key} /> ), From f586922ecda4a6d4b9eff0fc5af913a138c45db8 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 12 Nov 2020 13:04:09 -0800 Subject: [PATCH 25/39] add another component for the modal view --- src/components/AttachmentView.js | 2 +- src/components/BaseImageModal.js | 84 +++++-------------- src/components/BaseModalHeader.js | 7 +- .../ImageThumbnailWithModal/index.js | 11 --- .../ImageThumbnailWithModal/index.native.js | 5 -- src/components/ModalView.js | 77 +++++++++++++++++ 6 files changed, 105 insertions(+), 81 deletions(-) create mode 100644 src/components/ModalView.js diff --git a/src/components/AttachmentView.js b/src/components/AttachmentView.js index aaa7d3100c6d..ce20f46f7e03 100644 --- a/src/components/AttachmentView.js +++ b/src/components/AttachmentView.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; +import Str from 'js-libs/lib/str'; import styles from '../styles/StyleSheet'; -import Str from '../libs/Str'; import PDFView from './PDFView'; import ImageView from './ImageView'; diff --git a/src/components/BaseImageModal.js b/src/components/BaseImageModal.js index 532a9c958b8e..db2d1a7c719a 100644 --- a/src/components/BaseImageModal.js +++ b/src/components/BaseImageModal.js @@ -1,12 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import { - View, Image, Modal, TouchableOpacity, Dimensions, TouchableWithoutFeedback + View, Image, Modal, TouchableOpacity, Dimensions } from 'react-native'; -import BaseModalHeader from './BaseModalHeader'; import AttachmentView from './AttachmentView'; import styles, {webViewStyles} from '../styles/StyleSheet'; -import {getAuthToken} from '../libs/API'; +import ModalView from './ModalView'; /** * Modal component consisting of an image thumbnail which triggers a modal with a larger image display @@ -31,19 +30,15 @@ const propTypes = { // URL to full-sized image sourceURL: PropTypes.string, - - // Is the image an expensify attachment - isExpensifyAttachment: PropTypes.bool, }; const defaultProps = { - pinToEdges: true, + pinToEdges: false, modalWidth: Dimensions.get('window').width * 0.8, modalHeight: Dimensions.get('window').height * 0.8, modalImageWidth: Dimensions.get('window').width * 0.6, previewSourceURL: '', sourceURL: '', - isExpensifyAttachment: true, }; class BaseImageModal extends React.Component { @@ -57,31 +52,21 @@ class BaseImageModal extends React.Component { thumbnailHeight: 200, visible: false, calculatedImageSize: false, - previewSourceURL: this.props.previewSourceURL, - sourceURL: this.props.sourceURL, }; } componentDidMount() { // If the component unmounts by the time getSize() is finished, it will throw a warning // So this is to prevent setting state if the component isn't mounted - this.isMounted = true; - - // If the images are expensify attachments, add an authtoken so we can access them - if (this.props.isExpensifyAttachment) { - this.setState({ - previewSourceURL: `${this.props.previewSourceURL}?authToken=${getAuthToken()}`, - sourceURL: `${this.props.sourceURL}?authToken=${getAuthToken()}` - }); - } + this.isComponentMounted = true; // Scale image for thumbnail preview - Image.getSize(this.state.previewSourceURL, (width, height) => { + Image.getSize(this.props.previewSourceURL, (width, height) => { const screenWidth = 300; const scaleFactor = width / screenWidth; const imageHeight = height / scaleFactor; - if (this.isMounted) { + if (this.isComponentMounted) { this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}); } }); @@ -90,12 +75,12 @@ class BaseImageModal extends React.Component { componentDidUpdate() { // Only calculate image size if the modal is visible and if we haven't already done this if (this.state.visible && !this.state.calculatedImageSize) { - Image.getSize(this.state.sourceURL, (width, height) => { + Image.getSize(this.props.sourceURL, (width, height) => { const screenWidth = this.props.pinToEdges ? Dimensions.get('window').width : this.props.modalImageWidth; const scaleFactor = width / screenWidth; const imageHeight = height / scaleFactor; - if (this.isMounted) { + if (this.isComponentMounted) { this.setState({imageWidth: screenWidth, imageHeight}); } }); @@ -103,7 +88,7 @@ class BaseImageModal extends React.Component { } componentWillUnmount() { - this.isMounted = false; + this.isComponentMounted = false; } /** @@ -120,7 +105,7 @@ class BaseImageModal extends React.Component { <> this.setModalVisiblity(true)}> - {(this.props.pinToEdges) ? ( - - - - - + this.setModalVisiblity(false)} + > + + - ) : ( - this.setModalVisiblity(false)} - > - - - - - - - - - - )} + ); diff --git a/src/components/BaseModalHeader.js b/src/components/BaseModalHeader.js index e37f981f4ee7..96d0514df660 100644 --- a/src/components/BaseModalHeader.js +++ b/src/components/BaseModalHeader.js @@ -10,12 +10,13 @@ const propTypes = { // Title of the modal title: PropTypes.string, - // Method passed down to update visibility of the modal - setModalVisiblity: PropTypes.func.isRequired, + // Method to trigger when pressing close button of the modal + onCloseButtonPress: PropTypes.func, }; const defaultProps = { title: '', + onCloseButtonPress: null, }; const BaseModalHeader = props => ( @@ -34,7 +35,7 @@ const BaseModalHeader = props => ( props.setModalVisiblity(false)} + onPress={props.onCloseButtonPress} style={[styles.touchableButtonImage, styles.mr0]} > ( ); ImageThumbnailWithModal.propTypes = propTypes; -ImageThumbnailWithModal.defaultProps = defaultProps; ImageThumbnailWithModal.displayName = 'ImageThumbnailWithModal'; export default ImageThumbnailWithModal; diff --git a/src/components/ImageThumbnailWithModal/index.native.js b/src/components/ImageThumbnailWithModal/index.native.js index 16fc0cc951dc..665ac1e123ef 100644 --- a/src/components/ImageThumbnailWithModal/index.native.js +++ b/src/components/ImageThumbnailWithModal/index.native.js @@ -15,10 +15,6 @@ const propTypes = { sourceURL: PropTypes.string.isRequired, }; -const defaultProps = { - style: {} -}; - const ImageThumbnailWithModal = props => ( ( ); ImageThumbnailWithModal.propTypes = propTypes; -ImageThumbnailWithModal.defaultProps = defaultProps; ImageThumbnailWithModal.displayName = 'ImageThumbnailWithModal'; export default ImageThumbnailWithModal; diff --git a/src/components/ModalView.js b/src/components/ModalView.js new file mode 100644 index 000000000000..df4b9b10959e --- /dev/null +++ b/src/components/ModalView.js @@ -0,0 +1,77 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + View, TouchableOpacity, Dimensions, TouchableWithoutFeedback +} from 'react-native'; +import BaseModalHeader from './BaseModalHeader'; +import styles from '../styles/StyleSheet'; + +/** + * Renders an adjustable modal view and the children inside of it + * This component should be placed inside of a component for adjusting view of the modal + */ + +const propTypes = { + // Should modal go full screen + pinToEdges: PropTypes.bool, + + // Width of the modal if it isn't full screen + modalWidth: PropTypes.number, + + // Height of the modal if it isn't full screen + modalHeight: PropTypes.number, + + // Method to trigger when pressing close button of the modal + onCloseButtonPress: PropTypes.func, + + // Any children to display + children: PropTypes.node, +}; + +const defaultProps = { + pinToEdges: false, + modalWidth: Dimensions.get('window').width * 0.8, + modalHeight: Dimensions.get('window').height * 0.8, + onCloseButtonPress: null, + children: null, +}; + +const ModalView = props => ( + <> + {props.pinToEdges ? ( + + + {props.children} + + ) : ( + + + + + {props.children} + + + + + )} + +); + +ModalView.propTypes = propTypes; +ModalView.defaultProps = defaultProps; +ModalView.displayName = 'ModalView'; + +export default ModalView; From 1a0a8fbf81348776f910424c1aa2bacfdcabf1eb Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Fri, 13 Nov 2020 09:24:01 -0800 Subject: [PATCH 26/39] remove alter node, rename css classes, move down comments --- package-lock.json | 12 +++---- package.json | 6 ++-- src/components/BaseImageModal.js | 2 +- src/components/BaseModalHeader.js | 2 +- src/components/ImageView/index.native.js | 14 ++++---- src/components/ModalView.js | 6 ++-- src/components/PDFView/index.js | 14 ++++---- src/components/PDFView/index.native.js | 14 ++++---- .../home/report/ReportActionItemFragment.js | 32 ++++--------------- src/styles/StyleSheet.js | 18 ++--------- 10 files changed, 45 insertions(+), 75 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b631de8a83f..30670fa00e59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3134,14 +3134,14 @@ "integrity": "sha512-NAkkT68oF+M9o6El2xeUqZK7magPjG/tAcEbvCbqyhlh3yElKWnI1e1vpbVvFXzTefy67FwYFWOJqBN6U7Mnkg==" }, "@react-native-community/progress-bar-android": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@react-native-community/progress-bar-android/-/progress-bar-android-1.0.3.tgz", - "integrity": "sha512-Xy+nTCibCLQ6Jy0YNlVwrO+m/j2el6PN8nVQcdgVqp+Jm1XTguq7xjxUV5spzYmVDIVx+zW7WKfkeSbhUk91Tg==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@react-native-community/progress-bar-android/-/progress-bar-android-1.0.4.tgz", + "integrity": "sha512-O7UCpXDXlpxIx2eusj6bc8noJ5KyXZjnad0NQig9myq6iV4D9uGVziSj6J2v46dTnMd9iTX60szBu1LvsUO6rg==" }, "@react-native-community/progress-view": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@react-native-community/progress-view/-/progress-view-1.2.1.tgz", - "integrity": "sha512-x8YqINP5TsdKKTY3LV4QJT2gmFsRrrIbI3uJeq7T/np9jHtZLxJ85EZdaLyZGy/m0YZjP0fWxU1jLPgUtsYW4w==" + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@react-native-community/progress-view/-/progress-view-1.2.3.tgz", + "integrity": "sha512-CW7eOhxduIxA723aZlKMOnBEz1o5Cjo5ibMNsf81TcjNqtsamRHm8jFS98za7t5P7XdhM0MhzkbaS90cwOlN+Q==" }, "@react-native-community/push-notification-ios": { "version": "1.6.1", diff --git a/package.json b/package.json index 913a8db7aac9..24aae7b2210e 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,8 @@ "dependencies": { "@react-native-community/async-storage": "^1.11.0", "@react-native-community/netinfo": "^5.9.6", - "@react-native-community/progress-bar-android": "^1.0.3", - "@react-native-community/progress-view": "^1.2.1", + "@react-native-community/progress-bar-android": "^1.0.4", + "@react-native-community/progress-view": "^1.2.3", "@react-native-community/push-notification-ios": "^1.5.0", "@react-native-firebase/analytics": "^7.6.7", "@react-native-firebase/app": "^8.4.5", @@ -41,7 +41,6 @@ "file-loader": "^6.0.0", "html-entities": "^1.3.1", "js-libs": "git+https://git@github.com:Expensify/JS-Libs.git#92b874eed3640e7635f7342f8169ddf8f28ca7e4", - "react-native-onyx": "git+https://git@github.com:Expensify/react-native-onyx.git#0cc1d0e18cf15d0d6fd3d3e05a18fcbd917abefd", "lodash.get": "^4.4.2", "lodash.has": "^4.5.2", "lodash.merge": "^4.6.2", @@ -58,6 +57,7 @@ "react-native-image-pan-zoom": "^2.1.12", "react-native-image-picker": "^2.3.3", "react-native-keyboard-spacer": "^0.4.1", + "react-native-onyx": "git+https://git@github.com:Expensify/react-native-onyx.git#0cc1d0e18cf15d0d6fd3d3e05a18fcbd917abefd", "react-native-pdf": "^6.2.2", "react-native-render-html": "^4.2.3", "react-native-safe-area-context": "^3.1.4", diff --git a/src/components/BaseImageModal.js b/src/components/BaseImageModal.js index db2d1a7c719a..bafe51577e5c 100644 --- a/src/components/BaseImageModal.js +++ b/src/components/BaseImageModal.js @@ -96,7 +96,7 @@ class BaseImageModal extends React.Component { * * @param {Boolean} visibility */ - setModalVisiblity = (visibility) => { + setModalVisiblity(visibility) { this.setState({visible: visibility}); } diff --git a/src/components/BaseModalHeader.js b/src/components/BaseModalHeader.js index 96d0514df660..ee1c158f4474 100644 --- a/src/components/BaseModalHeader.js +++ b/src/components/BaseModalHeader.js @@ -20,7 +20,7 @@ const defaultProps = { }; const BaseModalHeader = props => ( - + ( ( <> {props.pinToEdges ? ( - + ( ) : ( ( ( tags, the attribute is used to be more cross-platform friendly this.customRenderers = { @@ -69,34 +68,18 @@ class ReportActionItemFragment extends React.PureComponent { ), img: (htmlAttribs, children, convertedCSSStyles, passProps) => ( ), }; } - /** - * Function to edit HTML on the fly before it's rendered, currently this attaches authTokens as a URL parameter to - * load image attachments. - * - * @param {object} node - * @returns {object} - */ - alterNode(node) { - const htmlNode = node; - - // We only want to attach auth tokens to images that come from Expensify attachments - if (htmlNode.name === 'img' && htmlNode.attribs['data-expensify-source']) { - htmlNode.attribs.preview = node.attribs.src; - htmlNode.attribs.src = htmlNode.attribs['data-expensify-source']; - htmlNode.attribs.isExpensifyAttachment = true; - return htmlNode; - } - } - render() { const {fragment} = this.props; const maxImageDimensions = 512; @@ -126,7 +109,6 @@ class ReportActionItemFragment extends React.PureComponent { tagsStyles={webViewStyles.tagStyles} onLinkPress={(event, href) => Linking.openURL(href)} html={fragment.html} - alterNode={this.alterNode} imagesMaxWidth={Math.min(maxImageDimensions, windowWidth * 0.8)} imagesInitialDimensions={{width: maxImageDimensions, height: maxImageDimensions}} /> diff --git a/src/styles/StyleSheet.js b/src/styles/StyleSheet.js index 9b27ea58dbaf..03fcb09fc8a1 100644 --- a/src/styles/StyleSheet.js +++ b/src/styles/StyleSheet.js @@ -855,14 +855,7 @@ const styles = { fontWeight: '700', }, - imageModal: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: colors.black, - }, - - imageModalContainer: { + modalViewContainer: { backgroundColor: colors.componentBG, borderColor: colors.border, borderWidth: 1, @@ -870,7 +863,7 @@ const styles = { height: '100%', }, - imageModalHeader: { + modalHeaderBar: { overflow: 'hidden', height: 87, justifyContent: 'center', @@ -884,7 +877,7 @@ const styles = { backgroundColor: colors.componentBG, }, - imageModalCenterContainer: { + modalCenterContentContainer: { flex: 1, flexDirection: 'column', justifyContent: 'center', @@ -899,11 +892,6 @@ const styles = { alignItems: 'center', overflow: 'hidden', }, - - imageModalImageContainer: { - margin: 50, - overflow: 'hidden', - }, }; const baseCodeTagStyles = { From 22d9e2eca922e22f604a7eba4c112c98d1ba62d7 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Fri, 13 Nov 2020 12:48:12 -0800 Subject: [PATCH 27/39] update hash for js-libs and fix image path for dev --- ios/Podfile.lock | 8 ++-- package-lock.json | 6 +-- package.json | 2 +- .../home/report/ReportActionItemFragment.js | 45 ++++++++++++++++--- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index aa7bcae05789..46598fe33667 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -332,9 +332,9 @@ PODS: - React-Core - react-native-pdf (6.2.2): - React-Core - - react-native-progress-bar-android (1.0.3): + - react-native-progress-bar-android (1.0.4): - React - - react-native-progress-view (1.2.1): + - react-native-progress-view (1.2.3): - React - react-native-safe-area-context (3.1.8): - React-Core @@ -646,8 +646,8 @@ SPEC CHECKSUMS: react-native-image-picker: 32d1ad2c0024ca36161ae0d5c2117e2d6c441f11 react-native-netinfo: e36c1bb6df27ab84aa933679b3f5bbf9d180b18f react-native-pdf: 4b5a9e4465a6a3b399e91dc4838eb44ddf716d1f - react-native-progress-bar-android: d1b109b86c699d36f6a7099dab1a117dc1d66d3c - react-native-progress-view: 0b937488a5730aeec461b00c4eb438e1fe626015 + react-native-progress-bar-android: ce95a69f11ac580799021633071368d08aaf9ad8 + react-native-progress-view: 5816e8a6be812c2b122c6225a2a3db82d9008640 react-native-safe-area-context: 01158a92c300895d79dee447e980672dc3fb85a6 react-native-webview: 2e330b109bfd610e9818bf7865d1979f898960a7 React-RCTActionSheet: 53ea72699698b0b47a6421cb1c8b4ab215a774aa diff --git a/package-lock.json b/package-lock.json index 30670fa00e59..a35b4b067673 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15468,8 +15468,8 @@ } }, "js-libs": { - "version": "git+ssh://git@github.com/Expensify/JS-Libs.git#92b874eed3640e7635f7342f8169ddf8f28ca7e4", - "from": "git+ssh://git@github.com/Expensify/JS-Libs.git#92b874eed3640e7635f7342f8169ddf8f28ca7e4", + "version": "git+ssh://git@github.com/Expensify/JS-Libs.git#346c72907bcd32c215227f00ec4db555a0a2a6aa", + "from": "git+ssh://git@github.com/Expensify/JS-Libs.git#346c72907bcd32c215227f00ec4db555a0a2a6aa", "requires": { "classnames": "2.2.5", "clipboard": "2.0.4", @@ -19357,7 +19357,7 @@ "from": "git+ssh://git@github.com/Expensify/react-native-onyx.git#0cc1d0e18cf15d0d6fd3d3e05a18fcbd917abefd", "requires": { "@react-native-community/async-storage": "^1.12.1", - "js-libs": "git+ssh://git@github.com/Expensify/JS-Libs.git#92b874eed3640e7635f7342f8169ddf8f28ca7e4", + "js-libs": "git+ssh://git@github.com/Expensify/JS-Libs.git#346c72907bcd32c215227f00ec4db555a0a2a6aa", "lodash.merge": "^4.6.2", "react": "^17.0.1", "underscore": "^1.11.0" diff --git a/package.json b/package.json index 24aae7b2210e..b04d51979bb5 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "electron-updater": "^4.3.4", "file-loader": "^6.0.0", "html-entities": "^1.3.1", - "js-libs": "git+https://git@github.com:Expensify/JS-Libs.git#92b874eed3640e7635f7342f8169ddf8f28ca7e4", + "js-libs": "git+https://git@github.com:Expensify/JS-Libs.git#346c72907bcd32c215227f00ec4db555a0a2a6aa", "lodash.get": "^4.4.2", "lodash.has": "^4.5.2", "lodash.merge": "^4.6.2", diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index cf9f574721e5..d1bded7b8714 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -12,6 +12,7 @@ import AnchorForCommentsOnly from '../../../components/AnchorForCommentsOnly'; import ImageThumbnailWithModal from '../../../components/ImageThumbnailWithModal'; import InlineCodeBlock from '../../../components/InlineCodeBlock'; import {getAuthToken} from '../../../libs/API'; +import Config from '../../../CONFIG'; const propTypes = { // The message fragment needing to be displayed @@ -33,6 +34,8 @@ class ReportActionItemFragment extends React.PureComponent { constructor(props) { super(props); + this.alterNode = this.alterNode.bind(this); + // Define the custom render methods // For tags, the attribute is used to be more cross-platform friendly this.customRenderers = { @@ -68,18 +71,47 @@ class ReportActionItemFragment extends React.PureComponent { ), img: (htmlAttribs, children, convertedCSSStyles, passProps) => ( ), }; } + /** + * Function to edit HTML on the fly before it's rendered, currently this attaches authTokens as a URL parameter to + * load image attachments and updates the image URL if the config is not on production. + * + * @param {object} node + * @returns {object} + */ + alterNode(node) { + const htmlNode = node; + + if (htmlNode.name === 'img') { + let previewSource = htmlNode.attribs['data-expensify-source'] + ? `${htmlNode.attribs.src}?authToken=${getAuthToken()}` + : htmlNode.attribs.src; + + let source = htmlNode.attribs['data-expensify-source'] + ? `${htmlNode.attribs['data-expensify-source']}?authToken=${getAuthToken()}` + : htmlNode.attribs.src; + + // Update the image URL so the images can be accessed in a dev envrionment + if (!Config.IS_IN_PRODUCTION) { + const devAPIURL = Config.EXPENSIFY.API_ROOT.replace('/api?', ''); + const imageURLHostname = 'https://www.expensify.com.dev'; + previewSource = previewSource.replace(imageURLHostname, devAPIURL); + source = source.replace(imageURLHostname, devAPIURL); + } + + htmlNode.attribs.preview = previewSource; + htmlNode.attribs.src = source; + return htmlNode; + } + } + render() { const {fragment} = this.props; const maxImageDimensions = 512; @@ -109,6 +141,7 @@ class ReportActionItemFragment extends React.PureComponent { tagsStyles={webViewStyles.tagStyles} onLinkPress={(event, href) => Linking.openURL(href)} html={fragment.html} + alterNode={this.alterNode} imagesMaxWidth={Math.min(maxImageDimensions, windowWidth * 0.8)} imagesInitialDimensions={{width: maxImageDimensions, height: maxImageDimensions}} /> From 78c8f16fc6dd9d9f1e6955719f78e3ba187d971e Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Fri, 13 Nov 2020 14:58:44 -0800 Subject: [PATCH 28/39] remove alternode --- ios/Podfile.lock | 20 +++--- src/components/AttachmentView.js | 2 +- src/components/BaseImageModal.js | 2 +- .../home/report/ReportActionItemFragment.js | 68 +++++++------------ 4 files changed, 37 insertions(+), 55 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 46598fe33667..41268e47b633 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -642,14 +642,14 @@ SPEC CHECKSUMS: React-jsiexecutor: b56c03e61c0dd5f5801255f2160a815f4a53d451 React-jsinspector: 8e68ffbfe23880d3ee9bafa8be2777f60b25cbe2 react-native-config: 9a061347e0136fdb32d43a34d60999297d672361 - react-native-document-picker: b3e78a8f7fef98b5cb069f20fc35797d55e68e28 - react-native-image-picker: 32d1ad2c0024ca36161ae0d5c2117e2d6c441f11 - react-native-netinfo: e36c1bb6df27ab84aa933679b3f5bbf9d180b18f + react-native-document-picker: 0bba80cc56caab1f67dbaa81ff557e3a9b7f2b9f + react-native-image-picker: c6d75c4ab2cf46f9289f341242b219cb3c1180d3 + react-native-netinfo: 250dc0ca126512f618a8a2ca6a936577e1f66586 react-native-pdf: 4b5a9e4465a6a3b399e91dc4838eb44ddf716d1f - react-native-progress-bar-android: ce95a69f11ac580799021633071368d08aaf9ad8 - react-native-progress-view: 5816e8a6be812c2b122c6225a2a3db82d9008640 - react-native-safe-area-context: 01158a92c300895d79dee447e980672dc3fb85a6 - react-native-webview: 2e330b109bfd610e9818bf7865d1979f898960a7 + react-native-progress-bar-android: be43138ab7da30d51fc038bafa98e9ed594d0c40 + react-native-progress-view: 21b1e29e70c7559c16c9e0a04c4adc19fce6ede2 + react-native-safe-area-context: 79fea126c6830c85f65947c223a5e3058a666937 + react-native-webview: 73e409ecc987c189772bda27ba7f0e2f3dcd2e81 React-RCTActionSheet: 53ea72699698b0b47a6421cb1c8b4ab215a774aa React-RCTAnimation: 1befece0b5183c22ae01b966f5583f42e69a83c2 React-RCTBlob: 0b284339cbe4b15705a05e2313a51c6d8b51fa40 @@ -661,12 +661,12 @@ SPEC CHECKSUMS: React-RCTVibration: 8e9fb25724a0805107fc1acc9075e26f814df454 ReactCommon: 4167844018c9ed375cc01a843e9ee564399e53c3 rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba - RNCAsyncStorage: cb9a623793918c6699586281f0b51cbc38f046f9 - RNCPushNotificationIOS: cfc6e1821f1af1874741d9b7055b75c0f8fa8a7d + RNCAsyncStorage: b03032fdbdb725bea0bd9e5ec5a7272865ae7398 + RNCPushNotificationIOS: 346c804c13fb99e644c996d1af5175a35452b2cb RNFBAnalytics: 2dc4dd9e2445faffca041b10447a23a71dcdabf8 RNFBApp: 7eacc7da7ab19f96c05e434017d44a9f09410da8 RNFBCrashlytics: 4870c14cf8833053b6b5648911abefe1923854d2 - urbanairship-react-native: fa123940041a6a13ab7dac192e32833c53754f00 + urbanairship-react-native: 87a82abfbc182a854f66eb7fd25198ce62e95a54 Yoga: 7d13633d129fd179e01b8953d38d47be90db185a YogaKit: f782866e155069a2cca2517aafea43200b01fd5a diff --git a/src/components/AttachmentView.js b/src/components/AttachmentView.js index ce20f46f7e03..0235a9de404b 100644 --- a/src/components/AttachmentView.js +++ b/src/components/AttachmentView.js @@ -23,7 +23,7 @@ const defaultProps = { const AttachmentView = props => ( <> - {(Str.isPDF(props.sourceURL)) ? ( + {Str.isPDF(props.sourceURL) ? ( tags, the attribute is used to be more cross-platform friendly this.customRenderers = { @@ -69,49 +67,34 @@ class ReportActionItemFragment extends React.PureComponent { {children} ), - img: (htmlAttribs, children, convertedCSSStyles, passProps) => ( - - ), + img: (htmlAttribs, children, convertedCSSStyles, passProps) => { + // Attaches authTokens as a URL parameter to load image attachments + let previewSource = htmlAttribs['data-expensify-source'] + ? `${htmlAttribs.src}?authToken=${getAuthToken()}` + : htmlAttribs.src; + + let source = htmlAttribs['data-expensify-source'] + ? `${htmlAttribs['data-expensify-source']}?authToken=${getAuthToken()}` + : htmlAttribs.src; + + // Update the image URL so the images can be accessed in a dev envrionment + if (!Config.IS_IN_PRODUCTION) { + const devAPIURL = Config.EXPENSIFY.API_ROOT.replace('/api?', ''); + const imageURLHostname = 'https://www.expensify.com.dev'; + previewSource = previewSource.replace(imageURLHostname, devAPIURL); + source = source.replace(imageURLHostname, devAPIURL); + } + return ( + + ); + }, }; } - /** - * Function to edit HTML on the fly before it's rendered, currently this attaches authTokens as a URL parameter to - * load image attachments and updates the image URL if the config is not on production. - * - * @param {object} node - * @returns {object} - */ - alterNode(node) { - const htmlNode = node; - - if (htmlNode.name === 'img') { - let previewSource = htmlNode.attribs['data-expensify-source'] - ? `${htmlNode.attribs.src}?authToken=${getAuthToken()}` - : htmlNode.attribs.src; - - let source = htmlNode.attribs['data-expensify-source'] - ? `${htmlNode.attribs['data-expensify-source']}?authToken=${getAuthToken()}` - : htmlNode.attribs.src; - - // Update the image URL so the images can be accessed in a dev envrionment - if (!Config.IS_IN_PRODUCTION) { - const devAPIURL = Config.EXPENSIFY.API_ROOT.replace('/api?', ''); - const imageURLHostname = 'https://www.expensify.com.dev'; - previewSource = previewSource.replace(imageURLHostname, devAPIURL); - source = source.replace(imageURLHostname, devAPIURL); - } - - htmlNode.attribs.preview = previewSource; - htmlNode.attribs.src = source; - return htmlNode; - } - } - render() { const {fragment} = this.props; const maxImageDimensions = 512; @@ -141,7 +124,6 @@ class ReportActionItemFragment extends React.PureComponent { tagsStyles={webViewStyles.tagStyles} onLinkPress={(event, href) => Linking.openURL(href)} html={fragment.html} - alterNode={this.alterNode} imagesMaxWidth={Math.min(maxImageDimensions, windowWidth * 0.8)} imagesInitialDimensions={{width: maxImageDimensions, height: maxImageDimensions}} /> From 74ed27a4b9035346ecc5578e0ab37e8c90d77598 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Fri, 13 Nov 2020 15:21:55 -0800 Subject: [PATCH 29/39] update the padding and some styles --- src/components/BaseImageModal.js | 17 ++++++++++++----- src/components/BaseModalHeader.js | 2 +- src/components/ImageView/index.native.js | 2 +- src/styles/StyleSheet.js | 9 ++++++++- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/components/BaseImageModal.js b/src/components/BaseImageModal.js index 3bebb2636d48..f3143a125662 100644 --- a/src/components/BaseImageModal.js +++ b/src/components/BaseImageModal.js @@ -62,7 +62,7 @@ class BaseImageModal extends React.Component { // Scale image for thumbnail preview Image.getSize(this.props.previewSourceURL, (width, height) => { - const screenWidth = 300; + const screenWidth = 250; const scaleFactor = width / screenWidth; const imageHeight = height / scaleFactor; @@ -76,12 +76,19 @@ class BaseImageModal extends React.Component { // Only calculate image size if the modal is visible and if we haven't already done this if (this.state.visible && !this.state.calculatedImageSize) { Image.getSize(this.props.sourceURL, (width, height) => { - const screenWidth = this.props.pinToEdges ? Dimensions.get('window').width : this.props.modalImageWidth; - const scaleFactor = width / screenWidth; - const imageHeight = height / scaleFactor; + const modalWidth = this.props.pinToEdges ? Dimensions.get('window').width : this.props.modalImageWidth; + let imageHeight = height; + let imageWidth = width; + + // Only resize if the image width is larger than the modal width + if (width > modalWidth) { + const scaleFactor = width / modalWidth; + imageHeight = height / scaleFactor; + imageWidth = modalWidth; + } if (this.isComponentMounted) { - this.setState({imageWidth: screenWidth, imageHeight, calculatedImageSize: true}); + this.setState({imageWidth, imageHeight, calculatedImageSize: true}); } }); } diff --git a/src/components/BaseModalHeader.js b/src/components/BaseModalHeader.js index ee1c158f4474..e13c1e810521 100644 --- a/src/components/BaseModalHeader.js +++ b/src/components/BaseModalHeader.js @@ -40,7 +40,7 @@ const BaseModalHeader = props => ( > diff --git a/src/components/ImageView/index.native.js b/src/components/ImageView/index.native.js index 12e0dc9c7c7c..3cbed19e44a8 100644 --- a/src/components/ImageView/index.native.js +++ b/src/components/ImageView/index.native.js @@ -28,7 +28,7 @@ const defaultProps = { sourceURL: '', imageHeight: 300, imageWidth: 300, - cropHeight: Dimensions.get('window').height, + cropHeight: Dimensions.get('window').height - 60, cropWidth: Dimensions.get('window').width, style: {}, }; diff --git a/src/styles/StyleSheet.js b/src/styles/StyleSheet.js index 03fcb09fc8a1..06bd98a157c7 100644 --- a/src/styles/StyleSheet.js +++ b/src/styles/StyleSheet.js @@ -571,6 +571,11 @@ const styles = { width: 18, }, + attachmentCloseIcon: { + height: 20, + width: 20, + }, + chatContent: { flex: 4, justifyContent: 'flex-end', @@ -865,11 +870,13 @@ const styles = { modalHeaderBar: { overflow: 'hidden', - height: 87, + height: 60, justifyContent: 'center', display: 'flex', paddingLeft: 20, paddingRight: 20, + borderBottomWidth: 1, + borderColor: colors.border, }, imageModalPDF: { From 5e4769b61b73cdd08c1f08fdd652195f935fe3af Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Fri, 13 Nov 2020 15:22:53 -0800 Subject: [PATCH 30/39] add comment about the cropHeight --- src/components/ImageView/index.native.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/ImageView/index.native.js b/src/components/ImageView/index.native.js index 3cbed19e44a8..5af8f233170a 100644 --- a/src/components/ImageView/index.native.js +++ b/src/components/ImageView/index.native.js @@ -28,6 +28,8 @@ const defaultProps = { sourceURL: '', imageHeight: 300, imageWidth: 300, + + // Default cropHeight accounts for the modal header height of 60 cropHeight: Dimensions.get('window').height - 60, cropWidth: Dimensions.get('window').width, style: {}, From 95f5a7ff987a57856c5fde93f0b367e14c2f86ac Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Mon, 16 Nov 2020 11:10:32 -0800 Subject: [PATCH 31/39] update the styling of modal title --- assets/images/icon-x--20x20.png | Bin 0 -> 774 bytes src/CONST.js | 2 ++ src/components/BaseModalHeader.js | 8 +++++--- src/components/ImageView/index.native.js | 4 ++-- src/pages/home/report/ReportActionItemFragment.js | 7 ++++--- src/styles/StyleSheet.js | 6 ++++-- 6 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 assets/images/icon-x--20x20.png diff --git a/assets/images/icon-x--20x20.png b/assets/images/icon-x--20x20.png new file mode 100644 index 0000000000000000000000000000000000000000..de40ebbf8642a681bc397d56389cf4dc1f190293 GIT binary patch literal 774 zcmV+h1Nr=kP)hqV0z$;X>E-1yW;iN-ql58br!xdaHnLClmdkTMcy?1%voTB#4kjQ#Bz#vE zZfc?@y+!u6D4{f4_#&MR8Vxwx_FO}{ME15Qfpi0yxF+-;e2y^qIh&nnKPQ?nXPqtB?8r_bA~9xZ3My|G?}OH9AH4G$6a;0o*I^2RWD8V;n+a07*qoM6N<$ Ef^ITZo&W#< literal 0 HcmV?d00001 diff --git a/src/CONST.js b/src/CONST.js index aec83345c7d2..580f927d8441 100644 --- a/src/CONST.js +++ b/src/CONST.js @@ -1,9 +1,11 @@ const CLOUDFRONT_URL = 'https://d2k5nsl2zxldvw.cloudfront.net'; const MOZILLA_PDF_VIEWER_URL = 'http://mozilla.github.com/pdf.js/web/viewer.html'; +const EXPENSIFY_DEV_URL = 'https://www.expensify.com.dev'; const CONST = { CLOUDFRONT_URL, MOZILLA_PDF_VIEWER_URL, + EXPENSIFY_DEV_URL, EXPENSIFY_ICON_URL: `${CLOUDFRONT_URL}/images/favicon-2019.png`, }; diff --git a/src/components/BaseModalHeader.js b/src/components/BaseModalHeader.js index e13c1e810521..cf320268a0a2 100644 --- a/src/components/BaseModalHeader.js +++ b/src/components/BaseModalHeader.js @@ -4,7 +4,7 @@ import { View, Image, TouchableOpacity, Text } from 'react-native'; import styles from '../styles/StyleSheet'; -import exitIcon from '../../assets/images/icon-x.png'; +import exitIcon from '../../assets/images/icon-x--20x20.png'; const propTypes = { // Title of the modal @@ -30,8 +30,10 @@ const BaseModalHeader = props => ( styles.overflowHidden ]} > - - {props.title} + + + {props.title} + Date: Mon, 16 Nov 2020 13:50:45 -0800 Subject: [PATCH 32/39] make changes based on review comments --- src/components/BaseImageModal.js | 34 ++++++++++++++----- src/components/BaseModalHeader.js | 2 +- .../ImageThumbnailWithModal/index.js | 1 + .../ImageThumbnailWithModal/index.native.js | 1 + src/components/ImageView/index.native.js | 14 ++++---- src/components/ModalView.js | 10 ++++-- src/components/PDFView/index.native.js | 4 +-- 7 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/components/BaseImageModal.js b/src/components/BaseImageModal.js index f3143a125662..c6a56530bff5 100644 --- a/src/components/BaseImageModal.js +++ b/src/components/BaseImageModal.js @@ -25,6 +25,9 @@ const propTypes = { // Width of image inside the modal modalImageWidth: PropTypes.number, + // Title of the modal header + modalTitle: PropTypes.string, + // URL to image preview previewSourceURL: PropTypes.string, @@ -34,9 +37,15 @@ const propTypes = { const defaultProps = { pinToEdges: false, + + // If pinToEdges is false, the default modal width and height will take up about 80% of the screen modalWidth: Dimensions.get('window').width * 0.8, modalHeight: Dimensions.get('window').height * 0.8, + + // The image inside the modal shouldn't span the entire width of the modal + // unless it is full screen so the default is 20% smaller than the width of the modal modalImageWidth: Dimensions.get('window').width * 0.6, + modalTitle: '', previewSourceURL: '', sourceURL: '', }; @@ -50,8 +59,7 @@ class BaseImageModal extends React.Component { imageHeight: 300, thumbnailWidth: 200, thumbnailHeight: 200, - visible: false, - calculatedImageSize: false, + isModalOpen: false, }; } @@ -60,21 +68,27 @@ class BaseImageModal extends React.Component { // So this is to prevent setting state if the component isn't mounted this.isComponentMounted = true; + // Property to check if we have already calculated the image size for inside the modal + // so we don't need to grab the image and resize it again + this.calculatedModalImageSize = false; + // Scale image for thumbnail preview Image.getSize(this.props.previewSourceURL, (width, height) => { - const screenWidth = 250; - const scaleFactor = width / screenWidth; + // Width of the thumbnail works better as a constant than it does + // a percentage of the screen width since it is relative to each screen + const thumbnailScreenWidth = 250; + const scaleFactor = width / thumbnailScreenWidth; const imageHeight = height / scaleFactor; if (this.isComponentMounted) { - this.setState({thumbnailWidth: screenWidth, thumbnailHeight: imageHeight}); + this.setState({thumbnailWidth: thumbnailScreenWidth, thumbnailHeight: imageHeight}); } }); } componentDidUpdate() { // Only calculate image size if the modal is visible and if we haven't already done this - if (this.state.visible && !this.state.calculatedImageSize) { + if (this.state.isModalOpen && !this.calculatedModalImageSize) { Image.getSize(this.props.sourceURL, (width, height) => { const modalWidth = this.props.pinToEdges ? Dimensions.get('window').width : this.props.modalImageWidth; let imageHeight = height; @@ -88,7 +102,8 @@ class BaseImageModal extends React.Component { } if (this.isComponentMounted) { - this.setState({imageWidth, imageHeight, calculatedImageSize: true}); + this.setState({imageWidth, imageHeight}); + this.calculatedModalImageSize = true; } }); } @@ -104,7 +119,7 @@ class BaseImageModal extends React.Component { * @param {Boolean} visibility */ setModalVisiblity(visibility) { - this.setState({visible: visibility}); + this.setState({isModalOpen: visibility}); } render() { @@ -123,13 +138,14 @@ class BaseImageModal extends React.Component { this.setModalVisiblity(false)} - visible={this.state.visible} + visible={this.state.isModalOpen} transparent > this.setModalVisiblity(false)} > diff --git a/src/components/BaseModalHeader.js b/src/components/BaseModalHeader.js index cf320268a0a2..a698cef0ae41 100644 --- a/src/components/BaseModalHeader.js +++ b/src/components/BaseModalHeader.js @@ -16,7 +16,7 @@ const propTypes = { const defaultProps = { title: '', - onCloseButtonPress: null, + onCloseButtonPress: () => {}, }; const BaseModalHeader = props => ( diff --git a/src/components/ImageThumbnailWithModal/index.js b/src/components/ImageThumbnailWithModal/index.js index 52e5acb886c7..668532af477b 100644 --- a/src/components/ImageThumbnailWithModal/index.js +++ b/src/components/ImageThumbnailWithModal/index.js @@ -14,6 +14,7 @@ const ImageThumbnailWithModal = props => ( ); diff --git a/src/components/ImageThumbnailWithModal/index.native.js b/src/components/ImageThumbnailWithModal/index.native.js index 665ac1e123ef..d6ecb4a8ac96 100644 --- a/src/components/ImageThumbnailWithModal/index.native.js +++ b/src/components/ImageThumbnailWithModal/index.native.js @@ -20,6 +20,7 @@ const ImageThumbnailWithModal = props => ( pinToEdges previewSourceURL={props.previewSourceURL} sourceURL={props.sourceURL} + modalTitle="Attachment" /> ); diff --git a/src/components/ImageView/index.native.js b/src/components/ImageView/index.native.js index 78c1158ba15c..dc17a897dd83 100644 --- a/src/components/ImageView/index.native.js +++ b/src/components/ImageView/index.native.js @@ -14,10 +14,10 @@ const propTypes = { imageWidth: PropTypes.number, // Window Height - cropHeight: PropTypes.number, + windowHeight: PropTypes.number, // Window Width - cropWidth: PropTypes.number, + windowWidth: PropTypes.number, // Any additional styles to apply // eslint-disable-next-line react/forbid-prop-types @@ -29,9 +29,9 @@ const defaultProps = { imageHeight: 300, imageWidth: 300, - // Default cropHeight accounts for the modal header height of 73 - cropHeight: Dimensions.get('window').height - 73, - cropWidth: Dimensions.get('window').width, + // Default windowHeight accounts for the modal header height of 73 + windowHeight: Dimensions.get('window').height - 73, + windowWidth: Dimensions.get('window').width, style: {}, }; @@ -45,8 +45,8 @@ const defaultProps = { const ImageView = props => ( diff --git a/src/components/ModalView.js b/src/components/ModalView.js index ef739aa89ba9..f68d80cd67af 100644 --- a/src/components/ModalView.js +++ b/src/components/ModalView.js @@ -24,6 +24,9 @@ const propTypes = { // Method to trigger when pressing close button of the modal onCloseButtonPress: PropTypes.func, + // Title of the modal header + modalTitle: PropTypes.string, + // Any children to display children: PropTypes.node, }; @@ -32,7 +35,8 @@ const defaultProps = { pinToEdges: false, modalWidth: Dimensions.get('window').width * 0.8, modalHeight: Dimensions.get('window').height * 0.8, - onCloseButtonPress: null, + onCloseButtonPress: () => {}, + modalTitle: '', children: null, }; @@ -52,7 +56,7 @@ const ModalView = props => ( activeOpacity={1} onPress={props.onCloseButtonPress} > - + ( height: props.modalHeight }} > - + {props.children} diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index 303bec57761a..2abea83b554a 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import {View, Dimensions} from 'react-native'; -import Pdf from 'react-native-pdf'; +import PDF from 'react-native-pdf'; import styles from '../../styles/StyleSheet'; const propTypes = { @@ -27,7 +27,7 @@ const defaultProps = { const PDFView = props => ( - Date: Tue, 17 Nov 2020 10:04:04 -0800 Subject: [PATCH 33/39] update .env variables --- .env.example | 4 ++-- .env.production | 4 ++-- src/CONFIG.js | 6 +++++- src/CONST.js | 2 -- src/components/AttachmentView.js | 2 +- src/libs/HttpUtils.js | 2 +- src/pages/home/report/ReportActionItemFragment.js | 7 +++---- tests/unit/mocks/react-native-config.js | 3 ++- 8 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.env.example b/.env.example index 0007bef5b6d9..d8a8be9eff7f 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ /** * Rename this file to `.env` and put your local config in here */ -EXPENSIFY_API_ROOT=https://www.expensify.com.dev/api? +EXPENSIFY_SITE_ROOT=https://expensify.com.dev/ EXPENSIFY_PARTNER_NAME=android EXPENSIFY_PARTNER_PASSWORD=c3a9ac418ea3f152aae2 -PUSHER_APP_KEY=ac6d22b891daae55283a +PUSHER_APP_KEY=ac6d22b891daae55283a \ No newline at end of file diff --git a/.env.production b/.env.production index 3e10ed31bd0e..853cd2d51138 100644 --- a/.env.production +++ b/.env.production @@ -1,5 +1,5 @@ -EXPENSIFY_SITE_ROOT=https://chat.expensify.com/ -EXPENSIFY_API_ROOT=https://www.expensify.com/api? +EXPENSIFY_CASH_SITE_ROOT=https://chat.expensify.com/ +EXPENSIFY_SITE_ROOT=https://expensify.com/ EXPENSIFY_PARTNER_NAME=chat-expensify-com EXPENSIFY_PARTNER_PASSWORD=e21965746fd75f82bb66 PUSHER_APP_KEY=268df511a204fbb60884 diff --git a/src/CONFIG.js b/src/CONFIG.js index 7dcc2f4a01ae..611a540b0272 100644 --- a/src/CONFIG.js +++ b/src/CONFIG.js @@ -4,7 +4,11 @@ import Config from 'react-native-config'; export default { AUTH_TOKEN_EXPIRATION_TIME: 1000 * 60 * 90, EXPENSIFY: { - API_ROOT: Config.EXPENSIFY_API_ROOT, + API_ROOT: Config.NGROK_URL + ? `${Config.NGROK_URL}/api?` + : `${Config.EXPENSIFY_SITE_ROOT}api?`, + NGROK_URL: Config.NGROK_URL, + CASH_SITE_ROOT: Config.EXPENSIFY_CASH_ROOT, SITE_ROOT: Config.EXPENSIFY_SITE_ROOT, PARTNER_NAME: Config.EXPENSIFY_PARTNER_NAME, PARTNER_PASSWORD: Config.EXPENSIFY_PARTNER_PASSWORD, diff --git a/src/CONST.js b/src/CONST.js index 580f927d8441..aec83345c7d2 100644 --- a/src/CONST.js +++ b/src/CONST.js @@ -1,11 +1,9 @@ const CLOUDFRONT_URL = 'https://d2k5nsl2zxldvw.cloudfront.net'; const MOZILLA_PDF_VIEWER_URL = 'http://mozilla.github.com/pdf.js/web/viewer.html'; -const EXPENSIFY_DEV_URL = 'https://www.expensify.com.dev'; const CONST = { CLOUDFRONT_URL, MOZILLA_PDF_VIEWER_URL, - EXPENSIFY_DEV_URL, EXPENSIFY_ICON_URL: `${CLOUDFRONT_URL}/images/favicon-2019.png`, }; diff --git a/src/components/AttachmentView.js b/src/components/AttachmentView.js index 0235a9de404b..8b681b3c77a1 100644 --- a/src/components/AttachmentView.js +++ b/src/components/AttachmentView.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Str from 'js-libs/lib/str'; +import Str from 'expensify-common/lib/str'; import styles from '../styles/StyleSheet'; import PDFView from './PDFView'; import ImageView from './ImageView'; diff --git a/src/libs/HttpUtils.js b/src/libs/HttpUtils.js index 130713dea731..6719149324d1 100644 --- a/src/libs/HttpUtils.js +++ b/src/libs/HttpUtils.js @@ -54,7 +54,7 @@ function xhr(command, data, type = 'post') { * @returns {Promise} */ function download(relativePath) { - const siteRoot = CONFIG.EXPENSIFY.SITE_ROOT; + const siteRoot = CONFIG.EXPENSIFY.CASH_SITE_ROOT; // Strip leading slashes and periods from relative path, if present const strippedRelativePath = relativePath.charAt(0) === '/' || relativePath.charAt(0) === '.' diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index dba8fac202ba..56693f7cf21b 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -13,7 +13,6 @@ import ImageThumbnailWithModal from '../../../components/ImageThumbnailWithModal import InlineCodeBlock from '../../../components/InlineCodeBlock'; import {getAuthToken} from '../../../libs/API'; import Config from '../../../CONFIG'; -import CONST from '../../../CONST'; const propTypes = { // The message fragment needing to be displayed @@ -80,9 +79,9 @@ class ReportActionItemFragment extends React.PureComponent { // Update the image URL so the images can be accessed in a dev envrionment if (!Config.IS_IN_PRODUCTION) { - const devAPIURL = Config.EXPENSIFY.API_ROOT.replace('/api?', ''); - previewSource = previewSource.replace(CONST.EXPENSIFY_DEV_URL, devAPIURL); - source = source.replace(CONST.EXPENSIFY_DEV_URL, devAPIURL); + const devURL = Config.EXPENSIFY.NGROK_URL || Config.EXPENSIFY.SITE_ROOT; + previewSource = previewSource.replace(Config.EXPENSIFY.SITE_ROOT, devURL); + source = source.replace(Config.EXPENSIFY.SITE_ROOT, devURL); } return ( diff --git a/tests/unit/mocks/react-native-config.js b/tests/unit/mocks/react-native-config.js index 2c287d369256..1da5a5b93de2 100644 --- a/tests/unit/mocks/react-native-config.js +++ b/tests/unit/mocks/react-native-config.js @@ -1,10 +1,11 @@ export default { REPORT_IDS: '1,2', - EXPENSIFY_API_ROOT: 'https://www.expensify.com.dev/api?', + EXPENSIFY_SITE_ROOT: 'https://expensify.com.dev/', EXPENSIFY_PARTNER_NAME: 'android', EXPENSIFY_PARTNER_PASSWORD: 'c3a9ac418ea3f152aae2', PUSHER_APP_KEY: 'ac6d22b891daae55283a', PUSHER_AUTH_URL: 'https://www.expensify.com.dev', + NGROK_URL: '', PARTNER_USER_ID: '', PARTNER_USER_SECRET: '' }; From 6f56f04ab500a4d1404bbee4fbd1cb62604e8397 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Wed, 18 Nov 2020 09:58:28 -0800 Subject: [PATCH 34/39] update config and .env variables --- .env.example | 6 ++++-- .env.production | 4 ++-- src/CONFIG.js | 15 ++++++++++----- src/pages/home/report/ReportActionItemFragment.js | 9 ++++++--- tests/unit/mocks/react-native-config.js | 1 + 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/.env.example b/.env.example index d8a8be9eff7f..875cf0d7259b 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,9 @@ /** * Rename this file to `.env` and put your local config in here */ -EXPENSIFY_SITE_ROOT=https://expensify.com.dev/ +EXPENSIFY_SITE_ROOT=https://www.expensify.com.dev/ EXPENSIFY_PARTNER_NAME=android EXPENSIFY_PARTNER_PASSWORD=c3a9ac418ea3f152aae2 -PUSHER_APP_KEY=ac6d22b891daae55283a \ No newline at end of file +PUSHER_APP_KEY=ac6d22b891daae55283a +NGROK_URL= +USE_NGROK=false diff --git a/.env.production b/.env.production index 853cd2d51138..68edfc619949 100644 --- a/.env.production +++ b/.env.production @@ -1,5 +1,5 @@ -EXPENSIFY_CASH_SITE_ROOT=https://chat.expensify.com/ -EXPENSIFY_SITE_ROOT=https://expensify.com/ +EXPENSIFY_CASH_SITE_ROOT=https://www.chat.expensify.com/ +EXPENSIFY_SITE_ROOT=https://www.expensify.com/ EXPENSIFY_PARTNER_NAME=chat-expensify-com EXPENSIFY_PARTNER_PASSWORD=e21965746fd75f82bb66 PUSHER_APP_KEY=268df511a204fbb60884 diff --git a/src/CONFIG.js b/src/CONFIG.js index 611a540b0272..3f5187233f5f 100644 --- a/src/CONFIG.js +++ b/src/CONFIG.js @@ -1,15 +1,20 @@ import {Platform} from 'react-native'; import Config from 'react-native-config'; +// Updates the API_ROOT and SITE_ROOT to use the NGROK route if .env flag is enabled +// Otherwise it will use the value inside config.EXPENSIFY_SITE_ROOT +// DEFAULT_SITE_ROOT will always contain the default site root of www.expensify.com or www.expensify.com.dev +const expensifySiteURL = Config.USE_NGROK === 'true' && Config.NGROK_URL + ? Config.NGROK_URL + : Config.EXPENSIFY_SITE_ROOT; + export default { AUTH_TOKEN_EXPIRATION_TIME: 1000 * 60 * 90, EXPENSIFY: { - API_ROOT: Config.NGROK_URL - ? `${Config.NGROK_URL}/api?` - : `${Config.EXPENSIFY_SITE_ROOT}api?`, - NGROK_URL: Config.NGROK_URL, + DEFAULT_SITE_ROOT: Config.EXPENSIFY_SITE_ROOT, + SITE_ROOT: expensifySiteURL, CASH_SITE_ROOT: Config.EXPENSIFY_CASH_ROOT, - SITE_ROOT: Config.EXPENSIFY_SITE_ROOT, + API_ROOT: `${expensifySiteURL}api?`, PARTNER_NAME: Config.EXPENSIFY_PARTNER_NAME, PARTNER_PASSWORD: Config.EXPENSIFY_PARTNER_PASSWORD, }, diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 56693f7cf21b..e0cde9dbd74f 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -78,10 +78,13 @@ class ReportActionItemFragment extends React.PureComponent { : htmlAttribs.src; // Update the image URL so the images can be accessed in a dev envrionment + // URLs do not need to be changed in production if (!Config.IS_IN_PRODUCTION) { - const devURL = Config.EXPENSIFY.NGROK_URL || Config.EXPENSIFY.SITE_ROOT; - previewSource = previewSource.replace(Config.EXPENSIFY.SITE_ROOT, devURL); - source = source.replace(Config.EXPENSIFY.SITE_ROOT, devURL); + previewSource = previewSource.replace( + Config.EXPENSIFY.DEFAULT_SITE_ROOT, + Config.EXPENSIFY.SITE_ROOT + ); + source = source.replace(Config.EXPENSIFY.DEFAULT_SITE_ROOT, Config.EXPENSIFY.SITE_ROOT); } return ( diff --git a/tests/unit/mocks/react-native-config.js b/tests/unit/mocks/react-native-config.js index 1da5a5b93de2..595eb0388573 100644 --- a/tests/unit/mocks/react-native-config.js +++ b/tests/unit/mocks/react-native-config.js @@ -6,6 +6,7 @@ export default { PUSHER_APP_KEY: 'ac6d22b891daae55283a', PUSHER_AUTH_URL: 'https://www.expensify.com.dev', NGROK_URL: '', + USE_NGROK: false, PARTNER_USER_ID: '', PARTNER_USER_SECRET: '' }; From 71804f9b6005de3b8b812f52d2c918f6fed6e3c2 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Wed, 18 Nov 2020 13:30:52 -0800 Subject: [PATCH 35/39] review comments --- .env.example | 2 +- src/components/BaseImageModal.js | 10 ++++++---- src/components/ImageView/index.js | 7 +++---- src/components/ImageView/index.native.js | 18 +++++++----------- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/.env.example b/.env.example index 875cf0d7259b..5fc1b02d8336 100644 --- a/.env.example +++ b/.env.example @@ -5,5 +5,5 @@ EXPENSIFY_SITE_ROOT=https://www.expensify.com.dev/ EXPENSIFY_PARTNER_NAME=android EXPENSIFY_PARTNER_PASSWORD=c3a9ac418ea3f152aae2 PUSHER_APP_KEY=ac6d22b891daae55283a -NGROK_URL= +NGROK_URL=https://expensify-user.ngrok.io/ USE_NGROK=false diff --git a/src/components/BaseImageModal.js b/src/components/BaseImageModal.js index c6a56530bff5..3d4e8c394054 100644 --- a/src/components/BaseImageModal.js +++ b/src/components/BaseImageModal.js @@ -61,6 +61,12 @@ class BaseImageModal extends React.Component { thumbnailHeight: 200, isModalOpen: false, }; + + this.setModalVisiblity = this.setModalVisiblity.bind(this); + + // Property to check if we have already calculated the image size for inside the modal + // so we don't need to grab the image and resize it again + this.calculatedModalImageSize = false; } componentDidMount() { @@ -68,10 +74,6 @@ class BaseImageModal extends React.Component { // So this is to prevent setting state if the component isn't mounted this.isComponentMounted = true; - // Property to check if we have already calculated the image size for inside the modal - // so we don't need to grab the image and resize it again - this.calculatedModalImageSize = false; - // Scale image for thumbnail preview Image.getSize(this.props.previewSourceURL, (width, height) => { // Width of the thumbnail works better as a constant than it does diff --git a/src/components/ImageView/index.js b/src/components/ImageView/index.js index 1c2abdaa5cc4..2a1ddcb9f255 100644 --- a/src/components/ImageView/index.js +++ b/src/components/ImageView/index.js @@ -13,19 +13,18 @@ const propTypes = { imageWidth: PropTypes.number, // Any additional styles to apply - // eslint-disable-next-line react/forbid-prop-types - style: PropTypes.any, + wrapperStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), }; const defaultProps = { sourceURL: '', imageHeight: 300, imageWidth: 300, - style: {}, + wrapperStyle: {}, }; const ImageView = props => ( - + ( - + Date: Thu, 19 Nov 2020 11:25:40 -0800 Subject: [PATCH 36/39] update config and readme --- .env.example | 2 +- .env.production | 4 ++-- README.md | 12 +++++++++++- src/CONFIG.js | 17 ++++++++--------- src/libs/HttpUtils.js | 4 ++-- src/libs/Pusher/pusher.js | 2 +- .../home/report/ReportActionItemFragment.js | 9 ++++++--- 7 files changed, 31 insertions(+), 19 deletions(-) diff --git a/.env.example b/.env.example index 5fc1b02d8336..662b2590df5b 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ /** * Rename this file to `.env` and put your local config in here */ -EXPENSIFY_SITE_ROOT=https://www.expensify.com.dev/ +EXPENSIFY_URL_COM=https://www.expensify.com.dev/ EXPENSIFY_PARTNER_NAME=android EXPENSIFY_PARTNER_PASSWORD=c3a9ac418ea3f152aae2 PUSHER_APP_KEY=ac6d22b891daae55283a diff --git a/.env.production b/.env.production index 68edfc619949..c3bf418016d6 100644 --- a/.env.production +++ b/.env.production @@ -1,5 +1,5 @@ -EXPENSIFY_CASH_SITE_ROOT=https://www.chat.expensify.com/ -EXPENSIFY_SITE_ROOT=https://www.expensify.com/ +EXPENSIFY_URL_CASH=https://www.chat.expensify.com/ +EXPENSIFY_URL_COM=https://www.expensify.com/ EXPENSIFY_PARTNER_NAME=chat-expensify-com EXPENSIFY_PARTNER_PASSWORD=e21965746fd75f82bb66 PUSHER_APP_KEY=268df511a204fbb60884 diff --git a/README.md b/README.md index ca7e9beef4dc..42b1a9360321 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,16 @@ This application is built with the following principles. You can use any IDE or code editing tool for developing on any platform. Use your favorite! +## Setting up ngrok +Ngrok makes the our locally-hosted web application appear to be hosted on a subdomain of ngrok.com. This allows us to avoid many of our cross-domain issues with our API and is required for doing local development on android or viewing images on mobile. + +1. Set up a permanent [ngrok route](https://stackoverflow.com/c/expensify/questions/3382) +2. Replace the value `NGROK_URL` in your `.env` file with the ngrok route you just set up +3. Set the `USE_NGROK` in your `.env` to true +4. Start ngrok with the name you previously set (`Expensidev/script/ngrok.sh thienlnam`) + +Now, all of your API calls will be using the ngrok route. + ## Running the web app 🕸 * To run a **Development Server**: `npm run web` * To build a **production build**: `npm run build` @@ -69,7 +79,7 @@ You can use any IDE or code editing tool for developing on any platform. Use you ## Running the Android app 🤖 * To install the Android dependencies, run: `npm install`, then `gradle` will install all linked dependencies * Running via `ngrok` is required to communicate with the API - * Start ngrok (`Expensidev/script/ngrok.sh`), replace `expensify.com.dev` value in `.env` with your ngrok value + * Follow the instructions under the section `Setting up ngrok` * To run a on a **Development Emulator**: `npm run android` * Changes applied to Javascript will be applied automatically, any changes to native code will require a recompile diff --git a/src/CONFIG.js b/src/CONFIG.js index 3f5187233f5f..b3e6a7907399 100644 --- a/src/CONFIG.js +++ b/src/CONFIG.js @@ -1,20 +1,19 @@ import {Platform} from 'react-native'; import Config from 'react-native-config'; -// Updates the API_ROOT and SITE_ROOT to use the NGROK route if .env flag is enabled -// Otherwise it will use the value inside config.EXPENSIFY_SITE_ROOT -// DEFAULT_SITE_ROOT will always contain the default site root of www.expensify.com or www.expensify.com.dev -const expensifySiteURL = Config.USE_NGROK === 'true' && Config.NGROK_URL +// Ngrok helps us avoid many of our cross-domain issues with connecting to our API +// and is reqired for viewing images on mobile and for developing on android +// To enable, set the USE_NGROK value to true in dev and update the NGROK_URL +const expensifyAPIPath = Config.USE_NGROK === 'true' && Config.NGROK_URL ? Config.NGROK_URL - : Config.EXPENSIFY_SITE_ROOT; + : Config.EXPENSIFY_URL_COM; export default { AUTH_TOKEN_EXPIRATION_TIME: 1000 * 60 * 90, EXPENSIFY: { - DEFAULT_SITE_ROOT: Config.EXPENSIFY_SITE_ROOT, - SITE_ROOT: expensifySiteURL, - CASH_SITE_ROOT: Config.EXPENSIFY_CASH_ROOT, - API_ROOT: `${expensifySiteURL}api?`, + URL_EXPENSIFY_COM: Config.EXPENSIFY_URL_COM, + URL_EXPENSIFY_API: expensifyAPIPath, + URL_EXPENSIFY_CASH: Config.EXPENSIFY_URL_CASH, PARTNER_NAME: Config.EXPENSIFY_PARTNER_NAME, PARTNER_PASSWORD: Config.EXPENSIFY_PARTNER_PASSWORD, }, diff --git a/src/libs/HttpUtils.js b/src/libs/HttpUtils.js index 6719149324d1..201ae52690ef 100644 --- a/src/libs/HttpUtils.js +++ b/src/libs/HttpUtils.js @@ -44,7 +44,7 @@ function processHTTPRequest(url, method = 'get', body = null) { function xhr(command, data, type = 'post') { const formData = new FormData(); _.each(data, (val, key) => formData.append(key, val)); - return processHTTPRequest(`${CONFIG.EXPENSIFY.API_ROOT}command=${command}`, type, formData); + return processHTTPRequest(`${CONFIG.EXPENSIFY.URL_EXPENSIFY_API}api?command=${command}`, type, formData); } /** @@ -54,7 +54,7 @@ function xhr(command, data, type = 'post') { * @returns {Promise} */ function download(relativePath) { - const siteRoot = CONFIG.EXPENSIFY.CASH_SITE_ROOT; + const siteRoot = CONFIG.EXPENSIFY.URL_EXPENSIFY_CASH; // Strip leading slashes and periods from relative path, if present const strippedRelativePath = relativePath.charAt(0) === '/' || relativePath.charAt(0) === '.' diff --git a/src/libs/Pusher/pusher.js b/src/libs/Pusher/pusher.js index eb74aaffc024..cebdbb230594 100644 --- a/src/libs/Pusher/pusher.js +++ b/src/libs/Pusher/pusher.js @@ -39,7 +39,7 @@ function init(appKey, params) { const options = { cluster: CONFIG.PUSHER.CLUSTER, - authEndpoint: `${CONFIG.EXPENSIFY.API_ROOT}command=Push_Authenticate`, + authEndpoint: `${CONFIG.EXPENSIFY.URL_EXPENSIFY_API}api?command=Push_Authenticate`, }; if (customAuthorizer) { diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index e0cde9dbd74f..732f4acb43aa 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -81,10 +81,13 @@ class ReportActionItemFragment extends React.PureComponent { // URLs do not need to be changed in production if (!Config.IS_IN_PRODUCTION) { previewSource = previewSource.replace( - Config.EXPENSIFY.DEFAULT_SITE_ROOT, - Config.EXPENSIFY.SITE_ROOT + Config.EXPENSIFY.URL_EXPENSIFY_COM, + Config.EXPENSIFY.URL_EXPENSIFY_API + ); + source = source.replace( + Config.EXPENSIFY.URL_EXPENSIFY_COM, + Config.EXPENSIFY.URL_EXPENSIFY_API ); - source = source.replace(Config.EXPENSIFY.DEFAULT_SITE_ROOT, Config.EXPENSIFY.SITE_ROOT); } return ( From eceebb3831265a8ab51efb2daa0cc5d41f2589e9 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 19 Nov 2020 11:27:06 -0800 Subject: [PATCH 37/39] add .env variable --- .env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.example b/.env.example index 662b2590df5b..3e27aa1604da 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,7 @@ * Rename this file to `.env` and put your local config in here */ EXPENSIFY_URL_COM=https://www.expensify.com.dev/ +EXPENSIFY_URL_CASH=https://www.chat.expensify.com/ EXPENSIFY_PARTNER_NAME=android EXPENSIFY_PARTNER_PASSWORD=c3a9ac418ea3f152aae2 PUSHER_APP_KEY=ac6d22b891daae55283a From 1c97737e77fc482142814a3e56ca931640c0fdc5 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 19 Nov 2020 11:34:19 -0800 Subject: [PATCH 38/39] small comment update --- src/CONFIG.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CONFIG.js b/src/CONFIG.js index b3e6a7907399..d8821b9e9cd2 100644 --- a/src/CONFIG.js +++ b/src/CONFIG.js @@ -3,7 +3,7 @@ import Config from 'react-native-config'; // Ngrok helps us avoid many of our cross-domain issues with connecting to our API // and is reqired for viewing images on mobile and for developing on android -// To enable, set the USE_NGROK value to true in dev and update the NGROK_URL +// To enable, set the USE_NGROK value to true in .env and update the NGROK_URL const expensifyAPIPath = Config.USE_NGROK === 'true' && Config.NGROK_URL ? Config.NGROK_URL : Config.EXPENSIFY_URL_COM; From 37bd307321c159187c0ae0fc866aa2d38f4e147d Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 19 Nov 2020 12:56:56 -0800 Subject: [PATCH 39/39] make config variable update and small comment change --- src/CONFIG.js | 4 ++-- src/libs/HttpUtils.js | 2 +- src/libs/Pusher/pusher.js | 2 +- .../home/report/ReportActionItemFragment.js | 21 ++++++++----------- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/CONFIG.js b/src/CONFIG.js index d8821b9e9cd2..26876ddb4f18 100644 --- a/src/CONFIG.js +++ b/src/CONFIG.js @@ -4,7 +4,7 @@ import Config from 'react-native-config'; // Ngrok helps us avoid many of our cross-domain issues with connecting to our API // and is reqired for viewing images on mobile and for developing on android // To enable, set the USE_NGROK value to true in .env and update the NGROK_URL -const expensifyAPIPath = Config.USE_NGROK === 'true' && Config.NGROK_URL +const expensifyURLRoot = Config.USE_NGROK === 'true' && Config.NGROK_URL ? Config.NGROK_URL : Config.EXPENSIFY_URL_COM; @@ -12,8 +12,8 @@ export default { AUTH_TOKEN_EXPIRATION_TIME: 1000 * 60 * 90, EXPENSIFY: { URL_EXPENSIFY_COM: Config.EXPENSIFY_URL_COM, - URL_EXPENSIFY_API: expensifyAPIPath, URL_EXPENSIFY_CASH: Config.EXPENSIFY_URL_CASH, + URL_API_ROOT: expensifyURLRoot, PARTNER_NAME: Config.EXPENSIFY_PARTNER_NAME, PARTNER_PASSWORD: Config.EXPENSIFY_PARTNER_PASSWORD, }, diff --git a/src/libs/HttpUtils.js b/src/libs/HttpUtils.js index 201ae52690ef..ce06022489d1 100644 --- a/src/libs/HttpUtils.js +++ b/src/libs/HttpUtils.js @@ -44,7 +44,7 @@ function processHTTPRequest(url, method = 'get', body = null) { function xhr(command, data, type = 'post') { const formData = new FormData(); _.each(data, (val, key) => formData.append(key, val)); - return processHTTPRequest(`${CONFIG.EXPENSIFY.URL_EXPENSIFY_API}api?command=${command}`, type, formData); + return processHTTPRequest(`${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=${command}`, type, formData); } /** diff --git a/src/libs/Pusher/pusher.js b/src/libs/Pusher/pusher.js index cebdbb230594..c797a84dc493 100644 --- a/src/libs/Pusher/pusher.js +++ b/src/libs/Pusher/pusher.js @@ -39,7 +39,7 @@ function init(appKey, params) { const options = { cluster: CONFIG.PUSHER.CLUSTER, - authEndpoint: `${CONFIG.EXPENSIFY.URL_EXPENSIFY_API}api?command=Push_Authenticate`, + authEndpoint: `${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=Push_Authenticate`, }; if (customAuthorizer) { diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 732f4acb43aa..856b75ddd767 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -77,18 +77,15 @@ class ReportActionItemFragment extends React.PureComponent { ? `${htmlAttribs['data-expensify-source']}?authToken=${getAuthToken()}` : htmlAttribs.src; - // Update the image URL so the images can be accessed in a dev envrionment - // URLs do not need to be changed in production - if (!Config.IS_IN_PRODUCTION) { - previewSource = previewSource.replace( - Config.EXPENSIFY.URL_EXPENSIFY_COM, - Config.EXPENSIFY.URL_EXPENSIFY_API - ); - source = source.replace( - Config.EXPENSIFY.URL_EXPENSIFY_COM, - Config.EXPENSIFY.URL_EXPENSIFY_API - ); - } + // Update the image URL so the images can be accessed depending on the config environment + previewSource = previewSource.replace( + Config.EXPENSIFY.URL_EXPENSIFY_COM, + Config.EXPENSIFY.URL_API_ROOT + ); + source = source.replace( + Config.EXPENSIFY.URL_EXPENSIFY_COM, + Config.EXPENSIFY.URL_API_ROOT + ); return (