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/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 721e21840..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( @@ -823,8 +827,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); } @@ -1485,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/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, 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 6a7f49900..b5626056b 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); } @@ -148,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/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/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/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); }, 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",