Merge branch 'cake-tech:dev' into dev

This commit is contained in:
BSN ∞/21M
2026-08-02 13:08:43 +02:00
committed by GitHub
80 changed files with 386 additions and 172 deletions
+17 -8
View File
@@ -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);
}
+15 -8
View File
@@ -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);
}
+1
View File
@@ -56,6 +56,7 @@ class _AboutPageState extends State<AboutPage> {
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(
+2
View File
@@ -128,8 +128,10 @@ class _AccountCustomizerState extends State<AccountCustomizer> {
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(
+1
View File
@@ -105,6 +105,7 @@ class _NewAddressesPageState extends State<NewAddressesPage> {
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(
@@ -116,8 +116,10 @@ class _BridgeAmountPageState extends State<BridgeAmountPage> {
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<BridgeAmountPage> {
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,
@@ -87,6 +87,7 @@ class _BridgeConfirmSheetState extends State<BridgeConfirmSheet> {
],
),
trailingIcon: const Icon(Icons.close),
trailingSemanticLabel: S.of(context).close,
onTrailingPressed: () => Navigator.of(context).maybePop(),
);
},
@@ -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(
@@ -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(
@@ -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),
@@ -55,6 +55,7 @@ class _BridgeReceiveAddressInputPageState extends State<BridgeReceiveAddressInpu
ModalTopBar(
title: 'Input Receive Address',
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),
@@ -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<BridgeReceivingWalletPage> {
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),
+1
View File
@@ -84,6 +84,7 @@ class _CardCustomizerState extends State<CardCustomizer> {
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: () {},
@@ -68,6 +68,7 @@ class _LightningUsernamePageState extends State<LightningUsernamePage> {
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(
+4
View File
@@ -201,6 +201,7 @@ class _NewReceivePageState extends State<NewReceivePage> {
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<NewReceivePage> {
: 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(
+6
View File
@@ -163,6 +163,7 @@ class _ScanPageState extends State<ScanPage> {
},
iconColor: buttonIconColor,
backgroundColor: buttonColor,
semanticLabel: S.of(context).switch_camera,
),
ModernButton(
size: 36,
@@ -171,6 +172,10 @@ class _ScanPageState extends State<ScanPage> {
(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<ScanPage> {
size: 36,
iconSize: 18,
icon: Icon(Icons.arrow_back_ios_new),
semanticLabel: S.of(context).seed_alert_back,
onPressed: () {
if (_textInputMode) {
setState(() {
+12
View File
@@ -328,6 +328,7 @@ class _NewSendPageState extends State<NewSendPage> {
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<NewSendPage> {
BlendMode.srcIn,
),
),
semanticLabel: S.of(context).remove,
onPressed: () {
final outputIndex = _selectedOutput;
if (_selectedOutput != 0) {
@@ -362,6 +364,7 @@ class _NewSendPageState extends State<NewSendPage> {
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<NewSendPage> {
BlendMode.srcIn,
),
),
semanticLabel: S.of(context).help,
onPressed: () {
Navigator.of(context).push(
CupertinoPageRoute(
@@ -823,8 +827,15 @@ class _NewSendPageState extends State<NewSendPage> {
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(
+2 -1
View File
@@ -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: () {},
),
+6
View File
@@ -472,12 +472,14 @@ class _NewSwapPageState extends State<NewSwapPage> {
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<NewSwapPage> {
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<SwapAmountBox> {
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<SwapAmountBox> {
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<SwapAmountBox> {
size: 36,
iconSize: 20,
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
semanticLabel: S.of(context).scan,
),
]
],
+1 -1
View File
@@ -107,7 +107,7 @@ class _ChangelogModalState extends State<ChangelogModal> {
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,
@@ -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,
),
),
),
],
),
],
),
);
}
}
@@ -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))
],
),
);
}
}
@@ -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(
@@ -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),
),
@@ -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(
@@ -68,6 +68,7 @@ class _TransactionDetailsModalState extends State<TransactionDetailsModal> {
ModalTopBar(
title: S.of(context).transaction,
leadingIcon: const Icon(Icons.close),
leadingSemanticLabel: S.of(context).close,
onLeadingPressed: Navigator.of(context).pop,
),
Expanded(
@@ -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,
),
],
),
@@ -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),
@@ -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,
)
]
],
@@ -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(
@@ -117,6 +117,7 @@ class _FiatCurrencyPickerSheetState extends State<FiatCurrencyPickerSheet> {
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(
@@ -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),
@@ -106,6 +106,7 @@ class _HardwareWalletProceedOnDeviceSheetState extends State<HardwareWalletProce
Icons.close,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
trailingSemanticLabel: S.of(context).close,
onTrailingPressed: () => showPopUp(
context: context,
builder: (context) => AlertWithTwoActions(
@@ -143,6 +144,7 @@ class _HardwareWalletProceedOnDeviceSheetState extends State<HardwareWalletProce
iconColor: Theme.of(context).colorScheme.onPrimary,
size: 36,
icon: Icon(Icons.arrow_forward),
semanticLabel: S.of(context).confirm,
onPressed: () =>
widget.trezorConnectVM.setParingPin(_controller.text.trim()),
),
@@ -32,6 +32,7 @@ class _HardwareWalletTrezorParingSheetState extends State<HardwareWalletTrezorPa
onLeadingPressed: Navigator.of(context).pop,
onTrailingPressed: () {},
leadingIcon: Icon(Icons.close),
leadingSemanticLabel: S.of(context).close,
),
Padding(
padding: const EdgeInsets.all(18.0),
+50 -28
View File
@@ -11,6 +11,11 @@ class ModernButton extends StatelessWidget {
final Color? backgroundColor;
final double? iconSize;
final String? label;
/// Accessible name announced by screen readers. Falls back to [label] (the
/// visible caption) when omitted, so one of the two is required. Must be
/// localized by the caller.
final String? semanticLabel;
static const iconSvgSizeRatio = 2 / 3;
const ModernButton(
@@ -21,8 +26,11 @@ class ModernButton extends StatelessWidget {
this.iconSize,
this.iconColor,
this.backgroundColor,
this.label})
: svgPath = null;
this.label,
this.semanticLabel})
: svgPath = null,
assert(semanticLabel != null || (label != null && label != ""),
"ModernButton needs a semanticLabel when it has no visible label");
const ModernButton.svg(
{super.key,
@@ -32,8 +40,11 @@ class ModernButton extends StatelessWidget {
this.iconSize,
this.iconColor,
this.backgroundColor,
this.label})
: icon = null;
this.label,
this.semanticLabel})
: icon = null,
assert(semanticLabel != null || (label != null && label != ""),
"ModernButton needs a semanticLabel when it has no visible label");
@override
Widget build(BuildContext context) {
@@ -56,31 +67,42 @@ class ModernButton extends StatelessWidget {
child: icon!,
);
return Column(
spacing: 8,
children: [
Container(
decoration: BoxDecoration(
color: resolvedBackgroundColor,
borderRadius: BorderRadius.circular(size),
),
width: size,
height: size,
child: IconButton(
padding: EdgeInsets.zero,
onPressed: onPressed,
icon: resolvedIcon,
),
// A single accessibility node for the whole control: the visible caption
// duplicates the accessible name, so it is excluded to avoid a second,
// detached stop for screen readers.
return MergeSemantics(
child: Semantics(
button: true,
label: semanticLabel ?? label,
child: Column(
spacing: 8,
children: [
Container(
decoration: BoxDecoration(
color: resolvedBackgroundColor,
borderRadius: BorderRadius.circular(size),
),
width: size,
height: size,
child: IconButton(
padding: EdgeInsets.zero,
onPressed: onPressed,
icon: ExcludeSemantics(child: resolvedIcon),
),
),
if (label != null && label!.isNotEmpty)
ExcludeSemantics(
child: Text(
label!,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurface),
),
)
],
),
if (label != null && label!.isNotEmpty)
Text(
label!,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurface),
)
],
),
);
}
}
+3
View File
@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:cake_wallet/generated/i18n.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';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
@@ -77,6 +78,7 @@ class _NewPickerState<Item> extends State<NewPicker<Item>> {
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<Item> extends State<PickerSliderPage<Item>> {
ModalTopBar(
title: widget.title,
leadingIcon: Icon(Icons.arrow_back_ios_new),
leadingSemanticLabel: S.of(context).seed_alert_back,
onLeadingPressed: Navigator.of(context).pop,
),
Expanded(
@@ -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),
@@ -73,6 +73,7 @@ class _ReceiveAddressTypeSelectorState extends State<ReceiveAddressTypeSelector>
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),
@@ -56,6 +56,7 @@ class _ReceiveAmountModalState extends State<ReceiveAmountModal> {
onLeadingPressed: Navigator.of(context).pop,
onTrailingPressed: () {},
leadingIcon: Icon(Icons.close),
leadingSemanticLabel: S.of(context).close,
),
Padding(
padding: const EdgeInsets.all(18.0),
@@ -66,6 +66,7 @@ class _ReceiveLabelModalState extends State<ReceiveLabelModal> {
ModalTopBar(
title: S.of(context).label_address,
leadingIcon: Icon(Icons.close),
leadingSemanticLabel: S.of(context).close,
onLeadingPressed: Navigator.of(context).pop,
onTrailingPressed: () {}),
Padding(
@@ -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!,
)
@@ -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(
@@ -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,
@@ -90,6 +90,7 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
? "${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(
@@ -73,6 +73,7 @@ class _L2SendExternalModalState extends State<L2SendExternalModal> {
],
),
trailingIcon: Icon(Icons.close),
trailingSemanticLabel: S.of(context).close,
onTrailingPressed: Navigator.of(context).pop,
),
SafeArea(
@@ -161,6 +161,7 @@ class SendTransactionDetails extends StatelessWidget {
],
),
trailingIcon: Icon(Icons.close),
trailingSemanticLabel: S.of(context).close,
onTrailingPressed: Navigator.of(context).maybePop,
),
isPage
@@ -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(
@@ -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(
@@ -44,6 +44,7 @@ class _RefundAddressModalState extends State<RefundAddressModal> {
ModalTopBar(
title: S.of(context).set_refund_address,
trailingIcon: Icon(Icons.close),
trailingSemanticLabel: S.of(context).close,
onTrailingPressed: Navigator.of(context).pop,
),
SafeArea(
@@ -82,6 +82,7 @@ class _SwapAddressSelectionModalState extends State<SwapAddressSelectionModal> {
? "${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(
@@ -47,15 +47,23 @@ class _SwapConfirmSheetState extends State<SwapConfirmSheet> {
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(
@@ -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(
@@ -69,6 +69,7 @@ class _SwapSendExternalModalState extends State<SwapSendExternalModal> {
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(
@@ -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(
@@ -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(
+1
View File
@@ -141,6 +141,7 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
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,
@@ -36,6 +36,7 @@ class _EditBackupPasswordPageState extends State<EditBackupPasswordPage> {
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(
+10 -14
View File
@@ -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),
),
),
);
@@ -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(
@@ -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(
+1
View File
@@ -59,6 +59,7 @@ class _SignPageState extends State<SignPage> {
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(
@@ -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);
@@ -93,8 +93,10 @@ class _NodeCreateOrEditPageState extends State<NodeCreateOrEditPage> {
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;
@@ -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(
@@ -75,6 +75,7 @@ class _PowNodeCreateOrEditPageState extends State<PowNodeCreateOrEditPage> {
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<PowNodeCreateOrEditPage> {
colorFilter:
ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
),
trailingSemanticLabel: S.current.scan,
onTrailingPressed: () =>
widget.nodeCreateOrEditViewModel.scanQRCodeForNewNode(context),
),
@@ -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(
+1
View File
@@ -114,6 +114,7 @@ class _RescanPageState extends State<RescanPage> {
? 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(
@@ -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",
@@ -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(
@@ -40,6 +40,7 @@ class _ManageNodesPageState extends State<ManageNodesPage> {
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<ManageNodesPage> {
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);
@@ -34,6 +34,7 @@ class _MwebNodePageState extends State<MwebNodePage> {
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: {
@@ -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(
@@ -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(
@@ -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",
@@ -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(
@@ -144,6 +144,7 @@ class _TOTPEnterCodeState extends State<TOTPEnterCode> {
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(
@@ -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<String>(
future: getCookie(),
@@ -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,
),
),
@@ -20,6 +20,7 @@ class AddPassphraseBottomSheet extends StatefulWidget {
class _AddPassphraseBottomSheetState extends State<AddPassphraseBottomSheet> {
late final TextEditingController passphraseController;
late final TextEditingController confirmPassphraseController;
final _passphraseFormKey = GlobalKey<FormState>();
@override
void initState() {
@@ -112,51 +113,60 @@ class _AddPassphraseBottomSheetState extends State<AddPassphraseBottomSheet> {
),
),
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<AddPassphraseBottomSheet> {
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);
},
+7
View File
@@ -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",