From f49b2606c91feb9b40831b8d5c9e5ef7ff41938e Mon Sep 17 00:00:00 2001 From: Amal Nazeem Date: Mon, 7 Feb 2022 21:13:13 -0500 Subject: [PATCH 001/596] Initial testing for refreshing Expenisfy app --- src/App.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index 677e93ad7a32..4db60277630b 100644 --- a/src/App.js +++ b/src/App.js @@ -34,7 +34,7 @@ const App = () => ( > - + ); From cb3d47e00bf872e34867184dcba0af28219f3134 Mon Sep 17 00:00:00 2001 From: Amal Nazeem Date: Tue, 15 Feb 2022 20:59:33 -0500 Subject: [PATCH 002/596] Add UserFixAccount command --- src/libs/API.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/libs/API.js b/src/libs/API.js index 2158a0505fd7..2be5fe069f4c 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -1226,6 +1226,15 @@ function TransferWalletBalance(parameters) { return Network.post(commandName, parameters); } +/** + * Runs command that fixes user's account and runs migrations retruning whether anything was changed + * @returns {Promise} + */ +function UserFixAccount() { + const commandName = 'UserFixAccount'; + return Network.post(commandName); +} + export { Authenticate, AuthenticateWithAccountID, From 81aecef0cfb5a53ff621e883ea6a0fbfb84356e0 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 21 Feb 2022 19:17:41 +0530 Subject: [PATCH 003/596] feat: Added separate download for native file download --- src/libs/fileDownload/nativeFileDownload.js | 45 +++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/libs/fileDownload/nativeFileDownload.js diff --git a/src/libs/fileDownload/nativeFileDownload.js b/src/libs/fileDownload/nativeFileDownload.js new file mode 100644 index 000000000000..2e3d43c002c0 --- /dev/null +++ b/src/libs/fileDownload/nativeFileDownload.js @@ -0,0 +1,45 @@ +import RNFetchBlob from 'rn-fetch-blob'; +import RNCameraRoll from 'react-native-cameraroll'; +import getPlatform from '../getPlatform'; + +function downloadImage(fileUrl) { + return RNCameraRoll.CameraRoll.save(fileUrl); +} + +function downloadVideo(fileUrl) { + return RNCameraRoll.CameraRoll.save(fileUrl); +} + +function downloadDocument(fileUrl, fileName) { + const dirs = RNFetchBlob.fs.dirs; + + // android files will download to Download directory + // ios files will download to documents directory + const path = getPlatform() === 'android' ? dirs.DownloadDir : dirs.DocumentDir; + + // fetching the attachment + const fetchedAttachment = RNFetchBlob.config({ + fileCache: true, + path: `${path}/${fileName}`, + addAndroidDownloads: { + useDownloadManager: true, + notification: true, + path: `${path}/Expensify/${fileName}`, + }, + }).fetch('GET', fileUrl); + return fetchedAttachment; +} + +function handleFileDownload(fileUrl, fileName) { + const fileType = 'image'; // getFileType(fileUrl); + switch (fileType) { + case 'image': + return downloadImage(fileUrl, fileName); + case 'video': + return downloadVideo(fileUrl, fileName); + default: + return downloadDocument(fileUrl, fileName); + } +} + +export default handleFileDownload; From 9ce92c990628d4ae36a4ad243e1ad51d3c68395e Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 21 Feb 2022 19:17:59 +0530 Subject: [PATCH 004/596] feat: Added "react-native-cameraroll" dependency --- package-lock.json | 5 +++++ package.json | 1 + 2 files changed, 6 insertions(+) diff --git a/package-lock.json b/package-lock.json index 5ed8a4d4a627..6fb09c6ea3ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5345,6 +5345,11 @@ "deep-assign": "^3.0.0" } }, + "@react-native-community/cameraroll": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cameraroll/-/cameraroll-4.1.2.tgz", + "integrity": "sha512-jkdhMByMKD2CZ/5MPeBieYn8vkCfC4MOTouPpBpps3I8N6HUYJk+1JnDdktVYl2WINnqXpQptDA2YptVyifYAg==" + }, "@react-native-community/cli": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-6.2.0.tgz", diff --git a/package.json b/package.json index 28610c6e340d..594f9c400a4d 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@formatjs/intl-pluralrules": "^4.0.13", "@onfido/react-native-sdk": "^2.2.0", "@react-native-async-storage/async-storage": "^1.15.5", + "@react-native-community/cameraroll": "^4.1.2", "@react-native-community/cli": "6.2.0", "@react-native-community/clipboard": "^1.5.1", "@react-native-community/datetimepicker": "^3.5.2", From 9e36c9736b4b1ceea5f749f099cd66a7fa639f7a Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 21 Feb 2022 19:18:26 +0530 Subject: [PATCH 005/596] feat: Replaced old function call with new call --- src/libs/fileDownload/index.native.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/libs/fileDownload/index.native.js b/src/libs/fileDownload/index.native.js index 31690b7bec36..51e5017e74b2 100644 --- a/src/libs/fileDownload/index.native.js +++ b/src/libs/fileDownload/index.native.js @@ -2,6 +2,7 @@ import {Alert, Linking, PermissionsAndroid} from 'react-native'; import RNFetchBlob from 'rn-fetch-blob'; import getPlatform from '../getPlatform'; import getAttachmentName from './getAttachmentName'; +import nativeFileDownload from './nativeFileDownload'; /** * Android permission check to store images @@ -57,23 +58,10 @@ function showAlert(content) { */ function handleDownload(url, fileName) { return new Promise((resolve) => { - const dirs = RNFetchBlob.fs.dirs; - - // android files will download to Download directory - // ios files will download to documents directory - const path = getPlatform() === 'android' ? dirs.DownloadDir : dirs.DocumentDir; const attachmentName = fileName || getAttachmentName(url); // fetching the attachment - const fetchedAttachment = RNFetchBlob.config({ - fileCache: true, - path: `${path}/${attachmentName}`, - addAndroidDownloads: { - useDownloadManager: true, - notification: true, - path: `${path}/Expensify/${attachmentName}`, - }, - }).fetch('GET', url); + const fetchedAttachment = nativeFileDownload(url, attachmentName); // resolving the fetched attachment fetchedAttachment.then((attachment) => { From 905275df19e8161900578ab9e2da45954b6a210f Mon Sep 17 00:00:00 2001 From: Amal Nazeem Date: Mon, 21 Feb 2022 13:45:00 -0500 Subject: [PATCH 006/596] Make UserFixAccount match other User_ functions --- src/libs/API.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/libs/API.js b/src/libs/API.js index da2ee068cc0e..e8f2180841cf 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -847,6 +847,15 @@ function User_UploadAvatar(parameters) { return Network.post(commandName, parameters); } +/** + * Runs command that fixes user's account and runs migrations retruning whether anything was changed + * @returns {Promise} + */ +function User_FixAccount() { + const commandName = 'User_FixAccount'; + return Network.post(commandName); +} + /** * @param {Object} parameters * @param {Number} parameters.accountID @@ -1228,15 +1237,6 @@ function TransferWalletBalance(parameters) { return Network.post(commandName, parameters); } -/** - * Runs command that fixes user's account and runs migrations retruning whether anything was changed - * @returns {Promise} - */ -function UserFixAccount() { - const commandName = 'UserFixAccount'; - return Network.post(commandName); -} - export { Authenticate, AuthenticateWithAccountID, @@ -1293,6 +1293,7 @@ export { User_ReopenAccount, User_SecondaryLogin_Send, User_UploadAvatar, + User_FixAccount, reauthenticate, CreateIOUTransaction, CreateIOUSplit, From fbb30242f8bb47795420fba2902bc6482b5c5c1a Mon Sep 17 00:00:00 2001 From: Amal Nazeem Date: Mon, 21 Feb 2022 13:45:27 -0500 Subject: [PATCH 007/596] Require authToken for User_FixAccount --- src/libs/API.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/API.js b/src/libs/API.js index e8f2180841cf..0db16310733c 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -60,6 +60,7 @@ function isAuthTokenRequired(command) { 'ResendValidateCode', 'ResetPassword', 'User_ReopenAccount', + 'User_FixAccount', 'ValidateEmail', ], command); } From 2e608ef9aeed04456476f3f80b0d3111a946a811 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Mar 2022 12:23:07 -0800 Subject: [PATCH 008/596] Merge branch 'main' of /Users/tgolen/Expensidev/App with conflicts. --- ios/NewExpensify.xcodeproj/project.pbxproj | 4 ++-- package.json | 18 +++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index de0bf9bf28ff..11d1b5e78da6 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -940,7 +940,7 @@ COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 i386"; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -1001,7 +1001,7 @@ COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 i386"; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; diff --git a/package.json b/package.json index 9a8a20acb67b..ff8c50b6a0fb 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,25 @@ { "name": "new.expensify", - "version": "1.1.40-2", + "version": "1.1.39-3", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", "license": "MIT", "private": true, "scripts": { - "postinstall": "scripts/react-native-web.sh && cd desktop && npm install", + "postinstall": "./build/react-native-web.sh", "clean": "react-native clean-project-auto", "android": "npm run check-metro-bundler-port && react-native run-android", "ios": "npm run check-metro-bundler-port && react-native run-ios", "ipad": "npm run check-metro-bundler-port && react-native run-ios --simulator=\"iPad Pro (12.9-inch) (4th generation)\"", "ipad-sm": "npm run check-metro-bundler-port && react-native run-ios --simulator=\"iPad Pro (9.7-inch)\"", + "desktop": "node desktop/start.js", "start": "react-native start", "web": "node web/proxy.js & webpack-dev-server --open --config config/webpack/webpack.dev.js", - "build": "webpack --config config/webpack/webpack.common.js --env.envFile=.env.production", - "build-staging": "webpack --config config/webpack/webpack.common.js --env.envFile=.env.staging", - "desktop": "node desktop/start.js", - "desktop-build": "scripts/build-desktop.sh production", - "desktop-build-staging": "scripts/build-desktop.sh staging", + "build": "webpack --config config/webpack/webpack.prod.js", + "build-staging": "webpack --config config/webpack/webpack.staging.js", + "desktop-build": "webpack --config config/webpack/webpack.prod.js --platform desktop && electron-builder --config config/electron.config.js", + "desktop-build-staging": "webpack --config config/webpack/webpack.staging.js --platform desktop && electron-builder --config config/electron.config.js", "ios-build": "fastlane ios build", "android-build": "fastlane android build", "test": "jest", @@ -61,6 +61,10 @@ "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", "dotenv": "^8.2.0", + "electron-context-menu": "^2.3.0", + "electron-log": "^4.3.5", + "electron-serve": "^1.0.0", + "electron-updater": "^4.3.4", "expensify-common": "git+https://github.com/Expensify/expensify-common.git#f77bb4710c13d01153716df7fb087b637ba3b8bd", "fbjs": "^3.0.2", "file-loader": "^6.0.0", From 9fa7b64b54c978f13aeefabb522bee4970ae5779 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Wed, 9 Mar 2022 22:21:53 +0530 Subject: [PATCH 009/596] fix: added mimetypes db --- package-lock.json | 15 +++++++++++++++ package.json | 1 + 2 files changed, 16 insertions(+) diff --git a/package-lock.json b/package-lock.json index 6d48890f11b4..de24de832d17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36509,6 +36509,21 @@ "resolved": "https://registry.npmjs.org/react-native-keyboard-spacer/-/react-native-keyboard-spacer-0.4.1.tgz", "integrity": "sha1-RvGKMgQyCYol6p+on1FD3SVNMy0=" }, + "react-native-mime-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-native-mime-types/-/react-native-mime-types-2.3.0.tgz", + "integrity": "sha512-9l/kkT1QM0i0xAKkOADkP6jIMhqiS+R/eSKBS/lsUmuMYMqboClyrwTFFZwUcaVIN0SpeH4wOKhYTqCnFgRsEw==", + "requires": { + "mime-db": "~1.37.0" + }, + "dependencies": { + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" + } + } + }, "react-native-modal": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-13.0.0.tgz", diff --git a/package.json b/package.json index f629fce31713..7fd1be824fcc 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "react-native-image-picker": "^4.1.2", "react-native-image-size": "^1.1.3", "react-native-keyboard-spacer": "^0.4.1", + "react-native-mime-types": "^2.3.0", "react-native-modal": "^13.0.0", "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#c5de440706469c3f13e51151b4f57c8c0f00eb03", "react-native-pdf": "^6.2.2", From 78b2c1ef85f48acda49dc5cb7ebd48e4f12341cc Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 09:09:45 +0530 Subject: [PATCH 010/596] fix: split the download functions for android and ios --- src/libs/fileDownload/FileUtils.js | 18 ++++ src/libs/fileDownload/index.android.js | 131 +++++++++++++++++++++++++ src/libs/fileDownload/index.native.js | 101 ++++--------------- 3 files changed, 166 insertions(+), 84 deletions(-) create mode 100644 src/libs/fileDownload/FileUtils.js create mode 100644 src/libs/fileDownload/index.android.js diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js new file mode 100644 index 000000000000..e12fd70dd28c --- /dev/null +++ b/src/libs/fileDownload/FileUtils.js @@ -0,0 +1,18 @@ +import {Alert} from 'react-native'; + +/** + * Re useable alert function + * @param {Object} content + */ +function showAlert(content) { + Alert.alert( + content.title || '', + content.message || '', + content.options || [], + {cancelable: false}, + ); +} + +export default { + showAlert, +}; diff --git a/src/libs/fileDownload/index.android.js b/src/libs/fileDownload/index.android.js new file mode 100644 index 000000000000..6858faf8aac8 --- /dev/null +++ b/src/libs/fileDownload/index.android.js @@ -0,0 +1,131 @@ +import {Linking, PermissionsAndroid} from 'react-native'; +import RNFetchBlob from 'rn-fetch-blob'; +import getAttachmentName from './getAttachmentName'; +import * as FileUtils from './FileUtils'; + +/** + * Android permission check to store images + * @returns{Promise} + */ +function hasAndroidPermission() { + return new Promise((resolve, reject) => { + // read and write permission + const readPermission = PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE; + const writePermission = PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE; + + const writePromise = PermissionsAndroid.check(writePermission); + const readPromise = PermissionsAndroid.check(readPermission); + + Promise.all([writePromise, readPromise]).then(([hasWritePermission, hasReadPermission]) => { + if (hasWritePermission && hasReadPermission) { + resolve(true); // return true if permission is already given + return; + } + + // ask for permission if not given + PermissionsAndroid.requestMultiple([ + readPermission, + writePermission, + ]).then((status) => { + resolve(status['android.permission.READ_EXTERNAL_STORAGE'] === 'granted' + && status['android.permission.WRITE_EXTERNAL_STORAGE'] === 'granted'); + }); + }).catch(error => reject(error)); + }); +} + +/** + * Handling the download + * @param {String} url + * @param {String} fileName + * @returns {Promise} + */ +function handleDownload(url, fileName) { + return new Promise((resolve) => { + const dirs = RNFetchBlob.fs.dirs; + + // android files will download to Download directory + const path = dirs.DownloadDir; + const attachmentName = fileName || getAttachmentName(url); + + // fetching the attachment + const fetchedAttachment = RNFetchBlob.config({ + fileCache: true, + path: `${path}/${attachmentName}`, + addAndroidDownloads: { + useDownloadManager: true, + notification: true, + path: `${path}/Expensify/${attachmentName}`, + }, + }).fetch('GET', url); + + // resolving the fetched attachment + fetchedAttachment.then((attachment) => { + if (!attachment || !attachment.info()) { + return; + } + + FileUtils.showAlert({ + title: 'Downloaded!', + message: 'Attachment successfully downloaded', + options: [ + { + text: 'OK', + style: 'cancel', + }, + ], + }); + return resolve(); + }).catch(() => { + FileUtils.showAlert({ + title: 'Attachment Error', + message: 'Attachment cannot be downloaded', + options: [ + { + text: 'Cancel', + style: 'cancel', + }, + ], + }); + return resolve(); + }); + }); +} + +/** + * Platform specifically check download + * @param {String} url + * @param {String} fileName + * @returns {Promise} fileName + */ +export default function fileDownload(url, fileName) { + return new Promise((resolve) => { + const permissionError = { + title: 'Access Needed', + // eslint-disable-next-line max-len + message: 'NewExpensify does not have access to save attachments. To enable access, tap Settings and allow access.', + options: [ + { + text: 'Cancel', + style: 'cancel', + }, + { + text: 'Settings', + onPress: () => Linking.openSettings(), + }, + ], + }; + + hasAndroidPermission().then((hasPermission) => { + if (hasPermission) { + handleDownload(url, fileName).then(() => resolve()); + } else { + FileUtils.showAlert(permissionError); + } + return resolve(); + }).catch(() => { + FileUtils.showAlert(permissionError); + return resolve(); + }); + }); +} diff --git a/src/libs/fileDownload/index.native.js b/src/libs/fileDownload/index.native.js index eb5578ab1bf4..ef0710a2a168 100644 --- a/src/libs/fileDownload/index.native.js +++ b/src/libs/fileDownload/index.native.js @@ -1,52 +1,6 @@ -import {Alert, Linking, PermissionsAndroid} from 'react-native'; import RNFetchBlob from 'rn-fetch-blob'; -import getPlatform from '../getPlatform'; import getAttachmentName from './getAttachmentName'; -import nativeFileDownload from './nativeFileDownload'; - -/** - * Android permission check to store images - * @returns{Promise} - */ -function hasAndroidPermission() { - return new Promise((resolve, reject) => { - // read and write permission - const readPermission = PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE; - const writePermission = PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE; - - const writePromise = PermissionsAndroid.check(writePermission); - const readPromise = PermissionsAndroid.check(readPermission); - - Promise.all([writePromise, readPromise]).then(([hasWritePermission, hasReadPermission]) => { - if (hasWritePermission && hasReadPermission) { - resolve(true); // return true if permission is already given - return; - } - - // ask for permission if not given - PermissionsAndroid.requestMultiple([ - readPermission, - writePermission, - ]).then((status) => { - resolve(status['android.permission.READ_EXTERNAL_STORAGE'] === 'granted' - && status['android.permission.WRITE_EXTERNAL_STORAGE'] === 'granted'); - }); - }).catch(error => reject(error)); - }); -} - -/** - * Re useable alert function - * @param {Object} content - */ -function showAlert(content) { - Alert.alert( - content.title || '', - content.message || '', - content.options || [], - {cancelable: false}, - ); -} +import * as FileUtils from './FileUtils'; /** * Handling the download @@ -56,10 +10,22 @@ function showAlert(content) { */ function handleDownload(url, fileName) { return new Promise((resolve) => { + const dirs = RNFetchBlob.fs.dirs; + + // ios files will download to documents directory + const path = dirs.DocumentDir; const attachmentName = fileName || getAttachmentName(url); // fetching the attachment - const fetchedAttachment = nativeFileDownload(url, attachmentName); + const fetchedAttachment = RNFetchBlob.config({ + fileCache: true, + path: `${path}/${attachmentName}`, + addAndroidDownloads: { + useDownloadManager: true, + notification: true, + path: `${path}/Expensify/${attachmentName}`, + }, + }).fetch('GET', url); // resolving the fetched attachment fetchedAttachment.then((attachment) => { @@ -67,7 +33,7 @@ function handleDownload(url, fileName) { return; } - showAlert({ + FileUtils.showAlert({ title: 'Downloaded!', message: 'Attachment successfully downloaded', options: [ @@ -79,7 +45,7 @@ function handleDownload(url, fileName) { }); return resolve(); }).catch(() => { - showAlert({ + FileUtils.showAlert({ title: 'Attachment Error', message: 'Attachment cannot be downloaded', options: [ @@ -101,38 +67,5 @@ function handleDownload(url, fileName) { * @returns {Promise} fileName */ export default function fileDownload(url, fileName) { - return new Promise((resolve) => { - const permissionError = { - title: 'Access Needed', - // eslint-disable-next-line max-len - message: 'NewExpensify does not have access to save attachments. To enable access, tap Settings and allow access.', - options: [ - { - text: 'Cancel', - style: 'cancel', - }, - { - text: 'Settings', - onPress: () => Linking.openSettings(), - }, - ], - }; - - // permission check for android - if (getPlatform() === 'android') { - hasAndroidPermission().then((hasPermission) => { - if (hasPermission) { - handleDownload(url, fileName).then(() => resolve()); - } else { - showAlert(permissionError); - } - return resolve(); - }).catch(() => { - showAlert(permissionError); - return resolve(); - }); - } else { - handleDownload(url, fileName).then(() => resolve()); - } - }); + return handleDownload(url, fileName); } From ff6ba29287be97a9cf962f86f2a8c5511b6e8454 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 12:30:01 +0530 Subject: [PATCH 011/596] fix: moved code to FileUtils --- src/libs/fileDownload/FileUtils.js | 16 +++++++++++++++- src/libs/fileDownload/getAttachmentName.js | 13 ------------- 2 files changed, 15 insertions(+), 14 deletions(-) delete mode 100644 src/libs/fileDownload/getAttachmentName.js diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index e12fd70dd28c..b1228c8611c8 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -1,4 +1,5 @@ import {Alert} from 'react-native'; +import moment from 'moment'; /** * Re useable alert function @@ -13,6 +14,19 @@ function showAlert(content) { ); } -export default { +/** + * Generating a random file name with timestamp and file extention + * @param {String} url + * @returns {String} + */ +export default function getAttachmentName(url) { + if (!url) { + return ''; + } + return `${moment().format('DDMMYYYYHHmmss')}.${url.split(/[#?]/)[0].split('.').pop().trim()}`; +} + +export { showAlert, + getAttachmentName, }; diff --git a/src/libs/fileDownload/getAttachmentName.js b/src/libs/fileDownload/getAttachmentName.js deleted file mode 100644 index 54d0e38275b8..000000000000 --- a/src/libs/fileDownload/getAttachmentName.js +++ /dev/null @@ -1,13 +0,0 @@ -import moment from 'moment'; - -/** - * Generating a random file name with timestamp and file extention - * @param {String} url - * @returns {String} - */ -export default function getAttachmentName(url) { - if (!url) { - return ''; - } - return `${moment().format('DDMMYYYYHHmmss')}.${url.split(/[#?]/)[0].split('.').pop().trim()}`; -} From 8f671520162cbde14a096098f0f759d22ccd3d87 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 12:30:17 +0530 Subject: [PATCH 012/596] fix: split android to an independent file --- src/libs/fileDownload/index.android.js | 3 +-- src/libs/fileDownload/{index.native.js => index.ios.js} | 3 +-- src/libs/fileDownload/index.js | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) rename src/libs/fileDownload/{index.native.js => index.ios.js} (94%) diff --git a/src/libs/fileDownload/index.android.js b/src/libs/fileDownload/index.android.js index 6858faf8aac8..d8492dc7ff0b 100644 --- a/src/libs/fileDownload/index.android.js +++ b/src/libs/fileDownload/index.android.js @@ -1,6 +1,5 @@ import {Linking, PermissionsAndroid} from 'react-native'; import RNFetchBlob from 'rn-fetch-blob'; -import getAttachmentName from './getAttachmentName'; import * as FileUtils from './FileUtils'; /** @@ -46,7 +45,7 @@ function handleDownload(url, fileName) { // android files will download to Download directory const path = dirs.DownloadDir; - const attachmentName = fileName || getAttachmentName(url); + const attachmentName = fileName || FileUtils.getAttachmentName(url); // fetching the attachment const fetchedAttachment = RNFetchBlob.config({ diff --git a/src/libs/fileDownload/index.native.js b/src/libs/fileDownload/index.ios.js similarity index 94% rename from src/libs/fileDownload/index.native.js rename to src/libs/fileDownload/index.ios.js index ef0710a2a168..885c093a9932 100644 --- a/src/libs/fileDownload/index.native.js +++ b/src/libs/fileDownload/index.ios.js @@ -1,5 +1,4 @@ import RNFetchBlob from 'rn-fetch-blob'; -import getAttachmentName from './getAttachmentName'; import * as FileUtils from './FileUtils'; /** @@ -14,7 +13,7 @@ function handleDownload(url, fileName) { // ios files will download to documents directory const path = dirs.DocumentDir; - const attachmentName = fileName || getAttachmentName(url); + const attachmentName = fileName || FileUtils.getAttachmentName(url); // fetching the attachment const fetchedAttachment = RNFetchBlob.config({ diff --git a/src/libs/fileDownload/index.js b/src/libs/fileDownload/index.js index 5c7c7a50c991..9e6dc76dbf66 100644 --- a/src/libs/fileDownload/index.js +++ b/src/libs/fileDownload/index.js @@ -1,5 +1,5 @@ import {Linking} from 'react-native'; -import getAttachmentName from './getAttachmentName'; +import * as FileUtils from './FileUtils'; /** * Downloading attachment in web, desktop @@ -25,7 +25,7 @@ export default function fileDownload(url, fileName) { link.style.display = 'none'; link.setAttribute( 'download', - fileName || getAttachmentName(url), // generating the file name + fileName || FileUtils.getAttachmentName(url), // generating the file name ); // Append to html link element page From 1bb1abe40eb667068870e9e340859958e55d30a0 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 12:48:11 +0530 Subject: [PATCH 013/596] fix: moved code from util to index.ios.js and added back alerts --- src/libs/fileDownload/index.ios.js | 78 +++++++++++++-------- src/libs/fileDownload/nativeFileDownload.js | 45 ------------ 2 files changed, 49 insertions(+), 74 deletions(-) delete mode 100644 src/libs/fileDownload/nativeFileDownload.js diff --git a/src/libs/fileDownload/index.ios.js b/src/libs/fileDownload/index.ios.js index 885c093a9932..e1ed8f941c83 100644 --- a/src/libs/fileDownload/index.ios.js +++ b/src/libs/fileDownload/index.ios.js @@ -1,33 +1,63 @@ import RNFetchBlob from 'rn-fetch-blob'; +import RNCameraRoll from 'react-native-cameraroll'; import * as FileUtils from './FileUtils'; /** * Handling the download - * @param {String} url + * @param {String} fileUrl * @param {String} fileName * @returns {Promise} */ -function handleDownload(url, fileName) { - return new Promise((resolve) => { - const dirs = RNFetchBlob.fs.dirs; +function downloadDocument(fileUrl, fileName) { + const dirs = RNFetchBlob.fs.dirs; - // ios files will download to documents directory - const path = dirs.DocumentDir; - const attachmentName = fileName || FileUtils.getAttachmentName(url); + // ios files will download to documents directory + const path = dirs.DocumentDir; - // fetching the attachment - const fetchedAttachment = RNFetchBlob.config({ - fileCache: true, - path: `${path}/${attachmentName}`, - addAndroidDownloads: { - useDownloadManager: true, - notification: true, - path: `${path}/Expensify/${attachmentName}`, - }, - }).fetch('GET', url); + // fetching the attachment + const fetchedAttachment = RNFetchBlob.config({ + fileCache: true, + path: `${path}/${fileName}`, + addAndroidDownloads: { + useDownloadManager: true, + notification: true, + path: `${path}/Expensify/${fileName}`, + }, + }).fetch('GET', fileUrl); + return fetchedAttachment; +} - // resolving the fetched attachment - fetchedAttachment.then((attachment) => { +function downloadImage(fileUrl) { + return RNCameraRoll.CameraRoll.save(fileUrl); +} + +function downloadVideo(fileUrl) { + return RNCameraRoll.CameraRoll.save(fileUrl); +} + +/** + * File type based download for iOS + * @param {String} fileUrl + * @param {String} fileName + * @returns {Promise} + */ +export default function fileDownload(fileUrl, fileName) { + return new Promise((resolve) => { + let fileDownloadPromise = null; + const fileType = 'image'; // getFileType(fileUrl); + switch (fileType) { + case 'image': + fileDownloadPromise = downloadImage(fileUrl, fileName); + break; + case 'video': + fileDownloadPromise = downloadVideo(fileUrl, fileName); + break; + default: + fileDownloadPromise = downloadDocument(fileUrl, fileName); + break; + } + + fileDownloadPromise.then((attachment) => { if (!attachment || !attachment.info()) { return; } @@ -58,13 +88,3 @@ function handleDownload(url, fileName) { }); }); } - -/** - * Platform specifically check download - * @param {String} url - * @param {String} fileName - * @returns {Promise} fileName - */ -export default function fileDownload(url, fileName) { - return handleDownload(url, fileName); -} diff --git a/src/libs/fileDownload/nativeFileDownload.js b/src/libs/fileDownload/nativeFileDownload.js deleted file mode 100644 index 2e3d43c002c0..000000000000 --- a/src/libs/fileDownload/nativeFileDownload.js +++ /dev/null @@ -1,45 +0,0 @@ -import RNFetchBlob from 'rn-fetch-blob'; -import RNCameraRoll from 'react-native-cameraroll'; -import getPlatform from '../getPlatform'; - -function downloadImage(fileUrl) { - return RNCameraRoll.CameraRoll.save(fileUrl); -} - -function downloadVideo(fileUrl) { - return RNCameraRoll.CameraRoll.save(fileUrl); -} - -function downloadDocument(fileUrl, fileName) { - const dirs = RNFetchBlob.fs.dirs; - - // android files will download to Download directory - // ios files will download to documents directory - const path = getPlatform() === 'android' ? dirs.DownloadDir : dirs.DocumentDir; - - // fetching the attachment - const fetchedAttachment = RNFetchBlob.config({ - fileCache: true, - path: `${path}/${fileName}`, - addAndroidDownloads: { - useDownloadManager: true, - notification: true, - path: `${path}/Expensify/${fileName}`, - }, - }).fetch('GET', fileUrl); - return fetchedAttachment; -} - -function handleFileDownload(fileUrl, fileName) { - const fileType = 'image'; // getFileType(fileUrl); - switch (fileType) { - case 'image': - return downloadImage(fileUrl, fileName); - case 'video': - return downloadVideo(fileUrl, fileName); - default: - return downloadDocument(fileUrl, fileName); - } -} - -export default handleFileDownload; From 86f9a625ad3b9d5ac8d7bd4ddd34d61508e05935 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 12:49:30 +0530 Subject: [PATCH 014/596] fix: corrected import --- src/libs/fileDownload/index.ios.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/fileDownload/index.ios.js b/src/libs/fileDownload/index.ios.js index e1ed8f941c83..4bf1e0d847e9 100644 --- a/src/libs/fileDownload/index.ios.js +++ b/src/libs/fileDownload/index.ios.js @@ -1,5 +1,5 @@ import RNFetchBlob from 'rn-fetch-blob'; -import RNCameraRoll from 'react-native-cameraroll'; +import RNCameraRoll from '@react-native-community/cameraroll'; import * as FileUtils from './FileUtils'; /** From f23b626da4822982dd0ca557f1b5c0344ca45d88 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 12:55:51 +0530 Subject: [PATCH 015/596] fix: addd file type handling --- src/libs/fileDownload/FileUtils.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index b1228c8611c8..944bd731fa1a 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -1,5 +1,7 @@ import {Alert} from 'react-native'; import moment from 'moment'; +import * as mime from 'react-native-mime-types'; +import CONST from '../../CONST'; /** * Re useable alert function @@ -19,14 +21,35 @@ function showAlert(content) { * @param {String} url * @returns {String} */ -export default function getAttachmentName(url) { +function getAttachmentName(url) { if (!url) { return ''; } return `${moment().format('DDMMYYYYHHmmss')}.${url.split(/[#?]/)[0].split('.').pop().trim()}`; } +/** + * Returns file type based on the filename/uri + * @param {String} fileName + * @returns {String} + */ + +function getFileType(fileName) { + if (!fileName) { + return; + } + const contentType = mime.contentType(fileName); + if (contentType.startsWith('image')) { + return CONST.ATTACHMENT_FILE_TYPE.IMAGE; + } + if (contentType.startsWith('video')) { + return CONST.ATTACHMENT_FILE_TYPE.VIDEO; + } + return CONST.ATTACHMENT_FILE_TYPE.FILE; +} + export { showAlert, getAttachmentName, + getFileType, }; From 709aaae921755333f6dfcd4caa6c4797d5a4b0ac Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 12:56:33 +0530 Subject: [PATCH 016/596] fix: updated attachment name null handling --- src/libs/fileDownload/index.ios.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/libs/fileDownload/index.ios.js b/src/libs/fileDownload/index.ios.js index 4bf1e0d847e9..188a55fd60cc 100644 --- a/src/libs/fileDownload/index.ios.js +++ b/src/libs/fileDownload/index.ios.js @@ -8,7 +8,7 @@ import * as FileUtils from './FileUtils'; * @param {String} fileName * @returns {Promise} */ -function downloadDocument(fileUrl, fileName) { +function downloadFile(fileUrl, fileName) { const dirs = RNFetchBlob.fs.dirs; // ios files will download to documents directory @@ -44,16 +44,18 @@ function downloadVideo(fileUrl) { export default function fileDownload(fileUrl, fileName) { return new Promise((resolve) => { let fileDownloadPromise = null; - const fileType = 'image'; // getFileType(fileUrl); + const fileType = FileUtils.getFileType(fileUrl); + const attachmentName = fileName || FileUtils.getAttachmentName(fileUrl); + switch (fileType) { case 'image': - fileDownloadPromise = downloadImage(fileUrl, fileName); + fileDownloadPromise = downloadImage(fileUrl, attachmentName); break; case 'video': - fileDownloadPromise = downloadVideo(fileUrl, fileName); + fileDownloadPromise = downloadVideo(fileUrl, attachmentName); break; default: - fileDownloadPromise = downloadDocument(fileUrl, fileName); + fileDownloadPromise = downloadFile(fileUrl, attachmentName); break; } From 1074f053c961210601d24a408010fa0c74b5428c Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 12:56:43 +0530 Subject: [PATCH 017/596] fix: file types in const --- src/CONST.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/CONST.js b/src/CONST.js index 67896a5f7adc..0dffe8aba17e 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -366,6 +366,12 @@ const CONST = { IMAGE: 'image', }, + ATTACHMENT_FILE_TYPE: { + FILE: 'file', + IMAGE: 'image', + VIDEO: 'video', + }, + ADD_PAYMENT_MENU_POSITION_Y: 226, ADD_PAYMENT_MENU_POSITION_X: 356, EMOJI_PICKER_SIZE: { From aa622c2f39e53ae64cacf019252da172e6592b6d Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 13:07:33 +0530 Subject: [PATCH 018/596] fix: fixed uri content typed --- src/libs/fileDownload/FileUtils.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index 944bd731fa1a..e7ea9163e906 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -29,15 +29,16 @@ function getAttachmentName(url) { } /** - * Returns file type based on the filename/uri - * @param {String} fileName + * Returns file type based on the uri + * @param {String} fileUri * @returns {String} */ -function getFileType(fileName) { - if (!fileName) { +function getFileType(fileUrl) { + if (!fileUrl) { return; } + const fileName = fileUrl.split('/').pop().split('?')[0].split('#')[0]; const contentType = mime.contentType(fileName); if (contentType.startsWith('image')) { return CONST.ATTACHMENT_FILE_TYPE.IMAGE; From 35974d5a21c878a907612b0f106a1562f671d546 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 13:07:42 +0530 Subject: [PATCH 019/596] fix: updated references for class --- src/libs/fileDownload/index.ios.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libs/fileDownload/index.ios.js b/src/libs/fileDownload/index.ios.js index 188a55fd60cc..184eb453b39a 100644 --- a/src/libs/fileDownload/index.ios.js +++ b/src/libs/fileDownload/index.ios.js @@ -1,6 +1,7 @@ import RNFetchBlob from 'rn-fetch-blob'; -import RNCameraRoll from '@react-native-community/cameraroll'; +import CameraRoll from '@react-native-community/cameraroll'; import * as FileUtils from './FileUtils'; +import CONST from '../../CONST'; /** * Handling the download @@ -28,11 +29,11 @@ function downloadFile(fileUrl, fileName) { } function downloadImage(fileUrl) { - return RNCameraRoll.CameraRoll.save(fileUrl); + return CameraRoll.save(fileUrl); } function downloadVideo(fileUrl) { - return RNCameraRoll.CameraRoll.save(fileUrl); + return CameraRoll.save(fileUrl); } /** @@ -48,10 +49,10 @@ export default function fileDownload(fileUrl, fileName) { const attachmentName = fileName || FileUtils.getAttachmentName(fileUrl); switch (fileType) { - case 'image': + case CONST.ATTACHMENT_FILE_TYPE.IMAGE: fileDownloadPromise = downloadImage(fileUrl, attachmentName); break; - case 'video': + case CONST.ATTACHMENT_FILE_TYPE.VIDEO: fileDownloadPromise = downloadVideo(fileUrl, attachmentName); break; default: From 24286ef15fc0b8a09f9cb37dd990a74b8aaf9cb4 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 13:11:15 +0530 Subject: [PATCH 020/596] fix: removed .info call for ios --- src/libs/fileDownload/index.ios.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/fileDownload/index.ios.js b/src/libs/fileDownload/index.ios.js index 184eb453b39a..044e4ab4134e 100644 --- a/src/libs/fileDownload/index.ios.js +++ b/src/libs/fileDownload/index.ios.js @@ -61,7 +61,7 @@ export default function fileDownload(fileUrl, fileName) { } fileDownloadPromise.then((attachment) => { - if (!attachment || !attachment.info()) { + if (!attachment) { return; } From a69922406bf67eb07e284736e63544dbca2376ac Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 17:35:57 +0530 Subject: [PATCH 021/596] fix: added remote video save --- src/libs/fileDownload/index.ios.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/libs/fileDownload/index.ios.js b/src/libs/fileDownload/index.ios.js index 044e4ab4134e..88540cf4b568 100644 --- a/src/libs/fileDownload/index.ios.js +++ b/src/libs/fileDownload/index.ios.js @@ -1,5 +1,6 @@ import RNFetchBlob from 'rn-fetch-blob'; import CameraRoll from '@react-native-community/cameraroll'; +import lodashGet from 'lodash/get'; import * as FileUtils from './FileUtils'; import CONST from '../../CONST'; @@ -32,8 +33,18 @@ function downloadImage(fileUrl) { return CameraRoll.save(fileUrl); } -function downloadVideo(fileUrl) { - return CameraRoll.save(fileUrl); +function downloadVideo(fileUrl, fileName) { + return new Promise((resolve) => { + let documentPathUri = null; + let cameraRollUri = null; + downloadFile(fileUrl, fileName).then((attachment) => { + documentPathUri = lodashGet(attachment, 'data'); + return CameraRoll.save(documentPathUri); + }).then((attachment) => { + cameraRollUri = attachment; + return RNFetchBlob.fs.unlink(documentPathUri); + }).then(() => resolve(cameraRollUri)); + }); } /** From c0db396a2da52f99f732f7288c5d15a7677ff69d Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 17:49:33 +0530 Subject: [PATCH 022/596] fix: photo lib access error --- src/CONST.js | 2 ++ src/libs/fileDownload/index.ios.js | 41 ++++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 0dffe8aba17e..d7e39858d497 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -372,6 +372,8 @@ const CONST = { VIDEO: 'video', }, + IOS_CAMERAROLL_ACCESS_ERROR: 'Access to photo library was denied', + ADD_PAYMENT_MENU_POSITION_Y: 226, ADD_PAYMENT_MENU_POSITION_X: 356, EMOJI_PICKER_SIZE: { diff --git a/src/libs/fileDownload/index.ios.js b/src/libs/fileDownload/index.ios.js index 88540cf4b568..957911273c97 100644 --- a/src/libs/fileDownload/index.ios.js +++ b/src/libs/fileDownload/index.ios.js @@ -1,3 +1,4 @@ +import {Linking} from 'react-native'; import RNFetchBlob from 'rn-fetch-blob'; import CameraRoll from '@react-native-community/cameraroll'; import lodashGet from 'lodash/get'; @@ -87,17 +88,35 @@ export default function fileDownload(fileUrl, fileName) { ], }); return resolve(); - }).catch(() => { - FileUtils.showAlert({ - title: 'Attachment Error', - message: 'Attachment cannot be downloaded', - options: [ - { - text: 'Cancel', - style: 'cancel', - }, - ], - }); + }).catch((err) => { + if (err.message === CONST.IOS_CAMERAROLL_ACCESS_ERROR) { + FileUtils.showAlert({ + title: 'Access Needed', + // eslint-disable-next-line max-len + message: 'NewExpensify does not have access to save attachments. To enable access, tap Settings and allow access.', + options: [ + { + text: 'Cancel', + style: 'cancel', + }, + { + text: 'Settings', + onPress: () => Linking.openSettings(), + }, + ], + }); + } else { + FileUtils.showAlert({ + title: 'Attachment Error', + message: 'Attachment cannot be downloaded', + options: [ + { + text: 'Cancel', + style: 'cancel', + }, + ], + }); + } return resolve(); }); }); From bf91492d2f73fd455edfdef77505f9c4362f37f6 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 17:59:33 +0530 Subject: [PATCH 023/596] fix: error messages clean up --- src/libs/fileDownload/FileUtils.js | 41 +++++++++++++++++++++++- src/libs/fileDownload/index.android.js | 44 +++----------------------- src/libs/fileDownload/index.ios.js | 39 ++--------------------- 3 files changed, 48 insertions(+), 76 deletions(-) diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index e7ea9163e906..fed214ba471d 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -1,4 +1,4 @@ -import {Alert} from 'react-native'; +import {Alert, Linking} from 'react-native'; import moment from 'moment'; import * as mime from 'react-native-mime-types'; import CONST from '../../CONST'; @@ -49,8 +49,47 @@ function getFileType(fileUrl) { return CONST.ATTACHMENT_FILE_TYPE.FILE; } +const ALERT_TYPES = { + SUCCESS: { + title: 'Downloaded!', + message: 'Attachment successfully downloaded', + options: [ + { + text: 'OK', + style: 'cancel', + }, + ], + }, + GENERAL_ERROR: { + title: 'Attachment Error', + message: 'Attachment cannot be downloaded', + options: [ + { + text: 'Cancel', + style: 'cancel', + }, + ], + }, + PERMISSION_ERROR: { + title: 'Access Needed', + // eslint-disable-next-line max-len + message: 'NewExpensify does not have access to save attachments. To enable access, tap Settings and allow access.', + options: [ + { + text: 'Cancel', + style: 'cancel', + }, + { + text: 'Settings', + onPress: () => Linking.openSettings(), + }, + ], + }, +}; + export { showAlert, getAttachmentName, getFileType, + ALERT_TYPES, }; diff --git a/src/libs/fileDownload/index.android.js b/src/libs/fileDownload/index.android.js index d8492dc7ff0b..0aeeda47631e 100644 --- a/src/libs/fileDownload/index.android.js +++ b/src/libs/fileDownload/index.android.js @@ -1,4 +1,4 @@ -import {Linking, PermissionsAndroid} from 'react-native'; +import {PermissionsAndroid} from 'react-native'; import RNFetchBlob from 'rn-fetch-blob'; import * as FileUtils from './FileUtils'; @@ -64,28 +64,10 @@ function handleDownload(url, fileName) { return; } - FileUtils.showAlert({ - title: 'Downloaded!', - message: 'Attachment successfully downloaded', - options: [ - { - text: 'OK', - style: 'cancel', - }, - ], - }); + FileUtils.showAlert(FileUtils.ALERT_TYPES.SUCCESS); return resolve(); }).catch(() => { - FileUtils.showAlert({ - title: 'Attachment Error', - message: 'Attachment cannot be downloaded', - options: [ - { - text: 'Cancel', - style: 'cancel', - }, - ], - }); + FileUtils.showAlert(FileUtils.ALERT_TYPES.GENERAL_ERROR); return resolve(); }); }); @@ -99,31 +81,15 @@ function handleDownload(url, fileName) { */ export default function fileDownload(url, fileName) { return new Promise((resolve) => { - const permissionError = { - title: 'Access Needed', - // eslint-disable-next-line max-len - message: 'NewExpensify does not have access to save attachments. To enable access, tap Settings and allow access.', - options: [ - { - text: 'Cancel', - style: 'cancel', - }, - { - text: 'Settings', - onPress: () => Linking.openSettings(), - }, - ], - }; - hasAndroidPermission().then((hasPermission) => { if (hasPermission) { handleDownload(url, fileName).then(() => resolve()); } else { - FileUtils.showAlert(permissionError); + FileUtils.showAlert(FileUtils.ALERT_TYPES.PERMISSION_ERROR); } return resolve(); }).catch(() => { - FileUtils.showAlert(permissionError); + FileUtils.showAlert(FileUtils.ALERT_TYPES.PERMISSION_ERROR); return resolve(); }); }); diff --git a/src/libs/fileDownload/index.ios.js b/src/libs/fileDownload/index.ios.js index 957911273c97..c198e4be1ad8 100644 --- a/src/libs/fileDownload/index.ios.js +++ b/src/libs/fileDownload/index.ios.js @@ -1,4 +1,3 @@ -import {Linking} from 'react-native'; import RNFetchBlob from 'rn-fetch-blob'; import CameraRoll from '@react-native-community/cameraroll'; import lodashGet from 'lodash/get'; @@ -77,45 +76,13 @@ export default function fileDownload(fileUrl, fileName) { return; } - FileUtils.showAlert({ - title: 'Downloaded!', - message: 'Attachment successfully downloaded', - options: [ - { - text: 'OK', - style: 'cancel', - }, - ], - }); + FileUtils.showAlert(FileUtils.ALERT_TYPES.SUCCESS); return resolve(); }).catch((err) => { if (err.message === CONST.IOS_CAMERAROLL_ACCESS_ERROR) { - FileUtils.showAlert({ - title: 'Access Needed', - // eslint-disable-next-line max-len - message: 'NewExpensify does not have access to save attachments. To enable access, tap Settings and allow access.', - options: [ - { - text: 'Cancel', - style: 'cancel', - }, - { - text: 'Settings', - onPress: () => Linking.openSettings(), - }, - ], - }); + FileUtils.showAlert(FileUtils.ALERT_TYPES.PERMISSION_ERROR); } else { - FileUtils.showAlert({ - title: 'Attachment Error', - message: 'Attachment cannot be downloaded', - options: [ - { - text: 'Cancel', - style: 'cancel', - }, - ], - }); + FileUtils.showAlert(FileUtils.ALERT_TYPES.GENERAL_ERROR); } return resolve(); }); From 8dd1eaffc416250b3fc8cc30913d7462839574a3 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 18:10:00 +0530 Subject: [PATCH 024/596] fix: localized strings for alerts --- src/languages/en.js | 32 ++++++++++++++++++++++++++++++ src/languages/es.js | 32 ++++++++++++++++++++++++++++++ src/libs/fileDownload/FileUtils.js | 22 ++++++++++---------- 3 files changed, 75 insertions(+), 11 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 56aab993560f..a63507eea2cf 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -945,4 +945,36 @@ export default { }, refresh: 'Refresh', }, + fileDownload: { + success: { + title: 'Downloaded!', + message: 'Attachment successfully downloaded', + buttons: { + ok: { + text: 'OK', + }, + }, + }, + generalError: { + title: 'Attachment Error', + message: 'Attachment cannot be downloaded', + buttons: { + cancel: { + text: 'Cancel', + }, + }, + }, + permissionError: { + title: 'Access Needed', + message: 'NewExpensify does not have access to save attachments. To enable access, tap Settings and allow access.', + options: { + cancel: { + text: 'Cancel', + }, + settings: { + text: 'Settings', + }, + }, + }, + }, }; diff --git a/src/languages/es.js b/src/languages/es.js index d6b40f3495c5..4be85f22cacc 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -947,4 +947,36 @@ export default { }, refresh: 'Refresh', }, + fileDownload: { + success: { + title: 'Descargado!', + message: 'Adjunto descargado con éxito', + buttons: { + ok: { + text: 'OK', + }, + }, + }, + generalError: { + title: 'Error de archivo adjunto', + message: 'No se puede descargar el archivo adjunto', + buttons: { + cancel: { + text: 'Cancelar', + }, + }, + }, + permissionError: { + title: 'Acceso necesario', + message: 'NewExpensify no tiene acceso para guardar archivos adjuntos. Para habilitar el acceso, toque Configuración y permita el acceso.', + buttons: { + cancel: { + text: 'Cancelar', + }, + settings: { + text: 'Ajustes', + }, + }, + }, + }, }; diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index fed214ba471d..45056d03e34a 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -2,6 +2,7 @@ import {Alert, Linking} from 'react-native'; import moment from 'moment'; import * as mime from 'react-native-mime-types'; import CONST from '../../CONST'; +import * as Localize from '../Localize'; /** * Re useable alert function @@ -51,36 +52,35 @@ function getFileType(fileUrl) { const ALERT_TYPES = { SUCCESS: { - title: 'Downloaded!', - message: 'Attachment successfully downloaded', + title: Localize.translateLocal('fileDownload.success.title'), + message: Localize.translateLocal('fileDownload.success.message'), options: [ { - text: 'OK', + text: Localize.translateLocal('fileDownload.success.buttons.ok.text'), style: 'cancel', }, ], }, GENERAL_ERROR: { - title: 'Attachment Error', - message: 'Attachment cannot be downloaded', + title: Localize.translateLocal('fileDownload.generalError.title'), + message: Localize.translateLocal('fileDownload.generalError.message'), options: [ { - text: 'Cancel', + text: Localize.translateLocal('fileDownload.generalError.buttons.cancel.text'), style: 'cancel', }, ], }, PERMISSION_ERROR: { - title: 'Access Needed', - // eslint-disable-next-line max-len - message: 'NewExpensify does not have access to save attachments. To enable access, tap Settings and allow access.', + title: Localize.translateLocal('fileDownload.permissionError.title'), + message: Localize.translateLocal('fileDownload.permissionError.message'), options: [ { - text: 'Cancel', + text: Localize.translateLocal('fileDownload.permissionError.buttons.cancel.text'), style: 'cancel', }, { - text: 'Settings', + text: Localize.translateLocal('fileDownload.permissionError.buttons.settings.text'), onPress: () => Linking.openSettings(), }, ], From c93d8097e74ba95143871c79e647c85cf502c180 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 18:17:01 +0530 Subject: [PATCH 025/596] fix: updated jsdocs --- src/libs/fileDownload/index.android.js | 4 ++-- src/libs/fileDownload/index.ios.js | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/libs/fileDownload/index.android.js b/src/libs/fileDownload/index.android.js index 0aeeda47631e..d941db7bcb72 100644 --- a/src/libs/fileDownload/index.android.js +++ b/src/libs/fileDownload/index.android.js @@ -74,10 +74,10 @@ function handleDownload(url, fileName) { } /** - * Platform specifically check download + * Checks permission and downloads the file for Android * @param {String} url * @param {String} fileName - * @returns {Promise} fileName + * @returns {Promise} */ export default function fileDownload(url, fileName) { return new Promise((resolve) => { diff --git a/src/libs/fileDownload/index.ios.js b/src/libs/fileDownload/index.ios.js index c198e4be1ad8..ce5e472d768d 100644 --- a/src/libs/fileDownload/index.ios.js +++ b/src/libs/fileDownload/index.ios.js @@ -5,7 +5,7 @@ import * as FileUtils from './FileUtils'; import CONST from '../../CONST'; /** - * Handling the download + * Downloads the file to Documents section in iOS * @param {String} fileUrl * @param {String} fileName * @returns {Promise} @@ -29,14 +29,29 @@ function downloadFile(fileUrl, fileName) { return fetchedAttachment; } +/** + * Downloads the image to photo lib in iOS + * @param {String} fileUrl + * @param {String} fileName + * @returns {String} + */ function downloadImage(fileUrl) { return CameraRoll.save(fileUrl); } +/** + * Downloads the video to photo lib in iOS + * @param {String} fileUrl + * @param {String} fileName + * @returns {String} + */ function downloadVideo(fileUrl, fileName) { return new Promise((resolve) => { let documentPathUri = null; let cameraRollUri = null; + + // Because CameraRoll doesn't allow remote URIs to save the video, + // we first download to documents, then copy to photo lib and unlink the original file, downloadFile(fileUrl, fileName).then((attachment) => { documentPathUri = lodashGet(attachment, 'data'); return CameraRoll.save(documentPathUri); @@ -48,7 +63,7 @@ function downloadVideo(fileUrl, fileName) { } /** - * File type based download for iOS + * Download the file based on type(image, video, other file types)for iOS * @param {String} fileUrl * @param {String} fileName * @returns {Promise} From 75243228d028804982bbdf6c16b5c8c915249af0 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 18:19:17 +0530 Subject: [PATCH 026/596] fix: corrected translation path --- src/languages/en.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en.js b/src/languages/en.js index a63507eea2cf..30d8c992f8bc 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -967,7 +967,7 @@ export default { permissionError: { title: 'Access Needed', message: 'NewExpensify does not have access to save attachments. To enable access, tap Settings and allow access.', - options: { + buttons: { cancel: { text: 'Cancel', }, From ac2bbb40f18b5d89b78957278d887364a80b1ec4 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 14 Mar 2022 18:31:41 +0530 Subject: [PATCH 027/596] refactor: split alerts to individual functions --- src/libs/fileDownload/FileUtils.js | 94 ++++++++++++++------------ src/libs/fileDownload/index.android.js | 8 +-- src/libs/fileDownload/index.ios.js | 6 +- 3 files changed, 56 insertions(+), 52 deletions(-) diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index 45056d03e34a..9edebdd6f59a 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -5,18 +5,58 @@ import CONST from '../../CONST'; import * as Localize from '../Localize'; /** - * Re useable alert function - * @param {Object} content + * Show alert on successful attachment download */ -function showAlert(content) { +function showSuccessAlert() { Alert.alert( - content.title || '', - content.message || '', - content.options || [], + Localize.translateLocal('fileDownload.success.title'), + Localize.translateLocal('fileDownload.success.message'), + [ + { + text: Localize.translateLocal('fileDownload.success.buttons.ok.text'), + style: 'cancel', + }, + ], {cancelable: false}, ); } +/** + * Show alert on attachment download error + */ +function showGeneralErrorAlert() { + Alert.alert( + Localize.translateLocal('fileDownload.generalError.title'), + Localize.translateLocal('fileDownload.generalError.message'), + [ + { + text: Localize.translateLocal('fileDownload.generalError.buttons.cancel.text'), + style: 'cancel', + }, + ], + ); +} + +/** + * Show alert on attachment download permissions error + */ +function showPermissionErrorAlert() { + Alert.alert( + Localize.translateLocal('fileDownload.permissionError.title'), + Localize.translateLocal('fileDownload.permissionError.message'), + [ + { + text: Localize.translateLocal('fileDownload.permissionError.buttons.cancel.text'), + style: 'cancel', + }, + { + text: Localize.translateLocal('fileDownload.permissionError.buttons.settings.text'), + onPress: () => Linking.openSettings(), + }, + ], + ); +} + /** * Generating a random file name with timestamp and file extention * @param {String} url @@ -50,46 +90,10 @@ function getFileType(fileUrl) { return CONST.ATTACHMENT_FILE_TYPE.FILE; } -const ALERT_TYPES = { - SUCCESS: { - title: Localize.translateLocal('fileDownload.success.title'), - message: Localize.translateLocal('fileDownload.success.message'), - options: [ - { - text: Localize.translateLocal('fileDownload.success.buttons.ok.text'), - style: 'cancel', - }, - ], - }, - GENERAL_ERROR: { - title: Localize.translateLocal('fileDownload.generalError.title'), - message: Localize.translateLocal('fileDownload.generalError.message'), - options: [ - { - text: Localize.translateLocal('fileDownload.generalError.buttons.cancel.text'), - style: 'cancel', - }, - ], - }, - PERMISSION_ERROR: { - title: Localize.translateLocal('fileDownload.permissionError.title'), - message: Localize.translateLocal('fileDownload.permissionError.message'), - options: [ - { - text: Localize.translateLocal('fileDownload.permissionError.buttons.cancel.text'), - style: 'cancel', - }, - { - text: Localize.translateLocal('fileDownload.permissionError.buttons.settings.text'), - onPress: () => Linking.openSettings(), - }, - ], - }, -}; - export { - showAlert, + showGeneralErrorAlert, + showSuccessAlert, + showPermissionErrorAlert, getAttachmentName, getFileType, - ALERT_TYPES, }; diff --git a/src/libs/fileDownload/index.android.js b/src/libs/fileDownload/index.android.js index d941db7bcb72..7e7e7f77db37 100644 --- a/src/libs/fileDownload/index.android.js +++ b/src/libs/fileDownload/index.android.js @@ -64,10 +64,10 @@ function handleDownload(url, fileName) { return; } - FileUtils.showAlert(FileUtils.ALERT_TYPES.SUCCESS); + FileUtils.showSuccessAlert(); return resolve(); }).catch(() => { - FileUtils.showAlert(FileUtils.ALERT_TYPES.GENERAL_ERROR); + FileUtils.showGeneralErrorAlert(); return resolve(); }); }); @@ -85,11 +85,11 @@ export default function fileDownload(url, fileName) { if (hasPermission) { handleDownload(url, fileName).then(() => resolve()); } else { - FileUtils.showAlert(FileUtils.ALERT_TYPES.PERMISSION_ERROR); + FileUtils.showPermissionErrorAlert(); } return resolve(); }).catch(() => { - FileUtils.showAlert(FileUtils.ALERT_TYPES.PERMISSION_ERROR); + FileUtils.showPermissionErrorAlert(); return resolve(); }); }); diff --git a/src/libs/fileDownload/index.ios.js b/src/libs/fileDownload/index.ios.js index ce5e472d768d..a5aa94b72691 100644 --- a/src/libs/fileDownload/index.ios.js +++ b/src/libs/fileDownload/index.ios.js @@ -91,13 +91,13 @@ export default function fileDownload(fileUrl, fileName) { return; } - FileUtils.showAlert(FileUtils.ALERT_TYPES.SUCCESS); + FileUtils.showSuccessAlert(); return resolve(); }).catch((err) => { if (err.message === CONST.IOS_CAMERAROLL_ACCESS_ERROR) { - FileUtils.showAlert(FileUtils.ALERT_TYPES.PERMISSION_ERROR); + FileUtils.showPermissionErrorAlert(); } else { - FileUtils.showAlert(FileUtils.ALERT_TYPES.GENERAL_ERROR); + FileUtils.showGeneralErrorAlert(); } return resolve(); }); From 6caff3ca9a49498cda995c14096f5202c1aa3bf3 Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Mon, 21 Mar 2022 13:02:39 -0600 Subject: [PATCH 028/596] convert to class, get ref, add styles --- .../Tooltip/TooltipRenderedOnPageBody.js | 90 ++++++++++++------- src/styles/getTooltipStyles.js | 6 ++ 2 files changed, 63 insertions(+), 33 deletions(-) diff --git a/src/components/Tooltip/TooltipRenderedOnPageBody.js b/src/components/Tooltip/TooltipRenderedOnPageBody.js index 3ed7c472883b..1867af355478 100644 --- a/src/components/Tooltip/TooltipRenderedOnPageBody.js +++ b/src/components/Tooltip/TooltipRenderedOnPageBody.js @@ -51,39 +51,63 @@ const propTypes = { const defaultProps = {}; -const TooltipRenderedOnPageBody = (props) => { - const { - animationStyle, - tooltipWrapperStyle, - tooltipTextStyle, - pointerWrapperStyle, - pointerStyle, - } = getTooltipStyles( - props.animation, - props.windowWidth, - props.xOffset, - props.yOffset, - props.wrapperWidth, - props.wrapperHeight, - props.tooltipWidth, - props.tooltipHeight, - props.shiftHorizontal, - props.shiftVertical, - ); - return ReactDOM.createPortal( - - {props.text} - - - - , - document.querySelector('body'), - ); -}; +// ! 1 +// {wordsToShow.map(word => {word + " "})} +// ! 2 +// {wordsToShow.join(' ')} + +class TooltipRenderedOnPageBody extends React.Component { + + constructor(props) { + super(props); + } + state = { + tooltipTextWidth: 300, + } + + resizeTooltip(el) { + console.log(el); + } + + render() { + const { + animationStyle, + tooltipWrapperStyle, + tooltipTextStyle, + pointerWrapperStyle, + pointerStyle, + } = getTooltipStyles( + this.props.animation, + this.props.windowWidth, + this.props.xOffset, + this.props.yOffset, + this.props.wrapperWidth, + this.props.wrapperHeight, + this.props.tooltipWidth, + this.props.tooltipHeight, + this.props.shiftHorizontal, + this.props.shiftVertical, + ); + const maximumWords = 4; + const wordsToShow = this.props.text.split(' ').slice(0, maximumWords); + + return ReactDOM.createPortal( + + this.resizeTooltip(ref)}> + {wordsToShow.join(' ')} + + + + + , + document.querySelector('body'), + ); + } +} TooltipRenderedOnPageBody.propTypes = propTypes; TooltipRenderedOnPageBody.defaultProps = defaultProps; diff --git a/src/styles/getTooltipStyles.js b/src/styles/getTooltipStyles.js index f39fe2c42311..4a81abf72091 100644 --- a/src/styles/getTooltipStyles.js +++ b/src/styles/getTooltipStyles.js @@ -109,6 +109,7 @@ export default function getTooltipStyles( ...tooltipVerticalPadding, ...spacing.ph2, zIndex: variables.tooltipzIndex, + maxWidth: 300, // Because it uses fixed positioning, the top-left corner of the tooltip is aligned // with the top-left corner of the window by default. @@ -144,6 +145,11 @@ export default function getTooltipStyles( color: themeColors.textReversed, fontFamily: fontFamily.GTA, fontSize: tooltipFontSize, + overflowWrap: 'normal', + // whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + display: 'inline', }, pointerWrapperStyle: { position: 'fixed', From 2674c63bf15a22b402735e5bc3a8faff7a083127 Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Tue, 22 Mar 2022 16:09:04 -0600 Subject: [PATCH 029/596] finish proposal --- src/components/MultipleAvatars.js | 2 +- .../Tooltip/TooltipRenderedOnPageBody.js | 132 +++++++++++------- src/components/Tooltip/index.js | 1 + src/components/Tooltip/tooltipPropTypes.js | 4 + src/styles/getTooltipStyles.js | 6 +- 5 files changed, 89 insertions(+), 56 deletions(-) diff --git a/src/components/MultipleAvatars.js b/src/components/MultipleAvatars.js index 993d36123c3a..f37bcc544dc1 100644 --- a/src/components/MultipleAvatars.js +++ b/src/components/MultipleAvatars.js @@ -80,7 +80,7 @@ const MultipleAvatars = (props) => { /> ) : ( - + diff --git a/src/components/Tooltip/TooltipRenderedOnPageBody.js b/src/components/Tooltip/TooltipRenderedOnPageBody.js index 1867af355478..e71fa082d275 100644 --- a/src/components/Tooltip/TooltipRenderedOnPageBody.js +++ b/src/components/Tooltip/TooltipRenderedOnPageBody.js @@ -4,6 +4,8 @@ import {Animated, View} from 'react-native'; import ReactDOM from 'react-dom'; import getTooltipStyles from '../../styles/getTooltipStyles'; import Text from '../Text'; +import variables from '../../styles/variables'; +import styles from '../../styles/styles'; const propTypes = { /** Window width */ @@ -47,66 +49,92 @@ const propTypes = { /** Callback to be used to calulate the width and height of tooltip */ measureTooltip: PropTypes.func.isRequired, + + /** Maximun amount of words the tooltip should show */ + maximumWords: PropTypes.number.isRequired, }; const defaultProps = {}; -// ! 1 -// {wordsToShow.map(word => {word + " "})} -// ! 2 -// {wordsToShow.join(' ')} - class TooltipRenderedOnPageBody extends React.Component { - - constructor(props) { - super(props); - } - state = { - tooltipTextWidth: 300, - } - - resizeTooltip(el) { - console.log(el); + constructor(props) { + super(props); + this.state = { + tooltipTextWidth: variables.sideBarWidth, + }; + + this.textRef = null; + this.getCorrectWidth = this.getCorrectWidth.bind(this); + } + + componentDidMount() { + this.setState({ + tooltipTextWidth: this.getCorrectWidth(this.textRef.offsetWidth), + }); + } + + getCorrectWidth(textWidth) { + const maxWidth = variables.sideBarWidth; + if (textWidth >= maxWidth) { + return maxWidth; } + const maxWidthDiffTextWidth = maxWidth - textWidth; - render() { - const { - animationStyle, - tooltipWrapperStyle, - tooltipTextStyle, - pointerWrapperStyle, - pointerStyle, - } = getTooltipStyles( - this.props.animation, - this.props.windowWidth, - this.props.xOffset, - this.props.yOffset, - this.props.wrapperWidth, - this.props.wrapperHeight, - this.props.tooltipWidth, - this.props.tooltipHeight, - this.props.shiftHorizontal, - this.props.shiftVertical, - ); - const maximumWords = 4; - const wordsToShow = this.props.text.split(' ').slice(0, maximumWords); - - return ReactDOM.createPortal( - - this.resizeTooltip(ref)}> - {wordsToShow.join(' ')} - - - - - , - document.querySelector('body'), - ); + // This operation will serve us to avoid adding more width than maxwidth + // Get padding of tooltipWrapper and sum the right and left + const leftRighPadding = styles.p2.padding * 2; + if (leftRighPadding > maxWidthDiffTextWidth) { + return textWidth + maxWidthDiffTextWidth; } + return textWidth + leftRighPadding; + } + + render() { + const { + animationStyle, + tooltipWrapperStyle, + tooltipTextStyle, + pointerWrapperStyle, + pointerStyle, + } = getTooltipStyles( + this.props.animation, + this.props.windowWidth, + this.props.xOffset, + this.props.yOffset, + this.props.wrapperWidth, + this.props.wrapperHeight, + this.props.tooltipWidth, + this.props.tooltipHeight, + this.props.shiftHorizontal, + this.props.shiftVertical, + this.state.tooltipTextWidth, + ); + const maximumWords = this.props.maximumWords; + const wordsProvided = this.props.text.split(' '); + + // Only show the amount words we want to see no matter the amount of lines needed to fit them + // This will give us an accurate width of visible text using ref.offsetWidth + // offsetWidth is accurate to visible text width if there no height overflow, + // otherwise will give us a longer width if there's longer line hidden in overflow + const wordsToShow = wordsProvided.slice(0, maximumWords); + + return ReactDOM.createPortal( + + + this.textRef = ref}>{wordsToShow.join(' ')} + {maximumWords < wordsProvided.length ? ... : '' } + + + + + , + document.querySelector('body'), + ); + } } TooltipRenderedOnPageBody.propTypes = propTypes; diff --git a/src/components/Tooltip/index.js b/src/components/Tooltip/index.js index 942029b4ab46..76ad28007f47 100644 --- a/src/components/Tooltip/index.js +++ b/src/components/Tooltip/index.js @@ -207,6 +207,7 @@ class Tooltip extends PureComponent { shiftVertical={_.result(this.props, 'shiftVertical')} measureTooltip={this.measureTooltip} text={this.props.text} + maximumWords={this.props.maximumWords} /> )} Date: Tue, 22 Mar 2022 19:17:07 -0600 Subject: [PATCH 030/596] move getCorrectWidth func --- .../Tooltip/TooltipRenderedOnPageBody.js | 21 ++-------------- src/styles/getTooltipStyles.js | 25 ++++++++++++++++++- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/components/Tooltip/TooltipRenderedOnPageBody.js b/src/components/Tooltip/TooltipRenderedOnPageBody.js index e71fa082d275..3f5cde8c9b75 100644 --- a/src/components/Tooltip/TooltipRenderedOnPageBody.js +++ b/src/components/Tooltip/TooltipRenderedOnPageBody.js @@ -5,7 +5,6 @@ import ReactDOM from 'react-dom'; import getTooltipStyles from '../../styles/getTooltipStyles'; import Text from '../Text'; import variables from '../../styles/variables'; -import styles from '../../styles/styles'; const propTypes = { /** Window width */ @@ -60,35 +59,19 @@ class TooltipRenderedOnPageBody extends React.Component { constructor(props) { super(props); this.state = { + // Set maxWidth as initial so we can get width of word wrapped text tooltipTextWidth: variables.sideBarWidth, }; this.textRef = null; - this.getCorrectWidth = this.getCorrectWidth.bind(this); } componentDidMount() { this.setState({ - tooltipTextWidth: this.getCorrectWidth(this.textRef.offsetWidth), + tooltipTextWidth: this.textRef.offsetWidth, }); } - getCorrectWidth(textWidth) { - const maxWidth = variables.sideBarWidth; - if (textWidth >= maxWidth) { - return maxWidth; - } - const maxWidthDiffTextWidth = maxWidth - textWidth; - - // This operation will serve us to avoid adding more width than maxwidth - // Get padding of tooltipWrapper and sum the right and left - const leftRighPadding = styles.p2.padding * 2; - if (leftRighPadding > maxWidthDiffTextWidth) { - return textWidth + maxWidthDiffTextWidth; - } - return textWidth + leftRighPadding; - } - render() { const { animationStyle, diff --git a/src/styles/getTooltipStyles.js b/src/styles/getTooltipStyles.js index 3ff32b373fc4..44549bee2253 100644 --- a/src/styles/getTooltipStyles.js +++ b/src/styles/getTooltipStyles.js @@ -49,6 +49,29 @@ function computeHorizontalShift(windowWidth, xOffset, componentWidth, tooltipWid return 0; } +/** + * Correct tooltipWrapper width according to visible inner text offssetWidth + * + * @param {Number} textWidth - The inner text offsetWidth of Tooltip + * + * @returns {Number} + */ +function getCorrectWidth(textWidth) { + const maxWidth = variables.sideBarWidth; + if (textWidth >= maxWidth) { + return maxWidth; + } + const maxWidthDiffTextWidth = maxWidth - textWidth; + + // This operation will serve us to avoid adding more width than maxwidth + // Get padding of tooltipWrapper and sum the right and left + const leftRighPadding = styles.p2.padding * 2; + if (leftRighPadding > maxWidthDiffTextWidth) { + return textWidth + maxWidthDiffTextWidth; + } + return textWidth + leftRighPadding; +} + /** * Generate styles for the tooltip component. * @@ -111,7 +134,7 @@ export default function getTooltipStyles( ...tooltipVerticalPadding, ...spacing.ph2, zIndex: variables.tooltipzIndex, - maxWidth: tooltipTextWidth, + maxWidth: getCorrectWidth(tooltipTextWidth), // Because it uses fixed positioning, the top-left corner of the tooltip is aligned // with the top-left corner of the window by default. From 4744791d542f804a3213733810b2b327aab96c9e Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Wed, 23 Mar 2022 14:47:48 +0530 Subject: [PATCH 031/596] fix: updated translation keys and comment jsdocs --- src/CONST.js | 1 - src/languages/en.js | 22 ++++------------------ src/languages/es.js | 22 ++++------------------ src/libs/fileDownload/FileUtils.js | 11 +++++------ 4 files changed, 13 insertions(+), 43 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 5889e26d4827..e39473626dd4 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -375,7 +375,6 @@ const CONST = { }, IOS_CAMERAROLL_ACCESS_ERROR: 'Access to photo library was denied', - ADD_PAYMENT_MENU_POSITION_Y: 226, ADD_PAYMENT_MENU_POSITION_X: 356, EMOJI_PICKER_SIZE: { diff --git a/src/languages/en.js b/src/languages/en.js index 6b685ef26057..b9269e17a538 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -963,32 +963,18 @@ export default { success: { title: 'Downloaded!', message: 'Attachment successfully downloaded', - buttons: { - ok: { - text: 'OK', - }, - }, + buttonOkText: 'OK', }, generalError: { title: 'Attachment Error', message: 'Attachment cannot be downloaded', - buttons: { - cancel: { - text: 'Cancel', - }, - }, + buttonCancelText: 'Cancel', }, permissionError: { title: 'Access Needed', message: 'NewExpensify does not have access to save attachments. To enable access, tap Settings and allow access.', - buttons: { - cancel: { - text: 'Cancel', - }, - settings: { - text: 'Settings', - }, - }, + buttonCancelText: 'Cancel', + buttonSettingsText: 'Settings', }, }, }; diff --git a/src/languages/es.js b/src/languages/es.js index d6650a9f55e5..46e22977e05e 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -965,32 +965,18 @@ export default { success: { title: 'Descargado!', message: 'Adjunto descargado con éxito', - buttons: { - ok: { - text: 'OK', - }, - }, + buttonOkText: 'OK', }, generalError: { title: 'Error de archivo adjunto', message: 'No se puede descargar el archivo adjunto', - buttons: { - cancel: { - text: 'Cancelar', - }, - }, + buttonCancelText: 'Cancelar', }, permissionError: { title: 'Acceso necesario', message: 'NewExpensify no tiene acceso para guardar archivos adjuntos. Para habilitar el acceso, toque Configuración y permita el acceso.', - buttons: { - cancel: { - text: 'Cancelar', - }, - settings: { - text: 'Ajustes', - }, - }, + buttonCancelText: 'Cancelar', + buttonSettingsText: 'Ajustes', }, }, }; diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index 9edebdd6f59a..7e63cb33fa9a 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -13,7 +13,7 @@ function showSuccessAlert() { Localize.translateLocal('fileDownload.success.message'), [ { - text: Localize.translateLocal('fileDownload.success.buttons.ok.text'), + text: Localize.translateLocal('fileDownload.success.buttonOkText'), style: 'cancel', }, ], @@ -30,7 +30,7 @@ function showGeneralErrorAlert() { Localize.translateLocal('fileDownload.generalError.message'), [ { - text: Localize.translateLocal('fileDownload.generalError.buttons.cancel.text'), + text: Localize.translateLocal('fileDownload.generalError.buttonCancelText'), style: 'cancel', }, ], @@ -46,11 +46,11 @@ function showPermissionErrorAlert() { Localize.translateLocal('fileDownload.permissionError.message'), [ { - text: Localize.translateLocal('fileDownload.permissionError.buttons.cancel.text'), + text: Localize.translateLocal('fileDownload.permissionError.buttonCancelText'), style: 'cancel', }, { - text: Localize.translateLocal('fileDownload.permissionError.buttons.settings.text'), + text: Localize.translateLocal('fileDownload.permissionError.buttonSettingsText'), onPress: () => Linking.openSettings(), }, ], @@ -71,10 +71,9 @@ function getAttachmentName(url) { /** * Returns file type based on the uri - * @param {String} fileUri + * @param {String} fileUrl * @returns {String} */ - function getFileType(fileUrl) { if (!fileUrl) { return; From 153d23e716f70e5e7ad39144ac17fbc5efb14314 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Wed, 23 Mar 2022 14:48:01 +0530 Subject: [PATCH 032/596] fix: promise simplification --- src/libs/fileDownload/index.android.js | 13 ++++--------- src/libs/fileDownload/index.ios.js | 4 +--- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/libs/fileDownload/index.android.js b/src/libs/fileDownload/index.android.js index 7e7e7f77db37..840d3bfdd8d4 100644 --- a/src/libs/fileDownload/index.android.js +++ b/src/libs/fileDownload/index.android.js @@ -65,11 +65,9 @@ function handleDownload(url, fileName) { } FileUtils.showSuccessAlert(); - return resolve(); }).catch(() => { FileUtils.showGeneralErrorAlert(); - return resolve(); - }); + }).finally(() => resolve()); }); } @@ -83,14 +81,11 @@ export default function fileDownload(url, fileName) { return new Promise((resolve) => { hasAndroidPermission().then((hasPermission) => { if (hasPermission) { - handleDownload(url, fileName).then(() => resolve()); - } else { - FileUtils.showPermissionErrorAlert(); + return handleDownload(url, fileName); } - return resolve(); + FileUtils.showPermissionErrorAlert(); }).catch(() => { FileUtils.showPermissionErrorAlert(); - return resolve(); - }); + }).finally(() => resolve()); }); } diff --git a/src/libs/fileDownload/index.ios.js b/src/libs/fileDownload/index.ios.js index a5aa94b72691..606e6eb8734b 100644 --- a/src/libs/fileDownload/index.ios.js +++ b/src/libs/fileDownload/index.ios.js @@ -92,14 +92,12 @@ export default function fileDownload(fileUrl, fileName) { } FileUtils.showSuccessAlert(); - return resolve(); }).catch((err) => { if (err.message === CONST.IOS_CAMERAROLL_ACCESS_ERROR) { FileUtils.showPermissionErrorAlert(); } else { FileUtils.showGeneralErrorAlert(); } - return resolve(); - }); + }).finally(() => resolve()); }); } From 3dbe0705b29c68ac2d383f8dd5f372f8466ef777 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Wed, 23 Mar 2022 15:05:30 +0530 Subject: [PATCH 033/596] fix: cleanup promises --- src/libs/fileDownload/index.android.js | 36 ++++++++++---------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/libs/fileDownload/index.android.js b/src/libs/fileDownload/index.android.js index 840d3bfdd8d4..e63bc9e1d14d 100644 --- a/src/libs/fileDownload/index.android.js +++ b/src/libs/fileDownload/index.android.js @@ -4,32 +4,24 @@ import * as FileUtils from './FileUtils'; /** * Android permission check to store images - * @returns{Promise} + * @returns {Promise} */ function hasAndroidPermission() { - return new Promise((resolve, reject) => { - // read and write permission - const readPermission = PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE; - const writePermission = PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE; + // read and write permission + const writePromise = PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE); + const readPromise = PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE); - const writePromise = PermissionsAndroid.check(writePermission); - const readPromise = PermissionsAndroid.check(readPermission); + return Promise.all([writePromise, readPromise]).then(([hasWritePermission, hasReadPermission]) => { + if (hasWritePermission && hasReadPermission) { + return true; // return true if permission is already given + } - Promise.all([writePromise, readPromise]).then(([hasWritePermission, hasReadPermission]) => { - if (hasWritePermission && hasReadPermission) { - resolve(true); // return true if permission is already given - return; - } - - // ask for permission if not given - PermissionsAndroid.requestMultiple([ - readPermission, - writePermission, - ]).then((status) => { - resolve(status['android.permission.READ_EXTERNAL_STORAGE'] === 'granted' + // ask for permission if not given + return PermissionsAndroid.requestMultiple([ + PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, + PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, + ]).then(status => status['android.permission.READ_EXTERNAL_STORAGE'] === 'granted' && status['android.permission.WRITE_EXTERNAL_STORAGE'] === 'granted'); - }); - }).catch(error => reject(error)); }); } @@ -37,7 +29,7 @@ function hasAndroidPermission() { * Handling the download * @param {String} url * @param {String} fileName - * @returns {Promise} + * @returns {Promise} */ function handleDownload(url, fileName) { return new Promise((resolve) => { From 250d9e419c969986a465a9c6a04f67ad38d5e5b4 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Wed, 23 Mar 2022 15:07:21 +0530 Subject: [PATCH 034/596] fix: correct promise types --- src/libs/fileDownload/index.android.js | 2 +- src/libs/fileDownload/index.ios.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/fileDownload/index.android.js b/src/libs/fileDownload/index.android.js index e63bc9e1d14d..9beee038d952 100644 --- a/src/libs/fileDownload/index.android.js +++ b/src/libs/fileDownload/index.android.js @@ -67,7 +67,7 @@ function handleDownload(url, fileName) { * Checks permission and downloads the file for Android * @param {String} url * @param {String} fileName - * @returns {Promise} + * @returns {Promise} */ export default function fileDownload(url, fileName) { return new Promise((resolve) => { diff --git a/src/libs/fileDownload/index.ios.js b/src/libs/fileDownload/index.ios.js index 606e6eb8734b..99579ce1bd6e 100644 --- a/src/libs/fileDownload/index.ios.js +++ b/src/libs/fileDownload/index.ios.js @@ -66,7 +66,7 @@ function downloadVideo(fileUrl, fileName) { * Download the file based on type(image, video, other file types)for iOS * @param {String} fileUrl * @param {String} fileName - * @returns {Promise} + * @returns {Promise} */ export default function fileDownload(fileUrl, fileName) { return new Promise((resolve) => { From 3c506fa38d6e14b16a0ea6e53fbf62b1002382d3 Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Wed, 23 Mar 2022 11:30:23 -0600 Subject: [PATCH 035/596] create tooltipHorizontalPadding, add new param --- src/styles/getTooltipStyles.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/styles/getTooltipStyles.js b/src/styles/getTooltipStyles.js index 44549bee2253..50566ada685f 100644 --- a/src/styles/getTooltipStyles.js +++ b/src/styles/getTooltipStyles.js @@ -53,10 +53,11 @@ function computeHorizontalShift(windowWidth, xOffset, componentWidth, tooltipWid * Correct tooltipWrapper width according to visible inner text offssetWidth * * @param {Number} textWidth - The inner text offsetWidth of Tooltip + * @param {Number} tooltipHorizontalPadding - The horizontal padding set for wrapper tooltip * * @returns {Number} */ -function getCorrectWidth(textWidth) { +function getCorrectWidth(textWidth, tooltipHorizontalPadding) { const maxWidth = variables.sideBarWidth; if (textWidth >= maxWidth) { return maxWidth; @@ -64,8 +65,8 @@ function getCorrectWidth(textWidth) { const maxWidthDiffTextWidth = maxWidth - textWidth; // This operation will serve us to avoid adding more width than maxwidth - // Get padding of tooltipWrapper and sum the right and left - const leftRighPadding = styles.p2.padding * 2; + // Get horizontal padding of tooltipWrapper and sum the right and left + const leftRighPadding = tooltipHorizontalPadding * 2; if (leftRighPadding > maxWidthDiffTextWidth) { return textWidth + maxWidthDiffTextWidth; } @@ -117,6 +118,7 @@ export default function getTooltipStyles( const tooltipVerticalPadding = spacing.pv1; const tooltipFontSize = variables.fontSizeSmall; + const tooltipHorizontalPadding = spacing.ph2; return { animationStyle: { @@ -132,9 +134,9 @@ export default function getTooltipStyles( backgroundColor: themeColors.heading, borderRadius: variables.componentBorderRadiusSmall, ...tooltipVerticalPadding, - ...spacing.ph2, + ...tooltipHorizontalPadding, zIndex: variables.tooltipzIndex, - maxWidth: getCorrectWidth(tooltipTextWidth), + maxWidth: getCorrectWidth(tooltipTextWidth, tooltipHorizontalPadding.paddingHorizontal), // Because it uses fixed positioning, the top-left corner of the tooltip is aligned // with the top-left corner of the window by default. From 43fd9cf9541cd7b4f5ff801fa228a3c0d52f4eb7 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Thu, 24 Mar 2022 00:33:49 +0530 Subject: [PATCH 036/596] fix: updated jsdoc comments --- src/libs/fileDownload/FileUtils.js | 2 +- src/libs/fileDownload/index.ios.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index 7e63cb33fa9a..f7b6417b824e 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -58,7 +58,7 @@ function showPermissionErrorAlert() { } /** - * Generating a random file name with timestamp and file extention + * Generate a random file name with timestamp and file extension * @param {String} url * @returns {String} */ diff --git a/src/libs/fileDownload/index.ios.js b/src/libs/fileDownload/index.ios.js index 99579ce1bd6e..de8b351e5e2e 100644 --- a/src/libs/fileDownload/index.ios.js +++ b/src/libs/fileDownload/index.ios.js @@ -30,7 +30,7 @@ function downloadFile(fileUrl, fileName) { } /** - * Downloads the image to photo lib in iOS + * Downloads the image to photo lib in iOS and returns the uri * @param {String} fileUrl * @param {String} fileName * @returns {String} @@ -40,7 +40,7 @@ function downloadImage(fileUrl) { } /** - * Downloads the video to photo lib in iOS + * Downloads the video to photo lib in iOS and returns the uri * @param {String} fileUrl * @param {String} fileName * @returns {String} From 5e4a50449b3a648ee141dcb0a0b37903758ad1b6 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Thu, 24 Mar 2022 01:01:44 +0530 Subject: [PATCH 037/596] fix: replaced file type checks with regex, removed mime-type dep --- package-lock.json | 15 --------------- package.json | 1 - src/CONST.js | 4 ++++ src/libs/fileDownload/FileUtils.js | 22 ++++++++++++++++++---- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index da10ee12cf85..6ad5ce30fc6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36509,21 +36509,6 @@ "resolved": "https://registry.npmjs.org/react-native-keyboard-spacer/-/react-native-keyboard-spacer-0.4.1.tgz", "integrity": "sha1-RvGKMgQyCYol6p+on1FD3SVNMy0=" }, - "react-native-mime-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/react-native-mime-types/-/react-native-mime-types-2.3.0.tgz", - "integrity": "sha512-9l/kkT1QM0i0xAKkOADkP6jIMhqiS+R/eSKBS/lsUmuMYMqboClyrwTFFZwUcaVIN0SpeH4wOKhYTqCnFgRsEw==", - "requires": { - "mime-db": "~1.37.0" - }, - "dependencies": { - "mime-db": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" - } - } - }, "react-native-modal": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-13.0.0.tgz", diff --git a/package.json b/package.json index 3babdf1454d2..d9ecd64aebb5 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,6 @@ "react-native-image-picker": "^4.1.2", "react-native-image-size": "^1.1.3", "react-native-keyboard-spacer": "^0.4.1", - "react-native-mime-types": "^2.3.0", "react-native-modal": "^13.0.0", "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#2d42566779884bb445aa59dd48b5bcdff6c0a4e6", "react-native-pdf": "^6.2.2", diff --git a/src/CONST.js b/src/CONST.js index e39473626dd4..9573c1466ca6 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -374,6 +374,10 @@ const CONST = { VIDEO: 'video', }, + FILE_TYPE_REGEX: { + IMAGE: /\.(jpg|jpeg|png|webp|avif|gif|tiff|wbmp|ico|jng|bmp|heic|svg|svg2)$/, + VIDEO: /\.(3gp|h261|h263|h264|m4s|jpgv|jpm|jpgm|mp4|mp4v|mpg4|mpeg|mpg|ogv|ogg|mov|qt|webm|flv|mkv|wmv|wav|avi|movie|f4v|avchd|mp2|mpe|mpv|m4v|swf)$/, + }, IOS_CAMERAROLL_ACCESS_ERROR: 'Access to photo library was denied', ADD_PAYMENT_MENU_POSITION_Y: 226, ADD_PAYMENT_MENU_POSITION_X: 356, diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index f7b6417b824e..44755b9997d2 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -1,6 +1,5 @@ import {Alert, Linking} from 'react-native'; import moment from 'moment'; -import * as mime from 'react-native-mime-types'; import CONST from '../../CONST'; import * as Localize from '../Localize'; @@ -69,6 +68,22 @@ function getAttachmentName(url) { return `${moment().format('DDMMYYYYHHmmss')}.${url.split(/[#?]/)[0].split('.').pop().trim()}`; } +/** + * @param {String} fileName + * @returns {Boolean} + */ +function isImage(fileName) { + return CONST.FILE_TYPE_REGEX.IMAGE.test(fileName); +} + +/** + * @param {String} fileName + * @returns {Boolean} + */ +function isVideo(fileName) { + return CONST.FILE_TYPE_REGEX.VIDEO.test(fileName); +} + /** * Returns file type based on the uri * @param {String} fileUrl @@ -79,11 +94,10 @@ function getFileType(fileUrl) { return; } const fileName = fileUrl.split('/').pop().split('?')[0].split('#')[0]; - const contentType = mime.contentType(fileName); - if (contentType.startsWith('image')) { + if (isImage(fileName)) { return CONST.ATTACHMENT_FILE_TYPE.IMAGE; } - if (contentType.startsWith('video')) { + if (isVideo(fileName)) { return CONST.ATTACHMENT_FILE_TYPE.VIDEO; } return CONST.ATTACHMENT_FILE_TYPE.FILE; From 5b1e42eaff63858db4c39b794c9218157b296426 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Thu, 24 Mar 2022 01:42:05 +0530 Subject: [PATCH 038/596] fix: updated translations --- src/languages/es.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/languages/es.js b/src/languages/es.js index 46e22977e05e..eed44448d6ff 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -964,19 +964,19 @@ export default { fileDownload: { success: { title: 'Descargado!', - message: 'Adjunto descargado con éxito', + message: 'Archivo descargado con éxito', buttonOkText: 'OK', }, generalError: { - title: 'Error de archivo adjunto', + title: 'Error al adjuntar archivo', message: 'No se puede descargar el archivo adjunto', buttonCancelText: 'Cancelar', }, permissionError: { - title: 'Acceso necesario', - message: 'NewExpensify no tiene acceso para guardar archivos adjuntos. Para habilitar el acceso, toque Configuración y permita el acceso.', + title: 'Acceso Restringido', + message: 'NewExpensify no tiene acceso para guardar archivos adjuntos. Para habilitar el acceso, pulse Configuración y permita el acceso.', buttonCancelText: 'Cancelar', - buttonSettingsText: 'Ajustes', + buttonSettingsText: 'Configuración', }, }, }; From 6d1d7cb26acb69e1cbffae52d5276e29d124b48f Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Thu, 24 Mar 2022 22:45:27 +0530 Subject: [PATCH 039/596] fix: updated translation keys --- src/languages/en.js | 6 +----- src/languages/es.js | 6 +----- src/libs/fileDownload/FileUtils.js | 8 ++++---- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index b9269e17a538..f6614a6a7b51 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -963,18 +963,14 @@ export default { success: { title: 'Downloaded!', message: 'Attachment successfully downloaded', - buttonOkText: 'OK', }, generalError: { title: 'Attachment Error', message: 'Attachment cannot be downloaded', - buttonCancelText: 'Cancel', }, permissionError: { title: 'Access Needed', - message: 'NewExpensify does not have access to save attachments. To enable access, tap Settings and allow access.', - buttonCancelText: 'Cancel', - buttonSettingsText: 'Settings', + message: 'NewExpensify does not have access to save attachments. To enable access, click Settings and allow access.', }, }, }; diff --git a/src/languages/es.js b/src/languages/es.js index eed44448d6ff..39482fd7285d 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -965,18 +965,14 @@ export default { success: { title: 'Descargado!', message: 'Archivo descargado con éxito', - buttonOkText: 'OK', }, generalError: { title: 'Error al adjuntar archivo', message: 'No se puede descargar el archivo adjunto', - buttonCancelText: 'Cancelar', }, permissionError: { title: 'Acceso Restringido', - message: 'NewExpensify no tiene acceso para guardar archivos adjuntos. Para habilitar el acceso, pulse Configuración y permita el acceso.', - buttonCancelText: 'Cancelar', - buttonSettingsText: 'Configuración', + message: 'NewExpensify no tiene acceso para guardar archivos adjuntos. Para habilitar el acceso, click Configuración y permita el acceso.', }, }, }; diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index 44755b9997d2..5271216fa741 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -12,7 +12,7 @@ function showSuccessAlert() { Localize.translateLocal('fileDownload.success.message'), [ { - text: Localize.translateLocal('fileDownload.success.buttonOkText'), + text: Localize.translateLocal('common.ok'), style: 'cancel', }, ], @@ -29,7 +29,7 @@ function showGeneralErrorAlert() { Localize.translateLocal('fileDownload.generalError.message'), [ { - text: Localize.translateLocal('fileDownload.generalError.buttonCancelText'), + text: Localize.translateLocal('common.cancel'), style: 'cancel', }, ], @@ -45,11 +45,11 @@ function showPermissionErrorAlert() { Localize.translateLocal('fileDownload.permissionError.message'), [ { - text: Localize.translateLocal('fileDownload.permissionError.buttonCancelText'), + text: Localize.translateLocal('common.cancel'), style: 'cancel', }, { - text: Localize.translateLocal('fileDownload.permissionError.buttonSettingsText'), + text: Localize.translateLocal('common.settings'), onPress: () => Linking.openSettings(), }, ], From 0d590c754fbe92603edc7ab50e9cf4e7361fefe9 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Thu, 24 Mar 2022 23:14:22 +0530 Subject: [PATCH 040/596] fix: rolled back click to tap --- src/languages/en.js | 2 +- src/languages/es.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index f6614a6a7b51..a7b7caaed377 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -970,7 +970,7 @@ export default { }, permissionError: { title: 'Access Needed', - message: 'NewExpensify does not have access to save attachments. To enable access, click Settings and allow access.', + message: 'NewExpensify does not have access to save attachments. To enable access, tap Settings and allow access.', }, }, }; diff --git a/src/languages/es.js b/src/languages/es.js index 39482fd7285d..8f5f658fbfe6 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -972,7 +972,7 @@ export default { }, permissionError: { title: 'Acceso Restringido', - message: 'NewExpensify no tiene acceso para guardar archivos adjuntos. Para habilitar el acceso, click Configuración y permita el acceso.', + message: 'NewExpensify no tiene acceso para guardar archivos adjuntos. Para habilitar el acceso, pulse Configuración y permita el acceso.', }, }, }; From 3394e01977959c2f52c3c677f1d251ab32c64cfc Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Fri, 25 Mar 2022 00:58:00 +0530 Subject: [PATCH 041/596] fix: make comments title case --- src/libs/fileDownload/index.android.js | 10 +++++----- src/libs/fileDownload/index.ios.js | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libs/fileDownload/index.android.js b/src/libs/fileDownload/index.android.js index 9beee038d952..7a758fe8151e 100644 --- a/src/libs/fileDownload/index.android.js +++ b/src/libs/fileDownload/index.android.js @@ -7,7 +7,7 @@ import * as FileUtils from './FileUtils'; * @returns {Promise} */ function hasAndroidPermission() { - // read and write permission + // Read and write permission const writePromise = PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE); const readPromise = PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE); @@ -16,7 +16,7 @@ function hasAndroidPermission() { return true; // return true if permission is already given } - // ask for permission if not given + // Ask for permission if not given return PermissionsAndroid.requestMultiple([ PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, @@ -35,11 +35,11 @@ function handleDownload(url, fileName) { return new Promise((resolve) => { const dirs = RNFetchBlob.fs.dirs; - // android files will download to Download directory + // Android files will download to Download directory const path = dirs.DownloadDir; const attachmentName = fileName || FileUtils.getAttachmentName(url); - // fetching the attachment + // Fetching the attachment const fetchedAttachment = RNFetchBlob.config({ fileCache: true, path: `${path}/${attachmentName}`, @@ -50,7 +50,7 @@ function handleDownload(url, fileName) { }, }).fetch('GET', url); - // resolving the fetched attachment + // Resolving the fetched attachment fetchedAttachment.then((attachment) => { if (!attachment || !attachment.info()) { return; diff --git a/src/libs/fileDownload/index.ios.js b/src/libs/fileDownload/index.ios.js index de8b351e5e2e..f90eb5a93ae3 100644 --- a/src/libs/fileDownload/index.ios.js +++ b/src/libs/fileDownload/index.ios.js @@ -13,10 +13,10 @@ import CONST from '../../CONST'; function downloadFile(fileUrl, fileName) { const dirs = RNFetchBlob.fs.dirs; - // ios files will download to documents directory + // The iOS files will download to documents directory const path = dirs.DocumentDir; - // fetching the attachment + // Fetching the attachment const fetchedAttachment = RNFetchBlob.config({ fileCache: true, path: `${path}/${fileName}`, From 7962dcb2b324c303cac640fc0a770dfb4be77c54 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Sat, 26 Mar 2022 04:22:20 +0530 Subject: [PATCH 042/596] fix: clean up comment --- src/libs/fileDownload/index.ios.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/libs/fileDownload/index.ios.js b/src/libs/fileDownload/index.ios.js index f90eb5a93ae3..a3ad032135de 100644 --- a/src/libs/fileDownload/index.ios.js +++ b/src/libs/fileDownload/index.ios.js @@ -30,28 +30,27 @@ function downloadFile(fileUrl, fileName) { } /** - * Downloads the image to photo lib in iOS and returns the uri + * Download the image to photo lib in iOS * @param {String} fileUrl * @param {String} fileName - * @returns {String} + * @returns {String} URI */ function downloadImage(fileUrl) { return CameraRoll.save(fileUrl); } /** - * Downloads the video to photo lib in iOS and returns the uri + * Download the video to photo lib in iOS * @param {String} fileUrl * @param {String} fileName - * @returns {String} + * @returns {String} URI */ function downloadVideo(fileUrl, fileName) { return new Promise((resolve) => { let documentPathUri = null; let cameraRollUri = null; - // Because CameraRoll doesn't allow remote URIs to save the video, - // we first download to documents, then copy to photo lib and unlink the original file, + // Because CameraRoll doesn't allow direct downloads of video with remote URIs, we first download as documents, then copy to photo lib and unlink the original file. downloadFile(fileUrl, fileName).then((attachment) => { documentPathUri = lodashGet(attachment, 'data'); return CameraRoll.save(documentPathUri); From 74935eb9b2ffbdac70909723e1c9546c88625e64 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Sat, 26 Mar 2022 12:35:55 +0530 Subject: [PATCH 043/596] fix: fetch pathname using url --- src/libs/fileDownload/FileUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index 5271216fa741..60529a44c15c 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -93,7 +93,7 @@ function getFileType(fileUrl) { if (!fileUrl) { return; } - const fileName = fileUrl.split('/').pop().split('?')[0].split('#')[0]; + const fileName = new URL(fileUrl).pathname; if (isImage(fileName)) { return CONST.ATTACHMENT_FILE_TYPE.IMAGE; } From c3aa57191d8372c6b8f0cc7ae6cd7629ab4dbd1f Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Wed, 30 Mar 2022 14:21:34 -0600 Subject: [PATCH 044/596] remove unneeded maximumWords prop --- src/components/MultipleAvatars.js | 2 +- .../Tooltip/TooltipRenderedOnPageBody.js | 16 ++-------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/components/MultipleAvatars.js b/src/components/MultipleAvatars.js index f37bcc544dc1..993d36123c3a 100644 --- a/src/components/MultipleAvatars.js +++ b/src/components/MultipleAvatars.js @@ -80,7 +80,7 @@ const MultipleAvatars = (props) => { /> ) : ( - + diff --git a/src/components/Tooltip/TooltipRenderedOnPageBody.js b/src/components/Tooltip/TooltipRenderedOnPageBody.js index 3f5cde8c9b75..3f0773d157a7 100644 --- a/src/components/Tooltip/TooltipRenderedOnPageBody.js +++ b/src/components/Tooltip/TooltipRenderedOnPageBody.js @@ -49,8 +49,6 @@ const propTypes = { /** Callback to be used to calulate the width and height of tooltip */ measureTooltip: PropTypes.func.isRequired, - /** Maximun amount of words the tooltip should show */ - maximumWords: PropTypes.number.isRequired, }; const defaultProps = {}; @@ -92,24 +90,14 @@ class TooltipRenderedOnPageBody extends React.Component { this.props.shiftVertical, this.state.tooltipTextWidth, ); - const maximumWords = this.props.maximumWords; - const wordsProvided = this.props.text.split(' '); - - // Only show the amount words we want to see no matter the amount of lines needed to fit them - // This will give us an accurate width of visible text using ref.offsetWidth - // offsetWidth is accurate to visible text width if there no height overflow, - // otherwise will give us a longer width if there's longer line hidden in overflow - const wordsToShow = wordsProvided.slice(0, maximumWords); - return ReactDOM.createPortal( - - this.textRef = ref}>{wordsToShow.join(' ')} - {maximumWords < wordsProvided.length ? ... : '' } + + this.textRef = ref}>{this.props.text} From 9e8271c3c4db8afe7b76b8f08892182fd4b45563 Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Wed, 30 Mar 2022 19:07:43 -0600 Subject: [PATCH 045/596] simplify code --- .../Tooltip/TooltipRenderedOnPageBody.js | 2 +- src/styles/getTooltipStyles.js | 33 ++++--------------- 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/src/components/Tooltip/TooltipRenderedOnPageBody.js b/src/components/Tooltip/TooltipRenderedOnPageBody.js index 3f0773d157a7..bb400f1f1147 100644 --- a/src/components/Tooltip/TooltipRenderedOnPageBody.js +++ b/src/components/Tooltip/TooltipRenderedOnPageBody.js @@ -96,7 +96,7 @@ class TooltipRenderedOnPageBody extends React.Component { onLayout={this.props.measureTooltip} style={[tooltipWrapperStyle, animationStyle]} > - + this.textRef = ref}>{this.props.text} diff --git a/src/styles/getTooltipStyles.js b/src/styles/getTooltipStyles.js index 50566ada685f..44b47df0c5c7 100644 --- a/src/styles/getTooltipStyles.js +++ b/src/styles/getTooltipStyles.js @@ -49,30 +49,6 @@ function computeHorizontalShift(windowWidth, xOffset, componentWidth, tooltipWid return 0; } -/** - * Correct tooltipWrapper width according to visible inner text offssetWidth - * - * @param {Number} textWidth - The inner text offsetWidth of Tooltip - * @param {Number} tooltipHorizontalPadding - The horizontal padding set for wrapper tooltip - * - * @returns {Number} - */ -function getCorrectWidth(textWidth, tooltipHorizontalPadding) { - const maxWidth = variables.sideBarWidth; - if (textWidth >= maxWidth) { - return maxWidth; - } - const maxWidthDiffTextWidth = maxWidth - textWidth; - - // This operation will serve us to avoid adding more width than maxwidth - // Get horizontal padding of tooltipWrapper and sum the right and left - const leftRighPadding = tooltipHorizontalPadding * 2; - if (leftRighPadding > maxWidthDiffTextWidth) { - return textWidth + maxWidthDiffTextWidth; - } - return textWidth + leftRighPadding; -} - /** * Generate styles for the tooltip component. * @@ -118,7 +94,6 @@ export default function getTooltipStyles( const tooltipVerticalPadding = spacing.pv1; const tooltipFontSize = variables.fontSizeSmall; - const tooltipHorizontalPadding = spacing.ph2; return { animationStyle: { @@ -134,9 +109,13 @@ export default function getTooltipStyles( backgroundColor: themeColors.heading, borderRadius: variables.componentBorderRadiusSmall, ...tooltipVerticalPadding, - ...tooltipHorizontalPadding, + ...spacing.ph2, zIndex: variables.tooltipzIndex, - maxWidth: getCorrectWidth(tooltipTextWidth, tooltipHorizontalPadding.paddingHorizontal), + maxWidth: tooltipTextWidth >= variables.sideBarWidth + ? variables.sideBarWidth + + // Sum left and right padding + : tooltipTextWidth + (spacing.ph2.paddingHorizontal * 2), // Because it uses fixed positioning, the top-left corner of the tooltip is aligned // with the top-left corner of the window by default. From 5163c67656863f657d1e2beecd9582f03ea42177 Mon Sep 17 00:00:00 2001 From: LucioChavezFuentes Date: Thu, 31 Mar 2022 12:02:20 -0600 Subject: [PATCH 046/596] remove unneeded maximumWords prop --- src/components/Tooltip/index.js | 1 - src/components/Tooltip/tooltipPropTypes.js | 3 --- src/styles/getTooltipStyles.js | 6 +++--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/components/Tooltip/index.js b/src/components/Tooltip/index.js index 76ad28007f47..942029b4ab46 100644 --- a/src/components/Tooltip/index.js +++ b/src/components/Tooltip/index.js @@ -207,7 +207,6 @@ class Tooltip extends PureComponent { shiftVertical={_.result(this.props, 'shiftVertical')} measureTooltip={this.measureTooltip} text={this.props.text} - maximumWords={this.props.maximumWords} /> )} Date: Fri, 1 Apr 2022 00:14:15 -0700 Subject: [PATCH 047/596] Use oldPolicyName report field in ArchivedReportFooter --- src/components/ArchivedReportFooter.js | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/components/ArchivedReportFooter.js b/src/components/ArchivedReportFooter.js index d423fa0d4e22..a24a923bfb3a 100644 --- a/src/components/ArchivedReportFooter.js +++ b/src/components/ArchivedReportFooter.js @@ -27,19 +27,13 @@ const propTypes = { /** The archived report */ report: PropTypes.shape({ - /** The policy this report is attached to */ - policyID: PropTypes.string, + /** The policy this report used to be attached to */ + oldPolicyName: PropTypes.string, }).isRequired, /** Personal details of all users */ personalDetails: PropTypes.objectOf(personalDetailsPropType).isRequired, - /** The list of policies the user has access to. */ - policies: PropTypes.objectOf(PropTypes.shape({ - /** The name of the policy */ - name: PropTypes.string, - })).isRequired, - ...withLocalizePropTypes, }; @@ -53,7 +47,6 @@ const defaultProps = { const ArchivedReportFooter = (props) => { const archiveReason = lodashGet(props.reportClosedAction, 'originalMessage.reason', CONST.REPORT.ARCHIVE_REASON.DEFAULT); - const policyName = lodashGet(props.policies, `${ONYXKEYS.COLLECTION.POLICY}${props.report.policyID}.name`); let displayName = lodashGet(props.personalDetails, `${props.report.ownerEmail}.displayName`, props.report.ownerEmail); let oldDisplayName; @@ -69,7 +62,7 @@ const ArchivedReportFooter = (props) => { text={props.translate(`reportArchiveReasons.${archiveReason}`, { displayName: `${displayName}`, oldDisplayName: `${oldDisplayName}`, - policyName: `${policyName}`, + policyName: `${props.report.oldPolicyName}`, })} shouldRenderHTML={archiveReason !== CONST.REPORT.ARCHIVE_REASON.DEFAULT} /> @@ -86,8 +79,5 @@ export default compose( personalDetails: { key: ONYXKEYS.PERSONAL_DETAILS, }, - policies: { - key: ONYXKEYS.COLLECTION.POLICY, - }, }), )(ArchivedReportFooter); From e5b80286d76dfb6714401c61835b35190722a55a Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Fri, 1 Apr 2022 00:20:34 -0700 Subject: [PATCH 048/596] Use oldPolicyName for ReportWelcomeMessage --- src/components/ReportWelcomeText.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ReportWelcomeText.js b/src/components/ReportWelcomeText.js index d60c8bd6c745..630d65cb5d4d 100644 --- a/src/components/ReportWelcomeText.js +++ b/src/components/ReportWelcomeText.js @@ -91,7 +91,9 @@ const ReportWelcomeText = (props) => { {props.translate('reportActionsView.beginningOfChatHistoryPolicyExpenseChatPartTwo')} - {lodashGet(props.policies, [`${ONYXKEYS.COLLECTION.POLICY}${props.report.policyID}`, 'name'], Localize.translateLocal('workspace.common.unavailable'))} + {lodashGet(props.policies, [`${ONYXKEYS.COLLECTION.POLICY}${props.report.policyID}`, 'name']) + || lodashGet(props.report, 'oldPolicyName') + || Localize.translateLocal('workspace.common.unavailable')} {props.translate('reportActionsView.beginningOfChatHistoryPolicyExpenseChatPartThree')} From 511fbf67d33a5bb66a16142acdeb919b8c7584cb Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Fri, 1 Apr 2022 00:31:29 -0700 Subject: [PATCH 049/596] Use oldPolicyName in sidebar links too --- src/libs/OptionsListUtils.js | 3 ++- src/pages/home/sidebar/SidebarLinks.js | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 8c600a2cb4eb..de7d7226aa31 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -273,7 +273,8 @@ function createOption(personalDetailList, report, { let text; let alternateText; if (isChatRoom || isPolicyExpenseChat) { - text = lodashGet(report, ['reportName'], ''); + text = lodashGet(report, 'reportName') + || lodashGet(report, 'oldPolicyName', ''); alternateText = (showChatPreviewLine && !forcePolicyNamePreview && lastMessageText) ? lastMessageText : subtitle; diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 554dc7c1a2d3..00945434275a 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -38,8 +38,13 @@ const propTypes = { /* Onyx Props */ /** List of reports */ reports: PropTypes.objectOf(PropTypes.shape({ + /** ID of the report */ reportID: PropTypes.number, + + /** Name of the report */ reportName: PropTypes.string, + + /** Number of unread actions on the report */ unreadActionCount: PropTypes.number, })), From 67ba2daa83ef16b3978f82007589b3f75d90cb6a Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Fri, 1 Apr 2022 01:04:34 -0700 Subject: [PATCH 050/596] Add unit test for OptionsListUtils change --- tests/unit/OptionsListUtilsTest.js | 106 +++++++++++++++++------------ 1 file changed, 64 insertions(+), 42 deletions(-) diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index dd4013416391..0db091823c0c 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -7,99 +7,113 @@ import CONST from '../../src/CONST'; import * as Report from '../../src/libs/actions/Report'; describe('OptionsListUtils', () => { + let reportCount = 0; + // Given a set of reports with both single participants and multiple participants some pinned and some not const REPORTS = { - 1: { + [++reportCount]: { lastVisitedTimestamp: 1610666739295, lastMessageTimestamp: 1, isPinned: false, - reportID: 1, + reportID: reportCount, participants: ['tonystark@expensify.com', 'reedrichards@expensify.com'], reportName: 'Iron Man, Mister Fantastic', unreadActionCount: 1, }, - 2: { + [++reportCount]: { lastVisitedTimestamp: 1610666739296, lastMessageTimestamp: 1, isPinned: false, - reportID: 2, + reportID: reportCount, participants: ['peterparker@expensify.com'], reportName: 'Spider-Man', unreadActionCount: 1, }, // This is the only report we are pinning in this test - 3: { + [++reportCount]: { lastVisitedTimestamp: 1610666739297, lastMessageTimestamp: 1, isPinned: true, - reportID: 3, + reportID: reportCount, participants: ['reedrichards@expensify.com'], reportName: 'Mister Fantastic', unreadActionCount: 0, }, - 4: { + [++reportCount]: { lastVisitedTimestamp: 1610666739298, lastMessageTimestamp: 1, isPinned: false, - reportID: 4, + reportID: reportCount, participants: ['tchalla@expensify.com'], reportName: 'Black Panther', unreadActionCount: 1, }, - 5: { + [++reportCount]: { lastVisitedTimestamp: 1610666739299, lastMessageTimestamp: 1, isPinned: false, - reportID: 5, + reportID: reportCount, participants: ['suestorm@expensify.com'], reportName: 'Invisible Woman', unreadActionCount: 1, }, - 6: { + [++reportCount]: { lastVisitedTimestamp: 1610666739300, lastMessageTimestamp: 1, isPinned: false, - reportID: 6, + reportID: reportCount, participants: ['thor@expensify.com'], reportName: 'Thor', unreadActionCount: 0, }, // Note: This report has the largest lastMessageTimestamp - 7: { + [++reportCount]: { lastVisitedTimestamp: 1610666739301, lastMessageTimestamp: 1611282169, isPinned: false, - reportID: 7, + reportID: reportCount, participants: ['steverogers@expensify.com'], reportName: 'Captain America', unreadActionCount: 1, }, // Note: This report has no lastMessageTimestamp - 8: { + [++reportCount]: { lastVisitedTimestamp: 1610666739301, lastMessageTimestamp: 0, isPinned: false, - reportID: 8, + reportID: reportCount, participants: ['galactus_herald@expensify.com'], reportName: 'Silver Surfer', unreadActionCount: 0, }, // Note: This report has an IOU - 9: { + [++reportCount]: { lastVisitedTimestamp: 1610666739302, lastMessageTimestamp: 1611282168, isPinned: false, - reportID: 9, + reportID: reportCount, participants: ['mistersinister@marauders.com'], reportName: 'Mister Sinister', unreadActionCount: 0, iouReportID: 100, hasOutstandingIOU: true, }, + + // This report is an archived room – it does not have a name and instead falls back on oldPolicyName + [++reportCount]: { + lastVisitedTimestamp: 1610666739200, + lastMessageTimestamp: 1, + reportID: reportCount, + isPinned: false, + participants: ['captain_britain@expensify.com', 'captain_america@expensify.com'], + reportName: '', + oldPolicyName: "SHIELD's workspace", + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + }, }; // And a set of personalDetails some with existing reports and some without @@ -152,11 +166,11 @@ describe('OptionsListUtils', () => { const REPORTS_WITH_CONCIERGE = { ...REPORTS, - 10: { + [++reportCount]: { lastVisitedTimestamp: 1610666739302, lastMessageTimestamp: 1, isPinned: false, - reportID: 10, + reportID: reportCount, participants: ['concierge@expensify.com'], reportName: 'Concierge', unreadActionCount: 1, @@ -165,11 +179,11 @@ describe('OptionsListUtils', () => { const REPORTS_WITH_CHRONOS = { ...REPORTS, - 11: { + [++reportCount]: { lastVisitedTimestamp: 1610666739302, lastMessageTimestamp: 1, isPinned: false, - reportID: 10, + reportID: reportCount, participants: ['chronos@expensify.com'], reportName: 'Chronos', unreadActionCount: 1, @@ -178,11 +192,11 @@ describe('OptionsListUtils', () => { const REPORTS_WITH_RECEIPTS = { ...REPORTS, - 12: { + [++reportCount]: { lastVisitedTimestamp: 1610666739302, lastMessageTimestamp: 1, isPinned: false, - reportID: 10, + reportID: reportCount, participants: ['receipts@expensify.com'], reportName: 'Receipts', unreadActionCount: 1, @@ -191,20 +205,20 @@ describe('OptionsListUtils', () => { const REPORTS_WITH_MORE_PINS = { ...REPORTS, - 13: { + [++reportCount]: { lastVisitedTimestamp: 1610666739302, lastMessageTimestamp: 1, isPinned: true, - reportID: 13, + reportID: reportCount, participants: ['d_email@email.com'], reportName: 'D report name', unreadActionCount: 0, }, - 14: { + [++reportCount]: { lastVisitedTimestamp: 1610666732302, lastMessageTimestamp: 1, isPinned: true, - reportID: 14, + reportID: reportCount, participants: ['z_email@email.com'], reportName: 'Z Report Name', unreadActionCount: 0, @@ -569,11 +583,11 @@ describe('OptionsListUtils', () => { ...REPORTS, // Note: This report has no lastMessageTimestamp but is also pinned - 10: { + [++reportCount]: { lastVisitedTimestamp: 1610666739300, lastMessageTimestamp: 0, isPinned: true, - reportID: 10, + reportID: reportCount, participants: ['captain_britain@expensify.com'], reportName: 'Captain Britain', }, @@ -600,16 +614,20 @@ describe('OptionsListUtils', () => { expect(results.personalDetails.length).toBe(0); // And the most recent pinned report is first in the list of reports - expect(results.recentReports[0].login).toBe('captain_britain@expensify.com'); + let index = 0; + expect(results.recentReports[index].login).toBe('captain_britain@expensify.com'); // And the third report is the report with an IOU debt - expect(results.recentReports[2].login).toBe('mistersinister@marauders.com'); + index += 2; + expect(results.recentReports[index].login).toBe('mistersinister@marauders.com'); // And the fourth report is the report with a draft comment - expect(results.recentReports[3].text).toBe('tonystark@expensify.com, reedrichards@expensify.com'); + expect(results.recentReports[++index].text).toBe('tonystark@expensify.com, reedrichards@expensify.com'); // And the fifth report is the report with the lastMessage timestamp - expect(results.recentReports[4].login).toBe('steverogers@expensify.com'); + expect(results.recentReports[++index].login).toBe('steverogers@expensify.com'); + + expect(_.last(results.recentReports).text).toBe("SHIELD's workspace"); }); }); @@ -628,28 +646,32 @@ describe('OptionsListUtils', () => { // Pinned reports are always on the top in alphabetical order regardless of whether they are unread or have IOU debt. // D report name (Alphabetically first among pinned reports) - expect(results.recentReports[0].login).toBe('d_email@email.com'); + let index = 0; + expect(results.recentReports[index].login).toBe('d_email@email.com'); // Mister Fantastic report name (Alphabetically second among pinned reports) - expect(results.recentReports[1].login).toBe('reedrichards@expensify.com'); + expect(results.recentReports[++index].login).toBe('reedrichards@expensify.com'); // Z report name (Alphabetically third among pinned reports) - expect(results.recentReports[2].login).toBe('z_email@email.com'); + expect(results.recentReports[++index].login).toBe('z_email@email.com'); // Unpinned report name ordered alphabetically after pinned reports // Black Panther report name has unread message - expect(results.recentReports[3].login).toBe('tchalla@expensify.com'); + expect(results.recentReports[++index].text).toBe("SHIELD's workspace"); + + expect(results.recentReports[++index].login).toBe('tchalla@expensify.com'); // Captain America report name has unread message - expect(results.recentReports[4].login).toBe('steverogers@expensify.com'); + expect(results.recentReports[++index].login).toBe('steverogers@expensify.com'); // Invisible woman report name has unread message - expect(results.recentReports[5].login).toBe('suestorm@expensify.com'); + expect(results.recentReports[++index].login).toBe('suestorm@expensify.com'); // Mister Sinister report name has IOU debt - expect(results.recentReports[7].login).toBe('mistersinister@marauders.com'); + index += 2; + expect(results.recentReports[index].login).toBe('mistersinister@marauders.com'); // Spider-Man report name is last report and has unread message - expect(results.recentReports[8].login).toBe('peterparker@expensify.com'); + expect(results.recentReports[++index].login).toBe('peterparker@expensify.com'); })); }); From 048d345cfe059938b921bfaa630b186a1556496d Mon Sep 17 00:00:00 2001 From: sahil Date: Mon, 4 Apr 2022 01:45:20 +0530 Subject: [PATCH 051/596] create confirmation screen --- src/languages/en.js | 8 ++ src/languages/es.js | 8 ++ src/pages/settings/ConfirmationScreen.js | 94 ++++++++++++++++++++++++ src/styles/utilities/spacing.js | 4 + 4 files changed, 114 insertions(+) create mode 100644 src/pages/settings/ConfirmationScreen.js diff --git a/src/languages/en.js b/src/languages/en.js index 6feddf407e3c..634a3359d641 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -337,6 +337,14 @@ export default { newPassword: 'Your password must have at least 8 characters, 1 capital letter, 1 lowercase letter, and 1 number.', }, }, + confirmationScreen: { + passwordUpdated: 'Password updated!', + allSet: 'You’re all set, keep your new password safe.', + set2FAPartOne: 'Set up', + set2FAPartTwo: 'two-factor authentication', + set2FAPartThree: 'for additional account security.', + gotIt: 'Got it', + }, addPayPalMePage: { enterYourUsernameToGetPaidViaPayPal: 'Enter your username to get paid back via PayPal.', payPalMe: 'PayPal.me/', diff --git a/src/languages/es.js b/src/languages/es.js index 7e750702033a..9d1635cb3875 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -337,6 +337,14 @@ export default { newPassword: 'Su contraseña debe tener al menos 8 caracteres, 1 letra mayúscula, 1 letra minúscula y 1 número.', }, }, + confirmationScreen: { + passwordUpdated: 'Contraseña actualizada!', + allSet: 'Todo está listo, guarda tu contraseña en un lugar seguro.', + set2FAPartOne: 'Configura', + set2FAPartTwo: 'autenticación de dos factores', + set2FAPartThree: 'para obtener seguridad adicional.', + gotIt: 'Ok, entendido', + }, addPayPalMePage: { enterYourUsernameToGetPaidViaPayPal: 'Escribe tu nombre de usuario para que otros puedan pagarte a través de PayPal.', payPalMe: 'PayPal.me/', diff --git a/src/pages/settings/ConfirmationScreen.js b/src/pages/settings/ConfirmationScreen.js new file mode 100644 index 000000000000..ef63d06ab6c3 --- /dev/null +++ b/src/pages/settings/ConfirmationScreen.js @@ -0,0 +1,94 @@ +import React from 'react'; +import {Image, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import PropTypes from 'prop-types'; +import Text from '../../components/Text'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import styles from '../../styles/styles'; +import confettiPop from '../../../assets/images/confetti-pop.gif'; +import Button from '../../components/Button'; +import FixedFooter from '../../components/FixedFooter'; +import * as Link from '../../libs/actions/Link'; +import compose from '../../libs/compose'; +import ONYXKEYS from '../../ONYXKEYS'; +import Navigation from '../../libs/Navigation/Navigation'; + +const propTypes = { + /* Onyx Props */ + + /** Holds information about the users account that is logging in */ + account: PropTypes.shape({ + /** Whether or not two factor authentication is enabled */ + requiresTwoFactorAuth: PropTypes.bool, + }), + + ...withLocalizePropTypes, +}; + +const defaultProps = { + account: {}, +}; + +const ConfirmationScreen = props => ( + <> + + + + + {props.translate('confirmationScreen.passwordUpdated')} + + + + {props.translate('confirmationScreen.allSet')} + + {!props.requiresTwoFactorAuth && ( + <> + + {' '} + {props.translate('confirmationScreen.set2FAPartOne')} + + Link.openExternalLink('')} + > + {' '} + {props.translate('confirmationScreen.set2FAPartTwo')} + {' '} + + {props.translate('confirmationScreen.set2FAPartThree')} + + )} + + + + +