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

This commit is contained in:
Blazebrain 2024-06-27 07:17:06 +01:00
parent 8ec38e10ff
commit 66af74f0c0
16 changed files with 315 additions and 64 deletions

View file

@ -1,5 +1,5 @@
import 'package:cake_wallet/main.dart' as app;
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
@ -7,6 +7,7 @@ import 'package:integration_test/integration_test.dart';
import 'robots/dashboard_page_robot.dart';
import 'robots/disclaimer_page_robot.dart';
import 'robots/exchange_page_robot.dart';
import 'robots/new_wallet_type_page_robot.dart';
import 'robots/restore_from_seed_or_key_robot.dart';
import 'robots/restore_options_page_robot.dart';
@ -32,6 +33,7 @@ void main() {
NewWalletTypePageRobot newWalletTypePageRobot;
RestoreFromSeedOrKeysPageRobot restoreFromSeedOrKeysPageRobot;
DashboardPageRobot dashboardPageRobot;
ExchangePageRobot exchangePageRobot;
group('Startup Test', () {
testWidgets('Test for Exchange flow using Restore Wallet - Exchanging USDT(Sol) to SOL',
@ -43,6 +45,7 @@ void main() {
newWalletTypePageRobot = NewWalletTypePageRobot(tester);
restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(tester);
dashboardPageRobot = DashboardPageRobot(tester);
exchangePageRobot = ExchangePageRobot(tester);
await app.main();
await tester.pumpAndSettle();
@ -139,14 +142,28 @@ void main() {
await restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonTapped();
// ----------- RestoreFromSeedOrKeys Page -------------
await dashboardPageRobot.isDashboardPage();
dashboardPageRobot.confirmServiceUpdateButtonDisplays();
dashboardPageRobot.confirmMenuButtonDisplays();
dashboardPageRobot.confirmSyncIndicatorButtonDisplays();
await dashboardPageRobot.confirmRightCryptoAssetTitleDisplaysPerPageView(WalletType.solana);
// await dashboardPageRobot.isDashboardPage();
// dashboardPageRobot.confirmServiceUpdateButtonDisplays();
// dashboardPageRobot.confirmMenuButtonDisplays();
// dashboardPageRobot.confirmSyncIndicatorButtonDisplays();
// await dashboardPageRobot.confirmRightCryptoAssetTitleDisplaysPerPageView(WalletType.solana);
await dashboardPageRobot.navigateToExchangePage();
await Future.delayed(Duration(seconds: 5));
await Future.delayed(Duration(seconds: 2));
// ----------- Exchange Page -------------
await exchangePageRobot.isExchangePage();
exchangePageRobot.hasTitle();
exchangePageRobot.hasResetButton();
await exchangePageRobot.displayBothExchangeCards();
exchangePageRobot.confirmRightComponentsDisplayOnDepositExchangeCards();
exchangePageRobot.confirmRightComponentsDisplayOnReceiveExchangeCards();
await exchangePageRobot.selectDepositCurrency(CryptoCurrency.btc);
await exchangePageRobot.selectReceiveCurrency(CryptoCurrency.usdtSol);
await Future.delayed(Duration(seconds: 10));
});
});
}

View file

@ -29,7 +29,7 @@ class CommonTestCases {
expect(typeWidget, findsOneWidget);
}
void hasKey(String key) {
void hasValueKey(String key) {
final typeWidget = find.byKey(ValueKey(key));
expect(typeWidget, findsOneWidget);
}
@ -51,5 +51,19 @@ class CommonTestCases {
await tester.pumpAndSettle();
}
Future<void> defaultSleepTime({int seconds = 2})async => await Future.delayed(Duration(seconds: seconds));
Future<void> scrollUntilVisible(String childKey, String parentScrollableKey, {double delta = 300}) async {
final scrollableWidget = find.descendant(
of: find.byKey(Key(parentScrollableKey)),
matching: find.byType(Scrollable),
);
await tester.scrollUntilVisible(
find.byKey(ValueKey(childKey)),
delta,
scrollable: scrollableWidget,
);
}
Future<void> defaultSleepTime({int seconds = 2}) async =>
await Future.delayed(Duration(seconds: seconds));
}

View file

@ -16,15 +16,15 @@ class DashboardPageRobot {
}
void confirmServiceUpdateButtonDisplays() {
commonTestCases.hasKey('dashboard_page_services_update_button_key');
commonTestCases.hasValueKey('dashboard_page_services_update_button_key');
}
void confirmSyncIndicatorButtonDisplays() {
commonTestCases.hasKey('dashboard_page_sync_indicator_button_key');
commonTestCases.hasValueKey('dashboard_page_sync_indicator_button_key');
}
void confirmMenuButtonDisplays() {
commonTestCases.hasKey('dashboard_page_wallet_menu_button_key');
commonTestCases.hasValueKey('dashboard_page_wallet_menu_button_key');
}
Future<void> confirmRightCryptoAssetTitleDisplaysPerPageView(WalletType type,

View file

@ -0,0 +1,152 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import '../components/common_checks.dart';
class ExchangePageRobot {
ExchangePageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
final WidgetTester tester;
late CommonTestCases commonTestCases;
Future<void> isExchangePage() async {
await commonTestCases.isSpecificPage<ExchangePage>();
}
void hasTitle() {
// commonTestCases.hasText(S.current.exchange);
}
void hasResetButton() {
commonTestCases.hasText(S.current.reset);
}
void displaysPresentProviderPicker() {
commonTestCases.hasType<PresentProviderPicker>();
}
Future<void> displayBothExchangeCards() async {
final ExchangePage exchangeCard = tester.widget<ExchangePage>(
find.byType(ExchangePage),
);
final depositKey = exchangeCard.depositKey;
final receiveKey = exchangeCard.receiveKey;
final depositExchangeCard = find.byKey(depositKey);
expect(depositExchangeCard, findsOneWidget);
final receiveExchangeCard = find.byKey(receiveKey);
expect(receiveExchangeCard, findsOneWidget);
}
void confirmRightComponentsDisplayOnDepositExchangeCards() {
ExchangePage exchangePage = tester.widget(find.byType(ExchangePage));
final exchangeViewModel = exchangePage.exchangeViewModel;
final depositCardPrefix = 'deposit_exchange_card';
commonTestCases.hasValueKey('${depositCardPrefix}_title_key');
commonTestCases.hasValueKey('${depositCardPrefix}_currency_picker_button_key');
commonTestCases.hasValueKey('${depositCardPrefix}_selected_currency_text_key');
commonTestCases.hasValueKey('${depositCardPrefix}_amount_textfield_key');
commonTestCases.hasValueKey('${depositCardPrefix}_min_limit_text_key');
final initialCurrency = exchangeViewModel.depositCurrency;
if (initialCurrency.tag != null) {
commonTestCases.hasValueKey('${depositCardPrefix}_selected_currency_tag_text_key');
}
if (exchangeViewModel.hasAllAmount) {
commonTestCases.hasValueKey('${depositCardPrefix}_send_all_button_key');
}
if (exchangeViewModel.isMoneroWallet) {
commonTestCases.hasValueKey('${depositCardPrefix}_address_book_button_key');
}
if (exchangeViewModel.isDepositAddressEnabled) {
commonTestCases.hasValueKey('${depositCardPrefix}_editable_address_textfield_key');
} else {
commonTestCases.hasValueKey('${depositCardPrefix}_non_editable_address_textfield_key');
commonTestCases.hasValueKey('${depositCardPrefix}_copy_refund_address_button_key');
}
// commonTestCases.hasValueKey('${depositCardPrefix}_max_limit_text_key');
}
void confirmRightComponentsDisplayOnReceiveExchangeCards() {
ExchangePage exchangePage = tester.widget(find.byType(ExchangePage));
final exchangeViewModel = exchangePage.exchangeViewModel;
final receiveCardPrefix = 'receive_exchange_card';
commonTestCases.hasValueKey('${receiveCardPrefix}_title_key');
commonTestCases.hasValueKey('${receiveCardPrefix}_currency_picker_button_key');
commonTestCases.hasValueKey('${receiveCardPrefix}_selected_currency_text_key');
commonTestCases.hasValueKey('${receiveCardPrefix}_amount_textfield_key');
commonTestCases.hasValueKey('${receiveCardPrefix}_min_limit_text_key');
final initialCurrency = exchangeViewModel.receiveCurrency;
if (initialCurrency.tag != null) {
commonTestCases.hasValueKey('${receiveCardPrefix}_selected_currency_tag_text_key');
}
if (exchangeViewModel.hasAllAmount) {
commonTestCases.hasValueKey('${receiveCardPrefix}_send_all_button_key');
}
if (exchangeViewModel.isMoneroWallet) {
commonTestCases.hasValueKey('${receiveCardPrefix}_address_book_button_key');
}
commonTestCases.hasValueKey('${receiveCardPrefix}_editable_address_textfield_key');
}
Future<void> selectDepositCurrency(CryptoCurrency depositCurrency) async {
final depositPrefix = 'deposit_exchange_card';
final currencyPickerKey = '${depositPrefix}_currency_picker_button_key';
final currencyPickerDialogKey = '${depositPrefix}_currency_picker_dialog_button_key';
await commonTestCases.tapItemByKey(currencyPickerKey);
commonTestCases.hasValueKey(currencyPickerDialogKey);
await commonTestCases.scrollUntilVisible(
'picker_items_index_${depositCurrency.name}_button_key',
'picker_scrollbar_key',
);
await commonTestCases.defaultSleepTime();
await commonTestCases.tapItemByKey('picker_items_index_${depositCurrency.name}_button_key');
await commonTestCases.defaultSleepTime();
}
Future<void> selectReceiveCurrency(CryptoCurrency receiveCurrency) async {
final receivePrefix = 'receive_exchange_card';
final currencyPickerKey = '${receivePrefix}_currency_picker_button_key';
final currencyPickerDialogKey = '${receivePrefix}_currency_picker_dialog_button_key';
await commonTestCases.tapItemByKey(currencyPickerKey);
commonTestCases.hasValueKey(currencyPickerDialogKey);
await commonTestCases.scrollUntilVisible(
'picker_items_index_${receiveCurrency.name}_button_key',
'picker_scrollbar_key',
);
await commonTestCases.defaultSleepTime();
await commonTestCases.tapItemByKey('picker_items_index_${receiveCurrency.name}_button_key');
await commonTestCases.defaultSleepTime();
}
Future<void> enterDepositAddress(String depositAddress) async {
final amountTextField = find.byKey(ValueKey('deposit_exchange_card_amount_textfield_key'));
}
Future<void> enterReceiveAddress(String receiveAddress) async {}
}

View file

@ -21,10 +21,10 @@ class RestoreFromSeedOrKeysPageRobot {
commonTestCases.hasText(S.current.enter_seed_phrase);
commonTestCases.hasText(S.current.restore_title_from_seed);
commonTestCases.hasKey('wallet_restore_from_seed_wallet_name_textfield_key');
commonTestCases.hasKey('wallet_restore_from_seed_wallet_name_refresh_button_key');
commonTestCases.hasKey('wallet_restore_from_seed_wallet_seeds_paste_button_key');
commonTestCases.hasKey('wallet_restore_from_seed_wallet_seeds_textfield_key');
commonTestCases.hasValueKey('wallet_restore_from_seed_wallet_name_textfield_key');
commonTestCases.hasValueKey('wallet_restore_from_seed_wallet_name_refresh_button_key');
commonTestCases.hasValueKey('wallet_restore_from_seed_wallet_seeds_paste_button_key');
commonTestCases.hasValueKey('wallet_restore_from_seed_wallet_seeds_textfield_key');
commonTestCases.hasText(S.current.private_key, hasWidget: false);
commonTestCases.hasText(S.current.restore_title_from_keys, hasWidget: false);
@ -43,11 +43,11 @@ class RestoreFromSeedOrKeysPageRobot {
}
void confirmRestoreButtonDisplays() {
commonTestCases.hasKey('wallet_restore_seed_or_key_restore_button_key');
commonTestCases.hasValueKey('wallet_restore_seed_or_key_restore_button_key');
}
void confirmAdvancedSettingButtonDisplays() {
commonTestCases.hasKey('wallet_restore_advanced_settings_button_key');
commonTestCases.hasValueKey('wallet_restore_advanced_settings_button_key');
}
Future<void> enterWalletNameText(String walletName) async {
@ -76,6 +76,6 @@ class RestoreFromSeedOrKeysPageRobot {
Future<void> onRestoreWalletButtonTapped() async {
await commonTestCases.tapItemByKey('wallet_restore_seed_or_key_restore_button_key');
await commonTestCases.defaultSleepTime(seconds: 15);
// await commonTestCases.defaultSleepTime(seconds: 15);
}
}

View file

@ -14,10 +14,10 @@ class RestoreOptionsPageRobot {
}
void hasRestoreOptionsButton() {
commonTestCases.hasKey('restore_options_from_seeds_button_key');
commonTestCases.hasKey('restore_options_from_backup_button_key');
commonTestCases.hasKey('restore_options_from_hardware_wallet_button_key');
commonTestCases.hasKey('restore_options_from_qr_button_key');
commonTestCases.hasValueKey('restore_options_from_seeds_button_key');
commonTestCases.hasValueKey('restore_options_from_backup_button_key');
commonTestCases.hasValueKey('restore_options_from_hardware_wallet_button_key');
commonTestCases.hasValueKey('restore_options_from_qr_button_key');
}
Future<void> navigateToRestoreFromSeedsPage() async {

View file

@ -28,11 +28,11 @@ class SetupPinCodeRobot {
void hasNumberButtonsVisible() {
// Confirmation for buttons 1-9
for (var i = 1; i < 10; i++) {
commonTestCases.hasKey('pin_code_button_${i}_key');
commonTestCases.hasValueKey('pin_code_button_${i}_key');
}
// Confirmation for 0 button
commonTestCases.hasKey('pin_code_button_0_key');
commonTestCases.hasValueKey('pin_code_button_0_key');
}
Future<void> pushPinButton(int index) async {

View file

@ -101,11 +101,11 @@ class ExchangePage extends BasePage {
@override
Function(BuildContext)? get pushToNextWidget => (context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
@override
Widget middle(BuildContext context) => Row(
@ -239,6 +239,7 @@ class ExchangePage extends BasePage {
),
Observer(
builder: (_) => LoadingPrimaryButton(
key: ValueKey('exchange_page_exchange_button_key'),
text: S.of(context).exchange,
onPressed: () {
if (_formKey.currentState != null &&
@ -340,7 +341,6 @@ class ExchangePage extends BasePage {
void applyTemplate(
BuildContext context, ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async {
final depositCryptoCurrency = CryptoCurrency.fromString(template.depositCurrency);
final receiveCryptoCurrency = CryptoCurrency.fromString(template.receiveCurrency);
@ -354,10 +354,12 @@ class ExchangePage extends BasePage {
exchangeViewModel.isFixedRateMode = false;
var domain = template.depositAddress;
exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, depositCryptoCurrency);
exchangeViewModel.depositAddress =
await fetchParsedAddress(context, domain, depositCryptoCurrency);
domain = template.receiveAddress;
exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, receiveCryptoCurrency);
exchangeViewModel.receiveAddress =
await fetchParsedAddress(context, domain, receiveCryptoCurrency);
}
void _setReactions(BuildContext context, ExchangeViewModel exchangeViewModel) {
@ -529,14 +531,16 @@ class ExchangePage extends BasePage {
_depositAddressFocus.addListener(() async {
if (!_depositAddressFocus.hasFocus && depositAddressController.text.isNotEmpty) {
final domain = depositAddressController.text;
exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency);
exchangeViewModel.depositAddress =
await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency);
}
});
_receiveAddressFocus.addListener(() async {
if (!_receiveAddressFocus.hasFocus && receiveAddressController.text.isNotEmpty) {
final domain = receiveAddressController.text;
exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency);
exchangeViewModel.receiveAddress =
await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency);
}
});
@ -589,7 +593,8 @@ class ExchangePage extends BasePage {
}
}
Future<String> fetchParsedAddress(BuildContext context, String domain, CryptoCurrency currency) async {
Future<String> fetchParsedAddress(
BuildContext context, String domain, CryptoCurrency currency) async {
final parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency);
final address = await extractAddressFromParsed(context, parsedAddress);
return address;
@ -619,6 +624,7 @@ class ExchangePage extends BasePage {
Widget _exchangeCardsSection(BuildContext context) {
final firstExchangeCard = Observer(
builder: (_) => ExchangeCard(
cardInstanceName: 'deposit_exchange_card',
onDispose: disposeBestRateSync,
hasAllAmount: exchangeViewModel.hasAllAmount,
allAmount: exchangeViewModel.hasAllAmount
@ -689,6 +695,7 @@ class ExchangePage extends BasePage {
final secondExchangeCard = Observer(
builder: (_) => ExchangeCard(
cardInstanceName: 'receive_exchange_card',
onDispose: disposeBestRateSync,
amountFocusNode: _receiveAmountFocus,
addressFocusNode: _receiveAddressFocus,

View file

@ -121,6 +121,7 @@ class ExchangeTemplatePage extends BasePage {
padding: EdgeInsets.fromLTRB(24, 100, 24, 32),
child: Observer(
builder: (_) => ExchangeCard(
cardInstanceName: 'deposit_exchange_template_card',
amountFocusNode: _depositAmountFocus,
key: depositKey,
title: S.of(context).you_will_send,
@ -157,6 +158,7 @@ class ExchangeTemplatePage extends BasePage {
padding: EdgeInsets.only(top: 29, left: 24, right: 24),
child: Observer(
builder: (_) => ExchangeCard(
cardInstanceName: 'receive_exchange_template_card',
amountFocusNode: _receiveAmountFocus,
key: receiveKey,
title: S.of(context).you_will_get,

View file

@ -12,7 +12,8 @@ class CurrencyPicker extends StatefulWidget {
this.title,
this.hintText,
this.isMoneroWallet = false,
this.isConvertFrom = false});
this.isConvertFrom = false,
super.key});
final int selectedAtIndex;
final List<Currency> items;

View file

@ -44,8 +44,9 @@ class ExchangeCard extends StatefulWidget {
this.allAmount,
this.onPushPasteButton,
this.onPushAddressBookButton,
this.onDispose})
: super(key: key);
this.onDispose,
required this.cardInstanceName,
}) : super(key: key);
final List<CryptoCurrency> currencies;
final Function(CryptoCurrency) onCurrencySelected;
@ -73,6 +74,7 @@ class ExchangeCard extends StatefulWidget {
final void Function(BuildContext context)? onPushPasteButton;
final void Function(BuildContext context)? onPushAddressBookButton;
final Function()? onDispose;
final String cardInstanceName;
@override
ExchangeCardState createState() => ExchangeCardState();
@ -88,11 +90,13 @@ class ExchangeCardState extends State<ExchangeCard> {
_walletName = '',
_selectedCurrency = CryptoCurrency.btc,
_isAmountEstimated = false,
_isMoneroWallet = false;
_isMoneroWallet = false,
_cardInstanceName = '';
final addressController = TextEditingController();
final amountController = TextEditingController();
String _cardInstanceName;
String _title;
String? _min;
String? _max;
@ -105,6 +109,7 @@ class ExchangeCardState extends State<ExchangeCard> {
@override
void initState() {
_cardInstanceName = widget.cardInstanceName;
_title = widget.title;
_isAmountEditable = widget.initialIsAmountEditable;
_isAddressEditable = widget.initialIsAddressEditable;
@ -183,6 +188,7 @@ class ExchangeCardState extends State<ExchangeCard> {
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
key: ValueKey('${_cardInstanceName}_title_key'),
_title,
style: TextStyle(
fontSize: 18,
@ -200,6 +206,7 @@ class ExchangeCardState extends State<ExchangeCard> {
height: 32,
color: widget.currencyButtonColor,
child: InkWell(
key: ValueKey('${_cardInstanceName}_currency_picker_button_key'),
onTap: () => _presentPicker(context),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -209,9 +216,12 @@ class ExchangeCardState extends State<ExchangeCard> {
padding: EdgeInsets.only(right: 5),
child: widget.imageArrow,
),
Text(_selectedCurrency.toString(),
style: TextStyle(
fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white))
Text(
key: ValueKey('${_cardInstanceName}_selected_currency_text_key'),
_selectedCurrency.toString(),
style: TextStyle(
fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white),
)
]),
),
),
@ -227,13 +237,16 @@ class ExchangeCardState extends State<ExchangeCard> {
child: Center(
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Text(_selectedCurrency.tag!,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonIconColor)),
child: Text(
key: ValueKey('${_cardInstanceName}_selected_currency_tag_text_key'),
_selectedCurrency.tag!,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonIconColor),
),
),
),
),
@ -252,6 +265,7 @@ class ExchangeCardState extends State<ExchangeCard> {
child: FocusTraversalOrder(
order: NumericFocusOrder(1),
child: BaseTextFormField(
key: ValueKey('${_cardInstanceName}_amount_textfield_key'),
focusNode: widget.amountFocusNode,
controller: amountController,
enabled: _isAmountEditable,
@ -272,9 +286,7 @@ class ExchangeCardState extends State<ExchangeCard> {
color: Theme.of(context)
.extension<ExchangePageTheme>()!
.hintTextColor),
validator: _isAmountEditable
? widget.currencyValueValidator
: null),
validator: _isAmountEditable ? widget.currencyValueValidator : null),
),
),
if (widget.hasAllAmount)
@ -287,6 +299,7 @@ class ExchangeCardState extends State<ExchangeCard> {
.textFieldButtonColor,
borderRadius: BorderRadius.all(Radius.circular(6))),
child: InkWell(
key: ValueKey('${_cardInstanceName}_send_all_button_key'),
onTap: () => widget.allAmount?.call(),
child: Center(
child: Text(S.of(context).all,
@ -313,6 +326,7 @@ class ExchangeCardState extends State<ExchangeCard> {
child: Row(mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[
_min != null
? Text(
key: ValueKey('${_cardInstanceName}_min_limit_text_key'),
S.of(context).min_value(_min ?? '', _selectedCurrency.toString()),
style: TextStyle(
fontSize: 10,
@ -322,11 +336,15 @@ class ExchangeCardState extends State<ExchangeCard> {
: Offstage(),
_min != null ? SizedBox(width: 10) : Offstage(),
_max != null
? Text(S.of(context).max_value(_max ?? '', _selectedCurrency.toString()),
? Text(
key: ValueKey('${_cardInstanceName}_max_limit_text_key'),
S.of(context).max_value(_max ?? '', _selectedCurrency.toString()),
style: TextStyle(
fontSize: 10,
height: 1.2,
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor))
fontSize: 10,
height: 1.2,
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor,
),
)
: Offstage(),
])),
),
@ -347,6 +365,7 @@ class ExchangeCardState extends State<ExchangeCard> {
child: Padding(
padding: EdgeInsets.only(top: 20),
child: AddressTextField(
key: ValueKey('${_cardInstanceName}_editable_address_textfield_key'),
focusNode: widget.addressFocusNode,
controller: addressController,
onURIScanned: (uri) {
@ -387,6 +406,8 @@ class ExchangeCardState extends State<ExchangeCard> {
FocusTraversalOrder(
order: NumericFocusOrder(3),
child: BaseTextFormField(
key: ValueKey(
'${_cardInstanceName}_non_editable_address_textfield_key'),
controller: addressController,
borderColor: Colors.transparent,
suffixIcon: SizedBox(width: _isMoneroWallet ? 80 : 36),
@ -410,6 +431,8 @@ class ExchangeCardState extends State<ExchangeCard> {
child: Semantics(
label: S.of(context).address_book,
child: InkWell(
key: ValueKey(
'${_cardInstanceName}_address_book_button_key'),
onTap: () async {
final contact =
await Navigator.of(context).pushNamed(
@ -447,6 +470,7 @@ class ExchangeCardState extends State<ExchangeCard> {
child: Semantics(
label: S.of(context).copy_address,
child: InkWell(
key: ValueKey('${_cardInstanceName}_copy_refund_address_button_key'),
onTap: () {
Clipboard.setData(
ClipboardData(text: addressController.text));
@ -470,6 +494,7 @@ class ExchangeCardState extends State<ExchangeCard> {
showPopUp<void>(
context: context,
builder: (_) => CurrencyPicker(
key: ValueKey('${_cardInstanceName}_currency_picker_dialog_button_key'),
selectedAtIndex: widget.currencies.indexOf(_selectedCurrency),
items: widget.currencies,
hintText: S.of(context).search_currency,

View file

@ -32,7 +32,7 @@ class AddressTextField extends StatelessWidget {
this.onPushPasteButton,
this.onPushAddressBookButton,
this.onSelectedContact,
this.selectedCurrency});
this.selectedCurrency, super.key});
static const prefixIconWidth = 34.0;
static const prefixIconHeight = 34.0;

View file

@ -3,7 +3,12 @@ import 'package:cake_wallet/palette.dart';
import 'package:flutter/material.dart';
class AlertCloseButton extends StatelessWidget {
AlertCloseButton({this.image, this.bottom, this.onTap});
AlertCloseButton({
this.image,
this.bottom,
this.onTap,
super.key,
});
final VoidCallback? onTap;

View file

@ -11,6 +11,7 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/cake_scrollbar_theme.dart';
import 'package:cake_wallet/themes/extensions/picker_theme.dart';
//TODO(David): PickerWidget is intertwined and confusing as is, find a way to optimize?
class Picker<Item> extends StatefulWidget {
Picker({
required this.selectedAtIndex,
@ -153,6 +154,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
Container(
padding: EdgeInsets.symmetric(horizontal: padding),
child: Text(
key: ValueKey('picker_title_text_key'),
widget.title!,
textAlign: TextAlign.center,
style: TextStyle(
@ -189,7 +191,10 @@ class _PickerState<Item> extends State<Picker<Item>> {
Padding(
padding: const EdgeInsets.all(16),
child: SearchBarWidget(
searchController: searchController, hintText: widget.hintText),
key: ValueKey('picker_search_bar_key'),
searchController: searchController,
hintText: widget.hintText,
),
),
Divider(
color: Theme.of(context).extension<PickerTheme>()!.dividerColor,
@ -203,6 +208,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
children: <Widget>[
filteredItems.length > 3
? Scrollbar(
key: ValueKey('picker_scrollbar_key'),
controller: controller,
child: itemsList(),
)
@ -213,6 +219,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
left: padding,
right: padding,
child: Text(
key: ValueKey('picker_descriptinon_text_key'),
widget.description!,
textAlign: TextAlign.center,
style: TextStyle(
@ -242,6 +249,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
if (widget.isWrapped) {
return PickerWrapperWidget(
key: ValueKey('picker_wrapper_widget_key'),
hasTitle: widget.title?.isNotEmpty ?? false,
children: [content],
);
@ -260,6 +268,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
color: Theme.of(context).extension<PickerTheme>()!.dividerColor,
child: widget.isGridView
? GridView.builder(
key: ValueKey('picker_items_grid_view_key'),
padding: EdgeInsets.zero,
controller: controller,
shrinkWrap: true,
@ -275,6 +284,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
: buildItem(index),
)
: ListView.separated(
key: ValueKey('picker_items_list_view_key'),
padding: EdgeInsets.zero,
controller: controller,
shrinkWrap: true,
@ -297,6 +307,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
final item = widget.headerEnabled ? filteredItems[index] : items[index];
final tag = item is Currency ? item.tag : null;
final currencyName = item is Currency ? item.name : '';
final icon = _getItemIcon(item);
final image = images.isNotEmpty ? filteredImages[index] : icon;
@ -316,6 +327,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
children: [
Flexible(
child: Text(
key: ValueKey('picker_items_index_${currencyName}_text_key'),
widget.displayItem?.call(item) ?? item.toString(),
softWrap: true,
style: TextStyle(
@ -335,6 +347,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
height: 18.0,
child: Center(
child: Text(
key: ValueKey('picker_items_index_${index}_tag_key'),
tag,
style: TextStyle(
fontSize: 7.0,
@ -358,6 +371,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
);
return GestureDetector(
key: ValueKey('picker_items_index_${currencyName}_button_key'),
onTap: () {
if (widget.closeOnItemSelected) Navigator.of(context).pop();
onItemSelected(item!);
@ -383,6 +397,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
final item = items[index];
final tag = item is Currency ? item.tag : null;
final currencyName = item is Currency ? item.name : '';
final icon = _getItemIcon(item);
final image = images.isNotEmpty ? images[index] : icon;
@ -390,6 +405,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
final isCustomItem = widget.customItemIndex != null && index == widget.customItemIndex;
final itemContent = Row(
key: ValueKey('picker_selected_item_row_key'),
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: widget.mainAxisAlignment,
crossAxisAlignment: CrossAxisAlignment.center,
@ -402,6 +418,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
children: [
Flexible(
child: Text(
key: ValueKey('picker_items_index_${currencyName}_selected_item_text_key'),
widget.displayItem?.call(item) ?? item.toString(),
softWrap: true,
style: TextStyle(
@ -445,6 +462,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
);
return GestureDetector(
key: ValueKey('picker_items_index_${currencyName}_selected_item_button_key'),
onTap: () {
if (widget.closeOnItemSelected) Navigator.of(context).pop();
},

View file

@ -4,7 +4,12 @@ import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
class PickerWrapperWidget extends StatelessWidget {
PickerWrapperWidget({required this.children, this.hasTitle = false, this.onClose});
PickerWrapperWidget({
required this.children,
this.hasTitle = false,
this.onClose,
super.key,
});
final List<Widget> children;
final bool hasTitle;
@ -29,8 +34,8 @@ class PickerWrapperWidget extends StatelessWidget {
final containerBottom = screenCenter - containerCenter;
// position the close button right below the search container
closeButtonBottom = closeButtonBottom -
containerBottom + (!hasTitle ? padding : padding / 1.5);
closeButtonBottom =
closeButtonBottom - containerBottom + (!hasTitle ? padding : padding / 1.5);
}
return AlertBackground(
@ -46,7 +51,11 @@ class PickerWrapperWidget extends StatelessWidget {
children: children,
),
SizedBox(height: ResponsiveLayoutUtilBase.kPopupSpaceHeight),
AlertCloseButton(bottom: closeButtonBottom, onTap: onClose),
AlertCloseButton(
key: ValueKey('picker_wrapper_close_button_key'),
bottom: closeButtonBottom,
onTap: onClose,
),
],
),
),

View file

@ -7,6 +7,7 @@ class SearchBarWidget extends StatelessWidget {
required this.searchController,
this.hintText,
this.borderRadius = 14,
super.key,
});
final TextEditingController searchController;