add an entry for “Historical fiat rate”

This commit is contained in:
Serhii 2023-04-28 15:15:45 +03:00
parent 82b513d1f8
commit 2b8ded327d
8 changed files with 162 additions and 8 deletions

View file

@ -5,7 +5,6 @@ import 'package:flutter/foundation.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/.secrets.g.dart' as secrets;
const _fiatApiClearNetAuthority = 'fiat-api.cakewallet.com'; const _fiatApiClearNetAuthority = 'fiat-api.cakewallet.com';
const _fiatApiOnionAuthority = 'n4z7bdcmwk2oyddxvzaap3x2peqcplh3pzdy7tpkk5ejz5n4mhfvoxqd.onion'; const _fiatApiOnionAuthority = 'n4z7bdcmwk2oyddxvzaap3x2peqcplh3pzdy7tpkk5ejz5n4mhfvoxqd.onion';
const _fiatApiPath = '/v2/rates'; const _fiatApiPath = '/v2/rates';
@ -19,7 +18,7 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
'interval_count': '1', 'interval_count': '1',
'base': crypto.toString(), 'base': crypto.toString(),
'quote': fiat.toString(), 'quote': fiat.toString(),
'key' : secrets.fiatApiKey, 'key': secrets.fiatApiKey,
}; };
double price = 0.0; double price = 0.0;
@ -51,9 +50,65 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
} }
} }
Future<double> _fetchHistoricalPrice(Map<String, dynamic> args) async {
final crypto = args['crypto'] as CryptoCurrency;
final fiat = args['fiat'] as FiatCurrency;
final torOnly = args['torOnly'] as bool;
final date = args['date'] as DateTime;
final intervalFromNow = DateTime.now().difference(date).inMinutes;
final Map<String, String> queryParams = {
'interval_count': '5',
'base': crypto.toString(),
'quote': fiat.toString(),
'key': secrets.fiatApiKey,
'interval_minutes': intervalFromNow.toString()
};
double price = 0.0;
try {
late final Uri uri;
if (torOnly) {
uri = Uri.http(_fiatApiOnionAuthority, _fiatApiPath, queryParams);
} else {
uri = Uri.https(_fiatApiClearNetAuthority, _fiatApiPath, queryParams);
}
final response = await get(uri);
if (response.statusCode != 200) {
return 0.0;
}
final data = json.decode(response.body) as Map<String, dynamic>;
final errors = data['errors'] as Map<String, dynamic>;
if (errors.isNotEmpty) {
return 0.0;
}
final results = data['results'] as Map<String, dynamic>;
if (results.isNotEmpty) {
price = results.values.first as double;
}
return price;
} catch (e) {
print(e.toString());
return 0.0;
}
}
Future<double> _fetchPriceAsync(CryptoCurrency crypto, FiatCurrency fiat, bool torOnly) async => Future<double> _fetchPriceAsync(CryptoCurrency crypto, FiatCurrency fiat, bool torOnly) async =>
compute(_fetchPrice, {'fiat': fiat, 'crypto': crypto, 'torOnly': torOnly}); compute(_fetchPrice, {'fiat': fiat, 'crypto': crypto, 'torOnly': torOnly});
Future<double> _fetchHistoricalAsync(
CryptoCurrency crypto, FiatCurrency fiat, bool torOnly, DateTime date) async =>
compute(
_fetchHistoricalPrice, {'fiat': fiat, 'crypto': crypto, 'torOnly': torOnly, 'date': date});
class FiatConversionService { class FiatConversionService {
static Future<double> fetchPrice({ static Future<double> fetchPrice({
required CryptoCurrency crypto, required CryptoCurrency crypto,
@ -61,4 +116,12 @@ class FiatConversionService {
required bool torOnly, required bool torOnly,
}) async => }) async =>
await _fetchPriceAsync(crypto, fiat, torOnly); await _fetchPriceAsync(crypto, fiat, torOnly);
static Future<double> fetchHistoricalPrice({
required CryptoCurrency crypto,
required FiatCurrency fiat,
required bool torOnly,
required DateTime date,
}) async =>
await _fetchHistoricalAsync(crypto, fiat, torOnly, date);
} }

View file

@ -45,4 +45,5 @@ class PreferencesKey {
static const lastSeenAppVersion = 'last_seen_app_version'; static const lastSeenAppVersion = 'last_seen_app_version';
static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard'; static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard';
static const isNewInstall = 'is_new_install'; static const isNewInstall = 'is_new_install';
static const showHistoricalFiatRateKey = 'show_historical_fiat_rate';
} }

