diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index ee0d714124fdd..75c0fb37e23e6 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -6,6 +6,9 @@
+
+
+
Your camera is used to create chat attachments.
NSLocationWhenInUseUsageDescription
+ NSLocationAlwaysAndWhenInUseUsageDescription
+
+ NSLocationAlwaysUsageDescription
+
NSPhotoLibraryAddUsageDescription
Your camera roll is used to store chat attachments.
NSPhotoLibraryUsageDescription
diff --git a/ios/Podfile b/ios/Podfile
index b8f5255f0274c..4bb3728efbfa6 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -6,8 +6,14 @@ platform :ios, '11.0'
target 'ExpensifyCash' do
config = use_native_modules!
+ permissions_path = '../node_modules/react-native-permissions/ios'
+
use_react_native!(:path => config["reactNativePath"])
+ pod 'Permission-LocationAccuracy', :path => "#{permissions_path}/LocationAccuracy"
+ pod 'Permission-LocationAlways', :path => "#{permissions_path}/LocationAlways"
+ pod 'Permission-LocationWhenInUse', :path => "#{permissions_path}/LocationWhenInUse"
+
target 'ExpensifyCashTests' do
inherit! :complete
# Pods for testing
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 352903821326f..4e7da22b3219d 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -153,6 +153,12 @@ PODS:
- OpenSSL-Universal (1.0.2.20):
- OpenSSL-Universal/Static (= 1.0.2.20)
- OpenSSL-Universal/Static (1.0.2.20)
+ - Permission-LocationAccuracy (3.0.1):
+ - RNPermissions
+ - Permission-LocationAlways (3.0.1):
+ - RNPermissions
+ - Permission-LocationWhenInUse (3.0.1):
+ - RNPermissions
- PromisesObjC (1.2.11)
- RCTRequired (0.63.3)
- RCTTypeSafety (0.63.3):
@@ -326,6 +332,8 @@ PODS:
- React-Core
- react-native-document-picker (4.0.0):
- React-Core
+ - react-native-geolocation (2.0.2):
+ - React
- react-native-image-picker (2.3.4):
- React-Core
- react-native-netinfo (5.9.10):
@@ -419,6 +427,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):
@@ -457,6 +467,9 @@ DEPENDENCIES:
- FlipperKit/SKIOSNetworkPlugin (~> 0.54.0)
- 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`)
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
- React (from `../node_modules/react-native/`)
@@ -471,6 +484,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 (from `../node_modules/@react-native-community/geolocation`)"
- 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`)
@@ -495,6 +509,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`)
@@ -539,6 +554,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:
@@ -563,6 +584,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-config"
react-native-document-picker:
:path: "../node_modules/react-native-document-picker"
+ react-native-geolocation:
+ :path: "../node_modules/@react-native-community/geolocation"
react-native-image-picker:
:path: "../node_modules/react-native-image-picker"
react-native-netinfo:
@@ -611,6 +634,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:
@@ -650,6 +675,9 @@ SPEC CHECKSUMS:
GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3
nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc
OpenSSL-Universal: ff34003318d5e1163e9529b08470708e389ffcdd
+ Permission-LocationAccuracy: e8adff9ede1b23b43b7054a4500113d515fc87a8
+ Permission-LocationAlways: 7f7f373d086af7a81b2f4f20d65d29266ca2043b
+ Permission-LocationWhenInUse: 3ae82a9feb5da4e94e386dba17c7dd3531af9feb
PromisesObjC: 8c196f5a328c2cba3e74624585467a557dcb482f
RCTRequired: 48884c74035a0b5b76dbb7a998bd93bcfc5f2047
RCTTypeSafety: edf4b618033c2f1c5b7bc3d90d8e085ed95ba2ab
@@ -663,6 +691,7 @@ SPEC CHECKSUMS:
React-jsinspector: 8e68ffbfe23880d3ee9bafa8be2777f60b25cbe2
react-native-config: d8b45133fd13d4f23bd2064b72f6e2c08b2763ed
react-native-document-picker: b3e78a8f7fef98b5cb069f20fc35797d55e68e28
+ react-native-geolocation: cbd9d6bd06bac411eed2671810f454d4908484a8
react-native-image-picker: 32d1ad2c0024ca36161ae0d5c2117e2d6c441f11
react-native-netinfo: 52cf0ee8342548a485e28f4b09e56b477567244d
react-native-pdf: 4b5a9e4465a6a3b399e91dc4838eb44ddf716d1f
@@ -687,6 +716,7 @@ SPEC CHECKSUMS:
RNFBApp: 7eacc7da7ab19f96c05e434017d44a9f09410da8
RNFBCrashlytics: 4870c14cf8833053b6b5648911abefe1923854d2
RNGestureHandler: 9b7e605a741412e20e13c512738a31bd1611759b
+ RNPermissions: eb94f9fdc0a8ecd02fcce0676d56ffb1395d41e1
RNReanimated: e03f7425cb7a38dcf1b644d680d1bfc91c3337ad
RNScreens: b6c9607e6fe47c1b6e2f1910d2acd46dd7ecea3a
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
@@ -694,6 +724,6 @@ SPEC CHECKSUMS:
Yoga: 7d13633d129fd179e01b8953d38d47be90db185a
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
-PODFILE CHECKSUM: 41b806c7f131f87b716be1f1f9377532d6c9e43a
+PODFILE CHECKSUM: c730d417ba39bd38d4b63c8529779f2eff43bf19
COCOAPODS: 1.10.0
diff --git a/package-lock.json b/package-lock.json
index 156e66cf2d45e..ebe0a61fe8921 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2080,11 +2080,6 @@
"picomatch": "^2.0.5"
}
},
- "mime": {
- "version": "2.4.6",
- "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz",
- "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA=="
- },
"pretty-format": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz",
@@ -3792,6 +3787,11 @@
"integrity": "sha512-W/J0fNYVO01tioHjvYWQ9m6RgndVtbElzYozBq1ZPrHO/iCzlqoySHl4gO/fpCl9QEFjvJfjPgtPMTMlsoq5DQ==",
"dev": true
},
+ "@react-native-community/geolocation": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@react-native-community/geolocation/-/geolocation-2.0.2.tgz",
+ "integrity": "sha512-tTNXRCgnhJBu79mulQwzabXRpDqfh/uaDqfHVpvF0nX4NTpolpy6mvTRiFg7eWFPGRArsnZz1EYp6rHfJWGgEA=="
+ },
"@react-native-community/masked-view": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/@react-native-community/masked-view/-/masked-view-0.1.10.tgz",
@@ -11080,8 +11080,8 @@
}
},
"expensify-common": {
- "version": "git+https://github.com/Expensify/expensify-common.git#b89c2c591a2f76ae1a3d8a4a2e2d5e5159d65edc",
- "from": "git+https://github.com/Expensify/expensify-common.git#b89c2c591a2f76ae1a3d8a4a2e2d5e5159d65edc",
+ "version": "git+ssh://git@github.com/Expensify/expensify-common.git#b89c2c591a2f76ae1a3d8a4a2e2d5e5159d65edc",
+ "from": "expensify-common@git+https://github.com/Expensify/expensify-common.git#b89c2c591a2f76ae1a3d8a4a2e2d5e5159d65edc",
"requires": {
"classnames": "2.2.5",
"clipboard": "2.0.4",
@@ -11092,7 +11092,7 @@
"react": "16.12.0",
"react-dom": "16.12.0",
"semver": "^7.3.4",
- "simply-deferred": "git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5",
+ "simply-deferred": "simply-deferred@git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5",
"underscore": "1.9.1"
},
"dependencies": {
@@ -21152,19 +21152,19 @@
}
},
"react-native-onyx": {
- "version": "git+https://github.com/Expensify/react-native-onyx.git#bd59626781393c93226fb80cb45569408e97b67c",
- "from": "git+https://github.com/Expensify/react-native-onyx.git#bd59626781393c93226fb80cb45569408e97b67c",
+ "version": "git+ssh://git@github.com/Expensify/react-native-onyx.git#bd59626781393c93226fb80cb45569408e97b67c",
+ "from": "react-native-onyx@git+https://github.com/Expensify/react-native-onyx.git#bd59626781393c93226fb80cb45569408e97b67c",
"requires": {
"@react-native-community/async-storage": "^1.12.1",
- "expensify-common": "git+https://github.com/Expensify/expensify-common.git#c7becaa79e10c10da521261ebb83dd3871847e84",
+ "expensify-common": "expensify-common@git+https://github.com/Expensify/expensify-common.git#c7becaa79e10c10da521261ebb83dd3871847e84",
"lodash": "4.17.21",
"react": "^16.13.1",
"underscore": "^1.11.0"
},
"dependencies": {
"expensify-common": {
- "version": "git+https://github.com/Expensify/expensify-common.git#c7becaa79e10c10da521261ebb83dd3871847e84",
- "from": "git+https://github.com/Expensify/expensify-common.git#c7becaa79e10c10da521261ebb83dd3871847e84",
+ "version": "git+ssh://git@github.com/Expensify/expensify-common.git#c7becaa79e10c10da521261ebb83dd3871847e84",
+ "from": "expensify-common@git+https://github.com/Expensify/expensify-common.git#c7becaa79e10c10da521261ebb83dd3871847e84",
"requires": {
"classnames": "2.2.5",
"clipboard": "2.0.4",
@@ -21175,7 +21175,7 @@
"react": "16.12.0",
"react-dom": "16.12.0",
"semver": "^7.3.4",
- "simply-deferred": "git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5",
+ "simply-deferred": "simply-deferred@git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5",
"underscore": "1.9.1"
},
"dependencies": {
@@ -21225,6 +21225,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",
@@ -22750,8 +22755,8 @@
}
},
"simply-deferred": {
- "version": "git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5",
- "from": "git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5"
+ "version": "git+ssh://git@github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5",
+ "from": "simply-deferred@git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5"
},
"sirv": {
"version": "1.0.11",
diff --git a/package.json b/package.json
index aad229cea85b5..91bcc4e0e3cda 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"@babel/preset-flow": "^7.12.13",
"@react-native-community/async-storage": "^1.11.0",
"@react-native-community/cli": "4.13.1",
+ "@react-native-community/geolocation": "^2.0.2",
"@react-native-community/masked-view": "^0.1.10",
"@react-native-community/netinfo": "^5.9.10",
"@react-native-community/progress-bar-android": "^1.0.4",
@@ -70,6 +71,7 @@
"react-native-modal": "^11.5.6",
"react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#bd59626781393c93226fb80cb45569408e97b67c",
"react-native-pdf": "^6.2.2",
+ "react-native-permissions": "^3.0.1",
"react-native-picker-select": "8.0.4",
"react-native-reanimated": "1.13.2",
"react-native-render-html": "^6.0.0-alpha.10",
diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js
index 8830470585943..40341c68a2ec2 100644
--- a/src/ONYXKEYS.js
+++ b/src/ONYXKEYS.js
@@ -42,6 +42,9 @@ export default {
// an international code
COUNTRY_CODE: 'countryCode',
+ // Saves the currency list obtained from the network
+ CURRENCY_LIST: 'currencyList',
+
// Contains all the users settings for the Settings page and sub pages
USER: 'user',
diff --git a/src/libs/API.js b/src/libs/API.js
index e6156fca7b7dc..edd92c5910b81 100644
--- a/src/libs/API.js
+++ b/src/libs/API.js
@@ -631,6 +631,24 @@ function GetIOUReport(parameters) {
return Network.post(commandName, parameters);
}
+/**
+ * @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,
@@ -661,4 +679,6 @@ export {
User_SecondaryLogin_Send,
User_UploadAvatar,
reauthenticate,
+ GetPreferredCurrency,
+ GetCurrencyList,
};
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js
index 948bac85a5e2f..94f43f87386fa 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.js
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.js
@@ -71,6 +71,7 @@ class AuthScreens extends React.Component {
// Fetch some data we need on initialization
NameValuePair.get(CONST.NVP.PRIORITY_MODE, ONYXKEYS.NVP_PRIORITY_MODE, 'default');
PersonalDetails.fetch();
+ PersonalDetails.setCurrencyPreferences();
User.fetch();
User.getBetas();
fetchAllReports(true, true);
diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js
index 58793741fc8e5..60d01a97fadc0 100644
--- a/src/libs/OptionsListUtils.js
+++ b/src/libs/OptionsListUtils.js
@@ -143,6 +143,32 @@ function isSearchStringMatch(searchValue, searchText) {
});
}
+/**
+ * Returns the personal details for an array of logins
+ *
+ * @param {Object} currencyListObject
+ * @param {String} searchValue
+ * @param {Object} selectedCurrency
+ * @returns {Array}
+ */
+function getCurrencyListForSections(currencyListObject, searchValue) {
+ const currencyListKeys = _.keys(currencyListObject);
+ const currencyOptions = _.map(currencyListKeys, currencyCode => ({
+ text: `${currencyCode} - ${currencyListObject[currencyCode].symbol}`,
+ searchText: `${currencyCode} ${currencyListObject[currencyCode].symbol}`,
+ currencyCode,
+ }));
+
+ const filteredOptions = currencyOptions.filter(
+ currencyOption => isSearchStringMatch(searchValue, currencyOption.searchText),
+ );
+
+ return {
+ // currency options holds a section for those currencies which are not selected
+ currencyOptions: filteredOptions,
+ };
+}
+
/**
* Build the options
*
@@ -432,4 +458,5 @@ export {
getSidebarOptions,
getHeaderMessage,
getPersonalDetailsForLogins,
+ getCurrencyListForSections,
};
diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js
index 7c14f6f3fc985..6d3c8d33c14e9 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 Onyx from 'react-native-onyx';
import Str from 'expensify-common/lib/str';
+import Geolocation from '@react-native-community/geolocation';
import ONYXKEYS from '../../ONYXKEYS';
import md5 from '../md5';
import CONST from '../../CONST';
@@ -213,6 +214,54 @@ function setPersonalDetails(details) {
Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, formatPersonalDetails({[currentUserEmail]: details}));
}
+/**
+ * Gets the preferred currency for the current user
+ *
+ * @param {Object} details
+ */
+function setCurrencyPreferences() {
+ let coords = {};
+ let currency = '';
+
+ Geolocation.getCurrentPosition((position) => {
+ coords = {
+ longitude: position.coords.longitude,
+ latitude: position.coords.latitude,
+ };
+ });
+
+
+ API.GetPreferredCurrency({...coords})
+ .then((data) => {
+ currency = data.currency;
+ })
+ .then(() => API.GetCurrencyList())
+ .then((data) => {
+ const currencyList = JSON.parse(data.currencyList);
+ Onyx.merge(ONYXKEYS.CURRENCY_LIST, currencyList);
+ return currencyList;
+ })
+ .then((currencyList) => {
+ Onyx.merge(ONYXKEYS.MY_PERSONAL_DETAILS,
+ {preferredCurrencyCode: currency, preferredCurrencySymbol: currencyList[currency].symbol});
+ })
+ .catch(error => console.debug(error));
+}
+
+/**
+ * Gets the preferred currency for the current user
+ *
+ * @param {Object} details
+ */
+function getCurrencyList() {
+ API.GetCurrencyList()
+ .then((data) => {
+ const currencyListObject = JSON.parse(data.currencyList);
+ Onyx.merge(ONYXKEYS.CURRENCY_LIST, currencyListObject);
+ return currencyListObject;
+ });
+}
+
/**
* Sets the user's avatar image
*
@@ -237,4 +286,6 @@ export {
getDefaultAvatar,
setPersonalDetails,
setAvatar,
+ setCurrencyPreferences,
+ getCurrencyList,
};
diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js
new file mode 100644
index 0000000000000..cff1e8f8033ff
--- /dev/null
+++ b/src/pages/iou/IOUCurrencySelection.js
@@ -0,0 +1,219 @@
+import React, {Component} from 'react';
+import {Pressable, SectionList, View} from 'react-native';
+import PropTypes from 'prop-types';
+import {withOnyx} from 'react-native-onyx';
+import styles from '../../styles/styles';
+import {getCurrencyList} from '../../libs/actions/PersonalDetails';
+import ONYXKEYS from '../../ONYXKEYS';
+import ScreenWrapper from '../../components/ScreenWrapper';
+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';
+
+/**
+ * IOU Currency selection for selecting currency
+ */
+const propTypes = {
+
+ // Callback that sets selectedCurrency in IOUModal
+ onCurrencySelected: PropTypes.func.isRequired,
+
+ // Callback that sets currency in Onyx and dismisses the currency mode
+ onCurrencyConfirm: PropTypes.func,
+
+ // User's currency preference
+ selectedCurrency: PropTypes.string.isRequired,
+
+ // 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,
+ }),
+
+ // The currency list constant object from Onyx
+ currencyList: PropTypes.objectOf(PropTypes.shape({
+ symbol: PropTypes.string,
+ name: PropTypes.string,
+ ISO4217: PropTypes.string,
+ })),
+};
+
+const defaultProps = {
+ myPersonalDetails: {preferredCurrencyCode: 'USD', preferredCurrencySymbol: '$'},
+ currencyList: {},
+ onCurrencyConfirm: null,
+};
+
+class IOUCurrencySelection extends Component {
+ constructor(props) {
+ super(props);
+
+ const {currencyOptions} = getCurrencyListForSections(this.props.currencyList,
+ '');
+
+ this.state = {
+ searchValue: '',
+ currencyData: currencyOptions,
+ };
+ this.renderItem = this.renderItem.bind(this);
+ this.getSections = this.getSections.bind(this);
+ }
+
+ componentDidMount() {
+ getCurrencyList();
+ }
+
+ /**
+ * Returns the sections needed for the OptionsSelector
+ *
+ * @param {Boolean} maxParticipantsReached
+ * @returns {Array}
+ */
+ getSections() {
+ const sections = [];
+
+ sections.push({
+ title: 'ALL CURRENCIES',
+ data: this.state.currencyData,
+ shouldShow: true,
+ indexOffset: 0,
+ });
+
+ return sections;
+ }
+
+ /**
+ * Returns the key used by the list
+ * @param {Object} option
+ * @return {String}
+ */
+ extractKey(option) {
+ return option.currencyCode;
+ }
+
+ /**
+ * Function which renders a row in the list
+ *
+ * @param {String} currencyCode
+ *
+ */
+ toggleOption(currencyCode) {
+ this.props.onCurrencySelected({
+ currencyCode,
+ currencySymbol: this.props.currencyList[currencyCode].symbol,
+ });
+ }
+
+ /**
+ * Function which renders a row in the list
+ *
+ * @param {Object} params
+ * @param {Object} params.item
+ *
+ * @return {Component}
+ */
+ renderItem({item, key}) {
+ return (
+ this.toggleOption(item.currencyCode)}
+ isSelected={item.currencyCode === this.props.selectedCurrency.currencyCode}
+ showSelectedState
+ hideAdditionalOptionStates
+ />
+ );
+ }
+
+ /**
+ * Function which renders a section header component
+ *
+ * @param {Object} params
+ * @param {Object} params.section
+ * @param {String} params.section.title
+ *
+ * @return {Component}
+ */
+ renderSectionHeader({section: {title}}) {
+ return (
+
+
+ {title}
+
+
+ );
+ }
+
+ render() {
+ const sections = this.getSections();
+ return (
+
+ {() => (
+ <>
+
+
+ this.textInput = el}
+ style={[styles.textInput]}
+ value={this.state.searchValue}
+ onChangeText={(searchValue = '') => {
+ const {currencyOptions} = getCurrencyListForSections(
+ this.props.currencyList,
+ searchValue,
+ );
+ this.setState({
+ searchValue,
+ currencyData: currencyOptions,
+ });
+ }}
+ placeholder="Search"
+ placeholderTextColor={themeColors.placeholderText}
+ />
+
+
+
+ [
+ styles.button,
+ styles.buttonSuccess,
+ styles.w100,
+ hovered && styles.buttonSuccessHovered,
+ ]}
+ >
+
+ Confirm
+
+
+
+
+ >
+ )}
+
+ );
+ }
+}
+
+IOUCurrencySelection.propTypes = propTypes;
+IOUCurrencySelection.defaultProps = defaultProps;
+IOUCurrencySelection.displayName = 'IOUModal';
+
+export default withOnyx({currencyList: {key: ONYXKEYS.CURRENCY_LIST}})(IOUCurrencySelection);
diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js
index 593addfda4700..048faba817a01 100644
--- a/src/pages/iou/IOUModal.js
+++ b/src/pages/iou/IOUModal.js
@@ -1,6 +1,7 @@
import React, {Component} from 'react';
-import {View, TouchableOpacity} from 'react-native';
+import {View, TouchableOpacity, Keyboard} from 'react-native';
import PropTypes from 'prop-types';
+import Onyx, {withOnyx} from 'react-native-onyx';
import IOUAmountPage from './steps/IOUAmountPage';
import IOUParticipantsPage from './steps/IOUParticipantsPage';
import IOUConfirmPage from './steps/IOUConfirmPage';
@@ -10,6 +11,7 @@ import Icon from '../../components/Icon';
import {getPreferredCurrency} from '../../libs/actions/IOU';
import {Close, BackArrow} from '../../components/Icon/Expensicons';
import Navigation from '../../libs/Navigation/Navigation';
+import ONYXKEYS from '../../ONYXKEYS';
/**
* IOU modal for requesting money and splitting bills.
@@ -17,10 +19,21 @@ import Navigation from '../../libs/Navigation/Navigation';
const propTypes = {
// Is this new IOU for a single request or group bill split?
hasMultipleParticipants: PropTypes.bool,
+
+ // 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,
+ }),
};
const defaultProps = {
hasMultipleParticipants: false,
+ myPersonalDetails: {preferredCurrencyCode: 'USD', preferredCurrencySymbol: '$'},
};
// Determines type of step to display within Modal, value provides the title for that page.
@@ -40,14 +53,20 @@ class IOUModal extends Component {
this.navigateToPreviousStep = this.navigateToPreviousStep.bind(this);
this.navigateToNextStep = this.navigateToNextStep.bind(this);
this.updateAmount = this.updateAmount.bind(this);
- this.currencySelected = this.currencySelected.bind(this);
-
+ this.selectCurrency = this.selectCurrency.bind(this);
+ this.setCurrencyMode = this.setCurrencyMode.bind(this);
+ this.confirmCurrencySelection = this.confirmCurrencySelection.bind(this);
this.addParticipants = this.addParticipants.bind(this);
+
this.state = {
currentStepIndex: 0,
participants: [],
amount: '',
- selectedCurrency: 'USD',
+ selectedCurrency: {
+ currencyCode: props.myPersonalDetails.preferredCurrencyCode,
+ currencySymbol: props.myPersonalDetails.preferredCurrencySymbol,
+ },
+ currencySelectionMode: false,
isAmountPageNextButtonDisabled: true,
};
}
@@ -63,8 +82,12 @@ class IOUModal extends Component {
*/
getTitleForStep() {
+ if (this.state.currencySelectionMode) {
+ return 'Select Currency';
+ }
if (this.state.currentStepIndex === 1) {
- return `${this.props.hasMultipleParticipants ? 'Split' : 'Request'} $${this.state.amount}`;
+ return (`${this.props.hasMultipleParticipants ? 'Split'
+ : 'Request'} ${this.state.selectedCurrency.currencySymbol}${this.state.amount}`);
}
if (steps[this.state.currentStepIndex] === Steps.IOUAmount) {
return this.props.hasMultipleParticipants ? 'Split Bill' : 'Request Money';
@@ -72,6 +95,16 @@ class IOUModal extends Component {
return steps[this.state.currentStepIndex] || '';
}
+ /**
+ * Update the currency state
+ *
+ * @param {bool} isCurrencyModeOn
+ */
+ setCurrencyMode(isCurrencyModeOn) {
+ Keyboard.dismiss();
+ this.setState({currencySelectionMode: isCurrencyModeOn});
+ }
+
/**
* Navigate to the next IOU step if possible
*/
@@ -132,12 +165,24 @@ class IOUModal extends Component {
/**
* Update the currency state
*
- * @param {String} selectedCurrency
+ * @param {Object} selectedCurrency
*/
- currencySelected(selectedCurrency) {
+ selectCurrency(selectedCurrency) {
this.setState({selectedCurrency});
}
+ /**
+ * Update the currency state
+ *
+ */
+ confirmCurrencySelection() {
+ Onyx.merge(ONYXKEYS.MY_PERSONAL_DETAILS, {
+ preferredCurrencyCode: this.state.selectedCurrency.currencyCode,
+ preferredCurrencySymbol: this.state.selectedCurrency.currencySymbol,
+ });
+ this.setState({currencySelectionMode: false});
+ }
+
render() {
const currentStep = steps[this.state.currentStepIndex];
return (
@@ -176,8 +221,11 @@ class IOUModal extends Component {
@@ -206,4 +254,4 @@ IOUModal.propTypes = propTypes;
IOUModal.defaultProps = defaultProps;
IOUModal.displayName = 'IOUModal';
-export default IOUModal;
+export default withOnyx({myPersonalDetails: {key: ONYXKEYS.MY_PERSONAL_DETAILS}})(IOUModal);
diff --git a/src/pages/iou/steps/IOUAmountPage.js b/src/pages/iou/steps/IOUAmountPage.js
index 4d0c2e99d67e6..015250fdae78c 100644
--- a/src/pages/iou/steps/IOUAmountPage.js
+++ b/src/pages/iou/steps/IOUAmountPage.js
@@ -13,6 +13,7 @@ import themeColors from '../../../styles/themes/default';
import BigNumberPad from '../../../components/BigNumberPad';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
import TextInputFocusable from '../../../components/TextInputFocusable';
+import IOUCurrencySelection from '../IOUCurrencySelection';
const propTypes = {
// Callback to inform parent modal of success
@@ -21,13 +22,21 @@ const propTypes = {
// Callback to inform parent modal with key pressed
numberPressed: PropTypes.func.isRequired,
- // Currency selection will be implemented later
- // eslint-disable-next-line react/no-unused-prop-types
- currencySelected: PropTypes.func.isRequired,
+ // Callback that sets selectedCurrency in IOUModal
+ onCurrencySelected: PropTypes.func.isRequired,
// User's currency preference
selectedCurrency: PropTypes.string.isRequired,
+ // Whether or not currency selection mode is on
+ currencySelectionMode: PropTypes.bool,
+
+ // Callback that sets currency in Onyx and dismisses the currency mode
+ onCurrencyConfirm: PropTypes.func,
+
+ // Callback to set currency selection mode
+ setCurrencySelectionMode: PropTypes.func,
+
// Amount value entered by user
amount: PropTypes.string.isRequired,
@@ -49,7 +58,11 @@ const propTypes = {
const defaultProps = {
iou: {},
+ currencySelectionMode: false,
+ setCurrencySelectionMode: null,
+ onCurrencyConfirm: null,
};
+
class IOUAmountPage extends React.Component {
constructor(props) {
super(props);
@@ -65,7 +78,35 @@ class IOUAmountPage extends React.Component {
}
}
+ /**
+ * Returns the sections needed for the OptionsSelector
+ *
+ * @param {Boolean} maxParticipantsReached
+ * @returns {Array}
+ */
+ getSections() {
+ const sections = [];
+ sections.push({
+ title: undefined,
+ data: this.props.participants,
+ shouldShow: true,
+ indexOffset: 0,
+ });
+
+ return sections;
+ }
+
render() {
+ if (this.props.currencySelectionMode) {
+ return (
+
+ );
+ }
return (
{this.props.iou.loading && }
@@ -77,9 +118,11 @@ class IOUAmountPage extends React.Component {
styles.justifyContentCenter,
]}
>
-
- {this.props.selectedCurrency}
-
+ this.props.setCurrencySelectionMode(true)}>
+
+ {this.props.selectedCurrency.currencySymbol}
+
+
{this.props.isSmallScreenWidth
? {this.props.amount}
: (
@@ -127,6 +170,7 @@ IOUAmountPage.displayName = 'IOUAmountPage';
IOUAmountPage.propTypes = propTypes;
IOUAmountPage.defaultProps = defaultProps;
-export default withWindowDimensions(withOnyx({
+export default withWindowDimensions(
+ withOnyx({
iou: {key: ONYXKEYS.IOU},
})(IOUAmountPage));