From 996c24576ab62d299f0e096a637d30f7ef180938 Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Fri, 16 Jun 2023 20:55:07 +1200 Subject: [PATCH 001/223] pushing a draft PR to change component to function for thumbnail images --- src/components/ThumbnailImage.js | 94 +++++++++++++++----------------- 1 file changed, 44 insertions(+), 50 deletions(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 5ac519c3bfbc..fda728351592 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -1,5 +1,5 @@ import lodashClamp from 'lodash/clamp'; -import React, {PureComponent} from 'react'; +import React, { useState } from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import ImageWithSizeCalculation from './ImageWithSizeCalculation'; @@ -33,18 +33,6 @@ const defaultProps = { imageHeight: 200, }; -class ThumbnailImage extends PureComponent { - constructor(props) { - super(props); - - this.updateImageSize = this.updateImageSize.bind(this); - const {thumbnailWidth, thumbnailHeight} = this.calculateThumbnailImageSize(props.imageWidth, props.imageHeight); - this.state = { - thumbnailWidth, - thumbnailHeight, - }; - } - /** * Compute the thumbnails width and height given original image dimensions. * @@ -52,54 +40,60 @@ class ThumbnailImage extends PureComponent { * @param {Number} height - Height of the original image. * @returns {Object} - Object containing thumbnails width and height. */ - calculateThumbnailImageSize(width, height) { - if (!width || !height) { - return {}; - } - - // 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 - // Note: Clamp minimum width 40px to support touch device - let thumbnailScreenWidth = lodashClamp(width, 40, 250); - const imageHeight = height / (width / thumbnailScreenWidth); - let thumbnailScreenHeight = lodashClamp(imageHeight, 40, this.props.windowHeight * 0.4); - const aspectRatio = height / width; - - // If thumbnail height is greater than its width, then the image is portrait otherwise landscape. - // For portrait images, we need to adjust the width of the image to keep the aspect ratio and vice-versa. - if (thumbnailScreenHeight > thumbnailScreenWidth) { - thumbnailScreenWidth = Math.round(thumbnailScreenHeight * (1 / aspectRatio)); - } else { - thumbnailScreenHeight = Math.round(thumbnailScreenWidth * aspectRatio); - } - return {thumbnailWidth: Math.max(40, thumbnailScreenWidth), thumbnailHeight: Math.max(40, thumbnailScreenHeight)}; + + +function calculateThumbnailImageSize(width, height) { + if (!width || !height) { + return {}; } - /** + // 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 + // Note: Clamp minimum width 40px to support touch device + let thumbnailScreenWidth = lodashClamp(width, 40, 250); + const imageHeight = height / (width / thumbnailScreenWidth); + let thumbnailScreenHeight = lodashClamp(imageHeight, 40, this.props.windowHeight * 0.4); + const aspectRatio = height / width; + + // If thumbnail height is greater than its width, then the image is portrait otherwise landscape. + // For portrait images, we need to adjust the width of the image to keep the aspect ratio and vice-versa. + if (thumbnailScreenHeight > thumbnailScreenWidth) { + thumbnailScreenWidth = Math.round(thumbnailScreenHeight * (1 / aspectRatio)); + } else { + thumbnailScreenHeight = Math.round(thumbnailScreenWidth * aspectRatio); + } + return {thumbnailWidth: Math.max(40, thumbnailScreenWidth), thumbnailHeight: Math.max(40, thumbnailScreenHeight)}; +} + + +function ThumbnailImage() { +const [imageWidth, setImageWidth] = useState(200); +const [imageHeight, setImageHeight] = useState(200); + + /** * Update the state with the computed thumbnail sizes. * * @param {{ width: number, height: number }} Params - width and height of the original image. */ - updateImageSize({width, height}) { + + function updateImageSize({width, height}) { const {thumbnailWidth, thumbnailHeight} = this.calculateThumbnailImageSize(width, height); - this.setState({thumbnailWidth, thumbnailHeight}); + this.setState({thumbnailWidth, thumbnailHeight}); --// use callback function } - render() { - return ( - - - - + return ( + + + - ); - } + + ); } ThumbnailImage.propTypes = propTypes; ThumbnailImage.defaultProps = defaultProps; -export default withWindowDimensions(ThumbnailImage); +export default withWindowDimensions(ThumbnailImage); \ No newline at end of file From 19961196237cdf79e36c13372971350fcfaac496 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 21 Jun 2023 13:16:20 +0200 Subject: [PATCH 002/223] Add react-window --- package-lock.json | 43 ++++++++++++++++++++++++++++++++++++------- package.json | 1 + 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0e83091a5474..3e8b85927585 100644 --- a/package-lock.json +++ b/package-lock.json @@ -96,6 +96,7 @@ "react-pdf": "5.7.2", "react-plaid-link": "3.3.2", "react-web-config": "^1.0.0", + "react-window": "^1.8.9", "save": "^2.4.0", "semver": "^7.3.8", "shim-keyboard-event-key": "^1.0.3", @@ -31801,6 +31802,11 @@ "node": ">= 4.0.0" } }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "node_modules/memoizerific": { "version": "1.11.3", "dev": true, @@ -36675,10 +36681,6 @@ "prop-types": "*" } }, - "node_modules/react-native/node_modules/memoize-one": { - "version": "5.2.1", - "license": "MIT" - }, "node_modules/react-native/node_modules/metro-runtime": { "version": "0.73.7", "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.73.7.tgz", @@ -37290,6 +37292,22 @@ "react-dom": ">=16.2.0" } }, + "node_modules/react-window": { + "version": "1.8.9", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.9.tgz", + "integrity": "sha512-+Eqx/fj1Aa5WnhRfj9dJg4VYATGwIUP2ItwItiJ6zboKWA6EX3lYDAXfGF2hyNqplEprhbtjbipiADEcwQ823Q==", + "dependencies": { + "@babel/runtime": "^7.0.0", + "memoize-one": ">=3.1.1 <6" + }, + "engines": { + "node": ">8.0.0" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/read-config-file": { "version": "6.3.2", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", @@ -64389,6 +64407,11 @@ "fs-monkey": "^1.0.3" } }, + "memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "memoizerific": { "version": "1.11.3", "dev": true, @@ -67331,9 +67354,6 @@ "prop-types": "*" } }, - "memoize-one": { - "version": "5.2.1" - }, "metro-runtime": { "version": "0.73.7", "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.73.7.tgz", @@ -68098,6 +68118,15 @@ "integrity": "sha512-8E/Eb/7ksKwn5QdLn67tOR7+TdP9BZdu6E5/DSt20v8yfW/s0VGBigE6VA7R4278mBuBUowovAB3DkCfVmSPvA==", "requires": {} }, + "react-window": { + "version": "1.8.9", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.9.tgz", + "integrity": "sha512-+Eqx/fj1Aa5WnhRfj9dJg4VYATGwIUP2ItwItiJ6zboKWA6EX3lYDAXfGF2hyNqplEprhbtjbipiADEcwQ823Q==", + "requires": { + "@babel/runtime": "^7.0.0", + "memoize-one": ">=3.1.1 <6" + } + }, "read-config-file": { "version": "6.3.2", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", diff --git a/package.json b/package.json index 0dbe21194334..650aefbb49cf 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,7 @@ "react-pdf": "5.7.2", "react-plaid-link": "3.3.2", "react-web-config": "^1.0.0", + "react-window": "^1.8.9", "save": "^2.4.0", "semver": "^7.3.8", "shim-keyboard-event-key": "^1.0.3", From e0d35358805b6b2c6809da6f1447f0883f3edff3 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 21 Jun 2023 13:16:46 +0200 Subject: [PATCH 003/223] Fix previewing of large pdf files --- src/components/PDFView/index.js | 91 ++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index 85821b8d8ca3..9d37c26db199 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -3,6 +3,7 @@ import React, {Component} from 'react'; import {View, Dimensions} from 'react-native'; import {Document, Page, pdfjs} from 'react-pdf/dist/esm/entry.webpack'; import pdfWorkerSource from 'pdfjs-dist/legacy/build/pdf.worker'; +import {VariableSizeList as List} from 'react-window'; import FullScreenLoadingIndicator from '../FullscreenLoadingIndicator'; import styles from '../../styles/styles'; import variables from '../../styles/variables'; @@ -19,6 +20,7 @@ class PDFView extends Component { super(props); this.state = { numPages: null, + pageViewports: [], windowWidth: Dimensions.get('window').width, shouldRequestPassword: false, isPasswordInvalid: false, @@ -28,6 +30,9 @@ class PDFView extends Component { this.initiatePasswordChallenge = this.initiatePasswordChallenge.bind(this); this.attemptPDFLoad = this.attemptPDFLoad.bind(this); this.toggleKeyboardOnSmallScreens = this.toggleKeyboardOnSmallScreens.bind(this); + this.calculatePageHeight = this.calculatePageHeight.bind(this); + this.calculatePageWidth = this.calculatePageWidth.bind(this); + this.renderPage = this.renderPage.bind(this); const workerBlob = new Blob([pdfWorkerSource], {type: 'text/javascript'}); pdfjs.GlobalWorkerOptions.workerSrc = URL.createObjectURL(workerBlob); @@ -45,21 +50,53 @@ class PDFView extends Component { } /** - * Upon successful document load, set the number of pages on PDF, + * Upon successful document load, combine an array of page viewports, + * set the number of pages on PDF, * hide/reset PDF password form, and notify parent component that * user input is no longer required. * - * @param {*} {numPages} No of pages in the rendered PDF + * @param {*} pdf The PDF file instance * @memberof PDFView */ - onDocumentLoadSuccess({numPages}) { - this.setState({ - numPages, - shouldRequestPassword: false, - isPasswordInvalid: false, + onDocumentLoadSuccess(pdf) { + const numPages = pdf.numPages; + + Promise.all( + _.times(numPages, (index) => { + const pageNumber = index + 1; + + return pdf.getPage(pageNumber).then((page) => page.getViewport({scale: 1})); + }), + ).then((pageViewports) => { + this.setState({ + pageViewports, + numPages, + shouldRequestPassword: false, + isPasswordInvalid: false, + }); }); } + calculatePageHeight(pageIndex) { + if (this.state.pageViewports.length === 0) { + throw new Error('calculatePageHeight() called too early'); + } + + const pageViewport = this.state.pageViewports[pageIndex]; + const scale = this.calculatePageWidth() / pageViewport.width; + const actualHeight = pageViewport.height * scale; + + return actualHeight; + } + + calculatePageWidth() { + const pdfContainerWidth = this.state.windowWidth - 100; + const pageWidthOnLargeScreen = pdfContainerWidth <= variables.pdfPageMaxWidth ? pdfContainerWidth : variables.pdfPageMaxWidth; + const pageWidth = this.props.isSmallScreenWidth ? this.state.windowWidth : pageWidthOnLargeScreen; + + return pageWidth; + } + /** * Initiate password challenge process. The react-pdf/Document * component calls this handler to indicate that a PDF requires a @@ -105,10 +142,25 @@ class PDFView extends Component { this.props.onToggleKeyboard(isKeyboardOpen); } + renderPage({index, style}) { + const pageWidth = this.calculatePageWidth(); + + return ( + + + + ); + } + render() { - const pdfContainerWidth = this.state.windowWidth - 100; - const pageWidthOnLargeScreen = pdfContainerWidth <= variables.pdfPageMaxWidth ? pdfContainerWidth : variables.pdfPageMaxWidth; - const pageWidth = this.props.isSmallScreenWidth ? this.state.windowWidth : pageWidthOnLargeScreen; + const pageWidth = this.calculatePageWidth(); const outerContainerStyle = [styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]; // If we're requesting a password then we need to hide - but still render - @@ -136,16 +188,17 @@ class PDFView extends Component { onLoadSuccess={this.onDocumentLoadSuccess} onPassword={this.initiatePasswordChallenge} > - {_.map(_.range(this.state.numPages), (v, index) => ( - 0 && ( + - ))} + height={this.props.windowHeight} + estimatedItemSize={this.calculatePageHeight(0)} + itemCount={this.state.numPages} + itemSize={this.calculatePageHeight} + > + {this.renderPage} + + )} {this.state.shouldRequestPassword && ( From 9ee915d70afb644f6790756471566819fbf70e2c Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Thu, 22 Jun 2023 00:45:55 +0100 Subject: [PATCH 004/223] Remove filetype restriction for file uploads --- src/CONST.js | 58 ------------------------------- src/components/AttachmentModal.js | 11 ------ 2 files changed, 69 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 85ee667eead5..75b9f1a61e9b 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -35,64 +35,6 @@ const CONST = { ARROW_HIDE_DELAY: 3000, API_ATTACHMENT_VALIDATIONS: { - // Same as the PHP layer allows - /* eslint-disable-next-line max-len */ - UNALLOWED_EXTENSIONS: [ - 'ade', - 'adp', - 'apk', - 'appx', - 'appxbundle', - 'bat', - 'cab', - 'chm', - 'cmd', - 'com', - 'cpl', - 'diagcab', - 'diagcfg', - 'diagpack', - 'dll', - 'dmg', - 'ex', - 'ex_', - 'exe', - 'hta', - 'img', - 'ins', - 'iso', - 'isp', - 'jar', - 'jnlp', - 'js', - 'jse', - 'lib', - 'lnk', - 'mde', - 'msc', - 'msi', - 'msix', - 'msixbundle', - 'msp', - 'mst', - 'nsh', - 'pif', - 'ps1', - 'scr', - 'sct', - 'shb', - 'sys', - 'vb', - 'vbe', - 'vbs', - 'vhd', - 'vxd', - 'wsc', - 'wsf', - 'wsh', - 'xll', - ], - // 24 megabytes in bytes, this is limit set on servers, do not update without wider internal discussion MAX_SIZE: 25165824, diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index a481a4026659..c1843a931815 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -11,7 +11,6 @@ import AttachmentView from './AttachmentView'; import AttachmentCarousel from './AttachmentCarousel'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; -import * as FileUtils from '../libs/fileDownload/FileUtils'; import themeColors from '../styles/themes/default'; import compose from '../libs/compose'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; @@ -166,16 +165,6 @@ function AttachmentModal(props) { */ const isValidFile = useCallback( (_file) => { - const {fileExtension} = FileUtils.splitExtensionFromFileName(lodashGet(_file, 'name', '')); - if (_.contains(CONST.API_ATTACHMENT_VALIDATIONS.UNALLOWED_EXTENSIONS, fileExtension.toLowerCase())) { - const invalidReason = props.translate('attachmentPicker.notAllowedExtension'); - - setIsAttachmentInvalid(true); - setAttachmentInvalidReasonTitle(props.translate('attachmentPicker.wrongFileType')); - setAttachmentInvalidReason(invalidReason); - return false; - } - if (lodashGet(_file, 'size', 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { setIsAttachmentInvalid(true); setAttachmentInvalidReasonTitle(props.translate('attachmentPicker.attachmentTooLarge')); From 5523aa29a174b075516d7a0755bd47179195e034 Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Tue, 27 Jun 2023 09:19:10 +1200 Subject: [PATCH 005/223] syntax changes and debugging completed with puneet --- src/components/ThumbnailImage.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index fda728351592..16b3b1c9965a 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -42,7 +42,7 @@ const defaultProps = { */ -function calculateThumbnailImageSize(width, height) { +function calculateThumbnailImageSize(width, height, windowHeight) { if (!width || !height) { return {}; } @@ -52,7 +52,7 @@ function calculateThumbnailImageSize(width, height) { // Note: Clamp minimum width 40px to support touch device let thumbnailScreenWidth = lodashClamp(width, 40, 250); const imageHeight = height / (width / thumbnailScreenWidth); - let thumbnailScreenHeight = lodashClamp(imageHeight, 40, this.props.windowHeight * 0.4); + let thumbnailScreenHeight = lodashClamp(imageHeight, 40, windowHeight * 0.4); const aspectRatio = height / width; // If thumbnail height is greater than its width, then the image is portrait otherwise landscape. @@ -65,8 +65,7 @@ function calculateThumbnailImageSize(width, height) { return {thumbnailWidth: Math.max(40, thumbnailScreenWidth), thumbnailHeight: Math.max(40, thumbnailScreenHeight)}; } - -function ThumbnailImage() { +function ThumbnailImage(props) { const [imageWidth, setImageWidth] = useState(200); const [imageHeight, setImageHeight] = useState(200); @@ -77,17 +76,17 @@ const [imageHeight, setImageHeight] = useState(200); */ function updateImageSize({width, height}) { - const {thumbnailWidth, thumbnailHeight} = this.calculateThumbnailImageSize(width, height); - this.setState({thumbnailWidth, thumbnailHeight}); --// use callback function + const {thumbnailWidth, thumbnailHeight} = calculateThumbnailImageSize(width, height, props.windowHeight); + setImageWidth(thumbnailWidth); + setImageHeight(thumbnailHeight); } - return ( - - + + From a942097b42a442e732a9135b4926eb753916e284 Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Tue, 27 Jun 2023 09:23:26 +1200 Subject: [PATCH 006/223] cleaned up code with prettier --- src/components/ThumbnailImage.js | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 16b3b1c9965a..402a9d69672a 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -1,5 +1,5 @@ import lodashClamp from 'lodash/clamp'; -import React, { useState } from 'react'; +import React, {useState} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import ImageWithSizeCalculation from './ImageWithSizeCalculation'; @@ -33,14 +33,13 @@ const defaultProps = { imageHeight: 200, }; - /** - * Compute the thumbnails width and height given original image dimensions. - * - * @param {Number} width - Width of the original image. - * @param {Number} height - Height of the original image. - * @returns {Object} - Object containing thumbnails width and height. - */ - +/** + * Compute the thumbnails width and height given original image dimensions. + * + * @param {Number} width - Width of the original image. + * @param {Number} height - Height of the original image. + * @returns {Object} - Object containing thumbnails width and height. + */ function calculateThumbnailImageSize(width, height, windowHeight) { if (!width || !height) { @@ -66,16 +65,16 @@ function calculateThumbnailImageSize(width, height, windowHeight) { } function ThumbnailImage(props) { -const [imageWidth, setImageWidth] = useState(200); -const [imageHeight, setImageHeight] = useState(200); + const [imageWidth, setImageWidth] = useState(200); + const [imageHeight, setImageHeight] = useState(200); - /** + /** * Update the state with the computed thumbnail sizes. * * @param {{ width: number, height: number }} Params - width and height of the original image. */ - - function updateImageSize({width, height}) { + + function updateImageSize({width, height}) { const {thumbnailWidth, thumbnailHeight} = calculateThumbnailImageSize(width, height, props.windowHeight); setImageWidth(thumbnailWidth); setImageHeight(thumbnailHeight); @@ -95,4 +94,4 @@ const [imageHeight, setImageHeight] = useState(200); ThumbnailImage.propTypes = propTypes; ThumbnailImage.defaultProps = defaultProps; -export default withWindowDimensions(ThumbnailImage); \ No newline at end of file +export default withWindowDimensions(ThumbnailImage); From f220bd5a356545af7ba31770c5457172d63d1f79 Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Tue, 27 Jun 2023 09:26:07 +1200 Subject: [PATCH 007/223] added display name --- src/components/ThumbnailImage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 402a9d69672a..111fa2deff67 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -94,4 +94,5 @@ function ThumbnailImage(props) { ThumbnailImage.propTypes = propTypes; ThumbnailImage.defaultProps = defaultProps; +ThumbnailImage.displayName = 'ThumbnailImage'; export default withWindowDimensions(ThumbnailImage); From 07a46eecc2222adb8fe30f367a8e45e0ecabec9c Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Tue, 27 Jun 2023 13:58:34 +1200 Subject: [PATCH 008/223] changes to clean up the lint failures --- src/components/ThumbnailImage.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 111fa2deff67..1c9850137c22 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -65,8 +65,10 @@ function calculateThumbnailImageSize(width, height, windowHeight) { } function ThumbnailImage(props) { - const [imageWidth, setImageWidth] = useState(200); - const [imageHeight, setImageHeight] = useState(200); + + const {initialWidth, initialHeight} = calculateThumbnailImageSize(props.imageWidth, props.imageHeight, props.windowHeight); + const [imageWidth, setImageWidth] = useState(initialWidth); + const [imageHeight, setImageHeight] = useState(initialHeight); /** * Update the state with the computed thumbnail sizes. @@ -84,7 +86,7 @@ function ThumbnailImage(props) { updateImageSize()} isAuthTokenRequired={props.isAuthTokenRequired} /> From 2e1f803946ea2c50e7c0770a6d7945933689446d Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Wed, 5 Jul 2023 15:52:36 +1200 Subject: [PATCH 009/223] ran prettier to tidy code changes --- src/components/ThumbnailImage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 1c9850137c22..3c7ae45593b1 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -65,7 +65,6 @@ function calculateThumbnailImageSize(width, height, windowHeight) { } function ThumbnailImage(props) { - const {initialWidth, initialHeight} = calculateThumbnailImageSize(props.imageWidth, props.imageHeight, props.windowHeight); const [imageWidth, setImageWidth] = useState(initialWidth); const [imageHeight, setImageHeight] = useState(initialHeight); From 220fc1eca1716422fc509787a2a00417c51e57fa Mon Sep 17 00:00:00 2001 From: kadiealexander <59587260+kadiealexander@users.noreply.github.com> Date: Wed, 5 Jul 2023 15:53:37 +1200 Subject: [PATCH 010/223] Update src/components/ThumbnailImage.js Co-authored-by: Puneet Lath --- src/components/ThumbnailImage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 3c7ae45593b1..57a73afb47cf 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -85,7 +85,7 @@ function ThumbnailImage(props) { updateImageSize()} + onMeasure={(width, height) => updateImageSize(width, height)} isAuthTokenRequired={props.isAuthTokenRequired} /> From 6d28dcfad1bf0dad6d8d5d7350f0fc5a5111d47a Mon Sep 17 00:00:00 2001 From: kadiealexander <59587260+kadiealexander@users.noreply.github.com> Date: Fri, 7 Jul 2023 17:15:24 +1200 Subject: [PATCH 011/223] Updating the arguments for the onMeasure function Updating the arguments for the onMeasure function Co-authored-by: Puneet Lath --- src/components/ThumbnailImage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 57a73afb47cf..6f68858fe9e3 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -85,7 +85,7 @@ function ThumbnailImage(props) { updateImageSize(width, height)} + onMeasure={(measurements) => updateImageSize(measurements)} isAuthTokenRequired={props.isAuthTokenRequired} /> From 0b0de987910cd894784871fd37876fa27fe89615 Mon Sep 17 00:00:00 2001 From: Youssef Lourayad Date: Fri, 7 Jul 2023 13:19:57 +0100 Subject: [PATCH 012/223] Remove translation keys no longer used --- src/languages/en.js | 2 -- src/languages/es.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 192d9954e5ff..5ed8bc64a56d 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -163,8 +163,6 @@ export default { sizeExceeded: 'Attachment size is larger than 24 MB limit.', attachmentTooSmall: 'Attachment too small', sizeNotMet: 'Attachment size must be greater than 240 bytes.', - wrongFileType: 'Attachment is the wrong type', - notAllowedExtension: 'This filetype is not allowed', }, avatarCropModal: { title: 'Edit photo', diff --git a/src/languages/es.js b/src/languages/es.js index d54fa5110117..e55c794fe989 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -162,8 +162,6 @@ export default { sizeExceeded: 'El archivo adjunto supera el límite de 24 MB.', attachmentTooSmall: 'Archivo adjunto demasiado pequeño', sizeNotMet: 'El archivo adjunto debe ser mas grande que 240 bytes.', - wrongFileType: 'El tipo del archivo adjunto es incorrecto', - notAllowedExtension: 'Este tipo de archivo no está permitido', }, avatarCropModal: { title: 'Editar foto', From 281839956ed12da9c224ebf1887ece5082833928 Mon Sep 17 00:00:00 2001 From: Alberto Date: Mon, 10 Jul 2023 13:17:37 +0200 Subject: [PATCH 013/223] changing branch --- src/pages/home/report/ReportActionItemSingle.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 3011dff69bf1..93daf5065ad3 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -91,6 +91,9 @@ function ReportActionItemSingle(props) { displayName = actorHint; avatarSource = UserUtils.getAvatar(delegateDetails.avatar, props.action.delegateAccountID); } + if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW) { + displayName = 'mimimi'; + } // Since the display name for a report action message is delivered with the report history as an array of fragments // we'll need to take the displayName from personal details and have it be in the same format for now. Eventually, From fd8f613771a1a3e1cc30fa5a3093388262fdd28c Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Tue, 11 Jul 2023 09:04:54 +1200 Subject: [PATCH 014/223] update updateImageSize to use callback function --- src/components/ThumbnailImage.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 6f68858fe9e3..70047d1136ed 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -1,5 +1,9 @@ import lodashClamp from 'lodash/clamp'; +<<<<<<< Updated upstream import React, {useState} from 'react'; +======= +import React, {useCallback,useState} from 'react'; +>>>>>>> Stashed changes import {View} from 'react-native'; import PropTypes from 'prop-types'; import ImageWithSizeCalculation from './ImageWithSizeCalculation'; @@ -75,17 +79,33 @@ function ThumbnailImage(props) { * @param {{ width: number, height: number }} Params - width and height of the original image. */ +<<<<<<< Updated upstream function updateImageSize({width, height}) { const {thumbnailWidth, thumbnailHeight} = calculateThumbnailImageSize(width, height, props.windowHeight); setImageWidth(thumbnailWidth); setImageHeight(thumbnailHeight); } +======= + const updateImageSize = useCallback(({width, height}) => { + const {thumbnailWidth, thumbnailHeight} = calculateThumbnailImageSize(width, height, props.windowHeight); + setImageWidth(thumbnailWidth); + setImageHeight(thumbnailHeight); + }, [props.windowHeight]) +>>>>>>> Stashed changes return ( updateImageSize(measurements)} +======= +<<<<<<< Updated upstream + onMeasure={() => updateImageSize()} +======= + onMeasure={(measurements) => updateImageSize(measurements)} +>>>>>>> Stashed changes +>>>>>>> Stashed changes isAuthTokenRequired={props.isAuthTokenRequired} /> @@ -96,4 +116,8 @@ function ThumbnailImage(props) { ThumbnailImage.propTypes = propTypes; ThumbnailImage.defaultProps = defaultProps; ThumbnailImage.displayName = 'ThumbnailImage'; +<<<<<<< Updated upstream +export default withWindowDimensions(ThumbnailImage); +======= export default withWindowDimensions(ThumbnailImage); +>>>>>>> Stashed changes From 241714ebc281dd18f2ec4111a35ed90bfcbcc118 Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Tue, 11 Jul 2023 09:11:32 +1200 Subject: [PATCH 015/223] update onMeasure to pass updateImageSize function directly --- src/components/ThumbnailImage.js | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 70047d1136ed..50098dede230 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -1,9 +1,5 @@ import lodashClamp from 'lodash/clamp'; -<<<<<<< Updated upstream import React, {useState} from 'react'; -======= -import React, {useCallback,useState} from 'react'; ->>>>>>> Stashed changes import {View} from 'react-native'; import PropTypes from 'prop-types'; import ImageWithSizeCalculation from './ImageWithSizeCalculation'; @@ -79,33 +75,17 @@ function ThumbnailImage(props) { * @param {{ width: number, height: number }} Params - width and height of the original image. */ -<<<<<<< Updated upstream - function updateImageSize({width, height}) { - const {thumbnailWidth, thumbnailHeight} = calculateThumbnailImageSize(width, height, props.windowHeight); - setImageWidth(thumbnailWidth); - setImageHeight(thumbnailHeight); - } -======= const updateImageSize = useCallback(({width, height}) => { const {thumbnailWidth, thumbnailHeight} = calculateThumbnailImageSize(width, height, props.windowHeight); setImageWidth(thumbnailWidth); setImageHeight(thumbnailHeight); }, [props.windowHeight]) ->>>>>>> Stashed changes return ( updateImageSize(measurements)} -======= -<<<<<<< Updated upstream - onMeasure={() => updateImageSize()} -======= - onMeasure={(measurements) => updateImageSize(measurements)} ->>>>>>> Stashed changes ->>>>>>> Stashed changes + onMeasure={updateImageSize} isAuthTokenRequired={props.isAuthTokenRequired} /> @@ -116,8 +96,4 @@ function ThumbnailImage(props) { ThumbnailImage.propTypes = propTypes; ThumbnailImage.defaultProps = defaultProps; ThumbnailImage.displayName = 'ThumbnailImage'; -<<<<<<< Updated upstream -export default withWindowDimensions(ThumbnailImage); -======= -export default withWindowDimensions(ThumbnailImage); ->>>>>>> Stashed changes +export default withWindowDimensions(ThumbnailImage); \ No newline at end of file From 967d52197834caa6e4221a73f9e1b33dbbded099 Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Tue, 11 Jul 2023 09:20:06 +1200 Subject: [PATCH 016/223] use useWindowDimensions hook instead of HOC prop --- src/components/ThumbnailImage.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 50098dede230..13893e3bcb7c 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import ImageWithSizeCalculation from './ImageWithSizeCalculation'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; -import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; +import useWindowDimensions from '../hooks/useWindowDimensions'; const propTypes = { /** Source URL for the preview image */ @@ -23,8 +23,6 @@ const propTypes = { /** Height of the thumbnail image */ imageHeight: PropTypes.number, - - ...windowDimensionsPropTypes, }; const defaultProps = { @@ -65,10 +63,12 @@ function calculateThumbnailImageSize(width, height, windowHeight) { } function ThumbnailImage(props) { - const {initialWidth, initialHeight} = calculateThumbnailImageSize(props.imageWidth, props.imageHeight, props.windowHeight); + const {windowHeight} = useWindowDimensions(); + const {initialWidth, initialHeight} = calculateThumbnailImageSize(props.imageWidth, props.imageHeight, windowHeight); const [imageWidth, setImageWidth] = useState(initialWidth); const [imageHeight, setImageHeight] = useState(initialHeight); + /** * Update the state with the computed thumbnail sizes. * @@ -76,10 +76,10 @@ function ThumbnailImage(props) { */ const updateImageSize = useCallback(({width, height}) => { - const {thumbnailWidth, thumbnailHeight} = calculateThumbnailImageSize(width, height, props.windowHeight); + const {thumbnailWidth, thumbnailHeight} = calculateThumbnailImageSize(width, height,windowHeight); setImageWidth(thumbnailWidth); setImageHeight(thumbnailHeight); - }, [props.windowHeight]) + }, [windowHeight]) return ( @@ -95,5 +95,4 @@ function ThumbnailImage(props) { ThumbnailImage.propTypes = propTypes; ThumbnailImage.defaultProps = defaultProps; -ThumbnailImage.displayName = 'ThumbnailImage'; -export default withWindowDimensions(ThumbnailImage); \ No newline at end of file +ThumbnailImage.displayName = 'ThumbnailImage'; \ No newline at end of file From b5b09a8ab255ab7f8f55096ee5becc5dfb421fd0 Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Tue, 11 Jul 2023 09:34:32 +1200 Subject: [PATCH 017/223] update variables to factor in undefined states --- src/components/ThumbnailImage.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 13893e3bcb7c..0bfb7f9f4e2b 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -1,5 +1,5 @@ import lodashClamp from 'lodash/clamp'; -import React, {useState} from 'react'; +import React, {useCallback, useState} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import ImageWithSizeCalculation from './ImageWithSizeCalculation'; @@ -64,10 +64,9 @@ function calculateThumbnailImageSize(width, height, windowHeight) { function ThumbnailImage(props) { const {windowHeight} = useWindowDimensions(); - const {initialWidth, initialHeight} = calculateThumbnailImageSize(props.imageWidth, props.imageHeight, windowHeight); - const [imageWidth, setImageWidth] = useState(initialWidth); - const [imageHeight, setImageHeight] = useState(initialHeight); - + const initialDimensions = calculateThumbnailImageSize(props.imageWidth, props.imageHeight, windowHeight); + const [imageWidth, setImageWidth] = useState(initialDimensions.thumbnailWidth); + const [imageHeight, setImageHeight] = useState(initialDimensions.thumbnailHeight); /** * Update the state with the computed thumbnail sizes. @@ -75,14 +74,17 @@ function ThumbnailImage(props) { * @param {{ width: number, height: number }} Params - width and height of the original image. */ - const updateImageSize = useCallback(({width, height}) => { - const {thumbnailWidth, thumbnailHeight} = calculateThumbnailImageSize(width, height,windowHeight); - setImageWidth(thumbnailWidth); - setImageHeight(thumbnailHeight); - }, [windowHeight]) + const updateImageSize = useCallback( + ({width, height}) => { + const {thumbnailWidth, thumbnailHeight} = calculateThumbnailImageSize(width, height, windowHeight); + setImageWidth(thumbnailWidth); + setImageHeight(thumbnailHeight); + }, + [windowHeight], + ); return ( - + Date: Wed, 12 Jul 2023 17:03:45 +0200 Subject: [PATCH 018/223] fix dimensions --- src/components/PDFView/index.js | 26 ++++++++++++++++++-------- src/styles/styles.js | 5 ++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index 357be7f8f40b..7d9da52a06b3 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -1,6 +1,6 @@ import _ from 'underscore'; import React, {Component} from 'react'; -import {View, Dimensions} from 'react-native'; +import {View} from 'react-native'; import {Document, Page, pdfjs} from 'react-pdf/dist/esm/entry.webpack'; import pdfWorkerSource from 'pdfjs-dist/legacy/build/pdf.worker'; import {VariableSizeList as List} from 'react-window'; @@ -15,13 +15,16 @@ import withLocalize from '../withLocalize'; import Text from '../Text'; import compose from '../../libs/compose'; +const PAGE_BORDER = 9; + class PDFView extends Component { constructor(props) { super(props); this.state = { numPages: null, pageViewports: [], - windowWidth: Dimensions.get('window').width, + containerWidth: props.windowWidth, + containerHeight: props.windowHeight, shouldRequestPassword: false, isPasswordInvalid: false, isKeyboardOpen: false, @@ -77,6 +80,7 @@ class PDFView extends Component { }); } + // TODO: Add a comment calculatePageHeight(pageIndex) { if (this.state.pageViewports.length === 0) { throw new Error('calculatePageHeight() called too early'); @@ -84,17 +88,18 @@ class PDFView extends Component { const pageViewport = this.state.pageViewports[pageIndex]; const scale = this.calculatePageWidth() / pageViewport.width; - const actualHeight = pageViewport.height * scale; + const actualHeight = pageViewport.height * scale + PAGE_BORDER * 2; return actualHeight; } + // TODO: Add a comment calculatePageWidth() { - const pdfContainerWidth = this.state.windowWidth - 100; + const pdfContainerWidth = this.state.containerWidth; const pageWidthOnLargeScreen = pdfContainerWidth <= variables.pdfPageMaxWidth ? pdfContainerWidth : variables.pdfPageMaxWidth; - const pageWidth = this.props.isSmallScreenWidth ? this.state.windowWidth : pageWidthOnLargeScreen; + const pageWidth = this.props.isSmallScreenWidth ? this.state.containerWidth : pageWidthOnLargeScreen; - return pageWidth; + return pageWidth + PAGE_BORDER * 2; } /** @@ -174,7 +179,11 @@ class PDFView extends Component { this.setState({windowWidth: event.nativeEvent.layout.width})} + onLayout={({ + nativeEvent: { + layout: {width, height}, + }, + }) => this.setState({containerWidth: width, containerHeight: height})} > {this.props.translate('attachmentView.failedToLoadPDF')}} @@ -190,8 +199,9 @@ class PDFView extends Component { > {this.state.pageViewports.length > 0 && ( Date: Wed, 12 Jul 2023 19:10:51 +0200 Subject: [PATCH 019/223] add JSDoc --- src/components/PDFView/index.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index 7d9da52a06b3..b51796e276fc 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -15,6 +15,10 @@ import withLocalize from '../withLocalize'; import Text from '../Text'; import compose from '../../libs/compose'; +/** + * Each page has a default border. The app should take this size into account + * when calculates the page width and height. + */ const PAGE_BORDER = 9; class PDFView extends Component { @@ -80,20 +84,31 @@ class PDFView extends Component { }); } - // TODO: Add a comment + /** + * Calculates a proper page height. + * It is based on a ratio between the specific page viewport width and provided page width. + * Also, the app should take into account the page borders. + * @param {*} pageIndex + * @returns {Number} + */ calculatePageHeight(pageIndex) { if (this.state.pageViewports.length === 0) { throw new Error('calculatePageHeight() called too early'); } const pageViewport = this.state.pageViewports[pageIndex]; - const scale = this.calculatePageWidth() / pageViewport.width; + const pageWidth = this.calculatePageWidth(); + const scale = pageWidth / pageViewport.width; const actualHeight = pageViewport.height * scale + PAGE_BORDER * 2; return actualHeight; } - // TODO: Add a comment + /** + * Calculates a proper page width. + * It depends on a screen size. Also, the app should take into account the page borders. + * @returns {Number} + */ calculatePageWidth() { const pdfContainerWidth = this.state.containerWidth; const pageWidthOnLargeScreen = pdfContainerWidth <= variables.pdfPageMaxWidth ? pdfContainerWidth : variables.pdfPageMaxWidth; @@ -147,6 +162,13 @@ class PDFView extends Component { this.props.onToggleKeyboard(isKeyboardOpen); } + /** + * Renders a specific page based on its index. + * It includes a wrapper to apply virtualized styles. + * @param {Number} index + * @param {Object} style + * @returns {JSX.Element} + */ renderPage({index, style}) { const pageWidth = this.calculatePageWidth(); From 926ab9f718dd38b0cea26080a8178d47c9c3c962 Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Thu, 13 Jul 2023 17:34:50 +1200 Subject: [PATCH 020/223] fix lint --- src/components/ThumbnailImage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 0bfb7f9f4e2b..089d97524c10 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -84,7 +84,7 @@ function ThumbnailImage(props) { ); return ( - + Date: Thu, 13 Jul 2023 17:40:28 +1200 Subject: [PATCH 021/223] fix prettier --- src/components/ThumbnailImage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 089d97524c10..2556fe9ae29c 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -98,4 +98,4 @@ function ThumbnailImage(props) { ThumbnailImage.propTypes = propTypes; ThumbnailImage.defaultProps = defaultProps; ThumbnailImage.displayName = 'ThumbnailImage'; -export default React.memo(ThumbnailImage); \ No newline at end of file +export default React.memo(ThumbnailImage); From c95c7754f11fafd9ff745a339c99fd2510e681a2 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 13 Jul 2023 11:20:26 +0200 Subject: [PATCH 022/223] improve PDFView methods --- src/components/PDFView/index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index b51796e276fc..49130013d88a 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -14,6 +14,7 @@ import withWindowDimensions from '../withWindowDimensions'; import withLocalize from '../withLocalize'; import Text from '../Text'; import compose from '../../libs/compose'; +import Log from '../../libs/Log'; /** * Each page has a default border. The app should take this size into account @@ -85,7 +86,7 @@ class PDFView extends Component { } /** - * Calculates a proper page height. + * Calculates a proper page height. The method should be called only when there are page viewports. * It is based on a ratio between the specific page viewport width and provided page width. * Also, the app should take into account the page borders. * @param {*} pageIndex @@ -93,7 +94,9 @@ class PDFView extends Component { */ calculatePageHeight(pageIndex) { if (this.state.pageViewports.length === 0) { - throw new Error('calculatePageHeight() called too early'); + Log.warn('Dev error: calculatePageHeight() in PDFView called too early'); + + return 0; } const pageViewport = this.state.pageViewports[pageIndex]; @@ -111,7 +114,7 @@ class PDFView extends Component { */ calculatePageWidth() { const pdfContainerWidth = this.state.containerWidth; - const pageWidthOnLargeScreen = pdfContainerWidth <= variables.pdfPageMaxWidth ? pdfContainerWidth : variables.pdfPageMaxWidth; + const pageWidthOnLargeScreen = Math.min(pdfContainerWidth, variables.pdfPageMaxWidth); const pageWidth = this.props.isSmallScreenWidth ? this.state.containerWidth : pageWidthOnLargeScreen; return pageWidth + PAGE_BORDER * 2; From 7c190a51fbf2fbbed489c1f1fa94813428f0c27f Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 13 Jul 2023 11:51:05 +0200 Subject: [PATCH 023/223] use currying --- src/components/PDFView/index.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index 49130013d88a..2949ae4a51ee 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -166,16 +166,13 @@ class PDFView extends Component { } /** - * Renders a specific page based on its index. - * It includes a wrapper to apply virtualized styles. - * @param {Number} index - * @param {Object} style + * It is a currying method that returns a function that renders a specific page based on its index. + * The function includes a wrapper to apply virtualized styles. + * @param {Number} pageWidth * @returns {JSX.Element} */ - renderPage({index, style}) { - const pageWidth = this.calculatePageWidth(); - - return ( + renderPage(pageWidth) { + return ({index, style}) => ( - {this.renderPage} + {this.renderPage(pageWidth)} )} From 48731082b4e1de4208c88d5cea5ecbcf5d0686fd Mon Sep 17 00:00:00 2001 From: Alberto Date: Mon, 17 Jul 2023 20:00:55 +0200 Subject: [PATCH 024/223] display both names and avatars --- src/pages/home/report/ReportActionItem.js | 4 ++++ src/pages/home/report/ReportActionItemSingle.js | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 710df7e30f9c..037c510ddbc2 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -405,6 +405,7 @@ function ReportActionItem(props) { wrapperStyles={[styles.chatItem, isWhisper ? styles.pt1 : {}]} shouldShowSubscriptAvatar={props.shouldShowSubscriptAvatar} report={props.report} + iouReport = {props.iouReport} hasBeenFlagged={!_.contains([CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING], moderationDecision)} > {content} @@ -551,6 +552,9 @@ export default compose( preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, }, + iouReport: { + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.iouReportID}`, + }, }), )( memo( diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 6524ba043250..8020e78c9386 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -41,6 +41,9 @@ const propTypes = { /** Report for this action */ report: reportPropTypes, + /** IOU Report for this action, if any */ + iouReport: reportPropTypes, + /** Show header for action */ showHeader: PropTypes.bool, @@ -60,6 +63,7 @@ const defaultProps = { shouldShowSubscriptAvatar: false, hasBeenFlagged: false, report: undefined, + iouReport: undefined, }; const showUserDetails = (accountID) => { @@ -91,8 +95,16 @@ function ReportActionItemSingle(props) { displayName = actorHint; avatarSource = UserUtils.getAvatar(delegateDetails.avatar, props.action.delegateAccountID); } + + // If this is a report preview, display names and avatars of both people involved + let secondaryAvatar = {}; if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW) { - displayName = 'mimimi'; + const secondaryUserDetails = props.personalDetailsList[props.iouReport.ownerAccountID] || {}; + const secondaryDisplayName = lodashGet(secondaryUserDetails, 'displayName', ''); + displayName = displayName + ' & ' + secondaryDisplayName; + secondaryAvatar = {source: UserUtils.getAvatar(secondaryUserDetails.avatar, props.iouReport.ownerAccountID), type: CONST.ICON_TYPE_AVATAR, name: secondaryDisplayName, id: props.iouReport.ownerAccountID}; + } else if (!isWorkspaceActor) { + secondaryAvatar = ReportUtils.getIcons(props.report, {})[props.report.isOwnPolicyExpenseChat ? 0 : 1] } const icon = {source: avatarSource, type: isWorkspaceActor ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR, name: displayName, id: actorAccountID}; @@ -130,7 +142,7 @@ function ReportActionItemSingle(props) { {props.shouldShowSubscriptAvatar ? ( Date: Mon, 17 Jul 2023 22:55:56 +0200 Subject: [PATCH 025/223] use helper --- .../home/report/ReportActionItemSingle.js | 70 ++++++++++++------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 8020e78c9386..50ed50739842 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -23,6 +23,7 @@ import reportPropTypes from '../../reportPropTypes'; import * as UserUtils from '../../../libs/UserUtils'; import PressableWithoutFeedback from '../../../components/Pressable/PressableWithoutFeedback'; import UserDetailsTooltip from '../../../components/UserDetailsTooltip'; +import MultipleAvatars from "../../../components/MultipleAvatars"; const propTypes = { /** All the data of the action */ @@ -98,13 +99,14 @@ function ReportActionItemSingle(props) { // If this is a report preview, display names and avatars of both people involved let secondaryAvatar = {}; - if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW) { + const displayAllActors = props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && props.iouReport; + if (displayAllActors) { const secondaryUserDetails = props.personalDetailsList[props.iouReport.ownerAccountID] || {}; const secondaryDisplayName = lodashGet(secondaryUserDetails, 'displayName', ''); displayName = displayName + ' & ' + secondaryDisplayName; secondaryAvatar = {source: UserUtils.getAvatar(secondaryUserDetails.avatar, props.iouReport.ownerAccountID), type: CONST.ICON_TYPE_AVATAR, name: secondaryDisplayName, id: props.iouReport.ownerAccountID}; } else if (!isWorkspaceActor) { - secondaryAvatar = ReportUtils.getIcons(props.report, {})[props.report.isOwnPolicyExpenseChat ? 0 : 1] + secondaryAvatar = ReportUtils.getIcons(props.report, {})[props.report.isOwnPolicyExpenseChat ? 0 : 1]; } const icon = {source: avatarSource, type: isWorkspaceActor ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR, name: displayName, id: actorAccountID}; @@ -128,6 +130,45 @@ function ReportActionItemSingle(props) { } }, [isWorkspaceActor, props.report.reportID, actorAccountID, props.action.delegateAccountID]); + const getAvatar = () => { + if (displayAllActors) { + return ( + + ); + } else if (props.shouldShowSubscriptAvatar) { + return ( + + ); + } else { + return ( + + + + + + ); + } + } + return ( - {props.shouldShowSubscriptAvatar ? ( - - ) : ( - - - - - - )} + {getAvatar()} From f1b45dcdb7ef8aaa1f342f96fd6cefd65284125a Mon Sep 17 00:00:00 2001 From: Alberto Date: Mon, 17 Jul 2023 23:31:56 +0200 Subject: [PATCH 026/223] lint --- src/pages/home/report/ReportActionItem.js | 4 ++ .../home/report/ReportActionItemSingle.js | 39 +++++++++---------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 037c510ddbc2..25a6fd78f35a 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -98,6 +98,9 @@ const propTypes = { /** Is this the only report action on the report? */ isOnlyReportAction: PropTypes.bool, + + /** IOU report for this action, if any */ + iouReport: reportPropTypes.isRequired, }; const defaultProps = { @@ -107,6 +110,7 @@ const defaultProps = { shouldShowSubscriptAvatar: false, hasOutstandingIOU: false, isOnlyReportAction: false, + iouReport: undefined, }; function ReportActionItem(props) { diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 50ed50739842..0696e2fb006f 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -103,7 +103,7 @@ function ReportActionItemSingle(props) { if (displayAllActors) { const secondaryUserDetails = props.personalDetailsList[props.iouReport.ownerAccountID] || {}; const secondaryDisplayName = lodashGet(secondaryUserDetails, 'displayName', ''); - displayName = displayName + ' & ' + secondaryDisplayName; + displayName = `${displayName} & ${secondaryDisplayName}`; secondaryAvatar = {source: UserUtils.getAvatar(secondaryUserDetails.avatar, props.iouReport.ownerAccountID), type: CONST.ICON_TYPE_AVATAR, name: secondaryDisplayName, id: props.iouReport.ownerAccountID}; } else if (!isWorkspaceActor) { secondaryAvatar = ReportUtils.getIcons(props.report, {})[props.report.isOwnPolicyExpenseChat ? 0 : 1]; @@ -135,8 +135,8 @@ function ReportActionItemSingle(props) { return ( ); } else if (props.shouldShowSubscriptAvatar) { @@ -149,24 +149,23 @@ function ReportActionItemSingle(props) { noMargin /> ); - } else { - return ( - - - - - - ); } + return ( + + + + + + ); } return ( From d09e3534dc68a3d84fde62c35c307425df1425cc Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 17 Jul 2023 16:11:47 -0700 Subject: [PATCH 027/223] Add beta check for distance requests --- src/CONST.js | 1 + src/libs/Permissions.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/CONST.js b/src/CONST.js index e5afc1e9a861..fe519ee57e85 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -270,6 +270,7 @@ const CONST = { TASKS: 'tasks', THREADS: 'threads', SCAN_RECEIPTS: 'scanReceipts', + DISTANCE_REQUESTS: 'distanceRequests', }, BUTTON_STATES: { DEFAULT: 'default', diff --git a/src/libs/Permissions.js b/src/libs/Permissions.js index 3617900109f2..58cebd499264 100644 --- a/src/libs/Permissions.js +++ b/src/libs/Permissions.js @@ -110,6 +110,10 @@ function canUseScanReceipts(betas) { return _.contains(betas, CONST.BETAS.SCAN_RECEIPTS) || canUseAllBetas(betas); } +function canUseDistanceRequests(betas) { + return _.contains(betas, CONST.BETAS.DISTANCE_REQUESTS) || canUseAllBetas(betas); +} + export default { canUseChronos, canUseIOU, @@ -123,4 +127,5 @@ export default { canUsePasswordlessLogins, canUseTasks, canUseScanReceipts, + canUseDistanceRequests, }; From 6c0487bb47b73c79052832acaccb0d2c6071a7f8 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 17 Jul 2023 16:12:09 -0700 Subject: [PATCH 028/223] Tab for distance requests if on beta --- src/components/TabSelector.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/components/TabSelector.js b/src/components/TabSelector.js index f96af7d2252d..e0ceb7c19e5e 100644 --- a/src/components/TabSelector.js +++ b/src/components/TabSelector.js @@ -9,17 +9,23 @@ import withLocalize from './withLocalize'; import ONYXKEYS from '../ONYXKEYS'; import TabSelectorItem from './TabSelectorItem'; import Tab from '../libs/actions/Tab'; +import * as Permissions from '../libs/Permissions'; const TAB_MANUAL = 'manual'; const TAB_SCAN = 'scan'; +const TAB_DISTANCE = 'distance'; const propTypes = { /** Which tab has been selected */ tabSelected: PropTypes.string, + + /** List of betas available to current user */ + betas: PropTypes.arrayOf(PropTypes.string), }; const defaultProps = { tabSelected: TAB_MANUAL, + betas: [], }; function TabSelector(props) { @@ -42,6 +48,16 @@ function TabSelector(props) { Tab.onTabPress(TAB_SCAN); }} /> + {Permissions.canUseDistanceRequests(props.betas) && ( + { + Tab.onTabPress(TAB_DISTANCE); + }} + /> + )} ); } @@ -56,5 +72,8 @@ export default compose( tabSelected: { key: ONYXKEYS.TAB_SELECTOR, }, + betas: { + key: ONYXKEYS.BETAS, + }, }), )(TabSelector); From 57ef1de469ea9a2787b71440547a493127633c93 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 17 Jul 2023 16:46:51 -0700 Subject: [PATCH 029/223] Extract tab constants --- src/CONST.js | 5 +++++ src/components/TabSelector.js | 22 ++++++++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index fe519ee57e85..9ab48a74571f 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -2540,6 +2540,11 @@ const CONST = { TRANSLATION_KEYS: { ATTACHMENT: 'common.attachment', }, + TABS: { + MANUAL: 'manual', + SCAN: 'scan', + DISTANCE: 'distance', + }, }; export default CONST; diff --git a/src/components/TabSelector.js b/src/components/TabSelector.js index e0ceb7c19e5e..ea7bf98077f7 100644 --- a/src/components/TabSelector.js +++ b/src/components/TabSelector.js @@ -9,11 +9,9 @@ import withLocalize from './withLocalize'; import ONYXKEYS from '../ONYXKEYS'; import TabSelectorItem from './TabSelectorItem'; import Tab from '../libs/actions/Tab'; -import * as Permissions from '../libs/Permissions'; +import Permissions from '../libs/Permissions'; +import CONST from '../CONST'; -const TAB_MANUAL = 'manual'; -const TAB_SCAN = 'scan'; -const TAB_DISTANCE = 'distance'; const propTypes = { /** Which tab has been selected */ @@ -24,37 +22,37 @@ const propTypes = { }; const defaultProps = { - tabSelected: TAB_MANUAL, + tabSelected: CONST.TABS.MANUAL, betas: [], }; function TabSelector(props) { - const selectedTab = lodashGet(props.tabSelected, 'selected', TAB_MANUAL); + const selectedTab = lodashGet(props.tabSelected, 'selected', CONST.TABS.MANUAL); return ( { - Tab.onTabPress(TAB_MANUAL); + Tab.onTabPress(CONST.TABS.MANUAL); }} /> { - Tab.onTabPress(TAB_SCAN); + Tab.onTabPress(CONST.TABS.SCAN); }} /> {Permissions.canUseDistanceRequests(props.betas) && ( { - Tab.onTabPress(TAB_DISTANCE); + Tab.onTabPress(CONST.TABS.DISTANCE); }} /> )} From 980ed82da83f3000c59a74d62dfdff31ce52304b Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 17 Jul 2023 16:47:30 -0700 Subject: [PATCH 030/223] WIP add distance request tab --- src/pages/iou/MoneyRequestSelectorPage.js | 41 ++++++++++++++--------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index 38bf3a30c867..a4cd9607fd4e 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -78,6 +78,30 @@ function MoneyRequestSelectorPage(props) { Navigation.navigate(ROUTES.getMoneyRequestParticipantsRoute(iouType.current)); }; + const renderTabContent = () => { + switch (selectedTab) { + case CONST.TABS.MANUAL: + return ; + case CONST.TABS.SCAN: + return ; + case CONST.TABS.DISTANCE: + return <>; + default: + return null; + } + }; + return ( @@ -110,22 +134,7 @@ function MoneyRequestSelectorPage(props) { onBackButtonPress={navigateBack} /> - {selectedTab === 'manual' ? ( - - ) : ( - - )} + {renderTabContent()} From 3538981a632e75db99d2b9b38a57fe2be4b44bd5 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 17 Jul 2023 17:12:25 -0700 Subject: [PATCH 031/223] Add dummy DistanceRequest component --- src/components/DistanceRequest.js | 57 +++++++++++++++++++++++ src/pages/iou/MoneyRequestSelectorPage.js | 7 ++- 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/components/DistanceRequest.js diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js new file mode 100644 index 000000000000..50f306620386 --- /dev/null +++ b/src/components/DistanceRequest.js @@ -0,0 +1,57 @@ +import React, {Text} from 'react-native'; +import PropTypes from 'prop-types'; +import CONST from '../CONST'; +import reportPropTypes from '../pages/reportPropTypes'; + +const propTypes = { + route: PropTypes.shape({ + params: PropTypes.shape({ + iouType: PropTypes.string, + reportID: PropTypes.string, + }), + }), + + /** The report on which the request is initiated on */ + report: reportPropTypes, + + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ + iou: PropTypes.shape({ + id: PropTypes.string, + amount: PropTypes.number, + currency: PropTypes.string, + participants: PropTypes.arrayOf( + PropTypes.shape({ + accountID: PropTypes.number, + login: PropTypes.string, + isPolicyExpenseChat: PropTypes.bool, + isOwnPolicyExpenseChat: PropTypes.bool, + selected: PropTypes.bool, + }), + ), + }), +}; + +const defaultProps = { + route: { + params: { + iouType: '', + reportID: '', + }, + }, + report: {}, + iou: { + id: '', + amount: 0, + currency: CONST.CURRENCY.USD, + participants: [], + }, +}; + +function DistanceRequest(props) { + return Distance Request +} + +DistanceRequest.displayName = 'DistanceRequest'; +DistanceRequest.propTypes = propTypes; +DistanceRequest.defaultProps = defaultProps; +export default DistanceRequest; \ No newline at end of file diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index a4cd9607fd4e..b6bf7befb1ff 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -23,6 +23,7 @@ import DragAndDrop from '../../components/DragAndDrop'; import Colors from '../../styles/colors'; import * as IOU from '../../libs/actions/IOU'; import * as ReportUtils from '../../libs/ReportUtils'; +import DistanceRequest from '../../components/DistanceRequest'; function MoneyRequestSelectorPage(props) { const iouType = useRef(lodashGet(props.route, 'params.iouType', '')); @@ -96,7 +97,11 @@ function MoneyRequestSelectorPage(props) { currentUserPersonalDetails={props.currentUserPersonalDetails} />; case CONST.TABS.DISTANCE: - return <>; + return ; default: return null; } From 8f3c610a232e215b61b80c1cfab2b6d1bf186d48 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 17 Jul 2023 17:51:26 -0700 Subject: [PATCH 032/223] Fix simple distance request component --- src/components/DistanceRequest.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index 50f306620386..80d37f4ddacc 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -1,5 +1,6 @@ -import React, {Text} from 'react-native'; +import React from 'react'; import PropTypes from 'prop-types'; +import Text from './Text'; import CONST from '../CONST'; import reportPropTypes from '../pages/reportPropTypes'; @@ -48,10 +49,12 @@ const defaultProps = { }; function DistanceRequest(props) { - return Distance Request + return ( + Distance Request + ); } DistanceRequest.displayName = 'DistanceRequest'; DistanceRequest.propTypes = propTypes; DistanceRequest.defaultProps = defaultProps; -export default DistanceRequest; \ No newline at end of file +export default DistanceRequest; From f650caf57e42117c6127ed3d338faccb9267c810 Mon Sep 17 00:00:00 2001 From: Alberto Date: Tue, 18 Jul 2023 12:37:40 +0200 Subject: [PATCH 033/223] style --- src/pages/home/report/ReportActionItem.js | 2 +- src/pages/home/report/ReportActionItemSingle.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 25a6fd78f35a..225e1bc5ff52 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -100,7 +100,7 @@ const propTypes = { isOnlyReportAction: PropTypes.bool, /** IOU report for this action, if any */ - iouReport: reportPropTypes.isRequired, + iouReport: reportPropTypes, }; const defaultProps = { diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 0696e2fb006f..43e8358b376a 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -139,7 +139,8 @@ function ReportActionItemSingle(props) { shouldShowTooltip /> ); - } else if (props.shouldShowSubscriptAvatar) { + } + if (props.shouldShowSubscriptAvatar) { return ( Date: Tue, 18 Jul 2023 13:50:45 +0200 Subject: [PATCH 034/223] prettier --- src/pages/home/report/ReportActionItem.js | 2 +- .../home/report/ReportActionItemSingle.js | 21 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 225e1bc5ff52..e584359ba554 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -409,7 +409,7 @@ function ReportActionItem(props) { wrapperStyles={[styles.chatItem, isWhisper ? styles.pt1 : {}]} shouldShowSubscriptAvatar={props.shouldShowSubscriptAvatar} report={props.report} - iouReport = {props.iouReport} + iouReport={props.iouReport} hasBeenFlagged={!_.contains([CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING], moderationDecision)} > {content} diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 43e8358b376a..677d07a055dc 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -23,7 +23,7 @@ import reportPropTypes from '../../reportPropTypes'; import * as UserUtils from '../../../libs/UserUtils'; import PressableWithoutFeedback from '../../../components/Pressable/PressableWithoutFeedback'; import UserDetailsTooltip from '../../../components/UserDetailsTooltip'; -import MultipleAvatars from "../../../components/MultipleAvatars"; +import MultipleAvatars from '../../../components/MultipleAvatars'; const propTypes = { /** All the data of the action */ @@ -104,7 +104,12 @@ function ReportActionItemSingle(props) { const secondaryUserDetails = props.personalDetailsList[props.iouReport.ownerAccountID] || {}; const secondaryDisplayName = lodashGet(secondaryUserDetails, 'displayName', ''); displayName = `${displayName} & ${secondaryDisplayName}`; - secondaryAvatar = {source: UserUtils.getAvatar(secondaryUserDetails.avatar, props.iouReport.ownerAccountID), type: CONST.ICON_TYPE_AVATAR, name: secondaryDisplayName, id: props.iouReport.ownerAccountID}; + secondaryAvatar = { + source: UserUtils.getAvatar(secondaryUserDetails.avatar, props.iouReport.ownerAccountID), + type: CONST.ICON_TYPE_AVATAR, + name: secondaryDisplayName, + id: props.iouReport.ownerAccountID, + }; } else if (!isWorkspaceActor) { secondaryAvatar = ReportUtils.getIcons(props.report, {})[props.report.isOwnPolicyExpenseChat ? 0 : 1]; } @@ -134,9 +139,9 @@ function ReportActionItemSingle(props) { if (displayAllActors) { return ( ); } @@ -167,7 +172,7 @@ function ReportActionItemSingle(props) { ); - } + }; return ( @@ -179,9 +184,7 @@ function ReportActionItemSingle(props) { accessibilityLabel={actorHint} accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} > - - {getAvatar()} - + {getAvatar()} {props.showHeader ? ( From f32c0779407e73778e6dee5cfb483822b59cc141 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 18 Jul 2023 17:32:48 -0700 Subject: [PATCH 035/223] Add car Expensicon --- assets/images/car.svg | 15 +++++++++++++++ src/components/Icon/Expensicons.js | 2 ++ 2 files changed, 17 insertions(+) create mode 100644 assets/images/car.svg diff --git a/assets/images/car.svg b/assets/images/car.svg new file mode 100644 index 000000000000..7172eb1db01e --- /dev/null +++ b/assets/images/car.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/src/components/Icon/Expensicons.js b/src/components/Icon/Expensicons.js index 80ad6d9283a2..f4e1fc5f3827 100644 --- a/src/components/Icon/Expensicons.js +++ b/src/components/Icon/Expensicons.js @@ -15,6 +15,7 @@ import Bug from '../../../assets/images/bug.svg'; import Building from '../../../assets/images/building.svg'; import Calendar from '../../../assets/images/calendar.svg'; import Camera from '../../../assets/images/camera.svg'; +import Car from '../../../assets/images/car.svg'; import Cash from '../../../assets/images/cash.svg'; import ChatBubble from '../../../assets/images/chatbubble.svg'; import Checkmark from '../../../assets/images/checkmark.svg'; @@ -137,6 +138,7 @@ export { Building, Calendar, Camera, + Car, Cash, ChatBubble, Checkmark, From 7810fb055a386f68d0493cd3f413a354613d84db Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 19 Jul 2023 16:35:40 -0700 Subject: [PATCH 036/223] Translate distance tab selector --- src/components/TabSelector.js | 2 +- src/languages/en.js | 1 + src/languages/es.js | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/TabSelector.js b/src/components/TabSelector.js index 83987b859e76..8c25daff44a7 100644 --- a/src/components/TabSelector.js +++ b/src/components/TabSelector.js @@ -47,7 +47,7 @@ function TabSelector(props) { /> {Permissions.canUseDistanceRequests(props.betas) && ( { diff --git a/src/languages/en.js b/src/languages/en.js index 109ed3e19054..ce83c795590a 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -356,6 +356,7 @@ export default { tabSelector: { manual: 'Manual', scan: 'Scan', + distance: 'Distance', }, receipt: { upload: 'Upload receipt', diff --git a/src/languages/es.js b/src/languages/es.js index e783b527801d..f8417d8d28ac 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -355,6 +355,7 @@ export default { tabSelector: { manual: 'Manual', scan: 'Escanear', + distance: 'Distancia', }, receipt: { upload: 'Subir recibo', From 623be15640a987460399af32a2c40d1c107646b0 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 19 Jul 2023 17:00:31 -0700 Subject: [PATCH 037/223] Fix tab contents --- src/components/TabSelector.js | 4 ++-- src/pages/iou/MoneyRequestSelectorPage.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/TabSelector.js b/src/components/TabSelector.js index 8c25daff44a7..87db4c79938f 100644 --- a/src/components/TabSelector.js +++ b/src/components/TabSelector.js @@ -21,7 +21,7 @@ const propTypes = { }; const defaultProps = { - tabSelected: CONST.TABS.MANUAL, + tabSelected: CONST.TAB.TAB_MANUAL, betas: [], }; @@ -48,7 +48,7 @@ function TabSelector(props) { {Permissions.canUseDistanceRequests(props.betas) && ( { Tab.onTabPress(CONST.TAB.TAB_DISTANCE); diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index 53fb0353dd39..5c10ab95a1a0 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -100,14 +100,14 @@ function MoneyRequestSelectorPage(props) { const renderTabContent = () => { switch (selectedTab) { - case CONST.TABS.MANUAL: + case CONST.TAB.TAB_MANUAL: return ; - case CONST.TABS.SCAN: + case CONST.TAB.TAB_SCAN: return ; - case CONST.TABS.DISTANCE: + case CONST.TAB.TAB_DISTANCE: return Date: Wed, 19 Jul 2023 18:06:25 -0700 Subject: [PATCH 038/223] accessibility label is required --- src/components/TabSelectorItem.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/TabSelectorItem.js b/src/components/TabSelectorItem.js index a5e5eac4b620..08dbe8b611da 100644 --- a/src/components/TabSelectorItem.js +++ b/src/components/TabSelectorItem.js @@ -37,6 +37,7 @@ function TabSelectorItem(props) { From f7326a409b339c811733bc9120def1bd9a9e2ad0 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 19 Jul 2023 18:14:38 -0700 Subject: [PATCH 039/223] Create empty transaction linked to iou --- src/components/DistanceRequest.js | 29 ++++++++++++++++++++++++++--- src/libs/actions/IOU.js | 8 ++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index 80d37f4ddacc..ff2e81525a13 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -1,8 +1,15 @@ -import React from 'react'; +import React, { useEffect } from 'react'; +import {View} from 'react-native'; import PropTypes from 'prop-types'; +import lodashGet from 'lodash/get'; +import {withOnyx} from 'react-native-onyx'; import Text from './Text'; import CONST from '../CONST'; import reportPropTypes from '../pages/reportPropTypes'; +import * as IOU from '../libs/actions/IOU'; +import styles from '../styles/styles'; +import ONYXKEYS from '../ONYXKEYS'; + const propTypes = { route: PropTypes.shape({ @@ -49,12 +56,28 @@ const defaultProps = { }; function DistanceRequest(props) { + const transactionID = lodashGet(props.iou, 'transactionID', ''); + useEffect(() => { + if (transactionID) { + return; + } + IOU.createEmptyTransaction(); + }, [transactionID]); + return ( - Distance Request + + Distance Request + transactionID: {transactionID} + ); } DistanceRequest.displayName = 'DistanceRequest'; DistanceRequest.propTypes = propTypes; DistanceRequest.defaultProps = defaultProps; -export default DistanceRequest; +export default withOnyx({ + iou: {key: ONYXKEYS.IOU}, + report: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID', '')}`, + }, +})(DistanceRequest); diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index e1760542884d..ea857b97562b 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -19,6 +19,7 @@ import TransactionUtils from '../TransactionUtils'; import * as ErrorUtils from '../ErrorUtils'; import * as UserUtils from '../UserUtils'; import * as Report from './Report'; +import * as NumberUtils from '../NumberUtils'; const chatReports = {}; const iouReports = {}; @@ -1399,6 +1400,12 @@ function setMoneyRequestReceipt(receiptPath) { Onyx.merge(ONYXKEYS.IOU, {receiptPath}); } +function createEmptyTransaction() { + const transactionID = NumberUtils.rand64(); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {}); + Onyx.merge(ONYXKEYS.IOU, {transactionID}); +} + export { deleteMoneyRequest, splitBill, @@ -1416,4 +1423,5 @@ export { setMoneyRequestDescription, setMoneyRequestParticipants, setMoneyRequestReceipt, + createEmptyTransaction, }; From 8f75d97023190ad7a71f9c0b0b3649991b435c84 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 19 Jul 2023 18:18:24 -0700 Subject: [PATCH 040/223] Make tab title required for accessibility --- src/components/TabSelectorItem.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/TabSelectorItem.js b/src/components/TabSelectorItem.js index 08dbe8b611da..e81bc83f7b24 100644 --- a/src/components/TabSelectorItem.js +++ b/src/components/TabSelectorItem.js @@ -9,6 +9,9 @@ import PressableWithFeedback from './Pressable/PressableWithFeedback'; import fontFamily from '../styles/fontFamily'; const propTypes = { + /** Title of the tab */ + title: PropTypes.string.isRequired, + /** Function to call when onPress */ onPress: PropTypes.func, @@ -17,16 +20,12 @@ const propTypes = { /** True if tab is the selected item */ selected: PropTypes.bool, - - /** Title of the tab */ - title: PropTypes.string, }; const defaultProps = { onPress: () => {}, icon: () => {}, selected: false, - title: '', }; function TabSelectorItem(props) { From a1421badc20540172e5518422152f312c7047859 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 19 Jul 2023 18:35:07 -0700 Subject: [PATCH 041/223] Remove unused props for now --- src/components/DistanceRequest.js | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index ff2e81525a13..af508f4c514b 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -5,23 +5,12 @@ import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; import Text from './Text'; import CONST from '../CONST'; -import reportPropTypes from '../pages/reportPropTypes'; import * as IOU from '../libs/actions/IOU'; import styles from '../styles/styles'; import ONYXKEYS from '../ONYXKEYS'; const propTypes = { - route: PropTypes.shape({ - params: PropTypes.shape({ - iouType: PropTypes.string, - reportID: PropTypes.string, - }), - }), - - /** The report on which the request is initiated on */ - report: reportPropTypes, - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ iou: PropTypes.shape({ id: PropTypes.string, @@ -40,13 +29,6 @@ const propTypes = { }; const defaultProps = { - route: { - params: { - iouType: '', - reportID: '', - }, - }, - report: {}, iou: { id: '', amount: 0, @@ -77,7 +59,4 @@ DistanceRequest.propTypes = propTypes; DistanceRequest.defaultProps = defaultProps; export default withOnyx({ iou: {key: ONYXKEYS.IOU}, - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID', '')}`, - }, })(DistanceRequest); From f68f998281c3f27f8f9e5ea32204519d1d891a79 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 19 Jul 2023 18:36:47 -0700 Subject: [PATCH 042/223] Clean up and prettify --- src/components/DistanceRequest.js | 3 +- src/pages/iou/MoneyRequestSelectorPage.js | 50 ++++++++++++----------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index af508f4c514b..423e03937667 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, {useEffect} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import lodashGet from 'lodash/get'; @@ -9,7 +9,6 @@ import * as IOU from '../libs/actions/IOU'; import styles from '../styles/styles'; import ONYXKEYS from '../ONYXKEYS'; - const propTypes = { /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ iou: PropTypes.shape({ diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index f9dff4b30863..0b47c251f3ec 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -1,8 +1,8 @@ import {withOnyx} from 'react-native-onyx'; import {View} from 'react-native'; -import React, {useCallback, useRef, useState} from 'react'; +import React, {useRef, useState} from 'react'; import lodashGet from 'lodash/get'; -import _, {compose} from 'underscore'; +import {compose} from 'underscore'; import {PortalHost} from '@gorhom/portal'; import PropTypes from 'prop-types'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '../../components/withCurrentUserPersonalDetails'; @@ -21,14 +21,10 @@ import MoneyRequestAmountPage from './steps/MoneyRequestAmountPage'; import ReceiptSelector from './ReceiptSelector'; import DragAndDrop from '../../components/DragAndDrop'; import * as IOU from '../../libs/actions/IOU'; -import * as ReportUtils from '../../libs/ReportUtils'; import DistanceRequest from '../../components/DistanceRequest'; import reportPropTypes from '../reportPropTypes'; import NavigateToNextIOUPage from './NavigateToNextIOUPage'; -import ConfirmModal from '../../components/ConfirmModal'; -import * as FileUtils from '../../libs/fileDownload/FileUtils'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; -import Receipt from '../../libs/actions/Receipt'; import AttachmentUtils from '../../libs/AttachmentUtils'; const propTypes = { @@ -107,26 +103,32 @@ function MoneyRequestSelectorPage(props) { const renderTabContent = () => { switch (selectedTab) { case CONST.TAB.TAB_MANUAL: - return ; + return ( + + ); case CONST.TAB.TAB_SCAN: - return ; + return ( + + ); case CONST.TAB.TAB_DISTANCE: - return ; + return ( + + ); default: return null; } From f72a4afa65e7f1816b76cf4b5380b83dd26deb78 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 19 Jul 2023 18:41:27 -0700 Subject: [PATCH 043/223] Use selector per checklist --- src/components/DistanceRequest.js | 35 +++++++------------------------ 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index 423e03937667..7b8333ab3ae3 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -1,54 +1,33 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; -import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; import Text from './Text'; -import CONST from '../CONST'; import * as IOU from '../libs/actions/IOU'; import styles from '../styles/styles'; import ONYXKEYS from '../ONYXKEYS'; const propTypes = { - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: PropTypes.shape({ - id: PropTypes.string, - amount: PropTypes.number, - currency: PropTypes.string, - participants: PropTypes.arrayOf( - PropTypes.shape({ - accountID: PropTypes.number, - login: PropTypes.string, - isPolicyExpenseChat: PropTypes.bool, - isOwnPolicyExpenseChat: PropTypes.bool, - selected: PropTypes.bool, - }), - ), - }), + /** The transactionID of this request */ + transactionID: PropTypes.string, }; const defaultProps = { - iou: { - id: '', - amount: 0, - currency: CONST.CURRENCY.USD, - participants: [], - }, + transactionID: '', }; function DistanceRequest(props) { - const transactionID = lodashGet(props.iou, 'transactionID', ''); useEffect(() => { - if (transactionID) { + if (props.transactionID) { return; } IOU.createEmptyTransaction(); - }, [transactionID]); + }, [props.transactionID]); return ( Distance Request - transactionID: {transactionID} + transactionID: {props.transactionID} ); } @@ -57,5 +36,5 @@ DistanceRequest.displayName = 'DistanceRequest'; DistanceRequest.propTypes = propTypes; DistanceRequest.defaultProps = defaultProps; export default withOnyx({ - iou: {key: ONYXKEYS.IOU}, + transactionID: {key: ONYXKEYS.IOU, selector: (iou) => iou.transactionID}, })(DistanceRequest); From ccd3017ae43ac5fd00c431b4b043707379e75e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Thu, 20 Jul 2023 14:04:32 +0200 Subject: [PATCH 044/223] wip: adding status page --- src/ROUTES.js | 10 +++++ src/languages/en.js | 3 ++ src/languages/es.js | 3 ++ .../AppNavigator/ModalStackNavigators.js | 7 ++++ src/libs/Navigation/linkingConfig.js | 4 ++ .../Profile/CustomStatus/StatusPage.js | 42 +++++++++++++++++++ src/pages/settings/Profile/ProfilePage.js | 5 +++ 7 files changed, 74 insertions(+) create mode 100644 src/pages/settings/Profile/CustomStatus/StatusPage.js diff --git a/src/ROUTES.js b/src/ROUTES.js index d32deaa63ab0..47dc9d6d7790 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -12,6 +12,11 @@ const IOU_SEND = 'send/new'; const NEW_TASK = 'new/task'; const SETTINGS_PERSONAL_DETAILS = 'settings/profile/personal-details'; const SETTINGS_CONTACT_METHODS = 'settings/profile/contact-methods'; +const SETTINGS_STATUS = 'settings/profile/status'; +const SETTINGS_STATUS_SET = 'settings/profile/status/set'; +const SETTINGS_STATUS_CLEAR_AFTER = 'settings/profile/status/clear-after'; +const SETTINGS_STATUS_SET_TIME = 'settings/profile/status/set-time'; +const SETTINGS_STATUS_SET_DATE = 'settings/profile/status/set-date'; export default { BANK_ACCOUNT: 'bank-account', @@ -59,6 +64,11 @@ export default { SETTINGS_2FA_CODES: 'settings/security/two-factor-auth/codes', SETTINGS_2FA_VERIFY: 'settings/security/two-factor-auth/verify', SETTINGS_2FA_SUCCESS: 'settings/security/two-factor-auth/success', + SETTINGS_STATUS, + SETTINGS_STATUS_SET, + SETTINGS_STATUS_CLEAR_AFTER, + SETTINGS_STATUS_SET_TIME, + SETTINGS_STATUS_SET_DATE, NEW_GROUP: 'new/group', NEW_CHAT: 'new/chat', NEW_TASK, diff --git a/src/languages/en.js b/src/languages/en.js index 4cf79bbcd097..b30d244274e0 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -845,6 +845,9 @@ export default { setPasswordLinkInvalid: 'This set password link is invalid or has expired. A new one is waiting for you in your email inbox!', validateAccount: 'Verify account', }, + statusPage: { + status: 'Status', + }, stepCounter: ({step, total, text}) => { let result = `Step ${step}`; diff --git a/src/languages/es.js b/src/languages/es.js index 27fde8e602ad..e20be36d1e66 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -849,6 +849,9 @@ export default { setPasswordLinkInvalid: 'El enlace para configurar tu contraseña ha expirado. Te hemos enviado un nuevo enlace a tu correo.', validateAccount: 'Verificar cuenta', }, + StatusPage: { + status: 'Estado', + }, stepCounter: ({step, total, text}) => { let result = `Paso ${step}`; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 732ec938e8ae..66f35cd10233 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -522,6 +522,13 @@ const SettingsModalStackNavigator = createModalStackNavigator([ }, name: 'Settings_Add_Bank_Account', }, + { + getComponent: () => { + const SettingsStatus = require('../../../pages/settings/Profile/CustomStatus/StatusPage').default; + return SettingsStatus; + }, + name: 'Settings_Status', + }, { getComponent: () => { const WorkspaceInitialPage = require('../../../pages/workspace/WorkspaceInitialPage').default; diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index 2657be156a01..d4b705d946fe 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -181,6 +181,10 @@ export default { path: ROUTES.SETTINGS_SHARE_CODE, exact: true, }, + Settings_Status: { + path: ROUTES.SETTINGS_STATUS, + exact: true, + }, Workspace_Initial: { path: ROUTES.WORKSPACE_INITIAL, }, diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.js b/src/pages/settings/Profile/CustomStatus/StatusPage.js new file mode 100644 index 000000000000..d039cd358115 --- /dev/null +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.js @@ -0,0 +1,42 @@ +import React from 'react'; +import {View} from 'react-native'; +import ScreenWrapper from '../../../../components/ScreenWrapper'; +import HeaderWithBackButton from '../../../../components/HeaderWithBackButton'; +import ROUTES from '../../../../ROUTES'; +import Navigation from '../../../../libs/Navigation/Navigation'; +import styles from '../../../../styles/styles'; +import Text from '../../../../components/Text'; +import MenuItemWithTopDescription from '../../../../components/MenuItemWithTopDescription'; + +function StatusPage() { + return ( + + Navigation.goBack(ROUTES.SETTINGS_PROFILE)} + /> + + Set your status + Add an emoji to give your colleagues and friends an easy way to know what's going on. You can optionally add a message too! + + + Navigation.navigate(ROUTES.SETTINGS_STATUS_SET)} + /> + Navigation.navigate(ROUTES.SETTINGS_STATUS_CLEAR_AFTER)} + /> + + + ); +} + +StatusPage.displayName = 'StatusPage'; + +export default StatusPage; diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 07db3d0cdffb..e66a06938ba9 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -73,6 +73,11 @@ function ProfilePage(props) { pageRoute: ROUTES.SETTINGS_CONTACT_METHODS, brickRoadIndicator: contactMethodBrickRoadIndicator, }, + { + description: props.translate('statusPage.status'), + title: '', // TODO: Get the status from onyx + pageRoute: ROUTES.SETTINGS_STATUS, + }, { description: props.translate('pronounsPage.pronouns'), title: getPronouns(), From cf6fa1cd3af47f56c7bd352f44b55f9ab7cc12fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Thu, 20 Jul 2023 14:50:20 +0200 Subject: [PATCH 045/223] wip --- src/languages/en.js | 2 ++ src/pages/settings/Profile/CustomStatus/StatusPage.js | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index b30d244274e0..cf9710a2b516 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -847,6 +847,8 @@ export default { }, statusPage: { status: 'Status', + setStatusTitle: 'Set your status', + statusExplanation: "Add an emoji to give your colleagues and friends an easy way to know what's going on. You can optionally add a message too!", }, stepCounter: ({step, total, text}) => { let result = `Step ${step}`; diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.js b/src/pages/settings/Profile/CustomStatus/StatusPage.js index d039cd358115..bb696a16a0e2 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.js @@ -7,8 +7,11 @@ import Navigation from '../../../../libs/Navigation/Navigation'; import styles from '../../../../styles/styles'; import Text from '../../../../components/Text'; import MenuItemWithTopDescription from '../../../../components/MenuItemWithTopDescription'; +import useLocalize from '../../../../hooks/useLocalize'; function StatusPage() { + const localize = useLocalize(); + return ( Navigation.goBack(ROUTES.SETTINGS_PROFILE)} /> - Set your status - Add an emoji to give your colleagues and friends an easy way to know what's going on. You can optionally add a message too! + {localize.translate('statusPage.setStatusTitle')} + {localize.translate('statusPage.statusExplanation')} - + Date: Thu, 20 Jul 2023 15:38:12 +0200 Subject: [PATCH 046/223] WIP --- src/components/Tooltip/index.js | 224 +++++++++++++++----------------- 1 file changed, 105 insertions(+), 119 deletions(-) diff --git a/src/components/Tooltip/index.js b/src/components/Tooltip/index.js index 0454327de284..db67335d719a 100644 --- a/src/components/Tooltip/index.js +++ b/src/components/Tooltip/index.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React, {PureComponent} from 'react'; +import React, {memo, useCallback, useEffect, useRef, useState} from 'react'; import {Animated} from 'react-native'; import {BoundsObserver} from '@react-ng/bounds-observer'; import TooltipRenderedOnPageBody from './TooltipRenderedOnPageBody'; @@ -11,111 +11,99 @@ import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; import compose from '../../libs/compose'; import withLocalize from '../withLocalize'; +const hasHoverSupport = DeviceCapabilities.hasHoverSupport(); + /** * A component used to wrap an element intended for displaying a tooltip. The term "tooltip's target" refers to the * wrapped element, which, upon hover, triggers the tooltip to be shown. + * @param {propTypes} props + * @returns {ReactNodeLike} */ -class Tooltip extends PureComponent { - constructor(props) { - super(props); - - this.state = { - // Is tooltip already rendered on the page's body? This happens once. - isRendered: false, - - // Is the tooltip currently visible? - isVisible: false, - - // The distance between the left side of the wrapper view and the left side of the window - xOffset: 0, - - // The distance between the top of the wrapper view and the top of the window - yOffset: 0, - - // The width and height of the wrapper view - wrapperWidth: 0, - wrapperHeight: 0, - }; - - // Whether the tooltip is first tooltip to activate the TooltipSense - this.isTooltipSenseInitiator = false; - this.animation = new Animated.Value(0); - this.hasHoverSupport = DeviceCapabilities.hasHoverSupport(); - - this.showTooltip = this.showTooltip.bind(this); - this.hideTooltip = this.hideTooltip.bind(this); - this.updateBounds = this.updateBounds.bind(this); - this.isAnimationCanceled = React.createRef(false); - } - - // eslint-disable-next-line rulesdir/prefer-early-return - componentDidUpdate(prevProps) { - // if the tooltip text changed before the initial animation was finished, then the tooltip won't be shown - // we need to show the tooltip again - if (this.state.isVisible && this.isAnimationCanceled.current && this.props.text && prevProps.text !== this.props.text) { - this.isAnimationCanceled.current = false; - this.showTooltip(); - } - } - - /** - * Update the tooltip bounding rectangle - * - * @param {Object} bounds - updated bounds - */ - updateBounds(bounds) { - if (bounds.width === 0) { - this.setState({isRendered: false}); - } - this.setState({ - wrapperWidth: bounds.width, - wrapperHeight: bounds.height, - xOffset: bounds.x, - yOffset: bounds.y, - }); - } +function Tooltip(props) { + // Is tooltip already rendered on the page's body? happens once. + const [isRendered, setIsRendered] = useState(false); + // Is the tooltip currently visible? + const [isVisible, setIsVisible] = useState(false); + // The distance between the left side of the wrapper view and the left side of the window + const [xOffset, setXOffset] = useState(0); + // The distance between the top of the wrapper view and the top of the window + const [yOffset, setYOffset] = useState(0); + // The width and height of the wrapper view + const [wrapperWidth, setWrapperWidth] = useState(0); + const [wrapperHeight, setWrapperHeight] = useState(0); + + const isTooltipSenseInitiator = useRef(false); + const animation = useRef(new Animated.Value(0)); + const isAnimationCanceled = useRef(false); + const prevText = useRef(props.text); /** * Display the tooltip in an animation. */ - showTooltip() { - if (!this.state.isRendered) { - this.setState({isRendered: true}); + const showTooltip = useCallback(() => { + if (!isRendered) { + setIsRendered(true); } - this.setState({isVisible: true}); + setIsVisible(true); - this.animation.stopAnimation(); + animation.current.stopAnimation(); // When TooltipSense is active, immediately show the tooltip if (TooltipSense.isActive()) { - this.animation.setValue(1); + animation.setValue(1); } else { - this.isTooltipSenseInitiator = true; - Animated.timing(this.animation, { + isTooltipSenseInitiator.current = true; + Animated.timing(animation.current, { toValue: 1, duration: 140, delay: 500, useNativeDriver: false, }).start(({finished}) => { - this.isAnimationCanceled.current = !finished; + isAnimationCanceled.current = !finished; }); } TooltipSense.activate(); - } + }, [isRendered]); + + useEffect(() => { + // if the tooltip text changed before the initial animation was finished, then the tooltip won't be shown + // we need to show the tooltip again + if (isVisible && isAnimationCanceled.current && props.text && prevText !== props.text) { + isAnimationCanceled.current = false; + showTooltip(); + } + + prevText.current = props.text; + }, [isVisible, props.text, showTooltip]); + + /** + * Update the tooltip bounding rectangle + * + * @param {Object} bounds - updated bounds + */ + const updateBounds = (bounds) => { + if (bounds.width === 0) { + setIsRendered(false); + } + setWrapperWidth(bounds.width); + setWrapperHeight(bounds.height); + setXOffset(bounds.x); + setYOffset(bounds.y); + }; /** * Hide the tooltip in an animation. */ - hideTooltip() { - this.animation.stopAnimation(); + const hideTooltip = () => { + animation.current.stopAnimation(); - if (TooltipSense.isActive() && !this.isTooltipSenseInitiator) { - this.animation.setValue(0); + if (TooltipSense.isActive() && !isTooltipSenseInitiator.current) { + animation.current.setValue(0); } else { // Hide the first tooltip which initiated the TooltipSense with animation - this.isTooltipSenseInitiator = false; - Animated.timing(this.animation, { + isTooltipSenseInitiator.current = false; + Animated.timing(animation.current, { toValue: 0, duration: 140, useNativeDriver: false, @@ -124,53 +112,51 @@ class Tooltip extends PureComponent { TooltipSense.deactivate(); - this.setState({isVisible: false}); - } + setIsVisible(false); + }; - render() { - // Skip the tooltip and return the children if the text is empty, - // we don't have a render function or the device does not support hovering - if ((_.isEmpty(this.props.text) && this.props.renderTooltipContent == null) || !this.hasHoverSupport) { - return this.props.children; - } + // Skip the tooltip and return the children if the text is empty, + // we don't have a render function or the device does not support hovering + if ((_.isEmpty(props.text) && props.renderTooltipContent == null) || !hasHoverSupport) { + return props.children; + } - return ( - <> - {this.state.isRendered && ( - - )} - + {isRendered && ( + + )} + + - - {this.props.children} - - - - ); - } + {props.children} + + + + ); } Tooltip.propTypes = tooltipPropTypes.propTypes; Tooltip.defaultProps = tooltipPropTypes.defaultProps; -export default compose(withWindowDimensions, withLocalize)(Tooltip); +export default compose(withWindowDimensions, withLocalize)(memo(Tooltip)); From 3b8a08639c1539dcce61b2a330e7189d71773197 Mon Sep 17 00:00:00 2001 From: jczekalski Date: Thu, 20 Jul 2023 16:04:05 +0200 Subject: [PATCH 047/223] fix .current issues --- src/components/Tooltip/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Tooltip/index.js b/src/components/Tooltip/index.js index db67335d719a..32eb54a9592b 100644 --- a/src/components/Tooltip/index.js +++ b/src/components/Tooltip/index.js @@ -51,7 +51,7 @@ function Tooltip(props) { // When TooltipSense is active, immediately show the tooltip if (TooltipSense.isActive()) { - animation.setValue(1); + animation.current.setValue(1); } else { isTooltipSenseInitiator.current = true; Animated.timing(animation.current, { @@ -125,7 +125,7 @@ function Tooltip(props) { <> {isRendered && ( Date: Thu, 20 Jul 2023 16:28:58 +0200 Subject: [PATCH 048/223] cleanup --- src/components/Tooltip/index.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/Tooltip/index.js b/src/components/Tooltip/index.js index 32eb54a9592b..ebb5afb420a8 100644 --- a/src/components/Tooltip/index.js +++ b/src/components/Tooltip/index.js @@ -10,13 +10,14 @@ import TooltipSense from './TooltipSense'; import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; import compose from '../../libs/compose'; import withLocalize from '../withLocalize'; +import usePrevious from '../../hooks/usePrevious'; const hasHoverSupport = DeviceCapabilities.hasHoverSupport(); /** * A component used to wrap an element intended for displaying a tooltip. The term "tooltip's target" refers to the * wrapped element, which, upon hover, triggers the tooltip to be shown. - * @param {propTypes} props + * @param {propTypes} props * @returns {ReactNodeLike} */ function Tooltip(props) { @@ -35,7 +36,7 @@ function Tooltip(props) { const isTooltipSenseInitiator = useRef(false); const animation = useRef(new Animated.Value(0)); const isAnimationCanceled = useRef(false); - const prevText = useRef(props.text); + const prevText = usePrevious(props.text); /** * Display the tooltip in an animation. @@ -69,13 +70,14 @@ function Tooltip(props) { useEffect(() => { // if the tooltip text changed before the initial animation was finished, then the tooltip won't be shown // we need to show the tooltip again + if (isVisible && isAnimationCanceled.current && props.text && prevText !== props.text) { isAnimationCanceled.current = false; showTooltip(); } prevText.current = props.text; - }, [isVisible, props.text, showTooltip]); + }, [isVisible, props.text, prevText, showTooltip]); /** * Update the tooltip bounding rectangle @@ -137,8 +139,8 @@ function Tooltip(props) { maxWidth={props.maxWidth} numberOfLines={props.numberOfLines} renderTooltipContent={props.renderTooltipContent} - // We pass a key, so whenever the content changes component will completely remount with a fresh - // prevents flickering/moving while remaining performant. + // We pass a key, so whenever the content changes this component will completely remount with a fresh state. + // This prevents flickering/moving while remaining performant. key={[props.text, ...props.renderTooltipContentKey, props.preferredLocale]} /> )} From 237c036a24fc65b938e8ec3f43a874b229d47af7 Mon Sep 17 00:00:00 2001 From: jczekalski Date: Thu, 20 Jul 2023 16:39:49 +0200 Subject: [PATCH 049/223] clean up --- src/components/Tooltip/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/Tooltip/index.js b/src/components/Tooltip/index.js index ebb5afb420a8..d2af74cad2b5 100644 --- a/src/components/Tooltip/index.js +++ b/src/components/Tooltip/index.js @@ -67,16 +67,14 @@ function Tooltip(props) { TooltipSense.activate(); }, [isRendered]); + // eslint-disable-next-line rulesdir/prefer-early-return useEffect(() => { // if the tooltip text changed before the initial animation was finished, then the tooltip won't be shown // we need to show the tooltip again - if (isVisible && isAnimationCanceled.current && props.text && prevText !== props.text) { isAnimationCanceled.current = false; showTooltip(); } - - prevText.current = props.text; }, [isVisible, props.text, prevText, showTooltip]); /** From c3edf1a7e14f75311c31e598a3ad763c73e35ec7 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Tue, 18 Jul 2023 19:57:05 +0200 Subject: [PATCH 050/223] check Keyboard status before dismissing --- src/components/ScreenWrapper/index.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index f5f0f2fadd41..9803914f39d4 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -29,6 +29,22 @@ class ScreenWrapper extends React.Component { onPanResponderRelease: toggleTestToolsModal, }); + this.keyboardDissmissPanResponder = PanResponder.create({ + onMoveShouldSetPanResponderCapture: (e, gestureState) => { + const isHorizontalSwipe = Math.abs(gestureState.dx) > Math.abs(gestureState.dy); + if (isHorizontalSwipe && this.props.shouldDismissKeyboardBeforeClose) { + return true; + } + return false; + }, + onPanResponderGrant: () => { + if(!Keyboard.isVisible()) { + return; + } + Keyboard.dismiss(); + }, + }); + this.state = { didScreenTransitionEnd: false, }; From ed2e461a0a8911f149ec8ee19ed30d278dc03094 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 19 Jul 2023 20:30:26 +0200 Subject: [PATCH 051/223] check if browser is mobile before keboard dismissing --- src/components/ScreenWrapper/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index 9803914f39d4..535669576c22 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -19,6 +19,7 @@ import withWindowDimensions from '../withWindowDimensions'; import withEnvironment from '../withEnvironment'; import toggleTestToolsModal from '../../libs/actions/TestTool'; import CustomDevMenu from '../CustomDevMenu'; +import * as Browser from '../../libs/Browser'; class ScreenWrapper extends React.Component { constructor(props) { @@ -38,7 +39,7 @@ class ScreenWrapper extends React.Component { return false; }, onPanResponderGrant: () => { - if(!Keyboard.isVisible()) { + if(!Keyboard.isVisible() || !Browser.isMobile()) { return; } Keyboard.dismiss(); From 7e10cdff3fee6345599c32d91fcf137d1c42a397 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 20 Jul 2023 16:55:20 +0200 Subject: [PATCH 052/223] pretty prettier --- src/components/ScreenWrapper/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index 535669576c22..f29f562cf622 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -39,7 +39,7 @@ class ScreenWrapper extends React.Component { return false; }, onPanResponderGrant: () => { - if(!Keyboard.isVisible() || !Browser.isMobile()) { + if (!Keyboard.isVisible() || !Browser.isMobile()) { return; } Keyboard.dismiss(); From efce4e4f39e5a1c75994b8ea6889cca39b71555a Mon Sep 17 00:00:00 2001 From: jczekalski Date: Thu, 20 Jul 2023 19:36:57 +0200 Subject: [PATCH 053/223] add missing comment --- src/components/Tooltip/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Tooltip/index.js b/src/components/Tooltip/index.js index d2af74cad2b5..004d27ef969c 100644 --- a/src/components/Tooltip/index.js +++ b/src/components/Tooltip/index.js @@ -33,6 +33,7 @@ function Tooltip(props) { const [wrapperWidth, setWrapperWidth] = useState(0); const [wrapperHeight, setWrapperHeight] = useState(0); + // Whether the tooltip is first tooltip to activate the TooltipSense const isTooltipSenseInitiator = useRef(false); const animation = useRef(new Animated.Value(0)); const isAnimationCanceled = useRef(false); From 6437e01a76aeec3fd26d76dafc31d9a66f594e14 Mon Sep 17 00:00:00 2001 From: Alberto Date: Fri, 21 Jul 2023 10:26:56 +0200 Subject: [PATCH 054/223] fix typo --- src/pages/home/report/ReportActionItem.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index c21b332e43e1..f2e5577b0ebe 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -574,6 +574,7 @@ export default compose( }, iouReport: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.iouReportID}`, + }, emojiReactions: { key: ({action}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`, }, From 26abf928aeb1f19dd6b3bce1a3e197e05f1f8e6c Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Fri, 21 Jul 2023 17:19:35 -0400 Subject: [PATCH 055/223] Destructure props --- .../MoneyRequestConfirmationList.js | 132 +++++++++--------- src/components/TabSelector.js | 4 +- src/components/TabSelectorItem.js | 16 +-- src/libs/AttachmentUtils.js | 8 +- src/pages/iou/MoneyRequestSelectorPage.js | 30 ++-- src/pages/iou/ReceiptDropUI.js | 6 +- src/pages/iou/ReceiptSelector/index.js | 20 +-- src/pages/iou/ReceiptSelector/index.native.js | 10 +- 8 files changed, 115 insertions(+), 111 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 9a0c4e6dbd5f..2187947054fe 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -100,10 +100,27 @@ const defaultProps = { ...withCurrentUserPersonalDetailsDefaultProps, }; -function MoneyRequestConfirmationList(props) { - // Destructure functions from props to pass it as a dependecy to useCallback/useMemo hooks. - // Prop functions pass props itself as a "this" value to the function which means they change every time props change. - const {onSendMoney, onConfirm, onSelectParticipant} = props; +function MoneyRequestConfirmationList({ + onConfirm, + onSendMoney, + onSelectParticipant, + hasMultipleParticipants, + iouAmount, + iouComment, + iouCurrencyCode, + iouType, + selectedParticipants, + canModifyParticipants, + isReadOnly, + bankAccountRoute, + policyID, + reportID, + session, + currentUserPersonalDetails, + payeePersonalDetails, + receiptPath, + receiptSource, +}) { const {translate} = useLocalize(); /** @@ -113,44 +130,43 @@ function MoneyRequestConfirmationList(props) { */ const getParticipantsWithAmount = useCallback( (participantsList) => { - const iouAmount = IOUUtils.calculateAmount(participantsList.length, props.iouAmount); - return OptionsListUtils.getIOUConfirmationOptionsFromParticipants(participantsList, CurrencyUtils.convertToDisplayString(iouAmount, props.iouCurrencyCode)); + const ammount = IOUUtils.calculateAmount(participantsList.length, iouAmount); + return OptionsListUtils.getIOUConfirmationOptionsFromParticipants(participantsList, CurrencyUtils.convertToDisplayString(ammount, iouCurrencyCode)); }, - [props.iouAmount, props.iouCurrencyCode], + [iouAmount, iouCurrencyCode], ); const [didConfirm, setDidConfirm] = useState(false); const splitOrRequestOptions = useMemo(() => { - const text = props.receiptPath + const text = receiptPath ? translate('iou.request') - : translate(props.hasMultipleParticipants ? 'iou.splitAmount' : 'iou.requestAmount', { - amount: CurrencyUtils.convertToDisplayString(props.iouAmount, props.iouCurrencyCode), + : translate(hasMultipleParticipants ? 'iou.splitAmount' : 'iou.requestAmount', { + amount: CurrencyUtils.convertToDisplayString(iouAmount, iouCurrencyCode), }); return [ { text: text[0].toUpperCase() + text.slice(1), - value: props.hasMultipleParticipants ? CONST.IOU.MONEY_REQUEST_TYPE.SPLIT : CONST.IOU.MONEY_REQUEST_TYPE.REQUEST, + value: hasMultipleParticipants ? CONST.IOU.MONEY_REQUEST_TYPE.SPLIT : CONST.IOU.MONEY_REQUEST_TYPE.REQUEST, }, ]; - }, [props.hasMultipleParticipants, props.iouAmount, props.receiptPath, props.iouCurrencyCode, translate]); + }, [hasMultipleParticipants, iouAmount, receiptPath, iouCurrencyCode, translate]); - const selectedParticipants = useMemo(() => _.filter(props.selectedParticipants, (participant) => participant.selected), [props.selectedParticipants]); - const payeePersonalDetails = useMemo(() => props.payeePersonalDetails || props.currentUserPersonalDetails, [props.payeePersonalDetails, props.currentUserPersonalDetails]); - const canModifyParticipants = !props.isReadOnly && props.canModifyParticipants && props.hasMultipleParticipants; + const memoSelectedParticipants = useMemo(() => _.filter(selectedParticipants, (participant) => participant.selected), [selectedParticipants]); + const memoPayeePersonalDetails = useMemo(() => payeePersonalDetails || currentUserPersonalDetails, [payeePersonalDetails, currentUserPersonalDetails]); const shouldDisablePaidBySection = canModifyParticipants; const optionSelectorSections = useMemo(() => { const sections = []; - const unselectedParticipants = _.filter(props.selectedParticipants, (participant) => !participant.selected); - if (props.hasMultipleParticipants) { - const formattedSelectedParticipants = getParticipantsWithAmount(selectedParticipants); + const unselectedParticipants = _.filter(memoSelectedParticipants, (participant) => !participant.selected); + if (hasMultipleParticipants) { + const formattedSelectedParticipants = getParticipantsWithAmount(memoSelectedParticipants); const formattedParticipantsList = _.union(formattedSelectedParticipants, unselectedParticipants); - const myIOUAmount = IOUUtils.calculateAmount(selectedParticipants.length, props.iouAmount, true); + const myIOUAmount = IOUUtils.calculateAmount(memoSelectedParticipants.length, iouAmount, true); const formattedPayeeOption = OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail( - payeePersonalDetails, - CurrencyUtils.convertToDisplayString(myIOUAmount, props.iouCurrencyCode), + memoPayeePersonalDetails, + CurrencyUtils.convertToDisplayString(myIOUAmount, iouCurrencyCode), ); sections.push( @@ -171,30 +187,20 @@ function MoneyRequestConfirmationList(props) { } else { sections.push({ title: translate('common.to'), - data: props.selectedParticipants, + data: memoSelectedParticipants, shouldShow: true, indexOffset: 0, }); } return sections; - }, [ - props.selectedParticipants, - props.hasMultipleParticipants, - props.iouAmount, - props.iouCurrencyCode, - getParticipantsWithAmount, - selectedParticipants, - payeePersonalDetails, - translate, - shouldDisablePaidBySection, - ]); + }, [memoSelectedParticipants, hasMultipleParticipants, iouAmount, iouCurrencyCode, getParticipantsWithAmount, memoPayeePersonalDetails, translate, shouldDisablePaidBySection]); const selectedOptions = useMemo(() => { - if (!props.hasMultipleParticipants) { + if (!hasMultipleParticipants) { return []; } - return [...selectedParticipants, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(payeePersonalDetails)]; - }, [selectedParticipants, props.hasMultipleParticipants, payeePersonalDetails]); + return [...memoSelectedParticipants, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(payeePersonalDetails)]; + }, [memoSelectedParticipants, hasMultipleParticipants, memoPayeePersonalDetails]); /** * @param {Object} option @@ -202,12 +208,12 @@ function MoneyRequestConfirmationList(props) { const selectParticipant = useCallback( (option) => { // Return early if selected option is currently logged in user. - if (option.accountID === props.session.accountID) { + if (option.accountID === session.accountID) { return; } onSelectParticipant(option); }, - [props.session.accountID, onSelectParticipant], + [session.accountID, onSelectParticipant], ); /** @@ -228,11 +234,11 @@ function MoneyRequestConfirmationList(props) { (paymentMethod) => { setDidConfirm(true); - if (_.isEmpty(selectedParticipants)) { + if (_.isEmpty(memoSelectedParticipants)) { return; } - if (props.iouType === CONST.IOU.MONEY_REQUEST_TYPE.SEND) { + if (iouType === CONST.IOU.MONEY_REQUEST_TYPE.SEND) { if (!paymentMethod) { return; } @@ -240,22 +246,22 @@ function MoneyRequestConfirmationList(props) { Log.info(`[IOU] Sending money via: ${paymentMethod}`); onSendMoney(paymentMethod); } else { - onConfirm(selectedParticipants); + onConfirm(memoSelectedParticipants); } }, - [selectedParticipants, onSendMoney, onConfirm, props.iouType], + [memoSelectedParticipants, onSendMoney, onConfirm, iouType], ); - const formattedAmount = CurrencyUtils.convertToDisplayString(props.iouAmount, props.iouCurrencyCode); + const formattedAmount = CurrencyUtils.convertToDisplayString(iouAmount, iouCurrencyCode); const footerContent = useMemo(() => { - if (props.isReadOnly) { + if (isReadOnly) { return; } - const shouldShowSettlementButton = props.iouType === CONST.IOU.MONEY_REQUEST_TYPE.SEND; - const shouldDisableButton = selectedParticipants.length === 0; - const recipient = props.selectedParticipants[0] || {}; + const shouldShowSettlementButton = iouType === CONST.IOU.MONEY_REQUEST_TYPE.SEND; + const shouldDisableButton = memoSelectedParticipants.length === 0; + const recipient = memoSelectedParticipants[0] || {}; return shouldShowSettlementButton ? ( ) : ( @@ -276,7 +282,7 @@ function MoneyRequestConfirmationList(props) { options={splitOrRequestOptions} /> ); - }, [confirm, props.selectedParticipants, props.bankAccountRoute, props.iouCurrencyCode, props.iouType, props.isReadOnly, props.policyID, selectedParticipants, splitOrRequestOptions]); + }, [confirm, memoSelectedParticipants, bankAccountRoute, iouCurrencyCode, iouType, isReadOnly, policyID, splitOrRequestOptions]); /** * Grab the appropriate image URI based on file type @@ -285,9 +291,9 @@ function MoneyRequestConfirmationList(props) { * @param {String} receiptSource * @returns {*} */ - const getImageURI = (receiptPath, receiptSource) => { + const getImageURI = () => { const {fileExtension} = FileUtils.splitExtensionFromFileName(receiptSource); - const isReceiptImage = Str.isImage(props.receiptSource); + const isReceiptImage = Str.isImage(receiptSource); if (isReceiptImage) { return receiptPath; @@ -325,30 +331,30 @@ function MoneyRequestConfirmationList(props) { optionHoveredStyle={canModifyParticipants ? styles.hoveredComponentBG : {}} footerContent={footerContent} > - {props.receiptPath && ( + {receiptPath && ( )} - {!props.receiptPath && ( + {!receiptPath && ( Navigation.navigate(ROUTES.getMoneyRequestAmountRoute(props.iouType, props.reportID))} + onPress={() => Navigation.navigate(ROUTES.getMoneyRequestAmountRoute(iouType, reportID))} style={[styles.moneyRequestMenuItem, styles.mt2]} titleStyle={styles.moneyRequestConfirmationAmount} - disabled={didConfirm || props.isReadOnly} + disabled={didConfirm || isReadOnly} /> )} Navigation.navigate(ROUTES.getMoneyRequestDescriptionRoute(props.iouType, props.reportID))} + onPress={() => Navigation.navigate(ROUTES.getMoneyRequestDescriptionRoute(iouType, reportID))} style={[styles.moneyRequestMenuItem, styles.mb2]} - disabled={didConfirm || props.isReadOnly} + disabled={didConfirm || isReadOnly} /> ); diff --git a/src/components/TabSelector.js b/src/components/TabSelector.js index e836edb74489..5b1697d55eba 100644 --- a/src/components/TabSelector.js +++ b/src/components/TabSelector.js @@ -18,9 +18,9 @@ const defaultProps = { tabSelected: CONST.TAB.TAB_MANUAL, }; -function TabSelector(props) { +function TabSelector({tabSelected}) { const {translate} = useLocalize(); - const selectedTab = props.tabSelected ? props.tabSelected : CONST.TAB.TAB_MANUAL; + const selectedTab = tabSelected || CONST.TAB.TAB_MANUAL; return ( - {props.title} + {title} ); diff --git a/src/libs/AttachmentUtils.js b/src/libs/AttachmentUtils.js index 49f1e6d21cec..f7a78233745f 100644 --- a/src/libs/AttachmentUtils.js +++ b/src/libs/AttachmentUtils.js @@ -4,20 +4,20 @@ import * as FileUtils from './fileDownload/FileUtils'; import CONST from '../CONST'; import Receipt from './actions/Receipt'; -const isValidFile = (_file, props) => { +const isValidFile = (translate, _file) => { const {fileExtension} = FileUtils.splitExtensionFromFileName(lodashGet(_file, 'name', '')); if (_.contains(CONST.API_ATTACHMENT_VALIDATIONS.UNALLOWED_EXTENSIONS, fileExtension.toLowerCase())) { - Receipt.onUploadReceiptError(true, props.translate('attachmentPicker.wrongFileType'), props.translate('attachmentPicker.notAllowedExtension')); + Receipt.onUploadReceiptError(true, translate('attachmentPicker.wrongFileType'), translate('attachmentPicker.notAllowedExtension')); return false; } if (lodashGet(_file, 'size', 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { - Receipt.onUploadReceiptError(true, props.translate('attachmentPicker.attachmentTooLarge'), props.translate('attachmentPicker.sizeExceeded')); + Receipt.onUploadReceiptError(true, translate('attachmentPicker.attachmentTooLarge'), translate('attachmentPicker.sizeExceeded')); return false; } if (lodashGet(_file, 'size', 0) < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { - Receipt.onUploadReceiptError(true, props.translate('attachmentPicker.attachmentTooSmall'), props.translate('attachmentPicker.sizeNotMet')); + Receipt.onUploadReceiptError(true, translate('attachmentPicker.attachmentTooSmall'), translate('attachmentPicker.sizeNotMet')); return false; } diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index bea7684b7277..ba45ecc7dc06 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -76,12 +76,12 @@ const defaultProps = { ...withCurrentUserPersonalDetailsDefaultProps, }; -function MoneyRequestSelectorPage(props) { - const iouType = useRef(lodashGet(props.route, 'params.iouType', '')); +function MoneyRequestSelectorPage({route, report, tabSelected, iou, currentUserPersonalDetails}) { + const iouType = useRef(lodashGet(route, 'params.iouType', '')); const {translate} = useLocalize(); - const isEditing = useRef(lodashGet(props.route, 'path', '').includes('amount')); - const reportID = useRef(lodashGet(props.route, 'params.reportID', '')); + const isEditing = useRef(lodashGet(route, 'path', '').includes('amount')); + const reportID = useRef(lodashGet(route, 'params.reportID', '')); const title = { [CONST.IOU.MONEY_REQUEST_TYPE.REQUEST]: translate('iou.requestMoney'), @@ -94,7 +94,7 @@ function MoneyRequestSelectorPage(props) { Navigation.goBack(isEditing.current ? ROUTES.getMoneyRequestConfirmationRoute(iouType.current, reportID.current) : null); }; - const selectedTab = props.tabSelected ? props.tabSelected : CONST.TAB.TAB_MANUAL; + const selectedTab = tabSelected || CONST.TAB.TAB_MANUAL; const [isDraggingOver, setIsDraggingOver] = useState(false); return ( @@ -115,13 +115,13 @@ function MoneyRequestSelectorPage(props) { setIsDraggingOver(false); const file = lodashGet(e, ['dataTransfer', 'files', 0]); - if (!AttachmentUtils.isValidFile(file, props)) { + if (!AttachmentUtils.isValidFile(translate, file)) { return; } const filePath = URL.createObjectURL(file); IOU.setMoneyRequestReceipt(filePath, file.name); - NavigateToNextIOUPage(props.iou, iouType, reportID, props.report, props.currentUserPersonalDetails); + NavigateToNextIOUPage(iou, iouType, reportID, report, currentUserPersonalDetails); }} > {selectedTab === CONST.TAB.TAB_MANUAL ? ( ) : ( )} diff --git a/src/pages/iou/ReceiptDropUI.js b/src/pages/iou/ReceiptDropUI.js index 38591df01b06..43104e22ea19 100644 --- a/src/pages/iou/ReceiptDropUI.js +++ b/src/pages/iou/ReceiptDropUI.js @@ -15,7 +15,7 @@ const defaultProps = { receiptImageTopPosition: 0, }; -function ReceiptDropUI(props) { +function ReceiptDropUI({receiptImageTopPosition}) { const {translate} = useLocalize(); return ( - + - + {translate('receipt.dropTitle')} {translate('receipt.dropMessage')} diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index 196ffc0be51d..4eac72b502b9 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -88,12 +88,12 @@ const defaultProps = { isDraggingOver: false, }; -function ReceiptSelector(props) { - const iouType = useRef(lodashGet(props.route, 'params.iouType', '')); - const reportID = useRef(lodashGet(props.route, 'params.reportID', '')); - const isAttachmentInvalid = lodashGet(props.receiptModal, 'isAttachmentInvalid', false); - const attachmentInvalidReasonTitle = lodashGet(props.receiptModal, 'attachmentInvalidReasonTitle', ''); - const attachmentInvalidReason = lodashGet(props.receiptModal, 'attachmentInvalidReason', ''); +function ReceiptSelector({route, currentUserPersonalDetails, isDraggingOver, iou, report, receiptModal}) { + const iouType = useRef(lodashGet(route, 'params.iouType', '')); + const reportID = useRef(lodashGet(route, 'params.reportID', '')); + const isAttachmentInvalid = lodashGet(receiptModal, 'isAttachmentInvalid', false); + const attachmentInvalidReasonTitle = lodashGet(receiptModal, 'attachmentInvalidReasonTitle', ''); + const attachmentInvalidReason = lodashGet(receiptModal, 'attachmentInvalidReason', ''); const [receiptImageTopPosition, setReceiptImageTopPosition] = useState(0); const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); @@ -157,13 +157,13 @@ function ReceiptSelector(props) { onPress={() => { openPicker({ onPicked: (file) => { - if (!AttachmentUtils.isValidFile(file, props)) { + if (!AttachmentUtils.isValidFile(translate, file)) { return; } const filePath = URL.createObjectURL(file); IOU.setMoneyRequestReceipt(filePath, file.name); - NavigateToNextIOUPage(props.iou, iouType, reportID, props.report, props.currentUserPersonalDetails); + NavigateToNextIOUPage(iou, iouType, reportID, report, currentUserPersonalDetails); }, }); }} @@ -176,8 +176,8 @@ function ReceiptSelector(props) { return ( - {!props.isDraggingOver ? defaultView() : null} - {props.isDraggingOver && } + {!isDraggingOver ? defaultView() : null} + {isDraggingOver && } { IOU.setMoneyRequestReceipt(`file://${photo.path}`, photo.path); - NavigateToNextIOUPage(props.iou, iouType, reportID, props.report, props.currentUserPersonalDetails); + NavigateToNextIOUPage(iou, iouType, reportID, report, currentUserPersonalDetails); }) .catch(() => { showCameraAlert(); @@ -267,7 +267,7 @@ function ReceiptSelector(props) { onPress={() => { showImagePicker(launchImageLibrary).then((receiptImage) => { IOU.setMoneyRequestReceipt(receiptImage[0].uri, receiptImage[0].fileName); - NavigateToNextIOUPage(props.iou, iouType, reportID, props.report, props.currentUserPersonalDetails); + NavigateToNextIOUPage(iou, iouType, reportID, report, currentUserPersonalDetails); }); }} > From d937bac48fb690bd34f2f7ba77499e19ddcae2d7 Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Fri, 21 Jul 2023 17:23:52 -0400 Subject: [PATCH 056/223] Fix lint --- src/components/MoneyRequestConfirmationList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 2187947054fe..6976eb53f049 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -199,7 +199,7 @@ function MoneyRequestConfirmationList({ if (!hasMultipleParticipants) { return []; } - return [...memoSelectedParticipants, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(payeePersonalDetails)]; + return [...memoSelectedParticipants, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(memoPayeePersonalDetails)]; }, [memoSelectedParticipants, hasMultipleParticipants, memoPayeePersonalDetails]); /** From e48d6149a3222f1e1ba9e46b0d4c29ba383ce611 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 21 Jul 2023 14:47:11 -0700 Subject: [PATCH 057/223] conditionally render tab content in returned jsx --- src/pages/iou/MoneyRequestSelectorPage.js | 59 +++++++++-------------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index 8d8263af1887..e141501f1afe 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -98,40 +98,6 @@ function MoneyRequestSelectorPage({route, report, tabSelected, iou, currentUserP const selectedTab = tabSelected || CONST.TAB.TAB_MANUAL; const [isDraggingOver, setIsDraggingOver] = useState(false); - const renderTabContent = () => { - switch (selectedTab) { - case CONST.TAB.TAB_MANUAL: - return ( - - ); - case CONST.TAB.TAB_SCAN: - return ( - - ); - case CONST.TAB.TAB_DISTANCE: - return ( - - ); - default: - return null; - } - }; - return ( @@ -168,7 +134,30 @@ function MoneyRequestSelectorPage({route, report, tabSelected, iou, currentUserP onBackButtonPress={navigateBack} /> - {renderTabContent()} + {selectedTab === CONST.TAB.TAB_MANUAL && ( + + )} + {selectedTab === CONST.TAB.TAB_SCAN && ( + + )} + {selectedTab === CONST.TAB.TAB_DISTANCE && ( + + )} From 7947230f34523f4094184c098ee6c332d9dcb5e6 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 21 Jul 2023 14:50:03 -0700 Subject: [PATCH 058/223] Add JSDoc for convention --- src/libs/Permissions.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/Permissions.js b/src/libs/Permissions.js index e72a57822fc0..69ef8615429d 100644 --- a/src/libs/Permissions.js +++ b/src/libs/Permissions.js @@ -102,6 +102,10 @@ function canUseScanReceipts(betas) { return _.contains(betas, CONST.BETAS.SCAN_RECEIPTS) || canUseAllBetas(betas); } +/** + * @param {Array} betas + * @returns {Boolean} + */ function canUseDistanceRequests(betas) { return _.contains(betas, CONST.BETAS.DISTANCE_REQUESTS) || canUseAllBetas(betas); } From 704434adce81d880a9d9053dae5e827d1b90350f Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 21 Jul 2023 14:57:51 -0700 Subject: [PATCH 059/223] Clean up after merge --- src/components/TabSelector.js | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/components/TabSelector.js b/src/components/TabSelector.js index b96e75791c01..3caa44762d65 100644 --- a/src/components/TabSelector.js +++ b/src/components/TabSelector.js @@ -16,7 +16,6 @@ const propTypes = { /** List of betas available to current user */ betas: PropTypes.arrayOf(PropTypes.string), - ...withLocalizePropTypes, }; const defaultProps = { @@ -24,7 +23,7 @@ const defaultProps = { betas: [], }; -function TabSelector({tabSelected}) { +function TabSelector({tabSelected, betas}) { const {translate} = useLocalize(); const selectedTab = tabSelected || CONST.TAB.TAB_MANUAL; return ( @@ -45,9 +44,9 @@ function TabSelector({tabSelected}) { Tab.onTabPress(CONST.TAB.TAB_SCAN); }} /> - {Permissions.canUseDistanceRequests(props.betas) && ( + {Permissions.canUseDistanceRequests(betas) && ( { @@ -63,14 +62,11 @@ TabSelector.propTypes = propTypes; TabSelector.defaultProps = defaultProps; TabSelector.displayName = 'TabSelector'; -export default compose( - withLocalize, - withOnyx({ - tabSelected: { - key: ONYXKEYS.TAB_SELECTOR, - }, - betas: { - key: ONYXKEYS.BETAS, - }, - }), -)(TabSelector); +export default withOnyx({ + tabSelected: { + key: ONYXKEYS.TAB_SELECTOR, + }, + betas: { + key: ONYXKEYS.BETAS, + }, +})(TabSelector); From 2f34d2be9bce77da59b2e91f1a2bd25e0b85f214 Mon Sep 17 00:00:00 2001 From: Nikhil Vats Date: Sat, 22 Jul 2023 16:55:51 +0530 Subject: [PATCH 060/223] Fix error props being incorrectly accessed in components --- src/libs/ReportUtils.js | 20 +++++++++++++++---- src/pages/home/ReportScreen.js | 3 +-- src/pages/home/report/ReportFooter.js | 7 +------ src/pages/iou/IOUCurrencySelection.js | 7 +------ src/pages/iou/steps/MoneyRequestAmountPage.js | 4 ++-- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 2b532b59575f..d5a5c5757506 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2541,13 +2541,24 @@ function getParentReport(report) { return lodashGet(allReports, `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, {}); } +/** + * Return the errors we have when creating a chat or a workspace room + * @param {Object} report + * @returns {Object} errors + */ +function getAddWorkspaceRoomOrChatReportErrors(report) { + // We are either adding a workspace room, or we're creating a chat, it isn't possible for both of these to have errors for the same report at the same time, so + // simply looking up the first truthy value will get the relevant property if it's set. + return lodashGet(report, 'errorFields.addWorkspaceRoom') || lodashGet(report, 'errorFields.createChat'); +} + /** * Return true if the composer should be hidden * @param {Object} report - * @param {Object} reportErrors * @returns {Boolean} */ -function shouldHideComposer(report, reportErrors) { +function shouldHideComposer(report) { + const reportErrors = getAddWorkspaceRoomOrChatReportErrors(report); return isArchivedRoom(report) || !_.isEmpty(reportErrors) || !isAllowedToComment(report) || isAnonymousUser; } @@ -2563,7 +2574,7 @@ function getOriginalReportID(reportID, reportAction) { } /** - * Return the pendingAction and the errors when we have creating a chat or a workspace room offline + * Return the pendingAction and the errors we have when creating a chat or a workspace room offline * @param {Object} report * @returns {Object} pending action , errors */ @@ -2571,7 +2582,7 @@ function getReportOfflinePendingActionAndErrors(report) { // We are either adding a workspace room, or we're creating a chat, it isn't possible for both of these to be pending, or to have errors for the same report at the same time, so // simply looking up the first truthy value for each case will get the relevant property if it's set. const addWorkspaceRoomOrChatPendingAction = lodashGet(report, 'pendingFields.addWorkspaceRoom') || lodashGet(report, 'pendingFields.createChat'); - const addWorkspaceRoomOrChatErrors = lodashGet(report, 'errorFields.addWorkspaceRoom') || lodashGet(report, 'errorFields.createChat'); + const addWorkspaceRoomOrChatErrors = getAddWorkspaceRoomOrChatReportErrors(report); return {addWorkspaceRoomOrChatPendingAction, addWorkspaceRoomOrChatErrors}; } @@ -2718,6 +2729,7 @@ export { shouldHideComposer, getOriginalReportID, canAccessReport, + getAddWorkspaceRoomOrChatReportErrors, getReportOfflinePendingActionAndErrors, getPolicy, shouldDisableSettings, diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 5d825a528bdf..565d88f02c2b 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -161,7 +161,7 @@ class ReportScreen extends React.Component { componentDidUpdate(prevProps) { // If composer should be hidden, hide emoji picker as well - if (ReportUtils.shouldHideComposer(this.props.report, this.props.errors)) { + if (ReportUtils.shouldHideComposer(this.props.report)) { EmojiPickerAction.hideEmojiPicker(true); } const onyxReportID = this.props.report.reportID; @@ -387,7 +387,6 @@ class ReportScreen extends React.Component { {this.isReportReadyForDisplay() && ( <> {}, - errors: {}, pendingAction: null, shouldShowComposeInput: true, shouldDisableCompose: false, @@ -66,7 +61,7 @@ function ReportFooter(props) { const isAnonymousUser = Session.isAnonymousUser(); const isSmallSizeLayout = props.windowWidth - (props.isSmallScreenWidth ? 0 : variables.sideBarWidth) < variables.anonymousReportFooterBreakpoint; - const hideComposer = ReportUtils.shouldHideComposer(props.report, props.errors); + const hideComposer = ReportUtils.shouldHideComposer(props.report); return ( <> diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index 44a7fba5d487..6ab2a043a272 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -48,16 +48,11 @@ const propTypes = { /** The report on which the request is initiated on */ report: reportPropTypes, - /** Any errors associated with an attempt to create a chat */ - // eslint-disable-next-line react/forbid-prop-types - errors: PropTypes.object, - ...withLocalizePropTypes, }; const defaultProps = { report: {}, - errors: {}, currencyList: {}, iou: { currency: CONST.CURRENCY.USD, @@ -71,7 +66,7 @@ function IOUCurrencySelection(props) { const iouType = lodashGet(props.route, 'params.iouType', CONST.IOU.MONEY_REQUEST_TYPE.REQUEST); const reportID = lodashGet(props.route, 'params.reportID', ''); - const shouldDismissModal = ReportUtils.shouldHideComposer(props.report, props.errors); + const shouldDismissModal = ReportUtils.shouldHideComposer(props.report); useEffect(() => { if (!shouldDismissModal) { diff --git a/src/pages/iou/steps/MoneyRequestAmountPage.js b/src/pages/iou/steps/MoneyRequestAmountPage.js index e25f7acb0553..fe2a107a23e8 100755 --- a/src/pages/iou/steps/MoneyRequestAmountPage.js +++ b/src/pages/iou/steps/MoneyRequestAmountPage.js @@ -210,11 +210,11 @@ function MoneyRequestAmountPage(props) { * Check and dismiss modal */ useEffect(() => { - if (!ReportUtils.shouldHideComposer(props.report, props.errors)) { + if (!ReportUtils.shouldHideComposer(props.report)) { return; } Navigation.dismissModal(reportID.current); - }, [props.errors, props.report]); + }, [props.report]); /** * Focus text input From ef921f474dc745933fc63bfd27b5d80281ee6888 Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Sun, 23 Jul 2023 22:11:44 +0530 Subject: [PATCH 061/223] Add migration file: CheckForPreviousReportActionID --- .../CheckForPreviousReportActionID.js | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/libs/migrations/CheckForPreviousReportActionID.js diff --git a/src/libs/migrations/CheckForPreviousReportActionID.js b/src/libs/migrations/CheckForPreviousReportActionID.js new file mode 100644 index 000000000000..b3a6f639343f --- /dev/null +++ b/src/libs/migrations/CheckForPreviousReportActionID.js @@ -0,0 +1,54 @@ +import _ from 'underscore'; +import Onyx from 'react-native-onyx'; +import Log from '../Log'; +import ONYXKEYS from '../../ONYXKEYS'; + +/** + * @returns {Promise} + */ +function getReportActionsFromOnyx() { + return new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (allReportActions) => { + Onyx.disconnect(connectionID); + return resolve(allReportActions); + }, + }); + }); +} + +/** + * Migrate Onyx data for reportActions. If the first reportAction of a reportActionsForReport + * does not contain a 'previousReportActionID', all reportActions for all reports are removed from Onyx. + * + * @returns {Promise} + */ +export default function () { + return getReportActionsFromOnyx().then((allReportActions) => { + if (_.isEmpty(allReportActions)) { + Log.info(`[Migrate Onyx] Skipped migration CheckForPreviousReportActionID because there were no reportActions`); + return; + } + + const firstReportActionID = _.keys(allReportActions)[0]; + const firstReportAction = allReportActions[firstReportActionID]; + const firstValueOfReportAction = _.values(firstReportAction)[0]; + + if (_.has(firstValueOfReportAction, 'previousReportActionID')) { + Log.info(`[Migrate Onyx] CheckForPreviousReportActionID Migration: previousReportActionID found. Migration complete`); + return; + } + + // If previousReportActionID not found: + Log.info(`[Migrate Onyx] CheckForPreviousReportActionID Migration: removing all reportActions because previousReportActionID not found in the first reportAction`); + + const onyxData = {}; + _.each(allReportActions, (reportAction, onyxKey) => { + onyxData[onyxKey] = {}; + }); + + return Onyx.multiSet(onyxData); + }); +} From f383720773432bfa373a647935f250d69a948836 Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Sun, 23 Jul 2023 23:09:27 +0530 Subject: [PATCH 062/223] Add tests for CheckForPreviousReportActionID --- tests/unit/MigrationTest.js | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index af7f39ddf828..548c50193ccd 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -639,4 +639,72 @@ describe('Migrations', () => { }); })); }); + + describe('CheckPreviousReportActionID', () => { + // Note: this test has to come before the others in this suite because Onyx.clear leaves traces and keys with null values aren't cleared out between tests + it("Should work even if there's no reportAction data in Onyx", () => + CheckForPreviousReportActionID().then(() => + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration CheckForPreviousReportActionID because there were no reportActions'), + )); + + it('Should remove all report actions given that a previousReportActionID does not exist', () => + Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { + 1: { + actorEmail: 'sample_email@example.com', + }, + 2: { + actorEmail: 'another_sample_email@example.com', + }, + }, + }) + .then(CheckForPreviousReportActionID) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith( + '[Migrate Onyx] CheckForPreviousReportActionID Migration: removing all reportActions because previousReportActionID not found in the first reportAction', + ); + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (allReportActions) => { + Onyx.disconnect(connectionID); + const expectedReportAction = {}; + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); + }, + }); + })); + + it('Should not remove any report action given that previousReportActionID exists in every action', () => { + Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { + 1: { + previousReportActionID: 0, + }, + 2: { + previousReportActionID: 1, + }, + }, + }) + .then(CheckForPreviousReportActionID) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] CheckForPreviousReportActionID Migration: previousReportActionID found. Migration complete'); + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (allReportActions) => { + Onyx.disconnect(connectionID); + const expectedReportAction = { + 1: { + previousReportActionID: 0, + }, + 2: { + previousReportActionID: 1, + }, + }; + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); + }, + }); + }); + }); + }); }); From 9bf6e7a389188a3504fe042faa97555e09bd2920 Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Mon, 24 Jul 2023 03:14:44 +0530 Subject: [PATCH 063/223] Import new file in MigrationTest.js --- tests/unit/MigrationTest.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index 548c50193ccd..091c5a286a6b 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -8,6 +8,7 @@ import AddLastVisibleActionCreated from '../../src/libs/migrations/AddLastVisibl import MoveToIndexedDB from '../../src/libs/migrations/MoveToIndexedDB'; import KeyReportActionsByReportActionID from '../../src/libs/migrations/KeyReportActionsByReportActionID'; import PersonalDetailsByAccountID from '../../src/libs/migrations/PersonalDetailsByAccountID'; +import CheckForPreviousReportActionID from '../../src/libs/migrations/CheckForPreviousReportActionID'; import ONYXKEYS from '../../src/ONYXKEYS'; jest.mock('../../src/libs/getPlatform'); From 3daf57d1a6539eeacfd12c9fe7de2da262fb07b4 Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Mon, 24 Jul 2023 04:31:10 +0530 Subject: [PATCH 064/223] Resolve prettier diff --- tests/unit/MigrationTest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index 091c5a286a6b..a92d47104cb2 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -640,7 +640,7 @@ describe('Migrations', () => { }); })); }); - + describe('CheckPreviousReportActionID', () => { // Note: this test has to come before the others in this suite because Onyx.clear leaves traces and keys with null values aren't cleared out between tests it("Should work even if there's no reportAction data in Onyx", () => From 58e6de5e9ffad782eb1464e82a237f7342338a02 Mon Sep 17 00:00:00 2001 From: jczekalski Date: Mon, 24 Jul 2023 10:42:15 +0200 Subject: [PATCH 065/223] replace HOCs with hooks, props destructuring --- src/components/Tooltip/index.js | 36 ++++++++++++---------- src/components/Tooltip/tooltipPropTypes.js | 4 --- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/components/Tooltip/index.js b/src/components/Tooltip/index.js index 004d27ef969c..398df07649cf 100644 --- a/src/components/Tooltip/index.js +++ b/src/components/Tooltip/index.js @@ -4,13 +4,12 @@ import {Animated} from 'react-native'; import {BoundsObserver} from '@react-ng/bounds-observer'; import TooltipRenderedOnPageBody from './TooltipRenderedOnPageBody'; import Hoverable from '../Hoverable'; -import withWindowDimensions from '../withWindowDimensions'; import * as tooltipPropTypes from './tooltipPropTypes'; import TooltipSense from './TooltipSense'; import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; -import compose from '../../libs/compose'; -import withLocalize from '../withLocalize'; import usePrevious from '../../hooks/usePrevious'; +import useLocalize from '../../hooks/useLocalize'; +import useWindowDimensions from '../../hooks/useWindowDimensions'; const hasHoverSupport = DeviceCapabilities.hasHoverSupport(); @@ -21,6 +20,11 @@ const hasHoverSupport = DeviceCapabilities.hasHoverSupport(); * @returns {ReactNodeLike} */ function Tooltip(props) { + const {children, numberOfLines, maxWidth, text, renderTooltipContent, renderTooltipContentKey} = props; + + const {preferredLocale} = useLocalize(); + const {windowWidth} = useWindowDimensions(); + // Is tooltip already rendered on the page's body? happens once. const [isRendered, setIsRendered] = useState(false); // Is the tooltip currently visible? @@ -37,7 +41,7 @@ function Tooltip(props) { const isTooltipSenseInitiator = useRef(false); const animation = useRef(new Animated.Value(0)); const isAnimationCanceled = useRef(false); - const prevText = usePrevious(props.text); + const prevText = usePrevious(text); /** * Display the tooltip in an animation. @@ -72,11 +76,11 @@ function Tooltip(props) { useEffect(() => { // if the tooltip text changed before the initial animation was finished, then the tooltip won't be shown // we need to show the tooltip again - if (isVisible && isAnimationCanceled.current && props.text && prevText !== props.text) { + if (isVisible && isAnimationCanceled.current && text && prevText !== text) { isAnimationCanceled.current = false; showTooltip(); } - }, [isVisible, props.text, prevText, showTooltip]); + }, [isVisible, text, prevText, showTooltip]); /** * Update the tooltip bounding rectangle @@ -118,8 +122,8 @@ function Tooltip(props) { // Skip the tooltip and return the children if the text is empty, // we don't have a render function or the device does not support hovering - if ((_.isEmpty(props.text) && props.renderTooltipContent == null) || !hasHoverSupport) { - return props.children; + if ((_.isEmpty(text) && renderTooltipContent == null) || !hasHoverSupport) { + return children; } return ( @@ -127,20 +131,20 @@ function Tooltip(props) { {isRendered && ( )} - {props.children} + {children} @@ -160,4 +164,4 @@ function Tooltip(props) { Tooltip.propTypes = tooltipPropTypes.propTypes; Tooltip.defaultProps = tooltipPropTypes.defaultProps; -export default compose(withWindowDimensions, withLocalize)(memo(Tooltip)); +export default memo(Tooltip); diff --git a/src/components/Tooltip/tooltipPropTypes.js b/src/components/Tooltip/tooltipPropTypes.js index f9a1847df242..af18c4cfa412 100644 --- a/src/components/Tooltip/tooltipPropTypes.js +++ b/src/components/Tooltip/tooltipPropTypes.js @@ -1,5 +1,4 @@ import PropTypes from 'prop-types'; -import {windowDimensionsPropTypes} from '../withWindowDimensions'; import variables from '../../styles/variables'; import CONST from '../../CONST'; @@ -13,9 +12,6 @@ const propTypes = { /** Children to wrap with Tooltip. */ children: PropTypes.node.isRequired, - /** Props inherited from withWindowDimensions */ - ...windowDimensionsPropTypes, - /** Any additional amount to manually adjust the horizontal position of the tooltip. A positive value shifts the tooltip to the right, and a negative value shifts it to the left. */ shiftHorizontal: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), From 51c08ad3c2697bec5526b61a3cc5437116207f21 Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Tue, 25 Jul 2023 09:22:39 +1200 Subject: [PATCH 066/223] fix potential undefined value error --- src/components/ThumbnailImage.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 2556fe9ae29c..99eb9da8f1da 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -41,7 +41,7 @@ const defaultProps = { function calculateThumbnailImageSize(width, height, windowHeight) { if (!width || !height) { - return {}; + return {thumbnailWidth: 200, thumbnailHeight: 200}; } // Width of the thumbnail works better as a constant than it does @@ -84,7 +84,7 @@ function ThumbnailImage(props) { ); return ( - + Date: Tue, 25 Jul 2023 09:54:28 +1200 Subject: [PATCH 067/223] make it prettier --- src/components/ThumbnailImage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 99eb9da8f1da..895e8baefb7f 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -98,4 +98,4 @@ function ThumbnailImage(props) { ThumbnailImage.propTypes = propTypes; ThumbnailImage.defaultProps = defaultProps; ThumbnailImage.displayName = 'ThumbnailImage'; -export default React.memo(ThumbnailImage); \ No newline at end of file +export default React.memo(ThumbnailImage); From 94a04acaea83976b596fe7e86e3db3e42887606a Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 25 Jul 2023 14:57:16 +0700 Subject: [PATCH 068/223] use props.report instead of task.report --- src/pages/tasks/TaskAssigneeSelectorModal.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.js b/src/pages/tasks/TaskAssigneeSelectorModal.js index 328d8837f724..e1687c08fb10 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.js +++ b/src/pages/tasks/TaskAssigneeSelectorModal.js @@ -164,13 +164,13 @@ function TaskAssigneeSelectorModal(props) { } // Check to see if we're editing a task and if so, update the assignee - if (props.route.params.reportID && props.task.report.reportID === props.route.params.reportID) { + if (props.route.params.reportID) { // There was an issue where sometimes a new assignee didn't have a DM thread // This would cause the app to crash, so we need to make sure we have a DM thread Task.setAssigneeValue(option.login, option.accountID, props.task.shareDestination, OptionsListUtils.isCurrentUser(option)); // Pass through the selected assignee - Task.editTaskAndNavigate(props.task.report, props.session.accountID, { + Task.editTaskAndNavigate(lodashGet(props.reports, `report_${props.route.params.reportID}`, {}), props.session.accountID, { assignee: option.login, assigneeAccountID: option.accountID, }); @@ -183,7 +183,7 @@ function TaskAssigneeSelectorModal(props) { <> (lodashGet(props.task.report, 'isExistingTaskReport') ? Navigation.dismissModal() : Navigation.goBack(ROUTES.NEW_TASK))} + onBackButtonPress={() => (lodashGet(lodashGet(props.reports, `report_${props.route.params.reportID}`, {}), 'isExistingTaskReport') ? Navigation.dismissModal() : Navigation.goBack(ROUTES.NEW_TASK))} /> Date: Tue, 25 Jul 2023 13:51:56 +0200 Subject: [PATCH 069/223] margin fix --- src/components/MultipleAvatars.js | 3 ++- src/styles/styles.js | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/MultipleAvatars.js b/src/components/MultipleAvatars.js index 3b20d99fa2ea..41d084c25d21 100644 --- a/src/components/MultipleAvatars.js +++ b/src/components/MultipleAvatars.js @@ -73,7 +73,8 @@ const defaultProps = { function MultipleAvatars(props) { const [avatarRows, setAvatarRows] = useState([props.icons]); - let avatarContainerStyles = props.size === CONST.AVATAR_SIZE.SMALL ? [styles.emptyAvatarSmall, styles.emptyAvatarMarginSmall] : [styles.emptyAvatar, styles.emptyAvatarMargin]; + const avatarMargin = props.isInReportAction ? styles.emptyAvatarMarginChat : styles.emptyAvatarMargin; + let avatarContainerStyles = props.size === CONST.AVATAR_SIZE.SMALL ? [styles.emptyAvatarSmall, styles.emptyAvatarMarginSmall] : [styles.emptyAvatar, avatarMargin]; const singleAvatarStyle = props.size === CONST.AVATAR_SIZE.SMALL ? styles.singleAvatarSmall : styles.singleAvatar; const secondAvatarStyles = [props.size === CONST.AVATAR_SIZE.SMALL ? styles.secondAvatarSmall : styles.secondAvatar, ...props.secondAvatarStyle]; const tooltipTexts = props.shouldShowTooltip ? _.pluck(props.icons, 'name') : ['']; diff --git a/src/styles/styles.js b/src/styles/styles.js index eeb39d38b381..d072fa8f63ae 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -1963,6 +1963,10 @@ const styles = { marginRight: variables.avatarChatSpacing, }, + emptyAvatarMarginChat: { + marginRight: variables.avatarChatSpacing - 12, + }, + emptyAvatarMarginSmall: { marginRight: variables.avatarChatSpacing - 4, }, From fc2d10df322669d04dae9be23b362f280e00a718 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 25 Jul 2023 15:04:37 +0200 Subject: [PATCH 070/223] fix width of list and pages --- src/components/PDFView/index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index 6181116391f3..be7075610d14 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -22,6 +22,11 @@ import Log from '../../libs/Log'; * when calculates the page width and height. */ const PAGE_BORDER = 9; +/** + * Pages should be more narrow than the container on large screens. The app should take this size into account + * when calculates the page width. + */ +const LARGE_SCREEN_SIDE_SPACING = 40; class PDFView extends Component { constructor(props) { @@ -118,7 +123,7 @@ class PDFView extends Component { */ calculatePageWidth() { const pdfContainerWidth = this.state.containerWidth; - const pageWidthOnLargeScreen = Math.min(pdfContainerWidth, variables.pdfPageMaxWidth); + const pageWidthOnLargeScreen = Math.min(pdfContainerWidth - LARGE_SCREEN_SIDE_SPACING * 2, variables.pdfPageMaxWidth); const pageWidth = this.props.isSmallScreenWidth ? this.state.containerWidth : pageWidthOnLargeScreen; return pageWidth + PAGE_BORDER * 2; @@ -226,7 +231,7 @@ class PDFView extends Component { {this.state.pageViewports.length > 0 && ( Date: Tue, 25 Jul 2023 15:13:19 +0200 Subject: [PATCH 071/223] fix border --- src/pages/home/report/ReportActionItem.js | 1 + src/pages/home/report/ReportActionItemSingle.js | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 1319cacded5a..6898393710c4 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -418,6 +418,7 @@ function ReportActionItem(props) { shouldShowSubscriptAvatar={props.shouldShowSubscriptAvatar} report={props.report} iouReport={props.iouReport} + isHovered={hovered} hasBeenFlagged={!_.contains([CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING], moderationDecision)} > {content} diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 677d07a055dc..b5307a7388d8 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -24,6 +24,8 @@ import * as UserUtils from '../../../libs/UserUtils'; import PressableWithoutFeedback from '../../../components/Pressable/PressableWithoutFeedback'; import UserDetailsTooltip from '../../../components/UserDetailsTooltip'; import MultipleAvatars from '../../../components/MultipleAvatars'; +import * as StyleUtils from '../../../styles/StyleUtils'; +import themeColors from '../../../styles/themes/default'; const propTypes = { /** All the data of the action */ @@ -54,6 +56,9 @@ const propTypes = { /** If the message has been flagged for moderation */ hasBeenFlagged: PropTypes.bool, + /** If the action is being hovered */ + isHovered: PropTypes.bool, + ...withLocalizePropTypes, }; @@ -65,6 +70,7 @@ const defaultProps = { hasBeenFlagged: false, report: undefined, iouReport: undefined, + isHovered: false, }; const showUserDetails = (accountID) => { @@ -142,6 +148,10 @@ function ReportActionItemSingle(props) { icons={[icon, secondaryAvatar]} isInReportAction shouldShowTooltip + secondAvatarStyle={[ + StyleUtils.getBackgroundAndBorderStyle(themeColors.appBG), + props.isHovered ? StyleUtils.getBackgroundAndBorderStyle(themeColors.highlightBG) : undefined, + ]} /> ); } From 1b8169e333961898968c957efa083327f23b99dd Mon Sep 17 00:00:00 2001 From: Aswin S Date: Wed, 26 Jul 2023 13:40:48 +0530 Subject: [PATCH 072/223] fix: handle path prefixes for split bill, request money and new chat --- .well-known/apple-app-site-association | 12 ++++++++++++ android/app/src/main/AndroidManifest.xml | 10 ++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.well-known/apple-app-site-association b/.well-known/apple-app-site-association index 8318b394d778..cf6bdf1dedef 100644 --- a/.well-known/apple-app-site-association +++ b/.well-known/apple-app-site-association @@ -47,6 +47,18 @@ { "/": "/concierge/*", "comment": "Concierge" + }, + { + "/": "/split/*", + "comment": "Split Bill" + }, + { + "/": "/request/*", + "comment": "Request Money" + }, + { + "/": "/new/*", + "comment": "New Chat" } ] } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b95d68c9c935..3597d6be924d 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -54,7 +54,7 @@ - + @@ -63,10 +63,13 @@ + + + - + @@ -75,6 +78,9 @@ + + + From a145a7bb0c3c5c15445a0c9f251e231ef30b2580 Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 26 Jul 2023 12:02:16 +0200 Subject: [PATCH 073/223] correct tooltip in workspace icon --- src/pages/home/report/ReportActionItemSingle.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index b5307a7388d8..cb81a272e37b 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -106,10 +106,11 @@ function ReportActionItemSingle(props) { // If this is a report preview, display names and avatars of both people involved let secondaryAvatar = {}; const displayAllActors = props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && props.iouReport; + const primaryDisplayName = displayName; if (displayAllActors) { const secondaryUserDetails = props.personalDetailsList[props.iouReport.ownerAccountID] || {}; const secondaryDisplayName = lodashGet(secondaryUserDetails, 'displayName', ''); - displayName = `${displayName} & ${secondaryDisplayName}`; + displayName = `${primaryDisplayName} & ${secondaryDisplayName}`; secondaryAvatar = { source: UserUtils.getAvatar(secondaryUserDetails.avatar, props.iouReport.ownerAccountID), type: CONST.ICON_TYPE_AVATAR, @@ -119,7 +120,7 @@ function ReportActionItemSingle(props) { } else if (!isWorkspaceActor) { secondaryAvatar = ReportUtils.getIcons(props.report, {})[props.report.isOwnPolicyExpenseChat ? 0 : 1]; } - const icon = {source: avatarSource, type: isWorkspaceActor ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR, name: displayName, id: actorAccountID}; + const icon = {source: avatarSource, type: isWorkspaceActor ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR, name: primaryDisplayName, id: actorAccountID}; // Since the display name for a report action message is delivered with the report history as an array of fragments // we'll need to take the displayName from personal details and have it be in the same format for now. Eventually, From b0109a41fe8dfef87bde35adc965afb2e9ff288e Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 26 Jul 2023 12:49:55 +0200 Subject: [PATCH 074/223] Display comment or money requests --- src/components/ReportActionItem/ReportPreview.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index a67f2a293460..a2e17784d613 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -25,6 +25,7 @@ import refPropTypes from '../refPropTypes'; import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; import themeColors from '../../styles/themes/default'; import reportPropTypes from '../../pages/reportPropTypes'; +import * as IOUUtils from "../../libs/IOUUtils"; const propTypes = { /** All the data of the action */ @@ -93,8 +94,12 @@ const defaultProps = { }; function ReportPreview(props) { + console.log(props.action); const managerID = props.iouReport.managerID || props.action.actorAccountID || 0; const isCurrentUserManager = managerID === lodashGet(props.session, 'accountID'); + const moneyRequestCount = lodashGet(props.action, 'childMoneyRequestCount', 0); + const moneyRequestComment = lodashGet(props.action, 'childLastMoneyRequestComment'); + const showComment = moneyRequestComment || moneyRequestCount > 1; let reportAmount = ReportUtils.getMoneyRequestTotal(props.iouReport); if (reportAmount) { reportAmount = CurrencyUtils.convertToDisplayString(reportAmount, props.iouReport.currency); @@ -146,6 +151,13 @@ function ReportPreview(props) { )} + {showComment && ( + + + {moneyRequestCount > 1 ? `${moneyRequestCount} requests` : moneyRequestComment} + + + )} {!_.isEmpty(props.iouReport) && isCurrentUserManager && !ReportUtils.isSettled(props.iouReportID) && !props.iouReport.isWaitingOnBankAccount && ( Date: Wed, 26 Jul 2023 12:56:39 +0200 Subject: [PATCH 075/223] build optimistic report preview correctly --- src/libs/ReportUtils.js | 4 +++- src/libs/actions/IOU.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index e4a6b801db57..a676fa06d0e0 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1752,7 +1752,7 @@ function buildOptimisticIOUReportAction(type, amount, currency, comment, partici }; } -function buildOptimisticReportPreview(chatReport, iouReport) { +function buildOptimisticReportPreview(chatReport, iouReport, comment) { const message = getReportPreviewMessage(iouReport); return { reportActionID: NumberUtils.rand64(), @@ -1773,6 +1773,8 @@ function buildOptimisticReportPreview(chatReport, iouReport) { created: DateUtils.getDBTime(), accountID: iouReport.managerID || 0, actorAccountID: iouReport.managerID || 0, + childMoneyRequestCount: 1, + moneyRequestComment: comment, }; } diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index dbea53c54c3a..05ede04667b4 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -389,7 +389,7 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part reportPreviewAction.message[0].html = message; reportPreviewAction.message[0].text = message; } else { - reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, iouReport); + reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, iouReport, comment); } // STEP 5: Build Onyx Data From 01382605313a21e51bf22335688139d039ac42cf Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 26 Jul 2023 16:03:40 +0200 Subject: [PATCH 076/223] Optimistically update info --- src/libs/ReportUtils.js | 2 +- src/libs/actions/IOU.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index a676fa06d0e0..66c3459521ad 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1774,7 +1774,7 @@ function buildOptimisticReportPreview(chatReport, iouReport, comment) { accountID: iouReport.managerID || 0, actorAccountID: iouReport.managerID || 0, childMoneyRequestCount: 1, - moneyRequestComment: comment, + childLastMoneyRequestComment: comment, }; } diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 05ede04667b4..d07b22a2f80b 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -388,6 +388,8 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part const message = ReportUtils.getReportPreviewMessage(iouReport, reportPreviewAction); reportPreviewAction.message[0].html = message; reportPreviewAction.message[0].text = message; + reportPreviewAction.childLastMoneyRequestComment = comment; + reportPreviewAction.childMoneyRequestCount += 1; } else { reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, iouReport, comment); } From 384e44186f20efe52aea027abdfb940ca8ce9c79 Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 26 Jul 2023 16:49:42 +0200 Subject: [PATCH 077/223] lint --- src/components/ReportActionItem/ReportPreview.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index a2e17784d613..079ea2b8b6e0 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -25,7 +25,6 @@ import refPropTypes from '../refPropTypes'; import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; import themeColors from '../../styles/themes/default'; import reportPropTypes from '../../pages/reportPropTypes'; -import * as IOUUtils from "../../libs/IOUUtils"; const propTypes = { /** All the data of the action */ @@ -94,7 +93,6 @@ const defaultProps = { }; function ReportPreview(props) { - console.log(props.action); const managerID = props.iouReport.managerID || props.action.actorAccountID || 0; const isCurrentUserManager = managerID === lodashGet(props.session, 'accountID'); const moneyRequestCount = lodashGet(props.action, 'childMoneyRequestCount', 0); From 9271651f9ed4d35a608b4035e5e8137c6bad2c0f Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 26 Jul 2023 21:05:14 +0200 Subject: [PATCH 078/223] update keybordDismissResponder as per review requests --- src/components/ScreenWrapper/index.js | 72 +++++++++++++-------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index ffc49eee1983..98ef7c8c7dc5 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -33,17 +33,10 @@ class ScreenWrapper extends React.Component { this.keyboardDissmissPanResponder = PanResponder.create({ onMoveShouldSetPanResponderCapture: (e, gestureState) => { const isHorizontalSwipe = Math.abs(gestureState.dx) > Math.abs(gestureState.dy); - if (isHorizontalSwipe && this.props.shouldDismissKeyboardBeforeClose) { - return true; - } - return false; - }, - onPanResponderGrant: () => { - if (!Keyboard.isVisible() || !Browser.isMobile()) { - return; - } - Keyboard.dismiss(); + const shouldDismissKeyboard = this.props.shouldDismissKeyboardBeforeClose && this.props.isKeyboardShown && Browser.isMobile(); + return isHorizontalSwipe && shouldDismissKeyboard; }, + onPanResponderGrant: Keyboard.dismiss, }); this.state = { @@ -112,36 +105,39 @@ class ScreenWrapper extends React.Component { } return ( - - + - - - {this.props.environment === CONST.ENVIRONMENT.DEV && } - {this.props.environment === CONST.ENVIRONMENT.DEV && } - { - // If props.children is a function, call it to provide the insets to the children. - _.isFunction(this.props.children) - ? this.props.children({ - insets, - safeAreaPaddingBottomStyle, - didScreenTransitionEnd: this.state.didScreenTransitionEnd, - }) - : this.props.children - } - {this.props.isSmallScreenWidth && this.props.shouldShowOfflineIndicator && } - - + + + {this.props.environment === CONST.ENVIRONMENT.DEV && } + {this.props.environment === CONST.ENVIRONMENT.DEV && } + { + // If props.children is a function, call it to provide the insets to the children. + _.isFunction(this.props.children) + ? this.props.children({ + insets, + safeAreaPaddingBottomStyle, + didScreenTransitionEnd: this.state.didScreenTransitionEnd, + }) + : this.props.children + } + {this.props.isSmallScreenWidth && this.props.shouldShowOfflineIndicator && } + + + ); }} From 26a9c12495e9d6349467cd18b60673b2aac842a2 Mon Sep 17 00:00:00 2001 From: Alberto Date: Thu, 27 Jul 2023 12:11:20 +0200 Subject: [PATCH 079/223] Internationalize --- src/components/ReportActionItem/ReportPreview.js | 2 +- src/languages/en.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 079ea2b8b6e0..6c00a028875c 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -152,7 +152,7 @@ function ReportPreview(props) { {showComment && ( - {moneyRequestCount > 1 ? `${moneyRequestCount} requests` : moneyRequestComment} + {moneyRequestCount > 1 ? props.translate('iou.requestCount', {count: moneyRequestCount}) : moneyRequestComment} )} diff --git a/src/languages/en.js b/src/languages/en.js index b7a130addf18..103c958a0b04 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -377,6 +377,7 @@ export default { pendingConversionMessage: "Total will update when you're back online", threadRequestReportName: ({formattedAmount, comment}) => `${formattedAmount} request${comment ? ` for ${comment}` : ''}`, threadSentMoneyReportName: ({formattedAmount, comment}) => `${formattedAmount} sent${comment ? ` for ${comment}` : ''}`, + requestCount: ({count}) => `${count} requests: `, error: { invalidSplit: 'Split amounts do not equal total amount', other: 'Unexpected error, please try again later', From 8940143c37009e84dc5fd3ac1b980eeb1a7ea83f Mon Sep 17 00:00:00 2001 From: Alberto Date: Thu, 27 Jul 2023 12:26:38 +0200 Subject: [PATCH 080/223] fix translation --- src/languages/en.js | 2 +- src/languages/es.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/languages/en.js b/src/languages/en.js index 103c958a0b04..53ad63fb7522 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -377,7 +377,7 @@ export default { pendingConversionMessage: "Total will update when you're back online", threadRequestReportName: ({formattedAmount, comment}) => `${formattedAmount} request${comment ? ` for ${comment}` : ''}`, threadSentMoneyReportName: ({formattedAmount, comment}) => `${formattedAmount} sent${comment ? ` for ${comment}` : ''}`, - requestCount: ({count}) => `${count} requests: `, + requestCount: ({count}) => `${count} requests`, error: { invalidSplit: 'Split amounts do not equal total amount', other: 'Unexpected error, please try again later', diff --git a/src/languages/es.js b/src/languages/es.js index 4006f559eb1f..38882d4e0aeb 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -376,6 +376,7 @@ export default { pendingConversionMessage: 'El total se actualizará cuando estés online', threadRequestReportName: ({formattedAmount, comment}) => `Solicitud de ${formattedAmount}${comment ? ` para ${comment}` : ''}`, threadSentMoneyReportName: ({formattedAmount, comment}) => `${formattedAmount} enviado${comment ? ` para ${comment}` : ''}`, + requestCount: ({count}) => `${count} solicitudes`, error: { invalidSplit: 'La suma de las partes no equivale al monto total', other: 'Error inesperado, por favor inténtalo más tarde', From cd7a8f684169a613e9f4a12044247ffe126cf887 Mon Sep 17 00:00:00 2001 From: Alberto Date: Thu, 27 Jul 2023 13:04:57 +0200 Subject: [PATCH 081/223] prettier --- src/components/ReportActionItem/ReportPreview.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 6c00a028875c..474073c47f21 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -152,7 +152,9 @@ function ReportPreview(props) { {showComment && ( - {moneyRequestCount > 1 ? props.translate('iou.requestCount', {count: moneyRequestCount}) : moneyRequestComment} + + {moneyRequestCount > 1 ? props.translate('iou.requestCount', {count: moneyRequestCount}) : moneyRequestComment} + )} From 15f1120f52d5c891ec6c28a87869ba828376018a Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Fri, 28 Jul 2023 14:09:16 +0200 Subject: [PATCH 082/223] move style to wrapper view --- src/components/ScreenWrapper/index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index 98ef7c8c7dc5..618b2b97d9f0 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -105,10 +105,13 @@ class ScreenWrapper extends React.Component { } return ( - // eslint-disable-next-line react/jsx-props-no-spreading - + From 65b14c426c0aa1577d654ca7f74c759969f7e669 Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Mon, 31 Jul 2023 18:04:06 +1200 Subject: [PATCH 083/223] adding new function argument to JSDocs description --- src/components/ThumbnailImage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index a67c86dbabe6..bd48cae5ffa1 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -37,6 +37,7 @@ const defaultProps = { * * @param {Number} width - Width of the original image. * @param {Number} height - Height of the original image. + * @param {Number} windowHeight - Height of the browser/device window. * @returns {Object} - Object containing thumbnails width and height. */ From 09872e578b314db6959aa3cda0f0863c83812756 Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Mon, 31 Jul 2023 18:14:03 +1200 Subject: [PATCH 084/223] adding new JSDocs parameter for new function argument --- src/components/ThumbnailImage.js | 41 ++++++++++---------------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index bd48cae5ffa1..348a575d9e30 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -1,12 +1,11 @@ import lodashClamp from 'lodash/clamp'; -import React, {PureComponent} from 'react'; -import {View, Dimensions} from 'react-native'; +import React, {useCallback, useState} from 'react'; +import {View} from 'react-native'; import PropTypes from 'prop-types'; import ImageWithSizeCalculation from './ImageWithSizeCalculation'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; -import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; -import * as DeviceCapabilities from '../libs/DeviceCapabilities'; +import useWindowDimensions from '../hooks/useWindowDimensions'; const propTypes = { /** Source URL for the preview image */ @@ -37,7 +36,7 @@ const defaultProps = { * * @param {Number} width - Width of the original image. * @param {Number} height - Height of the original image. - * @param {Number} windowHeight - Height of the browser/device window. + * @param {Number} windowHeight - Height of the device/browser window. * @returns {Object} - Object containing thumbnails width and height. */ @@ -46,27 +45,13 @@ function calculateThumbnailImageSize(width, height, windowHeight) { return {thumbnailWidth: 200, thumbnailHeight: 200}; } - /** - * Compute the thumbnails width and height given original image dimensions. - * - * @param {Number} width - Width of the original image. - * @param {Number} height - Height of the original image. - * @returns {Object} - Object containing thumbnails width and height. - */ - calculateThumbnailImageSize(width, height) { - if (!width || !height) { - return {}; - } - - // 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 - // Note: Clamp minimum width 40px to support touch device - let thumbnailScreenWidth = lodashClamp(width, 40, 250); - const imageHeight = height / (width / thumbnailScreenWidth); - // On mWeb, when soft keyboard opens, window height changes, making thumbnail height inconsistent. We use screen height instead. - const screenHeight = DeviceCapabilities.canUseTouchScreen() ? Dimensions.get('screen').height : this.props.windowHeight; - let thumbnailScreenHeight = lodashClamp(imageHeight, 40, screenHeight * 0.4); - const aspectRatio = height / width; + // 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 + // Note: Clamp minimum width 40px to support touch device + let thumbnailScreenWidth = lodashClamp(width, 40, 250); + const imageHeight = height / (width / thumbnailScreenWidth); + let thumbnailScreenHeight = lodashClamp(imageHeight, 40, windowHeight * 0.4); + const aspectRatio = height / width; // If thumbnail height is greater than its width, then the image is portrait otherwise landscape. // For portrait images, we need to adjust the width of the image to keep the aspect ratio and vice-versa. @@ -87,9 +72,7 @@ function ThumbnailImage(props) { /** * Update the state with the computed thumbnail sizes. * - * @param {Object} Params - width and height of the original image. - * @param {Number} Params.width - * @param {Number} Params.height + * @param {{ width: number, height: number }} Params - width and height of the original image. */ const updateImageSize = useCallback( From 2ea290c98bfa25ea006baa9378980330858786f6 Mon Sep 17 00:00:00 2001 From: Kadie Alexander Date: Mon, 31 Jul 2023 18:20:48 +1200 Subject: [PATCH 085/223] remove check for invalid image height/width since we'll always pass an image --- src/components/ThumbnailImage.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 348a575d9e30..47516164864f 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -41,10 +41,6 @@ const defaultProps = { */ function calculateThumbnailImageSize(width, height, windowHeight) { - if (!width || !height) { - return {thumbnailWidth: 200, thumbnailHeight: 200}; - } - // 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 // Note: Clamp minimum width 40px to support touch device From 5cad2d78a5215a830aa090b9d496945c9fdd82dc Mon Sep 17 00:00:00 2001 From: Alberto Date: Mon, 31 Jul 2023 10:45:29 +0200 Subject: [PATCH 086/223] add param to header --- src/libs/ReportUtils.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index b14334a5fb72..87a29ce29abb 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1763,6 +1763,7 @@ function buildOptimisticIOUReportAction(type, amount, currency, comment, partici * * @param {Object} chatReport * @param {Object} iouReport + * @param {String} comment - User comment for the IOU. * * @returns {Object} */ @@ -1797,6 +1798,7 @@ function buildOptimisticReportPreview(chatReport, iouReport, comment) { * * @param {Object} iouReport * @param {Object} reportPreviewAction + * @param {String} comment - User comment for the IOU. * * @returns {Object} */ From 725f8eba51c59e5e98d5c85deb5da271067b1aee Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Mon, 31 Jul 2023 22:29:03 +0200 Subject: [PATCH 087/223] add routes --- src/ROUTES.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ROUTES.js b/src/ROUTES.js index 90af74efd22e..e4fa03729919 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -15,6 +15,7 @@ const SETTINGS_CONTACT_METHODS = 'settings/profile/contact-methods'; const SETTINGS_STATUS = 'settings/profile/status'; const SETTINGS_STATUS_SET = 'settings/profile/status/set'; const SETTINGS_STATUS_CLEAR_AFTER = 'settings/profile/status/clear-after'; +const SETTINGS_STATUS_CUSTOM_CLEAR_AFTER = 'settings/profile/status/clear-after/custom'; const SETTINGS_STATUS_SET_TIME = 'settings/profile/status/set-time'; const SETTINGS_STATUS_SET_DATE = 'settings/profile/status/set-date'; @@ -66,6 +67,7 @@ export default { SETTINGS_STATUS, SETTINGS_STATUS_SET, SETTINGS_STATUS_CLEAR_AFTER, + SETTINGS_STATUS_CUSTOM_CLEAR_AFTER, SETTINGS_STATUS_SET_TIME, SETTINGS_STATUS_SET_DATE, NEW_GROUP: 'new/group', From 5e5fd815d005e54cdbe49f9522788f416c942608 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 1 Aug 2023 13:05:00 +0700 Subject: [PATCH 088/223] ussing reportID from route --- src/pages/tasks/TaskAssigneeSelectorModal.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.js b/src/pages/tasks/TaskAssigneeSelectorModal.js index e1687c08fb10..965d4125226a 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.js +++ b/src/pages/tasks/TaskAssigneeSelectorModal.js @@ -167,7 +167,7 @@ function TaskAssigneeSelectorModal(props) { if (props.route.params.reportID) { // There was an issue where sometimes a new assignee didn't have a DM thread // This would cause the app to crash, so we need to make sure we have a DM thread - Task.setAssigneeValue(option.login, option.accountID, props.task.shareDestination, OptionsListUtils.isCurrentUser(option)); + Task.setAssigneeValue(option.login, option.accountID, props.route.params.reportID, OptionsListUtils.isCurrentUser(option)); // Pass through the selected assignee Task.editTaskAndNavigate(lodashGet(props.reports, `report_${props.route.params.reportID}`, {}), props.session.accountID, { @@ -183,7 +183,10 @@ function TaskAssigneeSelectorModal(props) { <> (lodashGet(lodashGet(props.reports, `report_${props.route.params.reportID}`, {}), 'isExistingTaskReport') ? Navigation.dismissModal() : Navigation.goBack(ROUTES.NEW_TASK))} + onBackButtonPress={() => ( + (props.route.params.reportID && lodashGet(props.reports, `report_${props.route.params.reportID}`, undefined)) + ? Navigation.dismissModal() + : Navigation.goBack(ROUTES.NEW_TASK))} /> Date: Tue, 1 Aug 2023 13:06:30 +0700 Subject: [PATCH 089/223] run prettier --- src/pages/tasks/TaskAssigneeSelectorModal.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.js b/src/pages/tasks/TaskAssigneeSelectorModal.js index 965d4125226a..65fafe74b860 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.js +++ b/src/pages/tasks/TaskAssigneeSelectorModal.js @@ -183,10 +183,11 @@ function TaskAssigneeSelectorModal(props) { <> ( - (props.route.params.reportID && lodashGet(props.reports, `report_${props.route.params.reportID}`, undefined)) - ? Navigation.dismissModal() - : Navigation.goBack(ROUTES.NEW_TASK))} + onBackButtonPress={() => + props.route.params.reportID && lodashGet(props.reports, `report_${props.route.params.reportID}`, undefined) + ? Navigation.dismissModal() + : Navigation.goBack(ROUTES.NEW_TASK) + } /> Date: Tue, 1 Aug 2023 14:42:12 +0300 Subject: [PATCH 090/223] Update expensify-common --- package-lock.json | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 48c4befc96be..9236fbaa3045 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#98d8fea356f114f8b5b0cea889a41b355e5daf58", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#56db2a0fc9df6b4270a99e4d3a9a7b0730ad2aa4", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "jest-when": "^3.5.2", @@ -24387,8 +24387,8 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#98d8fea356f114f8b5b0cea889a41b355e5daf58", - "integrity": "sha512-gs3xWgNCjLJsgB9j/oIZaGxQXNM61MapC0eGqtFHgKEwHy6rt08zyZq5CZCow4PVTtF8xgWgkpVifti2dr43cQ==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#56db2a0fc9df6b4270a99e4d3a9a7b0730ad2aa4", + "integrity": "sha512-krP/XqFQRkgt83FEyuEIaVsOAfqTxlqhqDHTMm0/XZWA29bR7WB56AYsDNDHcRS7RVmudBtG9lUbEROeA8dApg==", "license": "MIT", "dependencies": { "classnames": "2.3.1", @@ -59954,9 +59954,9 @@ } }, "expensify-common": { - "version": "git+ssh://git@github.com/Expensify/expensify-common.git#98d8fea356f114f8b5b0cea889a41b355e5daf58", - "integrity": "sha512-gs3xWgNCjLJsgB9j/oIZaGxQXNM61MapC0eGqtFHgKEwHy6rt08zyZq5CZCow4PVTtF8xgWgkpVifti2dr43cQ==", - "from": "expensify-common@git+ssh://git@github.com/Expensify/expensify-common.git#98d8fea356f114f8b5b0cea889a41b355e5daf58", + "version": "git+ssh://git@github.com/Expensify/expensify-common.git#56db2a0fc9df6b4270a99e4d3a9a7b0730ad2aa4", + "integrity": "sha512-krP/XqFQRkgt83FEyuEIaVsOAfqTxlqhqDHTMm0/XZWA29bR7WB56AYsDNDHcRS7RVmudBtG9lUbEROeA8dApg==", + "from": "expensify-common@git+ssh://git@github.com/Expensify/expensify-common.git#56db2a0fc9df6b4270a99e4d3a9a7b0730ad2aa4", "requires": { "classnames": "2.3.1", "clipboard": "2.0.4", diff --git a/package.json b/package.json index 772b441667dc..8a457ebdb99e 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#98d8fea356f114f8b5b0cea889a41b355e5daf58", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#56db2a0fc9df6b4270a99e4d3a9a7b0730ad2aa4", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "jest-when": "^3.5.2", From 10b17791a665d431a47305229d926593805d6c8b Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Tue, 1 Aug 2023 21:05:36 +0200 Subject: [PATCH 091/223] add const --- src/CONST.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/CONST.js b/src/CONST.js index 39d190788b41..7e6c090478d3 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -2545,6 +2545,14 @@ const CONST = { TRANSLATION_KEYS: { ATTACHMENT: 'common.attachment', }, + CUSTOM_STATUS_TYPES: { + NEVER: 'never', + THIRTY_MINUTES: 'thirtyMinutes', + ONE_HOUR: 'oneHour', + AFTER_TODAY: 'afterToday', + AFTER_WEEK: 'afterWeek', + CUSTOM: 'custom', + }, }; export default CONST; From f054f6f7eaa79da8f75af2f6e1cd4044261a3e98 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Tue, 1 Aug 2023 21:06:51 +0200 Subject: [PATCH 092/223] update onyx keys --- src/ONYXKEYS.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index dd73bde936f9..b1000d6e67bc 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -42,6 +42,9 @@ export default { // Has information about the network status (offline/online) NETWORK: 'network', + // Contains all the personalDetails of the user + PERSONAL_DETAILS: 'personalDetails', + // Contains all the personalDetails the user has access to, keyed by accountID PERSONAL_DETAILS_LIST: 'personalDetailsList', @@ -212,6 +215,9 @@ export default { MONEY_REQUEST_DESCRIPTION_FORM: 'moneyRequestDescriptionForm', NEW_CONTACT_METHOD_FORM: 'newContactMethodForm', PAYPAL_FORM: 'payPalForm', + SETTINGS_STATUS_SET_FORM: 'settingsStatusSetForm', + SETTINGS_STATUS_CLEAR_AFTER_FORM: 'settingsStatusClearAfterForm', + SETTINGS_STATUS_SET_CLEAR_AFTER_FORM: 'settingsStatusSetClearAfterForm', }, // Whether we should show the compose input or not From dc343ddda052893688174035392eb76dd1a0ac0a Mon Sep 17 00:00:00 2001 From: Nikhil Vats Date: Wed, 2 Aug 2023 01:16:07 +0530 Subject: [PATCH 093/223] Revert PR 22787 and refactor new usages of shouldHideComposer --- src/libs/SidebarUtils.js | 3 +-- src/pages/home/ReportScreen.js | 31 ++++------------------ src/pages/home/report/ReportActionsList.js | 7 ++--- 3 files changed, 8 insertions(+), 33 deletions(-) diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index dd55b025b55e..670e2d5cdccc 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -263,9 +263,8 @@ function getOptionData(report, personalDetails, preferredLocale, policy) { result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; result.notificationPreference = report.notificationPreference || null; - const {addWorkspaceRoomOrChatErrors} = ReportUtils.getReportOfflinePendingActionAndErrors(report); // If the composer is hidden then the user is not allowed to comment, same can be used to hide the draft icon. - result.isAllowedToComment = !ReportUtils.shouldHideComposer(report, addWorkspaceRoomOrChatErrors); + result.isAllowedToComment = !ReportUtils.shouldHideComposer(report); const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat; const subtitle = ReportUtils.getChatRoomSubtitle(report); diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 04729023958b..ab6295380b3f 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -13,7 +13,6 @@ import * as Report from '../../libs/actions/Report'; import ONYXKEYS from '../../ONYXKEYS'; import * as ReportUtils from '../../libs/ReportUtils'; import ReportActionsView from './report/ReportActionsView'; -import CONST from '../../CONST'; import ReportActionsSkeletonView from '../../components/ReportActionsSkeletonView'; import reportActionPropTypes from './report/reportActionPropTypes'; import {withNetwork} from '../../components/OnyxProvider'; @@ -130,7 +129,6 @@ class ReportScreen extends React.Component { this.state = { skeletonViewContainerHeight: reportActionsListViewHeight, isBannerVisible: true, - isReportRemoved: false, }; this.firstRenderRef = React.createRef(); this.firstRenderRef.current = reportActionsListViewHeight === 0; @@ -161,33 +159,14 @@ class ReportScreen extends React.Component { if (ReportUtils.shouldHideComposer(this.props.report)) { EmojiPickerAction.hideEmojiPicker(true); } - const onyxReportID = this.props.report.reportID; - const prevOnyxReportID = prevProps.report.reportID; - const routeReportID = getReportID(this.props.route); - - // navigate to concierge when the room removed from another device (e.g. user leaving a room) - // the report will not really null when removed, it will have defaultProps properties and values - if ( - prevOnyxReportID && - prevOnyxReportID === routeReportID && - !onyxReportID && - // non-optimistic case - (_.isEqual(this.props.report, defaultProps.report) || - // optimistic case - (prevProps.report.statusNum === CONST.REPORT.STATUS.OPEN && this.props.report.statusNum === CONST.REPORT.STATUS.CLOSED)) - ) { - Navigation.goBack(); - Report.navigateToConciergeChat(); - // isReportRemoved will prevent showing when navigating - this.setState({isReportRemoved: true}); - return; - } - + // If you already have a report open and are deeplinking to a new report on native, // the ReportScreen never actually unmounts and the reportID in the route also doesn't change. // Therefore, we need to compare if the existing reportID is the same as the one in the route // before deciding that we shouldn't call OpenReport. - if (onyxReportID === prevOnyxReportID && (!onyxReportID || onyxReportID === routeReportID)) { + const onyxReportID = this.props.report.reportID; + const routeReportID = getReportID(this.props.route); + if (onyxReportID === prevProps.report.reportID && (!onyxReportID || onyxReportID === routeReportID)) { return; } @@ -315,7 +294,7 @@ class ReportScreen extends React.Component { shouldEnableKeyboardAvoidingView={isTopMostReportId} > From 8bead8694a91b235b6a674b7e2bd3058a290a8c9 Mon Sep 17 00:00:00 2001 From: Nikhil Vats Date: Wed, 2 Aug 2023 02:09:49 +0530 Subject: [PATCH 094/223] fix unnecessary check in IOUCurrencySelection page --- src/pages/home/ReportScreen.js | 2 +- src/pages/iou/IOUCurrencySelection.js | 20 +------------------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index ab6295380b3f..b242feff2369 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -159,7 +159,7 @@ class ReportScreen extends React.Component { if (ReportUtils.shouldHideComposer(this.props.report)) { EmojiPickerAction.hideEmojiPicker(true); } - + // If you already have a report open and are deeplinking to a new report on native, // the ReportScreen never actually unmounts and the reportID in the route also doesn't change. // Therefore, we need to compare if the existing reportID is the same as the one in the route diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index 6ab2a043a272..f9dc03c66f90 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -1,4 +1,4 @@ -import React, {useState, useMemo, useCallback, useEffect} from 'react'; +import React, {useState, useMemo, useCallback} from 'react'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -17,8 +17,6 @@ import * as CurrencyUtils from '../../libs/CurrencyUtils'; import ROUTES from '../../ROUTES'; import themeColors from '../../styles/themes/default'; import * as Expensicons from '../../components/Icon/Expensicons'; -import reportPropTypes from '../reportPropTypes'; -import * as ReportUtils from '../../libs/ReportUtils'; const greenCheckmark = {src: Expensicons.Checkmark, color: themeColors.success}; @@ -45,14 +43,10 @@ const propTypes = { currency: PropTypes.string, }), - /** The report on which the request is initiated on */ - report: reportPropTypes, - ...withLocalizePropTypes, }; const defaultProps = { - report: {}, currencyList: {}, iou: { currency: CONST.CURRENCY.USD, @@ -66,15 +60,6 @@ function IOUCurrencySelection(props) { const iouType = lodashGet(props.route, 'params.iouType', CONST.IOU.MONEY_REQUEST_TYPE.REQUEST); const reportID = lodashGet(props.route, 'params.reportID', ''); - const shouldDismissModal = ReportUtils.shouldHideComposer(props.report); - - useEffect(() => { - if (!shouldDismissModal) { - return; - } - Navigation.dismissModal(reportID); - }, [shouldDismissModal, reportID]); - const confirmCurrencySelection = useCallback( (option) => { const backTo = lodashGet(props.route, 'params.backTo', ''); @@ -160,9 +145,6 @@ export default compose( withOnyx({ currencyList: {key: ONYXKEYS.CURRENCY_LIST}, iou: {key: ONYXKEYS.IOU}, - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID', '')}`, - }, }), withNetwork(), )(IOUCurrencySelection); From a4a2483daafbc3a171a318033222c9d15244cc13 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 2 Aug 2023 05:36:26 +0700 Subject: [PATCH 095/223] fix: popover reaction list does not update dynamically --- .../ReportActionItemEmojiReactions.js | 2 +- .../BasePopoverReactionList.js} | 84 +++++++++++++------ .../ReactionList/PopoverReactionList/index.js | 33 ++++++++ 3 files changed, 94 insertions(+), 25 deletions(-) rename src/pages/home/report/ReactionList/{PopoverReactionList.js => PopoverReactionList/BasePopoverReactionList.js} (71%) create mode 100644 src/pages/home/report/ReactionList/PopoverReactionList/index.js diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.js b/src/components/Reactions/ReportActionItemEmojiReactions.js index 7102f9982f52..806e87b4301d 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.js +++ b/src/components/Reactions/ReportActionItemEmojiReactions.js @@ -91,7 +91,7 @@ function ReportActionItemEmojiReactions(props) { }; const onReactionListOpen = (event) => { - reactionListRef.current.showReactionList(event, popoverReactionListAnchor.current, reaction); + reactionListRef.current.showReactionList(event, popoverReactionListAnchor.current, reactionEmojiName, props.reportActionID); }; return { diff --git a/src/pages/home/report/ReactionList/PopoverReactionList.js b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.js similarity index 71% rename from src/pages/home/report/ReactionList/PopoverReactionList.js rename to src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.js index 8a9b32b5fd9a..677a120518aa 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList.js +++ b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.js @@ -2,17 +2,30 @@ import React from 'react'; import {Dimensions} from 'react-native'; import lodashGet from 'lodash/get'; import _ from 'underscore'; -import * as Report from '../../../../libs/actions/Report'; -import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; -import PopoverWithMeasuredContent from '../../../../components/PopoverWithMeasuredContent'; -import BaseReactionList from './BaseReactionList'; -import compose from '../../../../libs/compose'; -import withCurrentUserPersonalDetails from '../../../../components/withCurrentUserPersonalDetails'; -import * as PersonalDetailsUtils from '../../../../libs/PersonalDetailsUtils'; -import * as EmojiUtils from '../../../../libs/EmojiUtils'; -import CONST from '../../../../CONST'; - -class PopoverReactionList extends React.Component { +import {withOnyx} from "react-native-onyx"; +import * as Report from '../../../../../libs/actions/Report'; +import withLocalize, {withLocalizePropTypes} from '../../../../../components/withLocalize'; +import PopoverWithMeasuredContent from '../../../../../components/PopoverWithMeasuredContent'; +import BaseReactionList from '../BaseReactionList'; +import compose from '../../../../../libs/compose'; +import withCurrentUserPersonalDetails from '../../../../../components/withCurrentUserPersonalDetails'; +import * as PersonalDetailsUtils from '../../../../../libs/PersonalDetailsUtils'; +import * as EmojiUtils from '../../../../../libs/EmojiUtils'; +import CONST from '../../../../../CONST'; +import ONYXKEYS from "../../../../../ONYXKEYS"; +import EmojiReactionsPropTypes from "../../../../../components/Reactions/EmojiReactionsPropTypes"; + +const propTypes = { + emojiReactions: EmojiReactionsPropTypes, + + ...withLocalizePropTypes, +} + +const defaultProps = { + emojiReactions: {}, +}; + +class BasePopoverReactionList extends React.Component { constructor(props) { super(props); @@ -55,13 +68,29 @@ class PopoverReactionList extends React.Component { const nextLocale = lodashGet(nextProps, 'preferredLocale', CONST.LOCALES.DEFAULT); return ( - this.state.isPopoverVisible !== nextState.isPopoverVisible || - this.state.popoverAnchorPosition !== nextState.popoverAnchorPosition || - previousLocale !== nextLocale || - (this.state.isPopoverVisible && (this.state.reportActionID !== nextState.reportActionID || this.state.emojiName !== nextState.emojiName)) + this.props.reportActionID !== nextProps.reportActionID || + !_.isEqual(this.props.emojiReactions, nextProps.emojiReactions) || + !_.isEqual(this.state, nextState) || + previousLocale !== nextLocale ); } + componentDidUpdate() { + // Hide the list when all reactions are removed + if (this.state.isPopoverVisible && !_.some(lodashGet(this.props.emojiReactions, [this.state.emojiName, 'users']), (user) => !_.isNull(user))) { + this.hideReactionList(); + } + + const selectedReaction = lodashGet(this.props.emojiReactions, [this.state.emojiName]); + const {emojiCount, emojiCodes, hasUserReacted, users} = this.getReactionInformation(selectedReaction); + this.setState({ + users, + emojiCodes, + emojiCount, + hasUserReacted, + }); + } + componentWillUnmount() { if (!this.dimensionsEventListener) { return; @@ -70,7 +99,7 @@ class PopoverReactionList extends React.Component { } /** - * Get the PopoverReactionList anchor position + * Get the BasePopoverReactionList anchor position * We calculate the achor coordinates from measureInWindow async method * * @returns {Promise} @@ -120,14 +149,12 @@ class PopoverReactionList extends React.Component { * * @param {Object} [event] - A press event. * @param {Element} reactionListAnchor - reactionListAnchor - * @param {Object} emojiReaction * @param {String} emojiName - Name of emoji */ - showReactionList(event, reactionListAnchor, emojiReaction) { + showReactionList(event, reactionListAnchor, emojiName) { const nativeEvent = event.nativeEvent || {}; this.reactionListAnchor = reactionListAnchor; - const selectedReaction = emojiReaction; - const {emojiName} = emojiReaction; + const selectedReaction = lodashGet(this.props.emojiReactions, [emojiName]); const {emojiCount, emojiCodes, hasUserReacted, users} = this.getReactionInformation(selectedReaction); this.getReactionListMeasuredLocation().then(({x, y}) => { this.setState({ @@ -150,7 +177,7 @@ class PopoverReactionList extends React.Component { } /** - * This gets called on Dimensions change to find the anchor coordinates for the action PopoverReactionList. + * This gets called on Dimensions change to find the anchor coordinates for the action BasePopoverReactionList. */ measureReactionListPosition() { if (!this.state.isPopoverVisible) { @@ -207,6 +234,15 @@ class PopoverReactionList extends React.Component { } } -PopoverReactionList.propTypes = withLocalizePropTypes; - -export default compose(withLocalize, withCurrentUserPersonalDetails)(PopoverReactionList); +BasePopoverReactionList.propTypes = propTypes; +BasePopoverReactionList.defaultProps = defaultProps; + +export default compose( + withLocalize, + withCurrentUserPersonalDetails, + withOnyx({ + emojiReactions: { + key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, + }, + }), +)(BasePopoverReactionList); diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.js b/src/pages/home/report/ReactionList/PopoverReactionList/index.js new file mode 100644 index 000000000000..13ff09ec4b87 --- /dev/null +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.js @@ -0,0 +1,33 @@ +import React, {forwardRef, useImperativeHandle, useRef, useState} from "react"; +import BasePopoverReactionList from "./BasePopoverReactionList"; + +const PopoverReactionList = forwardRef((props, ref) => { + const innerReactionListRef = useRef(); + const [reactionListReportActionID, setReactionListReportActionID] = useState(""); + + /** + * Show the ReactionList modal popover. + * + * @param {Object} [event] - A press event. + * @param {Element} reactionListAnchor - reactionListAnchor + * @param {String} emojiName - Name of emoji + * @param {String} reportActionID + */ + const showReactionList = (event, reactionListAnchor, emojiName, reportActionID) => { + setReactionListReportActionID(reportActionID); + innerReactionListRef.current.showReactionList(event, reactionListAnchor, emojiName); + }; + + useImperativeHandle(ref, () => ({showReactionList, setReactionListReportActionID}), []); + + return ( + + ); +}); + +PopoverReactionList.displayName = 'PopoverReactionList'; + +export default PopoverReactionList; From ca3ee00c95d2f6261f300ea0a77b28e054de9379 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 2 Aug 2023 05:44:49 +0700 Subject: [PATCH 096/223] fix lint --- .../PopoverReactionList/BasePopoverReactionList.js | 8 ++++---- .../home/report/ReactionList/PopoverReactionList/index.js | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.js b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.js index 677a120518aa..2f6f112d5523 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.js +++ b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.js @@ -2,7 +2,7 @@ import React from 'react'; import {Dimensions} from 'react-native'; import lodashGet from 'lodash/get'; import _ from 'underscore'; -import {withOnyx} from "react-native-onyx"; +import {withOnyx} from 'react-native-onyx'; import * as Report from '../../../../../libs/actions/Report'; import withLocalize, {withLocalizePropTypes} from '../../../../../components/withLocalize'; import PopoverWithMeasuredContent from '../../../../../components/PopoverWithMeasuredContent'; @@ -12,14 +12,14 @@ import withCurrentUserPersonalDetails from '../../../../../components/withCurren import * as PersonalDetailsUtils from '../../../../../libs/PersonalDetailsUtils'; import * as EmojiUtils from '../../../../../libs/EmojiUtils'; import CONST from '../../../../../CONST'; -import ONYXKEYS from "../../../../../ONYXKEYS"; -import EmojiReactionsPropTypes from "../../../../../components/Reactions/EmojiReactionsPropTypes"; +import ONYXKEYS from '../../../../../ONYXKEYS'; +import EmojiReactionsPropTypes from '../../../../../components/Reactions/EmojiReactionsPropTypes'; const propTypes = { emojiReactions: EmojiReactionsPropTypes, ...withLocalizePropTypes, -} +}; const defaultProps = { emojiReactions: {}, diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.js b/src/pages/home/report/ReactionList/PopoverReactionList/index.js index 13ff09ec4b87..3b7e041ab61e 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.js +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.js @@ -1,9 +1,9 @@ -import React, {forwardRef, useImperativeHandle, useRef, useState} from "react"; -import BasePopoverReactionList from "./BasePopoverReactionList"; +import React, {forwardRef, useImperativeHandle, useRef, useState} from 'react'; +import BasePopoverReactionList from './BasePopoverReactionList'; const PopoverReactionList = forwardRef((props, ref) => { const innerReactionListRef = useRef(); - const [reactionListReportActionID, setReactionListReportActionID] = useState(""); + const [reactionListReportActionID, setReactionListReportActionID] = useState(''); /** * Show the ReactionList modal popover. From ba12a99476465ef2708834434ffd316bf479d48e Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 2 Aug 2023 15:32:56 +0700 Subject: [PATCH 097/223] fix: cannot leave public room in offline after adding comment --- src/libs/actions/Report.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 38e3676492d4..97dd87e100b5 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1729,6 +1729,8 @@ function openReportFromDeepLink(url, isAuthenticated) { * @param {String} reportID */ function leaveRoom(reportID) { + const report = lodashGet(allReports, `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {}); + const reportKeys = _.keys(report); API.write( 'LeaveRoom', { @@ -1745,6 +1747,15 @@ function leaveRoom(reportID) { }, }, ], + // Manually clear the report using merge. Should not use set here since it would cause race condition + // if it was called right after a merge. + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: _.object(reportKeys, Array(reportKeys.length).fill(null)), + }, + ], failureData: [ { onyxMethod: Onyx.METHOD.SET, From c474122affaa3029a56ced51b5fc55aefdf9b1bf Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 2 Aug 2023 16:17:14 +0700 Subject: [PATCH 098/223] get correct report --- src/libs/actions/Report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 97dd87e100b5..26fbc70b3ad8 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1729,7 +1729,7 @@ function openReportFromDeepLink(url, isAuthenticated) { * @param {String} reportID */ function leaveRoom(reportID) { - const report = lodashGet(allReports, `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {}); + const report = lodashGet(allReports, [reportID], {}); const reportKeys = _.keys(report); API.write( 'LeaveRoom', From 1fc8275f7e60cc73e74e4ca69d52ee962d27b342 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 2 Aug 2023 12:36:22 -0600 Subject: [PATCH 099/223] add confirm modal --- src/components/MoneyRequestHeader.js | 58 ++++++++++++++++++---------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index e73185bd39a4..a21affdcc1a1 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -18,6 +18,7 @@ import * as Policy from '../libs/actions/Policy'; import ONYXKEYS from '../ONYXKEYS'; import * as IOU from '../libs/actions/IOU'; import * as ReportActionsUtils from '../libs/ReportActionsUtils'; +import ConfirmModal from './ConfirmModal'; const propTypes = { /** The report currently being looked at */ @@ -67,27 +68,44 @@ function MoneyRequestHeader(props) { }, [parentReportAction]); return ( - - Navigation.goBack(ROUTES.HOME, false, true)} - shouldShowBorderBottom + <> + + Navigation.goBack(ROUTES.HOME, false, true)} + shouldShowBorderBottom + /> + + { + this.setState({reportID: '0', reportAction: {}}); + this.callbackWhenDeleteModalHide(); + }} + prompt={this.props.translate('reportActionContextMenu.deleteConfirmation', {action: this.state.reportAction})} + confirmText={this.props.translate('common.delete')} + cancelText={this.props.translate('common.cancel')} + danger /> - + ); } From 218976ba8ea4afc78559328a57580370067bc99d Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 2 Aug 2023 12:44:26 -0600 Subject: [PATCH 100/223] update props, add translation keys --- src/components/MoneyRequestHeader.js | 29 ++++++++++++++-------------- src/languages/en.js | 1 + src/languages/es.js | 1 + 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index a21affdcc1a1..8fb6dede0a3a 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -1,4 +1,4 @@ -import React, {useCallback} from 'react'; +import React, {useState, useCallback} from 'react'; import {withOnyx} from 'react-native-onyx'; import {View} from 'react-native'; import PropTypes from 'prop-types'; @@ -19,6 +19,7 @@ import ONYXKEYS from '../ONYXKEYS'; import * as IOU from '../libs/actions/IOU'; import * as ReportActionsUtils from '../libs/ReportActionsUtils'; import ConfirmModal from './ConfirmModal'; +import useLocalize from '../hooks/useLocalize'; const propTypes = { /** The report currently being looked at */ @@ -53,6 +54,8 @@ const defaultProps = { }; function MoneyRequestHeader(props) { + const {translate} = useLocalize(); + const {isDeleteModalVisible, setIsDeleteModalVisible} = useState(false); const moneyRequestReport = props.parentReport; const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const policy = props.policies[`${ONYXKEYS.COLLECTION.POLICY}${props.report.policyID}`]; @@ -65,7 +68,8 @@ function MoneyRequestHeader(props) { const deleteTransaction = useCallback(() => { IOU.deleteMoneyRequest(parentReportAction.originalMessage.IOUTransactionID, parentReportAction, true); - }, [parentReportAction]); + setIsDeleteModalVisible(false); + }, [parentReportAction, setIsDeleteModalVisible]); return ( <> @@ -78,7 +82,7 @@ function MoneyRequestHeader(props) { { icon: Expensicons.Trashcan, text: props.translate('reportActionContextMenu.deleteAction', {action: parentReportAction}), - onSelected: deleteTransaction, + onSelected: () => setIsDeleteModalVisible(true), }, ]} threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(props.windowWidth)} @@ -91,18 +95,13 @@ function MoneyRequestHeader(props) { /> { - this.setState({reportID: '0', reportAction: {}}); - this.callbackWhenDeleteModalHide(); - }} - prompt={this.props.translate('reportActionContextMenu.deleteConfirmation', {action: this.state.reportAction})} - confirmText={this.props.translate('common.delete')} - cancelText={this.props.translate('common.cancel')} + title={translate('iou.deleteRequest')} + isVisible={isDeleteModalVisible} + onConfirm={deleteTransaction} + onCancel={() => setIsDeleteModalVisible(false)} + prompt={translate('reportActionContextMenu.deleteConfirmation', {action: this.state.reportAction})} + confirmText={translate('common.delete')} + cancelText={translate('common.cancel')} danger /> diff --git a/src/languages/en.js b/src/languages/en.js index ba4aca6b4a2a..384836117494 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -354,6 +354,7 @@ export default { pay: 'Pay', viewDetails: 'View details', pending: 'Pending', + deleteRequest: 'Delete request', settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', settledPaypalMe: 'Paid using Paypal.me', diff --git a/src/languages/es.js b/src/languages/es.js index a3c983ac6c10..c3d6ad43b225 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -353,6 +353,7 @@ export default { pay: 'Pagar', viewDetails: 'Ver detalles', pending: 'Pendiente', + deleteRequest: 'Eliminar pedido', settledExpensify: 'Pagado', settledElsewhere: 'Pagado de otra forma', settledPaypalMe: 'Pagado con PayPal.me', From fd2fa3d891f3e505ec95b20063ecb0510ecae7d6 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 2 Aug 2023 12:49:57 -0600 Subject: [PATCH 101/223] add more copy, fix state syntax --- src/components/MoneyRequestHeader.js | 4 ++-- src/languages/en.js | 1 + src/languages/es.js | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 8fb6dede0a3a..271c87b20a89 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -55,7 +55,7 @@ const defaultProps = { function MoneyRequestHeader(props) { const {translate} = useLocalize(); - const {isDeleteModalVisible, setIsDeleteModalVisible} = useState(false); + const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const moneyRequestReport = props.parentReport; const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const policy = props.policies[`${ONYXKEYS.COLLECTION.POLICY}${props.report.policyID}`]; @@ -99,7 +99,7 @@ function MoneyRequestHeader(props) { isVisible={isDeleteModalVisible} onConfirm={deleteTransaction} onCancel={() => setIsDeleteModalVisible(false)} - prompt={translate('reportActionContextMenu.deleteConfirmation', {action: this.state.reportAction})} + prompt={translate('iou.deleteConfirmation')} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} danger diff --git a/src/languages/en.js b/src/languages/en.js index 384836117494..1058f6b1456d 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -355,6 +355,7 @@ export default { viewDetails: 'View details', pending: 'Pending', deleteRequest: 'Delete request', + deleteConfirmation: 'Are you sure that you want to delete this request?', settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', settledPaypalMe: 'Paid using Paypal.me', diff --git a/src/languages/es.js b/src/languages/es.js index c3d6ad43b225..1ab34dec85bd 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -354,6 +354,7 @@ export default { viewDetails: 'Ver detalles', pending: 'Pendiente', deleteRequest: 'Eliminar pedido', + deleteConfirmation: '¿Estás seguro de que quieres eliminar este pedido?', settledExpensify: 'Pagado', settledElsewhere: 'Pagado de otra forma', settledPaypalMe: 'Pagado con PayPal.me', From 2bde3aa75cd16492c7892060b35a465c9b4fe718 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 2 Aug 2023 13:13:57 -0600 Subject: [PATCH 102/223] replace hook usage, add proptypes --- src/components/MoneyRequestHeader.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 271c87b20a89..79a144a2dc1a 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -5,12 +5,11 @@ import PropTypes from 'prop-types'; import lodashGet from 'lodash/get'; import HeaderWithBackButton from './HeaderWithBackButton'; import iouReportPropTypes from '../pages/iouReportPropTypes'; -import withLocalize, {withLocalizePropTypes} from './withLocalize'; import * as ReportUtils from '../libs/ReportUtils'; import * as Expensicons from './Icon/Expensicons'; import participantPropTypes from './participantPropTypes'; import styles from '../styles/styles'; -import withWindowDimensions from './withWindowDimensions'; +import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import compose from '../libs/compose'; import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; @@ -43,7 +42,7 @@ const propTypes = { email: PropTypes.string, }), - ...withLocalizePropTypes, + ...windowDimensionsPropTypes, }; const defaultProps = { @@ -81,7 +80,7 @@ function MoneyRequestHeader(props) { threeDotsMenuItems={[ { icon: Expensicons.Trashcan, - text: props.translate('reportActionContextMenu.deleteAction', {action: parentReportAction}), + text: translate('reportActionContextMenu.deleteAction', {action: parentReportAction}), onSelected: () => setIsDeleteModalVisible(true), }, ]} @@ -114,7 +113,6 @@ MoneyRequestHeader.defaultProps = defaultProps; export default compose( withWindowDimensions, - withLocalize, withOnyx({ session: { key: ONYXKEYS.SESSION, From 062b8c1a14f9c413ae9e9f90ab3585e48a087765 Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Thu, 3 Aug 2023 06:27:52 +0530 Subject: [PATCH 103/223] Add logic for ignoring zombie report actions This finds reportAction using _.find(), and moves the object validation logic to its own function. --- .../CheckForPreviousReportActionID.js | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/libs/migrations/CheckForPreviousReportActionID.js b/src/libs/migrations/CheckForPreviousReportActionID.js index b3a6f639343f..ff043cb2499a 100644 --- a/src/libs/migrations/CheckForPreviousReportActionID.js +++ b/src/libs/migrations/CheckForPreviousReportActionID.js @@ -19,6 +19,16 @@ function getReportActionsFromOnyx() { }); } +/** + * Checks if the input object is not null, empty or undefined. + * + * @param {Object} data + * @returns {Boolean} + */ +function isValid(data) { + return data !== null && !_.isEmpty(data) && !_.isUndefined(data); +} + /** * Migrate Onyx data for reportActions. If the first reportAction of a reportActionsForReport * does not contain a 'previousReportActionID', all reportActions for all reports are removed from Onyx. @@ -27,25 +37,32 @@ function getReportActionsFromOnyx() { */ export default function () { return getReportActionsFromOnyx().then((allReportActions) => { - if (_.isEmpty(allReportActions)) { + if (!isValid(allReportActions)) { Log.info(`[Migrate Onyx] Skipped migration CheckForPreviousReportActionID because there were no reportActions`); return; } - const firstReportActionID = _.keys(allReportActions)[0]; - const firstReportAction = allReportActions[firstReportActionID]; - const firstValueOfReportAction = _.values(firstReportAction)[0]; + const firstValidReportAction = _.find(_.values(allReportActions), (reportAction) => isValid(reportAction)); + const firstValidValue = _.find(_.values(firstValidReportAction), (reportActionData) => isValid(reportActionData)); + + if (_.isUndefined(firstValidValue)) { + Log.info(`[Migrate Onyx] Skipped migration CheckForPreviousReportActionID because there were no valid reportActions`); + return; + } - if (_.has(firstValueOfReportAction, 'previousReportActionID')) { + if (_.has(firstValidValue, 'previousReportActionID')) { Log.info(`[Migrate Onyx] CheckForPreviousReportActionID Migration: previousReportActionID found. Migration complete`); return; } // If previousReportActionID not found: - Log.info(`[Migrate Onyx] CheckForPreviousReportActionID Migration: removing all reportActions because previousReportActionID not found in the first reportAction`); + Log.info(`[Migrate Onyx] CheckForPreviousReportActionID Migration: removing all reportActions because previousReportActionID not found in the first valid reportAction`); const onyxData = {}; _.each(allReportActions, (reportAction, onyxKey) => { + if (!isValid(reportAction)) { + return; + } onyxData[onyxKey] = {}; }); From a76ddd37ad0bbe692584815faaa3541f24b3444a Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Thu, 3 Aug 2023 06:47:38 +0530 Subject: [PATCH 104/223] Add tests to check for corner cases These tests would check for logic around empty/null data objects (zombie reportActions) --- tests/unit/MigrationTest.js | 107 ++++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 4 deletions(-) diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index a92d47104cb2..15c1c70e82dd 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -662,7 +662,7 @@ describe('Migrations', () => { .then(CheckForPreviousReportActionID) .then(() => { expect(LogSpy).toHaveBeenCalledWith( - '[Migrate Onyx] CheckForPreviousReportActionID Migration: removing all reportActions because previousReportActionID not found in the first reportAction', + '[Migrate Onyx] CheckForPreviousReportActionID Migration: removing all reportActions because previousReportActionID not found in the first valid reportAction', ); const connectionID = Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, @@ -675,7 +675,7 @@ describe('Migrations', () => { }); })); - it('Should not remove any report action given that previousReportActionID exists in every action', () => { + it('Should not remove any report action given that previousReportActionID exists in first valid report action', () => Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { 1: { @@ -705,7 +705,106 @@ describe('Migrations', () => { expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); }, }); - }); - }); + })); + + it('Should skip zombie report actions and proceed to remove all reportActions given that a previousReportActionID does not exist', () => + Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: {}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]: { + 1: { + actorEmail: 'sample_email@example.com', + }, + 2: { + actorEmail: 'another_sample_email@example.com', + }, + }, + }) + .then(CheckForPreviousReportActionID) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith( + '[Migrate Onyx] CheckForPreviousReportActionID Migration: removing all reportActions because previousReportActionID not found in the first valid reportAction', + ); + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (allReportActions) => { + Onyx.disconnect(connectionID); + const expectedReportAction = {}; + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toBeNull(); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toBeNull(); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toMatchObject(expectedReportAction); + }, + }); + })); + + it('Should skip zombie report actions and should not remove any report action given that previousReportActionID exists in first valid report action', () => + Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: {}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]: { + 1: { + actorEmail: 'sample_email@example.com', + previousReportActionID: 10, + }, + 2: { + actorEmail: 'another_sample_email@example.com', + previousReportActionID: 23, + }, + }, + }) + .then(CheckForPreviousReportActionID) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] CheckForPreviousReportActionID Migration: previousReportActionID found. Migration complete'); + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (allReportActions) => { + Onyx.disconnect(connectionID); + const expectedReportAction1 = {}; + const expectedReportAction4 = { + 1: { + actorEmail: 'sample_email@example.com', + previousReportActionID: 10, + }, + 2: { + actorEmail: 'another_sample_email@example.com', + previousReportActionID: 23, + }, + }; + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toMatchObject(expectedReportAction1); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toBeNull(); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toBeNull(); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toMatchObject(expectedReportAction4); + }, + }); + })); + + it('Should skip if no valid reportActions', () => + Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: {}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]: {}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]: null, + }) + .then(CheckForPreviousReportActionID) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration CheckForPreviousReportActionID because there were no valid reportActions'); + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (allReportActions) => { + Onyx.disconnect(connectionID); + const expectedReportAction = {}; + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]).toBeNull(); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]).toMatchObject(expectedReportAction); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]).toMatchObject(expectedReportAction); + expect(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]).toBeNull(); + }, + }); + })); }); }); From f94b25954a80c3c8a796045e2e074cd9d172c86f Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 3 Aug 2023 09:53:16 +0700 Subject: [PATCH 105/223] make code dry --- src/pages/tasks/TaskAssigneeSelectorModal.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.js b/src/pages/tasks/TaskAssigneeSelectorModal.js index 65fafe74b860..60011c1945f1 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.js +++ b/src/pages/tasks/TaskAssigneeSelectorModal.js @@ -108,6 +108,13 @@ function TaskAssigneeSelectorModal(props) { setSearchValue(newSearchTerm); }; + const report = useMemo(() => { + if (!props.route.params || !props.route.params.reportID) { + return null; + } + return lodashGet(props.reports, `${ONYXKEYS.COLLECTION.REPORT}${props.route.params.reportID}`, undefined); + }, [props.reports, props.route.params]); + const sections = useMemo(() => { const sectionsList = []; let indexOffset = 0; @@ -164,13 +171,13 @@ function TaskAssigneeSelectorModal(props) { } // Check to see if we're editing a task and if so, update the assignee - if (props.route.params.reportID) { + if (report) { // There was an issue where sometimes a new assignee didn't have a DM thread // This would cause the app to crash, so we need to make sure we have a DM thread Task.setAssigneeValue(option.login, option.accountID, props.route.params.reportID, OptionsListUtils.isCurrentUser(option)); // Pass through the selected assignee - Task.editTaskAndNavigate(lodashGet(props.reports, `report_${props.route.params.reportID}`, {}), props.session.accountID, { + Task.editTaskAndNavigate(report, props.session.accountID, { assignee: option.login, assigneeAccountID: option.accountID, }); @@ -183,11 +190,7 @@ function TaskAssigneeSelectorModal(props) { <> - props.route.params.reportID && lodashGet(props.reports, `report_${props.route.params.reportID}`, undefined) - ? Navigation.dismissModal() - : Navigation.goBack(ROUTES.NEW_TASK) - } + onBackButtonPress={() => (report ? Navigation.dismissModal() : Navigation.goBack(ROUTES.NEW_TASK))} /> Date: Thu, 3 Aug 2023 14:38:40 +0700 Subject: [PATCH 106/223] add propTypes --- .../ReportActionItemEmojiReactions.js | 3 +- .../ReactionList/PopoverReactionList/index.js | 29 ++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.js b/src/components/Reactions/ReportActionItemEmojiReactions.js index 806e87b4301d..43353c71d194 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.js +++ b/src/components/Reactions/ReportActionItemEmojiReactions.js @@ -91,7 +91,8 @@ function ReportActionItemEmojiReactions(props) { }; const onReactionListOpen = (event) => { - reactionListRef.current.showReactionList(event, popoverReactionListAnchor.current, reactionEmojiName, props.reportActionID); + reactionListRef.current.setReactionListReportActionID(props.reportActionID); + reactionListRef.current.showReactionList(event, popoverReactionListAnchor.current, reactionEmojiName); }; return { diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.js b/src/pages/home/report/ReactionList/PopoverReactionList/index.js index 3b7e041ab61e..0f7e7fcc2acf 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.js +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.js @@ -1,7 +1,16 @@ import React, {forwardRef, useImperativeHandle, useRef, useState} from 'react'; +import PropTypes from "prop-types"; import BasePopoverReactionList from './BasePopoverReactionList'; -const PopoverReactionList = forwardRef((props, ref) => { +const propTypes = { + ref: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), +} + +const defaultProps = { + ref: () => {}, +} + +function PopoverReactionList(props) { const innerReactionListRef = useRef(); const [reactionListReportActionID, setReactionListReportActionID] = useState(''); @@ -11,14 +20,12 @@ const PopoverReactionList = forwardRef((props, ref) => { * @param {Object} [event] - A press event. * @param {Element} reactionListAnchor - reactionListAnchor * @param {String} emojiName - Name of emoji - * @param {String} reportActionID */ - const showReactionList = (event, reactionListAnchor, emojiName, reportActionID) => { - setReactionListReportActionID(reportActionID); + const showReactionList = (event, reactionListAnchor, emojiName) => { innerReactionListRef.current.showReactionList(event, reactionListAnchor, emojiName); }; - useImperativeHandle(ref, () => ({showReactionList, setReactionListReportActionID}), []); + useImperativeHandle(props.ref, () => ({showReactionList, setReactionListReportActionID}), []); return ( { reportActionID={reactionListReportActionID} /> ); -}); +} +PopoverReactionList.propTypes = propTypes; +PopoverReactionList.defaultProps = defaultProps; PopoverReactionList.displayName = 'PopoverReactionList'; -export default PopoverReactionList; +export default forwardRef((props, ref) => ( + +)); From 7c57255da0ab9671f3937cfb3b4e3ff47e24d477 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 3 Aug 2023 15:27:31 +0700 Subject: [PATCH 107/223] fix lint --- .../home/report/ReactionList/PopoverReactionList/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.js b/src/pages/home/report/ReactionList/PopoverReactionList/index.js index 0f7e7fcc2acf..268c03045083 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.js +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.js @@ -1,14 +1,14 @@ import React, {forwardRef, useImperativeHandle, useRef, useState} from 'react'; -import PropTypes from "prop-types"; +import PropTypes from 'prop-types'; import BasePopoverReactionList from './BasePopoverReactionList'; const propTypes = { ref: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), -} +}; const defaultProps = { ref: () => {}, -} +}; function PopoverReactionList(props) { const innerReactionListRef = useRef(); From 32efe36b993d016a3f3a9438a4044c0090e377fb Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 3 Aug 2023 17:07:28 +0700 Subject: [PATCH 108/223] remove lodashget --- src/pages/tasks/TaskAssigneeSelectorModal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.js b/src/pages/tasks/TaskAssigneeSelectorModal.js index 0c33565cabca..f1355e62c008 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.js +++ b/src/pages/tasks/TaskAssigneeSelectorModal.js @@ -112,7 +112,7 @@ function TaskAssigneeSelectorModal(props) { if (!props.route.params || !props.route.params.reportID) { return null; } - return lodashGet(props.reports, `${ONYXKEYS.COLLECTION.REPORT}${props.route.params.reportID}`, undefined); + return props.reports[`${ONYXKEYS.COLLECTION.REPORT}${props.route.params.reportID}`]; }, [props.reports, props.route.params]); const sections = useMemo(() => { From 47414f87b831fc2ee1395d9f7cd8cba58a9afcd7 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 3 Aug 2023 17:16:10 +0700 Subject: [PATCH 109/223] remove lodashget --- src/pages/tasks/TaskAssigneeSelectorModal.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.js b/src/pages/tasks/TaskAssigneeSelectorModal.js index f1355e62c008..9ae7814815bf 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.js +++ b/src/pages/tasks/TaskAssigneeSelectorModal.js @@ -4,7 +4,6 @@ import {View} from 'react-native'; import _ from 'underscore'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; -import lodashGet from 'lodash/get'; import OptionsSelector from '../../components/OptionsSelector'; import * as OptionsListUtils from '../../libs/OptionsListUtils'; import ONYXKEYS from '../../ONYXKEYS'; From c15b7552e0022595300c5b64f44c3fa6d1f290e9 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 3 Aug 2023 18:39:20 +0700 Subject: [PATCH 110/223] rename ref to innerRef --- .../home/report/ReactionList/PopoverReactionList/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.js b/src/pages/home/report/ReactionList/PopoverReactionList/index.js index 268c03045083..64d22df0d16c 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.js +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.js @@ -3,11 +3,11 @@ import PropTypes from 'prop-types'; import BasePopoverReactionList from './BasePopoverReactionList'; const propTypes = { - ref: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), }; const defaultProps = { - ref: () => {}, + innerRef: () => {}, }; function PopoverReactionList(props) { @@ -25,7 +25,7 @@ function PopoverReactionList(props) { innerReactionListRef.current.showReactionList(event, reactionListAnchor, emojiName); }; - useImperativeHandle(props.ref, () => ({showReactionList, setReactionListReportActionID}), []); + useImperativeHandle(props.innerRef, () => ({showReactionList, setReactionListReportActionID}), []); return ( ( )); From b0ab02bedf61d1e4df8572a146777a4d5079ffd6 Mon Sep 17 00:00:00 2001 From: Pujan Date: Thu, 3 Aug 2023 17:43:04 +0530 Subject: [PATCH 111/223] added useCallback for scrollToBottom --- src/hooks/useReportScrollManager/index.native.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/useReportScrollManager/index.native.js b/src/hooks/useReportScrollManager/index.native.js index e206991efa03..35af064cb062 100644 --- a/src/hooks/useReportScrollManager/index.native.js +++ b/src/hooks/useReportScrollManager/index.native.js @@ -1,4 +1,4 @@ -import {useContext} from 'react'; +import {useContext, useCallback} from 'react'; import ReportScreenContext from '../../pages/home/ReportScreenContext'; function useReportScrollManager() { @@ -20,13 +20,13 @@ function useReportScrollManager() { /** * Scroll to the bottom of the flatlist. */ - const scrollToBottom = () => { + const scrollToBottom = useCallback(() => { if (!flatListRef.current) { return; } flatListRef.current.scrollToOffset({animated: false, offset: 0}); - }; + }, [flatListRef]); return {ref: flatListRef, scrollToIndex, scrollToBottom}; } From 6577e8936f47459cd58afdcb3ad2c015e62aafe5 Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Thu, 3 Aug 2023 19:38:19 +0530 Subject: [PATCH 112/223] Remove isValid function definition This removes the custom isValid() function and performs checks using the underscorejs function. --- .../CheckForPreviousReportActionID.js | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/libs/migrations/CheckForPreviousReportActionID.js b/src/libs/migrations/CheckForPreviousReportActionID.js index ff043cb2499a..c88b1820cf12 100644 --- a/src/libs/migrations/CheckForPreviousReportActionID.js +++ b/src/libs/migrations/CheckForPreviousReportActionID.js @@ -19,16 +19,6 @@ function getReportActionsFromOnyx() { }); } -/** - * Checks if the input object is not null, empty or undefined. - * - * @param {Object} data - * @returns {Boolean} - */ -function isValid(data) { - return data !== null && !_.isEmpty(data) && !_.isUndefined(data); -} - /** * Migrate Onyx data for reportActions. If the first reportAction of a reportActionsForReport * does not contain a 'previousReportActionID', all reportActions for all reports are removed from Onyx. @@ -37,13 +27,13 @@ function isValid(data) { */ export default function () { return getReportActionsFromOnyx().then((allReportActions) => { - if (!isValid(allReportActions)) { + if (_.isEmpty(allReportActions)) { Log.info(`[Migrate Onyx] Skipped migration CheckForPreviousReportActionID because there were no reportActions`); return; } - const firstValidReportAction = _.find(_.values(allReportActions), (reportAction) => isValid(reportAction)); - const firstValidValue = _.find(_.values(firstValidReportAction), (reportActionData) => isValid(reportActionData)); + const firstValidReportAction = _.find(_.values(allReportActions), (reportActions) => !_.isEmpty(reportActions)); + const firstValidValue = _.find(_.values(firstValidReportAction), (reportActionData) => _.has(reportActionData, 'reportActionID')); if (_.isUndefined(firstValidValue)) { Log.info(`[Migrate Onyx] Skipped migration CheckForPreviousReportActionID because there were no valid reportActions`); @@ -60,7 +50,7 @@ export default function () { const onyxData = {}; _.each(allReportActions, (reportAction, onyxKey) => { - if (!isValid(reportAction)) { + if (_.isEmpty(reportAction)) { return; } onyxData[onyxKey] = {}; From 4459e311d097ba75a5d41ae9bbcc767f4260a5ca Mon Sep 17 00:00:00 2001 From: Someshwar Tripathi Date: Thu, 3 Aug 2023 19:40:55 +0530 Subject: [PATCH 113/223] Update tests to add the presence of reportActionID in non zombie reportAction data --- tests/unit/MigrationTest.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index 15c1c70e82dd..ae14cea6d1df 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -641,7 +641,7 @@ describe('Migrations', () => { })); }); - describe('CheckPreviousReportActionID', () => { + describe('CheckForPreviousReportActionID', () => { // Note: this test has to come before the others in this suite because Onyx.clear leaves traces and keys with null values aren't cleared out between tests it("Should work even if there's no reportAction data in Onyx", () => CheckForPreviousReportActionID().then(() => @@ -652,10 +652,10 @@ describe('Migrations', () => { Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { 1: { - actorEmail: 'sample_email@example.com', + reportActionID: 1, }, 2: { - actorEmail: 'another_sample_email@example.com', + reportActionID: 2, }, }, }) @@ -679,9 +679,11 @@ describe('Migrations', () => { Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { 1: { + reportActionID: 1, previousReportActionID: 0, }, 2: { + reportActionID: 2, previousReportActionID: 1, }, }, @@ -696,9 +698,11 @@ describe('Migrations', () => { Onyx.disconnect(connectionID); const expectedReportAction = { 1: { + reportActionID: 1, previousReportActionID: 0, }, 2: { + reportActionID: 2, previousReportActionID: 1, }, }; @@ -714,10 +718,10 @@ describe('Migrations', () => { [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]: null, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]: { 1: { - actorEmail: 'sample_email@example.com', + reportActionID: 1, }, 2: { - actorEmail: 'another_sample_email@example.com', + reportActionID: 2, }, }, }) @@ -747,11 +751,11 @@ describe('Migrations', () => { [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}3`]: null, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}4`]: { 1: { - actorEmail: 'sample_email@example.com', + reportActionID: 1, previousReportActionID: 10, }, 2: { - actorEmail: 'another_sample_email@example.com', + reportActionID: 2, previousReportActionID: 23, }, }, @@ -767,11 +771,11 @@ describe('Migrations', () => { const expectedReportAction1 = {}; const expectedReportAction4 = { 1: { - actorEmail: 'sample_email@example.com', + reportActionID: 1, previousReportActionID: 10, }, 2: { - actorEmail: 'another_sample_email@example.com', + reportActionID: 2, previousReportActionID: 23, }, }; From 5a50ab98f94404c2e9570457f14de6ffa44828ed Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 3 Aug 2023 08:22:44 -0700 Subject: [PATCH 114/223] Fix selector --- src/components/DistanceRequest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index 7b8333ab3ae3..1fc6496553e7 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -36,5 +36,5 @@ DistanceRequest.displayName = 'DistanceRequest'; DistanceRequest.propTypes = propTypes; DistanceRequest.defaultProps = defaultProps; export default withOnyx({ - transactionID: {key: ONYXKEYS.IOU, selector: (iou) => iou.transactionID}, + transactionID: {key: ONYXKEYS.IOU, selector: (iou) => iou.transactionID || ''}, })(DistanceRequest); From 5395245317f9bae6cf273c6703288a529d40d9fb Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 3 Aug 2023 08:23:04 -0700 Subject: [PATCH 115/223] Fix distance tab after merge --- src/components/TabSelector/TabSelector.js | 11 ++++++++++- src/pages/iou/MoneyRequestSelectorPage.js | 16 +++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/components/TabSelector/TabSelector.js b/src/components/TabSelector/TabSelector.js index 7bb2f12d1181..3e0ff7eb4e99 100644 --- a/src/components/TabSelector/TabSelector.js +++ b/src/components/TabSelector/TabSelector.js @@ -27,7 +27,16 @@ const defaultProps = { onTabPress: () => {}, }; -const getIcon = (route) => (route === CONST.TAB.MANUAL ? Expensicons.Pencil : Expensicons.Receipt); +const getIcon = (route) => { + switch (route) { + case CONST.TAB.SCAN: + return Expensicons.Receipt; + case CONST.TAB.DISTANCE: + return Expensicons.Car; + default: + return Expensicons.Pencil; + } +}; function TabSelector({state, navigation, onTabPress}) { const {translate} = useLocalize(); diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index 7a1cec13385d..c490d23e601d 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -17,9 +17,6 @@ import MoneyRequestAmount from './steps/MoneyRequestAmount'; import ReceiptSelector from './ReceiptSelector'; import * as IOU from '../../libs/actions/IOU'; import DistanceRequest from '../../components/DistanceRequest'; -import reportPropTypes from '../reportPropTypes'; -import NavigateToNextIOUPage from './NavigateToNextIOUPage'; -import AttachmentUtils from '../../libs/AttachmentUtils'; import DragAndDropProvider from '../../components/DragAndDrop/Provider'; import usePermissions from '../../hooks/usePermissions'; import OnyxTabNavigator, {TopTab} from '../../libs/Navigation/OnyxTabNavigator'; @@ -66,7 +63,7 @@ function MoneyRequestSelectorPage(props) { const iouType = lodashGet(props.route, 'params.iouType', ''); const reportID = lodashGet(props.route, 'params.reportID', ''); const {translate} = useLocalize(); - const {canUseScanReceipts} = usePermissions(); + const {canUseScanReceipts, canUseDistanceRequests} = usePermissions(); const title = { [CONST.IOU.MONEY_REQUEST_TYPE.REQUEST]: translate('iou.requestMoney'), @@ -110,11 +107,12 @@ function MoneyRequestSelectorPage(props) { component={ReceiptSelector} initialParams={{reportID, iouType}} /> - + {canUseDistanceRequests && ( + + )} ) : ( From 350f896e5d382981eb339d840ed3093c0b1ec4a2 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Thu, 3 Aug 2023 17:01:55 +0000 Subject: [PATCH 116/223] fix: package.json & package-lock.json to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-ELECTRON-5812138 - https://snyk.io/vuln/SNYK-JS-ELECTRON-5812149 - https://snyk.io/vuln/SNYK-JS-ELECTRON-5812567 --- package-lock.json | 15 +++++++-------- package.json | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ac853f3c5ab..d57c28e143b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -170,7 +170,7 @@ "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", - "electron": "^25.2.0", + "electron": "^25.4.0", "electron-builder": "24.5.0", "eslint": "^7.6.0", "eslint-config-airbnb-typescript": "^17.1.0", @@ -23400,12 +23400,11 @@ } }, "node_modules/electron": { - "version": "25.2.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-25.2.0.tgz", - "integrity": "sha512-I/rhcW2sV2fyiveVSBr2N7v5ZiCtdGY0UiNCDZgk2fpSC+irQjbeh7JT2b4vWmJ2ogOXBjqesrN9XszTIG6DHg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-25.4.0.tgz", + "integrity": "sha512-VLTRxDhL4UvQbqM7pTNENnJo62cdAPZT92N+B7BZQ5Xfok1wuVPEewIjBot4K7U3EpLUuHn1veeLzho3ihiP+Q==", "dev": true, "hasInstallScript": true, - "license": "MIT", "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^18.11.18", @@ -61065,9 +61064,9 @@ } }, "electron": { - "version": "25.2.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-25.2.0.tgz", - "integrity": "sha512-I/rhcW2sV2fyiveVSBr2N7v5ZiCtdGY0UiNCDZgk2fpSC+irQjbeh7JT2b4vWmJ2ogOXBjqesrN9XszTIG6DHg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-25.4.0.tgz", + "integrity": "sha512-VLTRxDhL4UvQbqM7pTNENnJo62cdAPZT92N+B7BZQ5Xfok1wuVPEewIjBot4K7U3EpLUuHn1veeLzho3ihiP+Q==", "dev": true, "requires": { "@electron/get": "^2.0.0", diff --git a/package.json b/package.json index a511773f2971..1b161d9251d9 100644 --- a/package.json +++ b/package.json @@ -209,7 +209,7 @@ "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", - "electron": "^25.2.0", + "electron": "^25.4.0", "electron-builder": "24.5.0", "eslint": "^7.6.0", "eslint-config-airbnb-typescript": "^17.1.0", From f5bdc40ed5d5622390bc2e3ad024209e9b5629fc Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Thu, 3 Aug 2023 20:14:32 +0200 Subject: [PATCH 117/223] add svg --- assets/images/money-stack.svg | 125 ++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 assets/images/money-stack.svg diff --git a/assets/images/money-stack.svg b/assets/images/money-stack.svg new file mode 100644 index 000000000000..b9a93c76198c --- /dev/null +++ b/assets/images/money-stack.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 87fcf743ff720c12a44ce496bf0b2e349e6bcc54 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Thu, 3 Aug 2023 20:16:47 +0200 Subject: [PATCH 118/223] add translations --- src/languages/en.js | 4 ++++ src/languages/es.js | 12 +++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 4d218a6e53e2..14987d02a422 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -842,6 +842,10 @@ export default { status: 'Status', setStatusTitle: 'Set your status', statusExplanation: "Add an emoji to give your colleagues and friends an easy way to know what's going on. You can optionally add a message too!", + today: 'Today', + clearStatus: 'Clear status', + save: 'Save', + message: 'Message', }, stepCounter: ({step, total, text}) => { let result = `Step ${step}`; diff --git a/src/languages/es.js b/src/languages/es.js index bd89ede675d5..76eecb08628e 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -842,9 +842,15 @@ export default { setPasswordLinkInvalid: 'El enlace para configurar tu contraseña ha expirado. Te hemos enviado un nuevo enlace a tu correo.', validateAccount: 'Verificar cuenta', }, - StatusPage: { - status: 'Estado', - }, + statusPage: { + status: 'Estado', + setStatusTitle: 'Establece tu estado', + statusExplanation: "Agrega un emoji para que tus colegas y amigos puedan saber fácilmente qué está pasando. ¡También puedes agregar un mensaje opcionalmente!", + today: 'Hoy', + clearStatus: 'Borrar estado', + save: 'Guardar', + message: 'Mensaje', + }, stepCounter: ({step, total, text}) => { let result = `Paso ${step}`; From bd5ff5d7774c221c265e63c0d318a25f0448abb8 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Thu, 3 Aug 2023 20:18:50 +0200 Subject: [PATCH 119/223] update StatusPage --- .../Profile/CustomStatus/StatusPage.js | 128 ++++++++++++++---- src/styles/styles.js | 15 ++ 2 files changed, 113 insertions(+), 30 deletions(-) diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.js b/src/pages/settings/Profile/CustomStatus/StatusPage.js index bb696a16a0e2..c6c0ec978770 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.js @@ -1,45 +1,113 @@ -import React from 'react'; +import React, {useMemo, useCallback} from 'react'; import {View} from 'react-native'; -import ScreenWrapper from '../../../../components/ScreenWrapper'; -import HeaderWithBackButton from '../../../../components/HeaderWithBackButton'; -import ROUTES from '../../../../ROUTES'; -import Navigation from '../../../../libs/Navigation/Navigation'; -import styles from '../../../../styles/styles'; -import Text from '../../../../components/Text'; +import {withOnyx} from 'react-native-onyx'; +import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes} from '../../../../components/withCurrentUserPersonalDetails'; import MenuItemWithTopDescription from '../../../../components/MenuItemWithTopDescription'; +import StaticHeaderPageLayout from '../../../../components/StaticHeaderPageLayout'; +import * as Expensicons from '../../../../components/Icon/Expensicons'; +import withLocalize from '../../../../components/withLocalize'; +import MenuItem from '../../../../components/MenuItem'; +import Button from '../../../../components/Button'; +import Text from '../../../../components/Text'; +import Navigation from '../../../../libs/Navigation/Navigation'; +import * as User from '../../../../libs/actions/User'; +import MobileBackgroundImage from '../../../../../assets/images/money-stack.svg'; +import themeColors from '../../../../styles/themes/default'; import useLocalize from '../../../../hooks/useLocalize'; +import styles from '../../../../styles/styles'; +import compose from '../../../../libs/compose'; +import ONYXKEYS from '../../../../ONYXKEYS'; +import ROUTES from '../../../../ROUTES'; -function StatusPage() { +const propTypes = { + ...withCurrentUserPersonalDetailsPropTypes, +}; + +function StatusPage(props) { const localize = useLocalize(); - return ( - - Navigation.goBack(ROUTES.SETTINGS_PROFILE)} - /> + const defaultEmoji = props.draftStatus?.emojiCode || props.currentUserPersonalDetails?.status?.emojiCode || ''; + const defaultText = props.draftStatus?.text || props.currentUserPersonalDetails?.status?.text || ''; + const hasDraftStatus = !!defaultEmoji || !!defaultText; - {localize.translate('statusPage.setStatusTitle')} - {localize.translate('statusPage.statusExplanation')} + const updateStatus = useCallback(() => { + // TODO: part of next PR + const endOfDay = new Date(); + endOfDay.setHours(23, 59, 59, 999); + User.updateCustomStatus({text: defaultText, emojiCode: defaultEmoji, clearAfter: endOfDay}); + + User.clearDraftCustomStatus(); + Navigation.goBack(ROUTES.SETTINGS); + }, [defaultText, defaultEmoji]); + + const clearStatus = () => { + User.clearCustomStatus(); + User.clearDraftCustomStatus(); + }; - - Navigation.navigate(ROUTES.SETTINGS_STATUS_SET)} - /> - Navigation.navigate(ROUTES.SETTINGS_STATUS_CLEAR_AFTER)} + const footerComponent = useMemo( + () => + hasDraftStatus ? ( +