View file

@ -56,6 +56,13 @@ class DisplaySettingsPage extends BasePage {
currency.fullName.toLowerCase().contains(searchText); currency.fullName.toLowerCase().contains(searchText);
}, },
), ),
SettingsSwitcherCell(
title: S.current.historical_fiat_rate,
value: _displaySettingsViewModel.showHistoricalFiatRate,
onValueChange: (_, bool value) {
_displaySettingsViewModel.setShowHistoricalFiatRate(value);
},
),
SettingsPickerCell<String>( SettingsPickerCell<String>(
title: S.current.settings_change_language, title: S.current.settings_change_language,
searchHintText: S.current.search_language, searchHintText: S.current.search_language,

View file

@ -11,6 +11,7 @@ import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_i
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/utils/date_formatter.dart'; import 'package:cake_wallet/utils/date_formatter.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -27,7 +28,9 @@ class TransactionDetailsPage extends BasePage {
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
// FIX-ME: Added `context` it was not used here before, maby bug ? // FIX-ME: Added `context` it was not used here before, maby bug ?
return SectionStandardList( return Observer(
builder: (_) {
return SectionStandardList(
context: context, context: context,
sectionCount: 1, sectionCount: 1,
itemCounter: (int _) => transactionDetailsViewModel.items.length, itemCounter: (int _) => transactionDetailsViewModel.items.length,
@ -63,6 +66,6 @@ class TransactionDetailsPage extends BasePage {
} }
return Container(); return Container();
}); });});
} }
} }

View file

