Merge branch 'refs/heads/dev' into cw-1440-new-buy-sell-flow

# Conflicts:
#	lib/new-ui/widgets/coins_page/cards/cards_view.dart
#	lib/new-ui/widgets/receive_page/receive_top_bar.dart
This commit is contained in:
Robert Malikowski
2026-08-07 01:35:54 +02:00
286 changed files with 13997 additions and 6504 deletions
+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),
),
),
);
@@ -42,7 +42,7 @@ class ConnectDevicePage extends BasePage {
ConnectDevicePage(ConnectDevicePageParams params, this.hardwareWalletVM)
: walletType = params.walletType,
onConnectDevice = params.onConnectDevice,
allowChangeWallet = params.allowChangeWallet,
allowChangeWallet = params.allowChangeWallet || params.isReconnect,
isReconnect = params.isReconnect;
final WalletType walletType;
final OnConnectDevice onConnectDevice;
@@ -142,12 +142,22 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
super.dispose();
}
var _isRefreshingUsb = false;
Future<void> _refreshUsbDevices() async {
final dev = await widget.hardwareWalletVM.getAllUsbDevices();
if (usbDevices.length != dev.length) {
setState(() => usbDevices = dev);
if (_isRefreshingUsb) {
return;
}
_isRefreshingUsb = true;
try {
final dev = await widget.hardwareWalletVM.getAllUsbDevices();
if (usbDevices.length != dev.length) {
setState(() => usbDevices = dev);
}
} catch(e) {
printV(e);
}
_isRefreshingUsb = false;
}
Future<void> _refreshBleDevices() async {
@@ -1,19 +1,22 @@
import 'package:cake_wallet/core/wallet_name_validator.dart';
import 'package:cw_core/generate_name.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import "dart:async";
import "package:cake_wallet/core/wallet_name_validator.dart";
import "package:cake_wallet/generated/i18n.dart";
import "package:cake_wallet/src/screens/base_page.dart";
import "package:cake_wallet/src/screens/connect_device/monero_hardware_wallet_passphrase_input.dart";
import "package:cake_wallet/src/widgets/alert_with_one_action.dart";
import "package:cake_wallet/src/widgets/base_text_form_field.dart";
import "package:cake_wallet/src/widgets/blockchain_height_widget.dart";
import "package:cake_wallet/src/widgets/primary_button.dart";
import "package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart";
import "package:cake_wallet/utils/responsive_layout_util.dart";
import "package:cake_wallet/utils/show_pop_up.dart";
import "package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart";
import "package:cw_core/generate_name.dart";
import "package:cw_core/wallet_type.dart";
import "package:flutter/material.dart";
import "package:flutter_mobx/flutter_mobx.dart";
import "package:mobx/mobx.dart";
class MoneroHardwareWalletOptionsPage extends BasePage {
MoneroHardwareWalletOptionsPage(this._walletHardwareRestoreVM);
@@ -42,13 +45,15 @@ class _MoneroHardwareWalletOptionsFormState extends State<_MoneroHardwareWalletO
: _formKey = GlobalKey<FormState>(),
_blockchainHeightKey = GlobalKey<BlockchainHeightState>(),
_blockHeightFocusNode = FocusNode(),
_controller = TextEditingController();
_walletNameController = TextEditingController(),
_passphraseController = TextEditingController();
final GlobalKey<FormState> _formKey;
final GlobalKey<BlockchainHeightState> _blockchainHeightKey;
final FocusNode _blockHeightFocusNode;
final WalletHardwareRestoreViewModel _walletHardwareRestoreVM;
final TextEditingController _controller;
final TextEditingController _walletNameController;
final TextEditingController _passphraseController;
@override
void initState() {
@@ -57,131 +62,159 @@ class _MoneroHardwareWalletOptionsFormState extends State<_MoneroHardwareWalletO
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(top: 24),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
content: Center(
child: ConstrainedBox(
constraints:
BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(top: 0),
child: Form(
key: _formKey,
child: Stack(
alignment: Alignment.centerRight,
children: [
BaseTextFormField(
onChanged: (value) => _walletHardwareRestoreVM.name = value,
controller: _controller,
textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 20.0,
fontWeight: FontWeight.w600,
),
placeholderTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 18.0,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
hintText: S.of(context).wallet_name,
suffixIcon: Semantics(
label: S.of(context).generate_name,
child: IconButton(
onPressed: _onGenerateName,
icon: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
color: Theme.of(context).colorScheme.surfaceContainerHighest,
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.only(top: 24),
child: ScrollableWithBottomSection(
contentPadding: const EdgeInsets.only(left: 24, right: 24, bottom: 24),
content: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.zero,
child: Form(
key: _formKey,
child: Stack(
alignment: Alignment.centerRight,
children: [
BaseTextFormField(
onChanged: (value) => _walletHardwareRestoreVM.name = value,
controller: _walletNameController,
textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 20,
fontWeight: FontWeight.w600,
),
width: 34,
height: 34,
child: Image.asset(
'assets/images/refresh_icon.png',
color: Theme.of(context).colorScheme.primary,
placeholderTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
hintText: S.of(context).wallet_name,
suffixIcon: Semantics(
label: S.of(context).generate_name,
child: IconButton(
onPressed: _onGenerateName,
icon: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Theme.of(context).colorScheme.surfaceContainerHighest,
),
width: 34,
height: 34,
child: Image.asset(
"assets/images/refresh_icon.png",
color: Theme.of(context).colorScheme.primary,
),
),
),
),
validator: WalletNameValidator(),
),
validator: WalletNameValidator(),
),
],
],
),
),
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: BlockchainHeightWidget(
focusNode: _blockHeightFocusNode,
key: _blockchainHeightKey,
hasDatePicker: true,
walletType: WalletType.monero,
Padding(
padding: const EdgeInsets.only(top: 20),
child: BlockchainHeightWidget(
focusNode: _blockHeightFocusNode,
key: _blockchainHeightKey,
hasDatePicker: true,
walletType: WalletType.monero,
),
),
],
),
),
),
bottomSectionPadding: const EdgeInsets.all(24),
bottomSection: Observer(
builder: (_) => Column(
children: [
if (_walletHardwareRestoreVM.passphraseAvailable) ...[
TextButton(
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => MoneroHardwareWalletPassphraseInputModal(
controller: _passphraseController,
),
);
},
child: Text(S.of(context).add_a_passphrase),
),
],
LoadingPrimaryButton(
onPressed: _confirmForm,
text: S.of(context).seed_language_next,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isDisabled: _walletHardwareRestoreVM.name.isEmpty,
),
],
),
),
),
bottomSectionPadding: EdgeInsets.all(24),
bottomSection: Observer(
builder: (context) => LoadingPrimaryButton(
onPressed: _confirmForm,
text: S.of(context).seed_language_next,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isDisabled: _walletHardwareRestoreVM.name.isEmpty,
),
),
),
);
}
);
Future<void> _onGenerateName() async {
final rName = await generateName();
FocusManager.instance.primaryFocus?.unfocus();
setState(() {
_controller.text = rName;
_walletNameController.text = rName;
_walletHardwareRestoreVM.name = rName;
_controller.selection =
TextSelection.fromPosition(TextPosition(offset: _controller.text.length));
_walletNameController.selection =
TextSelection.fromPosition(TextPosition(offset: _walletNameController.text.length));
});
}
Future<void> _confirmForm() async {
showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).proceed_on_device,
alertContent: S.of(context).proceed_on_device_description,
buttonText: S.of(context).cancel,
alertBarrierDismissible: false,
buttonAction: () => Navigator.of(context).pop(),
unawaited(
showPopUp<void>(
context: context,
builder: (context) => AlertWithOneAction(
alertTitle: S.of(context).proceed_on_device,
alertContent: S.of(context).proceed_on_device_description,
buttonText: S.of(context).cancel,
alertBarrierDismissible: false,
buttonAction: () => Navigator.of(context).pop(),
),
),
);
final options = {'height': _blockchainHeightKey.currentState?.height ?? -1};
final options = <String, dynamic>{"height": _blockchainHeightKey.currentState?.height ?? -1};
if (_walletHardwareRestoreVM.passphraseAvailable && _passphraseController.text.isNotEmpty) {
options["passphrase"] = _passphraseController.text;
}
await _walletHardwareRestoreVM.create(options: options);
}
bool _effectsInstalled = false;
void _setEffects(BuildContext context) {
if (_effectsInstalled) return;
if (_effectsInstalled) {
return;
}
reaction((_) => _walletHardwareRestoreVM.error, (String? error) {
reaction((_) => _walletHardwareRestoreVM.error, (error) {
if (error != null) {
if (error == S.current.ledger_connection_error) Navigator.of(context).pop();
if (error == S.current.ledger_connection_error) {
Navigator.of(context).pop();
}
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
builder: (context) => AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: error,
buttonText: S.of(context).ok,
@@ -0,0 +1,57 @@
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/base_text_form_field.dart";
import "package:flutter/material.dart";
class MoneroHardwareWalletPassphraseInputModal extends StatelessWidget {
const MoneroHardwareWalletPassphraseInputModal({required this.controller, super.key});
final TextEditingController controller;
@override
Widget build(BuildContext context) => Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.vertical(top: Radius.circular(18)),
color: Theme.of(context).colorScheme.surface,
),
child: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ModalTopBar(
title: S.of(context).passphrase_entry,
leadingIcon: const Icon(Icons.close),
leadingSemanticLabel: S.of(context).close,
onLeadingPressed: Navigator.of(context).pop,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 18),
child: Column(
spacing: 12,
children: [
BaseTextFormField(
controller: controller,
hintText: S.of(context).passphrase_raw,
autocorrect: false,
enableSuggestions: false,
),
const SizedBox(),
NewPrimaryButton(
onPressed: Navigator.of(context).pop,
text: S.of(context).restore_next,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
),
const SizedBox(),
],
),
),
],
),
),
),
);
}
+51 -35
View File
@@ -40,13 +40,11 @@ class EditTokenPage extends BasePage {
: S.current.add_token;
@override
Widget body(BuildContext context) {
return EditTokenPageBody(
Widget body(BuildContext context) => EditTokenPageBody(
homeSettingsViewModel: homeSettingsViewModel,
token: token,
initialContractAddress: initialContractAddress,
);
}
}
class EditTokenPageBody extends StatefulWidget {
@@ -83,6 +81,11 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
bool _disclaimerChecked = false;
bool isEditingToken = false;
bool _isTokenVerified = false;
bool _isJupiterVerified = false;
String? _jupiterVerifiedAddress;
bool get _isJupiterVerifiedForCurrentAddress =>
_isJupiterVerified && _jupiterVerifiedAddress == _contractAddressController.text;
@override
void initState() {
@@ -129,14 +132,28 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
final isVerified = widget.homeSettingsViewModel.checkIfTokenIsWhitelisted(contractAddress);
if (!mounted) return;
setState(() {
_isTokenVerified = isVerified;
});
_checkIfTokenIsVerifiedOnJupiter(contractAddress);
}
Future<void> _checkIfTokenIsVerifiedOnJupiter(String contractAddress) async {
final isVerified =
await widget.homeSettingsViewModel.checkIfTokenIsVerifiedOnJupiter(contractAddress);
if (!mounted || _contractAddressController.text != contractAddress) return;
setState(() {
_isJupiterVerified = isVerified ?? false;
_jupiterVerifiedAddress = contractAddress;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
Widget build(BuildContext context) => GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.zero,
@@ -174,8 +191,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
SizedBox(height: 20),
],
Observer(
builder: (context) {
return Row(
builder: (context) => Row(
children: <Widget>[
Expanded(
child: LoadingPrimaryButton(
@@ -210,14 +226,12 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
if (isTokenAlreadyAdded) {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithOneAction(
builder: (dialogContext) => AlertWithOneAction(
alertTitle: S.current.warning,
alertContent: S.of(context).token_already_exists,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(dialogContext).pop(),
);
},
),
);
return;
}
@@ -225,13 +239,15 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
final isWhitelisted = await widget.homeSettingsViewModel
.checkIfTokenIsWhitelisted(_contractAddressController.text);
final hasPotentialError = !isWhitelisted &&
final isTrusted = isWhitelisted || _isJupiterVerifiedForCurrentAddress;
final hasPotentialError = !isTrusted &&
await widget.homeSettingsViewModel
.checkIfERC20TokenContractAddressIsAPotentialScamAddress(
_contractAddressController.text,
);
bool isPotentialScam = hasPotentialError && !isWhitelisted;
bool isPotentialScam = hasPotentialError && !isTrusted;
// Normalize to catch homoglyph spoofing attacks
final tokenSymbol = normalizeHomoglyphs(
@@ -243,7 +259,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
// (e.g. fake ETH on Ethereum, fake SOL on Solana)
final nativeSymbol =
widget.homeSettingsViewModel.nativeToken.title.toUpperCase();
if (tokenSymbol == nativeSymbol && !isWhitelisted) {
if (tokenSymbol == nativeSymbol && !isTrusted) {
isPotentialScam = true;
}
@@ -252,7 +268,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
if (widget.homeSettingsViewModel.checkIfTokenSymbolMatchesDefaultToken(
tokenSymbol,
) &&
!isWhitelisted) {
!isTrusted) {
isPotentialScam = true;
}
@@ -277,23 +293,20 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
} catch (e) {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithOneAction(
builder: (dialogContext) => AlertWithOneAction(
alertTitle: S.current.warning,
alertContent: e.toString(),
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(dialogContext).pop(),
);
},
),
);
}
};
if (hasPotentialError && !isWhitelisted) {
if (hasPotentialError && !isTrusted) {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
builder: (dialogContext) => AlertWithTwoActions(
alertTitle: S.current.warning,
alertContent: S.current.contract_warning,
rightButtonText: S.of(context).continue_text,
@@ -303,8 +316,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
await actionCall();
},
actionLeftButton: () => Navigator.of(dialogContext).pop(),
);
},
),
);
} else {
try {
@@ -312,14 +324,12 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
} catch (e) {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithOneAction(
builder: (dialogContext) => AlertWithOneAction(
alertTitle: "Unable to add token",
alertContent: "$e",
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
),
);
}
if (mounted) {
@@ -334,14 +344,12 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
),
),
],
);
},
),
),
],
),
),
);
}
void _getTokenInfo() async {
if (_contractAddressController.text.isNotEmpty) {
@@ -377,8 +385,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
}
}
Widget _tokenForm() {
return Form(
Widget _tokenForm() => Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
@@ -402,7 +409,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
size: 75,
),
),
if (_isTokenVerified)
if (_isTokenVerified || _isJupiterVerifiedForCurrentAddress)
Positioned(
bottom: 0,
right: 0,
@@ -425,6 +432,16 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
),
],
),
if (_isJupiterVerifiedForCurrentAddress) ...[
const SizedBox(height: 12),
Text(
S.of(context).token_verified_on_jupiter,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.primary,
),
),
],
SizedBox(height: 25),
AddressTextField(
controller: _contractAddressController,
@@ -517,5 +534,4 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
],
),
);
}
}
@@ -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(
@@ -334,37 +334,6 @@ class CryptoBalanceWidget extends StatelessWidget {
),
),
],
if (dashboardViewModel.showZcashMissingFundsCard) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: InfoCard(
title: S.of(context).zcash_card_missing_funds,
description: S.of(context).zcash_card_description,
leftButtonTitle: S.of(context).zcash_card_dismiss,
rightButtonTitle: S.of(context).zcash_card_scan,
image: 'assets/new-ui/crypto_full_icons/zcash.svg',
leftButtonAction: () => _dismissZcash(context),
rightButtonAction: () => _rescanInternalChangeZcash(context),
hintWidget: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse(
"https://docs.cakewallet.com/tutorials/zashi/#creating-a-new-zcash-wallet-on-cake"),
mode: LaunchMode.externalApplication,
),
child: Text(
S.of(context).learn_more,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
softWrap: true,
),
),
),
),
],
if (dashboardViewModel.showDecredInfoCard) ...[
SizedBox(height: 10),
Padding(
@@ -494,21 +463,6 @@ class CryptoBalanceWidget extends StatelessWidget {
dashboardViewModel.setMwebEnabled();
}
Future<void> _rescanInternalChangeZcash(BuildContext context) async {
dashboardViewModel.rescanInternalChangeZcash();
await showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).alert_notice,
alertContent: S.of(context).zcash_card_warning,
buttonText: S.of(context).understand,
buttonAction: () {
Navigator.of(context).pop();
},
),
);
}
Future<void> _dismissMweb(BuildContext context) async {
await showPopUp<void>(
context: context,
@@ -522,18 +476,4 @@ class CryptoBalanceWidget extends StatelessWidget {
));
dashboardViewModel.dismissMweb();
}
Future<void> _dismissZcash(BuildContext context) async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).alert_notice,
alertContent: S.of(context).zcash_card_enable_later,
buttonText: S.of(context).understand,
buttonAction: () {
Navigator.of(context).pop();
},
));
dashboardViewModel.dismissZcash();
}
}
@@ -111,7 +111,7 @@ class TransactionsPage extends StatelessWidget {
key: item.key,
onTap: () => Navigator.of(context)
.pushNamed(Routes.transactionDetails, arguments: transaction),
isShield: transaction.additionalInfo['autoShield'] == true,
isShield: transaction.additionalInfo['isAutoShield'] == true,
direction: transaction.direction,
formattedDate: DateFormat('HH:mm').format(transaction.date),
formattedAmount: item.formattedCryptoAmount,
+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(
@@ -187,50 +187,56 @@ class _NEWNewMainNavBarState extends State<NewMainNavBar> {
? iconHorizontalPadding / 100
: 0),
curve: Curves.easeOutCubic,
child: InkWell(
splashFactory: NoSplash.splashFactory,
splashColor: Colors.transparent,
borderRadius: BorderRadius.circular(pillBorderRadius),
onTap: () => _onItemTap(i),
child: AnimatedContainer(
duration:
_firstFrame ? Duration.zero : inactiveIconMoveDuration,
curve: Curves.easeOutCubic,
width: i == widget.selectedIndex ? pillWidth : iconBoxWidth,
alignment: Alignment.center,
child: AnimatedAlign(
duration: inactiveIconFadeDuration,
child: Semantics(
button: true,
selected: i == widget.selectedIndex,
inMutuallyExclusiveGroup: true,
label: visibleActions[i].name(context),
child: InkWell(
splashFactory: NoSplash.splashFactory,
splashColor: Colors.transparent,
borderRadius: BorderRadius.circular(pillBorderRadius),
onTap: () => _onItemTap(i),
child: AnimatedContainer(
duration:
_firstFrame ? Duration.zero : inactiveIconMoveDuration,
curve: Curves.easeOutCubic,
width: i == widget.selectedIndex ? pillWidth : iconBoxWidth,
alignment: Alignment.center,
child: AnimatedScale(
duration: inactiveIconAppearDuration,
child: AnimatedAlign(
duration: inactiveIconFadeDuration,
curve: Curves.easeOutCubic,
scale: (i == widget.selectedIndex) ? 0.857 : 1.0,
child: TweenAnimationBuilder<Color?>(
tween: ColorTween(
begin: (i == widget.selectedIndex)
? inactiveColor
: activeColor,
end: (i == widget.selectedIndex)
? activeColor
: inactiveColor,
),
duration: iconColorChangeDuration,
builder: (context, value, child) {
return Container(
height: NewMainNavBar.barHeight,
child: CakeImageWidget(
imageUrl: visibleActions[i].image,
width: iconWidth,
height: iconHeight,
//fit: BoxFit.scaleDown,
colorFilter: ColorFilter.mode(
value ?? inactiveColor,
BlendMode.srcIn,
alignment: Alignment.center,
child: AnimatedScale(
duration: inactiveIconAppearDuration,
curve: Curves.easeOutCubic,
scale: (i == widget.selectedIndex) ? 0.857 : 1.0,
child: TweenAnimationBuilder<Color?>(
tween: ColorTween(
begin: (i == widget.selectedIndex)
? inactiveColor
: activeColor,
end: (i == widget.selectedIndex)
? activeColor
: inactiveColor,
),
duration: iconColorChangeDuration,
builder: (context, value, child) {
return Container(
height: NewMainNavBar.barHeight,
child: CakeImageWidget(
imageUrl: visibleActions[i].image,
width: iconWidth,
height: iconHeight,
//fit: BoxFit.scaleDown,
colorFilter: ColorFilter.mode(
value ?? inactiveColor,
BlendMode.srcIn,
),
),
),
);
}),
);
}),
),
),
),
),
@@ -306,11 +312,15 @@ class AnimatedPill extends StatelessWidget {
children: [
Padding(
padding: EdgeInsets.only(left: pillIconWidth + 2),
child: Text(
currentAction.name(context),
style: pillTextStyle.copyWith(color: contentColor),
overflow: TextOverflow.fade,
softWrap: false,
// The selected tab's InkWell already announces this name, so the
// pill text must not become a second stop for screen readers.
child: ExcludeSemantics(
child: Text(
currentAction.name(context),
style: pillTextStyle.copyWith(color: contentColor),
overflow: TextOverflow.fade,
softWrap: false,
),
),
),
],
+2 -2
View File
@@ -659,8 +659,8 @@ class ExchangePage extends BasePage {
final parsedAddresses = await adrResService.resolve(
query: domain, wallet: exchangeViewModel.wallet, currency: currency);
return parsedAddresses.isNotEmpty
? parsedAddresses.first.parsedAddressByCurrencyMap[currency] ?? ''
: '';
? (parsedAddresses.first.parsedAddressByCurrencyMap[currency] ?? domain)
: domain;
}
void _showFeeAlert(BuildContext context) async {
@@ -14,11 +14,13 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/zcash/zcash_network_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -28,6 +30,8 @@ class AdvancedPrivacySettingsPage extends BasePage {
required this.isChildWallet,
required this.useTestnet,
required this.toggleUseTestnet,
required this.zcashNetwork,
required this.setZcashNetwork,
required this.advancedPrivacySettingsViewModel,
required this.nodeViewModel,
required this.seedSettingsViewModel,
@@ -44,6 +48,8 @@ class AdvancedPrivacySettingsPage extends BasePage {
final bool isChildWallet;
final bool useTestnet;
final Function(bool? val) toggleUseTestnet;
final int zcashNetwork;
final void Function(int network) setZcashNetwork;
@override
Widget body(BuildContext context) => _AdvancedPrivacySettingsBody(
@@ -51,6 +57,8 @@ class AdvancedPrivacySettingsPage extends BasePage {
isChildWallet,
useTestnet,
toggleUseTestnet,
zcashNetwork,
setZcashNetwork,
advancedPrivacySettingsViewModel,
nodeViewModel,
seedSettingsViewModel,
@@ -63,6 +71,8 @@ class _AdvancedPrivacySettingsBody extends StatefulWidget {
this.isChildWallet,
this.useTestnet,
this.toggleUseTestnet,
this.zcashNetwork,
this.setZcashNetwork,
this.privacySettingsViewModel,
this.nodeViewModel,
this.seedTypeViewModel,
@@ -76,6 +86,8 @@ class _AdvancedPrivacySettingsBody extends StatefulWidget {
final bool isChildWallet;
final bool useTestnet;
final Function(bool? val) toggleUseTestnet;
final int zcashNetwork;
final void Function(int network) setZcashNetwork;
@override
_AdvancedPrivacySettingsBodyState createState() => _AdvancedPrivacySettingsBodyState();
@@ -87,6 +99,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
final _formKey = GlobalKey<NodeFormState>();
final _passphraseFormKey = GlobalKey<FormState>();
bool? testnetValue;
int? zcashNetworkValue;
bool obscurePassphrase = true;
@@ -112,6 +125,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
if (testnetValue == null && widget.useTestnet) {
testnetValue = widget.useTestnet;
}
zcashNetworkValue ??= widget.zcashNetwork;
return Container(
padding: EdgeInsets.only(top: 24),
@@ -281,6 +295,20 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
],
);
}),
if (FeatureFlag.hasDevOptions &&
widget.privacySettingsViewModel.type == WalletType.zcash)
SettingsChoicesCell(
ChoicesListItem<int>(
title: 'Zcash network',
items: ZcashNetworkType.values,
selectedItem: zcashNetworkValue ?? ZcashNetworkType.mainnet,
displayItem: ZcashNetworkType.label,
onItemSelected: (final network) {
setState(() => zcashNetworkValue = network);
widget.setZcashNetwork(network);
},
),
),
if (widget.privacySettingsViewModel.type == WalletType.bitcoin ||
widget.privacySettingsViewModel.type == WalletType.decred)
Builder(builder: (_) {
@@ -332,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);
@@ -316,6 +316,8 @@ class _WalletNameFormState extends State<WalletNameForm> {
"type": _walletNewVM.type,
"useTestnet": _walletNewVM.useTestnet,
"toggleTestnet": _walletNewVM.toggleUseTestnet,
"zcashNetwork": _walletNewVM.zcashNetwork,
"setZcashNetwork": _walletNewVM.setZcashNetwork,
"isChildWallet": widget.isChildWallet,
});
},
@@ -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(
@@ -99,7 +99,7 @@ class WalletRestorePage extends BasePage {
children: [
Observer(
builder: (context) {
return walletRestoreViewModel.mode == WalletRestoreMode.seed
return walletRestoreViewModel.passphraseAvailable
? StandardCheckbox(
captionColor: Theme.of(context).colorScheme.onSecondaryContainer,
value: walletRestoreViewModel.hasPassphrase,
@@ -121,7 +121,9 @@ class WalletRestorePage extends BasePage {
'isFromRestore': true,
'type': walletRestoreViewModel.type,
'useTestnet': walletRestoreViewModel.useTestnet,
'toggleTestnet': walletRestoreViewModel.toggleUseTestnet
'toggleTestnet': walletRestoreViewModel.toggleUseTestnet,
'zcashNetwork': walletRestoreViewModel.zcashNetwork,
'setZcashNetwork': walletRestoreViewModel.setZcashNetwork,
},
);
},
@@ -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(
@@ -115,21 +116,12 @@ class DisplaySettingsPage extends StatelessWidget {
onChanged: (val) {
_displaySettingsViewModel.setDisableTradeOption(val);
}),
if (_displaySettingsViewModel.showZcashCardSetting)
ListItemToggle(
keyValue: "display_settings_show_zcashcard",
label: S.of(context).show_zcash_card,
value: _displaySettingsViewModel.showZcashCard,
onChanged: (val) {
_displaySettingsViewModel.setShowZcashCard(val);
}),
ListItemSelector(
keyValue: "display_settings_sync_status_display",
label: S.of(context).sync_status_display_mode,
options: [_displaySettingsViewModel.syncStatusDisplayMode.title],
onTap: () async {
final items = SyncStatusDisplayMode.values.toList();
final selectedAtIndex =
items.indexOf(_displaySettingsViewModel.syncStatusDisplayMode);
@@ -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(),
@@ -1,34 +1,36 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/list_row.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/standard_expandable_list.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/src/widgets/standard_picker_list.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import "package:cake_wallet/core/execution_state.dart";
import "package:cake_wallet/generated/i18n.dart";
import "package:cake_wallet/src/screens/base_page.dart";
import "package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart";
import "package:cake_wallet/src/screens/transaction_details/standart_list_item.dart";
import "package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart";
import "package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart";
import "package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart";
import "package:cake_wallet/src/widgets/alert_with_one_action.dart";
import "package:cake_wallet/src/widgets/alert_with_two_actions.dart";
import "package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart";
import "package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart";
import "package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart";
import "package:cake_wallet/src/widgets/list_row.dart";
import "package:cake_wallet/src/widgets/primary_button.dart";
import "package:cake_wallet/src/widgets/standard_expandable_list.dart";
import "package:cake_wallet/src/widgets/standard_list.dart";
import "package:cake_wallet/src/widgets/standard_picker_list.dart";
import "package:cake_wallet/utils/show_bar.dart";
import "package:cake_wallet/utils/show_pop_up.dart";
import "package:cake_wallet/view_model/send/send_view_model_state.dart";
import "package:cake_wallet/view_model/transaction_details_view_model.dart";
import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:flutter_mobx/flutter_mobx.dart";
import "package:mobx/mobx.dart";
class RBFDetailsPage extends BasePage {
RBFDetailsPage({required this.transactionDetailsViewModel, required this.rawTransaction}) {
transactionDetailsViewModel.addBumpFeesListItems(
transactionDetailsViewModel.transactionInfo, rawTransaction);
transactionDetailsViewModel.transactionInfo,
rawTransaction,
);
}
@override
@@ -46,70 +48,70 @@ class RBFDetailsPage extends BasePage {
children: [
Expanded(
child: SectionStandardList(
sectionCount: 1,
itemCounter: (int _) => transactionDetailsViewModel.RBFListItems.length,
itemBuilder: (__, index) {
final item = transactionDetailsViewModel.RBFListItems[index];
sectionCount: 1,
itemCounter: (_) => transactionDetailsViewModel.rbfListItems.length,
itemBuilder: (__, index) {
final item = transactionDetailsViewModel.rbfListItems[index];
if (item is StandartListItem) {
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: item.value));
showBar<void>(context, S.of(context).transaction_details_copied(item.title));
},
child: ListRow(title: '${item.title}:', value: item.value),
);
}
if (item is StandartListItem) {
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: item.value));
showBar<void>(context, S.of(context).transaction_details_copied(item.title));
},
child: ListRow(title: "${item.title}:", value: item.value),
);
}
if (item is StandardExpandableListItem) {
return StandardExpandableList(
title: '${item.title}: ${item.expandableItems.length}',
expandableItems: item.expandableItems,
);
}
if (item is StandardExpandableListItem) {
return StandardExpandableList(
title: "${item.title}: ${item.expandableItems.length}",
expandableItems: item.expandableItems,
);
}
if (item is StandardPickerListItem) {
return StandardPickerList(
title: item.title,
value: item.value,
items: item.items,
displayItem: item.displayItem,
onSliderChanged: item.onSliderChanged,
onItemSelected: item.onItemSelected,
selectedIdx: item.selectedIdx,
customItemIndex: item.customItemIndex,
customValue: item.customValue,
maxValue: item.maxValue,
);
}
if (item is StandardPickerListItem) {
return StandardPickerList(
title: item.title,
value: item.value,
items: item.items,
displayItem: item.displayItem,
onSliderChanged: item.onSliderChanged,
onItemSelected: item.onItemSelected,
selectedIdx: item.selectedIdx,
customItemIndex: item.customItemIndex,
customValue: item.customValue,
maxValue: item.maxValue,
);
}
if (item is TextFieldListItem) {
return TextFieldListRow(
title: item.title,
value: item.value,
onSubmitted: item.onSubmitted,
);
}
if (item is TextFieldListItem) {
return TextFieldListRow(
title: item.title,
value: item.value,
onSubmitted: item.onSubmitted,
);
}
return Container();
}),
return Container();
},
),
),
Padding(
padding: const EdgeInsets.all(24),
child: Observer(
builder: (_) => LoadingPrimaryButton(
onPressed: () async {
transactionDetailsViewModel
.replaceByFee(transactionDetailsViewModel.newFee.toString());
},
text: S.of(context).send,
isLoading:
transactionDetailsViewModel.sendViewModel.state is IsExecutingState,
isDisabled: transactionDetailsViewModel.sendViewModel.state
is ExecutedSuccessfullyState,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
))),
padding: const EdgeInsets.all(24),
child: Observer(
builder: (_) => LoadingPrimaryButton(
onPressed: () => transactionDetailsViewModel
.replaceByFee(transactionDetailsViewModel.newFee.toString()),
text: S.of(context).send,
isLoading: transactionDetailsViewModel.sendViewModel.state is IsExecutingState,
isDisabled:
transactionDetailsViewModel.sendViewModel.state is ExecutedSuccessfullyState,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
),
),
),
],
);
}
@@ -121,7 +123,7 @@ class RBFDetailsPage extends BasePage {
return;
}
reaction((_) => transactionDetailsViewModel.sendViewModel.state, (ExecutionState state) {
reaction((_) => transactionDetailsViewModel.sendViewModel.state, (state) {
if (state is! IsExecutingState &&
loadingBottomSheetContext != null &&
loadingBottomSheetContext!.mounted) {
@@ -131,35 +133,35 @@ class RBFDetailsPage extends BasePage {
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return AlertWithOneAction(
alertTitle: S.of(popupContext).error,
alertContent: state.error,
buttonText: S.of(popupContext).ok,
buttonAction: () => Navigator.of(popupContext).pop());
});
context: context,
builder: (popupContext) => AlertWithOneAction(
alertTitle: S.of(popupContext).error,
alertContent: state.error,
buttonText: S.of(popupContext).ok,
buttonAction: () => Navigator.of(popupContext).pop(),
),
);
});
}
if (state is AwaitingConfirmationState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return AlertWithTwoActions(
alertTitle: state.title ?? '',
alertContent: state.message ?? '',
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
state.onConfirm?.call();
Navigator.of(popupContext).pop();
},
actionLeftButton: () {
state.onCancel?.call();
Navigator.of(popupContext).pop();
});
});
context: context,
builder: (popupContext) => AlertWithTwoActions(
alertTitle: state.title ?? "",
alertContent: state.message ?? "",
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
state.onConfirm?.call();
Navigator.of(popupContext).pop();
},
actionLeftButton: () {
state.onCancel?.call();
Navigator.of(popupContext).pop();
},
),
);
});
}
@@ -169,7 +171,7 @@ class RBFDetailsPage extends BasePage {
showModalBottomSheet<void>(
context: context,
isDismissible: false,
builder: (BuildContext context) {
builder: (context) {
loadingBottomSheetContext = context;
return LoadingBottomSheet(
titleText: S.of(context).generating_transaction,
@@ -187,42 +189,42 @@ class RBFDetailsPage extends BasePage {
context: context,
isDismissible: false,
isScrollControlled: true,
builder: (BuildContext bottomSheetContext) {
return ConfirmSendingBottomSheet(
key: ValueKey('rbf_confirm_sending_bottom_sheet'),
titleText: S.of(bottomSheetContext).confirm_transaction,
isSlideActionEnabled: transactionDetailsViewModel.sendViewModel.isReadyForSend,
walletType: transactionDetailsViewModel.sendViewModel.walletType,
titleIconPath:
transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency,
amount: S.of(bottomSheetContext).send_amount,
amountValue:
transactionDetailsViewModel.sendViewModel.pendingTransaction!.amountFormatted,
fiatAmountValue: transactionDetailsViewModel
.sendViewModel.pendingTransactionFiatAmountFormatted,
fee: S.of(bottomSheetContext).send_fee,
feeValue:
transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeFormatted,
feeFiatAmount: transactionDetailsViewModel
.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
outputs: transactionDetailsViewModel.sendViewModel.outputs,
footerType: FooterType.slideActionButton,
accessibleNavigationModeSlideActionButtonText: S.of(context).send,
onSlideActionComplete: () async {
Navigator.of(bottomSheetContext).pop();
await transactionDetailsViewModel.sendViewModel.commitTransaction(context);
try {
builder: (bottomSheetContext) => ConfirmSendingBottomSheet(
key: const ValueKey("rbf_confirm_sending_bottom_sheet"),
titleText: S.of(bottomSheetContext).confirm_transaction,
isSlideActionEnabled: transactionDetailsViewModel.sendViewModel.isReadyForSend,
walletType: transactionDetailsViewModel.sendViewModel.walletType,
titleIconPath:
transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency,
amount: S.of(bottomSheetContext).send_amount,
amountValue:
transactionDetailsViewModel.sendViewModel.pendingTransaction!.amountFormatted,
fiatAmountValue:
transactionDetailsViewModel.sendViewModel.pendingTransactionFiatAmountFormatted,
fee: S.of(bottomSheetContext).send_fee,
feeValue:
transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeFormatted,
feeFiatAmount: transactionDetailsViewModel
.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
outputs: transactionDetailsViewModel.sendViewModel.outputs,
footerType: FooterType.slideActionButton,
accessibleNavigationModeSlideActionButtonText: S.of(context).send,
onSlideActionComplete: () async {
Navigator.of(bottomSheetContext).pop();
await transactionDetailsViewModel.sendViewModel.commitTransaction(context);
try {
if (bottomSheetContext.mounted) {
Navigator.of(bottomSheetContext).pop();
} catch (_) {}
},
change: transactionDetailsViewModel.sendViewModel.pendingTransaction!.change,
amountParsingProxy: transactionDetailsViewModel.sendViewModel.amountParsingProxy,
);
},
}
} catch (_) {}
},
change: transactionDetailsViewModel.sendViewModel.pendingTransaction!.change,
amountParsingProxy: transactionDetailsViewModel.sendViewModel.amountParsingProxy,
),
);
if (result == null) {
transactionDetailsViewModel.sendViewModel.dismissTransaction();
await transactionDetailsViewModel.sendViewModel.dismissTransaction();
}
}
});
@@ -232,14 +234,14 @@ class RBFDetailsPage extends BasePage {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.mounted) {
showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return AlertWithOneAction(
alertTitle: S.of(popupContext).sending,
alertContent: S.of(popupContext).transaction_sent,
buttonText: S.of(popupContext).ok,
buttonAction: () => Navigator.of(popupContext).pop());
});
context: context,
builder: (popupContext) => AlertWithOneAction(
alertTitle: S.of(popupContext).sending,
alertContent: S.of(popupContext).transaction_sent,
buttonText: S.of(popupContext).ok,
buttonAction: () => Navigator.of(popupContext).pop(),
),
);
}
});
}
@@ -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,
),
),
@@ -300,6 +300,7 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
color: Theme.of(context).colorScheme.surfaceContainer,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
@@ -313,21 +314,25 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
const SizedBox(width: 6),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Observer(
builder: (BuildContext context) {
return Text(
(widget.walletKeysViewModel.obscurePassphrase)
? "*****"
: widget.walletKeysViewModel.passphrase,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
);
},
Expanded(
child: Observer(
builder: (BuildContext context) {
return Text(
(widget.walletKeysViewModel.obscurePassphrase)
? "*****"
: widget.walletKeysViewModel.passphrase,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
);
},
),
),
const SizedBox(width: 6),
Observer(
builder: (BuildContext context) {
return GestureDetector(
@@ -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);
},
+48 -29
View File
@@ -1,3 +1,4 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/cupertino.dart';
@@ -92,35 +93,53 @@ class LoadingPrimaryButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
child: SizedBox(
width: double.infinity,
height: 52.0,
child: TextButton(
onPressed: (isLoading || isDisabled)
? null
: () {
FocusScope.of(context).unfocus();
onPressed.call();
},
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(isDisabled ? color.withOpacity(0.5) : color),
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
),
)),
child: isLoading
? CupertinoActivityIndicator(animating: true, color: textColor)
: Text(
text,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 15.0,
fontWeight: FontWeight.w600,
color: isDisabled ? textColor.withOpacity(0.5) : textColor,
),
),
final isEnabled = !isLoading && !isDisabled;
// The spinner replaces the label while loading, so the name is kept on a single
// semantics node that also reports the loading and disabled states.
return Semantics(
button: true,
enabled: isEnabled,
label: text,
value: isLoading ? S.of(context).loading : null,
onTap: isEnabled
? () {
FocusScope.of(context).unfocus();
onPressed.call();
}
: null,
excludeSemantics: true,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
child: SizedBox(
width: double.infinity,
height: 52.0,
child: TextButton(
onPressed: (isLoading || isDisabled)
? null
: () {
FocusScope.of(context).unfocus();
onPressed.call();
},
style: ButtonStyle(
backgroundColor:
WidgetStateProperty.all(isDisabled ? color.withOpacity(0.5) : color),
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
),
)),
child: isLoading
? CupertinoActivityIndicator(animating: true, color: textColor)
: Text(
text,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 15.0,
fontWeight: FontWeight.w600,
color: isDisabled ? textColor.withOpacity(0.5) : textColor,
),
),
),
),
),
);