What changed, and why it matters
This commit is a routine product update that brings user-interface improvements from another wallet project ("spice") into Skylight Wallet. It changes labels and help text, updates the address-book design, adds a language picker to the welcome screen, and refreshes the privacy policy and terms of service. There is no evidence of a security fix or vulnerability being introduced.
No security action required. Review as a normal UI/UX and legal-text update.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff is almost entirely non-functional UI/UX and localization changes: string renames (“fiat” → “price”), updated privacy policy/terms text, an expandable address-book tile with a direct “Send” shortcut, a reusable language picker widget, and new transaction-detail labels. No cryptographic, networking, or wallet-core logic is modified. The only behavioral change is that sending from the address book now pre-selects the contact card, which is a convenience feature, not a security issue.
Changed components
lib/screens/address_book.dartlib/screens/send.dartlib/screens/settings.dartlib/screens/welcome.dartlib/widgets/language_sheet.dartlib/widgets/tx_details.dartlib/screens/privacy_policy.dartlib/screens/terms_of_service.dartlib/l10n/*Inspect captured patch +382 / −230
diff --git a/lib/consts.dart b/lib/consts.dart
index e7d93bb..55555cb 100644
--- a/lib/consts.dart
+++ b/lib/consts.dart
@@ -3,15 +3,7 @@ const String torDataDirName = 'tor';
const int txDirectionIncoming = 0;
const int txDirectionOutgoing = 1;
const int moneroDecimals = 12;
-const supportedFiatCurrencies = [
- 'USD',
- 'EUR',
- 'CAD',
- 'AUD',
- 'GBP',
- 'CHF',
- 'JPY',
-];
+const supportedFiatCurrencies = ['USD', 'EUR', 'CAD', 'AUD', 'GBP', 'CHF', 'JPY'];
const indirectPairCurrencies = ['CAD', 'AUD', 'GBP', 'CHF', 'JPY'];
const currencySymbols = {
'USD': '\$',
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 1e057aa..238b500 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -25,7 +25,7 @@
"restoreWarningContinueButton": "Continue",
"lwsSetupTitle": "Connection Setup",
"lwsSetupDescription": "Connect to a Monero light-wallet server (LWS) or your own full node.",
- "lwsSetupAddressHint": "e.g. 192.168.1.1:18090 or example.com:18090",
+ "lwsSetupAddressHint": "lws.example.com:18090",
"lwsSetupProxyPortLabel": "HTTP Proxy Port (Optional)",
"lwsSetupProxyPortHint": "e.g. 4444 for I2P",
"lwsSetupUseTorLabel": "Use Tor",
@@ -77,15 +77,15 @@
}
}
},
- "fiatApiSetupTitle": "Fiat Display Setup",
+ "fiatApiSetupTitle": "Price Display Setup",
"fiatApiSetupDescription": "Optional reference price for your XMR balance.",
"fiatApiSettingsModeLabel": "Mode",
"fiatApiSettingsModeTorOnly": "Tor-Only",
"fiatApiSettingsModeClearnet": "Clearnet-Only (Not Private)",
"fiatApiSettingsModeDisabled": "Disabled",
- "fiatModeTorOnlyDesc": "Rates fetched over Tor · recommended",
- "fiatModeClearnetDesc": "Not private — the rate server sees your IP address",
- "fiatModeDisabledDesc": "No rates fetched, balances shown in crypto only",
+ "fiatModeTorOnlyDesc": "Prices fetched over Tor · recommended",
+ "fiatModeClearnetDesc": "Not private, the price server sees your IP address",
+ "fiatModeDisabledDesc": "No prices fetched, balances shown in crypto only",
"fiatApiSettingsDisplayCurrencyLabel": "Display Currency",
"createWalletTitle": "Create Wallet",
"createWalletDescription": "Do you already have a Monero wallet seed, or do you need to make a new one?",
@@ -100,16 +100,16 @@
"passwordTooShortError": "Password must be at least 8 characters long.",
"passwordsDoNotMatchError": "Passwords do not match.",
"generateSeedTitle": "Write these down, in order",
- "generateSeedTitleCovered": "Your seed phrase",
+ "generateSeedTitleCovered": "Seed Phrase",
"generateSeedDescription": "This is your polyseed. Write it down and keep it in a safe place.",
"generateSeedSubtitleCovered": "These words, in this order, are your wallet. Write them on paper — not in a photo or a notes app.",
- "generateSeedSubtitleRevealed": "Anyone with these words has your funds.",
+ "generateSeedSubtitleRevealed": "Securely save these. Do not share them.",
"generateSeedScreenshotNote": "Screenshots are blocked on this screen. Make sure nobody is looking over your shoulder.",
"generateSeedReveal": "Tap to reveal",
"generateSeedConfirm": "I have written down all the words and stored them somewhere only I can reach.",
"generateSeedContinueButton": "I Wrote It Down",
"lwsDetailsTitle": "Wallet Details",
- "lwsDetailsDescription": "You can use these details to whitelist this wallet on the light wallet server if needed.",
+ "lwsDetailsDescription": "If your Monero light wallet server (LWS) requires registration, you can use these details to add this wallet to that server. Not all servers require registration.",
"lwsDetailsPrimaryAddressLabel": "Primary Address",
"lwsDetailsSecretViewKeyLabel": "Secret View Key",
"lwsDetailsRestoreHeightLabel": "Restore Height",
@@ -119,7 +119,7 @@
"restoreWalletRestoreHeightLabel": "Restore Height (optional)",
"restoreWalletRestoreDateLabel": "Restore Date (optional)",
"restoreWalletScanFrom": "Scan from",
- "restoreWalletScanFromReason": "Earlier is slower but never misses funds.",
+ "restoreWalletScanFromReason": "Skip irrelevant history to save time",
"restoreWalletNotSet": "Not set",
"restoreScanTitle": "When did this seed first hold funds?",
"restoreScanDescription": "Skylight Wallet only scans forward from this point. Guess early — a wrong-but-earlier answer costs sync time, a wrong-but-later one hides transactions.",
@@ -173,13 +173,13 @@
"homeIncomingTxSemanticLabel": "Incoming Transaction",
"homeTransactionConfirmed": "Confirmed",
"homeNoTransactions": "No Transactions",
- "homeFiatApiError": "Error connecting to fiat API",
+ "homeFiatApiError": "Error connecting to price server",
"homeDisconnected": "Disconnected",
"coinHomeActivityTitle": "Activity",
"coinHomeReceived": "Received",
"coinHomeSent": "Sent",
"receiveTitle": "Receive",
- "receivePrimaryAddressWarn": "Warning: Unless you know what you're doing, please consider using subaddresses for better privacy.",
+ "receivePrimaryAddressWarn": "Warning: Unless you know what you're doing, please use subaddresses for better privacy.",
"receiveShareButton": "Share",
"receiveShowSubaddressButton": "Show Subaddress",
"receiveShowPrimaryAddressButton": "Show Primary Address",
@@ -216,13 +216,13 @@
"settingsSectionBehaviour": "Behaviour",
"settingsSectionWallet": "Wallet",
"settingsSectionAbout": "About",
- "settingsNotifyNewTxsLabel": "Notify New Transactions",
+ "settingsNotifyNewTxsLabel": "Transaction Notifications",
"settingsNotifyNewTxsDescription": "Shows a notification when you receive a transaction. When connected to a Monero node, Background Sync must also be enabled.",
"settingsNotifyNewTxsDescriptionIos": "Notifications will be delayed for Tor LWS connections.",
"settingsAppLockLabel": "App Lock",
"settingsAppLockUnlockReason": "Unlock Wallet",
"settingsAppLockUnableToAuthError": "Unable to authenticate. Make sure you have device unlock set up.",
- "settingsVerboseLoggingLabel": "Enable Logging to File",
+ "settingsVerboseLoggingLabel": "Diagnostic Logs",
"settingsVerboseLoggingDescription": "Logs wallet operations to a text file in the app's data folder for debugging purposes.",
"settingsVerboseLoggingDescriptionIos": "Logs wallet operations and allows the logs to be exported to a text file.",
"settingsExportLogsLabel": "Export Logs",
@@ -238,7 +238,7 @@
"settingsThemeSheetSubtitle": "Changes take effect straight away. Amount colours stay the same in both.",
"settingsLanguageLabel": "Language",
"settingsLanguageSheetSubtitle": "Amounts and dates follow the language you pick.",
- "settingsFiatApiSettingsLabel": "Fiat Display Settings",
+ "settingsFiatApiSettingsLabel": "Price Display Settings",
"settingsLwsViewKeysLabel": "LWS View Keys",
"settingsLwsViewKeysButton": "View",
"settingsSecretKeysLabel": "Secret Restore Keys",
@@ -257,7 +257,9 @@
"txDetailsConfirmationsLabel": "Confirmations",
"txDetailsViewKeyLabel": "View Key",
"txDetailsRecipientsLabel": "Recipients",
+ "txDetailsReceivedAtLabel": "Received At",
"txDetailsChangeRecipientLabel": "Change",
+ "unconfirmed": "Unconfirmed",
"txDetailsCopyHint": "tap any value to copy",
"txDetailsFailed": "This transaction failed. The funds were not sent.",
"txDetailsUnknownStatus": "This transaction was not confirmed as sent. Check before sending again.",
diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart
index b0cce47..42e556f 100644
--- a/lib/l10n/app_localizations.dart
+++ b/lib/l10n/app_localizations.dart
@@ -246,7 +246,7 @@ abstract class AppLocalizations {
/// No description provided for @lwsSetupAddressHint.
///
/// In en, this message translates to:
- /// **'e.g. 192.168.1.1:18090 or example.com:18090'**
+ /// **'lws.example.com:18090'**
String get lwsSetupAddressHint;
/// No description provided for @lwsSetupProxyPortLabel.
@@ -474,7 +474,7 @@ abstract class AppLocalizations {
/// No description provided for @fiatApiSetupTitle.
///
/// In en, this message translates to:
- /// **'Fiat Display Setup'**
+ /// **'Price Display Setup'**
String get fiatApiSetupTitle;
/// No description provided for @fiatApiSetupDescription.
@@ -510,19 +510,19 @@ abstract class AppLocalizations {
/// No description provided for @fiatModeTorOnlyDesc.
///
/// In en, this message translates to:
- /// **'Rates fetched over Tor · recommended'**
+ /// **'Prices fetched over Tor · recommended'**
String get fiatModeTorOnlyDesc;
/// No description provided for @fiatModeClearnetDesc.
///
/// In en, this message translates to:
- /// **'Not private — the rate server sees your IP address'**
+ /// **'Not private, the price server sees your IP address'**
String get fiatModeClearnetDesc;
/// No description provided for @fiatModeDisabledDesc.
///
/// In en, this message translates to:
- /// **'No rates fetched, balances shown in crypto only'**
+ /// **'No prices fetched, balances shown in crypto only'**
String get fiatModeDisabledDesc;
/// No description provided for @fiatApiSettingsDisplayCurrencyLabel.
@@ -612,7 +612,7 @@ abstract class AppLocalizations {
/// No description provided for @generateSeedTitleCovered.
///
/// In en, this message translates to:
- /// **'Your seed phrase'**
+ /// **'Seed Phrase'**
String get generateSeedTitleCovered;
/// No description provided for @generateSeedDescription.
@@ -630,7 +630,7 @@ abstract class AppLocalizations {
/// No description provided for @generateSeedSubtitleRevealed.
///
/// In en, this message translates to:
- /// **'Anyone with these words has your funds.'**
+ /// **'Securely save these. Do not share them.'**
String get generateSeedSubtitleRevealed;
/// No description provided for @generateSeedScreenshotNote.
@@ -666,7 +666,7 @@ abstract class AppLocalizations {
/// No description provided for @lwsDetailsDescription.
///
/// In en, this message translates to:
- /// **'You can use these details to whitelist this wallet on the light wallet server if needed.'**
+ /// **'If your Monero light wallet server (LWS) requires registration, you can use these details to add this wallet to that server. Not all servers require registration.'**
String get lwsDetailsDescription;
/// No description provided for @lwsDetailsPrimaryAddressLabel.
@@ -726,7 +726,7 @@ abstract class AppLocalizations {
/// No description provided for @restoreWalletScanFromReason.
///
/// In en, this message translates to:
- /// **'Earlier is slower but never misses funds.'**
+ /// **'Skip irrelevant history to save time'**
String get restoreWalletScanFromReason;
/// No description provided for @restoreWalletNotSet.
@@ -966,7 +966,7 @@ abstract class AppLocalizations {
/// No description provided for @homeFiatApiError.
///
/// In en, this message translates to:
- /// **'Error connecting to fiat API'**
+ /// **'Error connecting to price server'**
String get homeFiatApiError;
/// No description provided for @homeDisconnected.
@@ -1002,7 +1002,7 @@ abstract class AppLocalizations {
/// No description provided for @receivePrimaryAddressWarn.
///
/// In en, this message translates to:
- /// **'Warning: Unless you know what you\'re doing, please consider using subaddresses for better privacy.'**
+ /// **'Warning: Unless you know what you\'re doing, please use subaddresses for better privacy.'**
String get receivePrimaryAddressWarn;
/// No description provided for @receiveShareButton.
@@ -1140,7 +1140,7 @@ abstract class AppLocalizations {
/// No description provided for @settingsNotifyNewTxsLabel.
///
/// In en, this message translates to:
- /// **'Notify New Transactions'**
+ /// **'Transaction Notifications'**
String get settingsNotifyNewTxsLabel;
/// No description provided for @settingsNotifyNewTxsDescription.
@@ -1176,7 +1176,7 @@ abstract class AppLocalizations {
/// No description provided for @settingsVerboseLoggingLabel.
///
/// In en, this message translates to:
- /// **'Enable Logging to File'**
+ /// **'Diagnostic Logs'**
String get settingsVerboseLoggingLabel;
/// No description provided for @settingsVerboseLoggingDescription.
@@ -1272,7 +1272,7 @@ abstract class AppLocalizations {
/// No description provided for @settingsFiatApiSettingsLabel.
///
/// In en, this message translates to:
- /// **'Fiat Display Settings'**
+ /// **'Price Display Settings'**
String get settingsFiatApiSettingsLabel;
/// No description provided for @settingsLwsViewKeysLabel.
@@ -1383,12 +1383,24 @@ abstract class AppLocalizations {
/// **'Recipients'**
String get txDetailsRecipientsLabel;
+ /// No description provided for @txDetailsReceivedAtLabel.
+ ///
+ /// In en, this message translates to:
+ /// **'Received At'**
+ String get txDetailsReceivedAtLabel;
+
/// No description provided for @txDetailsChangeRecipientLabel.
///
/// In en, this message translates to:
/// **'Change'**
String get txDetailsChangeRecipientLabel;
+ /// No description provided for @unconfirmed.
+ ///
+ /// In en, this message translates to:
+ /// **'Unconfirmed'**
+ String get unconfirmed;
+
/// No description provided for @txDetailsCopyHint.
///
/// In en, this message translates to:
diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart
index 98d7bcb..0c328f6 100644
--- a/lib/l10n/app_localizations_en.dart
+++ b/lib/l10n/app_localizations_en.dart
@@ -87,7 +87,7 @@ class AppLocalizationsEn extends AppLocalizations {
'Connect to a Monero light-wallet server (LWS) or your own full node.';
@override
- String get lwsSetupAddressHint => 'e.g. 192.168.1.1:18090 or example.com:18090';
+ String get lwsSetupAddressHint => 'lws.example.com:18090';
@override
String get lwsSetupProxyPortLabel => 'HTTP Proxy Port (Optional)';
@@ -209,7 +209,7 @@ class AppLocalizationsEn extends AppLocalizations {
}
@override
- String get fiatApiSetupTitle => 'Fiat Display Setup';
+ String get fiatApiSetupTitle => 'Price Display Setup';
@override
String get fiatApiSetupDescription => 'Optional reference price for your XMR balance.';
@@ -227,13 +227,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get fiatApiSettingsModeDisabled => 'Disabled';
@override
- String get fiatModeTorOnlyDesc => 'Rates fetched over Tor · recommended';
+ String get fiatModeTorOnlyDesc => 'Prices fetched over Tor · recommended';
@override
- String get fiatModeClearnetDesc => 'Not private — the rate server sees your IP address';
+ String get fiatModeClearnetDesc => 'Not private, the price server sees your IP address';
@override
- String get fiatModeDisabledDesc => 'No rates fetched, balances shown in crypto only';
+ String get fiatModeDisabledDesc => 'No prices fetched, balances shown in crypto only';
@override
String get fiatApiSettingsDisplayCurrencyLabel => 'Display Currency';
@@ -280,7 +280,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get generateSeedTitle => 'Write these down, in order';
@override
- String get generateSeedTitleCovered => 'Your seed phrase';
+ String get generateSeedTitleCovered => 'Seed Phrase';
@override
String get generateSeedDescription =>
@@ -291,7 +291,7 @@ class AppLocalizationsEn extends AppLocalizations {
'These words, in this order, are your wallet. Write them on paper — not in a photo or a notes app.';
@override
- String get generateSeedSubtitleRevealed => 'Anyone with these words has your funds.';
+ String get generateSeedSubtitleRevealed => 'Securely save these. Do not share them.';
@override
String get generateSeedScreenshotNote =>
@@ -312,7 +312,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get lwsDetailsDescription =>
- 'You can use these details to whitelist this wallet on the light wallet server if needed.';
+ 'If your Monero light wallet server (LWS) requires registration, you can use these details to add this wallet to that server. Not all servers require registration.';
@override
String get lwsDetailsPrimaryAddressLabel => 'Primary Address';
@@ -343,7 +343,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get restoreWalletScanFrom => 'Scan from';
@override
- String get restoreWalletScanFromReason => 'Earlier is slower but never misses funds.';
+ String get restoreWalletScanFromReason => 'Skip irrelevant history to save time';
@override
String get restoreWalletNotSet => 'Not set';
@@ -470,7 +470,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get homeNoTransactions => 'No Transactions';
@override
- String get homeFiatApiError => 'Error connecting to fiat API';
+ String get homeFiatApiError => 'Error connecting to price server';
@override
String get homeDisconnected => 'Disconnected';
@@ -489,7 +489,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get receivePrimaryAddressWarn =>
- 'Warning: Unless you know what you\'re doing, please consider using subaddresses for better privacy.';
+ 'Warning: Unless you know what you\'re doing, please use subaddresses for better privacy.';
@override
String get receiveShareButton => 'Share';
@@ -565,7 +565,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get settingsSectionAbout => 'About';
@override
- String get settingsNotifyNewTxsLabel => 'Notify New Transactions';
+ String get settingsNotifyNewTxsLabel => 'Transaction Notifications';
@override
String get settingsNotifyNewTxsDescription =>
@@ -586,7 +586,7 @@ class AppLocalizationsEn extends AppLocalizations {
'Unable to authenticate. Make sure you have device unlock set up.';
@override
- String get settingsVerboseLoggingLabel => 'Enable Logging to File';
+ String get settingsVerboseLoggingLabel => 'Diagnostic Logs';
@override
String get settingsVerboseLoggingDescription =>
@@ -637,7 +637,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get settingsLanguageSheetSubtitle => 'Amounts and dates follow the language you pick.';
@override
- String get settingsFiatApiSettingsLabel => 'Fiat Display Settings';
+ String get settingsFiatApiSettingsLabel => 'Price Display Settings';
@override
String get settingsLwsViewKeysLabel => 'LWS View Keys';
@@ -696,9 +696,15 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get txDetailsRecipientsLabel => 'Recipients';
+ @override
+ String get txDetailsReceivedAtLabel => 'Received At';
+
@override
String get txDetailsChangeRecipientLabel => 'Change';
+ @override
+ String get unconfirmed => 'Unconfirmed';
+
@override
String get txDetailsCopyHint => 'tap any value to copy';
diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart
index 52b2ecd..84f5ddf 100644
--- a/lib/l10n/app_localizations_pt.dart
+++ b/lib/l10n/app_localizations_pt.dart
@@ -87,7 +87,7 @@ class AppLocalizationsPt extends AppLocalizations {
'Conecte-se a um servidor light-wallet Monero (LWS) ou ao seu próprio nó completo.';
@override
- String get lwsSetupAddressHint => 'ex: 192.168.1.1:18090 ou exemplo.com:18090';
+ String get lwsSetupAddressHint => 'lws.example.com:18090';
@override
String get lwsSetupProxyPortLabel => 'Porta do Proxy HTTP (opcional)';
@@ -209,7 +209,7 @@ class AppLocalizationsPt extends AppLocalizations {
}
@override
- String get fiatApiSetupTitle => 'Exibição de Saldo em Fiat';
+ String get fiatApiSetupTitle => 'Configuração de Exibição de Preços';
@override
String get fiatApiSetupDescription => 'Preço de referência opcional para seu saldo em XMR.';
@@ -227,13 +227,13 @@ class AppLocalizationsPt extends AppLocalizations {
String get fiatApiSettingsModeDisabled => 'Desativado';
@override
- String get fiatModeTorOnlyDesc => 'Cotações buscadas pelo Tor · recomendado';
+ String get fiatModeTorOnlyDesc => 'Preços buscados pelo Tor · recomendado';
@override
- String get fiatModeClearnetDesc => 'Não privado — o servidor de cotações vê seu endereço IP';
+ String get fiatModeClearnetDesc => 'Não privado, o servidor de preços vê seu endereço IP';
@override
- String get fiatModeDisabledDesc => 'Sem cotações; saldos exibidos apenas em cripto';
+ String get fiatModeDisabledDesc => 'Sem preços; saldos exibidos apenas em cripto';
@override
String get fiatApiSettingsDisplayCurrencyLabel => 'Moeda de Exibição';
@@ -280,7 +280,7 @@ class AppLocalizationsPt extends AppLocalizations {
String get generateSeedTitle => 'Anote-as, em ordem';
@override
- String get generateSeedTitleCovered => 'Sua frase seed';
+ String get generateSeedTitleCovered => 'Frase Seed';
@override
String get generateSeedDescription =>
@@ -291,7 +291,7 @@ class AppLocalizationsPt extends AppLocalizations {
'Estas palavras, nesta ordem, são a sua carteira. Anote-as no papel — não em uma foto ou app de notas.';
@override
- String get generateSeedSubtitleRevealed => 'Qualquer pessoa com estas palavras tem seus fundos.';
+ String get generateSeedSubtitleRevealed => 'Guarde-as em segurança. Não as compartilhe.';
@override
String get generateSeedScreenshotNote =>
@@ -312,7 +312,7 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get lwsDetailsDescription =>
- 'Você pode usar esses detalhes para permitir essa carteira no seu servidor de light wallet caso necessário.';
+ 'Se o seu servidor Light Wallet Monero (LWS) exigir registro, você pode usar estes dados para adicionar esta carteira a esse servidor. Nem todos os servidores exigem registro.';
@override
String get lwsDetailsPrimaryAddressLabel => 'Endereço Primário';
@@ -343,7 +343,7 @@ class AppLocalizationsPt extends AppLocalizations {
String get restoreWalletScanFrom => 'Escanear a partir de';
@override
- String get restoreWalletScanFromReason => 'Mais cedo é mais lento, mas nunca perde fundos.';
+ String get restoreWalletScanFromReason => 'Ignore o histórico irrelevante para economizar tempo';
@override
String get restoreWalletNotSet => 'Não definido';
@@ -470,7 +470,7 @@ class AppLocalizationsPt extends AppLocalizations {
String get homeNoTransactions => 'Sem transações';
@override
- String get homeFiatApiError => 'Erro ao conectar à API de cotação';
+ String get homeFiatApiError => 'Erro ao conectar ao servidor de preços';
@override
String get homeDisconnected => 'Desconectado';
@@ -489,7 +489,7 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get receivePrimaryAddressWarn =>
- 'Aviso: A menos que saiba o que está fazendo, por favor considere usar subendereços para melhor privacidade.';
+ 'Aviso: A menos que saiba o que está fazendo, por favor use subendereços para melhor privacidade.';
@override
String get receiveShareButton => 'Compartilhar';
@@ -565,7 +565,7 @@ class AppLocalizationsPt extends AppLocalizations {
String get settingsSectionAbout => 'Sobre';
@override
- String get settingsNotifyNewTxsLabel => 'Notificar Novas Transações';
+ String get settingsNotifyNewTxsLabel => 'Notificações de Transações';
@override
String get settingsNotifyNewTxsDescription =>
@@ -586,7 +586,7 @@ class AppLocalizationsPt extends AppLocalizations {
'Não foi possível autenticar. Verifique se o desbloqueio de tela está configurado.';
@override
- String get settingsVerboseLoggingLabel => 'Salvar Logs em Arquivo';
+ String get settingsVerboseLoggingLabel => 'Logs de Diagnóstico';
@override
String get settingsVerboseLoggingDescription =>
@@ -637,7 +637,7 @@ class AppLocalizationsPt extends AppLocalizations {
String get settingsLanguageSheetSubtitle => 'Valores e datas seguem o idioma escolhido.';
@override
- String get settingsFiatApiSettingsLabel => 'Exibição de Moeda Local';
+ String get settingsFiatApiSettingsLabel => 'Configurações de Exibição de Preços';
@override
String get settingsLwsViewKeysLabel => 'Chaves de Visualização do LWS';
@@ -696,9 +696,15 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get txDetailsRecipientsLabel => 'Destinatários';
+ @override
+ String get txDetailsReceivedAtLabel => 'Recebido em';
+
@override
String get txDetailsChangeRecipientLabel => 'Troco';
+ @override
+ String get unconfirmed => 'Não confirmado';
+
@override
String get txDetailsCopyHint => 'toque em qualquer valor para copiar';
diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb
index e75d480..e4ff49e 100644
--- a/lib/l10n/app_pt.arb
+++ b/lib/l10n/app_pt.arb
@@ -25,7 +25,7 @@
"restoreWarningContinueButton": "Continuar",
"lwsSetupTitle": "Configuração da conexão",
"lwsSetupDescription": "Conecte-se a um servidor light-wallet Monero (LWS) ou ao seu próprio nó completo.",
- "lwsSetupAddressHint": "ex: 192.168.1.1:18090 ou exemplo.com:18090",
+ "lwsSetupAddressHint": "lws.example.com:18090",
"lwsSetupProxyPortLabel": "Porta do Proxy HTTP (opcional)",
"lwsSetupProxyPortHint": "ex: 4444 para I2P",
"lwsSetupUseTorLabel": "Usar Tor",
@@ -77,15 +77,15 @@
}
}
},
- "fiatApiSetupTitle": "Exibição de Saldo em Fiat",
+ "fiatApiSetupTitle": "Configuração de Exibição de Preços",
"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",
- "fiatModeTorOnlyDesc": "Cotações buscadas pelo Tor · recomendado",
- "fiatModeClearnetDesc": "Não privado — o servidor de cotações vê seu endereço IP",
- "fiatModeDisabledDesc": "Sem cotações; saldos exibidos apenas em cripto",
+ "fiatModeTorOnlyDesc": "Preços buscados pelo Tor · recomendado",
+ "fiatModeClearnetDesc": "Não privado, o servidor de preços vê seu endereço IP",
+ "fiatModeDisabledDesc": "Sem preços; saldos exibidos apenas em cripto",
"fiatApiSettingsDisplayCurrencyLabel": "Moeda de Exibição",
"createWalletTitle": "Criar Carteira",
"createWalletDescription": "Você já possui uma semente de carteira Monero ou precisa criar uma nova?",
@@ -100,16 +100,16 @@
"passwordTooShortError": "A senha deve ter pelo menos 8 caracteres.",
"passwordsDoNotMatchError": "As senhas não coincidem.",
"generateSeedTitle": "Anote-as, em ordem",
- "generateSeedTitleCovered": "Sua frase seed",
+ "generateSeedTitleCovered": "Frase Seed",
"generateSeedDescription": "Esta é a sua polyseed. Anote-a e guarde-a em um lugar seguro.",
"generateSeedSubtitleCovered": "Estas palavras, nesta ordem, são a sua carteira. Anote-as no papel — não em uma foto ou app de notas.",
- "generateSeedSubtitleRevealed": "Qualquer pessoa com estas palavras tem seus fundos.",
+ "generateSeedSubtitleRevealed": "Guarde-as em segurança. Não as compartilhe.",
"generateSeedScreenshotNote": "As capturas de tela estão bloqueadas nesta tela. Certifique-se de que ninguém está olhando por cima do seu ombro.",
"generateSeedReveal": "Toque para revelar",
"generateSeedConfirm": "Anotei todas as palavras e as guardei em um lugar que só eu posso acessar.",
"generateSeedContinueButton": "Eu anotei",
"lwsDetailsTitle": "Detalhes da Carteira",
- "lwsDetailsDescription": "Você pode usar esses detalhes para permitir essa carteira no seu servidor de light wallet caso necessário.",
+ "lwsDetailsDescription": "Se o seu servidor Light Wallet Monero (LWS) exigir registro, você pode usar estes dados para adicionar esta carteira a esse servidor. Nem todos os servidores exigem registro.",
"lwsDetailsPrimaryAddressLabel": "Endereço Primário",
"lwsDetailsSecretViewKeyLabel": "Chave Privada de Visualização",
"lwsDetailsRestoreHeightLabel": "Bloco de Restauração",
@@ -119,7 +119,7 @@
"restoreWalletRestoreHeightLabel": "Bloco de Restauração (opcional)",
"restoreWalletRestoreDateLabel": "Data de Restauração (opcional)",
"restoreWalletScanFrom": "Escanear a partir de",
- "restoreWalletScanFromReason": "Mais cedo é mais lento, mas nunca perde fundos.",
+ "restoreWalletScanFromReason": "Ignore o histórico irrelevante para economizar tempo",
"restoreWalletNotSet": "Não definido",
"restoreScanTitle": "Quando esta seed teve fundos pela primeira vez?",
"restoreScanDescription": "A Skylight Wallet só escaneia a partir deste ponto. Chute cedo — uma resposta mais antiga custa tempo de sincronização, uma mais recente esconde transações.",
@@ -173,13 +173,13 @@
"homeIncomingTxSemanticLabel": "Transação de Entrada",
"homeTransactionConfirmed": "Confirmado",
"homeNoTransactions": "Sem transações",
- "homeFiatApiError": "Erro ao conectar à API de cotação",
+ "homeFiatApiError": "Erro ao conectar ao servidor de preços",
"homeDisconnected": "Desconectado",
"coinHomeActivityTitle": "Atividade",
"coinHomeReceived": "Recebido",
"coinHomeSent": "Enviado",
"receiveTitle": "Receber",
- "receivePrimaryAddressWarn": "Aviso: A menos que saiba o que está fazendo, por favor considere usar subendereços para melhor privacidade.",
+ "receivePrimaryAddressWarn": "Aviso: A menos que saiba o que está fazendo, por favor use subendereços para melhor privacidade.",
"receiveShareButton": "Compartilhar",
"receiveShowSubaddressButton": "Mostrar Subendereço",
"receiveShowPrimaryAddressButton": "Mostrar Endereço Primário",
@@ -216,13 +216,13 @@
"settingsSectionBehaviour": "Comportamento",
"settingsSectionWallet": "Carteira",
"settingsSectionAbout": "Sobre",
- "settingsNotifyNewTxsLabel": "Notificar Novas Transações",
+ "settingsNotifyNewTxsLabel": "Notificações de Transações",
"settingsNotifyNewTxsDescription": "Mostra uma notificação quando você recebe uma transação. Ao conectar-se a um nó Monero, a Sincronização em Segundo Plano também precisa estar ativada.",
"settingsNotifyNewTxsDescriptionIos": "As notificações serão atrasadas para conexões LWS via Tor.",
"settingsAppLockLabel": "Desbloqueio com PIN/Biometria",
"settingsAppLockUnlockReason": "Desbloquear carteira",
"settingsAppLockUnableToAuthError": "Não foi possível autenticar. Verifique se o desbloqueio de tela está configurado.",
- "settingsVerboseLoggingLabel": "Salvar Logs em Arquivo",
+ "settingsVerboseLoggingLabel": "Logs de Diagnóstico",
"settingsVerboseLoggingDescription": "Registra operações da carteira em um arquivo de texto na pasta de dados do app para fins de depuração.",
"settingsVerboseLoggingDescriptionIos": "Registra operações da carteira e permite exportar os logs para um arquivo de texto.",
"settingsExportLogsLabel": "Exportar Logs",
@@ -238,7 +238,7 @@
"settingsThemeSheetSubtitle": "As mudanças têm efeito imediato. As cores dos valores permanecem iguais nos dois.",
"settingsLanguageLabel": "Idioma",
"settingsLanguageSheetSubtitle": "Valores e datas seguem o idioma escolhido.",
- "settingsFiatApiSettingsLabel": "Exibição de Moeda Local",
+ "settingsFiatApiSettingsLabel": "Configurações de Exibição de Preços",
"settingsLwsViewKeysLabel": "Chaves de Visualização do LWS",
"settingsLwsViewKeysButton": "Ver",
"settingsSecretKeysLabel": "Chaves Privadas de Restauração",
@@ -257,7 +257,9 @@
"txDetailsConfirmationsLabel": "Confirmações",
"txDetailsViewKeyLabel": "Chave de Visualização",
"txDetailsRecipientsLabel": "Destinatários",
+ "txDetailsReceivedAtLabel": "Recebido em",
"txDetailsChangeRecipientLabel": "Troco",
+ "unconfirmed": "Não confirmado",
"txDetailsCopyHint": "toque em qualquer valor para copiar",
"txDetailsFailed": "Esta transação falhou. Os fundos não foram enviados.",
"txDetailsUnknownStatus": "Esta transação não foi confirmada como enviada. Verifique antes de enviar novamente.",
diff --git a/lib/screens/address_book.dart b/lib/screens/address_book.dart
index 6bb06bc..3a8cf84 100644
--- a/lib/screens/address_book.dart
+++ b/lib/screens/address_book.dart
@@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
import 'package:skylight_wallet/l10n/app_localizations.dart';
import 'package:skylight_wallet/models/contact_model.dart';
+import 'package:skylight_wallet/screens/send.dart';
import 'package:skylight_wallet/util/secure_clipboard.dart';
import 'package:skylight_wallet/wallet_core_glue.dart';
import 'package:skylight_wallet/widgets/ui/ui.dart';
@@ -21,6 +22,7 @@ class AddressBookScreen extends StatefulWidget {
class _AddressBookScreenState extends State<AddressBookScreen> {
final TextEditingController _searchController = TextEditingController();
String _searchQuery = '';
+ String? _expandedId;
@override
void dispose() {
@@ -149,8 +151,12 @@ class _AddressBookScreenState extends State<AddressBookScreen> {
itemCount: contacts.length,
itemBuilder: (context, index) {
final contact = contacts[index];
+ final expanded = contact.id == _expandedId;
return _ContactTile(
contact: contact,
+ expanded: expanded,
+ onToggle: () =>
+ setState(() => _expandedId = expanded ? null : contact.id),
onEdit: () => _showEditContactDialog(contact),
onDelete: () => _showDeleteContactDialog(contact),
isLast: index == contacts.length - 1,
@@ -269,134 +275,215 @@ class _Avatar extends StatelessWidget {
}
}
-/// Single-address contact row: monogram + name + shortened Monero address, with
-/// a brand popup menu for copy / edit / delete.
+/// Expandable contact: collapsed shows the monogram + name + chain; tapping
+/// opens a card with the Monero address row (copy / send) and edit / delete.
class _ContactTile extends StatelessWidget {
final Contact contact;
+ final bool expanded;
+ final VoidCallback onToggle;
final VoidCallback onEdit;
final VoidCallback onDelete;
final bool isLast;
const _ContactTile({
required this.contact,
+ required this.expanded,
+ required this.onToggle,
required this.onEdit,
required this.onDelete,
required this.isLast,
});
- void _copy(BuildContext context) {
- final i18n = AppLocalizations.of(context)!;
- // Treat as sensitive (auto-cleared) like other address/key copies.
- SecureClipboard.copy(contact.address);
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(i18n.addressCopied)));
- }
-
@override
Widget build(BuildContext context) {
- return Column(
- children: [
- InkWell(
- onTap: () => _copy(context),
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 14),
- child: Row(
- children: [
- _Avatar(name: contact.name),
- const SizedBox(width: 14),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- contact.name,
- style: TextStyle(
- fontSize: 15.5,
- fontWeight: FontWeight.w700,
- color: BrandColors.ink,
+ final i18n = AppLocalizations.of(context)!;
+
+ if (!expanded) {
+ return Column(
+ children: [
+ InkWell(
+ onTap: onToggle,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 14),
+ child: Row(
+ children: [
+ _Avatar(name: contact.name),
+ const SizedBox(width: 14),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ contact.name,
+ style: TextStyle(
+ fontSize: 15.5,
+ fontWeight: FontWeight.w700,
+ color: BrandColors.ink,
+ ),
),
- ),
- const SizedBox(height: 4),
- Text(
- shortenMiddle(contact.address, head: 8, tail: 8),
- style: TextStyle(
- fontFamily: 'Ubuntu Mono',
- fontSize: 11.5,
- color: BrandColors.inkMuted,
+ const SizedBox(height: 4),
+ Text(
+ shortenMiddle(contact.address, head: 8, tail: 8),
+ style: TextStyle(
+ fontFamily: 'Ubuntu Mono',
+ fontSize: 11.5,
+ color: BrandColors.inkMuted,
+ ),
),
- ),
- ],
+ ],
+ ),
),
- ),
- _ContactMenu(onCopy: () => _copy(context), onEdit: onEdit, onDelete: onDelete),
- ],
+ Icon(Icons.keyboard_arrow_down, size: 22, color: BrandColors.inkMuted),
+ ],
+ ),
),
),
+ if (!isLast) Divider(height: 1, thickness: 1, color: BrandColors.surfaceTinted),
+ ],
+ );
+ }
+
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 6),
+ child: BrandCard(
+ radius: 20,
+ child: Column(
+ children: [
+ InkWell(
+ onTap: onToggle,
+ borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(15, 15, 15, 13),
+ child: Row(
+ children: [
+ _Avatar(name: contact.name),
+ const SizedBox(width: 14),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ contact.name,
+ style: TextStyle(
+ fontSize: 15.5,
+ fontWeight: FontWeight.w700,
+ color: BrandColors.ink,
+ ),
+ ),
+ ],
+ ),
+ ),
+ Icon(Icons.keyboard_arrow_up, size: 22, color: BrandColors.inkMuted),
+ ],
+ ),
+ ),
+ ),
+ _AddressRow(contact: contact),
+ Padding(
+ padding: const EdgeInsets.fromLTRB(15, 6, 15, 15),
+ child: Row(
+ children: [
+ Expanded(
+ child: BrandButton.secondary(
+ label: i18n.addressBookEdit,
+ dense: true,
+ onPressed: onEdit,
+ ),
+ ),
+ const SizedBox(width: 10),
+ Expanded(
+ child: BrandButton.ghost(
+ label: i18n.addressBookDelete,
+ color: BrandColors.error,
+ dense: true,
+ onPressed: onDelete,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
),
- if (!isLast) Divider(height: 1, thickness: 1, color: BrandColors.surfaceTinted),
- ],
+ ),
);
}
}
-class _ContactMenu extends StatelessWidget {
- final VoidCallback onCopy;
- final VoidCallback onEdit;
- final VoidCallback onDelete;
+/// The Monero address row inside an expanded contact — icon, name, address, and
+/// copy / send actions.
+class _AddressRow extends StatelessWidget {
+ final Contact contact;
+ const _AddressRow({required this.contact});
- const _ContactMenu({required this.onCopy, required this.onEdit, required this.onDelete});
+ void _copy(BuildContext context) {
+ final i18n = AppLocalizations.of(context)!;
+ // Treat as sensitive (auto-cleared) like other address/key copies.
+ SecureClipboard.copy(contact.address);
+ ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(i18n.addressCopied)));
+ }
@override
Widget build(BuildContext context) {
final i18n = AppLocalizations.of(context)!;
- return PopupMenuButton<String>(
- icon: Icon(Icons.more_vert, size: 22, color: BrandColors.inkMuted),
- color: BrandColors.card,
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
- onSelected: (value) {
- switch (value) {
- case 'copy':
- onCopy();
- case 'edit':
- onEdit();
- case 'delete':
- onDelete();
- }
- },
- itemBuilder: (context) => [
- PopupMenuItem(
- value: 'copy',
- child: Row(
- children: [
- Icon(Icons.copy_outlined, size: 18, color: BrandColors.primaryDeep),
- const SizedBox(width: 10),
- Text(i18n.addressBookCopyAddress, style: BrandText.body),
- ],
- ),
- ),
- PopupMenuItem(
- value: 'edit',
- child: Row(
- children: [
- Icon(Icons.edit_outlined, size: 18, color: BrandColors.primaryDeep),
- const SizedBox(width: 10),
- Text(i18n.addressBookEdit, style: BrandText.body),
- ],
- ),
- ),
- PopupMenuItem(
- value: 'delete',
- child: Row(
- children: [
- Icon(Icons.delete_outline, size: 18, color: BrandColors.error),
- const SizedBox(width: 10),
- Text(
- i18n.addressBookDelete,
- style: BrandText.body.copyWith(color: BrandColors.error),
+ return Container(
+ decoration: BoxDecoration(
+ border: Border(top: BorderSide(color: BrandColors.surfaceTinted)),
+ ),
+ padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 12),
+ child: Row(
+ children: [
+ Expanded(
+ child: Text(
+ contact.address,
+ style: TextStyle(
+ fontFamily: 'Ubuntu Mono',
+ fontSize: 12,
+ height: 1.4,
+ color: BrandColors.ink,
),
- ],
+ ),
+ ),
+ const SizedBox(width: 10),
+ _IconSquare(icon: Icons.copy_outlined, onTap: () => _copy(context)),
+ const SizedBox(width: 8),
+ BrandButton(
+ label: i18n.homeSend,
+ icon: Icons.north_east,
+ dense: true,
+ expand: false,
+ onPressed: () => Navigator.pushNamed(
+ context,
+ '/send',
+ arguments: SendScreenArgs(destinationAddress: contact.address, contact: contact),
+ ),
),
+ ],
+ ),
+ );
+ }
+}
+
+class _IconSquare extends StatelessWidget {
+ final IconData icon;
+ final VoidCallback onTap;
+ const _IconSquare({required this.icon, required this.onTap});
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTap: onTap,
+ child: Container(
+ width: 44,
+ height: 44,
+ alignment: Alignment.center,
+ decoration: BoxDecoration(
+ color: BrandColors.surfaceSunken,
+ border: Border.all(color: BrandColors.border),
+ borderRadius: BorderRadius.circular(12),
),
- ],
+ child: Icon(icon, size: 18, color: BrandColors.primaryDeep),
+ ),
);
}
}
diff --git a/lib/screens/privacy_policy.dart b/lib/screens/privacy_policy.dart
index c70c167..7e8b7d6 100644
--- a/lib/screens/privacy_policy.dart
+++ b/lib/screens/privacy_policy.dart
@@ -84,19 +84,19 @@ class PrivacyPolicy extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Text('Updated 2025-09-19', style: bodyStyle),
+ Text('Updated 2026-09-03', style: bodyStyle),
SizedBox(height: 32),
paragraph(
'MAGIC Grants (“we”) respect your privacy and are committed to protecting it through our compliance with this policy.',
),
paragraph(
- 'This policy applies to information we collect through your direct use of the Skylight Wallet app. It does not apply to information we collect by any other means. This policy is subject to change, so make sure to periodically review this policy.',
+ 'This policy applies to information we collect through your direct use of the Skylight Wallet and/or Spice Wallet app (the "App"). It does not apply to information we collect by any other means. This policy is subject to change, so make sure to periodically review this policy.',
),
sectionHeading('Information We Do Not Collect'),
paragraph(
- 'We do not collect any usage data. The app does not send any private keys, including private spend keys, mnemonic seeds, and private view keys to MAGIC Grants. The app does not connect to any servers run by MAGIC Grants.',
+ 'We do not collect any usage data. The App does not send any private keys, including private spend keys, mnemonic seeds, and private view keys to MAGIC Grants. The App does not connect to any servers run by MAGIC Grants.',
),
paragraph(
'You do not need to make an account with MAGIC Grants to use the app.',
@@ -107,39 +107,45 @@ class PrivacyPolicy extends StatelessWidget {
sectionHeading('Information You May Share with Third Parties'),
paragraph(
- 'When using Skylight Wallet, you need to specify a server to connect to. This will share your cryptocurrency address and private view key with this server. You will also share transactions you create with this server. This is necessary for the core functionality of the wallet.',
+ 'When using the App, you need to specify a server (or servers) to connect to. The information shared with a server will depend on the cryptocurrency network that you are configuring and connecting to, but it could include sensitive information including your cryptocurrency address(es), your private view key(s), and your transaction data. This is necessary for the core functionality of the App.',
),
paragraph(
- 'MAGIC Grants does not provide a default server. You must research the server that you want to use and any terms of service or privacy policies associated with that server before you connect to that server.',
+ 'You can choose to only connect to the cryptocurrency networks that you choose. It is not necessary to connect to any particular cryptocurrency network to use the App.',
),
paragraph(
- 'You may choose to avoid using a third party by connecting to a server that you operate.',
+ 'MAGIC Grants does not provide default servers. You must research the servers that you want to use and any terms of service or privacy policies associated with those servers before you connect to those servers.',
+ ),
+ paragraph(
+ 'In many cases, you may choose to avoid using a third party by connecting to servers that you operate. For example, instead of connecting to a node operated by a third party, you can connect to a node operated by you.',
),
sectionHeading('Other Connections to Third Parties'),
paragraph(
- 'The app connects to services provided by third parties for your convenience. These include:',
+ 'The App optionally connects to services provided by third parties for your convenience. These include:',
),
BulletList(
items: [
- 'Block explorers, for viewing information about your transactions.',
- 'Price quotes, for displaying richer information about your transactions.',
- 'Cryptocurrency nodes, for the app to learn information about the cryptocurrency network’s status.',
+ 'Block explorers, to display network and transaction information.',
+ 'Nodes, to display network and transaction information.',
+ 'Price quotes, to display richer information about your transactions.',
],
),
paragraph(
- 'Within the app, connections to these services are protected automatically with Tor. However, MAGIC Grants does not warrant that these connections are bug-free, or that these protections are fit for a particular purpose.',
+ 'Within the App, connections to these services can be configured to be routed over the Tor network, which helps improve your privacy. However, MAGIC Grants does not warrant that these connections are bug-free, or that these protections are fit for a particular purpose. Please be mindful of the third party connections that you make when using the App. The App may contain links to websites that, when clicked, open outside of Tor in your default web browser.',
+ ),
+ paragraph(
+ 'Some third party connections, such as the price quotes, can be disabled in App settings.',
),
paragraph(
- 'Please be mindful of the third party connections that you make when using the App. The App may contain links to websites that, when clicked, open outside of Tor in your default web browser.',
+ 'When you connect to third parties, you must abide by their terms of service and privacy policies.',
),
sectionHeading('When You Contact Us'),
paragraph(
- 'There is no mechanism for contacting us within the application. If you contact us through a different means, then we may learn and retain information about you, such as your email address, account identifier, and the contents of your communications.',
+ 'There is no mechanism for contacting us within the App. If you contact us through a different means, then we may learn and retain information about you, such as your email address, account identifier, and the contents of your communications.',
),
paragraph(
- 'Before sending any app logs, make sure to review their contents for any potentially sensitive information. Remove this information before sending any information to us.',
+ 'Before sending any app diagnostic logs, make sure to review their contents for any potentially sensitive information. Remove this information before sending any information to us.',
),
],
),
diff --git a/lib/screens/restore_wallet.dart b/lib/screens/restore_wallet.dart
index 6d6022d..edb1f17 100644
--- a/lib/screens/restore_wallet.dart
+++ b/lib/screens/restore_wallet.dart
@@ -83,6 +83,7 @@ class _RestoreWalletScreenState extends State<RestoreWalletScreen> with SecureSc
final result = await showScanFromSheet(
context: context,
initial: _restoreDate,
+ chosen: _scanChosen,
labels: ScanFromSheetLabels(
title: i18n.restoreScanTitle,
description: i18n.restoreScanDescription,
diff --git a/lib/screens/send.dart b/lib/screens/send.dart
index 36d3ead..738166c 100644
--- a/lib/screens/send.dart
+++ b/lib/screens/send.dart
@@ -18,7 +18,11 @@ class SendScreenArgs {
String destinationAddress;
double? amount;
- SendScreenArgs({required this.destinationAddress, this.amount});
+ /// Set when the address came from a contact (e.g. Send in the address book),
+ /// so Send opens showing the contact card rather than a bare address.
+ final Contact? contact;
+
+ SendScreenArgs({required this.destinationAddress, this.amount, this.contact});
}
class SendScreen extends StatefulWidget {
@@ -106,6 +110,8 @@ class _SendScreenState extends State<SendScreen> {
if (args != null) {
_destinationAddressController.text = args.destinationAddress;
_amountController.text = args.amount != null ? args.amount.toString() : '';
+ // Same field the in-send picker sets, so the contact card renders here too.
+ _selectedContact = args.contact;
}
}
diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart
index a0a4419..2d2748f 100644
--- a/lib/screens/settings.dart
+++ b/lib/screens/settings.dart
@@ -18,6 +18,7 @@ import 'package:skylight_wallet/periodic_tasks.dart';
import 'package:skylight_wallet/services/notifications_service.dart';
import 'package:skylight_wallet/services/shared_preferences_service.dart';
import 'package:skylight_wallet/services/tor_settings_service.dart';
+import 'package:skylight_wallet/widgets/language_sheet.dart';
import 'package:skylight_wallet/widgets/ui/ui.dart';
import 'package:skylight_wallet/widgets/wallet_navigation_bar.dart';
import 'package:wallet_infra/wallet_infra.dart' show BiometricAuth, BiometricAuthResult;
@@ -425,36 +426,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
);
}
- void _showLanguagePicker() {
- final i18n = AppLocalizations.of(context)!;
- final language = context.read<LanguageModel>();
- showLanguagePickerSheet(
- context,
- labels: SettingsPickerLabels(
- title: i18n.settingsLanguageLabel,
- subtitle: i18n.settingsLanguageSheetSubtitle,
- done: i18n.done,
- ),
- options: [
- for (final locale in AppLocalizations.supportedLocales)
- LanguagePickerOption(
- code: locale.languageCode,
- native: _languageNames[locale.languageCode]?.$1 ?? locale.languageCode,
- english: _languageNames[locale.languageCode]?.$2 ?? '',
- ),
- ],
- selected: language.language,
- onSelect: language.setLanguage,
- iconBg: BrandColors.surfaceTinted,
- iconColor: BrandColors.primaryDeep,
- );
- }
-
- static const _languageNames = {
- 'en': ('English', 'English'),
- 'pt': ('Português', 'Portuguese (Brazil)'),
- };
-
@override
Widget build(BuildContext context) {
final i18n = AppLocalizations.of(context)!;
@@ -506,9 +477,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
SettingsNavTile(
title: i18n.settingsLanguageLabel,
value:
- _languageNames[language.language]?.$1 ??
+ languageNames[language.language]?.$1 ??
language.language.toUpperCase(),
- onTap: _showLanguagePicker,
+ onTap: () => showLanguageSheet(context),
),
if (isMobile)
SettingsToggleTile(
diff --git a/lib/screens/terms_of_service.dart b/lib/screens/terms_of_service.dart
index 4cb6808..a772022 100644
--- a/lib/screens/terms_of_service.dart
+++ b/lib/screens/terms_of_service.dart
@@ -52,12 +52,12 @@ class TermsOfService extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Text('Updated 2025-09-19', style: bodyStyle),
+ Text('Updated 2026-09-03', style: bodyStyle),
SizedBox(height: 12),
sectionHeading('Acceptance'),
paragraph(
- 'These Terms of Service (ToS) are entered into by and between You and MAGIC Grants, a Colorado nonprofit corporation (“we” or “us”). These terms govern your access to and use of Skylight Wallet (the “App”).',
+ 'These Terms of Service (ToS) are entered into by and between You and MAGIC Grants, a Colorado nonprofit corporation (“we” or “us”). These terms govern your access to and use of Skylight Wallet and Spice Wallet (the “App”).',
),
paragraph(
'Please read the Terms of Service carefully before you start to use the App. By using the App you accept and agree to be bound and abide by these Terms of Service and our Privacy Policy. If you do not agree to these Terms of Service or the Privacy Policy, then you must not access or use the App.',
@@ -79,26 +79,41 @@ class TermsOfService extends StatelessWidget {
Container(
width: double.infinity,
margin: EdgeInsets.only(bottom: 10.0),
+ decoration: BoxDecoration(borderRadius: BorderRadius.circular(6.0)),
child: Text(
'THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.',
style: monospaceStyle,
),
),
- sectionHeading('Security'),
+ sectionHeading('No Financial or Legal Advice'),
paragraph(
- 'You are fully responsible for the security of your funds when using the App. You agree that MAGIC Grants is not liable for your loss of funds, including due to any bugs that are present in the application. You agree that MAGIC Grants has not warranted that the App is secure for your desired purpose. You agree that MAGIC Grants has not claimed that the App is bug-free.',
+ 'MAGIC Grants does not provide any financial or legal advice in connection with the App. You are responsible for all of your actions when using the App.',
+ ),
+
+ sectionHeading('Security and Network Risks'),
+ paragraph(
+ 'You are fully responsible for the security of your funds when using the App. You agree that MAGIC Grants is not liable for your loss of funds, including due to any bugs that are present in the application. You agree that MAGIC Grants has not warranted that the App is secure for any particular purpose, including your desired purpose. You acknowledge that MAGIC Grants has not claimed that the App is bug-free.',
),
paragraph(
- 'You acknowledge that MAGIC Grants is not responsible for any loss of funds or other harms that could arise from related networks and software, including but not limited to the cryptocurrency networks that you connect to.',
+ 'You acknowledge that MAGIC Grants is not responsible for any loss of funds or other harms that could arise from related networks and software, including but not limited to the cryptocurrency networks that you connect to. These networks may have bugs (including severe ones which could lead to loss of funds or dilution of value), and infrastructure that you connect to can be malicious. Cryptocurrency networks may charge you fees, and you can lose value through value fluctuations and volatility, impermanent loss, slippage, and other market factors which may be very complex. You agree that MAGIC Grants is not liable for any of these issues. By interacting with these cryptocurrency networks, you agree that you are accepting all of the risks of these networks and accepting full responsibility for understanding them.',
+ ),
+ paragraph(
+ 'You acknowledge that MAGIC Grants offers no warranty of any kind, without limitation, for performance or security of the App or these cryptocurrency networks, even cryptocurrency networks that MAGIC Grants has commissioned security reviews for and cryptocurrency networks that MAGIC Grants runs our own nodes for.',
),
sectionHeading('No Custody by MAGIC Grants'),
paragraph(
- 'MAGIC Grants, the developer of this App, does not have custody of your funds while you are using the App. Skylight Wallet is a self-custody wallet. MAGIC Grants does not transmit funds for You. MAGIC Grants is not an exchange or money services business.',
+ 'MAGIC Grants does not have custody of your funds while you are using the App. The App is software that intends to allow you to manage your self-custody wallets. MAGIC Grants does not transmit funds for you. MAGIC Grants is not an exchange or money services business (MSB).',
),
paragraph(
- 'The App does not store cryptocurrencies, including Monero, Litecoin or Bitcoin. Cryptocurrencies exist only by virtue of the ownership record maintained in their respective networks. Any transfer of title in cryptocurrency occurs within a decentralized cryptocurrency network, and not in the App.',
+ 'If you use any feature in the App named swap, trade, exchange, add liquidity, remove liquidity, bridge, or similar, you acknowledge that MAGIC Grants is not involved with nor performing these activities/actions whatsoever. The App may include features that you may use to interact with decentralized exchanges; however, MAGIC Grants is not a party nor counterparty to these transactions. The App is solely software that allows you to connect to relevant cryptocurrency networks through mechanisms that you specify, and you have full, independent control over how you decide to connect.',
+ ),
+ paragraph(
+ 'MAGIC Grants may run our own nodes for various cryptocurrency networks, but MAGIC Grants does not include our nodes or any default nodes in the App. Even in cases where you create transactions in the App that are processed or reviewed in any way by MAGIC Grants nodes (which is a standard function of nodes on a cryptocurrency network), you agree that MAGIC Grants is not a party to your transaction, this action does not create any binding liability for MAGIC Grants, and MAGIC Grants is not responsible for performing any service to you.',
+ ),
+ paragraph(
+ 'Cryptocurrencies exist only by virtue of the ownership record maintained in their respective networks. Any transfer of title in cryptocurrency occurs within a decentralized cryptocurrency network.',
),
sectionHeading('Restricted Use'),
@@ -108,13 +123,16 @@ class TermsOfService extends StatelessWidget {
sectionHeading('Third Party Services'),
paragraph(
- 'The App includes links to other sites and resources provided by third parties, and the App incorporates information provided by third parties for convenience. MAGIC Grants does not guarantee that these sites, resources, and information are accurate or fit for Your desired purpose. We have no control over the contents of those sites or resources, and accept no responsibility for them or for any loss or damage that may arise from your use of them. If you decide to access any of the third-party services linked in this App, you do so entirely at your own risk and subject to the terms and conditions of use for such Apps.',
+ 'The App includes links to other sites and resources provided by third parties, and the App incorporates information provided by third parties for convenience. MAGIC Grants does not guarantee that these sites, resources, and information are accurate or fit for Your desired purpose. We have no control over the contents of those sites or resources, and you agree that we have no responsibility for them or for any loss or damage that may arise from your use of them. If you decide to access any of the third-party services linked in this App, you do so entirely at your own risk and subject to the terms and conditions of use for such Apps.',
),
sectionHeading('Tax Matters'),
paragraph(
'The users of the App are solely responsible in determining what, if any, taxes apply to their cryptocurrency transactions. We are not responsible for determining any taxes that apply to such transactions.',
),
+ paragraph(
+ 'If you donate to MAGIC Grants, a 501(c)(3) public charity, your donation may qualify for a tax deduction. Additional paperwork, especially for cryptocurrency donations, may be required. If you choose to donate to MAGIC Grants as a part of any other activity in the App, such as any transaction you make that includes a donation to MAGIC Grants, you acknowledge that the sole involvement by MAGIC Grants for such transactions is to receive your donation, and that MAGIC Grants is not otherwise party to nor responsible for your transaction in any other way.',
+ ),
sectionHeading('Indemnification'),
paragraph(
diff --git a/lib/screens/wallet_home.dart b/lib/screens/wallet_home.dart
index 9fa81d0..d6241b4 100644
--- a/lib/screens/wallet_home.dart
+++ b/lib/screens/wallet_home.dart
@@ -228,11 +228,13 @@ class _BalanceHero extends StatelessWidget {
Text(coinText, style: _balanceStyle),
const SizedBox(height: 10),
if (showFiat) Text(coinText, style: _balanceSubStyle),
+ // Funds that have arrived but aren't spendable yet — Monero locks a
+ // receipt for ten blocks, so the unlocked balance above omits it.
if (lockedBalance > 0)
Padding(
padding: const EdgeInsets.only(top: 6),
child: Text(
- '+${lockedBalance.toStringAsFixed(12)} ${i18n.pending.toLowerCase()}',
+ '${i18n.pending}: +${_amountText(lockedBalance)} XMR',
style: _balanceSubStyle,
),
),
diff --git a/lib/screens/welcome.dart b/lib/screens/welcome.dart
index 8878d62..216a1ad 100644
--- a/lib/screens/welcome.dart
+++ b/lib/screens/welcome.dart
@@ -4,6 +4,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:skylight_wallet/l10n/app_localizations.dart';
import 'package:skylight_wallet/wallet_core_glue.dart';
import 'package:skylight_wallet/widgets/floating_bob.dart';
+import 'package:skylight_wallet/widgets/language_sheet.dart';
import 'package:skylight_wallet/widgets/ui/ui.dart';
class WelcomeScreen extends StatefulWidget {
@@ -46,6 +47,7 @@ class _WelcomeScreenState extends State<WelcomeScreen> {
onGetStarted: () => Navigator.pushNamed(context, '/tor_settings'),
onTerms: () => Navigator.pushNamed(context, '/terms_of_service'),
onPrivacy: () => Navigator.pushNamed(context, '/privacy_policy'),
+ onLanguage: () => showLanguageSheet(context),
);
}
}
diff --git a/lib/widgets/language_sheet.dart b/lib/widgets/language_sheet.dart
new file mode 100644
index 0000000..9890a98
--- /dev/null
+++ b/lib/widgets/language_sheet.dart
@@ -0,0 +1,37 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+import 'package:skylight_wallet/l10n/app_localizations.dart';
+import 'package:skylight_wallet/models/language_model.dart';
+import 'package:skylight_wallet/widgets/ui/ui.dart';
+
+/// Native + English display names for the supported locales.
+const languageNames = {'en': ('English', 'English'), 'pt': ('Português', 'Portuguese (Brazil)')};
+
+/// Opens the language picker — reachable from Settings and the welcome screen
+/// (welcome has no route to Settings, so a reader who can't read it can switch
+/// here before anything else).
+void showLanguageSheet(BuildContext context) {
+ final i18n = AppLocalizations.of(context)!;
+ final language = context.read<LanguageModel>();
+ showLanguagePickerSheet(
+ context,
+ labels: SettingsPickerLabels(
+ title: i18n.settingsLanguageLabel,
+ subtitle: i18n.settingsLanguageSheetSubtitle,
+ done: i18n.done,
+ ),
+ options: [
+ for (final locale in AppLocalizations.supportedLocales)
+ LanguagePickerOption(
+ code: locale.languageCode,
+ native: languageNames[locale.languageCode]?.$1 ?? locale.languageCode,
+ english: languageNames[locale.languageCode]?.$2 ?? '',
+ ),
+ ],
+ selected: language.language,
+ onSelect: language.setLanguage,
+ iconBg: BrandColors.surfaceTinted,
+ iconColor: BrandColors.primaryDeep,
+ );
+}
diff --git a/lib/widgets/tx_details.dart b/lib/widgets/tx_details.dart
index 3e3e24e..7fbc0c1 100644
--- a/lib/widgets/tx_details.dart
+++ b/lib/widgets/tx_details.dart
@@ -33,6 +33,8 @@ class TxDetailsDialog {
copyHint: i18n.txDetailsCopyHint,
failed: i18n.txDetailsFailed,
unknownStatus: i18n.txDetailsUnknownStatus,
+ receivedAt: i18n.txDetailsReceivedAtLabel,
+ unconfirmed: i18n.unconfirmed,
),
);
}
Why this scored 15/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.