@ -36,6 +36,7 @@ abstract class SettingsStoreBase with Store {
required bool initialAppSecure, required bool initialAppSecure,
required FiatApiMode initialFiatMode, required FiatApiMode initialFiatMode,
required bool initialAllowBiometricalAuthentication, required bool initialAllowBiometricalAuthentication,
required bool initialShowHistoricalFiatRate,
required ExchangeApiMode initialExchangeStatus, required ExchangeApiMode initialExchangeStatus,
required ThemeBase initialTheme, required ThemeBase initialTheme,
required int initialPinLength, required int initialPinLength,
@ -59,6 +60,7 @@ abstract class SettingsStoreBase with Store {
isAppSecure = initialAppSecure, isAppSecure = initialAppSecure,
fiatApiMode = initialFiatMode, fiatApiMode = initialFiatMode,
allowBiometricalAuthentication = initialAllowBiometricalAuthentication, allowBiometricalAuthentication = initialAllowBiometricalAuthentication,
showHistoricalFiatRate = initialShowHistoricalFiatRate,
shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard, shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard,
exchangeStatus = initialExchangeStatus, exchangeStatus = initialExchangeStatus,
currentTheme = initialTheme, currentTheme = initialTheme,
@ -150,6 +152,12 @@ abstract class SettingsStoreBase with Store {
PreferencesKey.allowBiometricalAuthenticationKey, PreferencesKey.allowBiometricalAuthenticationKey,
biometricalAuthentication)); biometricalAuthentication));
reaction(
(_) => showHistoricalFiatRate,
(bool historicalFiatRate) => sharedPreferences.setBool(
PreferencesKey.showHistoricalFiatRateKey,
historicalFiatRate));
reaction( reaction(
(_) => shouldShowMarketPlaceInDashboard, (_) => shouldShowMarketPlaceInDashboard,
(bool value) => (bool value) =>
@ -220,6 +228,9 @@ abstract class SettingsStoreBase with Store {
@observable @observable
bool allowBiometricalAuthentication; bool allowBiometricalAuthentication;
@observable
bool showHistoricalFiatRate;
@observable @observable
ExchangeApiMode exchangeStatus; ExchangeApiMode exchangeStatus;
@ -315,6 +326,9 @@ abstract class SettingsStoreBase with Store {
final allowBiometricalAuthentication = sharedPreferences final allowBiometricalAuthentication = sharedPreferences
.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? .getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
false; false;
final showHistoricalFiatRate = sharedPreferences
.getBool(PreferencesKey.showHistoricalFiatRateKey) ??
false;
final shouldShowMarketPlaceInDashboard = final shouldShowMarketPlaceInDashboard =
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? true; sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? true;
final exchangeStatus = ExchangeApiMode.deserialize( final exchangeStatus = ExchangeApiMode.deserialize(
@ -390,6 +404,7 @@ abstract class SettingsStoreBase with Store {
initialAppSecure: isAppSecure, initialAppSecure: isAppSecure,
initialFiatMode: currentFiatApiMode, initialFiatMode: currentFiatApiMode,
initialAllowBiometricalAuthentication: allowBiometricalAuthentication, initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
initialShowHistoricalFiatRate: showHistoricalFiatRate,
initialExchangeStatus: exchangeStatus, initialExchangeStatus: exchangeStatus,
initialTheme: savedTheme, initialTheme: savedTheme,
actionlistDisplayMode: actionListDisplayMode, actionlistDisplayMode: actionListDisplayMode,
@ -438,6 +453,9 @@ abstract class SettingsStoreBase with Store {
allowBiometricalAuthentication = sharedPreferences allowBiometricalAuthentication = sharedPreferences
.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? .getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
allowBiometricalAuthentication; allowBiometricalAuthentication;
showHistoricalFiatRate = sharedPreferences
.getBool(PreferencesKey.showHistoricalFiatRateKey) ??
showHistoricalFiatRate;
shouldShowMarketPlaceInDashboard = shouldShowMarketPlaceInDashboard =
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ??
shouldShowMarketPlaceInDashboard; shouldShowMarketPlaceInDashboard;

View file

@ -37,6 +37,9 @@ abstract class DisplaySettingsViewModelBase with Store {
@computed @computed
bool get disabledFiatApiMode => _settingsStore.fiatApiMode == FiatApiMode.disabled; bool get disabledFiatApiMode => _settingsStore.fiatApiMode == FiatApiMode.disabled;
@computed
bool get showHistoricalFiatRate => _settingsStore.showHistoricalFiatRate;
@action @action
void setBalanceDisplayMode(BalanceDisplayMode value) => _settingsStore.balanceDisplayMode = value; void setBalanceDisplayMode(BalanceDisplayMode value) => _settingsStore.balanceDisplayMode = value;
@ -66,4 +69,7 @@ abstract class DisplaySettingsViewModelBase with Store {
void setShouldShowMarketPlaceInDashbaord(bool value) { void setShouldShowMarketPlaceInDashbaord(bool value) {
_settingsStore.shouldShowMarketPlaceInDashboard = value; _settingsStore.shouldShowMarketPlaceInDashboard = value;
} }
@action
void setShowHistoricalFiatRate(bool value) => _settingsStore.showHistoricalFiatRate = value;
} }

View file

@ -1,3 +1,6 @@
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/monero_amount_format.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
@ -14,6 +17,7 @@ import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
part 'transaction_details_view_model.g.dart'; part 'transaction_details_view_model.g.dart';
@ -26,9 +30,16 @@ abstract class TransactionDetailsViewModelBase with Store {
required this.transactionDescriptionBox, required this.transactionDescriptionBox,
required this.wallet, required this.wallet,
required this.settingsStore}) required this.settingsStore})
: items = [], : items = ObservableList<TransactionDetailsListItem>(),
isRecipientAddressShown = false, isRecipientAddressShown = false,
showRecipientAddress = settingsStore.shouldSaveRecipientAddress { showRecipientAddress = settingsStore.shouldSaveRecipientAddress,
fiatRateListItem = StandartListItem(
title: settingsStore.showHistoricalFiatRate
? S.current.historical_fiat_rate
: S.current.fiat_rate,
value: settingsStore.showHistoricalFiatRate
? '${S.current.fetching.toLowerCase()} ...'
: transactionInfo.fiatAmount() + ' ${settingsStore.fiatCurrency}') {
final dateFormat = DateFormatter.withCurrentLocal(); final dateFormat = DateFormatter.withCurrentLocal();
final tx = transactionInfo; final tx = transactionInfo;
@ -51,6 +62,8 @@ abstract class TransactionDetailsViewModelBase with Store {
if (feeFormatted != null) if (feeFormatted != null)
StandartListItem( StandartListItem(
title: S.current.transaction_details_fee, value: feeFormatted), title: S.current.transaction_details_fee, value: feeFormatted),
if (settingsStore.fiatApiMode != FiatApiMode.disabled)
fiatRateListItem,
if (key?.isNotEmpty ?? false) if (key?.isNotEmpty ?? false)
StandartListItem(title: S.current.transaction_key, value: key!) StandartListItem(title: S.current.transaction_key, value: key!)
]; ];
@ -105,6 +118,8 @@ abstract class TransactionDetailsViewModelBase with Store {
StandartListItem( StandartListItem(
title: S.current.transaction_details_fee, title: S.current.transaction_details_fee,
value: tx.feeFormatted()!), value: tx.feeFormatted()!),
if (settingsStore.fiatApiMode != FiatApiMode.disabled)
fiatRateListItem,
]; ];
items.addAll(_items); items.addAll(_items);
@ -125,6 +140,8 @@ abstract class TransactionDetailsViewModelBase with Store {
if (tx.feeFormatted()?.isNotEmpty ?? false) if (tx.feeFormatted()?.isNotEmpty ?? false)
StandartListItem( StandartListItem(
title: S.current.transaction_details_fee, value: tx.feeFormatted()!), title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
if (settingsStore.fiatApiMode != FiatApiMode.disabled)
fiatRateListItem,
]); ]);
} }
@ -171,17 +188,54 @@ abstract class TransactionDetailsViewModelBase with Store {
transactionDescriptionBox.add(description); transactionDescriptionBox.add(description);
} }
})); }));
if (settingsStore.showHistoricalFiatRate) {
getHistoricalFiatRate();
}
} }
final TransactionInfo transactionInfo; final TransactionInfo transactionInfo;
final Box<TransactionDescription> transactionDescriptionBox; final Box<TransactionDescription> transactionDescriptionBox;
final SettingsStore settingsStore; final SettingsStore settingsStore;
final WalletBase wallet; final WalletBase wallet;
final StandartListItem fiatRateListItem;
final List<TransactionDetailsListItem> items; final ObservableList<TransactionDetailsListItem> items;
bool showRecipientAddress; bool showRecipientAddress;
bool isRecipientAddressShown; bool isRecipientAddressShown;
@action
Future<void> getHistoricalFiatRate() async {
final fiatRateItemIndex = items.indexWhere((element) => element == fiatRateListItem);
if (fiatRateItemIndex == -1) return;
final fiat = settingsStore.fiatCurrency;
final historicalFiatRate = await FiatConversionService.fetchHistoricalPrice(
crypto: wallet.currency,
fiat: fiat,
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly,
date: transactionInfo.date);
var formattedFiatAmount = 0.0;
switch (wallet.type) {
case WalletType.bitcoin:
case WalletType.litecoin:
formattedFiatAmount = bitcoinAmountToDouble(amount: transactionInfo.amount);
break;
case WalletType.monero:
case WalletType.haven:
formattedFiatAmount = moneroAmountToDouble(amount: transactionInfo.amount);
break;
default:
formattedFiatAmount;
}
final historicalFiatAmountFormatted = formattedFiatAmount * historicalFiatRate;
items[fiatRateItemIndex] = StandartListItem(
title: S.current.historical_fiat_rate,
value: historicalFiatAmountFormatted > 0.0
? historicalFiatAmountFormatted.toStringAsFixed(2) + ' ${fiat}'
: 'no historical data');
}
String _explorerUrl(WalletType type, String txId) { String _explorerUrl(WalletType type, String txId) {
switch (type) { switch (type) {
case WalletType.monero: case WalletType.monero:

View file

@ -708,5 +708,7 @@
"error_text_input_below_minimum_limit" : "Amount is less than the minimum", "error_text_input_below_minimum_limit" : "Amount is less than the minimum",
"error_text_input_above_maximum_limit" : "Amount is more than the maximum", "error_text_input_above_maximum_limit" : "Amount is more than the maximum",
"show_market_place" :"Show Marketplace", "show_market_place" :"Show Marketplace",
"prevent_screenshots": "Prevent screenshots and screen recording" "prevent_screenshots": "Prevent screenshots and screen recording",
"fiat_rate": "Fiat rate",
"historical_fiat_rate": "Historical fiat rate"
} }