diff --git a/assets/images/avatars/room.png b/assets/images/avatars/room.png
new file mode 100644
index 000000000000..dca457fbfdf7
Binary files /dev/null and b/assets/images/avatars/room.png differ
diff --git a/assets/images/expensify_wordmark_white.svg b/assets/images/expensify_wordmark_white.svg
new file mode 100644
index 000000000000..1ad7640b2602
--- /dev/null
+++ b/assets/images/expensify_wordmark_white.svg
@@ -0,0 +1,26 @@
+
+
+
diff --git a/assets/images/qrcode.svg b/assets/images/qrcode.svg
new file mode 100644
index 000000000000..ead1d765b46a
--- /dev/null
+++ b/assets/images/qrcode.svg
@@ -0,0 +1,3 @@
+
diff --git a/config/webpack/webpack.common.js b/config/webpack/webpack.common.js
index 45b855a72730..7c6d1b9de3a9 100644
--- a/config/webpack/webpack.common.js
+++ b/config/webpack/webpack.common.js
@@ -21,6 +21,8 @@ const includeModules = [
'react-native-flipper',
'react-native-google-places-autocomplete',
'@react-navigation/drawer',
+ 'react-native-qrcode-svg',
+ 'react-native-view-shot',
].join('|');
const envToLogoSuffixMap = {
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index fc30bf617a24..f0d6720080e8 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -541,6 +541,8 @@ PODS:
- RCTTypeSafety
- React-Core
- ReactCommon/turbomodule/core
+ - react-native-view-shot (3.6.0):
+ - React-Core
- react-native-webview (11.23.0):
- React-Core
- React-perflogger (0.71.2-alpha.3)
@@ -698,7 +700,7 @@ PODS:
- RNScreens (3.17.0):
- React-Core
- React-RCTImage
- - RNSVG (13.5.0):
+ - RNSVG (13.9.0):
- React-Core
- SDWebImage (5.11.1):
- SDWebImage/Core (= 5.11.1)
@@ -781,6 +783,7 @@ DEPENDENCIES:
- react-native-quick-sqlite (from `../node_modules/react-native-quick-sqlite`)
- react-native-render-html (from `../node_modules/react-native-render-html`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
+ - react-native-view-shot (from `../node_modules/react-native-view-shot`)
- react-native-webview (from `../node_modules/react-native-webview`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
@@ -944,6 +947,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-render-html"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
+ react-native-view-shot:
+ :path: "../node_modules/react-native-view-shot"
react-native-webview:
:path: "../node_modules/react-native-webview"
React-perflogger:
@@ -1089,6 +1094,7 @@ SPEC CHECKSUMS:
react-native-quick-sqlite: bcc7a7a250a40222f18913a97cd356bf82d0a6c4
react-native-render-html: 96c979fe7452a0a41559685d2f83b12b93edac8c
react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a
+ react-native-view-shot: 705f999ac2a24e4e6c909c0ca65c732ed33ca2ff
react-native-webview: e771bc375f789ebfa02a26939a57dbc6fa897336
React-perflogger: 9f7c5a9d22d104ae0348f38235ee636d156b5938
React-RCTActionSheet: 43e74f3a54967bb1d0e9ff261bc076843fb50ca1
@@ -1120,7 +1126,7 @@ SPEC CHECKSUMS:
RNReactNativeHapticFeedback: 1e3efeca9628ff9876ee7cdd9edec1b336913f8c
RNReanimated: fbc356493970e3acddc15586b1bccb5eab3ff1ec
RNScreens: 0df01424e9e0ed7827200d6ed1087ddd06c493f9
- RNSVG: 38ca962c970dbce1ca38991a5aebf26d163f9efb
+ RNSVG: 53c661b76829783cdaf9b7a57258f3d3b4c28315
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608
diff --git a/package-lock.json b/package-lock.json
index 801117332f7c..141a7cb34f23 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -83,12 +83,14 @@
"react-native-permissions": "^3.0.1",
"react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#107b3786ae6bc155dec05c7fc5ee525d3421dc21",
"react-native-plaid-link-sdk": "^10.0.0",
+ "react-native-qrcode-svg": "^6.2.0",
"react-native-quick-sqlite": "^8.0.0-beta.2",
"react-native-reanimated": "3.0.0-rc.10",
"react-native-render-html": "6.3.1",
"react-native-safe-area-context": "4.4.1",
"react-native-screens": "3.17.0",
- "react-native-svg": "^13.5.0",
+ "react-native-svg": "^13.9.0",
+ "react-native-view-shot": "^3.6.0",
"react-native-web-lottie": "^1.4.4",
"react-native-webview": "^11.17.2",
"react-pdf": "5.7.2",
@@ -20909,6 +20911,11 @@
"version": "4.12.0",
"license": "MIT"
},
+ "node_modules/dijkstrajs": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
+ },
"node_modules/dir-compare": {
"version": "2.4.0",
"dev": true,
@@ -21594,6 +21601,11 @@
"node": ">= 4"
}
},
+ "node_modules/encode-utf8": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz",
+ "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw=="
+ },
"node_modules/encodeurl": {
"version": "1.0.2",
"license": "MIT",
@@ -33815,6 +33827,14 @@
"node": ">=8.0"
}
},
+ "node_modules/pngjs": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+ "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/pnp-webpack-plugin": {
"version": "1.6.4",
"dev": true,
@@ -34274,6 +34294,173 @@
"version": "14.18.24",
"license": "MIT"
},
+ "node_modules/qrcode": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz",
+ "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==",
+ "dependencies": {
+ "dijkstrajs": "^1.0.1",
+ "encode-utf8": "^1.0.3",
+ "pngjs": "^5.0.0",
+ "yargs": "^15.3.1"
+ },
+ "bin": {
+ "qrcode": "bin/qrcode"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/qrcode/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/qrcode/node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qrcode/node_modules/cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "node_modules/qrcode/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/qrcode/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "node_modules/qrcode/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/qrcode/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/qrcode/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/qrcode/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/qrcode/node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/qrcode/node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/qrcode/node_modules/yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "dependencies": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/qrcode/node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/qs": {
"version": "6.11.0",
"dev": true,
@@ -34945,6 +35132,20 @@
"react-native": ">=0.66.0"
}
},
+ "node_modules/react-native-qrcode-svg": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/react-native-qrcode-svg/-/react-native-qrcode-svg-6.2.0.tgz",
+ "integrity": "sha512-rb2PgUwT8QpQyReVYNvzRY84AHsMh81354Tnkfp6MfqRbcdJURhnBWLBOO11pLMS6eXiwlq4SkcQxy88hRq+Dw==",
+ "dependencies": {
+ "prop-types": "^15.8.0",
+ "qrcode": "^1.5.1"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": ">=0.63.4",
+ "react-native-svg": "^13.2.0"
+ }
+ },
"node_modules/react-native-quick-sqlite": {
"version": "8.0.0-beta.2",
"resolved": "https://registry.npmjs.org/react-native-quick-sqlite/-/react-native-quick-sqlite-8.0.0-beta.2.tgz",
@@ -35034,8 +35235,9 @@
}
},
"node_modules/react-native-svg": {
- "version": "13.5.0",
- "license": "MIT",
+ "version": "13.9.0",
+ "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.9.0.tgz",
+ "integrity": "sha512-Ey18POH0dA0ob/QiwCBVrxIiwflhYuw0P0hBlOHeY4J5cdbs8ngdKHeWC/Kt9+ryP6fNoEQ1PUgPYw2Bs/rp5Q==",
"dependencies": {
"css-select": "^5.1.0",
"css-tree": "^1.1.3"
@@ -35059,6 +35261,15 @@
"react-native-svg": ">=12.0.0"
}
},
+ "node_modules/react-native-view-shot": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/react-native-view-shot/-/react-native-view-shot-3.6.0.tgz",
+ "integrity": "sha512-QUYGaIaAxQwOTydUzqGMooBwrg455cuOQgTloZ+gPO1QCUuLRdncCqrEMwKW5eUnN5U8JGMKeFRll2m6egOxtA==",
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/react-native-web": {
"version": "0.18.12",
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.18.12.tgz",
@@ -55415,6 +55626,11 @@
}
}
},
+ "dijkstrajs": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
+ },
"dir-compare": {
"version": "2.4.0",
"dev": true,
@@ -55888,6 +56104,11 @@
"emojis-list": {
"version": "3.0.0"
},
+ "encode-utf8": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz",
+ "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw=="
+ },
"encodeurl": {
"version": "1.0.2"
},
@@ -63889,6 +64110,11 @@
}
}
},
+ "pngjs": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+ "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="
+ },
"pnp-webpack-plugin": {
"version": "1.6.4",
"dev": true,
@@ -64202,6 +64428,130 @@
"version": "0.3.8",
"dev": true
},
+ "qrcode": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz",
+ "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==",
+ "requires": {
+ "dijkstrajs": "^1.0.1",
+ "encode-utf8": "^1.0.3",
+ "pngjs": "^5.0.0",
+ "yargs": "^15.3.1"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
+ },
+ "cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "requires": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "requires": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "requires": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "requires": {
+ "p-limit": "^2.2.0"
+ }
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
+ },
+ "wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "requires": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ }
+ },
+ "yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ }
+ }
+ },
"qs": {
"version": "6.11.0",
"dev": true,
@@ -64719,6 +65069,15 @@
"integrity": "sha512-WqU44tYzQoR/cuufD6GI7vOWTLcL9RXuEqfGaCynHdh2rmj3SC+mSEmXpg/LG0Q4E1XivkjfgF9tAOdlbnLMHQ==",
"requires": {}
},
+ "react-native-qrcode-svg": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/react-native-qrcode-svg/-/react-native-qrcode-svg-6.2.0.tgz",
+ "integrity": "sha512-rb2PgUwT8QpQyReVYNvzRY84AHsMh81354Tnkfp6MfqRbcdJURhnBWLBOO11pLMS6eXiwlq4SkcQxy88hRq+Dw==",
+ "requires": {
+ "prop-types": "^15.8.0",
+ "qrcode": "^1.5.1"
+ }
+ },
"react-native-quick-sqlite": {
"version": "8.0.0-beta.2",
"resolved": "https://registry.npmjs.org/react-native-quick-sqlite/-/react-native-quick-sqlite-8.0.0-beta.2.tgz",
@@ -64781,7 +65140,9 @@
}
},
"react-native-svg": {
- "version": "13.5.0",
+ "version": "13.9.0",
+ "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.9.0.tgz",
+ "integrity": "sha512-Ey18POH0dA0ob/QiwCBVrxIiwflhYuw0P0hBlOHeY4J5cdbs8ngdKHeWC/Kt9+ryP6fNoEQ1PUgPYw2Bs/rp5Q==",
"requires": {
"css-select": "^5.1.0",
"css-tree": "^1.1.3"
@@ -64796,6 +65157,12 @@
"path-dirname": "^1.0.2"
}
},
+ "react-native-view-shot": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/react-native-view-shot/-/react-native-view-shot-3.6.0.tgz",
+ "integrity": "sha512-QUYGaIaAxQwOTydUzqGMooBwrg455cuOQgTloZ+gPO1QCUuLRdncCqrEMwKW5eUnN5U8JGMKeFRll2m6egOxtA==",
+ "requires": {}
+ },
"react-native-web": {
"version": "0.18.12",
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.18.12.tgz",
diff --git a/package.json b/package.json
index bc8d960badd3..96038fbca440 100644
--- a/package.json
+++ b/package.json
@@ -118,12 +118,14 @@
"react-native-permissions": "^3.0.1",
"react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#107b3786ae6bc155dec05c7fc5ee525d3421dc21",
"react-native-plaid-link-sdk": "^10.0.0",
+ "react-native-qrcode-svg": "^6.2.0",
"react-native-quick-sqlite": "^8.0.0-beta.2",
"react-native-reanimated": "3.0.0-rc.10",
"react-native-render-html": "6.3.1",
"react-native-safe-area-context": "4.4.1",
"react-native-screens": "3.17.0",
- "react-native-svg": "^13.5.0",
+ "react-native-svg": "^13.9.0",
+ "react-native-view-shot": "^3.6.0",
"react-native-web-lottie": "^1.4.4",
"react-native-webview": "^11.17.2",
"react-pdf": "5.7.2",
@@ -196,8 +198,8 @@
"metro-react-native-babel-preset": "^0.73.3",
"mock-fs": "^4.13.0",
"onchange": "^7.1.0",
- "prettier": "^2.8.8",
"portfinder": "^1.0.28",
+ "prettier": "^2.8.8",
"pusher-js-mock": "^0.3.3",
"react-native-clean-project": "^4.0.0-alpha4.0",
"react-native-flipper": "https://gitpkg.now.sh/facebook/flipper/react-native/react-native-flipper?9cacc9b59402550eae866e0e81e5f0c2f8203e6b",
diff --git a/src/CONST.js b/src/CONST.js
index ad98362484b6..e1335306d47a 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -1097,6 +1097,7 @@ const CONST = {
INFO: 'info',
},
REPORT_DETAILS_MENU_ITEM: {
+ SHARE_CODE: 'shareCode',
MEMBERS: 'member',
SETTINGS: 'settings',
LEAVE_ROOM: 'leaveRoom',
diff --git a/src/ROUTES.js b/src/ROUTES.js
index 1d5506f019e7..ec4730289e06 100644
--- a/src/ROUTES.js
+++ b/src/ROUTES.js
@@ -25,6 +25,7 @@ export default {
HOME: '',
SETTINGS: 'settings',
SETTINGS_PROFILE: 'settings/profile',
+ SETTINGS_SHARE_CODE: 'settings/shareCode',
SETTINGS_DISPLAY_NAME: 'settings/profile/display-name',
SETTINGS_TIMEZONE: 'settings/profile/timezone',
SETTINGS_TIMEZONE_SELECT: 'settings/profile/timezone/select',
@@ -60,6 +61,8 @@ export default {
REPORT,
REPORT_WITH_ID: 'r/:reportID',
getReportRoute: (reportID) => `r/${reportID}`,
+ REPORT_WITH_ID_DETAILS_SHARE_CODE: 'r/:reportID/details/shareCode',
+ getReportShareCodeRoute: (reportID) => `r/${reportID}/details/shareCode`,
SELECT_YEAR: 'select-year',
getYearSelectionRoute: (minYear, maxYear, currYear, backTo) => `select-year?min=${minYear}&max=${maxYear}&year=${currYear}&backTo=${backTo}`,
diff --git a/src/components/Icon/Expensicons.js b/src/components/Icon/Expensicons.js
index d3c7c2f4bc57..70503706f112 100644
--- a/src/components/Icon/Expensicons.js
+++ b/src/components/Icon/Expensicons.js
@@ -77,6 +77,7 @@ import Pin from '../../../assets/images/pin.svg';
import Plus from '../../../assets/images/plus.svg';
import Printer from '../../../assets/images/printer.svg';
import Profile from '../../../assets/images/profile.svg';
+import QrCode from '../../../assets/images/qrcode.svg';
import QuestionMark from '../../../assets/images/question-mark-circle.svg';
import Receipt from '../../../assets/images/receipt.svg';
import ReceiptSearch from '../../../assets/images/receipt-search.svg';
@@ -197,6 +198,7 @@ export {
Plus,
Printer,
Profile,
+ QrCode,
QuestionMark,
Receipt,
ReceiptSearch,
diff --git a/src/components/QRShare/QRShareWithDownload/index.js b/src/components/QRShare/QRShareWithDownload/index.js
new file mode 100644
index 000000000000..8cb7be79f7ad
--- /dev/null
+++ b/src/components/QRShare/QRShareWithDownload/index.js
@@ -0,0 +1,39 @@
+import React, {Component} from 'react';
+import fileDownload from '../../../libs/fileDownload';
+import QRShare from '..'
+import {qrShareDefaultProps, qrSharePropTypes} from '../propTypes';
+import getQrCodeFileName from '../getQrCodeDownloadFileName';
+
+class QRShareWithDownload extends Component {
+ qrShareRef = React.createRef();
+
+ constructor(props) {
+ super(props);
+
+ this.download = this.download.bind(this);
+ }
+
+ download() {
+ return new Promise((resolve, reject) => {
+ // eslint-disable-next-line es/no-optional-chaining
+ const svg = this.qrShareRef.current?.getSvg();
+ if (svg == null) return reject();
+
+ svg.toDataURL((dataURL) => resolve(fileDownload(dataURL, getQrCodeFileName(this.props.title))));
+ });
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+QRShareWithDownload.propTypes = qrSharePropTypes;
+QRShareWithDownload.defaultProps = qrShareDefaultProps;
+
+export default QRShareWithDownload;
diff --git a/src/components/QRShare/QRShareWithDownload/index.native.js b/src/components/QRShare/QRShareWithDownload/index.native.js
new file mode 100644
index 000000000000..27f05038733a
--- /dev/null
+++ b/src/components/QRShare/QRShareWithDownload/index.native.js
@@ -0,0 +1,36 @@
+import React, {Component} from 'react';
+import ViewShot from 'react-native-view-shot';
+import fileDownload from '../../../libs/fileDownload';
+import QRShare from '..'
+import {qrShareDefaultProps, qrSharePropTypes} from '../propTypes';
+import getQrCodeFileName from '../getQrCodeDownloadFileName';
+
+
+class QRShareWithDownload extends Component {
+ qrCodeScreenshotRef = React.createRef();
+
+ constructor(props) {
+ super(props);
+
+ this.download = this.download.bind(this);
+ }
+
+ download() {
+ return this.qrCodeScreenshotRef.current.capture().then((uri) => fileDownload(uri, getQrCodeFileName(this.props.title)));
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
+QRShareWithDownload.propTypes = qrSharePropTypes;
+QRShareWithDownload.defaultProps = qrShareDefaultProps;
+
+export default QRShareWithDownload;
diff --git a/src/components/QRShare/getQrCodeDownloadFileName.js b/src/components/QRShare/getQrCodeDownloadFileName.js
new file mode 100644
index 000000000000..cc3b38d42348
--- /dev/null
+++ b/src/components/QRShare/getQrCodeDownloadFileName.js
@@ -0,0 +1,4 @@
+// eslint-disable-next-line rulesdir/display-name-property
+const getQrCodeDownloadFileName = (title) => `${title}-ShareCode.png`;
+
+export default getQrCodeDownloadFileName;
diff --git a/src/components/QRShare/index.js b/src/components/QRShare/index.js
new file mode 100644
index 000000000000..014cddcf5090
--- /dev/null
+++ b/src/components/QRShare/index.js
@@ -0,0 +1,98 @@
+import React, {Component} from 'react';
+import QRCodeLibrary from 'react-native-qrcode-svg';
+import {View} from 'react-native';
+import withLocalize, {withLocalizePropTypes} from '../withLocalize';
+import defaultTheme from '../../styles/themes/default';
+import styles from '../../styles/styles';
+import Text from '../Text';
+import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions';
+import compose from '../../libs/compose';
+import variables from '../../styles/variables';
+import ExpensifyWordmark from '../../../assets/images/expensify-wordmark.svg';
+import {qrSharePropTypes, qrShareDefaultProps} from './propTypes'
+
+const propTypes = {
+ ...qrSharePropTypes,
+ ...windowDimensionsPropTypes,
+ ...withLocalizePropTypes,
+};
+
+class QRShare extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ qrCodeSize: 0,
+ };
+
+ this.onLayout = this.onLayout.bind(this);
+ this.getSvg = this.getSvg.bind(this);
+ }
+
+ onLayout(event) {
+ this.setState({
+ qrCodeSize: event.nativeEvent.layout.width - variables.qrShareHorizontalPadding * 2,
+ });
+ }
+
+ getSvg() {
+ return this.svg;
+ }
+
+ render() {
+ return (
+
+
+
+
+
+ (this.svg = svg)}
+ logoBackgroundColor="transparent"
+ logoSize={this.state.qrCodeSize * 0.3}
+ logoBorderRadius={this.state.qrCodeSize}
+ size={this.state.qrCodeSize}
+ backgroundColor={defaultTheme.highlightBG}
+ color={defaultTheme.text}
+ />
+
+
+ {this.props.title}
+
+
+ {this.props.subtitle && (
+
+ {this.props.subtitle}
+
+ )}
+
+ );
+ }
+}
+QRShare.propTypes = propTypes;
+QRShare.defaultProps = qrShareDefaultProps;
+
+export default compose(withLocalize, withWindowDimensions)(QRShare);
diff --git a/src/components/QRShare/propTypes.js b/src/components/QRShare/propTypes.js
new file mode 100644
index 000000000000..bcb55f99ea69
--- /dev/null
+++ b/src/components/QRShare/propTypes.js
@@ -0,0 +1,27 @@
+import PropTypes from 'prop-types';
+
+const qrSharePropTypes = {
+ /**
+ * The QR code URL
+ */
+ url: PropTypes.string.isRequired,
+ /**
+ * The title that is displayed below the QR Code (usually the user or report name)
+ */
+ title: PropTypes.string.isRequired,
+ /**
+ * The subtitle which will be shown below the title (usually user email or workspace name)
+ * */
+ subtitle: PropTypes.string,
+ /**
+ * The logo which will be display in the middle of the QR code
+ */
+ logo: PropTypes.string,
+};
+
+const defaultProps = {
+ subtitle: undefined,
+ logo: undefined,
+};
+
+export {qrSharePropTypes, defaultProps as qrShareDefaultProps};
diff --git a/src/languages/en.js b/src/languages/en.js
index 394342e6cada..c3543cd926e1 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -134,6 +134,8 @@ export default {
zipCodeExampleFormat: ({zipSampleFormat}) => (zipSampleFormat ? `e.g. ${zipSampleFormat}` : ''),
description: 'Description',
with: 'with',
+ shareCode: 'Share code',
+ share: 'Share',
},
attachmentPicker: {
cameraPermissionRequired: 'Camera access',
diff --git a/src/languages/es.js b/src/languages/es.js
index de00f658fb16..b936272d4622 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -133,6 +133,8 @@ export default {
zipCodeExampleFormat: ({zipSampleFormat}) => (zipSampleFormat ? `p. ej. ${zipSampleFormat}` : ''),
description: 'Descripción',
with: 'con',
+ shareCode: 'Compartir código',
+ share: 'Compartir',
},
attachmentPicker: {
cameraPermissionRequired: 'Permiso para acceder a la cámara',
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
index cc2b18ce47fc..7b33335fb86d 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
@@ -160,6 +160,13 @@ const ReportDetailsModalStackNavigator = createModalStackNavigator([
},
name: 'Report_Details_Root',
},
+ {
+ getComponent: () => {
+ const ShareCodePage = require('../../../pages/home/report/ReportDetailsShareCodePage').default;
+ return ShareCodePage;
+ },
+ name: 'Report_Details_Share_Code',
+ },
]);
const TaskModalStackNavigator = createModalStackNavigator([
@@ -296,6 +303,13 @@ const SettingsModalStackNavigator = createModalStackNavigator([
},
name: 'Settings_Root',
},
+ {
+ getComponent: () => {
+ const ShareCodePage = require('../../../pages/ShareCodePage').default;
+ return ShareCodePage;
+ },
+ name: 'Settings_Share_Code',
+ },
{
getComponent: () => {
const SettingsWorkspacesPage = require('../../../pages/workspace/WorkspacesListPage').default;
diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js
index 92b4414b99ae..1a7c7398caf0 100644
--- a/src/libs/Navigation/linkingConfig.js
+++ b/src/libs/Navigation/linkingConfig.js
@@ -30,6 +30,10 @@ export default {
Settings_Root: {
path: ROUTES.SETTINGS,
},
+ Settings_Share_Code: {
+ path: ROUTES.SETTINGS_SHARE_CODE,
+ exact: true,
+ },
Settings_Workspaces: {
path: ROUTES.SETTINGS_WORKSPACES,
exact: true,
@@ -186,6 +190,7 @@ export default {
Report_Details: {
screens: {
Report_Details_Root: ROUTES.REPORT_WITH_ID_DETAILS,
+ Report_Details_Share_Code: ROUTES.REPORT_WITH_ID_DETAILS_SHARE_CODE,
},
},
Report_Settings: {
diff --git a/src/libs/fileDownload/index.android.js b/src/libs/fileDownload/index.android.js
index 98bd5c975331..c14575881fea 100644
--- a/src/libs/fileDownload/index.android.js
+++ b/src/libs/fileDownload/index.android.js
@@ -43,26 +43,33 @@ function handleDownload(url, fileName) {
const path = dirs.DownloadDir;
const attachmentName = fileName || FileUtils.getAttachmentName(url);
- // Fetching the attachment
- const fetchedAttachment = RNFetchBlob.config({
- fileCache: true,
- path: `${path}/${attachmentName}`,
- addAndroidDownloads: {
- useDownloadManager: true,
- notification: false,
- path: `${path}/Expensify/${attachmentName}`,
- },
- }).fetch('GET', url);
+ const isLocalFile = url.startsWith('file://');
- let attachmentPath;
+ let attachmentPath = isLocalFile ? url : undefined;
+ let fetchedAttachment = Promise.resolve();
+
+ if (!isLocalFile) {
+ // Fetching the attachment
+ fetchedAttachment = RNFetchBlob.config({
+ fileCache: true,
+ path: `${path}/${attachmentName}`,
+ addAndroidDownloads: {
+ useDownloadManager: true,
+ notification: false,
+ path: `${path}/Expensify/${attachmentName}`,
+ },
+ }).fetch('GET', url);
+ }
// Resolving the fetched attachment
fetchedAttachment
.then((attachment) => {
- if (!attachment || !attachment.info()) {
+ if (!isLocalFile && (!attachment || !attachment.info())) {
return Promise.reject();
}
- attachmentPath = attachment.path();
+
+ if (!isLocalFile) attachmentPath = attachment.path();
+
return RNFetchBlob.MediaCollection.copyToMediaStore(
{
name: attachmentName,
diff --git a/src/pages/ReportDetailsPage.js b/src/pages/ReportDetailsPage.js
index ee2c2fefb122..9ed7bfa86092 100644
--- a/src/pages/ReportDetailsPage.js
+++ b/src/pages/ReportDetailsPage.js
@@ -57,7 +57,14 @@ const defaultProps = {
class ReportDetailsPage extends Component {
getMenuItems() {
- const menuItems = [];
+ const menuItems = [
+ {
+ key: CONST.REPORT_DETAILS_MENU_ITEM.SHARE_CODE,
+ translationKey: 'common.shareCode',
+ icon: Expensicons.QrCode,
+ action: () => Navigation.navigate(ROUTES.getReportShareCodeRoute(this.props.report.reportID)),
+ },
+ ];
if (ReportUtils.isArchivedRoom(this.props.report)) {
return [];
diff --git a/src/pages/ShareCodePage.js b/src/pages/ShareCodePage.js
new file mode 100644
index 000000000000..8ce21ccabd65
--- /dev/null
+++ b/src/pages/ShareCodePage.js
@@ -0,0 +1,92 @@
+import React from 'react';
+import {View, ScrollView} from 'react-native';
+import ScreenWrapper from '../components/ScreenWrapper';
+import HeaderWithCloseButton from '../components/HeaderWithCloseButton';
+import Navigation from '../libs/Navigation/Navigation';
+import withLocalize, {withLocalizePropTypes} from '../components/withLocalize';
+import QRShareWithDownload from '../components/QRShare/QRShareWithDownload';
+import compose from '../libs/compose';
+import reportPropTypes from './reportPropTypes';
+import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '../components/withCurrentUserPersonalDetails';
+import styles from '../styles/styles';
+import roomAvatar from '../../assets/images/avatars/room.png';
+import * as ReportUtils from '../libs/ReportUtils';
+import MenuItem from '../components/MenuItem';
+import Clipboard from '../libs/Clipboard';
+import * as Expensicons from '../components/Icon/Expensicons';
+import getPlatform from '../libs/getPlatform';
+import CONST from '../CONST';
+
+const propTypes = {
+ /** The report currently being looked at */
+ report: reportPropTypes,
+
+ ...withLocalizePropTypes,
+ ...withCurrentUserPersonalDetailsPropTypes,
+};
+
+const defaultProps = {
+ report: undefined,
+ ...withCurrentUserPersonalDetailsDefaultProps,
+};
+
+// eslint-disable-next-line react/prefer-stateless-function
+class ShareCodePage extends React.Component {
+ qrCodeRef = React.createRef();
+
+ render() {
+ const isReport = this.props.report != null && this.props.report.reportID != null;
+
+ const url = isReport ? `${CONST.NEW_EXPENSIFY_URL}r/${this.props.report.reportID}` : `${CONST.NEW_EXPENSIFY_URL}details?login=${this.props.session.email}`;
+
+ const platform = getPlatform();
+ const isNative = platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID;
+
+ return (
+
+ Navigation.goBack()}
+ onCloseButtonPress={() => Navigation.dismissModal(true)}
+ />
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+ShareCodePage.propTypes = propTypes;
+ShareCodePage.defaultProps = defaultProps;
+
+export default compose(withLocalize, withCurrentUserPersonalDetails)(ShareCodePage);
diff --git a/src/pages/home/report/ReportDetailsShareCodePage.js b/src/pages/home/report/ReportDetailsShareCodePage.js
new file mode 100644
index 000000000000..1dbfe25d2068
--- /dev/null
+++ b/src/pages/home/report/ReportDetailsShareCodePage.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {withOnyx} from 'react-native-onyx';
+import reportPropTypes from '../../reportPropTypes';
+import ONYXKEYS from '../../../ONYXKEYS';
+import ShareCodePage from '../../ShareCodePage';
+
+const propTypes = {
+ /** Navigation route context info provided by react navigation */
+ route: PropTypes.shape({
+ /** Route specific parameters used on this screen */
+ params: PropTypes.shape({
+ reportID: PropTypes.string,
+ }).isRequired,
+ }).isRequired,
+
+ /** The report currently being looked at */
+ report: reportPropTypes,
+};
+
+const defaultProps = {
+ report: undefined,
+};
+
+function ReportDetailsShareCodePage(props) {
+ return ;
+}
+
+ReportDetailsShareCodePage.propTypes = propTypes;
+ReportDetailsShareCodePage.defaultProps = defaultProps;
+
+export default withOnyx({
+ report: {
+ key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`,
+ },
+})(ReportDetailsShareCodePage);
diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js
index 84ae9e30cd3c..eba3ccefb9fd 100755
--- a/src/pages/settings/InitialSettingsPage.js
+++ b/src/pages/settings/InitialSettingsPage.js
@@ -177,6 +177,13 @@ class InitialSettingsPage extends React.Component {
const profileBrickRoadIndicator = UserUtils.getLoginListBrickRoadIndicator(this.props.loginList);
return [
+ {
+ translationKey: 'common.shareCode',
+ icon: Expensicons.QrCode,
+ action: () => {
+ Navigation.navigate(ROUTES.SETTINGS_SHARE_CODE);
+ },
+ },
{
translationKey: 'common.workspaces',
icon: Expensicons.Building,
diff --git a/src/styles/styles.js b/src/styles/styles.js
index cef8513b4cf5..c6b77c437b1c 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -3224,10 +3224,27 @@ const styles = {
contextMenuItemPopoverMaxWidth: {
maxWidth: 375,
},
+
taskCheckbox: {
height: 16,
width: 16,
},
+
+ shareCodePage: {
+ paddingHorizontal: 38.5,
+ },
+
+ shareCodeContainer: {
+ width: '100%',
+ alignItems: 'center',
+ paddingHorizontal: variables.qrShareHorizontalPadding,
+ paddingVertical: 20,
+ borderRadius: 20,
+ overflow: 'hidden',
+ borderColor: themeColors.borderFocus,
+ borderWidth: 2,
+ backgroundColor: themeColors.highlightBG,
+ },
};
export default styles;
diff --git a/src/styles/variables.js b/src/styles/variables.js
index 109c1dc410c3..33b98a0c4c08 100644
--- a/src/styles/variables.js
+++ b/src/styles/variables.js
@@ -136,4 +136,5 @@ export default {
googleEmptyListViewHeight: 14,
hoverDimValue: 0.5,
pressDimValue: 0.8,
+ qrShareHorizontalPadding: 32,
};