Automated Integration Tests Flows (#1686)

* feat: Integration tests setup and tests for Disclaimer, Welcome and Setup Pin Code pages

* feat: Integration test flow from start to restoring a wallet successfully done

* test: Dashboard view test and linking to flow

* feat: Testing the Exchange flow section, selecting sending and receiving currencies

* test: Successfully create an exchange section

* feat: Implement flow up to sending section

* test: Complete Exchange flow

* fix dependency issue

* test: Final cleanups

* feat: Add CI to run automated integration tests withan android emulator

* feat: Adjust Automated integration test CI to run on ubuntu 20.04-a

* fix: Move integration test CI into PR test build CI

* ci: Add automated test ci which is a streamlined replica of pr test build ci

* ci: Re-add step to access branch name

* ci: Add KVM

* ci: Add filepath to trigger the test run from

* ci: Add required key

* ci: Add required key

* ci: Add missing secret key

* ci: Add missing secret key

* ci: Add nano secrets to workflow

* ci: Switch step to free space on runner

* ci: Remove timeout from workflow

* ci: Confirm impact that removing copy_monero_deps would have on entire workflow time

* ci: Update CI and temporarily remove cache related to emulator

* ci: Remove dynamic java version

* ci: Temporarily switch CI

* ci: Switch to 11.x jdk

* ci: Temporarily switch CI

* ci: Revert ubuntu version

* ci: Add more api levels

* ci: Add more target options

* ci: Settled on stable emulator matrix options

* ci: Add more target options

* ci: Modify flow

* ci: Streamline api levels to 28 and 29

* ci: One more trial

* ci: Switch to flutter drive

* ci: Reduce options

* ci: Remove haven from test

* ci: Check for solana in list

* ci: Adjust amounts and currencies for exchange flow

* ci: Set write response on failure to true

* ci: Split ci to funds and non funds related tests

* test: Test for Send flow scenario and minor restructuring for test folders and files

* chore: cleanup

* ci: Pause CI for now

* ci: Pause CI for now

* ci: Pause CI for now

* test: Restore wallets integration automated tests

* Fix: Add keys back to currency amount textfield widget

* fix: Switch variable name

* fix: remove automation for now

* tests: Automated tests for Create wallets flow

* tests: Further optimize common flows

* tests: Add missing await for call

* tests: Confirm Seeds Display Properly WIP

* tests: Confirm Seeds Display Correctly Automated Tests

* fix: Add missing pubspec params for bitcoin and bitcoin_cash

* feat: Automated Tests for Transaction History Flow

* fix: Add missing pubspec parameter

* feat: Automated Integration Tests for Transaction History flow

* test: Updating send page robot and also syncing branch with main

* test: Modifying tests to flow with wallet grouping implementation

* fix: Issue with transaction history test

* fix: Modifications to the PR and add automated confirmation for checking that all wallet types are restored or created correctly

* test: Attempting automation for testing

* fix: Issue from merge conflicts

* test: Remove automation of test in this PR

---------

Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
This commit is contained in:
David Adegoke
2024-11-07 16:46:08 +02:00
committed by GitHub
co-authored by OmarHatem
parent 48457fdd6d
commit 0fcfd76afd
84 changed files with 2716 additions and 745 deletions
+6 -7
View File
@@ -21,6 +21,7 @@ abstract class InfoPage extends BasePage {
String get pageTitle;
String get pageDescription;
String get buttonText;
Key? get buttonKey;
void Function(BuildContext) get onPressed;
@override
@@ -39,15 +40,14 @@ abstract class InfoPage extends BasePage {
alignment: Alignment.center,
padding: EdgeInsets.all(24),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
constraints:
BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.3),
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.3),
child: AspectRatio(aspectRatio: 1, child: image),
),
),
@@ -61,14 +61,13 @@ abstract class InfoPage extends BasePage {
height: 1.7,
fontSize: 14,
fontWeight: FontWeight.normal,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
),
),
),
),
PrimaryButton(
key: buttonKey,
onPressed: () => onPressed(context),
text: buttonText,
color: Theme.of(context).primaryColor,
@@ -140,7 +140,7 @@ class _DashboardPageView extends BasePage {
bool get resizeToAvoidBottomInset => false;
@override
Widget get endDrawer => MenuWidget(dashboardViewModel);
Widget get endDrawer => MenuWidget(dashboardViewModel, ValueKey('dashboard_page_drawer_menu_widget_key'));
@override
Widget leading(BuildContext context) {
@@ -28,7 +28,10 @@ class NFTDetailsPage extends BasePage {
bool get resizeToAvoidBottomInset => false;
@override
Widget get endDrawer => MenuWidget(dashboardViewModel);
Widget get endDrawer => MenuWidget(
dashboardViewModel,
ValueKey('nft_details_page_menu_widget_key'),
);
@override
Widget trailing(BuildContext context) {
@@ -1,11 +1,13 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_row.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/order_row.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/trade_row.dart';
import 'package:cake_wallet/themes/extensions/placeholder_theme.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/wallet_type.dart';
@@ -14,9 +16,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/header_row.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/date_section_raw.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/trade_row.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/transaction_raw.dart';
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/date_section_item.dart';
import 'package:intl/intl.dart';
@@ -49,6 +49,7 @@ class TransactionsPage extends StatelessWidget {
return Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 8),
child: DashBoardRoundedCardWidget(
key: ValueKey('transactions_page_syncing_alert_card_key'),
onTap: () {
try {
final uri = Uri.parse(
@@ -64,82 +65,93 @@ class TransactionsPage extends StatelessWidget {
return Container();
}
}),
HeaderRow(dashboardViewModel: dashboardViewModel),
Expanded(child: Observer(builder: (_) {
final items = dashboardViewModel.items;
HeaderRow(
dashboardViewModel: dashboardViewModel,
key: ValueKey('transactions_page_header_row_key'),
),
Expanded(
child: Observer(
builder: (_) {
final items = dashboardViewModel.items;
return items.isNotEmpty
? ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return items.isNotEmpty
? ListView.builder(
key: ValueKey('transactions_page_list_view_builder_key'),
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
if (item is DateSectionItem) {
return DateSectionRaw(date: item.date);
}
if (item is TransactionListItem) {
if (item.hasTokens && item.assetOfTransaction == null) {
return Container();
}
final transaction = item.transaction;
final transactionType = dashboardViewModel.getTransactionType(transaction);
List<String> tags = [];
if (dashboardViewModel.type == WalletType.bitcoin) {
if (bitcoin!.txIsReceivedSilentPayment(transaction)) {
tags.add(S.of(context).silent_payment);
if (item is DateSectionItem) {
return DateSectionRaw(date: item.date, key: item.key);
}
}
if (dashboardViewModel.type == WalletType.litecoin) {
if (bitcoin!.txIsMweb(transaction)) {
tags.add("MWEB");
if (item is TransactionListItem) {
if (item.hasTokens && item.assetOfTransaction == null) {
return Container();
}
final transaction = item.transaction;
final transactionType =
dashboardViewModel.getTransactionType(transaction);
List<String> tags = [];
if (dashboardViewModel.type == WalletType.bitcoin) {
if (bitcoin!.txIsReceivedSilentPayment(transaction)) {
tags.add(S.of(context).silent_payment);
}
}
if (dashboardViewModel.type == WalletType.litecoin) {
if (bitcoin!.txIsMweb(transaction)) {
tags.add("MWEB");
}
}
return Observer(
builder: (_) => TransactionRow(
key: item.key,
onTap: () => Navigator.of(context)
.pushNamed(Routes.transactionDetails, arguments: transaction),
direction: transaction.direction,
formattedDate: DateFormat('HH:mm').format(transaction.date),
formattedAmount: item.formattedCryptoAmount,
formattedFiatAmount:
dashboardViewModel.balanceViewModel.isFiatDisabled
? ''
: item.formattedFiatAmount,
isPending: transaction.isPending,
title:
item.formattedTitle + item.formattedStatus + transactionType,
tags: tags,
),
);
}
}
return Observer(
builder: (_) => TransactionRow(
onTap: () => Navigator.of(context)
.pushNamed(Routes.transactionDetails, arguments: transaction),
direction: transaction.direction,
formattedDate: DateFormat('HH:mm').format(transaction.date),
formattedAmount: item.formattedCryptoAmount,
formattedFiatAmount:
dashboardViewModel.balanceViewModel.isFiatDisabled
? ''
: item.formattedFiatAmount,
isPending: transaction.isPending,
title:
item.formattedTitle + item.formattedStatus + transactionType,
tags: tags,
),
);
}
if (item is AnonpayTransactionListItem) {
final transactionInfo = item.transaction;
if (item is AnonpayTransactionListItem) {
final transactionInfo = item.transaction;
return AnonpayTransactionRow(
key: item.key,
onTap: () => Navigator.of(context).pushNamed(
Routes.anonPayDetailsPage,
arguments: transactionInfo),
currency: transactionInfo.fiatAmount != null
? transactionInfo.fiatEquiv ?? ''
: CryptoCurrency.fromFullName(transactionInfo.coinTo)
.name
.toUpperCase(),
provider: transactionInfo.provider,
amount: transactionInfo.fiatAmount?.toString() ??
(transactionInfo.amountTo?.toString() ?? ''),
createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt),
);
}
return AnonpayTransactionRow(
onTap: () => Navigator.of(context)
.pushNamed(Routes.anonPayDetailsPage, arguments: transactionInfo),
currency: transactionInfo.fiatAmount != null
? transactionInfo.fiatEquiv ?? ''
: CryptoCurrency.fromFullName(transactionInfo.coinTo)
.name
.toUpperCase(),
provider: transactionInfo.provider,
amount: transactionInfo.fiatAmount?.toString() ??
(transactionInfo.amountTo?.toString() ?? ''),
createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt),
);
}
if (item is TradeListItem) {
final trade = item.trade;
if (item is TradeListItem) {
final trade = item.trade;
return Observer(
builder: (_) => TradeRow(
return Observer(
builder: (_) => TradeRow(
key: item.key,
onTap: () => Navigator.of(context)
.pushNamed(Routes.tradeDetails, arguments: trade),
provider: trade.provider,
@@ -148,36 +160,44 @@ class TransactionsPage extends StatelessWidget {
createdAtFormattedDate: trade.createdAt != null
? DateFormat('HH:mm').format(trade.createdAt!)
: null,
formattedAmount: item.tradeFormattedAmount));
}
formattedAmount: item.tradeFormattedAmount,
),
);
}
if (item is OrderListItem) {
final order = item.order;
if (item is OrderListItem) {
final order = item.order;
return Observer(
builder: (_) => OrderRow(
onTap: () => Navigator.of(context)
.pushNamed(Routes.orderDetails, arguments: order),
provider: order.provider,
from: order.from!,
to: order.to!,
createdAtFormattedDate:
DateFormat('HH:mm').format(order.createdAt),
formattedAmount: item.orderFormattedAmount,
));
}
return Observer(
builder: (_) => OrderRow(
key: item.key,
onTap: () => Navigator.of(context)
.pushNamed(Routes.orderDetails, arguments: order),
provider: order.provider,
from: order.from!,
to: order.to!,
createdAtFormattedDate:
DateFormat('HH:mm').format(order.createdAt),
formattedAmount: item.orderFormattedAmount,
),
);
}
return Container(color: Colors.transparent, height: 1);
})
: Center(
child: Text(
S.of(context).placeholder_transactions,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).extension<PlaceholderTheme>()!.color),
),
);
}))
return Container(color: Colors.transparent, height: 1);
})
: Center(
child: Text(
key: ValueKey('transactions_page_placeholder_transactions_text_key'),
S.of(context).placeholder_transactions,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).extension<PlaceholderTheme>()!.color,
),
),
);
},
),
)
],
),
),
@@ -9,6 +9,7 @@ class AnonpayTransactionRow extends StatelessWidget {
required this.currency,
required this.onTap,
required this.amount,
super.key,
});
final VoidCallback? onTap;
@@ -1,42 +1,27 @@
import 'package:flutter/material.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:intl/intl.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/date_formatter.dart';
class DateSectionRaw extends StatelessWidget {
DateSectionRaw({required this.date});
DateSectionRaw({required this.date, super.key});
final DateTime date;
@override
Widget build(BuildContext context) {
final nowDate = DateTime.now();
final diffDays = date.difference(nowDate).inDays;
final isToday = nowDate.day == date.day &&
nowDate.month == date.month &&
nowDate.year == date.year;
final dateSectionDateFormat = DateFormatter.withCurrentLocal(hasTime: false);
var title = "";
if (isToday) {
title = S.of(context).today;
} else if (diffDays == 0) {
title = S.of(context).yesterday;
} else if (diffDays > -7 && diffDays < 0) {
final dateFormat = DateFormat.EEEE();
title = dateFormat.format(date);
} else {
title = dateSectionDateFormat.format(date);
}
final title = DateFormatter.convertDateTimeToReadableString(date);
return Container(
height: 35,
alignment: Alignment.center,
color: Colors.transparent,
child: Text(title,
style: TextStyle(
fontSize: 12,
color: Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor)));
height: 35,
alignment: Alignment.center,
color: Colors.transparent,
child: Text(
title,
style: TextStyle(
fontSize: 12,
color: Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor,
),
),
);
}
}
@@ -7,7 +7,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
class HeaderRow extends StatelessWidget {
HeaderRow({required this.dashboardViewModel});
HeaderRow({required this.dashboardViewModel, super.key});
final DashboardViewModel dashboardViewModel;
@@ -34,6 +34,7 @@ class HeaderRow extends StatelessWidget {
Semantics(
container: true,
child: GestureDetector(
key: ValueKey('transactions_page_header_row_transaction_filter_button_key'),
onTap: () {
showPopUp<void>(
context: context,
@@ -9,7 +9,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class MenuWidget extends StatefulWidget {
MenuWidget(this.dashboardViewModel);
MenuWidget(this.dashboardViewModel, Key? key);
final DashboardViewModel dashboardViewModel;
@@ -193,6 +193,7 @@ class MenuWidgetState extends State<MenuWidget> {
final isLastTile = index == itemCount - 1;
return SettingActionButton(
key: item.key,
isLastTile: isLastTile,
tileHeight: tileHeight,
selectionActive: false,
@@ -12,7 +12,10 @@ class OrderRow extends StatelessWidget {
required this.to,
required this.createdAtFormattedDate,
this.onTap,
this.formattedAmount});
this.formattedAmount,
super.key,
});
final VoidCallback? onTap;
final BuyProviderDescription provider;
final String from;
@@ -22,8 +25,7 @@ class OrderRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final iconColor =
Theme.of(context).extension<OrderTheme>()!.iconColor;
final iconColor = Theme.of(context).extension<OrderTheme>()!.iconColor;
final providerIcon = getBuyProviderIcon(provider, iconColor: iconColor);
@@ -36,46 +38,42 @@ class OrderRow extends StatelessWidget {
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (providerIcon != null) Padding(
padding: EdgeInsets.only(right: 12),
child: providerIcon,
),
if (providerIcon != null)
Padding(
padding: EdgeInsets.only(right: 12),
child: providerIcon,
),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('$from → $to',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor
)),
formattedAmount != null
? Text(formattedAmount! + ' ' + to,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor
))
: Container()
]),
SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(createdAtFormattedDate,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor))
])
],
)
)
mainAxisSize: MainAxisSize.min,
children: [
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
Text('$from → $to',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor)),
formattedAmount != null
? Text(formattedAmount! + ' ' + to,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color:
Theme.of(context).extension<DashboardPageTheme>()!.textColor))
: Container()
]),
SizedBox(height: 5),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
Text(createdAtFormattedDate,
style: TextStyle(
fontSize: 14,
color:
Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor))
])
],
))
],
),
));
}
}
}
@@ -13,6 +13,7 @@ class TradeRow extends StatelessWidget {
required this.createdAtFormattedDate,
this.onTap,
this.formattedAmount,
super.key,
});
final VoidCallback? onTap;
@@ -14,6 +14,7 @@ class TransactionRow extends StatelessWidget {
required this.tags,
required this.title,
required this.onTap,
super.key,
});
final VoidCallback onTap;
@@ -28,33 +29,36 @@ class TransactionRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Container(
padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
color: Colors.transparent,
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
height: 36,
width: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).extension<TransactionTradeTheme>()!.rowsColor),
child: Image.asset(direction == TransactionDirection.incoming
? 'assets/images/down_arrow.png'
: 'assets/images/up_arrow.png'),
),
SizedBox(width: 12),
Expanded(
child: Column(
onTap: onTap,
child: Container(
padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
color: Colors.transparent,
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
height: 36,
width: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).extension<TransactionTradeTheme>()!.rowsColor),
child: Image.asset(direction == TransactionDirection.incoming
? 'assets/images/down_arrow.png'
: 'assets/images/up_arrow.png'),
),
SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
Row(
children: [
Text(title,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
children: [
Text(
title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
@@ -65,28 +69,39 @@ class TransactionRow extends StatelessWidget {
),
Text(formattedAmount,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor))
]),
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
),
)
],
),
SizedBox(height: 5),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
Text(formattedDate,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(formattedDate,
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.dateSectionRowColor)),
Text(
formattedFiatAmount,
style: TextStyle(
fontSize: 14,
color:
Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor)),
Text(formattedFiatAmount,
style: TextStyle(
fontSize: 14,
color:
Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor))
])
fontSize: 14,
color: Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor,
),
)
],
),
],
))
],
),
));
),
)
],
),
),
);
}
}
@@ -112,10 +112,13 @@ class _WalletNameFormState extends State<WalletNameForm> {
context: context,
builder: (_) {
return AlertWithOneAction(
alertTitle: S.current.new_wallet,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
key: ValueKey('new_wallet_page_failure_dialog_key'),
buttonKey: ValueKey('new_wallet_page_failure_dialog_button_key'),
alertTitle: S.current.new_wallet,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
});
}
});
@@ -152,6 +155,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
child: Column(
children: [
TextFormField(
key: ValueKey('new_wallet_page_wallet_name_textformfield_key'),
onChanged: (value) => _walletNewVM.name = value,
controller: _nameController,
textAlign: TextAlign.center,
@@ -182,6 +186,8 @@ class _WalletNameFormState extends State<WalletNameForm> {
suffixIcon: Semantics(
label: S.of(context).generate_name,
child: IconButton(
key: ValueKey(
'new_wallet_page_wallet_name_textformfield_generate_name_button_key'),
onPressed: () async {
final rName = await generateName();
FocusManager.instance.primaryFocus?.unfocus();
@@ -297,6 +303,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
builder: (BuildContext build) => Padding(
padding: EdgeInsets.only(top: 24),
child: SelectButton(
key: ValueKey('new_wallet_page_monero_seed_type_button_key'),
text: widget._seedSettingsViewModel.moneroSeedType.title,
onTap: () async {
await showPopUp<void>(
@@ -318,6 +325,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
padding: EdgeInsets.only(top: 10),
child: SeedLanguageSelector(
key: _languageSelectorKey,
buttonKey: ValueKey('new_wallet_page_seed_language_selector_button_key'),
initialSelected: defaultSeedLanguage,
seedType: _walletNewVM.hasSeedType
? widget._seedSettingsViewModel.moneroSeedType
@@ -336,6 +344,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
Observer(
builder: (context) {
return LoadingPrimaryButton(
key: ValueKey('new_wallet_page_confirm_button_key'),
onPressed: _confirmForm,
text: S.of(context).seed_language_next,
color: Colors.green,
@@ -347,6 +356,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
),
const SizedBox(height: 25),
GestureDetector(
key: ValueKey('new_wallet_page_advanced_settings_button_key'),
onTap: () {
Navigator.of(context).pushNamed(Routes.advancedPrivacySettings, arguments: {
"type": _walletNewVM.type,
@@ -66,6 +66,7 @@ class WalletGroupDescriptionPage extends BasePage {
),
),
PrimaryButton(
key: ValueKey('wallet_group_description_page_create_new_seed_button_key'),
onPressed: () => Navigator.of(context).pushNamed(
Routes.newWallet,
arguments: NewWalletArguments(type: selectedWalletType),
@@ -76,6 +77,7 @@ class WalletGroupDescriptionPage extends BasePage {
),
SizedBox(height: 12),
PrimaryButton(
key: ValueKey('wallet_group_description_page_choose_wallet_group_button_key'),
onPressed: () => Navigator.of(context).pushNamed(
Routes.walletGroupsDisplayPage,
arguments: selectedWalletType,
@@ -56,7 +56,8 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
}
if (isMoneroOnly) {
return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS).isNotEmpty;
return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
.isNotEmpty;
}
return true;
@@ -80,13 +81,12 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
child: Column(
children: <Widget>[
OptionTile(
key: ValueKey('restore_options_from_seeds_button_key'),
onPressed: () =>
Navigator.pushNamed(
context,
Routes.restoreWalletFromSeedKeys,
arguments: widget.isNewInstall,
),
key: ValueKey('restore_options_from_seeds_or_keys_button_key'),
onPressed: () => Navigator.pushNamed(
context,
Routes.restoreWalletFromSeedKeys,
arguments: widget.isNewInstall,
),
image: imageSeedKeys,
title: S.of(context).restore_title_from_seed_keys,
description: S.of(context).restore_description_from_seed_keys,
@@ -107,7 +107,8 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
padding: EdgeInsets.only(top: 24),
child: OptionTile(
key: ValueKey('restore_options_from_hardware_wallet_button_key'),
onPressed: () => Navigator.pushNamed(context, Routes.restoreWalletFromHardwareWallet,
onPressed: () => Navigator.pushNamed(
context, Routes.restoreWalletFromHardwareWallet,
arguments: widget.isNewInstall),
image: imageLedger,
title: S.of(context).restore_title_from_hardware_wallet,
@@ -120,9 +121,9 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
key: ValueKey('restore_options_from_qr_button_key'),
onPressed: () => _onScanQRCode(context),
icon: Icon(
Icons.qr_code_rounded,
color: imageColor,
size: 50,
Icons.qr_code_rounded,
color: imageColor,
size: 50,
),
title: S.of(context).scan_qr_code,
description: S.of(context).cold_or_recover_wallet),
@@ -149,20 +150,20 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
buttonAction: () => Navigator.of(context).pop());
});
});
}
Future<void> _onScanQRCode(BuildContext context) async {
final isCameraPermissionGranted = await PermissionHandler.checkPermission(Permission.camera, context);
final isCameraPermissionGranted =
await PermissionHandler.checkPermission(Permission.camera, context);
if (!isCameraPermissionGranted) return;
bool isPinSet = false;
if (widget.isNewInstall) {
await Navigator.pushNamed(context, Routes.setupPin,
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
setupPinContext.close();
isPinSet = true;
});
setupPinContext.close();
isPinSet = true;
});
}
if (!widget.isNewInstall || isPinSet) {
try {
@@ -174,7 +175,8 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
});
final restoreWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
final restoreFromQRViewModel = getIt.get<WalletRestorationFromQRVM>(param1: restoreWallet.type);
final restoreFromQRViewModel =
getIt.get<WalletRestorationFromQRVM>(param1: restoreWallet.type);
await restoreFromQRViewModel.create(restoreWallet: restoreWallet);
if (restoreFromQRViewModel.state is FailureState) {
@@ -191,6 +191,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
),
if (widget.type == WalletType.monero || widget.type == WalletType.wownero)
GestureDetector(
key: ValueKey('wallet_restore_from_seed_seedtype_picker_button_key'),
onTap: () async {
await showPopUp<void>(
context: context,
@@ -264,6 +265,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
BlockchainHeightWidget(
focusNode: widget.blockHeightFocusNode,
key: blockchainHeightKey,
blockHeightTextFieldKey: ValueKey('wallet_restore_from_seed_blockheight_textfield_key'),
onHeightOrDateEntered: widget.onHeightOrDateEntered,
hasDatePicker: widget.type == WalletType.monero || widget.type == WalletType.wownero,
walletType: widget.type,
+6 -4
View File
@@ -15,13 +15,15 @@ class PreSeedPage extends InfoPage {
String get pageTitle => S.current.pre_seed_title;
@override
String get pageDescription =>
S.current.pre_seed_description(seedPhraseLength.toString());
String get pageDescription => S.current.pre_seed_description(seedPhraseLength.toString());
@override
String get buttonText => S.current.pre_seed_button_text;
@override
void Function(BuildContext) get onPressed => (BuildContext context) =>
Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true);
Key? get buttonKey => ValueKey('pre_seed_page_button_key');
@override
void Function(BuildContext) get onPressed =>
(BuildContext context) => Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true);
}
+60 -35
View File
@@ -33,16 +33,22 @@ class WalletSeedPage extends BasePage {
void onClose(BuildContext context) async {
if (isNewWalletCreated) {
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).seed_alert_title,
alertContent: S.of(context).seed_alert_content,
leftButtonText: S.of(context).seed_alert_back,
rightButtonText: S.of(context).seed_alert_yes,
actionLeftButton: () => Navigator.of(context).pop(false),
actionRightButton: () => Navigator.of(context).pop(true));
}) ??
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertDialogKey: ValueKey('wallet_seed_page_seed_alert_dialog_key'),
alertRightActionButtonKey:
ValueKey('wallet_seed_page_seed_alert_confirm_button_key'),
alertLeftActionButtonKey: ValueKey('wallet_seed_page_seed_alert_back_button_key'),
alertTitle: S.of(context).seed_alert_title,
alertContent: S.of(context).seed_alert_content,
leftButtonText: S.of(context).seed_alert_back,
rightButtonText: S.of(context).seed_alert_yes,
actionLeftButton: () => Navigator.of(context).pop(false),
actionRightButton: () => Navigator.of(context).pop(true),
);
},
) ??
false;
if (confirmed) {
@@ -62,6 +68,7 @@ class WalletSeedPage extends BasePage {
Widget trailing(BuildContext context) {
return isNewWalletCreated
? GestureDetector(
key: ValueKey('wallet_seed_page_next_button_key'),
onTap: () => onClose(context),
child: Container(
width: 100,
@@ -74,9 +81,9 @@ class WalletSeedPage extends BasePage {
child: Text(
S.of(context).seed_language_next,
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.w600, color: Theme.of(context)
.extension<CakeTextTheme>()!
.buttonTextColor),
fontSize: 14,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor),
),
),
)
@@ -93,7 +100,8 @@ class WalletSeedPage extends BasePage {
padding: EdgeInsets.all(24),
alignment: Alignment.center,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
constraints:
BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
@@ -106,6 +114,7 @@ class WalletSeedPage extends BasePage {
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
key: ValueKey('wallet_seed_page_wallet_name_text_key'),
walletSeedViewModel.name,
style: TextStyle(
fontSize: 20,
@@ -115,12 +124,14 @@ class WalletSeedPage extends BasePage {
Padding(
padding: EdgeInsets.only(top: 20, left: 16, right: 16),
child: Text(
key: ValueKey('wallet_seed_page_wallet_seed_text_key'),
walletSeedViewModel.seed,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
color:
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
),
)
],
@@ -132,12 +143,18 @@ class WalletSeedPage extends BasePage {
? Padding(
padding: EdgeInsets.only(bottom: 43, left: 43, right: 43),
child: Text(
key: ValueKey(
'wallet_seed_page_wallet_seed_reminder_text_key',
),
S.of(context).seed_reminder,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor),
fontSize: 12,
fontWeight: FontWeight.normal,
color: Theme.of(context)
.extension<TransactionTradeTheme>()!
.detailsTitlesColor,
),
),
)
: Offstage(),
@@ -145,9 +162,10 @@ class WalletSeedPage extends BasePage {
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: PrimaryButton(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: PrimaryButton(
key: ValueKey('wallet_seed_page_save_seeds_button_key'),
onPressed: () {
ShareUtil.share(
text: walletSeedViewModel.seed,
@@ -156,22 +174,29 @@ class WalletSeedPage extends BasePage {
},
text: S.of(context).save,
color: Colors.green,
textColor: Colors.white),
)),
textColor: Colors.white,
),
),
),
Flexible(
child: Container(
padding: EdgeInsets.only(left: 8.0),
child: Builder(
child: Container(
padding: EdgeInsets.only(left: 8.0),
child: Builder(
builder: (context) => PrimaryButton(
onPressed: () {
ClipboardUtil.setSensitiveDataToClipboard(
ClipboardData(text: walletSeedViewModel.seed));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
text: S.of(context).copy,
color: Theme.of(context).extension<PinCodeTheme>()!.indicatorsColor,
textColor: Colors.white)),
))
key: ValueKey('wallet_seed_page_copy_seeds_button_key'),
onPressed: () {
ClipboardUtil.setSensitiveDataToClipboard(
ClipboardData(text: walletSeedViewModel.seed),
);
showBar<void>(context, S.of(context).copied_to_clipboard);
},
text: S.of(context).copy,
color: Theme.of(context).extension<PinCodeTheme>()!.indicatorsColor,
textColor: Colors.white,
),
),
),
)
],
)
],
@@ -16,7 +16,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class SecurityBackupPage extends BasePage {
SecurityBackupPage(this._securitySettingsViewModel, this._authService, [this._isHardwareWallet = false]);
SecurityBackupPage(this._securitySettingsViewModel, this._authService,
[this._isHardwareWallet = false]);
final AuthService _authService;
@@ -30,10 +31,13 @@ class SecurityBackupPage extends BasePage {
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(mainAxisSize: MainAxisSize.min, children: [
padding: EdgeInsets.only(top: 10),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (!_isHardwareWallet)
SettingsCellWithArrow(
key: ValueKey('security_backup_page_show_keys_button_key'),
title: S.current.show_keys,
handler: (_) => _authService.authenticateAction(
context,
@@ -44,15 +48,17 @@ class SecurityBackupPage extends BasePage {
),
if (!SettingsStoreBase.walletPasswordDirectInput)
SettingsCellWithArrow(
key: ValueKey('security_backup_page_create_backup_button_key'),
title: S.current.create_backup,
handler: (_) => _authService.authenticateAction(
context,
route: Routes.backup,
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
conditionToDetermineIfToUse2FA:
_securitySettingsViewModel.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
),
),
SettingsCellWithArrow(
key: ValueKey('security_backup_page_change_pin_button_key'),
title: S.current.settings_change_pin,
handler: (_) => _authService.authenticateAction(
context,
@@ -60,28 +66,30 @@ class SecurityBackupPage extends BasePage {
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
setupPinContext.close();
},
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
conditionToDetermineIfToUse2FA:
_securitySettingsViewModel.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
),
),
if (DeviceInfo.instance.isMobile || Platform.isMacOS || Platform.isLinux)
Observer(builder: (_) {
return SettingsSwitcherCell(
key: ValueKey('security_backup_page_allow_biometrics_button_key'),
title: S.current.settings_allow_biometrical_authentication,
value: _securitySettingsViewModel.allowBiometricalAuthentication,
onValueChange: (BuildContext context, bool value) {
if (value) {
_authService.authenticateAction(context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (isAuthenticatedSuccessfully) {
if (await _securitySettingsViewModel.biometricAuthenticated()) {
_authService.authenticateAction(
context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (isAuthenticatedSuccessfully) {
if (await _securitySettingsViewModel.biometricAuthenticated()) {
_securitySettingsViewModel
.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
}
} else {
_securitySettingsViewModel
.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
}
} else {
_securitySettingsViewModel
.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
}
},
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
@@ -93,6 +101,7 @@ class SecurityBackupPage extends BasePage {
}),
Observer(builder: (_) {
return SettingsPickerCell<PinCodeRequiredDuration>(
key: ValueKey('security_backup_page_require_pin_after_button_key'),
title: S.current.require_pin_after,
items: PinCodeRequiredDuration.values,
selectedItem: _securitySettingsViewModel.pinCodeRequiredDuration,
@@ -104,14 +113,15 @@ class SecurityBackupPage extends BasePage {
Observer(
builder: (context) {
return SettingsCellWithArrow(
key: ValueKey('security_backup_page_totp_2fa_button_key'),
title: _securitySettingsViewModel.useTotp2FA
? S.current.modify_2fa
: S.current.setup_2fa,
handler: (_) => _authService.authenticateAction(
context,
route: _securitySettingsViewModel.useTotp2FA
? Routes.modify2FAPage
: Routes.setup2faInfoPage,
handler: (_) => _authService.authenticateAction(
context,
route: _securitySettingsViewModel.useTotp2FA
? Routes.modify2FAPage
: Routes.setup2faInfoPage,
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
),
@@ -3,8 +3,11 @@ import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
class SettingsCellWithArrow extends StandardListRow {
SettingsCellWithArrow({required String title, required Function(BuildContext context)? handler})
: super(title: title, isSelected: false, onTap: handler);
SettingsCellWithArrow({
required String title,
required Function(BuildContext context)? handler,
Key? key,
}) : super(title: title, isSelected: false, onTap: handler, key: key);
@override
Widget buildTrailing(BuildContext context) => Image.asset('assets/images/select_arrow.png',
@@ -5,19 +5,21 @@ import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
class SettingsPickerCell<ItemType> extends StandardListRow {
SettingsPickerCell(
{required String title,
required this.selectedItem,
required this.items,
this.displayItem,
this.images,
this.searchHintText,
this.isGridView = false,
this.matchingCriteria,
this.onItemSelected})
: super(
SettingsPickerCell({
required String title,
required this.selectedItem,
required this.items,
this.displayItem,
this.images,
this.searchHintText,
this.isGridView = false,
this.matchingCriteria,
this.onItemSelected,
Key? key,
}) : super(
title: title,
isSelected: false,
key: key,
onTap: (BuildContext context) async {
final selectedAtIndex = items.indexOf(selectedItem);
@@ -10,7 +10,8 @@ class SettingsSwitcherCell extends StandardListRow {
Decoration? decoration,
this.leading,
void Function(BuildContext context)? onTap,
}) : super(title: title, isSelected: false, decoration: decoration, onTap: onTap);
Key? key,
}) : super(title: title, isSelected: false, decoration: decoration, onTap: onTap, key: key);
final bool value;
final void Function(BuildContext context, bool value)? onValueChange;
@@ -4,7 +4,6 @@ import 'package:cake_wallet/src/screens/InfoPage.dart';
import 'package:flutter/cupertino.dart';
class Setup2FAInfoPage extends InfoPage {
@override
String get pageTitle => S.current.pre_seed_title;
@@ -15,6 +14,9 @@ class Setup2FAInfoPage extends InfoPage {
String get buttonText => S.current.understand;
@override
void Function(BuildContext) get onPressed => (BuildContext context) =>
Navigator.of(context).popAndPushNamed(Routes.setup_2faPage);
Key? get buttonKey => ValueKey('setup_2fa_info_page_button_key');
@override
void Function(BuildContext) get onPressed =>
(BuildContext context) => Navigator.of(context).popAndPushNamed(Routes.setup_2faPage);
}
@@ -1,7 +1,12 @@
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
import 'package:flutter/foundation.dart';
class BlockExplorerListItem extends TransactionDetailsListItem {
BlockExplorerListItem({required String title, required String value, required this.onTap})
: super(title: title, value: value);
BlockExplorerListItem({
required String title,
required String value,
required this.onTap,
Key? key,
}) : super(title: title, value: value, key: key);
final Function() onTap;
}
@@ -1,18 +1,20 @@
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
import 'package:flutter/widgets.dart';
class StandardPickerListItem<T> extends TransactionDetailsListItem {
StandardPickerListItem(
{required String title,
required String value,
required this.items,
required this.displayItem,
required this.onSliderChanged,
required this.onItemSelected,
required this.selectedIdx,
required this.customItemIndex,
this.maxValue,
required this.customValue})
: super(title: title, value: value);
StandardPickerListItem({
required String title,
required String value,
required this.items,
required this.displayItem,
required this.onSliderChanged,
required this.onItemSelected,
required this.selectedIdx,
required this.customItemIndex,
this.maxValue,
required this.customValue,
Key? key,
}) : super(title: title, value: value, key: key);
final List<T> items;
final String Function(T item, double sliderValue) displayItem;
@@ -1,6 +1,9 @@
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
class StandartListItem extends TransactionDetailsListItem {
StandartListItem({required String title, required String value})
: super(title: title, value: value);
StandartListItem({
required String super.title,
required String super.value,
super.key,
});
}
@@ -1,11 +1,17 @@
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
import 'package:flutter/foundation.dart';
class TextFieldListItem extends TransactionDetailsListItem {
TextFieldListItem({
required String title,
required String value,
required this.onSubmitted})
: super(title: title, value: value);
required this.onSubmitted,
Key? key,
}) : super(
title: title,
value: value,
key: key,
);
final Function(String value) onSubmitted;
}
@@ -1,6 +1,9 @@
import 'package:flutter/foundation.dart';
abstract class TransactionDetailsListItem {
TransactionDetailsListItem({required this.title, required this.value});
TransactionDetailsListItem({required this.title, required this.value, this.key});
final String title;
final String value;
}
final Key? key;
}
@@ -33,38 +33,42 @@ class TransactionDetailsPage extends BasePage {
children: [
Expanded(
child: SectionStandardList(
sectionCount: 1,
itemCounter: (int _) => transactionDetailsViewModel.items.length,
itemBuilder: (__, index) {
final item = transactionDetailsViewModel.items[index];
sectionCount: 1,
itemCounter: (int _) => transactionDetailsViewModel.items.length,
itemBuilder: (__, index) {
final item = transactionDetailsViewModel.items[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(
key: item.key,
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 BlockExplorerListItem) {
return GestureDetector(
onTap: item.onTap,
child: ListRow(title: '${item.title}:', value: item.value),
);
}
if (item is BlockExplorerListItem) {
return GestureDetector(
key: item.key,
onTap: item.onTap,
child: ListRow(title: '${item.title}:', value: item.value),
);
}
if (item is TextFieldListItem) {
return TextFieldListRow(
title: item.title,
value: item.value,
onSubmitted: item.onSubmitted,
);
}
if (item is TextFieldListItem) {
return TextFieldListRow(
key: item.key,
title: item.title,
value: item.value,
onSubmitted: item.onSubmitted,
);
}
return Container();
}),
return Container();
},
),
),
Observer(
builder: (_) {
@@ -1,7 +1,12 @@
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
import 'package:flutter/foundation.dart';
class StandardExpandableListItem<T> extends TransactionDetailsListItem {
StandardExpandableListItem({required String title, required this.expandableItems})
: super(title: title, value: '');
StandardExpandableListItem({
required String title,
required this.expandableItems,
Key? key,
}) : super(title: title, value: '', key: key);
final List<T> expandableItems;
}
@@ -4,12 +4,14 @@ import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
import 'package:flutter/material.dart';
class TextFieldListRow extends StatefulWidget {
TextFieldListRow(
{required this.title,
required this.value,
this.titleFontSize = 14,
this.valueFontSize = 16,
this.onSubmitted});
TextFieldListRow({
required this.title,
required this.value,
this.titleFontSize = 14,
this.valueFontSize = 16,
this.onSubmitted,
super.key,
});
final String title;
final String value;
@@ -25,23 +25,25 @@ class WalletKeysPage extends BasePage {
@override
Widget trailing(BuildContext context) => IconButton(
onPressed: () async {
final url = await walletKeysViewModel.url;
key: ValueKey('wallet_keys_page_fullscreen_qr_button_key'),
onPressed: () async {
final url = await walletKeysViewModel.url;
BrightnessUtil.changeBrightnessForFunction(() async {
await Navigator.pushNamed(
context,
Routes.fullscreenQR,
arguments: QrViewData(data: url.toString(), version: QrVersions.auto),
);
});
},
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent,
icon: Image.asset(
'assets/images/qr_code_icon.png',
));
BrightnessUtil.changeBrightnessForFunction(() async {
await Navigator.pushNamed(
context,
Routes.fullscreenQR,
arguments: QrViewData(data: url.toString(), version: QrVersions.auto),
);
});
},
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent,
icon: Image.asset(
'assets/images/qr_code_icon.png',
),
);
@override
Widget body(BuildContext context) {
@@ -60,6 +62,7 @@ class WalletKeysPage extends BasePage {
child: Padding(
padding: const EdgeInsets.all(8.0),
child: AutoSizeText(
key: ValueKey('wallet_keys_page_share_warning_text_key'),
S.of(context).do_not_share_warning_text.toUpperCase(),
textAlign: TextAlign.center,
maxLines: 4,
@@ -92,6 +95,7 @@ class WalletKeysPage extends BasePage {
final item = walletKeysViewModel.items[index];
return GestureDetector(
key: item.key,
onTap: () {
ClipboardUtil.setSensitiveDataToClipboard(ClipboardData(text: item.value));
showBar<void>(context, S.of(context).copied_key_to_clipboard(item.title));
@@ -318,6 +318,7 @@ class WalletListBodyState extends State<WalletListBody> {
child: Column(
children: <Widget>[
PrimaryImageButton(
key: ValueKey('wallet_list_page_create_new_wallet_button_key'),
onPressed: () {
//TODO(David): Find a way to optimize this
if (isSingleCoin) {
@@ -359,6 +360,7 @@ class WalletListBodyState extends State<WalletListBody> {
),
SizedBox(height: 10.0),
PrimaryImageButton(
key: ValueKey('wallet_list_page_restore_wallet_button_key'),
onPressed: () {
if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
widget.authService.authenticateAction(
@@ -23,6 +23,7 @@ class BlockchainHeightWidget extends StatefulWidget {
this.doSingleScan = false,
this.bitcoinMempoolAPIEnabled,
required this.walletType,
this.blockHeightTextFieldKey,
}) : super(key: key);
final Function(int)? onHeightChange;
@@ -35,6 +36,7 @@ class BlockchainHeightWidget extends StatefulWidget {
final Future<bool>? bitcoinMempoolAPIEnabled;
final Function()? toggleSingleScan;
final WalletType walletType;
final Key? blockHeightTextFieldKey;
@override
State<StatefulWidget> createState() => BlockchainHeightState();
@@ -81,6 +83,7 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
child: Container(
padding: EdgeInsets.only(top: 20.0, bottom: 10.0),
child: BaseTextFormField(
key: widget.blockHeightTextFieldKey,
focusNode: widget.focusNode,
controller: restoreHeightController,
keyboardType:
@@ -15,6 +15,7 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
this.icon,
this.onClose,
this.customBorder,
super.key,
});
final VoidCallback onTap;
+9 -9
View File
@@ -2,14 +2,14 @@ import 'package:cake_wallet/themes/extensions/option_tile_theme.dart';
import 'package:flutter/material.dart';
class OptionTile extends StatelessWidget {
const OptionTile(
{required this.onPressed,
this.image,
this.icon,
required this.title,
required this.description,
super.key})
: assert(image!=null || icon!=null);
const OptionTile({
required this.onPressed,
this.image,
this.icon,
required this.title,
required this.description,
super.key,
}) : assert(image != null || icon != null);
final VoidCallback onPressed;
final Image? image;
@@ -34,7 +34,7 @@ class OptionTile extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
icon ?? image!,
icon ?? image!,
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 16),
+3
View File
@@ -2,6 +2,7 @@
import 'dart:math';
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/transaction_priority.dart';
@@ -310,6 +311,8 @@ class _PickerState<Item> extends State<Picker<Item>> {
itemName = item.name;
} else if (item is TransactionPriority) {
itemName = item.title;
} else if (item is MoneroSeedType) {
itemName = item.title;
} else {
itemName = '';
}
+8 -3
View File
@@ -6,12 +6,16 @@ import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
class SeedLanguageSelector extends StatefulWidget {
SeedLanguageSelector(
{Key? key, required this.initialSelected, this.seedType = MoneroSeedType.defaultSeedType})
: super(key: key);
SeedLanguageSelector({
required this.initialSelected,
this.seedType = MoneroSeedType.defaultSeedType,
this.buttonKey,
Key? key,
}) : super(key: key);
final String initialSelected;
final MoneroSeedType seedType;
final Key? buttonKey;
@override
SeedLanguageSelectorState createState() => SeedLanguageSelectorState(selected: initialSelected);
@@ -25,6 +29,7 @@ class SeedLanguageSelectorState extends State<SeedLanguageSelector> {
@override
Widget build(BuildContext context) {
return SelectButton(
key: widget.buttonKey,
image: null,
text:
"${seedLanguages.firstWhere((e) => e.name == selected).nameLocalized} (${S.of(context).seed_language})",
+12
View File
@@ -5,9 +5,11 @@ import 'package:flutter/material.dart';
class SettingActions {
final String Function(BuildContext) name;
final String image;
final Key key;
final void Function(BuildContext) onTap;
SettingActions._({
required this.key,
required this.name,
required this.image,
required this.onTap,
@@ -39,6 +41,7 @@ class SettingActions {
];
static SettingActions silentPaymentsSettingAction = SettingActions._(
key: ValueKey('dashboard_page_menu_widget_silent_payment_settings_button_key'),
name: (context) => S.of(context).silent_payments_settings,
image: 'assets/images/bitcoin_menu.png',
onTap: (BuildContext context) {
@@ -48,6 +51,7 @@ class SettingActions {
);
static SettingActions litecoinMwebSettingAction = SettingActions._(
key: ValueKey('dashboard_page_menu_widget_litecoin_mweb_settings_button_key'),
name: (context) => S.of(context).litecoin_mweb_settings,
image: 'assets/images/litecoin_menu.png',
onTap: (BuildContext context) {
@@ -57,6 +61,7 @@ class SettingActions {
);
static SettingActions connectionSettingAction = SettingActions._(
key: ValueKey('dashboard_page_menu_widget_connection_and_sync_settings_button_key'),
name: (context) => S.of(context).connection_sync,
image: 'assets/images/nodes_menu.png',
onTap: (BuildContext context) {
@@ -66,6 +71,7 @@ class SettingActions {
);
static SettingActions walletSettingAction = SettingActions._(
key: ValueKey('dashboard_page_menu_widget_wallet_menu_button_key'),
name: (context) => S.of(context).wallets,
image: 'assets/images/wallet_menu.png',
onTap: (BuildContext context) {
@@ -75,6 +81,7 @@ class SettingActions {
);
static SettingActions addressBookSettingAction = SettingActions._(
key: ValueKey('dashboard_page_menu_widget_address_book_button_key'),
name: (context) => S.of(context).address_book_menu,
image: 'assets/images/open_book_menu.png',
onTap: (BuildContext context) {
@@ -84,6 +91,7 @@ class SettingActions {
);
static SettingActions securityBackupSettingAction = SettingActions._(
key: ValueKey('dashboard_page_menu_widget_security_and_backup_button_key'),
name: (context) => S.of(context).security_and_backup,
image: 'assets/images/key_menu.png',
onTap: (BuildContext context) {
@@ -93,6 +101,7 @@ class SettingActions {
);
static SettingActions privacySettingAction = SettingActions._(
key: ValueKey('dashboard_page_menu_widget_privacy_settings_button_key'),
name: (context) => S.of(context).privacy,
image: 'assets/images/privacy_menu.png',
onTap: (BuildContext context) {
@@ -102,6 +111,7 @@ class SettingActions {
);
static SettingActions displaySettingAction = SettingActions._(
key: ValueKey('dashboard_page_menu_widget_display_settings_button_key'),
name: (context) => S.of(context).display_settings,
image: 'assets/images/eye_menu.png',
onTap: (BuildContext context) {
@@ -111,6 +121,7 @@ class SettingActions {
);
static SettingActions otherSettingAction = SettingActions._(
key: ValueKey('dashboard_page_menu_widget_other_settings_button_key'),
name: (context) => S.of(context).other_settings,
image: 'assets/images/settings_menu.png',
onTap: (BuildContext context) {
@@ -120,6 +131,7 @@ class SettingActions {
);
static SettingActions supportSettingAction = SettingActions._(
key: ValueKey('dashboard_page_menu_widget_support_settings_button_key'),
name: (context) => S.of(context).settings_support,
image: 'assets/images/question_mark.png',
onTap: (BuildContext context) {
+7 -1
View File
@@ -4,7 +4,13 @@ import 'package:cake_wallet/src/widgets/standard_list_status_row.dart';
import 'package:flutter/material.dart';
class StandardListRow extends StatelessWidget {
StandardListRow({required this.title, required this.isSelected, this.onTap, this.decoration});
StandardListRow({
required this.title,
required this.isSelected,
this.onTap,
this.decoration,
super.key,
});
final String title;
final bool isSelected;