diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index d64c279c155d..68c269fe1bbe 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -3,6 +3,8 @@
+
+
/dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- 387B40C2520CE2773517F705 /* [CP] Check Pods Manifest.lock */ = {
+ 67625BD6E56D7B445CE0B75D /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -334,14 +334,14 @@
outputFileListPaths = (
);
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-ReactNativeChat-checkManifestLockResult.txt",
+ "$(DERIVED_FILE_DIR)/Pods-ReactNativeChat-ReactNativeChatTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- ABE62262A44D31B78B23818B /* [CP] Copy Pods Resources */ = {
+ 740ED6E57E4D77AE97454014 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -359,7 +359,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeChat-ReactNativeChatTests/Pods-ReactNativeChat-ReactNativeChatTests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
- EC2DD7546E5E118A6D3073B2 /* [CP] Copy Pods Resources */ = {
+ 930B6ED2A0414681598DB7E1 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -429,7 +429,7 @@
/* Begin XCBuildConfiguration section */
00E356F61AD99517003FC87E /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 4DAAA8DFB4A4EF50B076BE3D /* Pods-ReactNativeChat-ReactNativeChatTests.debug.xcconfig */;
+ baseConfigurationReference = BA6C613DE6755E40F7EDDC68 /* Pods-ReactNativeChat-ReactNativeChatTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -453,7 +453,7 @@
};
00E356F71AD99517003FC87E /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 45D4E4039B764281ECADF639 /* Pods-ReactNativeChat-ReactNativeChatTests.release.xcconfig */;
+ baseConfigurationReference = 1CFBC073CF30A72CCF109A40 /* Pods-ReactNativeChat-ReactNativeChatTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -474,7 +474,7 @@
};
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 05C75E1764EA836C54E53E91 /* Pods-ReactNativeChat.debug.xcconfig */;
+ baseConfigurationReference = 9B9FEA91E46D2B2609028E69 /* Pods-ReactNativeChat.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
@@ -503,7 +503,7 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = D232EFF53A6330C07E8BC0F8 /* Pods-ReactNativeChat.release.xcconfig */;
+ baseConfigurationReference = C6C1D4F5C262ACDAF1800CAD /* Pods-ReactNativeChat.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
diff --git a/ios/ReactNativeChat/Info.plist b/ios/ReactNativeChat/Info.plist
index 7fcd8cdc638b..56b565943add 100644
--- a/ios/ReactNativeChat/Info.plist
+++ b/ios/ReactNativeChat/Info.plist
@@ -23,33 +23,33 @@
CFBundleVersion
60
ITSAppUsesNonExemptEncryption
-
+
LSRequiresIPhoneOS
-
+
NSAppTransportSecurity
NSAllowsArbitraryLoads
-
+
NSExceptionDomains
localhost
NSExceptionAllowsInsecureHTTPLoads
-
+
NSIncludesSubdomains
-
+
www.expensify.com.dev
NSExceptionAllowsInsecureHTTPLoads
-
+
NSIncludesSubdomains
-
+
NSLocationWhenInUseUsageDescription
-
+
UIAppFonts
GTAmericaExp-Bold.otf
@@ -70,7 +70,13 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ NSPhotoLibraryUsageDescription
+ Your photos are used to create chat attachments.
+ NSCameraUsageDescription
+ Your camera is used to create chat attachments.
+ NSPhotoLibraryAddUsageDescription
+ Your camera roll is used to store chat attachments.
UIViewControllerBasedStatusBarAppearance
-
+
diff --git a/package-lock.json b/package-lock.json
index 0776bb1606f6..d3412f26b307 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2372,6 +2372,11 @@
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"dev": true
},
+ "@yarnpkg/lockfile": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
+ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ=="
+ },
"abab": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.4.tgz",
@@ -8228,6 +8233,104 @@
"path-exists": "^4.0.0"
}
},
+ "find-yarn-workspace-root": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-1.2.1.tgz",
+ "integrity": "sha512-dVtfb0WuQG+8Ag2uWkbG79hOUzEsRrhBzgfn86g2sJPkzmcpGdghbNTfUKGTxymFrY/tLIodDzLoW9nOJ4FY8Q==",
+ "requires": {
+ "fs-extra": "^4.0.3",
+ "micromatch": "^3.1.4"
+ },
+ "dependencies": {
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "fs-extra": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
+ "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.3.1",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "extglob": "^2.0.4",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^6.0.2",
+ "nanomatch": "^1.2.9",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.2"
+ }
+ },
+ "to-regex-range": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+ "requires": {
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1"
+ }
+ }
+ }
+ },
"findup-sync": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz",
@@ -10815,6 +10918,14 @@
"graceful-fs": "^4.1.9"
}
},
+ "klaw-sync": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
+ "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
+ "requires": {
+ "graceful-fs": "^4.1.11"
+ }
+ },
"kleur": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
@@ -13634,6 +13745,93 @@
"resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
},
+ "patch-package": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.2.2.tgz",
+ "integrity": "sha512-YqScVYkVcClUY0v8fF0kWOjDYopzIM8e3bj/RU1DPeEF14+dCGm6UeOYm4jvCyxqIEQ5/eJzmbWfDWnUleFNMg==",
+ "requires": {
+ "@yarnpkg/lockfile": "^1.1.0",
+ "chalk": "^2.4.2",
+ "cross-spawn": "^6.0.5",
+ "find-yarn-workspace-root": "^1.2.1",
+ "fs-extra": "^7.0.1",
+ "is-ci": "^2.0.0",
+ "klaw-sync": "^6.0.0",
+ "minimist": "^1.2.0",
+ "rimraf": "^2.6.3",
+ "semver": "^5.6.0",
+ "slash": "^2.0.0",
+ "tmp": "^0.0.33"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+ },
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
+ "slash": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
"path-browserify": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
@@ -14681,6 +14879,11 @@
"resolved": "https://registry.npmjs.org/react-native-config/-/react-native-config-1.3.3.tgz",
"integrity": "sha512-uc84IFVLqu2dC7Zutg3OlK6RZYvJlGbIdi0v1ZxjUhU379Nz9IkZR1t8J1L5QSnGV8885mtwWDPJRdQyVNFsAw=="
},
+ "react-native-image-picker": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-2.3.3.tgz",
+ "integrity": "sha512-i/7JDnxKkUGSbFY2i7YqFNn3ncJm1YlcrPKXrXmJ/YUElz8tHkuwknuqBd9QCJivMfHX41cmq4XvdBDwIOtO+A=="
+ },
"react-native-keyboard-spacer": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/react-native-keyboard-spacer/-/react-native-keyboard-spacer-0.4.1.tgz",
diff --git a/package.json b/package.json
index 7bf4e01296a5..4c98fd3d4cd8 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"android-build": "fastlane android build",
"test": "jest",
"lint": "eslint .",
+ "postinstall": "patch-package",
"postversion": "react-native-version",
"print-version": "echo $npm_package_version"
},
@@ -38,6 +39,7 @@
"lodash.orderby": "^4.6.0",
"moment": "^2.27.0",
"moment-timezone": "^0.5.31",
+ "patch-package": "^6.2.2",
"prop-types": "^15.7.2",
"pusher-js": "^7.0.0",
"react": "^16.13.1",
@@ -45,6 +47,7 @@
"react-dom": "^16.13.1",
"react-native": "0.63.2",
"react-native-config": "^1.3.3",
+ "react-native-image-picker": "^2.3.3",
"react-native-keyboard-spacer": "^0.4.1",
"react-native-render-html": "^4.2.3",
"react-native-safe-area-context": "^3.1.4",
diff --git a/patches/react-native+0.63.2.patch b/patches/react-native+0.63.2.patch
new file mode 100644
index 000000000000..bc5e41d5a9d5
--- /dev/null
+++ b/patches/react-native+0.63.2.patch
@@ -0,0 +1,13 @@
+diff --git a/node_modules/react-native/Libraries/Image/RCTImageLoader.mm b/node_modules/react-native/Libraries/Image/RCTImageLoader.mm
+index 3571647..ffb64b1 100644
+--- a/node_modules/react-native/Libraries/Image/RCTImageLoader.mm
++++ b/node_modules/react-native/Libraries/Image/RCTImageLoader.mm
+@@ -1040,7 +1040,7 @@ - (BOOL)canHandleRequest:(NSURLRequest *)request
+
+ - (id)sendRequest:(NSURLRequest *)request withDelegate:(id)delegate
+ {
+- __block RCTImageLoaderCancellationBlock requestToken;
++ __block RCTImageLoaderCancellationBlock requestToken = ^{};
+ requestToken = [self loadImageWithURLRequest:request callback:^(NSError *error, UIImage *image) {
+ if (error) {
+ [delegate URLRequest:requestToken didCompleteWithError:error];
diff --git a/src/components/AnchorForCommentsOnly/index.native.js b/src/components/AnchorForCommentsOnly/index.native.js
index 2cdd9956a648..d4ed3dfc944b 100644
--- a/src/components/AnchorForCommentsOnly/index.native.js
+++ b/src/components/AnchorForCommentsOnly/index.native.js
@@ -8,7 +8,7 @@ import {Linking, StyleSheet, Text} from 'react-native';
const propTypes = {
// The URL to open
- href: PropTypes.string.isRequired,
+ href: PropTypes.string,
// What headers to send to the linked page (usually noopener and noreferrer)
// This is unused in native, but is here for parity with web
@@ -28,6 +28,7 @@ const propTypes = {
};
const defaultProps = {
+ href: '',
rel: null,
target: null,
children: null,
diff --git a/src/lib/ImagePicker/index.js b/src/lib/ImagePicker/index.js
new file mode 100644
index 000000000000..2129bd12face
--- /dev/null
+++ b/src/lib/ImagePicker/index.js
@@ -0,0 +1,31 @@
+// Implementation adapted from https://github.com/QuantumBA/foqum-react-native-document-picker/blob/master/web/index.js
+
+const ImagePicker = {
+ showImagePicker(options, callback) {
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.onchange = function () {
+ const file = input.files[0];
+ file.uri = URL.createObjectURL(file);
+
+ callback(file);
+ };
+
+ input.click();
+ },
+
+ /**
+ *
+ * The data returned from `showImagePicker` is different on web and mobile, so use this function to ensure the
+ * data we send to the xhr will be handled properly by the API. On web, we just want to send the file data returned
+ * from the input.
+ *
+ * @param {object} fileData
+ * @returns {object}
+ */
+ getDataForUpload(fileData) {
+ return fileData;
+ },
+};
+
+export default ImagePicker;
diff --git a/src/lib/ImagePicker/index.native.js b/src/lib/ImagePicker/index.native.js
new file mode 100644
index 000000000000..e8fe1a5ad576
--- /dev/null
+++ b/src/lib/ImagePicker/index.native.js
@@ -0,0 +1,17 @@
+/**
+ * The react native document picker already works for iOS/Android, so just export the imported document picker
+ */
+import RNImagePicker from 'react-native-image-picker';
+
+const ImagePicker = RNImagePicker;
+
+/*
+ * The data returned from `showImagePicker` is different on web and mobile, so use this function to ensure the data we
+ * send to the xhr will be handled properly.
+ */
+ImagePicker.getDataForUpload = fileData => ({
+ name: fileData.fileName || 'chat_attachment',
+ type: fileData.type,
+ uri: fileData.uri,
+});
+export default ImagePicker;
diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js
index c7d5f3bfc7c4..a86f0bc72c80 100644
--- a/src/lib/actions/Report.js
+++ b/src/lib/actions/Report.js
@@ -369,13 +369,15 @@ function fetchOrCreateChatReport(participants) {
*
* @param {number} reportID
* @param {string} text
+ * @param {object} file
*/
-function addAction(reportID, text) {
+function addAction(reportID, text, file) {
const actionKey = `${IONKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`;
// Convert the comment from MD into HTML because that's how it is stored in the database
const parser = new ExpensiMark();
const htmlComment = parser.replace(text);
+ const isAttachment = _.isEmpty(text) && file !== undefined;
// The new sequence number will be one higher than the highest
let highestSequenceNumber = reportMaxSequenceNumbers[reportID] || 0;
@@ -405,20 +407,22 @@ function addAction(reportID, text) {
message: [
{
type: 'COMMENT',
- html: htmlComment,
+ html: isAttachment ? 'Uploading Attachment...' : htmlComment,
// Remove HTML from text when applying optimistic offline comment
- text: htmlComment.replace(/<[^>]*>?/gm, ''),
+ text: isAttachment ? 'Uploading Attachment...'
+ : htmlComment.replace(/<[^>]*>?/gm, ''),
}
],
isFirstItem: false,
- isAttachmentPlaceHolder: false,
+ isAttachment,
}
});
queueRequest('Report_AddComment', {
reportID,
reportComment: htmlComment,
+ file
});
}
diff --git a/src/page/home/report/ReportActionCompose.js b/src/page/home/report/ReportActionCompose.js
index 4880aae22c3f..cf1650647d19 100644
--- a/src/page/home/report/ReportActionCompose.js
+++ b/src/page/home/report/ReportActionCompose.js
@@ -7,11 +7,9 @@ import TextInputFocusable from '../../../components/TextInputFocusable';
import sendIcon from '../../../../assets/images/icon-send.png';
import IONKEYS from '../../../IONKEYS';
import paperClipIcon from '../../../../assets/images/icon-paper-clip.png';
-import CONFIG from '../../../CONFIG';
-import openURLInNewTab from '../../../lib/openURLInNewTab';
+import ImagePicker from '../../../lib/ImagePicker';
import withIon from '../../../components/withIon';
-import {saveReportComment} from '../../../lib/actions/Report';
-
+import {addAction, saveReportComment} from '../../../lib/actions/Report';
const propTypes = {
// A method to call when the form is submitted
@@ -37,6 +35,7 @@ class ReportActionCompose extends React.Component {
this.submitForm = this.submitForm.bind(this);
this.triggerSubmitShortcut = this.triggerSubmitShortcut.bind(this);
this.submitForm = this.submitForm.bind(this);
+ this.showAttachmentPicker = this.showAttachmentPicker.bind(this);
this.comment = '';
}
@@ -104,13 +103,44 @@ class ReportActionCompose extends React.Component {
this.updateComment('');
}
+ /**
+ * Handle the attachment icon being tapped
+ *
+ * @param {SyntheticEvent} e
+ */
+ showAttachmentPicker(e) {
+ e.preventDefault();
+
+ /**
+ * See https://github.com/react-native-community/react-native-image-picker/blob/master/docs/Reference.md#options
+ * for option definitions
+ */
+ const options = {
+ storageOptions: {
+ skipBackup: true,
+ },
+ };
+
+ ImagePicker.showImagePicker(options, (response) => {
+ if (response.didCancel) {
+ return;
+ }
+
+ if (response.error) {
+ console.error(`Error occurred picking image: ${response.error}`);
+ return;
+ }
+
+ addAction(this.props.reportID, '', ImagePicker.getDataForUpload(response));
+ });
+ }
+
render() {
- const href = `${CONFIG.PUSHER.AUTH_URL}/report?reportID=${this.props.reportID}&shouldScrollToLastUnread=true`;
return (
openURLInNewTab(href)}
+ onPress={this.showAttachmentPicker}
style={[styles.chatItemAttachButton]}
underlayColor={colors.componentBG}
>
diff --git a/src/page/home/report/ReportActionItemFragment.js b/src/page/home/report/ReportActionItemFragment.js
index 12f123efc9e7..b92d9f0dd0ef 100644
--- a/src/page/home/report/ReportActionItemFragment.js
+++ b/src/page/home/report/ReportActionItemFragment.js
@@ -1,9 +1,12 @@
import React from 'react';
import HTML from 'react-native-render-html';
-import {Linking, Platform} from 'react-native';
+import {
+ Linking, ActivityIndicator, View, Platform
+} from 'react-native';
+import PropTypes from 'prop-types';
import Str from '../../../lib/Str';
import ReportActionFragmentPropTypes from './ReportActionFragmentPropTypes';
-import styles, {webViewStyles} from '../../../style/StyleSheet';
+import styles, {webViewStyles, colors} from '../../../style/StyleSheet';
import Text from '../../../components/Text';
import AnchorForCommentsOnly from '../../../components/AnchorForCommentsOnly';
import {getAuthToken} from '../../../lib/API';
@@ -11,6 +14,13 @@ import {getAuthToken} from '../../../lib/API';
const propTypes = {
// The message fragment needing to be displayed
fragment: ReportActionFragmentPropTypes.isRequired,
+
+ // Is this fragment an attachment?
+ isAttachment: PropTypes.bool,
+};
+
+const defaultProps = {
+ isAttachment: false
};
class ReportActionItemFragment extends React.PureComponent {
@@ -57,6 +67,19 @@ class ReportActionItemFragment extends React.PureComponent {
const {fragment} = this.props;
switch (fragment.type) {
case 'COMMENT':
+ // If this is an attachment placeholder, return the placeholder component
+ if (this.props.isAttachment && fragment.html === fragment.text) {
+ return (
+
+
+
+ );
+ }
+
// Only render HTML if we have html in the fragment
return fragment.html !== fragment.text
? (
@@ -113,6 +136,7 @@ class ReportActionItemFragment extends React.PureComponent {
}
ReportActionItemFragment.propTypes = propTypes;
+ReportActionItemFragment.defaultProps = defaultProps;
ReportActionItemFragment.displayName = 'ReportActionItemFragment';
export default ReportActionItemFragment;
diff --git a/src/page/home/report/ReportActionItemMessage.js b/src/page/home/report/ReportActionItemMessage.js
index 7566e149750f..906596e880ff 100644
--- a/src/page/home/report/ReportActionItemMessage.js
+++ b/src/page/home/report/ReportActionItemMessage.js
@@ -15,6 +15,7 @@ const ReportActionItemMessage = ({action}) => (
))}
>
diff --git a/src/style/StyleSheet.js b/src/style/StyleSheet.js
index d0107b14d52a..b2c2c22111ed 100644
--- a/src/style/StyleSheet.js
+++ b/src/style/StyleSheet.js
@@ -9,6 +9,7 @@ const colors = {
black: '#000000',
blue: '#2EAAE2',
border: '#ECECEC',
+ borderLight: '#E0E0E0',
green: '#2ECB70',
heading: '#37444C',
icon: '#C6C9CA',
@@ -584,6 +585,17 @@ const styles = {
width: 39,
},
+ chatItemAttachmentPlaceholder: {
+ backgroundColor: colors.border,
+ borderColor: colors.borderLight,
+ borderWidth: 1,
+ borderRadius: 8,
+ height: 150,
+ textAlign: 'center',
+ verticalAlign: 'middle',
+ width: 200,
+ },
+
chatSwitcherInputClear: {
alignSelf: 'center',
},