From 22015eb1e19e314c35c5d5c2ee83be8507a17c66 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Fri, 31 Jul 2026 00:05:18 +0200 Subject: [PATCH 1/4] fix: add reconnection logic to hardware wallet flow to handle tap-backs (#3458) --- lib/buy/dfx/dfx_buy_provider.dart | 25 ++++++++++++------ lib/buy/robinhood/robinhood_buy_provider.dart | 23 ++++++++++------ lib/new-ui/pages/send_page.dart | 7 +++++ .../widgets/swap_page/swap_confirm_sheet.dart | 26 ++++++++++++------- 4 files changed, 56 insertions(+), 25 deletions(-) diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart index 104d66325..282ed7698 100644 --- a/lib/buy/dfx/dfx_buy_provider.dart +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -336,14 +336,23 @@ class DFXBuyProvider extends BuyProvider { String? countryCode}) async { if (wallet.isHardwareWallet) { if (!hardwareWalletVM!.isConnected(wallet.walletInfo.type)) { - await Navigator.of(context).pushNamed(Routes.connectDevices, - arguments: ConnectDevicePageParams( - walletType: wallet.walletInfo.type, - hardwareWalletType: wallet.walletInfo.hardwareWalletType!, - onConnectDevice: (context, hwwVM) { - hwwVM.initWallet(wallet); - Navigator.of(context).pop(); - })); + await Navigator.of(context).pushNamed( + Routes.connectDevices, + arguments: ConnectDevicePageParams( + walletType: wallet.walletInfo.type, + hardwareWalletType: wallet.walletInfo.hardwareWalletType!, + onConnectDevice: (context, hwwVM) { + hwwVM.initWallet(wallet); + Navigator.of(context).pop(); + }, + isReconnect: false, + ), + ); + + // Recheck to handle tap-backs + if (!hardwareWalletVM!.isConnected(wallet.walletInfo.type)) { + return; + } } else { hardwareWalletVM!.initWallet(wallet); } diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart index f56fc8c1b..651dc6b84 100644 --- a/lib/buy/robinhood/robinhood_buy_provider.dart +++ b/lib/buy/robinhood/robinhood_buy_provider.dart @@ -174,14 +174,21 @@ class RobinhoodBuyProvider extends BuyProvider { String? countryCode}) async { if (wallet.isHardwareWallet) { if (!hardwareWalletVM!.isConnected(wallet.walletInfo.type)) { - await Navigator.of(context).pushNamed(Routes.connectDevices, - arguments: ConnectDevicePageParams( - walletType: wallet.walletInfo.type, - hardwareWalletType: wallet.walletInfo.hardwareWalletType!, - onConnectDevice: (context, hwwVM) { - hwwVM.initWallet(wallet); - Navigator.of(context).pop(); - })); + await Navigator.of(context).pushNamed( + Routes.connectDevices, + arguments: ConnectDevicePageParams( + walletType: wallet.walletInfo.type, + hardwareWalletType: wallet.walletInfo.hardwareWalletType!, + onConnectDevice: (context, hwwVM) { + hwwVM.initWallet(wallet); + Navigator.of(context).pop(); + }, + isReconnect: false, + ), + ); + if (!hardwareWalletVM!.isConnected(wallet.walletInfo.type)) { + return; + } } else { hardwareWalletVM!.initWallet(wallet); } diff --git a/lib/new-ui/pages/send_page.dart b/lib/new-ui/pages/send_page.dart index 721e21840..7e386807c 100644 --- a/lib/new-ui/pages/send_page.dart +++ b/lib/new-ui/pages/send_page.dart @@ -823,8 +823,15 @@ class _NewSendPageState extends State { widget.sendViewModel.hardwareWalletViewModel!.initWallet(widget.sendViewModel.wallet); Navigator.of(context).pop(); }, + isReconnect: false, ), ); + + // Recheck to handle tap-backs + if (!widget.sendViewModel.hardwareWalletViewModel! + .isConnected(widget.sendViewModel.walletType)) { + return; + } } else { await widget.sendViewModel.hardwareWalletViewModel!.initWallet(widget.sendViewModel.wallet); } diff --git a/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart b/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart index 6a7f49900..c948d6b12 100644 --- a/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart +++ b/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart @@ -47,15 +47,23 @@ class _SwapConfirmSheetState extends State { if (sendVM.wallet.isHardwareWallet) { if (!sendVM.hardwareWalletViewModel!.isConnected(sendVM.walletType)) { - await Navigator.of(context).pushNamed(Routes.connectDevices, - arguments: ConnectDevicePageParams( - walletType: sendVM.walletType, - hardwareWalletType: sendVM.wallet.walletInfo.hardwareWalletType!, - onConnectDevice: (context, _) { - sendVM.hardwareWalletViewModel!.initWallet(sendVM.wallet); - Navigator.of(context).pop(); - }, - )); + await Navigator.of(context).pushNamed( + Routes.connectDevices, + arguments: ConnectDevicePageParams( + walletType: sendVM.walletType, + hardwareWalletType: sendVM.wallet.walletInfo.hardwareWalletType!, + onConnectDevice: (context, _) { + sendVM.hardwareWalletViewModel!.initWallet(sendVM.wallet); + Navigator.of(context).pop(); + }, + isReconnect: false, + ), + ); + + // Recheck to handle tap-backs + if (!sendVM.hardwareWalletViewModel!.isConnected(sendVM.walletType)) { + return; + } } else { sendVM.hardwareWalletViewModel!.initWallet(sendVM.wallet); } From b50ceb24629213cefd0e80b5bd1cbb79a673f0b4 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 01:01:52 +0200 Subject: [PATCH 2/4] Point changelog modal "View more info" at docs release notes (#3459) --- lib/new-ui/widgets/changelog_modal.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/new-ui/widgets/changelog_modal.dart b/lib/new-ui/widgets/changelog_modal.dart index 67599619d..1778b7cdc 100644 --- a/lib/new-ui/widgets/changelog_modal.dart +++ b/lib/new-ui/widgets/changelog_modal.dart @@ -107,7 +107,7 @@ class _ChangelogModalState extends State { NewPrimaryButton( onPressed: () { try { - launchUrl(Uri.https("blog.cakewallet.com")); + launchUrl(Uri.https("docs.cakewallet.com", "/release-notes")); } catch (_) {} }, text: S.of(context).view_more_info, From f6566e3bafcd8d6cc29caf072eb3db98bce1273d Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 09:43:46 +0200 Subject: [PATCH 3/4] Enforce passphrase confirmation match on the restore and create wallet flows (#3474) * fix: enforce passphrase confirmation in restore passphrase bottom sheet The confirm-passphrase field in AddPassphraseBottomSheet already had a validator comparing it to the first field, but the fields were not inside a Form and nothing ever called validate(), so the validator never ran. Tapping "Restore" restored the wallet with the first field's value even when the confirmation did not match, defeating the typo check. Wrap both fields in a Form and validate it before completing the restore, matching the existing pattern in advanced_privacy_settings_page.dart. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_019DtLNfYw1H81p7zZrkNDXM * Validate passphrase confirmation when the passphrase field is empty --------- Co-authored-by: Claude --- .../advanced_privacy_settings_page.dart | 8 +- .../add_passphrase_bottom_sheet_widget.dart | 103 ++++++++++-------- 2 files changed, 62 insertions(+), 49 deletions(-) diff --git a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart index 76683bb3b..2f190c857 100644 --- a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart +++ b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart @@ -360,11 +360,9 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo widget.nodeViewModel.save(); } - if (passphraseController.text.isNotEmpty) { - if (_passphraseFormKey.currentState != null && - !_passphraseFormKey.currentState!.validate()) { - return; - } + if (_passphraseFormKey.currentState != null && + !_passphraseFormKey.currentState!.validate()) { + return; } widget.seedTypeViewModel.setPassphrase(passphraseController.text); diff --git a/lib/src/widgets/bottom_sheet/add_passphrase_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/add_passphrase_bottom_sheet_widget.dart index dde80d139..c1d1f1dd4 100644 --- a/lib/src/widgets/bottom_sheet/add_passphrase_bottom_sheet_widget.dart +++ b/lib/src/widgets/bottom_sheet/add_passphrase_bottom_sheet_widget.dart @@ -20,6 +20,7 @@ class AddPassphraseBottomSheet extends StatefulWidget { class _AddPassphraseBottomSheetState extends State { late final TextEditingController passphraseController; late final TextEditingController confirmPassphraseController; + final _passphraseFormKey = GlobalKey(); @override void initState() { @@ -112,51 +113,60 @@ class _AddPassphraseBottomSheetState extends State { ), ), SizedBox(height: 24), - BaseTextFormField( - key: ValueKey('add_passphrase_bottom_sheet_widget_passphrase_textfield_key'), - controller: passphraseController, - obscureText: obscurePassphrase, - contentPadding: EdgeInsets.symmetric(horizontal: 12), - hintText: S.of(context).required_passphrase, - suffixIcon: GestureDetector( - onTap: () { - setState(() { - obscurePassphrase = !obscurePassphrase; - }); - }, - child: Icon( - obscurePassphrase ? Icons.visibility_off : Icons.visibility, - size: 24, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), - ), - ), - ), - SizedBox(height: 8), - BaseTextFormField( - key: ValueKey('add_passphrase_bottom_sheet_widget_confirm_passphrase_textfield_key'), - controller: confirmPassphraseController, - obscureText: obscurePassphrase, - contentPadding: EdgeInsets.symmetric(horizontal: 12), - hintText: S.of(context).confirm_passphrase, - suffixIcon: GestureDetector( - onTap: () { - setState(() { - obscurePassphrase = !obscurePassphrase; - }); - }, - child: Icon( - obscurePassphrase ? Icons.visibility_off : Icons.visibility, - size: 24, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), - ), - ), - validator: (text) { - if (text == passphraseController.text) { - return null; - } + Form( + key: _passphraseFormKey, + child: Column( + children: [ + BaseTextFormField( + key: ValueKey('add_passphrase_bottom_sheet_widget_passphrase_textfield_key'), + controller: passphraseController, + obscureText: obscurePassphrase, + contentPadding: EdgeInsets.symmetric(horizontal: 12), + hintText: S.of(context).required_passphrase, + suffixIcon: GestureDetector( + onTap: () { + setState(() { + obscurePassphrase = !obscurePassphrase; + }); + }, + child: Icon( + obscurePassphrase ? Icons.visibility_off : Icons.visibility, + size: 24, + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + ), + ), + ), + SizedBox(height: 8), + BaseTextFormField( + key: ValueKey( + 'add_passphrase_bottom_sheet_widget_confirm_passphrase_textfield_key', + ), + controller: confirmPassphraseController, + obscureText: obscurePassphrase, + contentPadding: EdgeInsets.symmetric(horizontal: 12), + hintText: S.of(context).confirm_passphrase, + suffixIcon: GestureDetector( + onTap: () { + setState(() { + obscurePassphrase = !obscurePassphrase; + }); + }, + child: Icon( + obscurePassphrase ? Icons.visibility_off : Icons.visibility, + size: 24, + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + ), + ), + validator: (text) { + if (text == passphraseController.text) { + return null; + } - return S.of(context).passphrases_doesnt_match; - }, + return S.of(context).passphrases_doesnt_match; + }, + ), + ], + ), ), SizedBox(height: 16), Padding( @@ -184,6 +194,11 @@ class _AddPassphraseBottomSheetState extends State { child: PrimaryButton( key: ValueKey('add_passphrase_bottom_sheet_widget_restore_button_key'), onPressed: () { + if (_passphraseFormKey.currentState != null && + !_passphraseFormKey.currentState!.validate()) { + return; + } + Navigator.pop(context); widget.onRestoreButtonPressed(passphraseController.text); }, From 26a16a4eef7c9976720d28b81c7a278ac55a1ee6 Mon Sep 17 00:00:00 2001 From: Seth For Privacy <40500387+sethforprivacy@users.noreply.github.com> Date: Sun, 2 Aug 2026 03:50:26 -0400 Subject: [PATCH 4/4] a11y: expose ModernButton, CoinActionButton and ModalTopBar as single labeled button nodes (#3463) * Give ModernButton and CoinActionButton one labeled a11y node ModernButton, CoinActionButton and the ModalTopBar chrome buttons were icon-only IconButtons with no tooltip and a detached caption sibling, so screen readers announced an unnamed "button" plus unrelated static text. - Add optional semanticLabel to ModernButton; wrap the control in MergeSemantics + Semantics(button: true, label: semanticLabel ?? label) and exclude both the icon and the visible caption so exactly one node is exposed. No visual or pointer change. - Give CoinActionButton the same treatment, reusing the label callers already pass (dashboard Send / Receive / Swap / Scan). - Add leadingSemanticLabel / trailingSemanticLabel to ModalTopBar, defaulting the leading button to "Close", and mark a non-empty title as a semantics header. - Migrate every ModernButton / ModalTopBar call site to pass a localized label, and drop the now-redundant external Semantics wrapper in base_page.dart so the back button is not labeled twice. - Localize the hard-coded "Settings" modal title in settings_page.dart. New English keys: rotate_address, swap_reverse_direction, switch_camera, switch_input_currency, test_node_speeds, turn_flash_off, turn_flash_on. * Require explicit accessible names on ModalTopBar and ModernButton icons ModalTopBar defaulted every leading icon's accessible name to "Close", so the ~40 callers that pass a back arrow announced the wrong action, and trailingSemanticLabel was optional, leaving the trailing icon controls unnamed. - Drop the "?? close" fallback in ModalTopBar: the leading ModernButton now gets exactly leadingSemanticLabel, and constructor asserts require a non-empty label whenever leadingIcon / trailingIcon is supplied. Callers that pass leadingWidget / trailingWidget instead are unaffected, and a null icon still builds no button at all. - Assert in both ModernButton constructors that the control has a name: either semanticLabel or a non-empty visible label. - Pass the matching localized label at every ModalTopBar call site: "Go back" for back arrows, "Close" for close icons, and the action name for trailing icons (History, Configure, Export CSV, Save, Scan, Close). - Label the last unnamed ModernButton (wallet accounts, wallet_info.dart). No new localization keys; all labels reuse existing strings_en.arb keys. * fix(a11y): put ModalTopBar header flag on the title Text node On-device audit (CW-1574, defect D2) found zero [heading] nodes in the Android accessibility tree on the Receive page: Semantics(header:) wrapped the AnimatedSwitcher rather than the Text inside it, so the flag never landed on the node that carries the title label. Move the header semantics inside the AnimatedSwitcher, directly around the Text. The ValueKey(title) moves with it onto the Semantics wrapper, since AnimatedSwitcher switches on its direct child's key -- keying the Semantics by title preserves the existing switch/animation behavior exactly. No test or robot resolves the title ValueKey through a Text-typed finder, so the key relocation is not observable from the test suites on this branch. * Set headingLevel so Android surfaces modal titles as headings The Flutter 3.41 engine drives AccessibilityNodeInfo.setHeading from headingLevel, not the IS_HEADER flag (which 3.35 used). Proven by an on-device probe: header:-only headings print on a 3.35-engine build and vanish on the 3.41-engine CI build. --- lib/new-ui/pages/about_page.dart | 1 + lib/new-ui/pages/account_customizer.dart | 2 + lib/new-ui/pages/addresses_page.dart | 1 + .../pages/bridge/bridge_amount_page.dart | 3 + .../pages/bridge/bridge_confirm_sheet.dart | 1 + .../pages/bridge/bridge_detail_page.dart | 1 + .../pages/bridge/bridge_history_page.dart | 2 + .../pages/bridge/bridge_network_page.dart | 2 + .../bridge_receive_address_input_page.dart | 1 + .../bridge/bridge_receiving_wallet_page.dart | 2 + lib/new-ui/pages/card_customizer.dart | 1 + lib/new-ui/pages/lightning_username_page.dart | 1 + lib/new-ui/pages/receive_page.dart | 4 + lib/new-ui/pages/scan_page.dart | 6 ++ lib/new-ui/pages/send_page.dart | 5 ++ lib/new-ui/pages/settings_page.dart | 3 +- lib/new-ui/pages/swap_page.dart | 6 ++ .../action_row/coin_action_button.dart | 88 +++++++++++-------- .../assets_history/asset_details_modal.dart | 28 +++--- .../assets_history/history_filters_page.dart | 1 + .../assets_history/history_modal.dart | 2 + .../history_swap_providers_page.dart | 1 + .../transaction_details_modal.dart | 1 + .../coins_page/top_bar_widget/top_bar.dart | 2 + .../unconfirmed_balance_widget.dart | 1 + .../widgets/coins_page/wallet_info.dart | 2 + .../currency_picker_sheet.dart | 1 + .../fiat_currency_picker_sheet.dart | 1 + .../currency_picker/select_network_page.dart | 1 + .../proceed_on_device_sheet.dart | 2 + .../hardware_wallet/trezor_paring_sheet.dart | 1 + lib/new-ui/widgets/modern_button.dart | 78 ++++++++++------ lib/new-ui/widgets/picker.dart | 3 + .../receive_page/payjoin_copy_modal.dart | 1 + .../receive_address_type_selector.dart | 1 + .../receive_page/receive_amount_modal.dart | 1 + .../receive_page/receive_label_modal.dart | 1 + .../widgets/receive_page/receive_top_bar.dart | 34 ++++++- .../widgets/scan_page/network_list.dart | 1 + .../widgets/send_page/fiat_amount_bar.dart | 1 + .../send_page/l2_action_wallet_selector.dart | 1 + .../send_page/l2_send_external_modal.dart | 1 + .../widgets/send_page/send_confirm_sheet.dart | 1 + .../swap_page/provider_options_page.dart | 1 + .../swap_page/provider_selector_page.dart | 1 + .../swap_page/refund_address_modal.dart | 1 + .../swap_address_selection_modal.dart | 1 + .../widgets/swap_page/swap_confirm_sheet.dart | 1 + .../widgets/swap_page/swap_options_page.dart | 1 + .../swap_page/swap_send_external_modal.dart | 1 + .../trocador_providers_settings.dart | 1 + .../widgets/wallet_deprecation_popup.dart | 2 + lib/src/screens/auth/auth_page.dart | 1 + .../backup/edit_backup_password_page.dart | 1 + lib/src/screens/base_page.dart | 24 +++-- ...nero_hardware_wallet_passphrase_input.dart | 1 + .../dashboard/favorite_token_modal.dart | 1 + lib/src/screens/dashboard/sign_page.dart | 1 + .../nodes/node_create_or_edit_page.dart | 2 + lib/src/screens/nodes/node_share_page.dart | 1 + .../nodes/pow_node_create_or_edit_page.dart | 2 + .../screens/receive/address_list_page.dart | 1 + lib/src/screens/rescan/rescan_page.dart | 1 + .../settings/connection_sync_page.dart | 1 + .../settings/display_settings_page.dart | 1 + .../screens/settings/manage_nodes_page.dart | 3 + lib/src/screens/settings/mweb_node_page.dart | 1 + .../screens/settings/other_settings_page.dart | 1 + lib/src/screens/settings/privacy_page.dart | 1 + .../settings/security_backup_page.dart | 1 + .../settings/silent_payments_settings.dart | 1 + .../setup_2fa/setup_2fa_enter_code_page.dart | 1 + .../support_chat/support_chat_page.dart | 1 + .../wc_connections_listing_view.dart | 1 + res/values/strings_en.arb | 7 ++ 75 files changed, 267 insertions(+), 97 deletions(-) diff --git a/lib/new-ui/pages/about_page.dart b/lib/new-ui/pages/about_page.dart index 533184eac..766ae2399 100644 --- a/lib/new-ui/pages/about_page.dart +++ b/lib/new-ui/pages/about_page.dart @@ -56,6 +56,7 @@ class _AboutPageState extends State { topBar: ModalTopBar( title: S.of(context).about, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), content: Container( diff --git a/lib/new-ui/pages/account_customizer.dart b/lib/new-ui/pages/account_customizer.dart index 15aa1f0c5..8ebaf4c20 100644 --- a/lib/new-ui/pages/account_customizer.dart +++ b/lib/new-ui/pages/account_customizer.dart @@ -128,8 +128,10 @@ class _AccountCustomizerState extends State { ModalTopBar( title: S.of(context).wallet_accounts, leadingIcon: Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: Navigator.of(context).maybePop, trailingIcon: Icon(Icons.refresh), + trailingSemanticLabel: S.of(context).reset, onTrailingPressed: showResetDialog, ), Padding( diff --git a/lib/new-ui/pages/addresses_page.dart b/lib/new-ui/pages/addresses_page.dart index 62075e23b..e75dc16fe 100644 --- a/lib/new-ui/pages/addresses_page.dart +++ b/lib/new-ui/pages/addresses_page.dart @@ -105,6 +105,7 @@ class _NewAddressesPageState extends State { ModalTopBar( title: widget.showHidden ? S.of(context).hidden_addresses : S.of(context).addresses, leadingIcon: Icon(Icons.arrow_back), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).pop, onTrailingPressed: () {}), Expanded( diff --git a/lib/new-ui/pages/bridge/bridge_amount_page.dart b/lib/new-ui/pages/bridge/bridge_amount_page.dart index 757a9579e..d3255c1a9 100644 --- a/lib/new-ui/pages/bridge/bridge_amount_page.dart +++ b/lib/new-ui/pages/bridge/bridge_amount_page.dart @@ -116,8 +116,10 @@ class _BridgeAmountPageState extends State { ModalTopBar( title: S.of(context).enter_amount, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: () => Navigator.of(context, rootNavigator: true).pop(), trailingIcon: Icon(Icons.history), + trailingSemanticLabel: S.of(context).history, onTrailingPressed: () { Navigator.pushNamed(context, Routes.bridgeHistoryPage, arguments: widget.bridgeHistoryViewModel); @@ -274,6 +276,7 @@ class _BridgeAmountPageState extends State { backgroundColor: theme.colorScheme.primary, iconColor: theme.colorScheme.onPrimary, icon: Icon(Icons.arrow_forward, size: 20), + semanticLabel: S.of(context).continue_text, onPressed: () { Navigator.pushNamed( context, diff --git a/lib/new-ui/pages/bridge/bridge_confirm_sheet.dart b/lib/new-ui/pages/bridge/bridge_confirm_sheet.dart index 290666d74..4ccbfb4e7 100644 --- a/lib/new-ui/pages/bridge/bridge_confirm_sheet.dart +++ b/lib/new-ui/pages/bridge/bridge_confirm_sheet.dart @@ -87,6 +87,7 @@ class _BridgeConfirmSheetState extends State { ], ), trailingIcon: const Icon(Icons.close), + trailingSemanticLabel: S.of(context).close, onTrailingPressed: () => Navigator.of(context).maybePop(), ); }, diff --git a/lib/new-ui/pages/bridge/bridge_detail_page.dart b/lib/new-ui/pages/bridge/bridge_detail_page.dart index 64cc457fd..d29602bc3 100644 --- a/lib/new-ui/pages/bridge/bridge_detail_page.dart +++ b/lib/new-ui/pages/bridge/bridge_detail_page.dart @@ -46,6 +46,7 @@ class BridgeDetailPage extends StatelessWidget { ModalTopBar( title: "Bridge Transfer Details", leadingIcon: const Icon(Icons.arrow_back_ios_new, size: 18), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: () => Navigator.of(context).pop(), ), Expanded( diff --git a/lib/new-ui/pages/bridge/bridge_history_page.dart b/lib/new-ui/pages/bridge/bridge_history_page.dart index 5dff00f31..49594850d 100644 --- a/lib/new-ui/pages/bridge/bridge_history_page.dart +++ b/lib/new-ui/pages/bridge/bridge_history_page.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/new-ui/pages/bridge/bridge_detail_page.dart'; import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; import 'package:cake_wallet/view_model/bridge/bridge_history_view_model.dart'; @@ -25,6 +26,7 @@ class BridgeHistoryPage extends StatelessWidget { ModalTopBar( title: "Bridge history", leadingIcon: const Icon(Icons.arrow_back_ios_new, size: 18), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: () => Navigator.of(context).pop(), ), Expanded( diff --git a/lib/new-ui/pages/bridge/bridge_network_page.dart b/lib/new-ui/pages/bridge/bridge_network_page.dart index 05f1c3440..b0df5270b 100644 --- a/lib/new-ui/pages/bridge/bridge_network_page.dart +++ b/lib/new-ui/pages/bridge/bridge_network_page.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart'; import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; import 'package:cake_wallet/routes.dart'; @@ -30,6 +31,7 @@ class BridgeNetworkPage extends StatelessWidget { ModalTopBar( title: 'Destination Network', leadingIcon: const Icon(Icons.arrow_back_ios_new, size: 18), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: () => Navigator.of(context).pop(), ), SizedBox(height: 48), diff --git a/lib/new-ui/pages/bridge/bridge_receive_address_input_page.dart b/lib/new-ui/pages/bridge/bridge_receive_address_input_page.dart index dfecf82d6..ee21669a4 100644 --- a/lib/new-ui/pages/bridge/bridge_receive_address_input_page.dart +++ b/lib/new-ui/pages/bridge/bridge_receive_address_input_page.dart @@ -55,6 +55,7 @@ class _BridgeReceiveAddressInputPageState extends State Navigator.of(context).pop(), ), SizedBox(height: 48), diff --git a/lib/new-ui/pages/bridge/bridge_receiving_wallet_page.dart b/lib/new-ui/pages/bridge/bridge_receiving_wallet_page.dart index 16872b511..6b4f3f0e6 100644 --- a/lib/new-ui/pages/bridge/bridge_receiving_wallet_page.dart +++ b/lib/new-ui/pages/bridge/bridge_receiving_wallet_page.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/view_model/bridge/bridge_receiving_wallet_option.dart'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/new-ui/pages/bridge/bridge_confirm_sheet.dart'; import 'package:cake_wallet/new-ui/pages/bridge/bridge_receive_address_input_page.dart'; import 'package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart'; @@ -42,6 +43,7 @@ class _BridgeReceivingWalletPageState extends State { ModalTopBar( title: 'Receiving Wallet', leadingIcon: const Icon(Icons.arrow_back_ios_new, size: 18), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: () => Navigator.of(context).pop(), ), const SizedBox(height: 48), diff --git a/lib/new-ui/pages/card_customizer.dart b/lib/new-ui/pages/card_customizer.dart index 9b2249d4f..9973ec265 100644 --- a/lib/new-ui/pages/card_customizer.dart +++ b/lib/new-ui/pages/card_customizer.dart @@ -84,6 +84,7 @@ class _CardCustomizerState extends State { ModalTopBar( title: editEnabled ? S.of(context).edit_account : S.of(context).edit_card, leadingIcon: Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, // trailingIcon: editEnabled ? Icon(Icons.delete_forever) : null, onLeadingPressed: () => Navigator.of(context).maybePop(), // onTrailingPressed: () {}, diff --git a/lib/new-ui/pages/lightning_username_page.dart b/lib/new-ui/pages/lightning_username_page.dart index 38c801443..c42c19f0b 100644 --- a/lib/new-ui/pages/lightning_username_page.dart +++ b/lib/new-ui/pages/lightning_username_page.dart @@ -68,6 +68,7 @@ class _LightningUsernamePageState extends State { ModalTopBar( title: "Lightning ${S.of(context).username}", leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), Expanded( diff --git a/lib/new-ui/pages/receive_page.dart b/lib/new-ui/pages/receive_page.dart index 4b7846c03..e68a09305 100644 --- a/lib/new-ui/pages/receive_page.dart +++ b/lib/new-ui/pages/receive_page.dart @@ -201,6 +201,7 @@ class _NewReceivePageState extends State { ModalTopBar( title: _largeQrMode ? "" : S.of(context).receive, leadingIcon: const Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: () { Navigator.of(context, rootNavigator: true).pop(); }, @@ -223,6 +224,9 @@ class _NewReceivePageState extends State { : widget.addressListViewModel.isRotatingAddress ? const CupertinoActivityIndicator() : const Icon(Icons.refresh), + semanticLabel: _largeQrMode + ? S.of(context).share_address + : S.of(context).rotate_address, onPressed: () { if (_largeQrMode) { ShareUtil.share( diff --git a/lib/new-ui/pages/scan_page.dart b/lib/new-ui/pages/scan_page.dart index 16dcb38bd..695382be1 100644 --- a/lib/new-ui/pages/scan_page.dart +++ b/lib/new-ui/pages/scan_page.dart @@ -163,6 +163,7 @@ class _ScanPageState extends State { }, iconColor: buttonIconColor, backgroundColor: buttonColor, + semanticLabel: S.of(context).switch_camera, ), ModernButton( size: 36, @@ -171,6 +172,10 @@ class _ScanPageState extends State { (controller.value.torchState == TorchState.on || _frontFlashMode) ? Icons.flash_off_outlined : Icons.flash_on), + semanticLabel: + (controller.value.torchState == TorchState.on || _frontFlashMode) + ? S.of(context).turn_flash_off + : S.of(context).turn_flash_on, onPressed: () { if (controller.value.cameraDirection == CameraFacing.front) { setState(() { @@ -194,6 +199,7 @@ class _ScanPageState extends State { size: 36, iconSize: 18, icon: Icon(Icons.arrow_back_ios_new), + semanticLabel: S.of(context).seed_alert_back, onPressed: () { if (_textInputMode) { setState(() { diff --git a/lib/new-ui/pages/send_page.dart b/lib/new-ui/pages/send_page.dart index 7e386807c..f03381734 100644 --- a/lib/new-ui/pages/send_page.dart +++ b/lib/new-ui/pages/send_page.dart @@ -328,6 +328,7 @@ class _NewSendPageState extends State { title: widget.mode.title, subtitle: widget.mode.description, leadingIcon: const Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: Navigator.of(context, rootNavigator: true).pop, trailingWidget: Observer( builder: (_) => Row( @@ -343,6 +344,7 @@ class _NewSendPageState extends State { BlendMode.srcIn, ), ), + semanticLabel: S.of(context).remove, onPressed: () { final outputIndex = _selectedOutput; if (_selectedOutput != 0) { @@ -362,6 +364,7 @@ class _NewSendPageState extends State { ModernButton( size: 36, icon: const Icon(Icons.add), + semanticLabel: S.of(context).add_receiver, onPressed: () { _addInputControllers(); widget.sendViewModel.addOutput(); @@ -378,6 +381,7 @@ class _NewSendPageState extends State { BlendMode.srcIn, ), ), + semanticLabel: S.of(context).help, onPressed: () { Navigator.of(context).push( CupertinoPageRoute( @@ -1492,6 +1496,7 @@ class SendHelpPage extends StatelessWidget { ModalTopBar( title: content.title, leadingIcon: const Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), Padding( diff --git a/lib/new-ui/pages/settings_page.dart b/lib/new-ui/pages/settings_page.dart index f7b8d4c79..2183ec5d4 100644 --- a/lib/new-ui/pages/settings_page.dart +++ b/lib/new-ui/pages/settings_page.dart @@ -152,8 +152,9 @@ class SettingsMainPage extends StatelessWidget { color: Theme.of(context).colorScheme.surface, child: Column(children: [ ModalTopBar( - title: "Settings", + title: S.of(context).settings_title, leadingIcon: Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: Navigator.of(context, rootNavigator: true).pop, onTrailingPressed: () {}, ), diff --git a/lib/new-ui/pages/swap_page.dart b/lib/new-ui/pages/swap_page.dart index 4f82a4eb1..6d3d7049a 100644 --- a/lib/new-ui/pages/swap_page.dart +++ b/lib/new-ui/pages/swap_page.dart @@ -472,12 +472,14 @@ class _NewSwapPageState extends State { ModalTopBar( title: S.of(context).swap, leadingIcon: Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: Navigator.of(context).maybePop, trailingIcon: CakeImageWidget( imageUrl: "assets/new-ui/options.svg", colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn), ), + trailingSemanticLabel: S.of(context).configure, onTrailingPressed: () { Navigator.of(context).push(CupertinoPageRoute( builder: (context) => Material( @@ -576,6 +578,7 @@ class _NewSwapPageState extends State { iconSize: 24, svgPath: "assets/new-ui/swap_amounts.svg", onPressed: widget.exchangeViewModel.reverseSwapDirection, + semanticLabel: S.of(context).swap_reverse_direction, ), ], ), @@ -1174,6 +1177,7 @@ class SwapAmountBoxState extends State { size: 36, iconSize: 18, backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest, + semanticLabel: S.of(context).refund_address, ), if (widget.isReceiverCard && widget.exchangeViewModel.receiveAddress.isEmpty) ...[ @@ -1183,6 +1187,7 @@ class SwapAmountBoxState extends State { size: 36, iconSize: 20, backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest, + semanticLabel: S.of(context).paste, ), ModernButton.svg( svgPath: "assets/new-ui/scan.svg", @@ -1190,6 +1195,7 @@ class SwapAmountBoxState extends State { size: 36, iconSize: 20, backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest, + semanticLabel: S.of(context).scan, ), ] ], diff --git a/lib/new-ui/widgets/coins_page/action_row/coin_action_button.dart b/lib/new-ui/widgets/coins_page/action_row/coin_action_button.dart index 4c02acfdf..cb2ff1015 100644 --- a/lib/new-ui/widgets/coins_page/action_row/coin_action_button.dart +++ b/lib/new-ui/widgets/coins_page/action_row/coin_action_button.dart @@ -23,47 +23,57 @@ class CoinActionButton extends StatelessWidget { final size = MediaQuery.of(context).size.width * sizeFactor; final double effectiveSize = min(size, 80); - return Column( - children: [ - Container( - width: effectiveSize, - height: effectiveSize, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: LinearGradient( - colors: [ - context.customColors.cardGradientColorPrimary, - context.customColors.cardGradientColorSecondary - ], - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - ), - border: Border.all( - color: Theme.of(context).colorScheme.surfaceContainerHigh, - width: 1, - ), - ), - child: IconButton( - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - onPressed: () { - HapticFeedback.mediumImpact(); - action(); - }, - icon: icon, - color: Theme.of(context).colorScheme.primary, - ), - ), - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, + // The caption below the circle is the accessible name, so it is excluded + // from the semantics tree to keep this a single button node. + return MergeSemantics( + child: Semantics( + button: true, + label: label, + child: Column( + children: [ + Container( + width: effectiveSize, + height: effectiveSize, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + colors: [ + context.customColors.cardGradientColorPrimary, + context.customColors.cardGradientColorSecondary + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, ), - label, - ), + border: Border.all( + color: Theme.of(context).colorScheme.surfaceContainerHigh, + width: 1, + ), + ), + child: IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + onPressed: () { + HapticFeedback.mediumImpact(); + action(); + }, + icon: ExcludeSemantics(child: icon), + color: Theme.of(context).colorScheme.primary, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: ExcludeSemantics( + child: Text( + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + label, + ), + ), + ), + ], ), - ], + ), ); } } diff --git a/lib/new-ui/widgets/coins_page/assets_history/asset_details_modal.dart b/lib/new-ui/widgets/coins_page/assets_history/asset_details_modal.dart index 3bb7084af..0ee643cf1 100644 --- a/lib/new-ui/widgets/coins_page/assets_history/asset_details_modal.dart +++ b/lib/new-ui/widgets/coins_page/assets_history/asset_details_modal.dart @@ -62,6 +62,7 @@ class AssetDetailsModal extends StatelessWidget { ModalTopBar( title: "", trailingIcon: Icon(Icons.close), + trailingSemanticLabel: S.of(context).close, onTrailingPressed: Navigator.of(context).pop, padding: EdgeInsets.only(top: 12, right: 18), ), @@ -356,17 +357,22 @@ class AssetDetailsModalBottomButton extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - spacing: 8, - children: [ - ModernButton.svg( - onPressed: onPressed, - svgPath: iconPath, - size: 60, - iconSize: 32, - ), - Text(title) - ], + // The caption is the button's accessible name, so it is excluded from the + // semantics tree to keep the whole control a single button node. + return MergeSemantics( + child: Column( + spacing: 8, + children: [ + ModernButton.svg( + onPressed: onPressed, + svgPath: iconPath, + size: 60, + iconSize: 32, + semanticLabel: title, + ), + ExcludeSemantics(child: Text(title)) + ], + ), ); } } diff --git a/lib/new-ui/widgets/coins_page/assets_history/history_filters_page.dart b/lib/new-ui/widgets/coins_page/assets_history/history_filters_page.dart index 97c9260c0..383ced531 100644 --- a/lib/new-ui/widgets/coins_page/assets_history/history_filters_page.dart +++ b/lib/new-ui/widgets/coins_page/assets_history/history_filters_page.dart @@ -27,6 +27,7 @@ class HistoryFiltersPage extends StatelessWidget { ModalTopBar( title: S.of(context).filters, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), Expanded( diff --git a/lib/new-ui/widgets/coins_page/assets_history/history_modal.dart b/lib/new-ui/widgets/coins_page/assets_history/history_modal.dart index c35779974..913266746 100644 --- a/lib/new-ui/widgets/coins_page/assets_history/history_modal.dart +++ b/lib/new-ui/widgets/coins_page/assets_history/history_modal.dart @@ -25,11 +25,13 @@ class HistoryModal extends StatelessWidget { ModalTopBar( title: S.of(context).history, leadingIcon: Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: Navigator.of(context).maybePop, trailingIcon: CakeImageWidget( imageUrl: "assets/new-ui/tx_export.svg", colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn), ), + trailingSemanticLabel: S.of(context).export_csv, onTrailingPressed: () => CsvExportService().exportToCsv(dashboardViewModel.items, context), ), diff --git a/lib/new-ui/widgets/coins_page/assets_history/history_swap_providers_page.dart b/lib/new-ui/widgets/coins_page/assets_history/history_swap_providers_page.dart index 16051f30a..d58773f69 100644 --- a/lib/new-ui/widgets/coins_page/assets_history/history_swap_providers_page.dart +++ b/lib/new-ui/widgets/coins_page/assets_history/history_swap_providers_page.dart @@ -24,6 +24,7 @@ class HistorySwapProvidersPage extends StatelessWidget { ModalTopBar( title: S.of(context).filter_swap_providers, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), Expanded( diff --git a/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart b/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart index fb705ace6..4002d236a 100644 --- a/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart +++ b/lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart @@ -68,6 +68,7 @@ class _TransactionDetailsModalState extends State { ModalTopBar( title: S.of(context).transaction, leadingIcon: const Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: Navigator.of(context).pop, ), Expanded( diff --git a/lib/new-ui/widgets/coins_page/top_bar_widget/top_bar.dart b/lib/new-ui/widgets/coins_page/top_bar_widget/top_bar.dart index de08f70bd..71627f9d9 100644 --- a/lib/new-ui/widgets/coins_page/top_bar_widget/top_bar.dart +++ b/lib/new-ui/widgets/coins_page/top_bar_widget/top_bar.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/chain_icon.dart'; import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/lightning_switcher.dart'; import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/sync_bar.dart'; @@ -53,6 +54,7 @@ class TopBar extends StatelessWidget { onSettingsButtonPress(); }, svgPath: "assets/new-ui/top-settings.svg", + semanticLabel: S.of(context).settings_title, ), ], ), diff --git a/lib/new-ui/widgets/coins_page/unconfirmed_balance_widget.dart b/lib/new-ui/widgets/coins_page/unconfirmed_balance_widget.dart index 5420d6a3f..461b8167a 100644 --- a/lib/new-ui/widgets/coins_page/unconfirmed_balance_widget.dart +++ b/lib/new-ui/widgets/coins_page/unconfirmed_balance_widget.dart @@ -125,6 +125,7 @@ class UnconfirmedBalanceModal extends StatelessWidget { ModalTopBar( title: S.of(context).balance_confirmation, leadingIcon: Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: Navigator.of(context).pop), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), diff --git a/lib/new-ui/widgets/coins_page/wallet_info.dart b/lib/new-ui/widgets/coins_page/wallet_info.dart index 212f57798..fa77237ee 100644 --- a/lib/new-ui/widgets/coins_page/wallet_info.dart +++ b/lib/new-ui/widgets/coins_page/wallet_info.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/new-ui/widgets/modern_button.dart'; import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cw_core/wallet_info.dart'; @@ -76,6 +77,7 @@ class WalletInfoBar extends StatelessWidget { } }, svgPath: "assets/new-ui/icon-accounts.svg", + semanticLabel: S.of(context).wallet_accounts, ) ] ], diff --git a/lib/new-ui/widgets/currency_picker/currency_picker_sheet.dart b/lib/new-ui/widgets/currency_picker/currency_picker_sheet.dart index be6600d52..a59b8d71b 100644 --- a/lib/new-ui/widgets/currency_picker/currency_picker_sheet.dart +++ b/lib/new-ui/widgets/currency_picker/currency_picker_sheet.dart @@ -43,6 +43,7 @@ class CurrencyPickerSheet extends StatelessWidget { ModalTopBar( title: S.of(context).select_asset, leadingIcon: const Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: () => Navigator.of(context).maybePop(), ), Expanded( diff --git a/lib/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart b/lib/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart index 47dba9f24..c4713708d 100644 --- a/lib/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart +++ b/lib/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart @@ -117,6 +117,7 @@ class _FiatCurrencyPickerSheetState extends State { ModalTopBar( title: S.of(context).select_fiat_currency_title, leadingIcon: const Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: () => Navigator.of(context).maybePop(), ), Expanded( diff --git a/lib/new-ui/widgets/currency_picker/select_network_page.dart b/lib/new-ui/widgets/currency_picker/select_network_page.dart index 988dc3b24..29f18af48 100644 --- a/lib/new-ui/widgets/currency_picker/select_network_page.dart +++ b/lib/new-ui/widgets/currency_picker/select_network_page.dart @@ -35,6 +35,7 @@ class SelectNetworkPage extends StatelessWidget { ModalTopBar( title: S.of(context).select_network_title, leadingIcon: const Icon(Icons.arrow_back), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: () => Navigator.of(context).maybePop(), ), const SizedBox(height: 24), diff --git a/lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart b/lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart index 2a06f37e6..b68a4011b 100644 --- a/lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart +++ b/lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart @@ -106,6 +106,7 @@ class _HardwareWalletProceedOnDeviceSheetState extends State showPopUp( context: context, builder: (context) => AlertWithTwoActions( @@ -143,6 +144,7 @@ class _HardwareWalletProceedOnDeviceSheetState extends State widget.trezorConnectVM.setParingPin(_controller.text.trim()), ), diff --git a/lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart b/lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart index 9b429edc4..5d0dcf1cc 100644 --- a/lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart +++ b/lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart @@ -32,6 +32,7 @@ class _HardwareWalletTrezorParingSheetState extends State extends State> { ModalTopBar( title: widget.title, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).maybePop, ), Padding( @@ -387,6 +389,7 @@ class _PickerSliderPageState extends State> { ModalTopBar( title: widget.title, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), Expanded( diff --git a/lib/new-ui/widgets/receive_page/payjoin_copy_modal.dart b/lib/new-ui/widgets/receive_page/payjoin_copy_modal.dart index 06ec6b4ef..8fd7c1730 100644 --- a/lib/new-ui/widgets/receive_page/payjoin_copy_modal.dart +++ b/lib/new-ui/widgets/receive_page/payjoin_copy_modal.dart @@ -25,6 +25,7 @@ class PayjoinCopyModal extends StatelessWidget { ModalTopBar( title: S.of(context).select_address_to_copy, leadingIcon: Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: Navigator.of(context).pop), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), diff --git a/lib/new-ui/widgets/receive_page/receive_address_type_selector.dart b/lib/new-ui/widgets/receive_page/receive_address_type_selector.dart index d628573e7..1a3ce73d5 100644 --- a/lib/new-ui/widgets/receive_page/receive_address_type_selector.dart +++ b/lib/new-ui/widgets/receive_page/receive_address_type_selector.dart @@ -73,6 +73,7 @@ class _ReceiveAddressTypeSelectorState extends State ModalTopBar( title: S.of(context).address_type, leadingIcon: Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: Navigator.of(context).pop, onTrailingPressed: () {}), SizedBox(height: 24), diff --git a/lib/new-ui/widgets/receive_page/receive_amount_modal.dart b/lib/new-ui/widgets/receive_page/receive_amount_modal.dart index 882887020..bbc1daecf 100644 --- a/lib/new-ui/widgets/receive_page/receive_amount_modal.dart +++ b/lib/new-ui/widgets/receive_page/receive_amount_modal.dart @@ -56,6 +56,7 @@ class _ReceiveAmountModalState extends State { onLeadingPressed: Navigator.of(context).pop, onTrailingPressed: () {}, leadingIcon: Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, ), Padding( padding: const EdgeInsets.all(18.0), diff --git a/lib/new-ui/widgets/receive_page/receive_label_modal.dart b/lib/new-ui/widgets/receive_page/receive_label_modal.dart index 9dc05c19d..60e4c01a1 100644 --- a/lib/new-ui/widgets/receive_page/receive_label_modal.dart +++ b/lib/new-ui/widgets/receive_page/receive_label_modal.dart @@ -66,6 +66,7 @@ class _ReceiveLabelModalState extends State { ModalTopBar( title: S.of(context).label_address, leadingIcon: Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: Navigator.of(context).pop, onTrailingPressed: () {}), Padding( diff --git a/lib/new-ui/widgets/receive_page/receive_top_bar.dart b/lib/new-ui/widgets/receive_page/receive_top_bar.dart index 97b72b148..374c58d07 100644 --- a/lib/new-ui/widgets/receive_page/receive_top_bar.dart +++ b/lib/new-ui/widgets/receive_page/receive_top_bar.dart @@ -14,7 +14,14 @@ class ModalTopBar extends StatelessWidget { this.trailingIcon, this.padding, this.leadingWidget, - this.trailingWidget}) { + this.trailingWidget, + this.leadingSemanticLabel, + this.trailingSemanticLabel}) + : assert(leadingIcon == null || (leadingSemanticLabel != null && leadingSemanticLabel != ""), + "leadingIcon requires a non-empty leadingSemanticLabel"), + assert( + trailingIcon == null || (trailingSemanticLabel != null && trailingSemanticLabel != ""), + "trailingIcon requires a non-empty trailingSemanticLabel") { if (leadingIcon != null && leadingWidget != null) { throw Exception("Cannot have both leadingIcon and leadingWidget"); } @@ -33,6 +40,17 @@ class ModalTopBar extends StatelessWidget { final Widget? leadingWidget; final Widget? trailingWidget; + /// Accessible name for the leading chrome button. Required (and must be + /// non-empty) whenever a [leadingIcon] is supplied, because the icon alone + /// does not say whether it closes the modal or goes back. Must be localized + /// by the caller. + final String? leadingSemanticLabel; + + /// Accessible name for the trailing chrome button. Required (and must be + /// non-empty) whenever a [trailingIcon] is supplied. Must be localized by + /// the caller. + final String? trailingSemanticLabel; + static const buttonSize = 36.0; @override @@ -51,10 +69,16 @@ class ModalTopBar extends StatelessWidget { children: [ AnimatedSwitcher( duration: const Duration(milliseconds: 200), - child: Text( - title, + child: Semantics( key: ValueKey(title), - style: Theme.of(context).textTheme.headlineMedium, + header: title.isNotEmpty, + // Android reads the heading from headingLevel since the + // Flutter 3.41 engine; header: alone only covers iOS. + headingLevel: title.isNotEmpty ? 1 : null, + child: Text( + title, + style: Theme.of(context).textTheme.headlineMedium, + ), ), ), if (subtitle != null && subtitle!.isNotEmpty) @@ -77,6 +101,7 @@ class ModalTopBar extends StatelessWidget { size: buttonSize, onPressed: onLeadingPressed, icon: leadingIcon!, + semanticLabel: leadingSemanticLabel, iconColor: Theme.of(context).colorScheme.onSurfaceVariant) : leadingWidget! else @@ -90,6 +115,7 @@ class ModalTopBar extends StatelessWidget { size: buttonSize, onPressed: onTrailingPressed, icon: trailingIcon!, + semanticLabel: trailingSemanticLabel, ) : trailingWidget!, ) diff --git a/lib/new-ui/widgets/scan_page/network_list.dart b/lib/new-ui/widgets/scan_page/network_list.dart index aa3d8e448..007ba5167 100644 --- a/lib/new-ui/widgets/scan_page/network_list.dart +++ b/lib/new-ui/widgets/scan_page/network_list.dart @@ -22,6 +22,7 @@ class ScanPageNetworkList extends StatelessWidget { ModalTopBar( title: S.of(context).compatible_services, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), Padding( diff --git a/lib/new-ui/widgets/send_page/fiat_amount_bar.dart b/lib/new-ui/widgets/send_page/fiat_amount_bar.dart index bd383b50f..76cbcfc23 100644 --- a/lib/new-ui/widgets/send_page/fiat_amount_bar.dart +++ b/lib/new-ui/widgets/send_page/fiat_amount_bar.dart @@ -47,6 +47,7 @@ class FiatAmountBar extends StatelessWidget { svgPath: "assets/new-ui/switch.svg", iconSize: 18, onPressed: onSwitchButtonPressed, + semanticLabel: S.of(context).switch_input_currency, ), GestureDetector( onTap: onSwitchButtonPressed, diff --git a/lib/new-ui/widgets/send_page/l2_action_wallet_selector.dart b/lib/new-ui/widgets/send_page/l2_action_wallet_selector.dart index 835631e9a..e9aac029d 100644 --- a/lib/new-ui/widgets/send_page/l2_action_wallet_selector.dart +++ b/lib/new-ui/widgets/send_page/l2_action_wallet_selector.dart @@ -90,6 +90,7 @@ class _L2ActionWalletSelectorState extends State { ? "${S.of(context).send_from}..." : "${S.of(context).receive_to}...", leadingIcon: const Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), Flexible( diff --git a/lib/new-ui/widgets/send_page/l2_send_external_modal.dart b/lib/new-ui/widgets/send_page/l2_send_external_modal.dart index 82c077da6..6fb6ddf94 100644 --- a/lib/new-ui/widgets/send_page/l2_send_external_modal.dart +++ b/lib/new-ui/widgets/send_page/l2_send_external_modal.dart @@ -73,6 +73,7 @@ class _L2SendExternalModalState extends State { ], ), trailingIcon: Icon(Icons.close), + trailingSemanticLabel: S.of(context).close, onTrailingPressed: Navigator.of(context).pop, ), SafeArea( diff --git a/lib/new-ui/widgets/send_page/send_confirm_sheet.dart b/lib/new-ui/widgets/send_page/send_confirm_sheet.dart index b6df11d7b..dccaf41ba 100644 --- a/lib/new-ui/widgets/send_page/send_confirm_sheet.dart +++ b/lib/new-ui/widgets/send_page/send_confirm_sheet.dart @@ -161,6 +161,7 @@ class SendTransactionDetails extends StatelessWidget { ], ), trailingIcon: Icon(Icons.close), + trailingSemanticLabel: S.of(context).close, onTrailingPressed: Navigator.of(context).maybePop, ), isPage diff --git a/lib/new-ui/widgets/swap_page/provider_options_page.dart b/lib/new-ui/widgets/swap_page/provider_options_page.dart index 782a5b0ac..88333451f 100644 --- a/lib/new-ui/widgets/swap_page/provider_options_page.dart +++ b/lib/new-ui/widgets/swap_page/provider_options_page.dart @@ -37,6 +37,7 @@ class ProviderOptionsPage extends StatelessWidget { title: S.of(context).swap_providers, onLeadingPressed: Navigator.of(context).pop, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, ), Expanded( child: SingleChildScrollView( diff --git a/lib/new-ui/widgets/swap_page/provider_selector_page.dart b/lib/new-ui/widgets/swap_page/provider_selector_page.dart index e521ea937..c8e15e8f0 100644 --- a/lib/new-ui/widgets/swap_page/provider_selector_page.dart +++ b/lib/new-ui/widgets/swap_page/provider_selector_page.dart @@ -24,6 +24,7 @@ class ProviderSelectorPage extends StatelessWidget { ModalTopBar( title: S.of(context).change_provider, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).maybePop, ), Expanded( diff --git a/lib/new-ui/widgets/swap_page/refund_address_modal.dart b/lib/new-ui/widgets/swap_page/refund_address_modal.dart index 6a8f0e2b9..031d67e5a 100644 --- a/lib/new-ui/widgets/swap_page/refund_address_modal.dart +++ b/lib/new-ui/widgets/swap_page/refund_address_modal.dart @@ -44,6 +44,7 @@ class _RefundAddressModalState extends State { ModalTopBar( title: S.of(context).set_refund_address, trailingIcon: Icon(Icons.close), + trailingSemanticLabel: S.of(context).close, onTrailingPressed: Navigator.of(context).pop, ), SafeArea( diff --git a/lib/new-ui/widgets/swap_page/swap_address_selection_modal.dart b/lib/new-ui/widgets/swap_page/swap_address_selection_modal.dart index 67eb2d125..8c6d83498 100644 --- a/lib/new-ui/widgets/swap_page/swap_address_selection_modal.dart +++ b/lib/new-ui/widgets/swap_page/swap_address_selection_modal.dart @@ -82,6 +82,7 @@ class _SwapAddressSelectionModalState extends State { ? "${S.of(context).receive_to}..." : "${S.of(context).send_from}...", leadingIcon: Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: Navigator.of(context).pop, ), Flexible( diff --git a/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart b/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart index c948d6b12..b5626056b 100644 --- a/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart +++ b/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart @@ -156,6 +156,7 @@ class SwapTransactionDetails extends StatelessWidget { fromIconPath: exchangeViewModel.depositCurrency.iconPath ?? "", toIconPath: exchangeViewModel.receiveCurrency.iconPath ?? ""), trailingIcon: Icon(Icons.close), + trailingSemanticLabel: S.of(context).close, onTrailingPressed: Navigator.of(context).maybePop, ), SafeArea( diff --git a/lib/new-ui/widgets/swap_page/swap_options_page.dart b/lib/new-ui/widgets/swap_page/swap_options_page.dart index b4726440b..98c710e9d 100644 --- a/lib/new-ui/widgets/swap_page/swap_options_page.dart +++ b/lib/new-ui/widgets/swap_page/swap_options_page.dart @@ -25,6 +25,7 @@ class SwapOptionsPage extends StatelessWidget { ModalTopBar( title: S.of(context).configure, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), Padding( diff --git a/lib/new-ui/widgets/swap_page/swap_send_external_modal.dart b/lib/new-ui/widgets/swap_page/swap_send_external_modal.dart index f48d96a04..6b33af206 100644 --- a/lib/new-ui/widgets/swap_page/swap_send_external_modal.dart +++ b/lib/new-ui/widgets/swap_page/swap_send_external_modal.dart @@ -69,6 +69,7 @@ class _SwapSendExternalModalState extends State { leadingWidget: SwapModalHeader( fromIconPath: widget.from.iconPath ?? "", toIconPath: widget.to.iconPath ?? ""), trailingIcon: Icon(Icons.close), + trailingSemanticLabel: S.of(context).close, onTrailingPressed: Navigator.of(context).pop, ), Padding( diff --git a/lib/new-ui/widgets/swap_page/trocador_providers_settings.dart b/lib/new-ui/widgets/swap_page/trocador_providers_settings.dart index 36474acd4..a07b2cd12 100644 --- a/lib/new-ui/widgets/swap_page/trocador_providers_settings.dart +++ b/lib/new-ui/widgets/swap_page/trocador_providers_settings.dart @@ -25,6 +25,7 @@ class TrocadorProvidersSettings extends StatelessWidget { ModalTopBar( title: "Trocador ${S.of(context).providers}", leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), Expanded( diff --git a/lib/new-ui/widgets/wallet_deprecation_popup.dart b/lib/new-ui/widgets/wallet_deprecation_popup.dart index 2b233c0ac..a28c5d1f0 100644 --- a/lib/new-ui/widgets/wallet_deprecation_popup.dart +++ b/lib/new-ui/widgets/wallet_deprecation_popup.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/new-ui/widgets/animated_dropdown.dart'; import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart'; import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart'; @@ -27,6 +28,7 @@ class WalletDeprecationPopup extends StatelessWidget { ModalTopBar( title: "", leadingIcon: Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: Navigator.of(context).pop, ), Padding( diff --git a/lib/src/screens/auth/auth_page.dart b/lib/src/screens/auth/auth_page.dart index 4cee26565..df1b72b39 100644 --- a/lib/src/screens/auth/auth_page.dart +++ b/lib/src/screens/auth/auth_page.dart @@ -141,6 +141,7 @@ class AuthPagePinCodeStateImpl extends AuthPageState { size: 36, icon: Icon(Icons.close), onPressed: Navigator.of(context).pop, + semanticLabel: S.of(context).close, iconColor: Theme.of(context).colorScheme.onSurfaceVariant) : Container(), backgroundColor: Theme.of(context).colorScheme.surface, diff --git a/lib/src/screens/backup/edit_backup_password_page.dart b/lib/src/screens/backup/edit_backup_password_page.dart index d3ac5e740..4abb460ef 100644 --- a/lib/src/screens/backup/edit_backup_password_page.dart +++ b/lib/src/screens/backup/edit_backup_password_page.dart @@ -36,6 +36,7 @@ class _EditBackupPasswordPageState extends State { ModalTopBar( title: S.of(context).edit_backup_password, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), Expanded( diff --git a/lib/src/screens/base_page.dart b/lib/src/screens/base_page.dart index 4602c92b6..ef119faa2 100644 --- a/lib/src/screens/base_page.dart +++ b/lib/src/screens/base_page.dart @@ -84,20 +84,16 @@ abstract class BasePage extends StatelessWidget { return null; } - return MergeSemantics( - child: Padding( - padding: const EdgeInsets.only(top: 4.0), - child: ButtonTheme( - minWidth: double.minPositive, - child: Semantics( - label: S.of(context).seed_alert_back, - child: ModernButton( - size: 37, - icon: Icon(CupertinoIcons.back), - iconColor: Theme.of(context).colorScheme.onSurfaceVariant, - onPressed: () => onClose(context), - ), - ), + return Padding( + padding: const EdgeInsets.only(top: 4.0), + child: ButtonTheme( + minWidth: double.minPositive, + child: ModernButton( + size: 37, + icon: Icon(CupertinoIcons.back), + semanticLabel: S.of(context).seed_alert_back, + iconColor: Theme.of(context).colorScheme.onSurfaceVariant, + onPressed: () => onClose(context), ), ), ); diff --git a/lib/src/screens/connect_device/monero_hardware_wallet_passphrase_input.dart b/lib/src/screens/connect_device/monero_hardware_wallet_passphrase_input.dart index 5db520050..f893b532b 100644 --- a/lib/src/screens/connect_device/monero_hardware_wallet_passphrase_input.dart +++ b/lib/src/screens/connect_device/monero_hardware_wallet_passphrase_input.dart @@ -24,6 +24,7 @@ class MoneroHardwareWalletPassphraseInputModal extends StatelessWidget { ModalTopBar( title: S.of(context).passphrase_entry, leadingIcon: const Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: Navigator.of(context).pop, ), Padding( diff --git a/lib/src/screens/dashboard/favorite_token_modal.dart b/lib/src/screens/dashboard/favorite_token_modal.dart index ef461140a..1b0c9ac13 100644 --- a/lib/src/screens/dashboard/favorite_token_modal.dart +++ b/lib/src/screens/dashboard/favorite_token_modal.dart @@ -22,6 +22,7 @@ class FavoriteTokenModal extends StatelessWidget { ModalTopBar( title: S.of(context).favorite_token, leadingIcon: Icon(Icons.close), + leadingSemanticLabel: S.of(context).close, onLeadingPressed: Navigator.of(context).pop, ), Expanded( diff --git a/lib/src/screens/dashboard/sign_page.dart b/lib/src/screens/dashboard/sign_page.dart index 5e805c30c..4a6f70b9b 100644 --- a/lib/src/screens/dashboard/sign_page.dart +++ b/lib/src/screens/dashboard/sign_page.dart @@ -59,6 +59,7 @@ class _SignPageState extends State { ModalTopBar( title: S.current.sign_verify_title, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.current.seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), Expanded( diff --git a/lib/src/screens/nodes/node_create_or_edit_page.dart b/lib/src/screens/nodes/node_create_or_edit_page.dart index 1ac1d5ee1..78a76f8b5 100644 --- a/lib/src/screens/nodes/node_create_or_edit_page.dart +++ b/lib/src/screens/nodes/node_create_or_edit_page.dart @@ -93,8 +93,10 @@ class _NodeCreateOrEditPageState extends State { ModalTopBar( title: widget.editingNode != null ? S.current.edit_node : S.current.node_new, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.current.seed_alert_back, onLeadingPressed: Navigator.of(context).pop, trailingIcon: Icon(Icons.check), + trailingSemanticLabel: S.current.save, onTrailingPressed: () async { if (_nodeFormKey.currentState != null && !_nodeFormKey.currentState!.validate()) { return; diff --git a/lib/src/screens/nodes/node_share_page.dart b/lib/src/screens/nodes/node_share_page.dart index bb96b5b0c..1258f12fb 100644 --- a/lib/src/screens/nodes/node_share_page.dart +++ b/lib/src/screens/nodes/node_share_page.dart @@ -24,6 +24,7 @@ class NodeSharePage extends StatelessWidget { ModalTopBar( title: S.of(context).share, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), Expanded( diff --git a/lib/src/screens/nodes/pow_node_create_or_edit_page.dart b/lib/src/screens/nodes/pow_node_create_or_edit_page.dart index 0763fc8e2..0e9896094 100644 --- a/lib/src/screens/nodes/pow_node_create_or_edit_page.dart +++ b/lib/src/screens/nodes/pow_node_create_or_edit_page.dart @@ -75,6 +75,7 @@ class _PowNodeCreateOrEditPageState extends State { ModalTopBar( title: widget.editingNode != null ? S.current.edit_node : S.current.node_new, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.current.seed_alert_back, onLeadingPressed: Navigator.of(context).pop, trailingIcon: CakeImageWidget( imageUrl: "assets/new-ui/scan.svg", @@ -83,6 +84,7 @@ class _PowNodeCreateOrEditPageState extends State { colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn), ), + trailingSemanticLabel: S.current.scan, onTrailingPressed: () => widget.nodeCreateOrEditViewModel.scanQRCodeForNewNode(context), ), diff --git a/lib/src/screens/receive/address_list_page.dart b/lib/src/screens/receive/address_list_page.dart index abcf9e1e1..f310a5546 100644 --- a/lib/src/screens/receive/address_list_page.dart +++ b/lib/src/screens/receive/address_list_page.dart @@ -22,6 +22,7 @@ class AddressListPage extends StatelessWidget { ModalTopBar( title: S.of(context).accounts_subaddresses, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), Expanded( diff --git a/lib/src/screens/rescan/rescan_page.dart b/lib/src/screens/rescan/rescan_page.dart index e678e0185..8d7395068 100644 --- a/lib/src/screens/rescan/rescan_page.dart +++ b/lib/src/screens/rescan/rescan_page.dart @@ -114,6 +114,7 @@ class _RescanPageState extends State { ? S.current.silent_payments_scanning : S.current.rescan, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.current.seed_alert_back, onLeadingPressed: Navigator.of(context).pop), Expanded( child: Padding( diff --git a/lib/src/screens/settings/connection_sync_page.dart b/lib/src/screens/settings/connection_sync_page.dart index 6406f5854..fb251f55f 100644 --- a/lib/src/screens/settings/connection_sync_page.dart +++ b/lib/src/screens/settings/connection_sync_page.dart @@ -34,6 +34,7 @@ class ConnectionSyncPage extends BasePage { topBar: ModalTopBar( title: "", leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: () => Navigator.of(context).pop()), header: ModalHeader( iconPath: "assets/new-ui/settings_row_icons/connections.svg", diff --git a/lib/src/screens/settings/display_settings_page.dart b/lib/src/screens/settings/display_settings_page.dart index 6a3fa6f69..392a62289 100644 --- a/lib/src/screens/settings/display_settings_page.dart +++ b/lib/src/screens/settings/display_settings_page.dart @@ -35,6 +35,7 @@ class DisplaySettingsPage extends StatelessWidget { topBar: ModalTopBar( title: S.of(context).display, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: () => Navigator.of(context).pop(), ), content: Column( diff --git a/lib/src/screens/settings/manage_nodes_page.dart b/lib/src/screens/settings/manage_nodes_page.dart index c750397e0..f867ef2f8 100644 --- a/lib/src/screens/settings/manage_nodes_page.dart +++ b/lib/src/screens/settings/manage_nodes_page.dart @@ -40,6 +40,7 @@ class _ManageNodesPageState extends State { topBar: ModalTopBar( title: S.of(context).manage_nodes, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: () => Navigator.of(context).pop(), trailingWidget: Row( spacing: 8, @@ -50,11 +51,13 @@ class _ManageNodesPageState extends State { icon: widget.nodeListViewModel.isTestingNodeSpeed ? CupertinoActivityIndicator() : Icon(Icons.refresh), + semanticLabel: S.of(context).test_node_speeds, onPressed: () => widget.nodeListViewModel.speedTestNodes()), ), ModernButton( size: 36, icon: Icon(Icons.add), + semanticLabel: S.of(context).add_new_node, onPressed: () async { final res = await Navigator.of(context) .pushNamed(widget.isPow ? Routes.newPowNode : Routes.newNode); diff --git a/lib/src/screens/settings/mweb_node_page.dart b/lib/src/screens/settings/mweb_node_page.dart index f5be67ee0..a05feed8d 100644 --- a/lib/src/screens/settings/mweb_node_page.dart +++ b/lib/src/screens/settings/mweb_node_page.dart @@ -34,6 +34,7 @@ class _MwebNodePageState extends State { topBar: ModalTopBar( title: S.current.litecoin_mweb_settings, onLeadingPressed: Navigator.of(context).pop, + leadingSemanticLabel: S.current.seed_alert_back, leadingIcon: Icon(Icons.arrow_back_ios_new)), content: Container( child: NewListSections(controllers: { diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index 7a08d6d86..3f5291984 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -42,6 +42,7 @@ class OtherSettingsPage extends BasePage { topBar: ModalTopBar( title: S.of(context).other, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: () => Navigator.of(context).pop(), ), // header: ModalHeader( diff --git a/lib/src/screens/settings/privacy_page.dart b/lib/src/screens/settings/privacy_page.dart index 3d8b651a1..3e35f08c3 100644 --- a/lib/src/screens/settings/privacy_page.dart +++ b/lib/src/screens/settings/privacy_page.dart @@ -26,6 +26,7 @@ class PrivacyPage extends BasePage { topBar: ModalTopBar( title: "", leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: () => Navigator.of(context).pop(), ), header: ModalHeader( diff --git a/lib/src/screens/settings/security_backup_page.dart b/lib/src/screens/settings/security_backup_page.dart index fc8f5a1be..916189566 100644 --- a/lib/src/screens/settings/security_backup_page.dart +++ b/lib/src/screens/settings/security_backup_page.dart @@ -45,6 +45,7 @@ class SecurityBackupPage extends BasePage { topBar: ModalTopBar( title: "", leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.of(context).seed_alert_back, onLeadingPressed: () => Navigator.of(context).pop()), header: ModalHeader( iconPath: "assets/new-ui/settings_row_icons/security.svg", diff --git a/lib/src/screens/settings/silent_payments_settings.dart b/lib/src/screens/settings/silent_payments_settings.dart index 15184d5e5..4505a4e17 100644 --- a/lib/src/screens/settings/silent_payments_settings.dart +++ b/lib/src/screens/settings/silent_payments_settings.dart @@ -22,6 +22,7 @@ class SilentPaymentsSettingsPage extends StatelessWidget { ModalTopBar( title: S.current.silent_payments_settings, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.current.seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), Expanded( diff --git a/lib/src/screens/setup_2fa/setup_2fa_enter_code_page.dart b/lib/src/screens/setup_2fa/setup_2fa_enter_code_page.dart index a9f992787..8a6df6f98 100644 --- a/lib/src/screens/setup_2fa/setup_2fa_enter_code_page.dart +++ b/lib/src/screens/setup_2fa/setup_2fa_enter_code_page.dart @@ -144,6 +144,7 @@ class _TOTPEnterCodeState extends State { ModalTopBar( title: widget.isForSetup ? S.current.setup_2fa : S.current.verify_with_2fa, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.current.seed_alert_back, onLeadingPressed: Navigator.of(context).pop, ), Expanded( diff --git a/lib/src/screens/support_chat/support_chat_page.dart b/lib/src/screens/support_chat/support_chat_page.dart index d2a3cae76..88079add3 100644 --- a/lib/src/screens/support_chat/support_chat_page.dart +++ b/lib/src/screens/support_chat/support_chat_page.dart @@ -22,6 +22,7 @@ class SupportChatPage extends StatelessWidget { ModalTopBar( title: S.current.settings_support, leadingIcon: Icon(Icons.arrow_back_ios_new), + leadingSemanticLabel: S.current.seed_alert_back, onLeadingPressed: Navigator.of(context).pop), FutureBuilder( future: getCookie(), diff --git a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart index 1a2e4f44a..b422b801b 100644 --- a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart +++ b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart @@ -131,6 +131,7 @@ class WalletConnectConnectionsView extends StatelessWidget { size: 40, onPressed: () => Navigator.of(context).maybePop(), icon: Icon(Icons.arrow_back_ios_new, size: 16), + semanticLabel: S.of(context).seed_alert_back, iconColor: Theme.of(context).colorScheme.onSurfaceVariant, ), ), diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 97fac4c54..ea39386cb 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -918,6 +918,7 @@ "return_address_desc": "In the rare case of a failed swap, our partners can automatically refund your payment to the provided refund address.", "return_address_desc_external": "Since you chose Send From External, please set a refund address manually below.", "robinhood_option_description": "Buy and transfer instantly using your debit card, bank account, or Robinhood balance. USA only.", + "rotate_address": "Get new address", "router_no_route": "No route defined for ${name}", "salary": "Salary", "save": "Save", @@ -1147,10 +1148,13 @@ "swap_id": "Swap ID", "swap_provider_initial_desc": "Select the type of swap provider you prefer to use when swapping in", "swap_providers": "Swap providers", + "swap_reverse_direction": "Reverse swap direction", "sweeping_wallet": "Sweeping wallet", "sweeping_wallet_alert": "This shouldn’t take long. DO NOT LEAVE THIS SCREEN OR THE SWEPT FUNDS MAY BE LOST.", "swipe_to_bridge": "Swipe to bridge", "swipe_to_send": "Swipe to send", + "switch_camera": "Switch camera", + "switch_input_currency": "Switch between crypto and fiat entry", "switch_wallet": "Switch Wallet", "switchToETHWallet": "Please switch to an Ethereum wallet and try again", "switchToWCCompatibleWallet": "Please switch to a WalletConnect compatible wallet and try again (${chains}).", @@ -1187,6 +1191,7 @@ "tap_to_copy": "Tap to copy", "template": "Template", "template_name": "Template Name", + "test_node_speeds": "Test node speeds", "testnet_coins_no_value": "Testnet coins have no value", "third_intro_content": "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!", "third_intro_title": "Yat plays nicely with others", @@ -1285,6 +1290,8 @@ "trusted": "Trusted", "try_again": "Try again", "trying_to_spend_locked_funds": "Transaction rejected because you are trying to spend funds that have relative time lock, please wait and try again later, or disable the locked funds from coin control", + "turn_flash_off": "Turn flash off", + "turn_flash_on": "Turn flash on", "tx_commit_exception_no_dust_on_change": "The transaction is rejected with this amount. With these coins you can send ${min} without change or ${max} that returns change.", "tx_commit_failed": "Transaction commit failed. Please contact support.", "tx_commit_failed_no_peers": "Transaction failed to broadcast, please try again in a second",