diff --git a/README.md b/README.md
index 9e646a9..0cb2db2 100644
--- a/README.md
+++ b/README.md
@@ -6,10 +6,11 @@ Available languages : 🇬🇧, 🇫🇷, 🇪🇸
[
](https://play.google.com/store/apps/details?id=com.unitstool)
[
](https://f-droid.org/packages/com.unitstool/)
-
+
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
index 6f2a1d2..6d5b002 100755
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
index d240c09..643c573 100755
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png
new file mode 100755
index 0000000..a95fad5
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png differ
diff --git a/package-lock.json b/package-lock.json
index 36084e8..290bddd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,18 +1,19 @@
{
"name": "UnitsTool",
- "version": "0.0.1",
+ "version": "1.0.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "UnitsTool",
- "version": "0.0.1",
+ "version": "1.0.4",
"dependencies": {
"@react-native-async-storage/async-storage": "^1.17.5",
"@react-navigation/native": "^6.0.10",
"@react-navigation/native-stack": "^6.6.2",
"@rneui/base": "^4.0.0-rc.3",
"@rneui/themed": "^4.0.0-rc.3",
+ "axios": "^0.27.2",
"i18next": "^21.8.4",
"react": "17.0.2",
"react-i18next": "^11.16.9",
@@ -20,6 +21,7 @@
"react-native-popup-menu": "^0.15.12",
"react-native-safe-area-context": "^4.2.5",
"react-native-screens": "^3.13.1",
+ "react-native-snackbar": "^2.4.0",
"react-native-touchable-scale": "^2.1.2",
"react-native-vector-icons": "^9.1.0"
},
@@ -4392,8 +4394,7 @@
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
- "dev": true
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"node_modules/atob": {
"version": "2.1.2",
@@ -4406,6 +4407,28 @@
"node": ">= 4.5.0"
}
},
+ "node_modules/axios": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
+ "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
+ "dependencies": {
+ "follow-redirects": "^1.14.9",
+ "form-data": "^4.0.0"
+ }
+ },
+ "node_modules/axios/node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/babel-core": {
"version": "7.0.0-bridge.0",
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz",
@@ -5210,7 +5233,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "dev": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
@@ -5530,7 +5552,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
- "dev": true,
"engines": {
"node": ">=0.4.0"
}
@@ -6827,6 +6848,25 @@
"node": ">=0.4.0"
}
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
+ "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
"node_modules/for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -11847,6 +11887,15 @@
"react-native": "*"
}
},
+ "node_modules/react-native-snackbar": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/react-native-snackbar/-/react-native-snackbar-2.4.0.tgz",
+ "integrity": "sha512-xEDIDxw0ubBHrsvzaMfrZlFoy+ai1WEJZQwyAsG55fqYxUfQM20h+V4v25u+/H/YKY7ZCRMdCZ8zB5JOnbvCQw==",
+ "peerDependencies": {
+ "react": ">=16",
+ "react-native": ">=0.60"
+ }
+ },
"node_modules/react-native-touchable-scale": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/react-native-touchable-scale/-/react-native-touchable-scale-2.1.2.tgz",
@@ -17561,14 +17610,34 @@
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
- "dev": true
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
},
+ "axios": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
+ "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
+ "requires": {
+ "follow-redirects": "^1.14.9",
+ "form-data": "^4.0.0"
+ },
+ "dependencies": {
+ "form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ }
+ }
+ }
+ },
"babel-core": {
"version": "7.0.0-bridge.0",
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz",
@@ -18184,7 +18253,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "dev": true,
"requires": {
"delayed-stream": "~1.0.0"
}
@@ -18447,8 +18515,7 @@
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
- "dev": true
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"denodeify": {
"version": "1.2.1",
@@ -19418,6 +19485,11 @@
"resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.121.0.tgz",
"integrity": "sha512-1gIBiWJNR0tKUNv8gZuk7l9rVX06OuLzY9AoGio7y/JT4V1IZErEMEq2TJS+PFcw/y0RshZ1J/27VfK1UQzYVg=="
},
+ "follow-redirects": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
+ "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
+ },
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -23297,6 +23369,12 @@
"integrity": "sha512-8/C0htHrFWeUm9N8JegmadovUfgTWkGBkDPZ1N3YkXtDWb+98Ya2gThiKcu445r8c7YhcrBfnHz/mYsXIusaOQ==",
"requires": {}
},
+ "react-native-snackbar": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/react-native-snackbar/-/react-native-snackbar-2.4.0.tgz",
+ "integrity": "sha512-xEDIDxw0ubBHrsvzaMfrZlFoy+ai1WEJZQwyAsG55fqYxUfQM20h+V4v25u+/H/YKY7ZCRMdCZ8zB5JOnbvCQw==",
+ "requires": {}
+ },
"react-native-touchable-scale": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/react-native-touchable-scale/-/react-native-touchable-scale-2.1.2.tgz",
diff --git a/package.json b/package.json
index 3b2e457..44e0a09 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"@react-navigation/native-stack": "^6.6.2",
"@rneui/base": "^4.0.0-rc.3",
"@rneui/themed": "^4.0.0-rc.3",
+ "axios": "^0.27.2",
"i18next": "^21.8.4",
"react": "17.0.2",
"react-i18next": "^11.16.9",
@@ -22,6 +23,7 @@
"react-native-popup-menu": "^0.15.12",
"react-native-safe-area-context": "^4.2.5",
"react-native-screens": "^3.13.1",
+ "react-native-snackbar": "^2.4.0",
"react-native-touchable-scale": "^2.1.2",
"react-native-vector-icons": "^9.1.0"
},
diff --git a/screenshot/currency.png b/screenshot/currency.png
new file mode 100755
index 0000000..643c573
Binary files /dev/null and b/screenshot/currency.png differ
diff --git a/screenshot/darkmode.png b/screenshot/darkmode.png
index d240c09..a95fad5 100755
Binary files a/screenshot/darkmode.png and b/screenshot/darkmode.png differ
diff --git a/screenshot/home.png b/screenshot/home.png
index 6f2a1d2..6d5b002 100755
Binary files a/screenshot/home.png and b/screenshot/home.png differ
diff --git a/src/components/ListCategoryItem.js b/src/components/ListCategoryItem.js
index ac4b582..fcd70d3 100644
--- a/src/components/ListCategoryItem.js
+++ b/src/components/ListCategoryItem.js
@@ -3,6 +3,7 @@ import { Icon } from "@rneui/themed";
import TouchableScale from 'react-native-touchable-scale';
import { ListItem, Text, useTheme } from "@rneui/themed";
import { useTranslation } from 'react-i18next';
+import { currencyCount } from 'src/utils/currencies';
const ListCategoryItem = ({ navigation, conversion }) => {
@@ -24,7 +25,11 @@ const ListCategoryItem = ({ navigation, conversion }) => {
{t(conversion.title)}
- ({conversion.units.length} {t('units')})
+ {
+ conversion.category === 'currency'
+ ? ({currencyCount} {t('currencies')})
+ : ({conversion.units.length} {t('units')})
+ }
diff --git a/src/components/ListUnitItem.js b/src/components/ListUnitItem.js
index af92d10..8a7c47b 100644
--- a/src/components/ListUnitItem.js
+++ b/src/components/ListUnitItem.js
@@ -26,7 +26,10 @@ const ListUnitItem = ({ unit, value, isReferenceUnit, setRefUnit }) => {
{unit.symbol}
- {t(unit.name)}
+
+ {unit.emoji ? `${unit.emoji} ` : null}
+ {t(unit.name)}
+
diff --git a/src/components/UnitValue.js b/src/components/UnitValue.js
index b8ef63c..bb0d389 100644
--- a/src/components/UnitValue.js
+++ b/src/components/UnitValue.js
@@ -24,7 +24,10 @@ const UnitValue = ({ value, setValue, unit }) => {
/>
- {unit?.symbol ?? ''}
+
+ {unit?.symbol ?? ''}
+ {unit?.emoji ? ` ${unit.emoji}` : ''}
+
);
diff --git a/src/containers/ConvertCurrencyScreen.js b/src/containers/ConvertCurrencyScreen.js
new file mode 100644
index 0000000..8304a29
--- /dev/null
+++ b/src/containers/ConvertCurrencyScreen.js
@@ -0,0 +1,150 @@
+import React, { useEffect, useRef, useState } from 'react';
+import AsyncStorage from '@react-native-async-storage/async-storage';
+import UnitValue from 'src/components/UnitValue';
+import ListUnitItem from 'src/components/ListUnitItem';
+import Snackbar from 'react-native-snackbar';
+import { StyleSheet, View, FlatList } from 'react-native';
+import { Text, useTheme } from '@rneui/themed';
+import { convertCurrency, getEuropeanCentralBankRates } from 'src/utils/currencies';
+import { useTranslation } from 'react-i18next';
+
+
+const ConvertCurrencyScreen = ({ navigation }) => {
+
+ const defaultUnit = {iso: 'EUR', name: 'Euro', symbol: '€', emoji: '🇪🇺'};
+
+ const { t } = useTranslation();
+ const isInitialized = useRef(false);
+ const [isRefreshing, setIsRefreshing] = useState(false);
+ const [refUnit, setRefUnit] = useState(defaultUnit);
+ const [value, setValue] = useState(0);
+ const [fxRate, setFxRate] = useState([]);
+ const { theme } = useTheme();
+
+ const bgColor = theme.mode === 'light' ? theme.colors.disabled : theme.colors.background;
+
+ const keyExtractor = (item, index) => item + index;
+
+ const renderItem = ({ item }) => {
+ const isReferenceUnit = item.iso == refUnit.iso;
+ let unityValue = isReferenceUnit ? value : convertCurrency(refUnit, item, value);
+ if (isNaN(unityValue)) unityValue = '?';
+
+ return
+ }
+
+ const initFxRate = async () => {
+ const savedFxRate = await AsyncStorage.getItem(`unitstool_currency_fxRate`);
+ if (savedFxRate !== null) {
+ const objFxRate = JSON.parse(savedFxRate);
+ const now = new Date();
+ const today = `${now.getFullYear()}-${(now.getMonth()+1).toString().padStart(2, '0')}-${now.getDate()}`;
+ const isWeekend = now.getDay() === 0 || now.getDay() === 6;
+ // The BCE doesn't update the week-end, so check it
+ if (objFxRate.day === undefined || (!isWeekend && today !== objFxRate.day)) {
+ fetchFxRate();
+ } else {
+ setFxRate(JSON.parse(savedFxRate));
+ }
+ } else {
+ fetchFxRate();
+ }
+ }
+
+ const fetchFxRate = async () => {
+ setIsRefreshing(true);
+
+ let lastFxRate = await getEuropeanCentralBankRates();
+ if (lastFxRate.day !== undefined) {
+ saveFxRate(lastFxRate);
+ } else {
+ Snackbar.show({
+ text: t('failToFetchCurrency'),
+ duration: Snackbar.LENGTH_LONG,
+ action: {
+ text: 'OK',
+ textColor: theme.colors.success
+ },
+ });
+ }
+
+ setIsRefreshing(false);
+ }
+
+ const loadCurrencyFavorite = async () => {
+ try {
+ const value = await AsyncStorage.getItem(`unitstool_currency_favorite`);
+ if (value !== null && value.length > 0) {
+ setRefUnit(JSON.parse(value));
+ }
+ } catch(e) {
+ }
+ }
+
+ const saveCurrencyFavorite = async (value) => {
+ try {
+ const jsonStrValue = JSON.stringify(value);
+ await AsyncStorage.setItem(`unitstool_currency_favorite`, jsonStrValue);
+ setRefUnit(value);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ const saveFxRate = async (value) => {
+ try {
+ const jsonStrValue = JSON.stringify(value);
+ await AsyncStorage.setItem(`unitstool_currency_fxRate`, jsonStrValue);
+ setFxRate(value);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ useEffect(() => {
+ if (!isInitialized.current) {
+ initFxRate();
+ loadCurrencyFavorite();
+ }
+
+ return () => {
+ isInitialized.current = true;
+ };
+ }, []);
+
+ return (
+
+
+ {t('update')}: {fxRate.day} ({t('sourceECB')})
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ }
+});
+
+export default ConvertCurrencyScreen;
\ No newline at end of file
diff --git a/src/containers/RootNavigation.js b/src/containers/RootNavigation.js
index c66634c..7c98766 100644
--- a/src/containers/RootNavigation.js
+++ b/src/containers/RootNavigation.js
@@ -1,6 +1,8 @@
import React, { useEffect, useRef, useState } from 'react';
+import Icon from 'react-native-vector-icons/FontAwesome5';
import AsyncStorage from '@react-native-async-storage/async-storage';
import ConvertScreen from 'src/containers/ConvertScreen';
+import ConvertCurrencyScreen from 'src/containers/ConvertCurrencyScreen';
import MainMenu from 'src/components/MainMenu';
import HomeScreen from 'src/containers/HomeScreen';
import i18n from 'i18next';
@@ -12,6 +14,8 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { initReactI18next, useTranslation } from 'react-i18next';
import { NavigationContainer } from '@react-navigation/native';
import { useTheme, useThemeMode } from '@rneui/themed';
+import { Alert } from 'react-native';
+import { Button } from '@rneui/base';
import { version } from '../../package.json';
i18n
@@ -101,6 +105,16 @@ const RootNavigation = ({ value, setValue, unit }) => {
}
}
+ const displayCurrencyInfo = () => {
+ Alert.alert(
+ t('currencyInfoTitle'),
+ t('currencyInfoMessage'),
+ [
+ { text: "OK" }
+ ]
+ );
+ }
+
useEffect(() => {
if (!isInitialized.current) {
initLanguage();
@@ -134,8 +148,20 @@ const RootNavigation = ({ value, setValue, unit }) => {
return
- {props => }
+ options={{
+ title: t(conv.title),
+ headerStyle: {backgroundColor: theme.colors.primary },
+ headerTintColor: theme.colors.white,
+ headerRight: () => (
+ conv.category === 'currency'
+ ?
+ : null
+ )
+ }}
+ >
+ {props => conv.category === 'currency' ? : }
})
}
diff --git a/src/locales/en.json b/src/locales/en.json
index edfa95a..85faec2 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -3,6 +3,11 @@
"home": "Home",
"value": "Value",
"lightmode": "Light mode",
- "darkmode": "Dark mode"
+ "darkmode": "Dark mode",
+ "sourceECB": "from ECB",
+ "update": "Update",
+ "currencyInfoTitle": "Information",
+ "currencyInfoMessage": "The data come from the European Central Bank and are updated once a day, except the weekend.\n\nYou need internet to get the data.",
+ "failToFetchCurrency": "Failed to get ECB rates.\nCheck your internet connection"
}
}
\ No newline at end of file
diff --git a/src/locales/es.json b/src/locales/es.json
index 79ca22a..f955746 100644
--- a/src/locales/es.json
+++ b/src/locales/es.json
@@ -3,8 +3,14 @@
"home": "Hogar",
"value": "Valor",
"units": "unidades",
+ "currencies": "monedas",
"lightmode": "Modo de luz",
"darkmode": "Modo oscuro",
+ "sourceECB": "fuente ECB",
+ "update": "Actualización",
+ "currencyInfoTitle": "Información",
+ "currencyInfoMessage": "Los datos provienen del Banco Central Europeo y se actualizan una vez al día, excepto el fin de semana.\n\nNecesitas internet para obtener los datos.",
+ "failToFetchCurrency": "Error al obtener las tasas del BCE.\nComprueba tu conexión a Internet",
"Distance": "Distancia",
"Weight": "Peso",
@@ -13,6 +19,7 @@
"Surface": "Superficie",
"Volume": "Volumen",
"Chemistry": "Química",
+ "Currency": "Divisa",
"Millimeter": "Milímetro",
"Centimeter": "Centímetro",
@@ -53,6 +60,15 @@
"Cubic yard": "Yarda cúbica",
"Millimole per liter (glucose)": "Milimole por litro (glucosa)",
- "Milligram per deciliter (glucose)": "Miligramo por decilitro (glucosa)"
+ "Milligram per deciliter (glucose)": "Miligramo por decilitro (glucosa)",
+
+ "Euro": "Euro",
+ "Canadian dollar": "Dolar Canadiense",
+ "US dollar": "Dólar Estadounidense",
+ "Pound sterling": "Libra esterlina",
+ "Japanese yen": "Yen Japonés",
+ "Swiss franc": "Franco Suizo",
+ "Mexican peso": "Peso Mexicano",
+ "Australian dollar": "Dólar Australiano"
}
}
\ No newline at end of file
diff --git a/src/locales/fr.json b/src/locales/fr.json
index 7717903..3e12d7e 100644
--- a/src/locales/fr.json
+++ b/src/locales/fr.json
@@ -3,8 +3,14 @@
"home": "Accueil",
"value": "Valeur",
"units": "unités",
+ "currencies": "monnaies",
"lightmode": "Mode clair",
"darkmode": "Mode sombre",
+ "sourceECB": "source BCE",
+ "update": "Mise à jour",
+ "currencyInfoTitle": "Information",
+ "currencyInfoMessage": "Les données proviennent de la Banque centrale européenne et sont mises à jour une fois par jour, sauf le week-end.\n\nVous avez besoin d'Internet pour obtenir les données.",
+ "failToFetchCurrency": "Impossible d'obtenir les taux de la BCE.\nVérifiez votre connection internet",
"Distance": "Distance",
"Weight": "Poids",
@@ -13,6 +19,7 @@
"Surface": "Superficie",
"Volume": "Volume",
"Chemistry": "Chimie",
+ "Currency": "Devise",
"Millimeter": "Millimètre",
"Centimeter": "Centimètre",
@@ -53,6 +60,15 @@
"Cubic yard": "Yard cube",
"Millimole per liter (glucose)": "Millimole par litre (glucose)",
- "Milligram per deciliter (glucose)": "Milligramme par décilitre (glucose)"
+ "Milligram per deciliter (glucose)": "Milligramme par décilitre (glucose)",
+
+ "Euro": "Euro",
+ "Canadian dollar": "Dollar Canadien",
+ "US dollar": "Dollar US",
+ "Pound sterling": "Livre sterling",
+ "Japanese yen": "Yen Japonais",
+ "Swiss franc": "Franc Suisse",
+ "Mexican peso": "Peso Mexicain",
+ "Australian dollar": "Dollar Australien"
}
}
\ No newline at end of file
diff --git a/src/utils/conversion.js b/src/utils/conversion.js
index 4ef43a1..b98b8bc 100644
--- a/src/utils/conversion.js
+++ b/src/utils/conversion.js
@@ -46,14 +46,14 @@ export const getlowestfraction = (value) => {
return `${k === 1 ? h : h + "/" + k}`;
}
-const twoDecimals = (value) => {
+export const twoDecimals = (value) => {
let log10 = value ? Math.floor(Math.log10(value)) : 0;
let div = log10 < 0 ? Math.pow(10, 1 - log10) : 100;
return Math.round(value * div) / div;
}
-const formulaToValue = (formula, value, invertSign) => {
+export const formulaToValue = (formula, value, invertSign) => {
let result = Number.parseFloat(value);
const steps = formula.split(' ');
diff --git a/src/utils/conversion.json b/src/utils/conversion.json
index b7bf16d..99a6d52 100644
--- a/src/utils/conversion.json
+++ b/src/utils/conversion.json
@@ -227,5 +227,12 @@
"reverse_formula": "* 0.0555"
}
]
+ },
+ {
+ "category": "currency",
+ "title": "Currency",
+ "icon": "coins",
+ "reference": "EUR",
+ "units": []
}
]
diff --git a/src/utils/currencies.js b/src/utils/currencies.js
new file mode 100644
index 0000000..21f1a2e
--- /dev/null
+++ b/src/utils/currencies.js
@@ -0,0 +1,56 @@
+import axios from "axios";
+import { formulaToValue, twoDecimals } from "src/utils/conversion";
+
+const managedCurrency = {
+ 'EUR': {name: 'Euro', symbol: '€', emoji: '🇪🇺'},
+ 'CAD': {name: 'Canadian dollar', symbol: '$', emoji: '🇨🇦'},
+ 'USD': {name: 'US dollar', symbol: '$', emoji: '🇺🇲'},
+ 'GBP': {name: 'Pound sterling', symbol: '£', emoji: '🇬🇧'},
+ 'JPY': {name: 'Japanese yen', symbol: '¥', emoji: '🇯🇵'},
+ 'CHF': {name: 'Swiss franc', symbol: 'CH', emoji: '🇨🇭'},
+ 'MXN': {name: 'Mexican peso', symbol: '$', emoji: '🇲🇽'},
+ 'AUD': {name: 'Australian dollar', symbol: '$', emoji: '🇦🇺'},
+};
+
+export const currencyCount = Object.keys(managedCurrency).length;
+
+export const convertCurrency = (fxSrc, fxDest, value) => {
+ const srcIsReference = (fxSrc.iso === 'EUR');
+ // If the source unity is not the reference, convert it to simplify
+ if (!srcIsReference){
+ value = formulaToValue(`/ ${fxSrc.rate}`, value, false);
+ }
+ let newValue = formulaToValue(`* ${fxDest.rate}`, value, false);
+ // Round to 2 decimals
+ return twoDecimals(newValue);
+}
+
+export const getEuropeanCentralBankRates = async () => {
+ let fxRate = {};
+ try {
+ const result = await axios.get('https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml');
+ const data = result.data;
+ const timeRegex = /
/g;
+ const timeMatch = timeRegex.exec(data);
+ const currencyRegex = //g;
+ const currencyMatches = data.matchAll(currencyRegex);
+ // Init with euro because it's the reference of ECB
+ const currencies = [
+ {iso: 'EUR', name: 'Euro', symbol: '€', emoji: '🇪🇺', rate: 1}
+ ];
+ for (const currency of currencyMatches) {
+ const managed = managedCurrency[currency[1]];
+ if (managed !== undefined) {
+ managed['iso'] = currency[1];
+ managed['rate'] = currency[2];
+ currencies.push(managed);
+ }
+ }
+ fxRate['day'] = timeMatch[1];
+ //fxRate['rates'] = currencies.sort((a, b) => a.iso > b.iso);
+ fxRate['rates'] = currencies;
+ } catch (e) {
+ console.log('Fail to get ECB rates', e);
+ }
+ return fxRate;
+}