What changed, and why it matters
This commit adds a new setup screen and settings for choosing how the wallet fetches the Monero-to-fiat exchange rate. Users can now pick Tor-only (the default), clearnet (direct internet), or disabled. The clearnet option fetches the rate directly from Kraken's API without routing through Tor, which the app labels as 'not private.' This is a privacy-relevant change, not a code-execution vulnerability, because it lets users intentionally trade some privacy for reliability. The commit also renames many 'connection' labels to 'LWS' (light-wallet server) to avoid confusion with the new fiat API connection.
Reviewers should verify that the clearnet path cannot be silently forced on users, that the default remains Tor-only after upgrades, and that the Kraken endpoint and JSON parsing are robust against malformed responses. Consider adding certificate pinning or a known-endpoints allowlist if not already present elsewhere, and ensure the fiat API mode setting is included in any privacy documentation.
Security signals we found
New clearnet network path added for fiat price fetching
Default remains Tor-only, preserving prior privacy posture
User-facing label explicitly warns 'Clearnet-Only (not private)'
Direct HTTPS request to api.kraken.com without additional pinning or proxying
SharedPreferences key added for fiatApiMode
Onboarding flow changed: connection setup now routes to fiat API setup before wallet creation
Evidence from the diff
The patch introduces FiatApiMode (torOnly/clearnet/disabled) and persists it in SharedPreferences. In lib/models/fiat_rate_model.dart, _requestPairRate now branches: clearnet mode uses dart:io HttpClient directly to api.kraken.com, while torOnly continues using makeSocksHttpRequest through the Tor proxy. The default remains torOnly. A new FiatApiSetupScreen is inserted into the onboarding flow after LWS setup, and a FiatApiSettingsForm is added to the settings dialog. The clearnet path bypasses Tor and performs a direct HTTPS GET, which exposes the user’s IP to Kraken and any network observer. No certificate pinning, host validation hardening, or request signing changes are visible in the diff. The change is user-configurable and clearly labeled as non-private.
Changed components
lib/models/fiat_rate_model.dartlib/screens/fiat_api_setup_screen.dartlib/widgets/fiat_api_settings_form.dartlib/screens/connection_setup.dartlib/screens/settings.dartlib/services/shared_preferences_service.dartlib/l10n localization filesInspect captured patch +574 / −224
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index b6052a5..64fcf49 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -18,16 +18,23 @@
"restoreWarningTitle": "Restore Warning",
"restoreWarningDescription": "Are you sure? The server that you connect to will be able to see your past and future Monero transaction history.",
"restoreWarningContinueButton": "Continue",
- "connectionSetupTitle": "Connection Setup",
- "connectionSetupDescription": "Let's setup a connection with your Monero light-wallet server (LWS).",
- "connectionSetupAddressHint": "e.g. 192.168.1.1:18090 or example.com:18090",
- "connectionSetupProxyPortLabel": "HTTP Proxy Port (optional)",
- "connectionSetupProxyPortHint": "e.g. 4444 for I2P",
- "connectionSetupUseTorLabel": "Use Tor",
- "connectionSetupUseSslLabel": "Use SSL",
- "connectionSetupTestConnectionButton": "Test Connection",
- "connectionSetupStartingTor": "Starting Tor...",
- "connectionSetupContinueButton": "Continue",
+ "lwsSetupTitle": "LWS Setup",
+ "lwsSetupDescription": "Enter the address of your Monero light-wallet server (LWS).",
+ "lwsSetupAddressHint": "e.g. 192.168.1.1:18090 or example.com:18090",
+ "lwsSetupProxyPortLabel": "HTTP Proxy Port (optional)",
+ "lwsSetupProxyPortHint": "e.g. 4444 for I2P",
+ "lwsSetupUseTorLabel": "Use Tor",
+ "lwsSetupUseSslLabel": "Use SSL",
+ "lwsSetupTestConnectionButton": "Test Connection",
+ "lwsSetupStartingTor": "Starting Tor...",
+ "lwsSetupContinueButton": "Continue",
+ "fiatApiSetupTitle": "Fiat Display Setup",
+ "fiatApiSetupDescription": "Optional reference price for your XMR balance.",
+ "fiatApiSettingsModeLabel": "Mode",
+ "fiatApiSettingsModeTorOnly": "Tor-Only",
+ "fiatApiSettingsModeClearnet": "Clearnet-Only (not private)",
+ "fiatApiSettingsModeDisabled": "Disabled",
+ "fiatApiSettingsDisplayCurrencyLabel": "Display Currency",
"createWalletTitle": "Create Wallet",
"createWalletDescription": "Do you already have a Monero wallet seed, or do you need to make a new one?",
"createWalletRestoreExistingButton": "Restore Existing",
@@ -48,7 +55,6 @@
"restoreWalletInvalidMnemonic": "Invalid seed.",
"navigationBarWallet": "Wallet",
"navigationBarSettings": "Settings",
- "navigationBarKeys": "Keys",
"unlockButton": "Unlock",
"unlockReason": "Unlock wallet",
"unlockUnableToAuthError": "Unable to authenticate.",
@@ -99,7 +105,7 @@
"settingsThemeLight": "Light",
"settingsThemeDark": "Dark",
"settingsLanguageLabel": "Language",
- "settingsDisplayCurrencyLabel": "Display Currency",
+ "settingsFiatApiSettingsLabel": "Fiat API Settings",
"settingsLwsViewKeysLabel": "LWS View Keys",
"settingsLwsViewKeysButton": "View",
"settingsSecretKeysLabel": "Secret Restore Keys",
@@ -152,10 +158,8 @@
"addressBookContactName": "Contact Name",
"addressBookUpdate": "Update",
"addressBookSave": "Save",
- "sendSelectFromAddressBook": "Select from Address Book",
"sendSelectedContact": "Selected contact",
"sendClearSelectedContact": "Clear selected contact",
- "sendEditAddress": "Edit Address",
"sendPriorityLow": "Low",
"sendPriorityNormal": "Normal",
"sendPriorityHigh": "High",
@@ -179,17 +183,17 @@
"torSettingsUseOrbotLabelIos": "Use Orbot",
"torSettingsSaveButton": "Save",
"torSettingsTestConnectionButton": "Test Connection",
- "settingsConnectionSettingsLabel": "Connection Settings",
+ "settingsLwsSettingsLabel": "LWS Settings",
"settingsTorSettingsLabel": "Tor Settings",
- "connectionSetupUsingInternalTor": "Using internal Tor",
- "connectionSetupUsingExternalTor": "Using external Tor proxy at {address}",
- "@connectionSetupUsingExternalTor": {
+ "lwsSetupUsingInternalTor": "Using internal Tor",
+ "lwsSetupUsingExternalTor": "Using external Tor proxy at {address}",
+ "@lwsSetupUsingExternalTor": {
"placeholders": {
"address": {
"type": "String"
}
}
},
- "connectionSetupTorDisabledError": "Tor is disabled. Please go back and enable it.",
- "connectionSetupInvalidQrCode": "Invalid connection address."
+ "lwsSetupTorDisabledError": "Tor is disabled. Please go back and enable it.",
+ "lwsSetupInvalidQrCode": "Invalid connection address."
}
diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart
index cd29f94..44c0b68 100644
--- a/lib/l10n/app_localizations.dart
+++ b/lib/l10n/app_localizations.dart
@@ -201,65 +201,107 @@ abstract class AppLocalizations {
/// **'Continue'**
String get restoreWarningContinueButton;
- /// No description provided for @connectionSetupTitle.
+ /// No description provided for @lwsSetupTitle.
///
/// In en, this message translates to:
- /// **'Connection Setup'**
- String get connectionSetupTitle;
+ /// **'LWS Setup'**
+ String get lwsSetupTitle;
- /// No description provided for @connectionSetupDescription.
+ /// No description provided for @lwsSetupDescription.
///
/// In en, this message translates to:
- /// **'Let\'s setup a connection with your Monero light-wallet server (LWS).'**
- String get connectionSetupDescription;
+ /// **'Enter the address of your Monero light-wallet server (LWS).'**
+ String get lwsSetupDescription;
- /// No description provided for @connectionSetupAddressHint.
+ /// No description provided for @lwsSetupAddressHint.
///
/// In en, this message translates to:
/// **'e.g. 192.168.1.1:18090 or example.com:18090'**
- String get connectionSetupAddressHint;
+ String get lwsSetupAddressHint;
- /// No description provided for @connectionSetupProxyPortLabel.
+ /// No description provided for @lwsSetupProxyPortLabel.
///
/// In en, this message translates to:
/// **'HTTP Proxy Port (optional)'**
- String get connectionSetupProxyPortLabel;
+ String get lwsSetupProxyPortLabel;
- /// No description provided for @connectionSetupProxyPortHint.
+ /// No description provided for @lwsSetupProxyPortHint.
///
/// In en, this message translates to:
/// **'e.g. 4444 for I2P'**
- String get connectionSetupProxyPortHint;
+ String get lwsSetupProxyPortHint;
- /// No description provided for @connectionSetupUseTorLabel.
+ /// No description provided for @lwsSetupUseTorLabel.
///
/// In en, this message translates to:
/// **'Use Tor'**
- String get connectionSetupUseTorLabel;
+ String get lwsSetupUseTorLabel;
- /// No description provided for @connectionSetupUseSslLabel.
+ /// No description provided for @lwsSetupUseSslLabel.
///
/// In en, this message translates to:
/// **'Use SSL'**
- String get connectionSetupUseSslLabel;
+ String get lwsSetupUseSslLabel;
- /// No description provided for @connectionSetupTestConnectionButton.
+ /// No description provided for @lwsSetupTestConnectionButton.
///
/// In en, this message translates to:
/// **'Test Connection'**
- String get connectionSetupTestConnectionButton;
+ String get lwsSetupTestConnectionButton;
- /// No description provided for @connectionSetupStartingTor.
+ /// No description provided for @lwsSetupStartingTor.
///
/// In en, this message translates to:
/// **'Starting Tor...'**
- String get connectionSetupStartingTor;
+ String get lwsSetupStartingTor;
- /// No description provided for @connectionSetupContinueButton.
+ /// No description provided for @lwsSetupContinueButton.
///
/// In en, this message translates to:
/// **'Continue'**
- String get connectionSetupContinueButton;
+ String get lwsSetupContinueButton;
+
+ /// No description provided for @fiatApiSetupTitle.
+ ///
+ /// In en, this message translates to:
+ /// **'Fiat Display Setup'**
+ String get fiatApiSetupTitle;
+
+ /// No description provided for @fiatApiSetupDescription.
+ ///
+ /// In en, this message translates to:
+ /// **'Optional reference price for your XMR balance.'**
+ String get fiatApiSetupDescription;
+
+ /// No description provided for @fiatApiSettingsModeLabel.
+ ///
+ /// In en, this message translates to:
+ /// **'Mode'**
+ String get fiatApiSettingsModeLabel;
+
+ /// No description provided for @fiatApiSettingsModeTorOnly.
+ ///
+ /// In en, this message translates to:
+ /// **'Tor-Only'**
+ String get fiatApiSettingsModeTorOnly;
+
+ /// No description provided for @fiatApiSettingsModeClearnet.
+ ///
+ /// In en, this message translates to:
+ /// **'Clearnet-Only (not private)'**
+ String get fiatApiSettingsModeClearnet;
+
+ /// No description provided for @fiatApiSettingsModeDisabled.
+ ///
+ /// In en, this message translates to:
+ /// **'Disabled'**
+ String get fiatApiSettingsModeDisabled;
+
+ /// No description provided for @fiatApiSettingsDisplayCurrencyLabel.
+ ///
+ /// In en, this message translates to:
+ /// **'Display Currency'**
+ String get fiatApiSettingsDisplayCurrencyLabel;
/// No description provided for @createWalletTitle.
///
@@ -381,12 +423,6 @@ abstract class AppLocalizations {
/// **'Settings'**
String get navigationBarSettings;
- /// No description provided for @navigationBarKeys.
- ///
- /// In en, this message translates to:
- /// **'Keys'**
- String get navigationBarKeys;
-
/// No description provided for @unlockButton.
///
/// In en, this message translates to:
@@ -687,11 +723,11 @@ abstract class AppLocalizations {
/// **'Language'**
String get settingsLanguageLabel;
- /// No description provided for @settingsDisplayCurrencyLabel.
+ /// No description provided for @settingsFiatApiSettingsLabel.
///
/// In en, this message translates to:
- /// **'Display Currency'**
- String get settingsDisplayCurrencyLabel;
+ /// **'Fiat API Settings'**
+ String get settingsFiatApiSettingsLabel;
/// No description provided for @settingsLwsViewKeysLabel.
///
@@ -963,12 +999,6 @@ abstract class AppLocalizations {
/// **'Save'**
String get addressBookSave;
- /// No description provided for @sendSelectFromAddressBook.
- ///
- /// In en, this message translates to:
- /// **'Select from Address Book'**
- String get sendSelectFromAddressBook;
-
/// No description provided for @sendSelectedContact.
///
/// In en, this message translates to:
@@ -981,12 +1011,6 @@ abstract class AppLocalizations {
/// **'Clear selected contact'**
String get sendClearSelectedContact;
- /// No description provided for @sendEditAddress.
- ///
- /// In en, this message translates to:
- /// **'Edit Address'**
- String get sendEditAddress;
-
/// No description provided for @sendPriorityLow.
///
/// In en, this message translates to:
@@ -1125,11 +1149,11 @@ abstract class AppLocalizations {
/// **'Test Connection'**
String get torSettingsTestConnectionButton;
- /// No description provided for @settingsConnectionSettingsLabel.
+ /// No description provided for @settingsLwsSettingsLabel.
///
/// In en, this message translates to:
- /// **'Connection Settings'**
- String get settingsConnectionSettingsLabel;
+ /// **'LWS Settings'**
+ String get settingsLwsSettingsLabel;
/// No description provided for @settingsTorSettingsLabel.
///
@@ -1137,29 +1161,29 @@ abstract class AppLocalizations {
/// **'Tor Settings'**
String get settingsTorSettingsLabel;
- /// No description provided for @connectionSetupUsingInternalTor.
+ /// No description provided for @lwsSetupUsingInternalTor.
///
/// In en, this message translates to:
/// **'Using internal Tor'**
- String get connectionSetupUsingInternalTor;
+ String get lwsSetupUsingInternalTor;
- /// No description provided for @connectionSetupUsingExternalTor.
+ /// No description provided for @lwsSetupUsingExternalTor.
///
/// In en, this message translates to:
/// **'Using external Tor proxy at {address}'**
- String connectionSetupUsingExternalTor(String address);
+ String lwsSetupUsingExternalTor(String address);
- /// No description provided for @connectionSetupTorDisabledError.
+ /// No description provided for @lwsSetupTorDisabledError.
///
/// In en, this message translates to:
/// **'Tor is disabled. Please go back and enable it.'**
- String get connectionSetupTorDisabledError;
+ String get lwsSetupTorDisabledError;
- /// No description provided for @connectionSetupInvalidQrCode.
+ /// No description provided for @lwsSetupInvalidQrCode.
///
/// In en, this message translates to:
/// **'Invalid connection address.'**
- String get connectionSetupInvalidQrCode;
+ String get lwsSetupInvalidQrCode;
}
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart
index 078de7b..050acda 100644
--- a/lib/l10n/app_localizations_en.dart
+++ b/lib/l10n/app_localizations_en.dart
@@ -65,35 +65,55 @@ class AppLocalizationsEn extends AppLocalizations {
String get restoreWarningContinueButton => 'Continue';
@override
- String get connectionSetupTitle => 'Connection Setup';
+ String get lwsSetupTitle => 'LWS Setup';
@override
- String get connectionSetupDescription =>
- 'Let\'s setup a connection with your Monero light-wallet server (LWS).';
+ String get lwsSetupDescription => 'Enter the address of your Monero light-wallet server (LWS).';
@override
- String get connectionSetupAddressHint => 'e.g. 192.168.1.1:18090 or example.com:18090';
+ String get lwsSetupAddressHint => 'e.g. 192.168.1.1:18090 or example.com:18090';
@override
- String get connectionSetupProxyPortLabel => 'HTTP Proxy Port (optional)';
+ String get lwsSetupProxyPortLabel => 'HTTP Proxy Port (optional)';
@override
- String get connectionSetupProxyPortHint => 'e.g. 4444 for I2P';
+ String get lwsSetupProxyPortHint => 'e.g. 4444 for I2P';
@override
- String get connectionSetupUseTorLabel => 'Use Tor';
+ String get lwsSetupUseTorLabel => 'Use Tor';
@override
- String get connectionSetupUseSslLabel => 'Use SSL';
+ String get lwsSetupUseSslLabel => 'Use SSL';
@override
- String get connectionSetupTestConnectionButton => 'Test Connection';
+ String get lwsSetupTestConnectionButton => 'Test Connection';
@override
- String get connectionSetupStartingTor => 'Starting Tor...';
+ String get lwsSetupStartingTor => 'Starting Tor...';
@override
- String get connectionSetupContinueButton => 'Continue';
+ String get lwsSetupContinueButton => 'Continue';
+
+ @override
+ String get fiatApiSetupTitle => 'Fiat Display Setup';
+
+ @override
+ String get fiatApiSetupDescription => 'Optional reference price for your XMR balance.';
+
+ @override
+ String get fiatApiSettingsModeLabel => 'Mode';
+
+ @override
+ String get fiatApiSettingsModeTorOnly => 'Tor-Only';
+
+ @override
+ String get fiatApiSettingsModeClearnet => 'Clearnet-Only (not private)';
+
+ @override
+ String get fiatApiSettingsModeDisabled => 'Disabled';
+
+ @override
+ String get fiatApiSettingsDisplayCurrencyLabel => 'Display Currency';
@override
String get createWalletTitle => 'Create Wallet';
@@ -159,9 +179,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get navigationBarSettings => 'Settings';
- @override
- String get navigationBarKeys => 'Keys';
-
@override
String get unlockButton => 'Unlock';
@@ -320,7 +337,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get settingsLanguageLabel => 'Language';
@override
- String get settingsDisplayCurrencyLabel => 'Display Currency';
+ String get settingsFiatApiSettingsLabel => 'Fiat API Settings';
@override
String get settingsLwsViewKeysLabel => 'LWS View Keys';
@@ -463,18 +480,12 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get addressBookSave => 'Save';
- @override
- String get sendSelectFromAddressBook => 'Select from Address Book';
-
@override
String get sendSelectedContact => 'Selected contact';
@override
String get sendClearSelectedContact => 'Clear selected contact';
- @override
- String get sendEditAddress => 'Edit Address';
-
@override
String get sendPriorityLow => 'Low';
@@ -546,22 +557,22 @@ class AppLocalizationsEn extends AppLocalizations {
String get torSettingsTestConnectionButton => 'Test Connection';
@override
- String get settingsConnectionSettingsLabel => 'Connection Settings';
+ String get settingsLwsSettingsLabel => 'LWS Settings';
@override
String get settingsTorSettingsLabel => 'Tor Settings';
@override
- String get connectionSetupUsingInternalTor => 'Using internal Tor';
+ String get lwsSetupUsingInternalTor => 'Using internal Tor';
@override
- String connectionSetupUsingExternalTor(String address) {
+ String lwsSetupUsingExternalTor(String address) {
return 'Using external Tor proxy at $address';
}
@override
- String get connectionSetupTorDisabledError => 'Tor is disabled. Please go back and enable it.';
+ String get lwsSetupTorDisabledError => 'Tor is disabled. Please go back and enable it.';
@override
- String get connectionSetupInvalidQrCode => 'Invalid connection address.';
+ String get lwsSetupInvalidQrCode => 'Invalid connection address.';
}
diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart
index 41ff7c0..2fa6549 100644
--- a/lib/l10n/app_localizations_pt.dart
+++ b/lib/l10n/app_localizations_pt.dart
@@ -65,35 +65,55 @@ class AppLocalizationsPt extends AppLocalizations {
String get restoreWarningContinueButton => 'Continuar';
@override
- String get connectionSetupTitle => 'Configuração da Conexão';
+ String get lwsSetupTitle => 'Configuração do LWS';
@override
- String get connectionSetupDescription =>
- 'Vamos configurar uma conexão com seu servidor light-wallet de Monero (LWS).';
+ String get lwsSetupDescription => 'Informe o endereço do seu servidor light-wallet Monero (LWS).';
@override
- String get connectionSetupAddressHint => 'ex: 192.168.1.1:18090 ou exemplo.com:18090';
+ String get lwsSetupAddressHint => 'ex: 192.168.1.1:18090 ou exemplo.com:18090';
@override
- String get connectionSetupProxyPortLabel => 'Porta do Proxy HTTP (opcional)';
+ String get lwsSetupProxyPortLabel => 'Porta do Proxy HTTP (opcional)';
@override
- String get connectionSetupProxyPortHint => 'ex: 4444 para I2P';
+ String get lwsSetupProxyPortHint => 'ex: 4444 para I2P';
@override
- String get connectionSetupUseTorLabel => 'Usar Tor';
+ String get lwsSetupUseTorLabel => 'Usar Tor';
@override
- String get connectionSetupUseSslLabel => 'Usar SSL';
+ String get lwsSetupUseSslLabel => 'Usar SSL';
@override
- String get connectionSetupTestConnectionButton => 'Testar Conexão';
+ String get lwsSetupTestConnectionButton => 'Testar Conexão';
@override
- String get connectionSetupStartingTor => 'Iniciando Tor...';
+ String get lwsSetupStartingTor => 'Iniciando Tor...';
@override
- String get connectionSetupContinueButton => 'Continuar';
+ String get lwsSetupContinueButton => 'Continuar';
+
+ @override
+ String get fiatApiSetupTitle => 'Exibição de Saldo em Fiat';
+
+ @override
+ String get fiatApiSetupDescription => 'Preço de referência opcional para seu saldo em XMR.';
+
+ @override
+ String get fiatApiSettingsModeLabel => 'Modo';
+
+ @override
+ String get fiatApiSettingsModeTorOnly => 'Somente Tor';
+
+ @override
+ String get fiatApiSettingsModeClearnet => 'Somente Clearnet (não privado)';
+
+ @override
+ String get fiatApiSettingsModeDisabled => 'Desativado';
+
+ @override
+ String get fiatApiSettingsDisplayCurrencyLabel => 'Moeda de Exibição';
@override
String get createWalletTitle => 'Criar Carteira';
@@ -159,9 +179,6 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get navigationBarSettings => 'Configurações';
- @override
- String get navigationBarKeys => 'Chaves';
-
@override
String get unlockButton => 'Desbloquear';
@@ -320,7 +337,7 @@ class AppLocalizationsPt extends AppLocalizations {
String get settingsLanguageLabel => 'Idioma';
@override
- String get settingsDisplayCurrencyLabel => 'Moeda Local';
+ String get settingsFiatApiSettingsLabel => 'Configs. da API de Moeda Local';
@override
String get settingsLwsViewKeysLabel => 'Chaves de Visualização do LWS';
@@ -463,18 +480,12 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get addressBookSave => 'Salvar';
- @override
- String get sendSelectFromAddressBook => 'Selecionar da Lista de Contatos';
-
@override
String get sendSelectedContact => 'Contato selecionado';
@override
String get sendClearSelectedContact => 'Limpar contato selecionado';
- @override
- String get sendEditAddress => 'Editar Endereço';
-
@override
String get sendPriorityLow => 'Baixa';
@@ -546,23 +557,22 @@ class AppLocalizationsPt extends AppLocalizations {
String get torSettingsTestConnectionButton => 'Testar Conexão';
@override
- String get settingsConnectionSettingsLabel => 'Configurações da Conexão';
+ String get settingsLwsSettingsLabel => 'Configurações do LWS';
@override
String get settingsTorSettingsLabel => 'Configurações do Tor';
@override
- String get connectionSetupUsingInternalTor => 'Usando Tor interno';
+ String get lwsSetupUsingInternalTor => 'Usando Tor interno';
@override
- String connectionSetupUsingExternalTor(String address) {
+ String lwsSetupUsingExternalTor(String address) {
return 'Usando proxy Tor externo em $address';
}
@override
- String get connectionSetupTorDisabledError =>
- 'O Tor está desativado. Por favor, volte e ative-o.';
+ String get lwsSetupTorDisabledError => 'O Tor está desativado. Por favor, volte e ative-o.';
@override
- String get connectionSetupInvalidQrCode => 'Endereço de conexão inválido.';
+ String get lwsSetupInvalidQrCode => 'Endereço de conexão inválido.';
}
diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb
index 2529197..ec10ff9 100644
--- a/lib/l10n/app_pt.arb
+++ b/lib/l10n/app_pt.arb
@@ -18,17 +18,24 @@
"restoreWarningTitle": "Aviso de Restauração",
"restoreWarningDescription": "Você tem certeza? O servidor ao qual você se conectar poderá ver seu histórico de transações Monero passadas e futuras.",
"restoreWarningContinueButton": "Continuar",
- "connectionSetupTitle": "Configuração da Conexão",
- "connectionSetupDescription": "Vamos configurar uma conexão com seu servidor light-wallet de Monero (LWS).",
- "connectionSetupAddressHint": "ex: 192.168.1.1:18090 ou exemplo.com:18090",
- "connectionSetupProxyPortLabel": "Porta do Proxy HTTP (opcional)",
- "connectionSetupProxyPortHint": "ex: 4444 para I2P",
- "connectionSetupUseTorLabel": "Usar Tor",
- "connectionSetupUseSslLabel": "Usar SSL",
- "connectionSetupTestConnectionButton": "Testar Conexão",
- "connectionSetupStartingTor": "Iniciando Tor...",
- "connectionSetupContinueButton": "Continuar",
- "createWalletTitle": "Criar Carteira",
+ "lwsSetupTitle": "Configuração do LWS",
+ "lwsSetupDescription": "Informe o endereço do seu servidor light-wallet Monero (LWS).",
+ "lwsSetupAddressHint": "ex: 192.168.1.1:18090 ou exemplo.com:18090",
+ "lwsSetupProxyPortLabel": "Porta do Proxy HTTP (opcional)",
+ "lwsSetupProxyPortHint": "ex: 4444 para I2P",
+ "lwsSetupUseTorLabel": "Usar Tor",
+ "lwsSetupUseSslLabel": "Usar SSL",
+ "lwsSetupTestConnectionButton": "Testar Conexão",
+ "lwsSetupStartingTor": "Iniciando Tor...",
+ "lwsSetupContinueButton": "Continuar",
+ "fiatApiSetupTitle": "Exibição de Saldo em Fiat",
+ "fiatApiSetupDescription": "Preço de referência opcional para seu saldo em XMR.",
+ "fiatApiSettingsModeLabel": "Modo",
+ "fiatApiSettingsModeTorOnly": "Somente Tor",
+ "fiatApiSettingsModeClearnet": "Somente Clearnet (não privado)",
+ "fiatApiSettingsModeDisabled": "Desativado",
+ "fiatApiSettingsDisplayCurrencyLabel": "Moeda de Exibição",
+ "createWalletTitle": "Criar Carteira",
"createWalletDescription": "Você já possui uma semente de carteira Monero ou precisa criar uma nova?",
"createWalletRestoreExistingButton": "Restaurar Existente",
"createWalletCreateNewButton": "Criar Nova",
@@ -48,7 +55,6 @@
"restoreWalletInvalidMnemonic": "Semente inválida.",
"navigationBarWallet": "Carteira",
"navigationBarSettings": "Configurações",
- "navigationBarKeys": "Chaves",
"unlockButton": "Desbloquear",
"unlockReason": "Desbloquear carteira",
"unlockUnableToAuthError": "Não foi possível autenticar.",
@@ -99,7 +105,7 @@
"settingsThemeLight": "Claro",
"settingsThemeDark": "Escuro",
"settingsLanguageLabel": "Idioma",
- "settingsDisplayCurrencyLabel": "Moeda Local",
+ "settingsFiatApiSettingsLabel": "Configs. da API de Moeda Local",
"settingsLwsViewKeysLabel": "Chaves de Visualização do LWS",
"settingsLwsViewKeysButton": "Ver",
"settingsSecretKeysLabel": "Chaves Privadas de Restauração",
@@ -152,10 +158,8 @@
"addressBookContactName": "Nome do Contato",
"addressBookUpdate": "Atualizar",
"addressBookSave": "Salvar",
- "sendSelectFromAddressBook": "Selecionar da Lista de Contatos",
"sendSelectedContact": "Contato selecionado",
"sendClearSelectedContact": "Limpar contato selecionado",
- "sendEditAddress": "Editar Endereço",
"sendPriorityLow": "Baixa",
"sendPriorityNormal": "Normal",
"sendPriorityHigh": "Alta",
@@ -179,17 +183,17 @@
"torSettingsUseOrbotLabelIos": "Usar Orbot",
"torSettingsSaveButton": "Salvar",
"torSettingsTestConnectionButton": "Testar Conexão",
- "settingsConnectionSettingsLabel": "Configurações da Conexão",
+ "settingsLwsSettingsLabel": "Configurações do LWS",
"settingsTorSettingsLabel": "Configurações do Tor",
- "connectionSetupUsingInternalTor": "Usando Tor interno",
- "connectionSetupUsingExternalTor": "Usando proxy Tor externo em {address}",
- "@connectionSetupUsingExternalTor": {
+ "lwsSetupUsingInternalTor": "Usando Tor interno",
+ "lwsSetupUsingExternalTor": "Usando proxy Tor externo em {address}",
+ "@lwsSetupUsingExternalTor": {
"placeholders": {
"address": {
"type": "String"
}
}
},
- "connectionSetupTorDisabledError": "O Tor está desativado. Por favor, volte e ative-o.",
- "connectionSetupInvalidQrCode": "Endereço de conexão inválido."
+ "lwsSetupTorDisabledError": "O Tor está desativado. Por favor, volte e ative-o.",
+ "lwsSetupInvalidQrCode": "Endereço de conexão inválido."
}
diff --git a/lib/main.dart b/lib/main.dart
index bdf051b..dfc8748 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -22,6 +22,7 @@ import 'package:skylight_wallet/l10n/app_localizations.dart';
import 'package:skylight_wallet/screens/settings.dart';
import 'package:skylight_wallet/models/wallet_model.dart';
import 'package:skylight_wallet/screens/connection_setup.dart';
+import 'package:skylight_wallet/screens/fiat_api_setup_screen.dart';
import 'package:skylight_wallet/screens/generate_seed.dart';
import 'package:skylight_wallet/screens/receive.dart';
import 'package:skylight_wallet/screens/send.dart';
@@ -181,6 +182,7 @@ class MyApp extends StatelessWidget {
'/tor_info': (context) => TorInfoScreen(),
'/tor_settings': (context) => TorSettingsScreen(),
'/connection_setup': (context) => ConnectionSetupScreen(),
+ '/fiat_api_setup': (context) => FiatApiSetupScreen(),
'/create_wallet_password': (context) => CreateWalletPasswordScreen(),
'/create_wallet': (context) => CreateWalletScreen(),
'/generate_seed': (context) => GenerateSeedScreen(),
diff --git a/lib/models/fiat_rate_model.dart b/lib/models/fiat_rate_model.dart
index 8898ce8..fcc963a 100644
--- a/lib/models/fiat_rate_model.dart
+++ b/lib/models/fiat_rate_model.dart
@@ -1,4 +1,7 @@
import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
import 'package:flutter/material.dart';
import 'package:skylight_wallet/consts.dart';
@@ -7,12 +10,30 @@ import 'package:skylight_wallet/services/shared_preferences_service.dart';
import 'package:skylight_wallet/util/logging.dart';
import 'package:skylight_wallet/util/socks_http.dart';
+enum FiatApiMode { torOnly, clearnet, disabled }
+
class FiatRateModel with ChangeNotifier {
+ static Future<FiatApiMode> loadFiatApiMode() async {
+ final s = await SharedPreferencesService.get<String>(SharedPreferencesKeys.fiatApiMode);
+ if (s == null) {
+ return FiatApiMode.torOnly;
+ }
+ for (final m in FiatApiMode.values) {
+ if (m.name == s) return m;
+ }
+ return FiatApiMode.torOnly;
+ }
+
+ static Future<void> saveFiatApiMode(FiatApiMode mode) async {
+ await SharedPreferencesService.set<String>(SharedPreferencesKeys.fiatApiMode, mode.name);
+ }
+
double? _rate;
bool _isLoading = false;
bool _hasFailed = false;
bool _isDisabled = false;
String _fiatCode = 'USD';
+ FiatApiMode _fiatApiMode = FiatApiMode.torOnly;
Timer? _rateFetchTimer;
double? get rate => _rate;
@@ -22,28 +43,20 @@ class FiatRateModel with ChangeNotifier {
String get fiatCode => _fiatCode;
void _startRateFetchTimer() {
- if (TorSettingsService.sharedInstance.torMode == TorMode.disabled) {
+ if (_fiatApiMode == FiatApiMode.disabled) {
_isDisabled = true;
- log(LogLevel.info, 'Tor is disabled. Not starting rate fetch timer.');
+ log(LogLevel.info, 'Fiat API is disabled. Not starting rate fetch timer.');
return;
} else {
_isDisabled = false;
}
- if (_rateFetchTimer != null) {
- _rateFetchTimer?.cancel();
- }
+ _rateFetchTimer?.cancel();
_loadRate();
- _rateFetchTimer = Timer.periodic(Duration(minutes: 10), (timer) {
- if (TorSettingsService.sharedInstance.torMode != TorMode.disabled) {
- _loadRate();
- } else {
- log(LogLevel.info, 'Tor is disabled. Stopping rate fetch timer.');
- timer.cancel();
- _rateFetchTimer = null;
- }
+ _rateFetchTimer = Timer.periodic(Duration(minutes: 10), (_) {
+ _loadRate();
});
}
@@ -51,28 +64,52 @@ class FiatRateModel with ChangeNotifier {
_fiatCode =
await SharedPreferencesService.get<String>(SharedPreferencesKeys.fiatCurrency) ?? 'USD';
_rate = await SharedPreferencesService.get<double>(SharedPreferencesKeys.fiatRate);
+ _fiatApiMode = await FiatRateModel.loadFiatApiMode();
}
Future<void> _persist(double rate) async {
await SharedPreferencesService.set<double>(SharedPreferencesKeys.fiatRate, rate);
}
- Future<double> _requestPairRate(String pair) async {
+ Future<double> _requestPairRateClearnet(String pair) async {
+ final url = 'https://api.kraken.com/0/public/Ticker?pair=$pair';
+ log(LogLevel.info, 'Fetching rate from fiat api (clearnet): $url');
+
+ final client = HttpClient();
+ try {
+ final request = await client.getUrl(Uri.parse(url));
+ final response = await request.close().timeout(Duration(seconds: 20));
+ final body = await response.transform(utf8.decoder).join();
+ if (response.statusCode != 200) {
+ throw Exception('Status code: ${response.statusCode}');
+ }
+ final jsonBody = jsonDecode(body) as Map<String, dynamic>;
+ final rate = jsonBody['result']?[pair]?['o'];
+ if (rate is! String) {
+ throw Exception('Could not find rate for $pair');
+ }
+ return double.parse(rate);
+ } finally {
+ client.close(force: true);
+ }
+ }
+
+ Future<double> _requestPairRateTor(String pair) async {
final url = 'https://api.kraken.com/0/public/Ticker?pair=$pair';
final proxyInfo = await TorSettingsService.sharedInstance.getProxy();
- log(LogLevel.info, 'Fetching rate from fiat api:');
+ log(LogLevel.info, 'Fetching rate from fiat api (Tor):');
log(LogLevel.info, ' url: $url');
log(LogLevel.info, ' proxyInfo: $proxyInfo');
if (proxyInfo == null) {
- throw Exception('Not fetching rate from fiat API because Tor is disabled');
+ throw Exception('Not fetching rate from fiat API because no Tor proxy is available');
}
try {
final response = await makeSocksHttpRequest('GET', url, proxyInfo);
if (response.statusCode == 200) {
- final rate = response.jsonBody['result']?[pair]?['o'];
+ final rate = response.jsonBody?['result']?[pair]?['o'];
if (rate is! String) {
throw Exception('Could not find rate for $pair');
}
@@ -86,7 +123,29 @@ class FiatRateModel with ChangeNotifier {
}
}
+ Future<double> _requestPairRate(String pair) async {
+ if (_fiatApiMode == FiatApiMode.clearnet) {
+ try {
+ return await _requestPairRateClearnet(pair);
+ } catch (error) {
+ log(LogLevel.error, 'Failed to get fiat rate. ${error.toString()}');
+ throw Exception('Failed to get fiat rate. ${error.toString()}');
+ }
+ }
+ return _requestPairRateTor(pair);
+ }
+
Future<void> _loadRate() async {
+ _fiatApiMode = await FiatRateModel.loadFiatApiMode();
+ if (_fiatApiMode == FiatApiMode.disabled) {
+ _rateFetchTimer?.cancel();
+ _rateFetchTimer = null;
+ _isDisabled = true;
+ notifyListeners();
+ return;
+ }
+ _isDisabled = false;
+
_fiatCode =
await SharedPreferencesService.get<String>(SharedPreferencesKeys.fiatCurrency) ?? 'USD';
@@ -117,6 +176,8 @@ class FiatRateModel with ChangeNotifier {
}
Future<void> startService() async {
+ _rateFetchTimer?.cancel();
+ _rateFetchTimer = null;
await _loadPersisted();
_startRateFetchTimer();
}
diff --git a/lib/screens/connection_setup.dart b/lib/screens/connection_setup.dart
index 8902064..2b066fa 100644
--- a/lib/screens/connection_setup.dart
+++ b/lib/screens/connection_setup.dart
@@ -1,4 +1,3 @@
-import 'dart:io';
import 'package:flutter/material.dart';
import 'package:skylight_wallet/l10n/app_localizations.dart';
@@ -12,12 +11,7 @@ class ConnectionSetupScreen extends StatelessWidget {
final i18n = AppLocalizations.of(context)!;
void onSaved() {
- // On desktop platforms, navigate to password screen first
- if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
- Navigator.pushNamed(context, '/create_wallet_password');
- } else {
- Navigator.pushNamed(context, '/create_wallet');
- }
+ Navigator.pushNamed(context, '/fiat_api_setup');
}
return Scaffold(
@@ -35,18 +29,18 @@ class ConnectionSetupScreen extends StatelessWidget {
spacing: 10,
children: [
Text(
- i18n.connectionSetupTitle,
+ i18n.lwsSetupTitle,
style: Theme.of(context).textTheme.headlineMedium,
),
Text(
- i18n.connectionSetupDescription,
+ i18n.lwsSetupDescription,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge,
),
],
),
ConnectionSettingsForm(
- saveButtonLabel: i18n.connectionSetupContinueButton,
+ saveButtonLabel: i18n.lwsSetupContinueButton,
onSaved: onSaved,
),
],
diff --git a/lib/screens/fiat_api_setup_screen.dart b/lib/screens/fiat_api_setup_screen.dart
new file mode 100644
index 0000000..2cc3ba4
--- /dev/null
+++ b/lib/screens/fiat_api_setup_screen.dart
@@ -0,0 +1,127 @@
+import 'dart:io';
+
+import 'package:flutter/material.dart';
+
+import 'package:skylight_wallet/consts.dart';
+import 'package:skylight_wallet/l10n/app_localizations.dart';
+import 'package:skylight_wallet/models/fiat_rate_model.dart';
+import 'package:skylight_wallet/services/shared_preferences_service.dart';
+
+class FiatApiSetupScreen extends StatefulWidget {
+ const FiatApiSetupScreen({super.key});
+
+ @override
+ State<FiatApiSetupScreen> createState() => _FiatApiSetupScreenState();
+}
+
+class _FiatApiSetupScreenState extends State<FiatApiSetupScreen> {
+ FiatApiMode _fiatMode = FiatApiMode.torOnly;
+ String _fiatCurrency = 'USD';
+
+ Future<void> _onContinue() async {
+ await FiatRateModel.saveFiatApiMode(_fiatMode);
+ await SharedPreferencesService.set<String>(SharedPreferencesKeys.fiatCurrency, _fiatCurrency);
+ await SharedPreferencesService.remove(SharedPreferencesKeys.fiatRate);
+
+ if (!mounted) return;
+ if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
+ Navigator.pushNamed(context, '/create_wallet_password');
+ } else {
+ Navigator.pushNamed(context, '/create_wallet');
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final i18n = AppLocalizations.of(context)!;
+ final theme = Theme.of(context);
+
+ return Scaffold(
+ appBar: AppBar(title: Text('Skylight Monero Wallet')),
+ body: Center(
+ child: Container(
+ constraints: BoxConstraints(maxWidth: 500),
+ child: Padding(
+ padding: EdgeInsets.all(20),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ spacing: 20,
+ children: [
+ Column(
+ spacing: 10,
+ children: [
+ Text(
+ i18n.fiatApiSetupTitle,
+ style: theme.textTheme.headlineMedium,
+ ),
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16),
+ child: Text(
+ i18n.fiatApiSetupDescription,
+ textAlign: TextAlign.center,
+ style: theme.textTheme.bodyLarge,
+ ),
+ ),
+ ],
+ ),
+ Column(
+ spacing: 12,
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ DropdownButtonFormField<FiatApiMode>(
+ decoration: InputDecoration(
+ labelText: i18n.fiatApiSettingsModeLabel,
+ border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
+ ),
+ value: _fiatMode,
+ items: [
+ DropdownMenuItem(
+ value: FiatApiMode.torOnly,
+ child: Text(i18n.fiatApiSettingsModeTorOnly),
+ ),
+ DropdownMenuItem(
+ value: FiatApiMode.clearnet,
+ child: Text(i18n.fiatApiSettingsModeClearnet),
+ ),
+ DropdownMenuItem(
+ value: FiatApiMode.disabled,
+ child: Text(i18n.fiatApiSettingsModeDisabled),
+ ),
+ ],
+ onChanged: (v) {
+ if (v != null) setState(() => _fiatMode = v);
+ },
+ ),
+ if (_fiatMode != FiatApiMode.disabled)
+ DropdownButtonFormField<String>(
+ decoration: InputDecoration(
+ labelText: i18n.fiatApiSettingsDisplayCurrencyLabel,
+ border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
+ ),
+ value: _fiatCurrency,
+ items: supportedFiatCurrencies
+ .map((c) => DropdownMenuItem<String>(value: c, child: Text(c)))
+ .toList(),
+ onChanged: (v) {
+ if (v != null) setState(() => _fiatCurrency = v);
+ },
+ ),
+ ],
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ FilledButton(
+ onPressed: _onContinue,
+ child: Text(i18n.lwsSetupContinueButton),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart
index 245c349..2f1d228 100644
--- a/lib/screens/settings.dart
+++ b/lib/screens/settings.dart
@@ -6,10 +6,10 @@ import 'package:skylight_wallet/util/logging.dart';
import 'package:provider/provider.dart';
import 'package:local_auth/local_auth.dart';
-import 'package:skylight_wallet/consts.dart';
import 'package:skylight_wallet/l10n/app_localizations.dart';
import 'package:skylight_wallet/models/fiat_rate_model.dart';
import 'package:skylight_wallet/widgets/connection_settings_form.dart';
+import 'package:skylight_wallet/widgets/fiat_api_settings_form.dart';
import 'package:skylight_wallet/widgets/tor_settings_form.dart';
import 'package:skylight_wallet/models/language_model.dart';
import 'package:skylight_wallet/models/theme_model.dart';
@@ -28,7 +28,6 @@ class SettingsScreen extends StatefulWidget {
class _SettingsScreenState extends State<SettingsScreen> {
var _newTxNotificationsEnabled = false;
- var _fiatCurrency = 'USD';
var _appLockEnabled = false;
var _verboseLoggingEnabled = false;
String _appVersion = '';
@@ -54,9 +53,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
await SharedPreferencesService.get<bool>(SharedPreferencesKeys.notificationsEnabled) ??
false;
- final fiatCurrency =
- await SharedPreferencesService.get<String>(SharedPreferencesKeys.fiatCurrency) ?? 'USD';
-
final appLockEnabled =
await SharedPreferencesService.get<bool>(SharedPreferencesKeys.appLockEnabled) ?? false;
@@ -66,7 +62,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
setState(() {
_newTxNotificationsEnabled = newTxNotificationsEnabled;
- _fiatCurrency = fiatCurrency;
_appLockEnabled = appLockEnabled;
_verboseLoggingEnabled = verboseLoggingEnabled;
});
@@ -204,22 +199,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
);
}
- void _setFiatCurrency(String? value) async {
- if (value == null) return;
-
- setState(() {
- _fiatCurrency = value;
- });
-
- await SharedPreferencesService.set<String>(SharedPreferencesKeys.fiatCurrency, value);
- await SharedPreferencesService.remove(SharedPreferencesKeys.fiatRate);
-
- if (mounted) {
- final fiatRate = Provider.of<FiatRateModel>(context, listen: false);
- await fiatRate.startService();
- }
- }
-
void _showDeleteWalletDialog() {
final i18n = AppLocalizations.of(context)!;
@@ -380,6 +359,31 @@ class _SettingsScreenState extends State<SettingsScreen> {
);
}
+ void _showFiatApiSettingsDialog() {
+ final i18n = AppLocalizations.of(context)!;
+ final screenWidth = MediaQuery.of(context).size.width;
+ final dialogWidth = screenWidth.clamp(0.0, 500.0);
+
+ showDialog(
+ context: context,
+ builder: (dialogContext) => AlertDialog(
+ constraints: BoxConstraints.tightFor(width: dialogWidth),
+ insetPadding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0),
+ title: Text(i18n.settingsFiatApiSettingsLabel),
+ content: FiatApiSettingsForm(
+ saveButtonLabel: i18n.torSettingsSaveButton,
+ onSaved: () async {
+ final fiatRate = Provider.of<FiatRateModel>(context, listen: false);
+ await fiatRate.startService();
+ if (dialogContext.mounted) {
+ Navigator.of(dialogContext).pop();
+ }
+ },
+ ),
+ ),
+ );
+ }
+
void _showConnectionSettingsDialog() {
final i18n = AppLocalizations.of(context)!;
final screenWidth = MediaQuery.of(context).size.width;
@@ -400,7 +404,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
builder: (context) => AlertDialog(
constraints: BoxConstraints.tightFor(width: dialogWidth),
insetPadding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0),
- title: Text(i18n.settingsConnectionSettingsLabel),
+ title: Text(i18n.settingsLwsSettingsLabel),
content: ConnectionSettingsForm(
saveButtonLabel: i18n.torSettingsSaveButton,
onSaved: onSaved,
@@ -458,19 +462,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
],
),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text(i18n.settingsDisplayCurrencyLabel, style: TextStyle(fontSize: 18)),
- DropdownButton<String>(
- value: _fiatCurrency,
- onChanged: _setFiatCurrency,
- items: supportedFiatCurrencies.map((fiatCode) {
- return DropdownMenuItem<String>(value: fiatCode, child: Text(fiatCode));
- }).toList(),
- ),
- ],
- ),
if (Platform.isAndroid || Platform.isIOS)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -522,7 +513,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- Text(i18n.settingsConnectionSettingsLabel, style: TextStyle(fontSize: 18)),
+ Text(i18n.settingsLwsSettingsLabel, style: TextStyle(fontSize: 18)),
TextButton.icon(
onPressed: _showConnectionSettingsDialog,
icon: Icon(Icons.dns),
@@ -541,6 +532,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
],
),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(i18n.settingsFiatApiSettingsLabel, style: TextStyle(fontSize: 18)),
+ TextButton.icon(
+ onPressed: _showFiatApiSettingsDialog,
+ icon: Icon(Icons.currency_exchange),
+ label: Text(i18n.settingsLwsViewKeysButton),
+ ),
+ ],
+ ),
Container(margin: EdgeInsetsGeometry.symmetric(vertical: 10), child: Divider()),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
diff --git a/lib/services/shared_preferences_service.dart b/lib/services/shared_preferences_service.dart
index 7241f13..9794581 100644
--- a/lib/services/shared_preferences_service.dart
+++ b/lib/services/shared_preferences_service.dart
@@ -3,6 +3,7 @@ import 'package:shared_preferences/shared_preferences.dart';
class SharedPreferencesKeys {
static const String language = 'language';
static const String fiatCurrency = 'fiatCurrency';
+ static const String fiatApiMode = 'fiatApiMode';
static const String fiatRate = 'fiatRate';
static const String theme = 'theme';
static const String notificationsEnabled = 'notificationsEnabled';
diff --git a/lib/widgets/connection_settings_form.dart b/lib/widgets/connection_settings_form.dart
index 2fd6a83..11d0710 100644
--- a/lib/widgets/connection_settings_form.dart
+++ b/lib/widgets/connection_settings_form.dart
@@ -26,12 +26,14 @@ class ConnectionSettingsForm extends StatefulWidget {
final String saveButtonLabel;
final VoidCallback onSaved;
final bool isInDialog;
+ final Future<void> Function()? onBeforeSave;
const ConnectionSettingsForm({
super.key,
required this.saveButtonLabel,
required this.onSaved,
this.isInDialog = false,
+ this.onBeforeSave,
});
@override
@@ -107,12 +109,12 @@ class _ConnectionSettingsFormState extends State<ConnectionSettingsForm> {
if (mounted) {
if (widget.isInDialog) {
setState(() {
- _errorMessage = i18n.connectionSetupInvalidQrCode;
+ _errorMessage = i18n.lwsSetupInvalidQrCode;
});
} else {
ScaffoldMessenger.of(
context,
- ).showSnackBar(SnackBar(content: Text(i18n.connectionSetupInvalidQrCode)));
+ ).showSnackBar(SnackBar(content: Text(i18n.lwsSetupInvalidQrCode)));
}
}
}
@@ -235,7 +237,7 @@ class _ConnectionSettingsFormState extends State<ConnectionSettingsForm> {
// show error toast
ScaffoldMessenger.of(
context,
- ).showSnackBar(SnackBar(content: Text(i18n.connectionSetupTorDisabledError)));
+ ).showSnackBar(SnackBar(content: Text(i18n.lwsSetupTorDisabledError)));
return;
}
@@ -292,6 +294,7 @@ class _ConnectionSettingsFormState extends State<ConnectionSettingsForm> {
);
await wallet.persistCurrentConnection();
+ await widget.onBeforeSave?.call();
widget.onSaved();
}
@@ -311,7 +314,7 @@ class _ConnectionSettingsFormState extends State<ConnectionSettingsForm> {
onChanged: _onAddressChange,
decoration: InputDecoration(
labelText: i18n.address,
- hintText: i18n.connectionSetupAddressHint,
+ hintText: i18n.lwsSetupAddressHint,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)),
suffixIcon: Row(
mainAxisSize: MainAxisSize.min,
@@ -338,22 +341,22 @@ class _ConnectionSettingsFormState extends State<ConnectionSettingsForm> {
onChanged: _onProxyPortChange,
enabled: !_useTor,
decoration: InputDecoration(
- labelText: i18n.connectionSetupProxyPortLabel,
- hintText: i18n.connectionSetupProxyPortHint,
+ labelText: i18n.lwsSetupProxyPortLabel,
+ hintText: i18n.lwsSetupProxyPortHint,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)),
),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly],
),
CheckboxListTile(
- title: Text(i18n.connectionSetupUseTorLabel),
+ title: Text(i18n.lwsSetupUseTorLabel),
value: _useTor,
onChanged: _useSsl || torMode == TorMode.disabled ? null : _setUseTor,
controlAffinity: ListTileControlAffinity.leading,
contentPadding: EdgeInsets.zero,
),
CheckboxListTile(
- title: Text(i18n.connectionSetupUseSslLabel),
+ title: Text(i18n.lwsSetupUseSslLabel),
value: _useSsl,
onChanged: !_useTor ? _setUseSsl : null,
controlAffinity: ListTileControlAffinity.leading,
@@ -363,8 +366,8 @@ class _ConnectionSettingsFormState extends State<ConnectionSettingsForm> {
Center(
child: Text(
torMode == TorMode.builtIn
- ? i18n.connectionSetupUsingInternalTor
- : i18n.connectionSetupUsingExternalTor(
+ ? i18n.lwsSetupUsingInternalTor
+ : i18n.lwsSetupUsingExternalTor(
'127.0.0.1:${TorSettingsService.sharedInstance.socksPort}',
),
style: TextStyle(color: Colors.purple, fontStyle: FontStyle.italic),
@@ -382,12 +385,12 @@ class _ConnectionSettingsFormState extends State<ConnectionSettingsForm> {
children: [
SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)),
SizedBox(width: 8),
- Text(i18n.connectionSetupStartingTor),
+ Text(i18n.lwsSetupStartingTor),
],
)
else
TextButton.icon(
- label: Text(i18n.connectionSetupTestConnectionButton),
+ label: Text(i18n.lwsSetupTestConnectionButton),
onPressed: () => _testConnection(),
icon: !_connectionTestIsLoading
? Icon(Icons.network_check)
diff --git a/lib/widgets/fiat_api_settings_form.dart b/lib/widgets/fiat_api_settings_form.dart
new file mode 100644
index 0000000..8911451
--- /dev/null
+++ b/lib/widgets/fiat_api_settings_form.dart
@@ -0,0 +1,106 @@
+import 'package:flutter/material.dart';
+
+import 'package:skylight_wallet/consts.dart';
+import 'package:skylight_wallet/l10n/app_localizations.dart';
+import 'package:skylight_wallet/models/fiat_rate_model.dart';
+import 'package:skylight_wallet/services/shared_preferences_service.dart';
+
+class FiatApiSettingsForm extends StatefulWidget {
+ final String saveButtonLabel;
+ final Future<void> Function() onSaved;
+
+ const FiatApiSettingsForm({super.key, required this.saveButtonLabel, required this.onSaved});
+
+ @override
+ State<FiatApiSettingsForm> createState() => _FiatApiSettingsFormState();
+}
+
+class _FiatApiSettingsFormState extends State<FiatApiSettingsForm> {
+ FiatApiMode _mode = FiatApiMode.torOnly;
+ String _currency = 'USD';
+ var _loaded = false;
+
+ @override
+ void initState() {
+ super.initState();
+ _load();
+ }
+
+ Future<void> _load() async {
+ final mode = await FiatRateModel.loadFiatApiMode();
+ final cur =
+ await SharedPreferencesService.get<String>(SharedPreferencesKeys.fiatCurrency) ?? 'USD';
+ if (mounted) {
+ setState(() {
+ _mode = mode;
+ _currency = cur;
+ _loaded = true;
+ });
+ }
+ }
+
+ Future<void> _save() async {
+ await FiatRateModel.saveFiatApiMode(_mode);
+ await SharedPreferencesService.set<String>(SharedPreferencesKeys.fiatCurrency, _currency);
+ await SharedPreferencesService.remove(SharedPreferencesKeys.fiatRate);
+ await widget.onSaved();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ if (!_loaded) {
+ return SizedBox(
+ width: 280,
+ height: 120,
+ child: Center(child: CircularProgressIndicator()),
+ );
+ }
+ final i18n = AppLocalizations.of(context)!;
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ spacing: 12,
+ children: [
+ DropdownButtonFormField<FiatApiMode>(
+ decoration: InputDecoration(
+ labelText: i18n.fiatApiSettingsModeLabel,
+ border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
+ ),
+ value: _mode,
+ items: [
+ DropdownMenuItem(value: FiatApiMode.torOnly, child: Text(i18n.fiatApiSettingsModeTorOnly)),
+ DropdownMenuItem(
+ value: FiatApiMode.clearnet,
+ child: Text(i18n.fiatApiSettingsModeClearnet),
+ ),
+ DropdownMenuItem(
+ value: FiatApiMode.disabled,
+ child: Text(i18n.fiatApiSettingsModeDisabled),
+ ),
+ ],
+ onChanged: (v) {
+ if (v != null) setState(() => _mode = v);
+ },
+ ),
+ if (_mode != FiatApiMode.disabled)
+ DropdownButtonFormField<String>(
+ decoration: InputDecoration(
+ labelText: i18n.fiatApiSettingsDisplayCurrencyLabel,
+ border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
+ ),
+ value: _currency,
+ items: supportedFiatCurrencies
+ .map((c) => DropdownMenuItem<String>(value: c, child: Text(c)))
+ .toList(),
+ onChanged: (v) {
+ if (v != null) setState(() => _currency = v);
+ },
+ ),
+ Align(
+ alignment: Alignment.centerRight,
+ child: FilledButton(onPressed: _save, child: Text(widget.saveButtonLabel)),
+ ),
+ ],
+ );
+ }
+}
Why this scored 27/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.