mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 09:50:15 +01:00
ADD: Update Outdated Rate
This commit is contained in:
parent
c1dfe8906a
commit
303f9e356c
@ -9,10 +9,11 @@ const PREFERRED_CURRENCY_STORAGE_KEY = 'preferredCurrency';
|
||||
const EXCHANGE_RATES_STORAGE_KEY = 'currency';
|
||||
|
||||
let preferredFiatCurrency = FiatUnit.USD;
|
||||
let exchangeRates = {};
|
||||
let exchangeRates = { LAST_UPDATED_ERROR: false };
|
||||
let lastTimeUpdateExchangeRateWasCalled = 0;
|
||||
|
||||
const LAST_UPDATED = 'LAST_UPDATED';
|
||||
const LAST_UPDATED_ERROR = false;
|
||||
|
||||
/**
|
||||
* Saves to storage preferred currency, whole object
|
||||
@ -40,9 +41,10 @@ async function getPreferredCurrency() {
|
||||
async function _restoreSavedExchangeRatesFromStorage() {
|
||||
try {
|
||||
exchangeRates = JSON.parse(await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY));
|
||||
if (!exchangeRates) exchangeRates = {};
|
||||
exchangeRates.LAST_UPDATED_ERROR = false;
|
||||
if (!exchangeRates) exchangeRates = { LAST_UPDATED_ERROR: false };
|
||||
} catch (_) {
|
||||
exchangeRates = {};
|
||||
exchangeRates = { LAST_UPDATED_ERROR: false };
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,12 +90,19 @@ async function updateExchangeRate() {
|
||||
exchangeRates[LAST_UPDATED] = +new Date();
|
||||
exchangeRates['BTC_' + preferredFiatCurrency.endPointKey] = rate;
|
||||
await AsyncStorage.setItem(EXCHANGE_RATES_STORAGE_KEY, JSON.stringify(exchangeRates));
|
||||
exchangeRates[LAST_UPDATED_ERROR] = false;
|
||||
} catch (Err) {
|
||||
console.log('Error encountered when attempting to update exchange rate...');
|
||||
console.warn(Err.message);
|
||||
exchangeRates[LAST_UPDATED_ERROR] = true;
|
||||
throw Err;
|
||||
}
|
||||
}
|
||||
|
||||
function isRateOutdated() {
|
||||
return exchangeRates[LAST_UPDATED_ERROR] || new Date() - exchangeRates[LAST_UPDATED] >= 31 * 60 * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* this function reads storage and restores current preferred fiat currency & last saved exchange rate, then calls
|
||||
* updateExchangeRate() to update rates.
|
||||
@ -167,7 +176,7 @@ async function mostRecentFetchedRate() {
|
||||
currency: preferredFiatCurrency.endPointKey,
|
||||
});
|
||||
return {
|
||||
LastUpdated: currencyInformation[LAST_UPDATED],
|
||||
LastUpdated: isRateOutdated() ? exchangeRates[LAST_UPDATED] : currencyInformation[LAST_UPDATED],
|
||||
Rate: formatter.format(JSON.parse(currencyInformation)[`BTC_${preferredFiatCurrency.endPointKey}`]),
|
||||
};
|
||||
}
|
||||
@ -227,3 +236,4 @@ module.exports.PREFERRED_CURRENCY = PREFERRED_CURRENCY_STORAGE_KEY;
|
||||
module.exports.EXCHANGE_RATES = EXCHANGE_RATES_STORAGE_KEY;
|
||||
module.exports.LAST_UPDATED = LAST_UPDATED;
|
||||
module.exports.mostRecentFetchedRate = mostRecentFetchedRate;
|
||||
module.exports.isRateOutdated = isRateOutdated;
|
||||
|
@ -1,13 +1,16 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { Text } from 'react-native-elements';
|
||||
import { Image, Pressable, StyleSheet, TextInput, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { Badge, Icon, Text } from 'react-native-elements';
|
||||
import { Image, LayoutAnimation, Pressable, StyleSheet, TextInput, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { useTheme } from '@react-navigation/native';
|
||||
import confirm from '../helpers/confirm';
|
||||
import { BitcoinUnit } from '../models/bitcoinUnits';
|
||||
import loc, { formatBalanceWithoutSuffix, formatBalancePlain, removeTrailingZeros } from '../loc';
|
||||
import { BlueText } from '../BlueComponents';
|
||||
import dayjs from 'dayjs';
|
||||
const currency = require('../blue_modules/currency');
|
||||
dayjs.extend(require('dayjs/plugin/localizedFormat'));
|
||||
|
||||
class AmountInput extends Component {
|
||||
static propTypes = {
|
||||
@ -47,6 +50,22 @@ class AmountInput extends Component {
|
||||
AmountInput.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY] = sats;
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = { mostRecentFetchedRate: Date(), isRateOutdated: false, isRateBeingUpdated: false };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
currency
|
||||
.mostRecentFetchedRate()
|
||||
.then(mostRecentFetchedRate => {
|
||||
this.setState({ mostRecentFetchedRate });
|
||||
})
|
||||
.finally(() => {
|
||||
this.setState({ isRateOutdated: currency.isRateOutdated() });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* here we must recalculate old amont value (which was denominated in `previousUnit`) to new denomination `newUnit`
|
||||
* and fill this value in input box, so user can switch between, for example, 0.001 BTC <=> 100000 sats
|
||||
@ -168,6 +187,22 @@ class AmountInput extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
updateRate = () => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
this.setState({ isRateBeingUpdated: true }, async () => {
|
||||
try {
|
||||
await currency.updateExchangeRate();
|
||||
currency.mostRecentFetchedRate().then(mostRecentFetchedRate => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
this.setState({ mostRecentFetchedRate });
|
||||
});
|
||||
} finally {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
this.setState({ isRateBeingUpdated: false, isRateOutdated: currency.isRateOutdated() });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { colors, disabled, unit } = this.props;
|
||||
const amount = this.props.amount || 0;
|
||||
@ -205,63 +240,82 @@ class AmountInput extends Component {
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback disabled={this.props.pointerEvents === 'none'} onPress={() => this.textInput.focus()}>
|
||||
<View style={styles.root}>
|
||||
{!disabled && <View style={[styles.center, stylesHook.center]} />}
|
||||
<View style={styles.flex}>
|
||||
<View style={styles.container}>
|
||||
{unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && (
|
||||
<Text style={[styles.localCurrency, stylesHook.localCurrency]}>{currency.getCurrencySymbol() + ' '}</Text>
|
||||
)}
|
||||
{amount !== BitcoinUnit.MAX ? (
|
||||
<TextInput
|
||||
{...this.props}
|
||||
testID="BitcoinAmountInput"
|
||||
keyboardType="numeric"
|
||||
adjustsFontSizeToFit
|
||||
onChangeText={this.handleChangeText}
|
||||
onBlur={() => {
|
||||
if (this.props.onBlur) this.props.onBlur();
|
||||
}}
|
||||
onFocus={() => {
|
||||
if (this.props.onFocus) this.props.onFocus();
|
||||
}}
|
||||
placeholder="0"
|
||||
maxLength={this.maxLength()}
|
||||
ref={textInput => (this.textInput = textInput)}
|
||||
editable={!this.props.isLoading && !disabled}
|
||||
value={amount === BitcoinUnit.MAX ? loc.units.MAX : parseFloat(amount) >= 0 ? String(amount) : undefined}
|
||||
placeholderTextColor={disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2}
|
||||
style={[styles.input, stylesHook.input]}
|
||||
/>
|
||||
) : (
|
||||
<Pressable onPress={this.resetAmount}>
|
||||
<Text style={[styles.input, stylesHook.input]}>{BitcoinUnit.MAX}</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
{unit !== BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && (
|
||||
<Text style={[styles.cryptoCurrency, stylesHook.cryptoCurrency]}>{' ' + loc.units[unit]}</Text>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.secondaryRoot}>
|
||||
<Text style={styles.secondaryText}>
|
||||
{unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX
|
||||
? removeTrailingZeros(secondaryDisplayCurrency)
|
||||
: secondaryDisplayCurrency}
|
||||
{unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX ? ` ${loc.units[BitcoinUnit.BTC]}` : null}
|
||||
</Text>
|
||||
<>
|
||||
<View style={styles.root}>
|
||||
{!disabled && <View style={[styles.center, stylesHook.center]} />}
|
||||
<View style={styles.flex}>
|
||||
<View style={styles.container}>
|
||||
{unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && (
|
||||
<Text style={[styles.localCurrency, stylesHook.localCurrency]}>{currency.getCurrencySymbol() + ' '}</Text>
|
||||
)}
|
||||
{amount !== BitcoinUnit.MAX ? (
|
||||
<TextInput
|
||||
{...this.props}
|
||||
testID="BitcoinAmountInput"
|
||||
keyboardType="numeric"
|
||||
adjustsFontSizeToFit
|
||||
onChangeText={this.handleChangeText}
|
||||
onBlur={() => {
|
||||
if (this.props.onBlur) this.props.onBlur();
|
||||
}}
|
||||
onFocus={() => {
|
||||
if (this.props.onFocus) this.props.onFocus();
|
||||
}}
|
||||
placeholder="0"
|
||||
maxLength={this.maxLength()}
|
||||
ref={textInput => (this.textInput = textInput)}
|
||||
editable={!this.props.isLoading && !disabled}
|
||||
value={amount === BitcoinUnit.MAX ? loc.units.MAX : parseFloat(amount) >= 0 ? String(amount) : undefined}
|
||||
placeholderTextColor={disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2}
|
||||
style={[styles.input, stylesHook.input]}
|
||||
/>
|
||||
) : (
|
||||
<Pressable onPress={this.resetAmount}>
|
||||
<Text style={[styles.input, stylesHook.input]}>{BitcoinUnit.MAX}</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
{unit !== BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && (
|
||||
<Text style={[styles.cryptoCurrency, stylesHook.cryptoCurrency]}>{' ' + loc.units[unit]}</Text>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.secondaryRoot}>
|
||||
<Text style={styles.secondaryText}>
|
||||
{unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX
|
||||
? removeTrailingZeros(secondaryDisplayCurrency)
|
||||
: secondaryDisplayCurrency}
|
||||
{unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX ? ` ${loc.units[BitcoinUnit.BTC]}` : null}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
{!disabled && amount !== BitcoinUnit.MAX && (
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
testID="changeAmountUnitButton"
|
||||
style={styles.changeAmountUnit}
|
||||
onPress={this.changeAmountUnit}
|
||||
>
|
||||
<Image source={require('../img/round-compare-arrows-24-px.png')} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
{!disabled && amount !== BitcoinUnit.MAX && (
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
testID="changeAmountUnitButton"
|
||||
style={styles.changeAmountUnit}
|
||||
onPress={this.changeAmountUnit}
|
||||
>
|
||||
<Image source={require('../img/round-compare-arrows-24-px.png')} />
|
||||
</TouchableOpacity>
|
||||
{this.state.isRateOutdated && (
|
||||
<View style={styles.outdatedRateContainer}>
|
||||
<Badge status="warning" />
|
||||
<View style={styles.spacing8} />
|
||||
<BlueText>
|
||||
{loc.formatString(loc.send.outdated_rate, { date: dayjs(this.state.mostRecentFetchedRate.LastUpdated).format('LT') })}
|
||||
</BlueText>
|
||||
<View style={styles.spacing8} />
|
||||
<TouchableOpacity
|
||||
onPress={this.updateRate}
|
||||
disabled={this.state.isRateBeingUpdated}
|
||||
style={this.state.isRateBeingUpdated ? styles.disabledButton : styles.enabledButon}
|
||||
>
|
||||
<Icon name="sync" type="font-awesome-5" size={16} color={colors.buttonAlternativeTextColor} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
</TouchableWithoutFeedback>
|
||||
);
|
||||
}
|
||||
@ -278,6 +332,21 @@ const styles = StyleSheet.create({
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
spacing8: {
|
||||
width: 8,
|
||||
},
|
||||
disabledButton: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
enabledButton: {
|
||||
opacity: 1,
|
||||
},
|
||||
outdatedRateContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginVertical: 8,
|
||||
},
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignContent: 'space-between',
|
||||
|
@ -257,6 +257,7 @@
|
||||
"psbt_this_is_psbt": "This is a Partially Signed Bitcoin Transaction (PSBT). Please finish signing it with your hardware wallet.",
|
||||
"psbt_tx_export": "Export to file",
|
||||
"no_tx_signing_in_progress": "There is no transaction signing in progress.",
|
||||
"outdated_rate": "Rate was last updated at {date}",
|
||||
"psbt_tx_open": "Open Signed Transaction",
|
||||
"psbt_tx_scan": "Scan Signed Transaction",
|
||||
"qr_error_no_qrcode": "We were unable to find a QR Code in the selected image. Make sure the image contains only a QR Code and no additional content such as text, or buttons.",
|
||||
@ -290,6 +291,7 @@
|
||||
"biom_remove_decrypt": "All your wallets will be removed and your storage will be decrypted. Are you sure you want to proceed?",
|
||||
"currency": "Currency",
|
||||
"currency_source": "Prices are obtained from",
|
||||
"currency_fetch_error": "There was an error while obtaining the rate for the selected currency.",
|
||||
"default_desc": "When disabled, BlueWallet will immediately open the selected wallet at launch.",
|
||||
"default_info": "Default info",
|
||||
"default_title": "On Launch",
|
||||
|
@ -4,10 +4,11 @@ import { useTheme } from '@react-navigation/native';
|
||||
|
||||
import navigationStyle from '../../components/navigationStyle';
|
||||
import { SafeBlueArea, BlueListItem, BlueText, BlueCard, BlueSpacing10 } from '../../BlueComponents';
|
||||
import { FiatUnit, FiatUnitSource } from '../../models/fiatUnit';
|
||||
import { FiatUnit, FiatUnitSource, getFiatRate } from '../../models/fiatUnit';
|
||||
import loc from '../../loc';
|
||||
import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
import dayjs from 'dayjs';
|
||||
import alert from '../../components/Alert';
|
||||
dayjs.extend(require('dayjs/plugin/calendar'));
|
||||
const currency = require('../../blue_modules/currency');
|
||||
const data = Object.values(FiatUnit);
|
||||
@ -67,12 +68,19 @@ const Currency = () => {
|
||||
checkmark={selectedCurrency.endPointKey === item.endPointKey}
|
||||
onPress={async () => {
|
||||
setIsSavingNewPreferredCurrency(true);
|
||||
setSelectedCurrency(item);
|
||||
await currency.setPrefferedCurrency(item);
|
||||
await currency.init(true);
|
||||
setIsSavingNewPreferredCurrency(false);
|
||||
setPreferredFiatCurrency();
|
||||
fetchCurrency();
|
||||
try {
|
||||
await getFiatRate(item.endPointKey);
|
||||
await currency.setPrefferedCurrency(item);
|
||||
await currency.init(true);
|
||||
await fetchCurrency();
|
||||
setSelectedCurrency(item);
|
||||
setPreferredFiatCurrency();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
alert(loc.settings.currency_fetch_error);
|
||||
} finally {
|
||||
setIsSavingNewPreferredCurrency(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user