diff --git a/__mocks__/react-native-geolocation-service.js b/__mocks__/react-native-geolocation-service.js
new file mode 100644
index 000000000000..f4631519a163
--- /dev/null
+++ b/__mocks__/react-native-geolocation-service.js
@@ -0,0 +1,9 @@
+export default {
+ addListener: jest.fn(),
+ getCurrentPosition: jest.fn(),
+ removeListeners: jest.fn(),
+ requestAuthorization: jest.fn(),
+ setConfiguration: jest.fn(),
+ startObserving: jest.fn(),
+ stopObserving: jest.fn(),
+};
diff --git a/__mocks__/react-native-permissions.js b/__mocks__/react-native-permissions.js
new file mode 100644
index 000000000000..e0355503f1e0
--- /dev/null
+++ b/__mocks__/react-native-permissions.js
@@ -0,0 +1,65 @@
+const {PERMISSIONS} = require('react-native-permissions/dist/commonjs/permissions');
+const {RESULTS} = require('react-native-permissions/dist/commonjs/results');
+
+export {PERMISSIONS, RESULTS};
+
+export const openLimitedPhotoLibraryPicker = jest.fn((() => {}));
+export const openSettings = jest.fn(() => {});
+export const check = jest.fn(() => RESULTS.GRANTED);
+export const request = jest.fn(() => RESULTS.GRANTED);
+export const checkLocationAccuracy = jest.fn(() => 'full');
+export const requestLocationAccuracy = jest.fn(() => 'full');
+
+const notificationOptions = ['alert', 'badge', 'sound', 'carPlay', 'criticalAlert', 'provisional'];
+
+const notificationSettings = {
+ alert: true,
+ badge: true,
+ sound: true,
+ carPlay: true,
+ criticalAlert: true,
+ provisional: true,
+ lockScreen: true,
+ notificationCenter: true,
+};
+
+export const checkNotifications = jest.fn(() => ({
+ status: RESULTS.GRANTED,
+ settings: notificationSettings,
+}));
+
+export const requestNotifications = jest.fn(options => ({
+ status: RESULTS.GRANTED,
+ settings: options
+ .filter(option => notificationOptions.includes(option))
+ .reduce((acc, option) => ({...acc, [option]: true}), {
+ lockScreen: true,
+ notificationCenter: true,
+ }),
+}));
+
+export const checkMultiple = jest.fn(permissions => permissions.reduce((acc, permission) => ({
+ ...acc,
+ [permission]: RESULTS.GRANTED,
+})));
+
+export const requestMultiple = jest.fn(permissions => permissions.reduce((acc, permission) => ({
+ ...acc,
+ [permission]: RESULTS.GRANTED,
+})));
+
+export default {
+ PERMISSIONS,
+ RESULTS,
+
+ check,
+ checkLocationAccuracy,
+ checkMultiple,
+ checkNotifications,
+ openLimitedPhotoLibraryPicker,
+ openSettings,
+ request,
+ requestLocationAccuracy,
+ requestMultiple,
+ requestNotifications,
+};
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 01f2ba8ded56..8b7e639b1993 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -6,6 +6,7 @@
+
/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;
- };
- 97ED3CCB9FD5A5A2AB20CC03 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-ExpensifyCash-ExpensifyCashTests/Pods-ExpensifyCash-ExpensifyCashTests-frameworks.sh",
- "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL/OpenSSL.framework/OpenSSL",
- "${PODS_XCFRAMEWORKS_BUILD_DIR}/LinkKit/LinkKit.framework/LinkKit",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LinkKit.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ExpensifyCash-ExpensifyCashTests/Pods-ExpensifyCash-ExpensifyCashTests-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- AEFD4743761AD0E2373D0494 /* [CP] Check Pods Manifest.lock */ = {
+ 39ECFD0897ADA7A0E34E0C36 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -405,7 +366,7 @@
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;
};
- C9071365B664DE0D85DC5195 /* [CP] Copy Pods Resources */ = {
+ 7466D6D5FFFCE8DCCD1EB333 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -533,15 +494,35 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ExpensifyCash/Pods-ExpensifyCash-resources.sh\"\n";
showEnvVarsInLog = 0;
};
- CF6259710B5A341870372EA2 /* [CP-User] [RNFB] Core Configuration */ = {
+ 97ED3CCB9FD5A5A2AB20CC03 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
- name = "[CP-User] [RNFB] Core Configuration";
+ inputPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-ExpensifyCash-ExpensifyCashTests/Pods-ExpensifyCash-ExpensifyCashTests-frameworks.sh",
+ "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL/OpenSSL.framework/OpenSSL",
+ "${PODS_XCFRAMEWORKS_BUILD_DIR}/LinkKit/LinkKit.framework/LinkKit",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LinkKit.framework",
+ );
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n _JSON_OUTPUT_BASE64=$(python -c 'import json,sys,base64;print(base64.b64encode(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"').read())['${_JSON_ROOT}'])))' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\n\n # config.admob_delay_app_measurement_init\n _ADMOB_DELAY_APP_MEASUREMENT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"admob_delay_app_measurement_init\")\n if [[ $_ADMOB_DELAY_APP_MEASUREMENT == \"true\" ]]; then\n _PLIST_ENTRY_KEYS+=(\"GADDelayAppMeasurementInit\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"YES\")\n fi\n\n # config.admob_ios_app_id\n _ADMOB_IOS_APP_ID=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"admob_ios_app_id\")\n if [[ $_ADMOB_IOS_APP_ID ]]; then\n _PLIST_ENTRY_KEYS+=(\"GADApplicationIdentifier\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_ADMOB_IOS_APP_ID\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ExpensifyCash-ExpensifyCashTests/Pods-ExpensifyCash-ExpensifyCashTests-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ DA4F8CF5B777BDB8DD1EB45D /* [CP-User] [RNFB] Crashlytics Configuration */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ name = "[CP-User] [RNFB] Crashlytics Configuration";
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\nif [[ ${PODS_ROOT} ]]; then\n echo \"info: Exec FirebaseCrashlytics Run from Pods\"\n \"${PODS_ROOT}/FirebaseCrashlytics/run\"\nelse\n echo \"info: Exec FirebaseCrashlytics Run from framework\"\n \"${PROJECT_DIR}/FirebaseCrashlytics.framework/run\"\nfi\n";
};
DBBB399CCB345652E314C5BC /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
@@ -563,17 +544,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ExpensifyCash/Pods-ExpensifyCash-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
- ED5B8E90A3384FC6A128FB95 /* [CP-User] [RNFB] Crashlytics Configuration */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- name = "[CP-User] [RNFB] Crashlytics Configuration";
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\nif [[ ${PODS_ROOT} ]]; then\n echo \"info: Exec FirebaseCrashlytics Run from Pods\"\n \"${PODS_ROOT}/FirebaseCrashlytics/run\"\nelse\n echo \"info: Exec FirebaseCrashlytics Run from framework\"\n \"${PROJECT_DIR}/FirebaseCrashlytics.framework/run\"\nfi\n";
- };
- F2B8013B9E9ECAB2B509E702 /* [CP] Copy Pods Resources */ = {
+ E3E26870421B0A221D65F7A4 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -701,6 +672,38 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ExpensifyCash-ExpensifyCashTests/Pods-ExpensifyCash-ExpensifyCashTests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
+ ED5B8E90A3384FC6A128FB95 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-ExpensifyCash-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;
+ };
+ F928D8F3D996EB54A782A79C /* [CP-User] [RNFB] Core Configuration */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ name = "[CP-User] [RNFB] Core Configuration";
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n _JSON_OUTPUT_BASE64=$(python -c 'import json,sys,base64;print(base64.b64encode(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"').read())['${_JSON_ROOT}'])))' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\n\n # config.admob_delay_app_measurement_init\n _ADMOB_DELAY_APP_MEASUREMENT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"admob_delay_app_measurement_init\")\n if [[ $_ADMOB_DELAY_APP_MEASUREMENT == \"true\" ]]; then\n _PLIST_ENTRY_KEYS+=(\"GADDelayAppMeasurementInit\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"YES\")\n fi\n\n # config.admob_ios_app_id\n _ADMOB_IOS_APP_ID=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"admob_ios_app_id\")\n if [[ $_ADMOB_IOS_APP_ID ]]; then\n _PLIST_ENTRY_KEYS+=(\"GADApplicationIdentifier\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_ADMOB_IOS_APP_ID\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n";
+ };
FD10A7F022414F080027D42C /* Start Packager */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -736,6 +739,7 @@
buildActionMask = 2147483647;
files = (
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
+ 18D050E0262400AF000D658B /* BridgingFile.swift in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -753,8 +757,9 @@
/* Begin XCBuildConfiguration section */
00E356F61AD99517003FC87E /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = AF728B3FCBC8C2731C4DA7B4 /* Pods-ExpensifyCash-ExpensifyCashTests.debug.xcconfig */;
+ baseConfigurationReference = CF8C66EE73F9B503DE3E0D58 /* Pods-ExpensifyCash-ExpensifyCashTests.debug.xcconfig */;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
GCC_PREPROCESSOR_DEFINITIONS = (
@@ -777,8 +782,9 @@
};
00E356F71AD99517003FC87E /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = E7967C67752432EA2031954F /* Pods-ExpensifyCash-ExpensifyCashTests.release.xcconfig */;
+ baseConfigurationReference = 1C3425E20B41643777DE7722 /* Pods-ExpensifyCash-ExpensifyCashTests.release.xcconfig */;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
@@ -799,7 +805,7 @@
};
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = F4BC0E78FF1E9BD8B2D38C66 /* Pods-ExpensifyCash.debug.xcconfig */;
+ baseConfigurationReference = DDF4204498D793D7FB483440 /* Pods-ExpensifyCash.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
@@ -822,6 +828,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.chat.expensify.chat;
PRODUCT_NAME = Expensify.cash;
PROVISIONING_PROFILE_SPECIFIER = chat_expensify_appstore;
+ SWIFT_OBJC_BRIDGING_HEADER = "ExpensifyCash-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -831,7 +838,7 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = A13EE2CFAF952F935D201D2F /* Pods-ExpensifyCash.release.xcconfig */;
+ baseConfigurationReference = 46BF67CDAA47420D541264C2 /* Pods-ExpensifyCash.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
@@ -853,6 +860,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.chat.expensify.chat;
PRODUCT_NAME = Expensify.cash;
PROVISIONING_PROFILE_SPECIFIER = chat_expensify_appstore;
+ SWIFT_OBJC_BRIDGING_HEADER = "ExpensifyCash-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
diff --git a/ios/ExpensifyCash/Info.plist b/ios/ExpensifyCash/Info.plist
index 95020e9820e9..fe2ae3e2243e 100644
--- a/ios/ExpensifyCash/Info.plist
+++ b/ios/ExpensifyCash/Info.plist
@@ -88,6 +88,7 @@
UIBackgroundModes
+ location
remote-notification
UIFileSharingEnabled
diff --git a/ios/Podfile b/ios/Podfile
index 8e6678d56384..91bc944897d2 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -4,9 +4,14 @@ require_relative '../node_modules/@react-native-community/cli-platform-ios/nativ
platform :ios, '11.0'
target 'ExpensifyCash' do
+ permissions_path = '../node_modules/react-native-permissions/ios'
+
pod 'Plaid', '~> 2.1.2'
- config = use_native_modules!
+ pod 'Permission-LocationAccuracy', :path => "#{permissions_path}/LocationAccuracy"
+ pod 'Permission-LocationAlways', :path => "#{permissions_path}/LocationAlways"
+ pod 'Permission-LocationWhenInUse', :path => "#{permissions_path}/LocationWhenInUse"
+ config = use_native_modules!
use_react_native!(:path => config["reactNativePath"])
target 'ExpensifyCashTests' do
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 6b4061284d47..7e562ae6947b 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -151,6 +151,12 @@ PODS:
- nanopb/decode (1.30906.0)
- nanopb/encode (1.30906.0)
- OpenSSL-Universal (1.1.180)
+ - Permission-LocationAccuracy (3.0.1):
+ - RNPermissions
+ - Permission-LocationAlways (3.0.1):
+ - RNPermissions
+ - Permission-LocationWhenInUse (3.0.1):
+ - RNPermissions
- Plaid (2.1.2)
- PromisesObjC (1.2.12)
- RCTRequired (0.63.3)
@@ -325,6 +331,8 @@ PODS:
- React-Core
- react-native-document-picker (4.0.0):
- React-Core
+ - react-native-geolocation-service (5.2.0):
+ - React
- react-native-image-picker (2.3.4):
- React-Core
- react-native-netinfo (5.9.10):
@@ -425,6 +433,8 @@ PODS:
- RNFBApp
- RNGestureHandler (1.9.0):
- React-Core
+ - RNPermissions (3.0.1):
+ - React-Core
- RNReanimated (1.13.2):
- React-Core
- RNScreens (2.17.1):
@@ -463,6 +473,9 @@ DEPENDENCIES:
- FlipperKit/SKIOSNetworkPlugin (= 0.75.1)
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
+ - Permission-LocationAccuracy (from `../node_modules/react-native-permissions/ios/LocationAccuracy`)
+ - Permission-LocationAlways (from `../node_modules/react-native-permissions/ios/LocationAlways`)
+ - Permission-LocationWhenInUse (from `../node_modules/react-native-permissions/ios/LocationWhenInUse`)
- Plaid (~> 2.1.2)
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
@@ -478,6 +491,7 @@ DEPENDENCIES:
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- react-native-config (from `../node_modules/react-native-config`)
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
+ - react-native-geolocation-service (from `../node_modules/react-native-geolocation-service`)
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-pdf (from `../node_modules/react-native-pdf`)
@@ -505,6 +519,7 @@ DEPENDENCIES:
- "RNFBApp (from `../node_modules/@react-native-firebase/app`)"
- "RNFBCrashlytics (from `../node_modules/@react-native-firebase/crashlytics`)"
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
+ - RNPermissions (from `../node_modules/react-native-permissions`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`)
- RNSVG (from `../node_modules/react-native-svg`)
@@ -550,6 +565,12 @@ EXTERNAL SOURCES:
:podspec: "../node_modules/react-native/third-party-podspecs/Folly.podspec"
glog:
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
+ Permission-LocationAccuracy:
+ :path: "../node_modules/react-native-permissions/ios/LocationAccuracy"
+ Permission-LocationAlways:
+ :path: "../node_modules/react-native-permissions/ios/LocationAlways"
+ Permission-LocationWhenInUse:
+ :path: "../node_modules/react-native-permissions/ios/LocationWhenInUse"
RCTRequired:
:path: "../node_modules/react-native/Libraries/RCTRequired"
RCTTypeSafety:
@@ -574,6 +595,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-config"
react-native-document-picker:
:path: "../node_modules/react-native-document-picker"
+ react-native-geolocation-service:
+ :path: "../node_modules/react-native-geolocation-service"
react-native-image-picker:
:path: "../node_modules/react-native-image-picker"
react-native-netinfo:
@@ -628,6 +651,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-firebase/crashlytics"
RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler"
+ RNPermissions:
+ :path: "../node_modules/react-native-permissions"
RNReanimated:
:path: "../node_modules/react-native-reanimated"
RNScreens:
@@ -667,6 +692,9 @@ SPEC CHECKSUMS:
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc
OpenSSL-Universal: 1aa4f6a6ee7256b83db99ec1ccdaa80d10f9af9b
+ Permission-LocationAccuracy: 76669f87b4c276f5ae803cc0ddd1862a4c0e9dd8
+ Permission-LocationAlways: a274bc04bb386068782468dbdaca3859f51634ca
+ Permission-LocationWhenInUse: 3a2b0dbc167d79e8e920a4377ff9520cdc108407
Plaid: c02276ccc630a726a9ed790bf923d29839ff4017
PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97
RCTRequired: 48884c74035a0b5b76dbb7a998bd93bcfc5f2047
@@ -680,14 +708,15 @@ SPEC CHECKSUMS:
React-jsiexecutor: b56c03e61c0dd5f5801255f2160a815f4a53d451
React-jsinspector: 8e68ffbfe23880d3ee9bafa8be2777f60b25cbe2
react-native-config: d8b45133fd13d4f23bd2064b72f6e2c08b2763ed
- react-native-document-picker: b3e78a8f7fef98b5cb069f20fc35797d55e68e28
- react-native-image-picker: 32d1ad2c0024ca36161ae0d5c2117e2d6c441f11
- react-native-netinfo: 52cf0ee8342548a485e28f4b09e56b477567244d
+ react-native-document-picker: 0bba80cc56caab1f67dbaa81ff557e3a9b7f2b9f
+ react-native-geolocation-service: 7c9436da6dfdecd9526c62eac62ea2bc3f0cc8ea
+ react-native-image-picker: c6d75c4ab2cf46f9289f341242b219cb3c1180d3
+ react-native-netinfo: 30fb89fa913c342be82a887b56e96be6d71201dd
react-native-pdf: 4b5a9e4465a6a3b399e91dc4838eb44ddf716d1f
- react-native-plaid-link-sdk: 1a6593e2d3d790e8113c29178d883eb883f8c032
- react-native-progress-bar-android: ce95a69f11ac580799021633071368d08aaf9ad8
- react-native-progress-view: 5816e8a6be812c2b122c6225a2a3db82d9008640
- react-native-safe-area-context: 01158a92c300895d79dee447e980672dc3fb85a6
+ react-native-plaid-link-sdk: 59b7376efca9f00e9693321c5cf7c6ab2c567635
+ react-native-progress-bar-android: be43138ab7da30d51fc038bafa98e9ed594d0c40
+ react-native-progress-view: 21b1e29e70c7559c16c9e0a04c4adc19fce6ede2
+ react-native-safe-area-context: 79fea126c6830c85f65947c223a5e3058a666937
React-RCTActionSheet: 53ea72699698b0b47a6421cb1c8b4ab215a774aa
React-RCTAnimation: 1befece0b5183c22ae01b966f5583f42e69a83c2
React-RCTBlob: 0b284339cbe4b15705a05e2313a51c6d8b51fa40
@@ -699,22 +728,23 @@ SPEC CHECKSUMS:
React-RCTVibration: 8e9fb25724a0805107fc1acc9075e26f814df454
ReactCommon: 4167844018c9ed375cc01a843e9ee564399e53c3
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
- RNBootSplash: 3123ba68fe44d8be09a014e89cc8f0f55b68a521
- RNCAsyncStorage: cb9a623793918c6699586281f0b51cbc38f046f9
- RNCClipboard: 5e299c6df8e0c98f3d7416b86ae563d3a9f768a3
- RNCMaskedView: f5c7d14d6847b7b44853f7acb6284c1da30a3459
+ RNBootSplash: 24175aa28fe203b10c48dc34e78d946fd33c77af
+ RNCAsyncStorage: b03032fdbdb725bea0bd9e5ec5a7272865ae7398
+ RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
+ RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f
RNCPicker: 6780c753e9e674065db90d9c965920516402579d
RNFBAnalytics: 2dc4dd9e2445faffca041b10447a23a71dcdabf8
RNFBApp: 7eacc7da7ab19f96c05e434017d44a9f09410da8
RNFBCrashlytics: 4870c14cf8833053b6b5648911abefe1923854d2
RNGestureHandler: 9b7e605a741412e20e13c512738a31bd1611759b
+ RNPermissions: 4c8a37b4dde50f1f152bf8cd08c4a43d2355829e
RNReanimated: e03f7425cb7a38dcf1b644d680d1bfc91c3337ad
RNScreens: b6c9607e6fe47c1b6e2f1910d2acd46dd7ecea3a
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
- urbanairship-react-native: dfb6dc22b2f41ccaadd636b73d51b448cd1b2bbc
+ urbanairship-react-native: afab7684561909be2a6a2a52baa3435776d050de
Yoga: 7d13633d129fd179e01b8953d38d47be90db185a
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
-PODFILE CHECKSUM: 6eb5d43e785faa9b1fe52688d5bf7a328bd1b1cf
+PODFILE CHECKSUM: e2cbcef0a80ad10b622900511a519e73949d415d
COCOAPODS: 1.10.1
diff --git a/package-lock.json b/package-lock.json
index 9b23fbef5be2..42cb4a1e52f6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -32673,6 +32673,11 @@
"resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-4.0.0.tgz",
"integrity": "sha512-tjIOBBcyjv4j5E1MDL2OvEKNpXxQybLNkjjfpTyDUzek7grZ5eOvSlp6i/Y3EfuIGLByeaw++9R1SZtOij6R7w=="
},
+ "react-native-geolocation-service": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/react-native-geolocation-service/-/react-native-geolocation-service-5.2.0.tgz",
+ "integrity": "sha512-ai7xd6QbLl6WMyEbPfXSaXyYQ/L6CDcPjOZAJYboqwNPclAqxGkzJHJQyvBNy9J410EIrDJg0p9KyaciXmxyCw=="
+ },
"react-native-gesture-handler": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.9.0.tgz",
@@ -32760,6 +32765,11 @@
"prop-types": "^15.7.2"
}
},
+ "react-native-permissions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-3.0.1.tgz",
+ "integrity": "sha512-loCoNEeBeLsrvITZmrkuCUEMFiuwhKuFvkWbC5Imco4bj2hUW7BVI29i8QyxkC53ydRfSR9OtbR3KQIyghWiNQ=="
+ },
"react-native-picker-select": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/react-native-picker-select/-/react-native-picker-select-8.0.4.tgz",
diff --git a/package.json b/package.json
index ae815de8d14d..6a7925143ade 100644
--- a/package.json
+++ b/package.json
@@ -74,6 +74,7 @@
"react-native-bootsplash": "^3.2.0",
"react-native-config": "^1.4.0",
"react-native-document-picker": "^4.0.0",
+ "react-native-geolocation-service": "^5.2.0",
"react-native-gesture-handler": "1.9.0",
"react-native-image-pan-zoom": "^2.1.12",
"react-native-image-picker": "^2.3.3",
@@ -81,6 +82,7 @@
"react-native-modal": "^11.5.6",
"react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#586c76e7b90dbbde051d7ec7adbc4d53b2d51cd1",
"react-native-pdf": "^6.2.2",
+ "react-native-permissions": "^3.0.1",
"react-native-picker-select": "8.0.4",
"react-native-plaid-link-sdk": "^7.0.5",
"react-native-reanimated": "1.13.2",
diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index 6ee6ba2a626d..03119bd6a65b 100755
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -42,6 +42,10 @@ export default {
// Contains all the personalDetails the user has access to
PERSONAL_DETAILS: 'personalDetails',
+ // Contains a list of all currencies available to the user - user can
+ // select a currency based on the list
+ CURRENCY_LIST: 'currencyList',
+
// Indicates whether an update is available and ready to beinstalled.
UPDATE_AVAILABLE: 'updateAvailable',
diff --git a/src/ROUTES.js b/src/ROUTES.js
index 69223366be97..87dd0cdb4cc4 100644
--- a/src/ROUTES.js
+++ b/src/ROUTES.js
@@ -24,9 +24,13 @@ export default {
REPORT,
REPORT_WITH_ID: 'r/:reportID',
getReportRoute: reportID => `r/${reportID}`,
+ IOU_BILL_CURRENCY: 'iou/split/:reportID/currency',
+ IOU_REQUEST_CURRENCY: 'iou/request/:reportID/currency',
+ getIouRequestCurrencyRoute: reportID => `iou/request/${reportID}/currency`,
+ getIouBillCurrencyRoute: reportID => `iou/split/${reportID}/currency`,
IOU_REQUEST: 'iou/request/:reportID',
- getIouRequestRoute: reportID => `iou/request/${reportID}`,
IOU_BILL: 'iou/split/:reportID',
+ getIouRequestRoute: reportID => `iou/request/${reportID}`,
getIouSplitRoute: reportID => `iou/split/${reportID}`,
IOU_DETAILS: 'iou/details',
IOU_DETAILS_WITH_IOU_REPORT_ID: 'iou/details/:chatReportID/:iouReportID/',
diff --git a/src/components/IOUConfirmationList.js b/src/components/IOUConfirmationList.js
index ace33de1ac36..739b866e62c4 100755
--- a/src/components/IOUConfirmationList.js
+++ b/src/components/IOUConfirmationList.js
@@ -23,7 +23,16 @@ const propTypes = {
/** Callback to inform parent modal of success */
onConfirm: PropTypes.func.isRequired,
- /** Callback to update comment from IOUModal */
+ // User's currency preference
+ selectedCurrency: PropTypes.shape({
+ // Currency code for the selected currency
+ currencyCode: PropTypes.string,
+
+ // Currency symbol for the selected currency
+ currencySymbol: PropTypes.string,
+ }).isRequired,
+
+ // Callback to update comment from IOUModal
onUpdateComment: PropTypes.func,
/** Comment value from IOUModal */
@@ -38,12 +47,7 @@ const propTypes = {
/** IOU amount */
iouAmount: PropTypes.string.isRequired,
- /** Selected currency from the user
- Remove eslint disable after currency symbol is available */
- // eslint-disable-next-line react/no-unused-prop-types
- selectedCurrency: PropTypes.string.isRequired,
-
- /** Selected participants from IOUMOdal with login */
+ // Selected participants from IOUModal with login
participants: PropTypes.arrayOf(PropTypes.shape({
login: PropTypes.string.isRequired,
alternateText: PropTypes.string,
@@ -111,12 +115,12 @@ class IOUConfirmationList extends Component {
// Convert from cent to bigger form
// USD is temporary and there must be support for other currencies in the future
- `$${this.calculateAmount(true) / 100}`,
+ `${this.props.selectedCurrency.currencySymbol}${this.calculateAmount(true) / 100}`,
);
// Cents is temporary and there must be support for other currencies in the future
const formattedParticipants = getIOUConfirmationOptionsFromParticipants(this.props.participants,
- `$${this.calculateAmount() / 100}`);
+ `${this.props.selectedCurrency.currencySymbol}${this.calculateAmount() / 100}`);
sections.push({
title: this.props.translate('iOUConfirmationList.whoPaid'),
@@ -133,7 +137,7 @@ class IOUConfirmationList extends Component {
} else {
// $ Should be replaced by currency symbol once available
const formattedParticipants = getIOUConfirmationOptionsFromParticipants(this.props.participants,
- `$${this.props.iouAmount}`);
+ `${this.props.selectedCurrency.currencySymbol}${this.props.iouAmount}`);
sections.push({
title: this.props.translate('common.to').toUpperCase(),
@@ -264,8 +268,14 @@ class IOUConfirmationList extends Component {
isLoading={this.props.iou.loading}
text={this.props.hasMultipleParticipants
? this.props.translate('common.split')
- : this.props.translate('iou.request', {amount: this.props.iouAmount})}
- onPress={() => this.props.onConfirm(this.getSplits())}
+ : this.props.translate('iou.request',
+ {
+ amount: this.props.numberFormat(this.props.iouAmount, {
+ style: 'currency',
+ currency: this.props.selectedCurrency.currencyCode,
+ }),
+ })}
+ onClick={() => this.props.onConfirm(this.getSplits())}
/>
diff --git a/src/languages/en.js b/src/languages/en.js
index ea9b5061e42f..d1895aff0d3b 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -52,6 +52,10 @@ export default {
whoWasThere: 'WHO WAS THERE?',
whatsItFor: 'WHAT\'S IT FOR?',
},
+ iOUCurrencySelection: {
+ selectCurrency: 'Select a Currency',
+ allCurrencies: 'ALL CURRENCIES',
+ },
optionsSelector: {
nameEmailOrPhoneNumber: 'Name, email, or phone number',
},
diff --git a/src/libs/API.js b/src/libs/API.js
index a6770801a185..e942b2a3683b 100644
--- a/src/libs/API.js
+++ b/src/libs/API.js
@@ -783,6 +783,24 @@ function BankAccount_Create(parameters) {
return Network.post(commandName, parameters, CONST.NETWORK.METHOD.POST, true);
}
+/**
+ * @param {object} parameters
+ * @param {number} [parameters.latitude]
+ * @param {number} [parameters.longitude]
+ * @returns {Promise}
+ */
+function GetPreferredCurrency(parameters) {
+ const commandName = 'GetPreferredCurrency';
+ return Network.post(commandName, parameters);
+}
+
+/**
+ * @returns {Promise}
+ */
+function GetCurrencyList() {
+ return Mobile_GetConstants({data: ['currencyList']});
+}
+
export {
getAuthToken,
Authenticate,
@@ -824,4 +842,6 @@ export {
CreateIOUSplit,
ValidateEmail,
Wallet_GetOnfidoSDKToken,
+ GetPreferredCurrency,
+ GetCurrencyList,
};
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js
index f9c8e072fd75..15afffdb1291 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.js
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.js
@@ -117,7 +117,8 @@ class AuthScreens extends React.Component {
PersonalDetails.fetchPersonalDetails();
User.getUserDetails();
User.getBetas();
- fetchAllReports(true, true);
+ PersonalDetails.fetchCurrencyPreferences();
+ fetchAllReports(true, true, true);
fetchCountryCodeByRequestIP();
BankAccounts.fetchBankAccountList();
UnreadIndicatorUpdater.listenForReportChanges();
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
index 2fbbca0e6dae..d2af35edba66 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
@@ -17,6 +17,7 @@ import SettingsAppDownloadLinks from '../../../pages/settings/AppDownloadLinks';
import SettingsPasswordPage from '../../../pages/settings/PasswordPage';
import SettingsPaymentsPage from '../../../pages/settings/PaymentsPage';
import SettingsAddSecondaryLoginPage from '../../../pages/settings/AddSecondaryLoginPage';
+import IOUCurrencySelection from '../../../pages/iou/IOUCurrencySelection';
import ReportParticipantsPage from '../../../pages/ReportParticipantsPage';
import AddBankAccountPage from '../../../pages/AddBankAccountPage';
@@ -54,11 +55,19 @@ function createModalStackNavigator(screens) {
const IOUBillStackNavigator = createModalStackNavigator([{
Component: IOUBillPage,
name: 'IOU_Bill_Root',
+},
+{
+ Component: IOUCurrencySelection,
+ name: 'IOU_Bill_Currency',
}]);
const IOURequestModalStackNavigator = createModalStackNavigator([{
Component: IOURequestPage,
name: 'IOU_Request_Root',
+},
+{
+ Component: IOUCurrencySelection,
+ name: 'IOU_Request_Currency',
}]);
const IOUDetailsModalStackNavigator = createModalStackNavigator([{
diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js
index 48039032d008..d2267f5131d5 100644
--- a/src/libs/Navigation/linkingConfig.js
+++ b/src/libs/Navigation/linkingConfig.js
@@ -90,11 +90,13 @@ export default {
IOU_Request: {
screens: {
IOU_Request_Root: ROUTES.IOU_REQUEST,
+ IOU_Request_Currency: ROUTES.IOU_REQUEST_CURRENCY,
},
},
IOU_Bill: {
screens: {
IOU_Bill_Root: ROUTES.IOU_BILL,
+ IOU_Bill_Currency: ROUTES.IOU_BILL_CURRENCY,
},
},
IOU_Details: {
diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js
index f53835f9a116..20171a4c95d1 100644
--- a/src/libs/OptionsListUtils.js
+++ b/src/libs/OptionsListUtils.js
@@ -482,6 +482,24 @@ function getHeaderMessage(hasSelectableOptions, hasUserToInvite, searchValue, ma
return '';
}
+/**
+ * Returns the currency list for sections display
+ *
+ * @param {Object} currencyOptions
+ * @param {String} searchValue
+ * @param {Object} selectedCurrency
+ * @returns {Array}
+ */
+function getCurrencyListForSections(currencyOptions, searchValue) {
+ const filteredOptions = currencyOptions.filter(currencyOption => (
+ isSearchStringMatch(searchValue, currencyOption.searchText)));
+
+ return {
+ // returns filtered options i.e. options with string match if search text is entered
+ currencyOptions: filteredOptions,
+ };
+}
+
export {
getSearchOptions,
getNewChatOptions,
@@ -489,6 +507,7 @@ export {
getSidebarOptions,
getHeaderMessage,
getPersonalDetailsForLogins,
+ getCurrencyListForSections,
getIOUConfirmationOptionsFromMyPersonalDetail,
getIOUConfirmationOptionsFromParticipants,
};
diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js
index cdd3fe381f7f..4f65c96b2ee7 100644
--- a/src/libs/actions/PersonalDetails.js
+++ b/src/libs/actions/PersonalDetails.js
@@ -2,6 +2,7 @@ import _ from 'underscore';
import lodashGet from 'lodash/get';
import lodashMerge from 'lodash/merge';
import Onyx from 'react-native-onyx';
+import Geolocation from 'react-native-geolocation-service';
import Str from 'expensify-common/lib/str';
import ONYXKEYS from '../../ONYXKEYS';
import md5 from '../md5';
@@ -236,6 +237,56 @@ function setPersonalDetails(details) {
mergeLocalPersonalDetails(details);
}
+/**
+ * Sets the onyx with the currency list from the network
+ * @returns {Object}
+ */
+function getCurrencyList() {
+ return API.GetCurrencyList()
+ .then((data) => {
+ const currencyListObject = JSON.parse(data.currencyList);
+ Onyx.merge(ONYXKEYS.CURRENCY_LIST, currencyListObject);
+ return currencyListObject;
+ });
+}
+
+/**
+ * Fetches the Currency preferences based on location
+ * @param {bool} withLocation
+ */
+function fetchCurrencyPreferences(withLocation) {
+ let coords = {};
+ let currency = '';
+
+ if (withLocation) {
+ Geolocation.getCurrentPosition((position) => {
+ coords = {
+ longitude: position.coords.longitude,
+ latitude: position.coords.latitude,
+ };
+ });
+ Onyx.merge(ONYXKEYS.MY_PERSONAL_DETAILS,
+ {
+ isCurrencyPreferencesSaved: true,
+ });
+ }
+
+ API.GetPreferredCurrency({...coords})
+ .then((data) => {
+ currency = data.currency;
+ })
+ .then(API.GetCurrencyList)
+ .then(getCurrencyList)
+ .then((currencyList) => {
+ Onyx.merge(ONYXKEYS.MY_PERSONAL_DETAILS,
+ {
+ preferredCurrencyCode: currency,
+ preferredCurrencySymbol: currencyList[currency].symbol,
+ });
+ })
+ .catch(error => console.debug(`Error fetching currency preference: , ${error}`));
+}
+
/**
* Sets the user's avatar image
*
@@ -273,4 +324,6 @@ export {
setPersonalDetails,
setAvatar,
deleteAvatar,
+ fetchCurrencyPreferences,
+ getCurrencyList,
};
diff --git a/src/pages/home/sidebar/OptionRow.js b/src/pages/home/sidebar/OptionRow.js
index 28532fc42f2d..d6bdb9b34ef2 100644
--- a/src/pages/home/sidebar/OptionRow.js
+++ b/src/pages/home/sidebar/OptionRow.js
@@ -123,7 +123,8 @@ const OptionRow = ({
{displayName: (isMultipleParticipant ? firstName : displayName) || login, tooltip: login}
),
);
- const fullTitle = displayNamesWithTooltips.map(({displayName}) => displayName).join(', ');
+ const fullTitle = option.text ? option.text
+ : displayNamesWithTooltips.map(({displayName}) => displayName).join(', ');
return (
{hovered => (
diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js
new file mode 100644
index 000000000000..c0d3cd0430ff
--- /dev/null
+++ b/src/pages/iou/IOUCurrencySelection.js
@@ -0,0 +1,243 @@
+import React, {Component} from 'react';
+import {Pressable, SectionList, View} from 'react-native';
+import PropTypes from 'prop-types';
+import Onyx, {withOnyx} from 'react-native-onyx';
+import _ from 'underscore';
+import styles from '../../styles/styles';
+import {fetchCurrencyPreferences, getCurrencyList} from '../../libs/actions/PersonalDetails';
+import ONYXKEYS from '../../ONYXKEYS';
+import {getCurrencyListForSections} from '../../libs/OptionsListUtils';
+import Text from '../../components/Text';
+import OptionRow from '../home/sidebar/OptionRow';
+import themeColors from '../../styles/themes/default';
+import TextInputWithFocusStyles from '../../components/TextInputWithFocusStyles';
+import Navigation from '../../libs/Navigation/Navigation';
+import ScreenWrapper from '../../components/ScreenWrapper';
+import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
+import compose from '../../libs/compose';
+import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
+
+/**
+ * IOU Currency selection for selecting currency
+ */
+const propTypes = {
+
+ // The personal details of the person who is logged in
+ myPersonalDetails: PropTypes.shape({
+
+ // Preferred Currency Code of the current user
+ preferredCurrencyCode: PropTypes.string,
+
+ // Currency Symbol of the Preferred Currency
+ preferredCurrencySymbol: PropTypes.string,
+
+ // Whether preferences for the currency are saved
+ isCurrencyPreferencesSaved: PropTypes.bool,
+ }),
+
+ // The currency list constant object from Onyx
+ currencyList: PropTypes.objectOf(PropTypes.shape({
+ // Symbol for the currency
+ symbol: PropTypes.string,
+
+ // Name of the currency
+ name: PropTypes.string,
+
+ // ISO4217 Code for the currency
+ ISO4217: PropTypes.string,
+ })),
+ ...withLocalizePropTypes,
+};
+
+const defaultProps = {
+ myPersonalDetails: {preferredCurrencyCode: 'USD', preferredCurrencySymbol: '$'},
+ currencyList: {},
+};
+
+class IOUCurrencySelection extends Component {
+ constructor(props) {
+ super(props);
+
+ const {currencyOptions} = getCurrencyListForSections(this.getCurrencyOptions(this.props.currencyList),
+ '');
+
+ this.state = {
+ searchValue: '',
+ currencyData: currencyOptions,
+ selectedCurrency: {
+ currencyCode: this.props.myPersonalDetails.preferredCurrencyCode,
+ currencySymbol: this.props.myPersonalDetails.preferredCurrencySymbol,
+ },
+ };
+ this.getCurrencyOptions = this.getCurrencyOptions.bind(this);
+ this.toggleOption = this.toggleOption.bind(this);
+ this.getSections = this.getSections.bind(this);
+ this.confirmCurrencySelection = this.confirmCurrencySelection.bind(this);
+ this.changeSearchValue = this.changeSearchValue.bind(this);
+ }
+
+ componentDidMount() {
+ getCurrencyList();
+ if (!this.props.myPersonalDetails.isCurrencyPreferencesSaved) {
+ fetchCurrencyPreferences(true);
+ }
+ }
+
+ /**
+ * Returns the sections needed for the OptionsSelector
+ *
+ * @param {Boolean} maxParticipantsReached
+ * @returns {Array}
+ */
+ getSections() {
+ const sections = [];
+
+ sections.push({
+ title: this.props.translate('iOUCurrencySelection.allCurrencies'),
+ data: this.state.currencyData,
+ shouldShow: true,
+ indexOffset: 0,
+ });
+
+ return sections;
+ }
+
+ /**
+ *
+ * @returns {Object}
+ */
+ getCurrencyOptions() {
+ const currencyListKeys = _.keys(this.props.currencyList);
+ const currencyOptions = _.map(currencyListKeys, currencyCode => ({
+ text: `${currencyCode} - ${this.props.currencyList[currencyCode].symbol}`,
+ searchText: `${currencyCode} ${this.props.currencyList[currencyCode].symbol}`,
+ currencyCode,
+ }));
+ return currencyOptions;
+ }
+
+ /**
+ * Function which renders a row in the list
+ *
+ * @param {String} currencyCode
+ *
+ */
+ toggleOption(currencyCode) {
+ this.setState({
+ selectedCurrency: {
+ currencyCode,
+ currencySymbol: this.props.currencyList[currencyCode].symbol,
+ },
+ });
+ }
+
+ /**
+ * Sets new search value
+ * @param {String} searchValue
+ * @return {void}
+ */
+ changeSearchValue(searchValue) {
+ const {currencyOptions} = getCurrencyListForSections(
+ this.getCurrencyOptions(this.props.currencyList),
+ searchValue,
+ );
+ this.setState({
+ searchValue,
+ currencyData: currencyOptions,
+ });
+ }
+
+ /**
+ * Confirms the selection of currency and sets values in Onyx
+ * @return {void}
+ */
+ confirmCurrencySelection() {
+ Onyx.merge(ONYXKEYS.MY_PERSONAL_DETAILS, {
+ preferredCurrencyCode: this.state.selectedCurrency.currencyCode,
+ preferredCurrencySymbol: this.state.selectedCurrency.currencySymbol,
+ });
+ Navigation.goBack();
+ }
+
+ render() {
+ return (
+
+ Navigation.goBack()}
+ />
+
+
+
+ this.textInput = el}
+ style={[styles.textInput]}
+ value={this.state.searchValue}
+ onChangeText={this.changeSearchValue}
+ placeholder="Search"
+ placeholderTextColor={themeColors.placeholderText}
+ />
+
+
+ option.currencyCode}
+ stickySectionHeadersEnabled={false}
+ renderItem={({item, key}) => (
+ this.toggleOption(item.currencyCode)}
+ isSelected={item.currencyCode === this.state.selectedCurrency.currencyCode}
+ showSelectedState
+ hideAdditionalOptionStates
+ />
+ )}
+ renderSectionHeader={({section: {title}}) => (
+
+
+ {title}
+
+
+ )}
+ />
+
+
+
+ [
+ styles.button,
+ styles.buttonSuccess,
+ styles.w100,
+ hovered && styles.buttonSuccessHovered,
+ ]}
+ >
+
+ Confirm
+
+
+
+
+
+ );
+ }
+}
+
+IOUCurrencySelection.propTypes = propTypes;
+IOUCurrencySelection.defaultProps = defaultProps;
+IOUCurrencySelection.displayName = 'IOUCurrencySelection';
+
+export default compose(
+ withLocalize,
+ withOnyx({
+ currencyList: {key: ONYXKEYS.CURRENCY_LIST},
+ myPersonalDetails: {key: ONYXKEYS.MY_PERSONAL_DETAILS},
+ }),
+)(IOUCurrencySelection);
diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js
index bca1798c0a99..ebc2ae3fbb9a 100755
--- a/src/pages/iou/IOUModal.js
+++ b/src/pages/iou/IOUModal.js
@@ -30,7 +30,17 @@ const propTypes = {
participants: PropTypes.arrayOf(PropTypes.string),
}),
- /** Holds data related to IOU view state, rather than the underlying IOU data. */
+ // The personal details of the person who is logged in
+ myPersonalDetails: PropTypes.shape({
+
+ // Preferred Currency Code of the current user
+ preferredCurrencyCode: PropTypes.string,
+
+ // Currency Symbol of the Preferred Currency
+ preferredCurrencySymbol: PropTypes.string,
+ }),
+
+ // Holds data related to IOU view state, rather than the underlying IOU data.
iou: PropTypes.shape({
/** Whether or not transaction creation has started */
creatingIOUTransaction: PropTypes.bool,
@@ -59,6 +69,10 @@ const defaultProps = {
report: {
participants: [],
},
+ myPersonalDetails: {
+ preferredCurrencyCode: 'USD',
+ preferredCurrencySymbol: '$',
+ },
};
// Determines type of step to display within Modal, value provides the title for that page.
@@ -94,7 +108,10 @@ class IOUModal extends Component {
// amount is currency in decimal format
amount: '',
- selectedCurrency: 'USD',
+ selectedCurrency: {
+ currencyCode: props.myPersonalDetails.preferredCurrencyCode,
+ currencySymbol: props.myPersonalDetails.preferredCurrencySymbol,
+ },
comment: '',
};
@@ -116,6 +133,14 @@ class IOUModal extends Component {
if (prevProps.iou.creatingIOUTransaction && !this.props.iou.creatingIOUTransaction && !this.props.iou.error) {
Navigation.dismissModal();
}
+
+ if (prevProps.myPersonalDetails.preferredCurrencyCode
+ !== this.props.myPersonalDetails.preferredCurrencyCode) {
+ this.updateSelectedCurrency({
+ currencyCode: this.props.myPersonalDetails.preferredCurrencyCode,
+ currencySymbol: this.props.myPersonalDetails.preferredCurrencySymbol,
+ });
+ }
}
/**
@@ -128,7 +153,13 @@ class IOUModal extends Component {
if (currentStepIndex === 1 || currentStepIndex === 2) {
return `${this.props.hasMultipleParticipants
? this.props.translate('common.split')
- : this.props.translate('iou.request', {amount: this.state.amount})}`;
+ : this.props.translate('iou.request',
+ {
+ amount: this.props.numberFormat(this.state.amount, {
+ style: 'currency',
+ currency: this.state.selectedCurrency.currencyCode,
+ }),
+ })}`;
}
if (currentStepIndex === 0) {
return this.props.translate(this.props.hasMultipleParticipants ? 'iou.splitBill' : 'iou.requestMoney');
@@ -143,6 +174,16 @@ class IOUModal extends Component {
});
}
+ /**
+ * Update the selected currency
+ * @param {Object} selectedCurrency
+ */
+ updateSelectedCurrency(selectedCurrency) {
+ this.setState({
+ selectedCurrency,
+ });
+ }
+
/**
* Navigate to the next IOU step if possible
*/
@@ -197,7 +238,7 @@ class IOUModal extends Component {
// should send in cents to API
amount: this.state.amount * 100,
- currency: this.state.selectedCurrency,
+ currency: this.state.selectedCurrency.currencyCode,
splits,
});
return;
@@ -208,7 +249,7 @@ class IOUModal extends Component {
// should send in cents to API
amount: this.state.amount * 100,
- currency: this.state.selectedCurrency,
+ currency: this.state.selectedCurrency.currencyCode,
debtorEmail: this.state.participants[0].login,
});
}
@@ -254,7 +295,9 @@ class IOUModal extends Component {
this.navigateToNextStep();
}}
currencySelected={this.currencySelected}
+ reportID={this.props.route.params.reportID}
selectedCurrency={this.state.selectedCurrency}
+ hasMultipleParticipants={this.props.hasMultipleParticipants}
selectedAmount={this.state.amount}
navigation={this.props.navigation}
/>
@@ -302,5 +345,8 @@ export default compose(
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS,
},
+ myPersonalDetails: {
+ key: ONYXKEYS.MY_PERSONAL_DETAILS,
+ },
}),
)(IOUModal);
diff --git a/src/pages/iou/steps/IOUAmountPage.js b/src/pages/iou/steps/IOUAmountPage.js
index 275f5927e6dc..a5613a8bd8ea 100755
--- a/src/pages/iou/steps/IOUAmountPage.js
+++ b/src/pages/iou/steps/IOUAmountPage.js
@@ -2,6 +2,7 @@ import React from 'react';
import {
View,
Text,
+ TouchableOpacity,
} from 'react-native';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
@@ -10,21 +11,35 @@ import styles from '../../../styles/styles';
import BigNumberPad from '../../../components/BigNumberPad';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
import TextInputAutoWidth from '../../../components/TextInputAutoWidth';
+import Navigation from '../../../libs/Navigation/Navigation';
+import ROUTES from '../../../ROUTES';
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
import compose from '../../../libs/compose';
import Button from '../../../components/Button';
import KeyboardShortcut from '../../../libs/KeyboardShortcut';
const propTypes = {
- /** Callback to inform parent modal of success */
+ // Whether or not this IOU has multiple participants
+ hasMultipleParticipants: PropTypes.bool.isRequired,
+
+ /* The ID of the report this screen should display */
+ reportID: PropTypes.string.isRequired,
+
+ // Callback to inform parent modal of success
onStepComplete: PropTypes.func.isRequired,
/** Currency selection will be implemented later */
// eslint-disable-next-line react/no-unused-prop-types
currencySelected: PropTypes.func.isRequired,
- /** User's currency preference */
- selectedCurrency: PropTypes.string.isRequired,
+ // User's currency preference
+ selectedCurrency: PropTypes.shape({
+ // Currency code for the selected currency
+ currencyCode: PropTypes.string,
+
+ // Currency symbol for the selected currency
+ currencySymbol: PropTypes.string,
+ }).isRequired,
/** Previously selected amount to show if the user comes back to this screen */
selectedAmount: PropTypes.string.isRequired,
@@ -134,9 +149,14 @@ class IOUAmountPage extends React.Component {
styles.justifyContentCenter,
]}
>
-
- {this.props.selectedCurrency}
-
+ Navigation.navigate(this.props.hasMultipleParticipants
+ ? ROUTES.getIouBillCurrencyRoute(this.props.reportID)
+ : ROUTES.getIouRequestCurrencyRoute(this.props.reportID))}
+ >
+
+ {this.props.selectedCurrency.currencySymbol}
+
+
{this.props.isSmallScreenWidth
? (