mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-16 17:27:37 +00:00
CW-727/728-Automated-Integrated-Tests (#1514)
Some checks are pending
Cache Dependencies / test (push) Waiting to run
Some checks are pending
Cache Dependencies / test (push) Waiting to run
* 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 * Fix: Add keys back to currency amount textfield widget * fix: Switch variable name * fix: remove automation for now * test: Updating send page robot and also syncing branch with main --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
This commit is contained in:
parent
32e119e24f
commit
4adb81c4dc
67 changed files with 2381 additions and 240 deletions
3
.github/workflows/pr_test_build_android.yml
vendored
3
.github/workflows/pr_test_build_android.yml
vendored
|
@ -13,6 +13,9 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
PR_test_build:
|
PR_test_build:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
api-level: [29]
|
||||||
env:
|
env:
|
||||||
STORE_PASS: test@cake_wallet
|
STORE_PASS: test@cake_wallet
|
||||||
KEY_PASS: test@cake_wallet
|
KEY_PASS: test@cake_wallet
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -171,6 +171,9 @@ macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
|
||||||
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
|
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
|
||||||
macos/Runner/Configs/AppInfo.xcconfig
|
macos/Runner/Configs/AppInfo.xcconfig
|
||||||
|
|
||||||
|
|
||||||
|
integration_test/playground.dart
|
||||||
|
|
||||||
# Monero.dart (Monero_C)
|
# Monero.dart (Monero_C)
|
||||||
scripts/monero_c
|
scripts/monero_c
|
||||||
# iOS generated framework bin
|
# iOS generated framework bin
|
||||||
|
|
|
@ -277,10 +277,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file
|
name: file
|
||||||
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.4"
|
version: "7.0.0"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
96
integration_test/components/common_test_cases.dart
Normal file
96
integration_test/components/common_test_cases.dart
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
class CommonTestCases {
|
||||||
|
WidgetTester tester;
|
||||||
|
CommonTestCases(this.tester);
|
||||||
|
|
||||||
|
Future<void> isSpecificPage<T>() async {
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
hasType<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tapItemByKey(String key, {bool shouldPumpAndSettle = true}) async {
|
||||||
|
final widget = find.byKey(ValueKey(key));
|
||||||
|
await tester.tap(widget);
|
||||||
|
shouldPumpAndSettle ? await tester.pumpAndSettle() : await tester.pump();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tapItemByFinder(Finder finder, {bool shouldPumpAndSettle = true}) async {
|
||||||
|
await tester.tap(finder);
|
||||||
|
shouldPumpAndSettle ? await tester.pumpAndSettle() : await tester.pump();
|
||||||
|
}
|
||||||
|
|
||||||
|
void hasText(String text, {bool hasWidget = true}) {
|
||||||
|
final textWidget = find.text(text);
|
||||||
|
expect(textWidget, hasWidget ? findsOneWidget : findsNothing);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hasType<T>() {
|
||||||
|
final typeWidget = find.byType(T);
|
||||||
|
expect(typeWidget, findsOneWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hasValueKey(String key) {
|
||||||
|
final typeWidget = find.byKey(ValueKey(key));
|
||||||
|
expect(typeWidget, findsOneWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> swipePage({bool swipeRight = true}) async {
|
||||||
|
await tester.drag(find.byType(PageView), Offset(swipeRight ? -300 : 300, 0));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> swipeByPageKey({required String key, bool swipeRight = true}) async {
|
||||||
|
await tester.drag(find.byKey(ValueKey(key)), Offset(swipeRight ? -300 : 300, 0));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> goBack() async {
|
||||||
|
tester.printToConsole('Routing back to previous screen');
|
||||||
|
final NavigatorState navigator = tester.state(find.byType(Navigator));
|
||||||
|
navigator.pop();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> scrollUntilVisible(String childKey, String parentScrollableKey,
|
||||||
|
{double delta = 300}) async {
|
||||||
|
final scrollableWidget = find.descendant(
|
||||||
|
of: find.byKey(Key(parentScrollableKey)),
|
||||||
|
matching: find.byType(Scrollable),
|
||||||
|
);
|
||||||
|
|
||||||
|
final isAlreadyVisibile = isWidgetVisible(find.byKey(ValueKey(childKey)));
|
||||||
|
|
||||||
|
if (isAlreadyVisibile) return;
|
||||||
|
|
||||||
|
await tester.scrollUntilVisible(
|
||||||
|
find.byKey(ValueKey(childKey)),
|
||||||
|
delta,
|
||||||
|
scrollable: scrollableWidget,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isWidgetVisible(Finder finder) {
|
||||||
|
try {
|
||||||
|
final Element element = finder.evaluate().single;
|
||||||
|
final RenderBox renderBox = element.renderObject as RenderBox;
|
||||||
|
return renderBox.paintBounds
|
||||||
|
.shift(renderBox.localToGlobal(Offset.zero))
|
||||||
|
.overlaps(tester.binding.renderViews.first.paintBounds);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> enterText(String text, String editableTextKey) async {
|
||||||
|
final editableTextWidget = find.byKey(ValueKey((editableTextKey)));
|
||||||
|
|
||||||
|
await tester.enterText(editableTextWidget, text);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> defaultSleepTime({int seconds = 2}) async =>
|
||||||
|
await Future.delayed(Duration(seconds: seconds));
|
||||||
|
}
|
13
integration_test/components/common_test_constants.dart
Normal file
13
integration_test/components/common_test_constants.dart
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
|
class CommonTestConstants {
|
||||||
|
static final pin = [0, 8, 0, 1];
|
||||||
|
static final String sendTestAmount = '0.00008';
|
||||||
|
static final String exchangeTestAmount = '8';
|
||||||
|
static final WalletType testWalletType = WalletType.solana;
|
||||||
|
static final String testWalletName = 'Integrated Testing Wallet';
|
||||||
|
static final CryptoCurrency testReceiveCurrency = CryptoCurrency.sol;
|
||||||
|
static final CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol;
|
||||||
|
static final String testWalletAddress = 'An2Y2fsUYKfYvN1zF89GAqR1e6GUMBg3qA83Y5ZWDf8L';
|
||||||
|
}
|
101
integration_test/components/common_test_flows.dart
Normal file
101
integration_test/components/common_test_flows.dart
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
|
import 'package:cake_wallet/main.dart' as app;
|
||||||
|
|
||||||
|
import '../robots/disclaimer_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';
|
||||||
|
import '../robots/setup_pin_code_robot.dart';
|
||||||
|
import '../robots/welcome_page_robot.dart';
|
||||||
|
import 'common_test_cases.dart';
|
||||||
|
import 'common_test_constants.dart';
|
||||||
|
|
||||||
|
class CommonTestFlows {
|
||||||
|
CommonTestFlows(this._tester)
|
||||||
|
: _commonTestCases = CommonTestCases(_tester),
|
||||||
|
_welcomePageRobot = WelcomePageRobot(_tester),
|
||||||
|
_setupPinCodeRobot = SetupPinCodeRobot(_tester),
|
||||||
|
_disclaimerPageRobot = DisclaimerPageRobot(_tester),
|
||||||
|
_newWalletTypePageRobot = NewWalletTypePageRobot(_tester),
|
||||||
|
_restoreOptionsPageRobot = RestoreOptionsPageRobot(_tester),
|
||||||
|
_restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester);
|
||||||
|
|
||||||
|
final WidgetTester _tester;
|
||||||
|
final CommonTestCases _commonTestCases;
|
||||||
|
|
||||||
|
final WelcomePageRobot _welcomePageRobot;
|
||||||
|
final SetupPinCodeRobot _setupPinCodeRobot;
|
||||||
|
final DisclaimerPageRobot _disclaimerPageRobot;
|
||||||
|
final NewWalletTypePageRobot _newWalletTypePageRobot;
|
||||||
|
final RestoreOptionsPageRobot _restoreOptionsPageRobot;
|
||||||
|
final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot;
|
||||||
|
|
||||||
|
Future<void> startAppFlow(Key key) async {
|
||||||
|
await app.main(topLevelKey: ValueKey('send_flow_test_app_key'));
|
||||||
|
|
||||||
|
await _tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// --------- Disclaimer Page ------------
|
||||||
|
// Tap checkbox to accept disclaimer
|
||||||
|
await _disclaimerPageRobot.tapDisclaimerCheckbox();
|
||||||
|
|
||||||
|
// Tap accept button
|
||||||
|
await _disclaimerPageRobot.tapAcceptButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> restoreWalletThroughSeedsFlow() async {
|
||||||
|
await _welcomeToRestoreFromSeedsPath();
|
||||||
|
await _restoreFromSeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> restoreWalletThroughKeysFlow() async {
|
||||||
|
await _welcomeToRestoreFromSeedsPath();
|
||||||
|
await _restoreFromKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _welcomeToRestoreFromSeedsPath() async {
|
||||||
|
// --------- Welcome Page ---------------
|
||||||
|
await _welcomePageRobot.navigateToRestoreWalletPage();
|
||||||
|
|
||||||
|
// ----------- Restore Options Page -----------
|
||||||
|
// Route to restore from seeds page to continue flow
|
||||||
|
await _restoreOptionsPageRobot.navigateToRestoreFromSeedsPage();
|
||||||
|
|
||||||
|
// ----------- SetupPinCode Page -------------
|
||||||
|
// Confirm initial defaults - Widgets to be displayed etc
|
||||||
|
await _setupPinCodeRobot.isSetupPinCodePage();
|
||||||
|
|
||||||
|
await _setupPinCodeRobot.enterPinCode(CommonTestConstants.pin, true);
|
||||||
|
await _setupPinCodeRobot.enterPinCode(CommonTestConstants.pin, false);
|
||||||
|
await _setupPinCodeRobot.tapSuccessButton();
|
||||||
|
|
||||||
|
// ----------- NewWalletType Page -------------
|
||||||
|
// Confirm scroll behaviour works properly
|
||||||
|
await _newWalletTypePageRobot
|
||||||
|
.findParticularWalletTypeInScrollableList(CommonTestConstants.testWalletType);
|
||||||
|
|
||||||
|
// Select a wallet and route to next page
|
||||||
|
await _newWalletTypePageRobot.selectWalletType(CommonTestConstants.testWalletType);
|
||||||
|
await _newWalletTypePageRobot.onNextButtonPressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _restoreFromSeeds() async {
|
||||||
|
// ----------- RestoreFromSeedOrKeys Page -------------
|
||||||
|
await _restoreFromSeedOrKeysPageRobot.enterWalletNameText(CommonTestConstants.testWalletName);
|
||||||
|
await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(secrets.solanaTestWalletSeeds);
|
||||||
|
await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _restoreFromKeys() async {
|
||||||
|
await _commonTestCases.swipePage();
|
||||||
|
await _commonTestCases.defaultSleepTime();
|
||||||
|
|
||||||
|
await _restoreFromSeedOrKeysPageRobot.enterWalletNameText(CommonTestConstants.testWalletName);
|
||||||
|
|
||||||
|
await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore('');
|
||||||
|
await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed();
|
||||||
|
}
|
||||||
|
}
|
84
integration_test/funds_related_tests.dart
Normal file
84
integration_test/funds_related_tests.dart
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import 'components/common_test_constants.dart';
|
||||||
|
import 'components/common_test_flows.dart';
|
||||||
|
import 'robots/auth_page_robot.dart';
|
||||||
|
import 'robots/dashboard_page_robot.dart';
|
||||||
|
import 'robots/exchange_confirm_page_robot.dart';
|
||||||
|
import 'robots/exchange_page_robot.dart';
|
||||||
|
import 'robots/exchange_trade_page_robot.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
DashboardPageRobot dashboardPageRobot;
|
||||||
|
ExchangePageRobot exchangePageRobot;
|
||||||
|
ExchangeConfirmPageRobot exchangeConfirmPageRobot;
|
||||||
|
AuthPageRobot authPageRobot;
|
||||||
|
ExchangeTradePageRobot exchangeTradePageRobot;
|
||||||
|
CommonTestFlows commonTestFlows;
|
||||||
|
|
||||||
|
group('Startup Test', () {
|
||||||
|
testWidgets('Test for Exchange flow using Restore Wallet - Exchanging USDT(Sol) to SOL',
|
||||||
|
(tester) async {
|
||||||
|
authPageRobot = AuthPageRobot(tester);
|
||||||
|
exchangePageRobot = ExchangePageRobot(tester);
|
||||||
|
dashboardPageRobot = DashboardPageRobot(tester);
|
||||||
|
exchangeTradePageRobot = ExchangeTradePageRobot(tester);
|
||||||
|
exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester);
|
||||||
|
commonTestFlows = CommonTestFlows(tester);
|
||||||
|
|
||||||
|
await commonTestFlows.startAppFlow(ValueKey('funds_exchange_test_app_key'));
|
||||||
|
|
||||||
|
await commonTestFlows.restoreWalletThroughSeedsFlow();
|
||||||
|
|
||||||
|
// ----------- RestoreFromSeedOrKeys Page -------------
|
||||||
|
await dashboardPageRobot.navigateToExchangePage();
|
||||||
|
|
||||||
|
// ----------- Exchange Page -------------
|
||||||
|
await exchangePageRobot.isExchangePage();
|
||||||
|
exchangePageRobot.hasResetButton();
|
||||||
|
await exchangePageRobot.displayBothExchangeCards();
|
||||||
|
exchangePageRobot.confirmRightComponentsDisplayOnDepositExchangeCards();
|
||||||
|
exchangePageRobot.confirmRightComponentsDisplayOnReceiveExchangeCards();
|
||||||
|
|
||||||
|
await exchangePageRobot.selectDepositCurrency(CommonTestConstants.testDepositCurrency);
|
||||||
|
await exchangePageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency);
|
||||||
|
|
||||||
|
await exchangePageRobot.enterDepositAmount(CommonTestConstants.exchangeTestAmount);
|
||||||
|
await exchangePageRobot.enterDepositRefundAddress(
|
||||||
|
depositAddress: CommonTestConstants.testWalletAddress);
|
||||||
|
|
||||||
|
await exchangePageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress);
|
||||||
|
|
||||||
|
await exchangePageRobot.onExchangeButtonPressed();
|
||||||
|
|
||||||
|
await exchangePageRobot.handleErrors(CommonTestConstants.exchangeTestAmount);
|
||||||
|
|
||||||
|
final onAuthPage = authPageRobot.onAuthPage();
|
||||||
|
if (onAuthPage) {
|
||||||
|
await authPageRobot.enterPinCode(CommonTestConstants.pin, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------- Exchange Confirm Page -------------
|
||||||
|
await exchangeConfirmPageRobot.isExchangeConfirmPage();
|
||||||
|
|
||||||
|
exchangeConfirmPageRobot.confirmComponentsOfTradeDisplayProperly();
|
||||||
|
await exchangeConfirmPageRobot.confirmCopyTradeIdToClipBoardWorksProperly();
|
||||||
|
await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed();
|
||||||
|
|
||||||
|
// ----------- Exchange Trade Page -------------
|
||||||
|
await exchangeTradePageRobot.isExchangeTradePage();
|
||||||
|
exchangeTradePageRobot.hasInformationDialog();
|
||||||
|
await exchangeTradePageRobot.onGotItButtonPressed();
|
||||||
|
|
||||||
|
await exchangeTradePageRobot.onConfirmSendingButtonPressed();
|
||||||
|
|
||||||
|
await exchangeTradePageRobot.handleConfirmSendResult();
|
||||||
|
|
||||||
|
await exchangeTradePageRobot.onSendButtonOnConfirmSendingDialogPressed();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
25
integration_test/helpers/mocks.dart
Normal file
25
integration_test/helpers/mocks.dart
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import 'package:cake_wallet/core/auth_service.dart';
|
||||||
|
import 'package:cake_wallet/core/secure_storage.dart';
|
||||||
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
|
import 'package:cake_wallet/store/authentication_store.dart';
|
||||||
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
import 'package:cake_wallet/store/wallet_list_store.dart';
|
||||||
|
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
|
class MockAppStore extends Mock implements AppStore{}
|
||||||
|
class MockAuthService extends Mock implements AuthService{}
|
||||||
|
class MockSettingsStore extends Mock implements SettingsStore {}
|
||||||
|
class MockAuthenticationStore extends Mock implements AuthenticationStore{}
|
||||||
|
class MockWalletListStore extends Mock implements WalletListStore{}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MockLinkViewModel extends Mock implements LinkViewModel {}
|
||||||
|
|
||||||
|
class MockHiveInterface extends Mock implements HiveInterface {}
|
||||||
|
|
||||||
|
class MockHiveBox extends Mock implements Box<dynamic> {}
|
||||||
|
|
||||||
|
class MockSecureStorage extends Mock implements SecureStorage{}
|
100
integration_test/helpers/test_helpers.dart
Normal file
100
integration_test/helpers/test_helpers.dart
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
import 'package:cake_wallet/core/auth_service.dart';
|
||||||
|
import 'package:cake_wallet/core/secure_storage.dart';
|
||||||
|
import 'package:cake_wallet/di.dart';
|
||||||
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
|
import 'package:cake_wallet/store/authentication_store.dart';
|
||||||
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
import 'package:cake_wallet/store/wallet_list_store.dart';
|
||||||
|
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
|
import 'mocks.dart';
|
||||||
|
|
||||||
|
class TestHelpers {
|
||||||
|
static void setup() {
|
||||||
|
// Fallback values can also be declared here
|
||||||
|
registerDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void registerDependencies() {
|
||||||
|
getAndRegisterAppStore();
|
||||||
|
getAndRegisterAuthService();
|
||||||
|
getAndRegisterSettingsStore();
|
||||||
|
getAndRegisterAuthenticationStore();
|
||||||
|
getAndRegisterWalletListStore();
|
||||||
|
|
||||||
|
getAndRegisterLinkViewModel();
|
||||||
|
getAndRegisterSecureStorage();
|
||||||
|
getAndRegisterHiveInterface();
|
||||||
|
}
|
||||||
|
|
||||||
|
static MockSettingsStore getAndRegisterSettingsStore() {
|
||||||
|
_removeRegistrationIfExists<SettingsStore>();
|
||||||
|
final service = MockSettingsStore();
|
||||||
|
getIt.registerSingleton<SettingsStore>(service);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MockAppStore getAndRegisterAppStore() {
|
||||||
|
_removeRegistrationIfExists<AppStore>();
|
||||||
|
final service = MockAppStore();
|
||||||
|
final settingsStore = getAndRegisterSettingsStore();
|
||||||
|
|
||||||
|
when(() => service.settingsStore).thenAnswer((invocation) => settingsStore);
|
||||||
|
getIt.registerSingleton<AppStore>(service);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MockAuthService getAndRegisterAuthService() {
|
||||||
|
_removeRegistrationIfExists<AuthService>();
|
||||||
|
final service = MockAuthService();
|
||||||
|
getIt.registerSingleton<AuthService>(service);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MockAuthenticationStore getAndRegisterAuthenticationStore() {
|
||||||
|
_removeRegistrationIfExists<AuthenticationStore>();
|
||||||
|
final service = MockAuthenticationStore();
|
||||||
|
when(() => service.state).thenReturn(AuthenticationState.uninitialized);
|
||||||
|
getIt.registerSingleton<AuthenticationStore>(service);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MockWalletListStore getAndRegisterWalletListStore() {
|
||||||
|
_removeRegistrationIfExists<WalletListStore>();
|
||||||
|
final service = MockWalletListStore();
|
||||||
|
getIt.registerSingleton<WalletListStore>(service);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MockLinkViewModel getAndRegisterLinkViewModel() {
|
||||||
|
_removeRegistrationIfExists<LinkViewModel>();
|
||||||
|
final service = MockLinkViewModel();
|
||||||
|
getIt.registerSingleton<LinkViewModel>(service);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MockHiveInterface getAndRegisterHiveInterface() {
|
||||||
|
_removeRegistrationIfExists<HiveInterface>();
|
||||||
|
final service = MockHiveInterface();
|
||||||
|
final box = MockHiveBox();
|
||||||
|
getIt.registerSingleton<HiveInterface>(service);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MockSecureStorage getAndRegisterSecureStorage() {
|
||||||
|
_removeRegistrationIfExists<SecureStorage>();
|
||||||
|
final service = MockSecureStorage();
|
||||||
|
getIt.registerSingleton<SecureStorage>(service);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _removeRegistrationIfExists<T extends Object>() {
|
||||||
|
if (getIt.isRegistered<T>()) {
|
||||||
|
getIt.unregister<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tearDown() => getIt.reset();
|
||||||
|
}
|
1
integration_test/integration_response_data.json
Normal file
1
integration_test/integration_response_data.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
null
|
30
integration_test/robots/auth_page_robot.dart
Normal file
30
integration_test/robots/auth_page_robot.dart
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../components/common_test_cases.dart';
|
||||||
|
import 'pin_code_widget_robot.dart';
|
||||||
|
|
||||||
|
class AuthPageRobot extends PinCodeWidgetRobot {
|
||||||
|
AuthPageRobot(this.tester)
|
||||||
|
: commonTestCases = CommonTestCases(tester),
|
||||||
|
super(tester);
|
||||||
|
|
||||||
|
final WidgetTester tester;
|
||||||
|
late CommonTestCases commonTestCases;
|
||||||
|
|
||||||
|
bool onAuthPage() {
|
||||||
|
final hasPinButtons = find.byKey(ValueKey('pin_code_button_3_key'));
|
||||||
|
final hasPin = hasPinButtons.tryEvaluate();
|
||||||
|
return hasPin;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> isAuthPage() async {
|
||||||
|
await commonTestCases.isSpecificPage<AuthPage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void hasTitle() {
|
||||||
|
commonTestCases.hasText(S.current.setup_pin);
|
||||||
|
}
|
||||||
|
}
|
75
integration_test/robots/dashboard_page_robot.dart
Normal file
75
integration_test/robots/dashboard_page_robot.dart
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../components/common_test_cases.dart';
|
||||||
|
|
||||||
|
class DashboardPageRobot {
|
||||||
|
DashboardPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||||
|
|
||||||
|
final WidgetTester tester;
|
||||||
|
late CommonTestCases commonTestCases;
|
||||||
|
|
||||||
|
Future<void> isDashboardPage() async {
|
||||||
|
await commonTestCases.isSpecificPage<DashboardPage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void confirmServiceUpdateButtonDisplays() {
|
||||||
|
commonTestCases.hasValueKey('dashboard_page_services_update_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
void confirmSyncIndicatorButtonDisplays() {
|
||||||
|
commonTestCases.hasValueKey('dashboard_page_sync_indicator_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
void confirmMenuButtonDisplays() {
|
||||||
|
commonTestCases.hasValueKey('dashboard_page_wallet_menu_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> confirmRightCryptoAssetTitleDisplaysPerPageView(WalletType type,
|
||||||
|
{bool isHaven = false}) async {
|
||||||
|
//Balance Page
|
||||||
|
final walletName = walletTypeToString(type);
|
||||||
|
final assetName = isHaven ? '$walletName Assets' : walletName;
|
||||||
|
commonTestCases.hasText(assetName);
|
||||||
|
|
||||||
|
// Swipe to Cake features Page
|
||||||
|
await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key', swipeRight: false);
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
commonTestCases.hasText('Cake ${S.current.features}');
|
||||||
|
|
||||||
|
// Swipe back to balance
|
||||||
|
await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key');
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
|
||||||
|
// Swipe to Transactions Page
|
||||||
|
await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key');
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
commonTestCases.hasText(S.current.transactions);
|
||||||
|
|
||||||
|
// Swipe back to balance
|
||||||
|
await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key', swipeRight: false);
|
||||||
|
await commonTestCases.defaultSleepTime(seconds: 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> navigateToBuyPage() async {
|
||||||
|
await commonTestCases.tapItemByKey('dashboard_page_${S.current.buy}_action_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> navigateToSendPage() async {
|
||||||
|
await commonTestCases.tapItemByKey('dashboard_page_${S.current.send}_action_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> navigateToSellPage() async {
|
||||||
|
await commonTestCases.tapItemByKey('dashboard_page_${S.current.sell}_action_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> navigateToReceivePage() async {
|
||||||
|
await commonTestCases.tapItemByKey('dashboard_page_${S.current.receive}_action_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> navigateToExchangePage() async {
|
||||||
|
await commonTestCases.tapItemByKey('dashboard_page_${S.current.exchange}_action_button_key');
|
||||||
|
}
|
||||||
|
}
|
39
integration_test/robots/disclaimer_page_robot.dart
Normal file
39
integration_test/robots/disclaimer_page_robot.dart
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../components/common_test_cases.dart';
|
||||||
|
|
||||||
|
class DisclaimerPageRobot {
|
||||||
|
DisclaimerPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||||
|
|
||||||
|
final WidgetTester tester;
|
||||||
|
late CommonTestCases commonTestCases;
|
||||||
|
|
||||||
|
Future<void> isDisclaimerPage() async {
|
||||||
|
await commonTestCases.isSpecificPage<DisclaimerPage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void hasCheckIcon(bool hasBeenTapped) {
|
||||||
|
// The checked Icon should not be available initially, until user taps the checkbox
|
||||||
|
final checkIcon = find.byKey(ValueKey('disclaimer_check_icon_key'));
|
||||||
|
expect(checkIcon, hasBeenTapped ? findsOneWidget : findsNothing);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hasDisclaimerCheckbox() {
|
||||||
|
final checkBox = find.byKey(ValueKey('disclaimer_check_key'));
|
||||||
|
expect(checkBox, findsOneWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tapDisclaimerCheckbox() async {
|
||||||
|
await commonTestCases.tapItemByKey('disclaimer_check_key');
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tapAcceptButton() async {
|
||||||
|
await commonTestCases.tapItemByKey('disclaimer_accept_button_key');
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
}
|
45
integration_test/robots/exchange_confirm_page_robot.dart
Normal file
45
integration_test/robots/exchange_confirm_page_robot.dart
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../components/common_test_cases.dart';
|
||||||
|
|
||||||
|
class ExchangeConfirmPageRobot {
|
||||||
|
ExchangeConfirmPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||||
|
|
||||||
|
final WidgetTester tester;
|
||||||
|
late CommonTestCases commonTestCases;
|
||||||
|
|
||||||
|
Future<void> isExchangeConfirmPage() async {
|
||||||
|
await commonTestCases.isSpecificPage<ExchangeConfirmPage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void confirmComponentsOfTradeDisplayProperly() {
|
||||||
|
final ExchangeConfirmPage exchangeConfirmPage = tester.widget(find.byType(ExchangeConfirmPage));
|
||||||
|
final trade = exchangeConfirmPage.trade;
|
||||||
|
|
||||||
|
commonTestCases.hasText(trade.id);
|
||||||
|
commonTestCases.hasText('${trade.provider.title} ${S.current.trade_id}');
|
||||||
|
|
||||||
|
commonTestCases.hasValueKey('exchange_confirm_page_saved_id_button_key');
|
||||||
|
commonTestCases.hasValueKey('exchange_confirm_page_copy_to_clipboard_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> confirmCopyTradeIdToClipBoardWorksProperly() async {
|
||||||
|
final ExchangeConfirmPage exchangeConfirmPage = tester.widget(find.byType(ExchangeConfirmPage));
|
||||||
|
final trade = exchangeConfirmPage.trade;
|
||||||
|
|
||||||
|
await commonTestCases.tapItemByKey('exchange_confirm_page_copy_to_clipboard_button_key');
|
||||||
|
|
||||||
|
ClipboardData? clipboardData = await Clipboard.getData('text/plain');
|
||||||
|
|
||||||
|
expect(clipboardData?.text, trade.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onSavedTradeIdButtonPressed() async {
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
await commonTestCases.tapItemByKey('exchange_confirm_page_saved_id_button_key');
|
||||||
|
}
|
||||||
|
}
|
330
integration_test/robots/exchange_page_robot.dart
Normal file
330
integration_test/robots/exchange_page_robot.dart
Normal file
|
@ -0,0 +1,330 @@
|
||||||
|
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/present_provider_picker.dart';
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../components/common_test_cases.dart';
|
||||||
|
|
||||||
|
class ExchangePageRobot {
|
||||||
|
ExchangePageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||||
|
|
||||||
|
final WidgetTester tester;
|
||||||
|
late CommonTestCases commonTestCases;
|
||||||
|
|
||||||
|
Future<void> isExchangePage() async {
|
||||||
|
await commonTestCases.isSpecificPage<ExchangePage>();
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
exchangePage.depositKey.currentState!.changeLimits(min: '0.1');
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
ExchangePage exchangePage = tester.widget(find.byType(ExchangePage));
|
||||||
|
final exchangeViewModel = exchangePage.exchangeViewModel;
|
||||||
|
|
||||||
|
if (depositCurrency == exchangeViewModel.depositCurrency) {
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
await commonTestCases
|
||||||
|
.tapItemByKey('picker_items_index_${depositCurrency.name}_selected_item_button_key');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
ExchangePage exchangePage = tester.widget(find.byType(ExchangePage));
|
||||||
|
final exchangeViewModel = exchangePage.exchangeViewModel;
|
||||||
|
|
||||||
|
if (receiveCurrency == exchangeViewModel.receiveCurrency) {
|
||||||
|
await commonTestCases
|
||||||
|
.tapItemByKey('picker_items_index_${receiveCurrency.name}_selected_item_button_key');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> enterDepositAmount(String amount) async {
|
||||||
|
await commonTestCases.enterText(amount, 'deposit_exchange_card_amount_textfield_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> enterDepositRefundAddress({String? depositAddress}) async {
|
||||||
|
ExchangePage exchangePage = tester.widget(find.byType(ExchangePage));
|
||||||
|
final exchangeViewModel = exchangePage.exchangeViewModel;
|
||||||
|
|
||||||
|
if (exchangeViewModel.isDepositAddressEnabled && depositAddress != null) {
|
||||||
|
await commonTestCases.enterText(
|
||||||
|
depositAddress, 'deposit_exchange_card_editable_address_textfield_key');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> enterReceiveAddress(String receiveAddress) async {
|
||||||
|
await commonTestCases.enterText(
|
||||||
|
receiveAddress,
|
||||||
|
'receive_exchange_card_editable_address_textfield_key',
|
||||||
|
);
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onExchangeButtonPressed() async {
|
||||||
|
await commonTestCases.tapItemByKey('exchange_page_exchange_button_key');
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasMaxLimitError() {
|
||||||
|
final maxErrorText = find.text(S.current.error_text_input_above_maximum_limit);
|
||||||
|
|
||||||
|
bool hasMaxError = maxErrorText.tryEvaluate();
|
||||||
|
|
||||||
|
return hasMaxError;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasMinLimitError() {
|
||||||
|
final minErrorText = find.text(S.current.error_text_input_below_minimum_limit);
|
||||||
|
|
||||||
|
bool hasMinError = minErrorText.tryEvaluate();
|
||||||
|
|
||||||
|
return hasMinError;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasTradeCreationFailureError() {
|
||||||
|
final tradeCreationFailureDialogButton =
|
||||||
|
find.byKey(ValueKey('exchange_page_trade_creation_failure_dialog_button_key'));
|
||||||
|
|
||||||
|
bool hasTradeCreationFailure = tradeCreationFailureDialogButton.tryEvaluate();
|
||||||
|
tester.printToConsole('Trade not created error: $hasTradeCreationFailure');
|
||||||
|
return hasTradeCreationFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onTradeCreationFailureDialogButtonPressed() async {
|
||||||
|
await commonTestCases.tapItemByKey('exchange_page_trade_creation_failure_dialog_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handling Trade Failure Errors or errors shown through the Failure Dialog.
|
||||||
|
///
|
||||||
|
/// Simulating the user's flow and response when this error comes up.
|
||||||
|
/// Examples are:
|
||||||
|
/// - No provider can handle this trade error,
|
||||||
|
/// - Trade amount below limit error.
|
||||||
|
Future<void> _handleTradeCreationFailureErrors() async {
|
||||||
|
bool isTradeCreationFailure = false;
|
||||||
|
|
||||||
|
isTradeCreationFailure = hasTradeCreationFailureError();
|
||||||
|
|
||||||
|
int maxRetries = 20;
|
||||||
|
int retries = 0;
|
||||||
|
|
||||||
|
while (isTradeCreationFailure && retries < maxRetries) {
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
await onTradeCreationFailureDialogButtonPressed();
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime(seconds: 5);
|
||||||
|
|
||||||
|
await onExchangeButtonPressed();
|
||||||
|
|
||||||
|
isTradeCreationFailure = hasTradeCreationFailureError();
|
||||||
|
retries++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles the min limit error.
|
||||||
|
///
|
||||||
|
/// Simulates the user's flow and response when it comes up.
|
||||||
|
///
|
||||||
|
/// Has a max retry of 20 times.
|
||||||
|
Future<void> _handleMinLimitError(String initialAmount) async {
|
||||||
|
bool isMinLimitError = false;
|
||||||
|
|
||||||
|
isMinLimitError = hasMinLimitError();
|
||||||
|
|
||||||
|
double amount;
|
||||||
|
|
||||||
|
amount = double.parse(initialAmount);
|
||||||
|
|
||||||
|
int maxRetries = 20;
|
||||||
|
int retries = 0;
|
||||||
|
|
||||||
|
while (isMinLimitError && retries < maxRetries) {
|
||||||
|
amount++;
|
||||||
|
tester.printToConsole('Amount: $amount');
|
||||||
|
|
||||||
|
enterDepositAmount(amount.toString());
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
|
||||||
|
await onExchangeButtonPressed();
|
||||||
|
|
||||||
|
isMinLimitError = hasMinLimitError();
|
||||||
|
|
||||||
|
retries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retries >= maxRetries) {
|
||||||
|
tester.printToConsole('Max retries reached for minLimit Error. Exiting loop.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles the max limit error.
|
||||||
|
///
|
||||||
|
/// Simulates the user's flow and response when it comes up.
|
||||||
|
///
|
||||||
|
/// Has a max retry of 20 times.
|
||||||
|
Future<void> _handleMaxLimitError(String initialAmount) async {
|
||||||
|
bool isMaxLimitError = false;
|
||||||
|
|
||||||
|
isMaxLimitError = hasMaxLimitError();
|
||||||
|
|
||||||
|
double amount;
|
||||||
|
|
||||||
|
amount = double.parse(initialAmount);
|
||||||
|
|
||||||
|
int maxRetries = 20;
|
||||||
|
int retries = 0;
|
||||||
|
|
||||||
|
while (isMaxLimitError && retries < maxRetries) {
|
||||||
|
amount++;
|
||||||
|
tester.printToConsole('Amount: $amount');
|
||||||
|
|
||||||
|
enterDepositAmount(amount.toString());
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
|
||||||
|
await onExchangeButtonPressed();
|
||||||
|
|
||||||
|
isMaxLimitError = hasMaxLimitError();
|
||||||
|
|
||||||
|
retries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retries >= maxRetries) {
|
||||||
|
tester.printToConsole('Max retries reached for maxLimit Error. Exiting loop.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> handleErrors(String initialAmount) async {
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await _handleMinLimitError(initialAmount);
|
||||||
|
|
||||||
|
await _handleMaxLimitError(initialAmount);
|
||||||
|
|
||||||
|
await _handleTradeCreationFailureErrors();
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
}
|
152
integration_test/robots/exchange_trade_page_robot.dart
Normal file
152
integration_test/robots/exchange_trade_page_robot.dart
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cake_wallet/core/execution_state.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../components/common_test_cases.dart';
|
||||||
|
|
||||||
|
class ExchangeTradePageRobot {
|
||||||
|
ExchangeTradePageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||||
|
|
||||||
|
final WidgetTester tester;
|
||||||
|
late CommonTestCases commonTestCases;
|
||||||
|
|
||||||
|
Future<void> isExchangeTradePage() async {
|
||||||
|
await commonTestCases.isSpecificPage<ExchangeTradePage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void hasInformationDialog() {
|
||||||
|
commonTestCases.hasValueKey('information_page_dialog_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onGotItButtonPressed() async {
|
||||||
|
await commonTestCases.tapItemByKey('information_page_got_it_button_key');
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onConfirmSendingButtonPressed() async {
|
||||||
|
tester.printToConsole('Now confirming sending');
|
||||||
|
|
||||||
|
await commonTestCases.tapItemByKey(
|
||||||
|
'exchange_trade_page_confirm_sending_button_key',
|
||||||
|
shouldPumpAndSettle: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final Completer<void> completer = Completer<void>();
|
||||||
|
|
||||||
|
// Loop to wait for the async operation to complete
|
||||||
|
while (true) {
|
||||||
|
await Future.delayed(Duration(seconds: 1));
|
||||||
|
|
||||||
|
final ExchangeTradeState state = tester.state(find.byType(ExchangeTradeForm));
|
||||||
|
final execState = state.widget.exchangeTradeViewModel.sendViewModel.state;
|
||||||
|
|
||||||
|
bool isDone = execState is ExecutedSuccessfullyState;
|
||||||
|
bool isFailed = execState is FailureState;
|
||||||
|
|
||||||
|
tester.printToConsole('isDone: $isDone');
|
||||||
|
tester.printToConsole('isFailed: $isFailed');
|
||||||
|
|
||||||
|
if (isDone || isFailed) {
|
||||||
|
tester.printToConsole(
|
||||||
|
isDone ? 'Completer is done' : 'Completer is done though operation failed');
|
||||||
|
completer.complete();
|
||||||
|
await tester.pump();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
tester.printToConsole('Completer is not done');
|
||||||
|
await tester.pump();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await expectLater(completer.future, completes);
|
||||||
|
|
||||||
|
tester.printToConsole('Done confirming sending');
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime(seconds: 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onSendButtonOnConfirmSendingDialogPressed() async {
|
||||||
|
tester.printToConsole('Send Button on Confirm Dialog Triggered');
|
||||||
|
await commonTestCases.defaultSleepTime(seconds: 4);
|
||||||
|
|
||||||
|
final sendText = find.text(S.current.send);
|
||||||
|
bool hasText = sendText.tryEvaluate();
|
||||||
|
|
||||||
|
if (hasText) {
|
||||||
|
await commonTestCases.tapItemByFinder(sendText);
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime(seconds: 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onCancelButtonOnConfirmSendingDialogPressed() async {
|
||||||
|
tester.printToConsole('Cancel Button on Confirm Dialog Triggered');
|
||||||
|
|
||||||
|
await commonTestCases.tapItemByKey(
|
||||||
|
'exchange_trade_page_confirm_sending_dialog_cancel_button_key',
|
||||||
|
);
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onSendFailureDialogButtonPressed() async {
|
||||||
|
await commonTestCases.defaultSleepTime(seconds: 6);
|
||||||
|
|
||||||
|
tester.printToConsole('Send Button Failure Dialog Triggered');
|
||||||
|
|
||||||
|
await commonTestCases.tapItemByKey('exchange_trade_page_send_failure_dialog_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> hasErrorWhileSending() async {
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
tester.printToConsole('Checking if there is an error');
|
||||||
|
|
||||||
|
final errorDialog = find.byKey(
|
||||||
|
ValueKey('exchange_trade_page_send_failure_dialog_button_key'),
|
||||||
|
);
|
||||||
|
|
||||||
|
bool hasError = errorDialog.tryEvaluate();
|
||||||
|
|
||||||
|
tester.printToConsole('Has error: $hasError');
|
||||||
|
|
||||||
|
return hasError;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> handleConfirmSendResult() async {
|
||||||
|
bool hasError = false;
|
||||||
|
|
||||||
|
hasError = await hasErrorWhileSending();
|
||||||
|
|
||||||
|
int maxRetries = 20;
|
||||||
|
int retries = 0;
|
||||||
|
|
||||||
|
while (hasError && retries < maxRetries) {
|
||||||
|
tester.printToConsole('hasErrorInLoop: $hasError');
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
await onSendFailureDialogButtonPressed();
|
||||||
|
tester.printToConsole('Failure button tapped');
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
|
||||||
|
await onConfirmSendingButtonPressed();
|
||||||
|
tester.printToConsole('Confirm sending button tapped');
|
||||||
|
|
||||||
|
hasError = await hasErrorWhileSending();
|
||||||
|
|
||||||
|
retries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasError) {
|
||||||
|
tester.printToConsole('No error, proceeding with flow');
|
||||||
|
await tester.pump();
|
||||||
|
}
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
}
|
59
integration_test/robots/new_wallet_type_page_robot.dart
Normal file
59
integration_test/robots/new_wallet_type_page_robot.dart
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart';
|
||||||
|
import 'package:cake_wallet/themes/theme_base.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../components/common_test_cases.dart';
|
||||||
|
|
||||||
|
class NewWalletTypePageRobot {
|
||||||
|
NewWalletTypePageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||||
|
|
||||||
|
final WidgetTester tester;
|
||||||
|
late CommonTestCases commonTestCases;
|
||||||
|
|
||||||
|
Future<void> isNewWalletTypePage() async {
|
||||||
|
await commonTestCases.isSpecificPage<NewWalletTypePage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void displaysCorrectTitle(bool isCreate) {
|
||||||
|
commonTestCases.hasText(
|
||||||
|
isCreate ? S.current.wallet_list_create_new_wallet : S.current.wallet_list_restore_wallet,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hasWalletTypeForm() {
|
||||||
|
commonTestCases.hasType<WalletTypeForm>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void displaysCorrectImage(ThemeType type) {
|
||||||
|
final walletTypeImage = Image.asset('assets/images/wallet_type.png').image;
|
||||||
|
final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png').image;
|
||||||
|
|
||||||
|
find.image(
|
||||||
|
type == ThemeType.dark ? walletTypeImage : walletTypeLightImage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> findParticularWalletTypeInScrollableList(WalletType type) async {
|
||||||
|
final scrollableWidget = find.descendant(
|
||||||
|
of: find.byKey(Key('new_wallet_type_scrollable_key')),
|
||||||
|
matching: find.byType(Scrollable),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.scrollUntilVisible(
|
||||||
|
find.byKey(ValueKey('new_wallet_type_${type.name}_button_key')),
|
||||||
|
300,
|
||||||
|
scrollable: scrollableWidget,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> selectWalletType(WalletType type) async {
|
||||||
|
await commonTestCases.tapItemByKey('new_wallet_type_${type.name}_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onNextButtonPressed() async {
|
||||||
|
await commonTestCases.tapItemByKey('new_wallet_type_next_button_key');
|
||||||
|
}
|
||||||
|
}
|
38
integration_test/robots/pin_code_widget_robot.dart
Normal file
38
integration_test/robots/pin_code_widget_robot.dart
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../components/common_test_cases.dart';
|
||||||
|
|
||||||
|
class PinCodeWidgetRobot {
|
||||||
|
PinCodeWidgetRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||||
|
|
||||||
|
final WidgetTester tester;
|
||||||
|
late CommonTestCases commonTestCases;
|
||||||
|
|
||||||
|
void hasPinCodeWidget() {
|
||||||
|
final pinCodeWidget = find.bySubtype<PinCodeWidget>();
|
||||||
|
expect(pinCodeWidget, findsOneWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hasNumberButtonsVisible() {
|
||||||
|
// Confirmation for buttons 1-9
|
||||||
|
for (var i = 1; i < 10; i++) {
|
||||||
|
commonTestCases.hasValueKey('pin_code_button_${i}_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirmation for 0 button
|
||||||
|
commonTestCases.hasValueKey('pin_code_button_0_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> pushPinButton(int index) async {
|
||||||
|
await commonTestCases.tapItemByKey('pin_code_button_${index}_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> enterPinCode(List<int> pinCode, bool isFirstEntry) async {
|
||||||
|
for (int pin in pinCode) {
|
||||||
|
await pushPinButton(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
}
|
89
integration_test/robots/restore_from_seed_or_key_robot.dart
Normal file
89
integration_test/robots/restore_from_seed_or_key_robot.dart
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../components/common_test_cases.dart';
|
||||||
|
|
||||||
|
class RestoreFromSeedOrKeysPageRobot {
|
||||||
|
RestoreFromSeedOrKeysPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||||
|
|
||||||
|
final WidgetTester tester;
|
||||||
|
late CommonTestCases commonTestCases;
|
||||||
|
|
||||||
|
Future<void> isRestoreFromSeedKeyPage() async {
|
||||||
|
await commonTestCases.isSpecificPage<WalletRestorePage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> confirmViewComponentsDisplayProperlyPerPageView() async {
|
||||||
|
commonTestCases.hasText(S.current.wallet_name);
|
||||||
|
commonTestCases.hasText(S.current.enter_seed_phrase);
|
||||||
|
commonTestCases.hasText(S.current.restore_title_from_seed);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
await commonTestCases.swipePage();
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
|
||||||
|
commonTestCases.hasText(S.current.wallet_name);
|
||||||
|
commonTestCases.hasText(S.current.private_key);
|
||||||
|
commonTestCases.hasText(S.current.restore_title_from_keys);
|
||||||
|
|
||||||
|
commonTestCases.hasText(S.current.enter_seed_phrase, hasWidget: false);
|
||||||
|
commonTestCases.hasText(S.current.restore_title_from_seed, hasWidget: false);
|
||||||
|
|
||||||
|
await commonTestCases.swipePage(swipeRight: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void confirmRestoreButtonDisplays() {
|
||||||
|
commonTestCases.hasValueKey('wallet_restore_seed_or_key_restore_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
void confirmAdvancedSettingButtonDisplays() {
|
||||||
|
commonTestCases.hasValueKey('wallet_restore_advanced_settings_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> enterWalletNameText(String walletName, {bool isSeedFormEntry = true}) async {
|
||||||
|
await commonTestCases.enterText(
|
||||||
|
walletName,
|
||||||
|
'wallet_restore_from_${isSeedFormEntry ? 'seed' : 'keys'}_wallet_name_textfield_key',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> selectWalletNameFromAvailableOptions({bool isSeedFormEntry = true}) async {
|
||||||
|
await commonTestCases.tapItemByKey(
|
||||||
|
'wallet_restore_from_${isSeedFormEntry ? 'seed' : 'keys'}_wallet_name_refresh_button_key',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> enterSeedPhraseForWalletRestore(String text) async {
|
||||||
|
ValidatableAnnotatedEditableTextState seedTextState =
|
||||||
|
await tester.state(find.byType(ValidatableAnnotatedEditableText));
|
||||||
|
|
||||||
|
seedTextState.widget.controller.text = text;
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onPasteSeedPhraseButtonPressed() async {
|
||||||
|
await commonTestCases.tapItemByKey('wallet_restore_from_seed_wallet_seeds_paste_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> enterPrivateKeyForWalletRestore(String privateKey) async {
|
||||||
|
await commonTestCases.enterText(
|
||||||
|
privateKey,
|
||||||
|
'wallet_restore_from_key_private_key_textfield_key',
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onRestoreWalletButtonPressed() async {
|
||||||
|
await commonTestCases.tapItemByKey('wallet_restore_seed_or_key_restore_button_key');
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
}
|
42
integration_test/robots/restore_options_page_robot.dart
Normal file
42
integration_test/robots/restore_options_page_robot.dart
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import 'package:cake_wallet/src/screens/restore/restore_options_page.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../components/common_test_cases.dart';
|
||||||
|
|
||||||
|
class RestoreOptionsPageRobot {
|
||||||
|
RestoreOptionsPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||||
|
|
||||||
|
final WidgetTester tester;
|
||||||
|
late CommonTestCases commonTestCases;
|
||||||
|
|
||||||
|
Future<void> isRestoreOptionsPage() async {
|
||||||
|
await commonTestCases.isSpecificPage<RestoreOptionsPage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void hasRestoreOptionsButton() {
|
||||||
|
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 {
|
||||||
|
await commonTestCases.tapItemByKey('restore_options_from_seeds_button_key');
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> navigateToRestoreFromBackupPage() async {
|
||||||
|
await commonTestCases.tapItemByKey('restore_options_from_backup_button_key');
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> navigateToRestoreFromHardwareWalletPage() async {
|
||||||
|
await commonTestCases.tapItemByKey('restore_options_from_hardware_wallet_button_key');
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> backAndVerify() async {
|
||||||
|
await commonTestCases.goBack();
|
||||||
|
await isRestoreOptionsPage();
|
||||||
|
}
|
||||||
|
}
|
366
integration_test/robots/send_page_robot.dart
Normal file
366
integration_test/robots/send_page_robot.dart
Normal file
|
@ -0,0 +1,366 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cake_wallet/core/execution_state.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/send/send_page.dart';
|
||||||
|
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../components/common_test_cases.dart';
|
||||||
|
import '../components/common_test_constants.dart';
|
||||||
|
import 'auth_page_robot.dart';
|
||||||
|
|
||||||
|
class SendPageRobot {
|
||||||
|
SendPageRobot({required this.tester})
|
||||||
|
: commonTestCases = CommonTestCases(tester),
|
||||||
|
authPageRobot = AuthPageRobot(tester);
|
||||||
|
|
||||||
|
WidgetTester tester;
|
||||||
|
CommonTestCases commonTestCases;
|
||||||
|
AuthPageRobot authPageRobot;
|
||||||
|
|
||||||
|
Future<void> isSendPage() async {
|
||||||
|
await commonTestCases.isSpecificPage<SendPage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void hasTitle() {
|
||||||
|
commonTestCases.hasText(S.current.send);
|
||||||
|
}
|
||||||
|
|
||||||
|
void confirmViewComponentsDisplayProperly() {
|
||||||
|
SendPage sendPage = tester.widget(find.byType(SendPage));
|
||||||
|
final sendViewModel = sendPage.sendViewModel;
|
||||||
|
|
||||||
|
commonTestCases.hasValueKey('send_page_address_textfield_key');
|
||||||
|
commonTestCases.hasValueKey('send_page_note_textfield_key');
|
||||||
|
commonTestCases.hasValueKey('send_page_amount_textfield_key');
|
||||||
|
commonTestCases.hasValueKey('send_page_add_template_button_key');
|
||||||
|
|
||||||
|
if (sendViewModel.hasMultipleTokens) {
|
||||||
|
commonTestCases.hasValueKey('send_page_currency_picker_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sendViewModel.isBatchSending) {
|
||||||
|
commonTestCases.hasValueKey('send_page_send_all_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sendViewModel.isFiatDisabled) {
|
||||||
|
commonTestCases.hasValueKey('send_page_fiat_amount_textfield_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendViewModel.hasFees) {
|
||||||
|
commonTestCases.hasValueKey('send_page_select_fee_priority_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendViewModel.hasCoinControl) {
|
||||||
|
commonTestCases.hasValueKey('send_page_unspent_coin_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendViewModel.hasCurrecyChanger) {
|
||||||
|
commonTestCases.hasValueKey('send_page_change_asset_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendViewModel.sendTemplateViewModel.hasMultiRecipient) {
|
||||||
|
commonTestCases.hasValueKey('send_page_add_receiver_button_key');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> selectReceiveCurrency(CryptoCurrency receiveCurrency) async {
|
||||||
|
final currencyPickerKey = 'send_page_currency_picker_button_key';
|
||||||
|
final currencyPickerDialogKey = 'send_page_currency_picker_dialog_button_key';
|
||||||
|
|
||||||
|
await commonTestCases.tapItemByKey(currencyPickerKey);
|
||||||
|
commonTestCases.hasValueKey(currencyPickerDialogKey);
|
||||||
|
|
||||||
|
SendPage sendPage = tester.widget(find.byType(SendPage));
|
||||||
|
final sendViewModel = sendPage.sendViewModel;
|
||||||
|
|
||||||
|
if (receiveCurrency == sendViewModel.selectedCryptoCurrency) {
|
||||||
|
await commonTestCases
|
||||||
|
.tapItemByKey('picker_items_index_${receiveCurrency.name}_selected_item_button_key');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> enterReceiveAddress(String receiveAddress) async {
|
||||||
|
await commonTestCases.enterText(receiveAddress, 'send_page_address_textfield_key');
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> enterAmount(String amount) async {
|
||||||
|
await commonTestCases.enterText(amount, 'send_page_amount_textfield_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> selectTransactionPriority({TransactionPriority? priority}) async {
|
||||||
|
SendPage sendPage = tester.widget(find.byType(SendPage));
|
||||||
|
final sendViewModel = sendPage.sendViewModel;
|
||||||
|
|
||||||
|
if (!sendViewModel.hasFees || priority == null) return;
|
||||||
|
|
||||||
|
final transactionPriorityPickerKey = 'send_page_select_fee_priority_button_key';
|
||||||
|
await commonTestCases.tapItemByKey(transactionPriorityPickerKey);
|
||||||
|
|
||||||
|
if (priority == sendViewModel.transactionPriority) {
|
||||||
|
await commonTestCases
|
||||||
|
.tapItemByKey('picker_items_index_${priority.title}_selected_item_button_key');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await commonTestCases.scrollUntilVisible(
|
||||||
|
'picker_items_index_${priority.title}_button_key',
|
||||||
|
'picker_scrollbar_key',
|
||||||
|
);
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
|
||||||
|
await commonTestCases.tapItemByKey('picker_items_index_${priority.title}_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onSendButtonPressed() async {
|
||||||
|
tester.printToConsole('Pressing send');
|
||||||
|
|
||||||
|
await commonTestCases.tapItemByKey(
|
||||||
|
'send_page_send_button_key',
|
||||||
|
shouldPumpAndSettle: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _waitForSendTransactionCompletion();
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _waitForSendTransactionCompletion() async {
|
||||||
|
await tester.pump();
|
||||||
|
final Completer<void> completer = Completer<void>();
|
||||||
|
|
||||||
|
// Loop to wait for the async operation to complete
|
||||||
|
while (true) {
|
||||||
|
await Future.delayed(Duration(seconds: 1));
|
||||||
|
|
||||||
|
tester.printToConsole('Before _handleAuth');
|
||||||
|
|
||||||
|
await _handleAuthPage();
|
||||||
|
|
||||||
|
tester.printToConsole('After _handleAuth');
|
||||||
|
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
final sendPage = tester.widget<SendPage>(find.byType(SendPage));
|
||||||
|
final state = sendPage.sendViewModel.state;
|
||||||
|
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
bool isDone = state is ExecutedSuccessfullyState;
|
||||||
|
bool isFailed = state is FailureState;
|
||||||
|
|
||||||
|
tester.printToConsole('isDone: $isDone');
|
||||||
|
tester.printToConsole('isFailed: $isFailed');
|
||||||
|
|
||||||
|
if (isDone || isFailed) {
|
||||||
|
tester.printToConsole(
|
||||||
|
isDone ? 'Completer is done' : 'Completer is done though operation failed',
|
||||||
|
);
|
||||||
|
completer.complete();
|
||||||
|
await tester.pump();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
tester.printToConsole('Completer is not done');
|
||||||
|
await tester.pump();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await expectLater(completer.future, completes);
|
||||||
|
|
||||||
|
tester.printToConsole('Done confirming sending operation');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleAuthPage() async {
|
||||||
|
tester.printToConsole('Inside _handleAuth');
|
||||||
|
await tester.pump();
|
||||||
|
tester.printToConsole('starting auth checks');
|
||||||
|
|
||||||
|
final authPage = authPageRobot.onAuthPage();
|
||||||
|
|
||||||
|
tester.printToConsole('hasAuth:$authPage');
|
||||||
|
|
||||||
|
if (authPage) {
|
||||||
|
await tester.pump();
|
||||||
|
tester.printToConsole('Starting inner _handleAuth loop checks');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await authPageRobot.enterPinCode(CommonTestConstants.pin, false);
|
||||||
|
tester.printToConsole('Auth done');
|
||||||
|
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
tester.printToConsole('Auth pump done');
|
||||||
|
} catch (e) {
|
||||||
|
tester.printToConsole('Auth failed, retrying');
|
||||||
|
await tester.pump();
|
||||||
|
_handleAuthPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> handleSendResult() async {
|
||||||
|
tester.printToConsole('Inside handle function');
|
||||||
|
|
||||||
|
bool hasError = false;
|
||||||
|
|
||||||
|
hasError = await hasErrorWhileSending();
|
||||||
|
|
||||||
|
tester.printToConsole('Has an Error in the handle: $hasError');
|
||||||
|
|
||||||
|
int maxRetries = 20;
|
||||||
|
int retries = 0;
|
||||||
|
|
||||||
|
while (hasError && retries < maxRetries) {
|
||||||
|
tester.printToConsole('hasErrorInLoop: $hasError');
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
await onSendFailureDialogButtonPressed();
|
||||||
|
tester.printToConsole('Failure button tapped');
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
|
||||||
|
await onSendButtonPressed();
|
||||||
|
tester.printToConsole('Send button tapped');
|
||||||
|
|
||||||
|
hasError = await hasErrorWhileSending();
|
||||||
|
|
||||||
|
retries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasError) {
|
||||||
|
tester.printToConsole('No error, proceeding with flow');
|
||||||
|
await tester.pump();
|
||||||
|
}
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
//* ------ On Sending Failure ------------
|
||||||
|
Future<bool> hasErrorWhileSending() async {
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
tester.printToConsole('Checking if there is an error');
|
||||||
|
|
||||||
|
final errorDialog = find.byKey(ValueKey('send_page_send_failure_dialog_button_key'));
|
||||||
|
|
||||||
|
bool hasError = errorDialog.tryEvaluate();
|
||||||
|
|
||||||
|
tester.printToConsole('Has error: $hasError');
|
||||||
|
|
||||||
|
return hasError;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onSendFailureDialogButtonPressed() async {
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
|
||||||
|
tester.printToConsole('Send Button Failure Dialog Triggered');
|
||||||
|
|
||||||
|
await commonTestCases.tapItemByKey('send_page_send_failure_dialog_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
//* ------ On Sending Success ------------
|
||||||
|
Future<void> onSendButtonOnConfirmSendingDialogPressed() async {
|
||||||
|
tester.printToConsole('Inside confirm sending dialog: For sending');
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
final sendText = find.text(S.current.send).last;
|
||||||
|
bool hasText = sendText.tryEvaluate();
|
||||||
|
tester.printToConsole('Has Text: $hasText');
|
||||||
|
|
||||||
|
if (hasText) {
|
||||||
|
await commonTestCases.tapItemByFinder(sendText, shouldPumpAndSettle: false);
|
||||||
|
// Loop to wait for the operation to commit transaction
|
||||||
|
await _waitForCommitTransactionCompletion();
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime(seconds: 4);
|
||||||
|
} else {
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
await tester.pump();
|
||||||
|
onSendButtonOnConfirmSendingDialogPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _waitForCommitTransactionCompletion() async {
|
||||||
|
final Completer<void> completer = Completer<void>();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
await Future.delayed(Duration(seconds: 1));
|
||||||
|
|
||||||
|
final sendPage = tester.widget<SendPage>(find.byType(SendPage));
|
||||||
|
final state = sendPage.sendViewModel.state;
|
||||||
|
|
||||||
|
bool isDone = state is TransactionCommitted;
|
||||||
|
bool isFailed = state is FailureState;
|
||||||
|
|
||||||
|
tester.printToConsole('isDone: $isDone');
|
||||||
|
tester.printToConsole('isFailed: $isFailed');
|
||||||
|
|
||||||
|
if (isDone || isFailed) {
|
||||||
|
tester.printToConsole(
|
||||||
|
isDone ? 'Completer is done' : 'Completer is done though operation failed',
|
||||||
|
);
|
||||||
|
completer.complete();
|
||||||
|
await tester.pump();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
tester.printToConsole('Completer is not done');
|
||||||
|
await tester.pump();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await expectLater(completer.future, completes);
|
||||||
|
|
||||||
|
tester.printToConsole('Done Committing Transaction');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onCancelButtonOnConfirmSendingDialogPressed() async {
|
||||||
|
tester.printToConsole('Inside confirm sending dialog: For canceling');
|
||||||
|
await commonTestCases.defaultSleepTime(seconds: 4);
|
||||||
|
|
||||||
|
final cancelText = find.text(S.current.cancel);
|
||||||
|
bool hasText = cancelText.tryEvaluate();
|
||||||
|
|
||||||
|
if (hasText) {
|
||||||
|
await commonTestCases.tapItemByFinder(cancelText);
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime(seconds: 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//* ---- Add Contact Dialog On Send Successful Dialog -----
|
||||||
|
Future<void> onSentDialogPopUp() async {
|
||||||
|
SendPage sendPage = tester.widget(find.byType(SendPage));
|
||||||
|
final sendViewModel = sendPage.sendViewModel;
|
||||||
|
|
||||||
|
final newContactAddress = sendPage.newContactAddress ?? sendViewModel.newContactAddress();
|
||||||
|
if (newContactAddress != null) {
|
||||||
|
await _onAddContactButtonOnSentDialogPressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onAddContactButtonOnSentDialogPressed() async {
|
||||||
|
await commonTestCases.tapItemByKey('send_page_sent_dialog_add_contact_button_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore: unused_element
|
||||||
|
Future<void> _onIgnoreButtonOnSentDialogPressed() async {
|
||||||
|
await commonTestCases.tapItemByKey('send_page_sent_dialog_ignore_button_key');
|
||||||
|
}
|
||||||
|
}
|
28
integration_test/robots/setup_pin_code_robot.dart
Normal file
28
integration_test/robots/setup_pin_code_robot.dart
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../components/common_test_cases.dart';
|
||||||
|
import 'pin_code_widget_robot.dart';
|
||||||
|
|
||||||
|
class SetupPinCodeRobot extends PinCodeWidgetRobot {
|
||||||
|
SetupPinCodeRobot(this.tester)
|
||||||
|
: commonTestCases = CommonTestCases(tester),
|
||||||
|
super(tester);
|
||||||
|
|
||||||
|
final WidgetTester tester;
|
||||||
|
late CommonTestCases commonTestCases;
|
||||||
|
|
||||||
|
Future<void> isSetupPinCodePage() async {
|
||||||
|
await commonTestCases.isSpecificPage<SetupPinCodePage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void hasTitle() {
|
||||||
|
commonTestCases.hasText(S.current.setup_pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tapSuccessButton() async {
|
||||||
|
await commonTestCases.tapItemByKey('setup_pin_code_success_button_key');
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
}
|
40
integration_test/robots/welcome_page_robot.dart
Normal file
40
integration_test/robots/welcome_page_robot.dart
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:cake_wallet/src/screens/welcome/welcome_page.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../components/common_test_cases.dart';
|
||||||
|
|
||||||
|
class WelcomePageRobot {
|
||||||
|
WelcomePageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||||
|
|
||||||
|
final WidgetTester tester;
|
||||||
|
late CommonTestCases commonTestCases;
|
||||||
|
|
||||||
|
Future<void> isWelcomePage() async {
|
||||||
|
await commonTestCases.isSpecificPage<WelcomePage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void confirmActionButtonsDisplay() {
|
||||||
|
final createNewWalletButton = find.byKey(ValueKey('welcome_page_create_new_wallet_button_key'));
|
||||||
|
|
||||||
|
final restoreWalletButton = find.byKey(ValueKey('welcome_page_restore_wallet_button_key'));
|
||||||
|
|
||||||
|
expect(createNewWalletButton, findsOneWidget);
|
||||||
|
expect(restoreWalletButton, findsOneWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> navigateToCreateNewWalletPage() async {
|
||||||
|
await commonTestCases.tapItemByKey('welcome_page_create_new_wallet_button_key');
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> navigateToRestoreWalletPage() async {
|
||||||
|
await commonTestCases.tapItemByKey('welcome_page_restore_wallet_button_key');
|
||||||
|
await commonTestCases.defaultSleepTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> backAndVerify() async {
|
||||||
|
await commonTestCases.goBack();
|
||||||
|
await isWelcomePage();
|
||||||
|
}
|
||||||
|
}
|
59
integration_test/test_suites/exchange_flow_test.dart
Normal file
59
integration_test/test_suites/exchange_flow_test.dart
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import '../components/common_test_constants.dart';
|
||||||
|
import '../components/common_test_flows.dart';
|
||||||
|
import '../robots/auth_page_robot.dart';
|
||||||
|
import '../robots/dashboard_page_robot.dart';
|
||||||
|
import '../robots/exchange_confirm_page_robot.dart';
|
||||||
|
import '../robots/exchange_page_robot.dart';
|
||||||
|
import '../robots/exchange_trade_page_robot.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
AuthPageRobot authPageRobot;
|
||||||
|
CommonTestFlows commonTestFlows;
|
||||||
|
ExchangePageRobot exchangePageRobot;
|
||||||
|
DashboardPageRobot dashboardPageRobot;
|
||||||
|
ExchangeTradePageRobot exchangeTradePageRobot;
|
||||||
|
ExchangeConfirmPageRobot exchangeConfirmPageRobot;
|
||||||
|
|
||||||
|
group('Exchange Flow Tests', () {
|
||||||
|
testWidgets('Exchange flow', (tester) async {
|
||||||
|
authPageRobot = AuthPageRobot(tester);
|
||||||
|
commonTestFlows = CommonTestFlows(tester);
|
||||||
|
exchangePageRobot = ExchangePageRobot(tester);
|
||||||
|
dashboardPageRobot = DashboardPageRobot(tester);
|
||||||
|
exchangeTradePageRobot = ExchangeTradePageRobot(tester);
|
||||||
|
exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester);
|
||||||
|
|
||||||
|
await commonTestFlows.startAppFlow(ValueKey('exchange_app_test_key'));
|
||||||
|
await commonTestFlows.restoreWalletThroughSeedsFlow();
|
||||||
|
await dashboardPageRobot.navigateToExchangePage();
|
||||||
|
|
||||||
|
// ----------- Exchange Page -------------
|
||||||
|
await exchangePageRobot.selectDepositCurrency(CommonTestConstants.testDepositCurrency);
|
||||||
|
await exchangePageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency);
|
||||||
|
|
||||||
|
await exchangePageRobot.enterDepositAmount(CommonTestConstants.exchangeTestAmount);
|
||||||
|
await exchangePageRobot.enterDepositRefundAddress(
|
||||||
|
depositAddress: CommonTestConstants.testWalletAddress,
|
||||||
|
);
|
||||||
|
await exchangePageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress);
|
||||||
|
|
||||||
|
await exchangePageRobot.onExchangeButtonPressed();
|
||||||
|
|
||||||
|
await exchangePageRobot.handleErrors(CommonTestConstants.exchangeTestAmount);
|
||||||
|
|
||||||
|
final onAuthPage = authPageRobot.onAuthPage();
|
||||||
|
if (onAuthPage) {
|
||||||
|
await authPageRobot.enterPinCode(CommonTestConstants.pin, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed();
|
||||||
|
await exchangeTradePageRobot.onGotItButtonPressed();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
41
integration_test/test_suites/send_flow_test.dart
Normal file
41
integration_test/test_suites/send_flow_test.dart
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import '../components/common_test_constants.dart';
|
||||||
|
import '../components/common_test_flows.dart';
|
||||||
|
import '../robots/dashboard_page_robot.dart';
|
||||||
|
import '../robots/send_page_robot.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
SendPageRobot sendPageRobot;
|
||||||
|
CommonTestFlows commonTestFlows;
|
||||||
|
DashboardPageRobot dashboardPageRobot;
|
||||||
|
|
||||||
|
group('Send Flow Tests', () {
|
||||||
|
testWidgets('Send flow', (tester) async {
|
||||||
|
commonTestFlows = CommonTestFlows(tester);
|
||||||
|
sendPageRobot = SendPageRobot(tester: tester);
|
||||||
|
dashboardPageRobot = DashboardPageRobot(tester);
|
||||||
|
|
||||||
|
await commonTestFlows.startAppFlow(ValueKey('send_test_app_key'));
|
||||||
|
await commonTestFlows.restoreWalletThroughSeedsFlow();
|
||||||
|
await dashboardPageRobot.navigateToSendPage();
|
||||||
|
|
||||||
|
await sendPageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress);
|
||||||
|
await sendPageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency);
|
||||||
|
await sendPageRobot.enterAmount(CommonTestConstants.sendTestAmount);
|
||||||
|
await sendPageRobot.selectTransactionPriority();
|
||||||
|
|
||||||
|
await sendPageRobot.onSendButtonPressed();
|
||||||
|
|
||||||
|
await sendPageRobot.handleSendResult();
|
||||||
|
|
||||||
|
await sendPageRobot.onSendButtonOnConfirmSendingDialogPressed();
|
||||||
|
|
||||||
|
await sendPageRobot.onSentDialogPopUp();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -66,6 +66,8 @@ PODS:
|
||||||
- Toast
|
- Toast
|
||||||
- in_app_review (0.2.0):
|
- in_app_review (0.2.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- integration_test (0.0.1):
|
||||||
|
- Flutter
|
||||||
- MTBBarcodeScanner (5.0.11)
|
- MTBBarcodeScanner (5.0.11)
|
||||||
- OrderedSet (5.0.0)
|
- OrderedSet (5.0.0)
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
|
@ -120,6 +122,8 @@ DEPENDENCIES:
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||||
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
|
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
|
||||||
|
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||||
|
- package_info (from `.symlinks/plugins/package_info/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
|
@ -174,6 +178,10 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||||
in_app_review:
|
in_app_review:
|
||||||
:path: ".symlinks/plugins/in_app_review/ios"
|
:path: ".symlinks/plugins/in_app_review/ios"
|
||||||
|
integration_test:
|
||||||
|
:path: ".symlinks/plugins/integration_test/ios"
|
||||||
|
package_info:
|
||||||
|
:path: ".symlinks/plugins/package_info/ios"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
|
@ -216,6 +224,7 @@ SPEC CHECKSUMS:
|
||||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||||
fluttertoast: 48c57db1b71b0ce9e6bba9f31c940ff4b001293c
|
fluttertoast: 48c57db1b71b0ce9e6bba9f31c940ff4b001293c
|
||||||
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d
|
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d
|
||||||
|
integration_test: 13825b8a9334a850581300559b8839134b124670
|
||||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
||||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||||
|
|
|
@ -47,11 +47,11 @@ final navigatorKey = GlobalKey<NavigatorState>();
|
||||||
final rootKey = GlobalKey<RootState>();
|
final rootKey = GlobalKey<RootState>();
|
||||||
final RouteObserver<PageRoute<dynamic>> routeObserver = RouteObserver<PageRoute<dynamic>>();
|
final RouteObserver<PageRoute<dynamic>> routeObserver = RouteObserver<PageRoute<dynamic>>();
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main({Key? topLevelKey}) async {
|
||||||
await runAppWithZone();
|
await runAppWithZone(topLevelKey: topLevelKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> runAppWithZone() async {
|
Future<void> runAppWithZone({Key? topLevelKey}) async {
|
||||||
bool isAppRunning = false;
|
bool isAppRunning = false;
|
||||||
|
|
||||||
await runZonedGuarded(() async {
|
await runZonedGuarded(() async {
|
||||||
|
@ -67,7 +67,8 @@ Future<void> runAppWithZone() async {
|
||||||
};
|
};
|
||||||
await initializeAppAtRoot();
|
await initializeAppAtRoot();
|
||||||
|
|
||||||
runApp(App());
|
runApp(App(key: topLevelKey));
|
||||||
|
|
||||||
isAppRunning = true;
|
isAppRunning = true;
|
||||||
}, (error, stackTrace) async {
|
}, (error, stackTrace) async {
|
||||||
if (!isAppRunning) {
|
if (!isAppRunning) {
|
||||||
|
@ -236,6 +237,9 @@ Future<void> initialSetup(
|
||||||
}
|
}
|
||||||
|
|
||||||
class App extends StatefulWidget {
|
class App extends StatefulWidget {
|
||||||
|
App({this.key});
|
||||||
|
|
||||||
|
final Key? key;
|
||||||
@override
|
@override
|
||||||
AppState createState() => AppState();
|
AppState createState() => AppState();
|
||||||
}
|
}
|
||||||
|
@ -264,7 +268,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
|
||||||
statusBarIconBrightness: statusBarIconBrightness));
|
statusBarIconBrightness: statusBarIconBrightness));
|
||||||
|
|
||||||
return Root(
|
return Root(
|
||||||
key: rootKey,
|
key: widget.key ?? rootKey,
|
||||||
appStore: appStore,
|
appStore: appStore,
|
||||||
authenticationStore: authenticationStore,
|
authenticationStore: authenticationStore,
|
||||||
navigatorKey: navigatorKey,
|
navigatorKey: navigatorKey,
|
||||||
|
|
|
@ -147,6 +147,7 @@ class _DashboardPageView extends BasePage {
|
||||||
return Observer(
|
return Observer(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return ServicesUpdatesWidget(
|
return ServicesUpdatesWidget(
|
||||||
|
key: ValueKey('dashboard_page_services_update_button_key'),
|
||||||
dashboardViewModel.getServicesStatus(),
|
dashboardViewModel.getServicesStatus(),
|
||||||
enabled: dashboardViewModel.isEnabledBulletinAction,
|
enabled: dashboardViewModel.isEnabledBulletinAction,
|
||||||
);
|
);
|
||||||
|
@ -157,6 +158,7 @@ class _DashboardPageView extends BasePage {
|
||||||
@override
|
@override
|
||||||
Widget middle(BuildContext context) {
|
Widget middle(BuildContext context) {
|
||||||
return SyncIndicator(
|
return SyncIndicator(
|
||||||
|
key: ValueKey('dashboard_page_sync_indicator_button_key'),
|
||||||
dashboardViewModel: dashboardViewModel,
|
dashboardViewModel: dashboardViewModel,
|
||||||
onTap: () => Navigator.of(context, rootNavigator: true).pushNamed(Routes.connectionSync),
|
onTap: () => Navigator.of(context, rootNavigator: true).pushNamed(Routes.connectionSync),
|
||||||
);
|
);
|
||||||
|
@ -173,6 +175,7 @@ class _DashboardPageView extends BasePage {
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
width: 40,
|
width: 40,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
|
key: ValueKey('dashboard_page_wallet_menu_button_key'),
|
||||||
// FIX-ME: Style
|
// FIX-ME: Style
|
||||||
//highlightColor: Colors.transparent,
|
//highlightColor: Colors.transparent,
|
||||||
//splashColor: Colors.transparent,
|
//splashColor: Colors.transparent,
|
||||||
|
@ -226,6 +229,7 @@ class _DashboardPageView extends BasePage {
|
||||||
child: Observer(
|
child: Observer(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return PageView.builder(
|
return PageView.builder(
|
||||||
|
key: ValueKey('dashboard_page_view_key'),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
itemCount: pages.length,
|
itemCount: pages.length,
|
||||||
itemBuilder: (context, index) => pages[index],
|
itemBuilder: (context, index) => pages[index],
|
||||||
|
@ -291,6 +295,8 @@ class _DashboardPageView extends BasePage {
|
||||||
button: true,
|
button: true,
|
||||||
enabled: (action.isEnabled?.call(dashboardViewModel) ?? true),
|
enabled: (action.isEnabled?.call(dashboardViewModel) ?? true),
|
||||||
child: ActionButton(
|
child: ActionButton(
|
||||||
|
key: ValueKey(
|
||||||
|
'dashboard_page_${action.name(context)}_action_button_key'),
|
||||||
image: Image.asset(
|
image: Image.asset(
|
||||||
action.image,
|
action.image,
|
||||||
height: 24,
|
height: 24,
|
||||||
|
|
|
@ -2,13 +2,15 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||||
|
|
||||||
class ActionButton extends StatelessWidget {
|
class ActionButton extends StatelessWidget {
|
||||||
ActionButton(
|
ActionButton({
|
||||||
{required this.image,
|
required this.image,
|
||||||
required this.title,
|
required this.title,
|
||||||
this.route,
|
this.route,
|
||||||
this.onClick,
|
this.onClick,
|
||||||
this.alignment = Alignment.center,
|
this.alignment = Alignment.center,
|
||||||
this.textColor});
|
this.textColor,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
final Image image;
|
final Image image;
|
||||||
final String title;
|
final String title;
|
||||||
|
|
|
@ -7,7 +7,11 @@ import 'package:cw_core/sync_status.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
|
||||||
|
|
||||||
class SyncIndicator extends StatelessWidget {
|
class SyncIndicator extends StatelessWidget {
|
||||||
SyncIndicator({required this.dashboardViewModel, required this.onTap});
|
SyncIndicator({
|
||||||
|
required this.dashboardViewModel,
|
||||||
|
required this.onTap,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
final DashboardViewModel dashboardViewModel;
|
final DashboardViewModel dashboardViewModel;
|
||||||
final Function() onTap;
|
final Function() onTap;
|
||||||
|
|
|
@ -207,6 +207,7 @@ class DisclaimerBodyState extends State<DisclaimerPageBody> {
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
left: 24.0, top: 10.0, right: 24.0, bottom: 10.0),
|
left: 24.0, top: 10.0, right: 24.0, bottom: 10.0),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
|
key: ValueKey('disclaimer_check_key'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_checked = !_checked;
|
_checked = !_checked;
|
||||||
|
@ -230,6 +231,7 @@ class DisclaimerBodyState extends State<DisclaimerPageBody> {
|
||||||
color: Theme.of(context).colorScheme.background),
|
color: Theme.of(context).colorScheme.background),
|
||||||
child: _checked
|
child: _checked
|
||||||
? Icon(
|
? Icon(
|
||||||
|
key: ValueKey('disclaimer_check_icon_key'),
|
||||||
Icons.check,
|
Icons.check,
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
size: 20.0,
|
size: 20.0,
|
||||||
|
@ -253,6 +255,7 @@ class DisclaimerBodyState extends State<DisclaimerPageBody> {
|
||||||
padding:
|
padding:
|
||||||
EdgeInsets.only(left: 24.0, right: 24.0, bottom: 24.0),
|
EdgeInsets.only(left: 24.0, right: 24.0, bottom: 24.0),
|
||||||
child: PrimaryButton(
|
child: PrimaryButton(
|
||||||
|
key: ValueKey('disclaimer_accept_button_key'),
|
||||||
onPressed: _checked
|
onPressed: _checked
|
||||||
? () => Navigator.of(context)
|
? () => Navigator.of(context)
|
||||||
.popAndPushNamed(Routes.welcome)
|
.popAndPushNamed(Routes.welcome)
|
||||||
|
|
|
@ -228,6 +228,7 @@ class ExchangePage extends BasePage {
|
||||||
),
|
),
|
||||||
Observer(
|
Observer(
|
||||||
builder: (_) => LoadingPrimaryButton(
|
builder: (_) => LoadingPrimaryButton(
|
||||||
|
key: ValueKey('exchange_page_exchange_button_key'),
|
||||||
text: S.of(context).exchange,
|
text: S.of(context).exchange,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_formKey.currentState != null &&
|
if (_formKey.currentState != null &&
|
||||||
|
@ -430,6 +431,8 @@ class ExchangePage extends BasePage {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertWithOneAction(
|
return AlertWithOneAction(
|
||||||
|
key: ValueKey('exchange_page_trade_creation_failure_dialog_key'),
|
||||||
|
buttonKey: ValueKey('exchange_page_trade_creation_failure_dialog_button_key'),
|
||||||
alertTitle: S.of(context).provider_error(state.title),
|
alertTitle: S.of(context).provider_error(state.title),
|
||||||
alertContent: state.error,
|
alertContent: state.error,
|
||||||
buttonText: S.of(context).ok,
|
buttonText: S.of(context).ok,
|
||||||
|
@ -612,6 +615,7 @@ class ExchangePage extends BasePage {
|
||||||
Widget _exchangeCardsSection(BuildContext context) {
|
Widget _exchangeCardsSection(BuildContext context) {
|
||||||
final firstExchangeCard = Observer(
|
final firstExchangeCard = Observer(
|
||||||
builder: (_) => ExchangeCard(
|
builder: (_) => ExchangeCard(
|
||||||
|
cardInstanceName: 'deposit_exchange_card',
|
||||||
onDispose: disposeBestRateSync,
|
onDispose: disposeBestRateSync,
|
||||||
hasAllAmount: exchangeViewModel.hasAllAmount,
|
hasAllAmount: exchangeViewModel.hasAllAmount,
|
||||||
allAmount: exchangeViewModel.hasAllAmount
|
allAmount: exchangeViewModel.hasAllAmount
|
||||||
|
@ -681,6 +685,7 @@ class ExchangePage extends BasePage {
|
||||||
|
|
||||||
final secondExchangeCard = Observer(
|
final secondExchangeCard = Observer(
|
||||||
builder: (_) => ExchangeCard(
|
builder: (_) => ExchangeCard(
|
||||||
|
cardInstanceName: 'receive_exchange_card',
|
||||||
onDispose: disposeBestRateSync,
|
onDispose: disposeBestRateSync,
|
||||||
amountFocusNode: _receiveAmountFocus,
|
amountFocusNode: _receiveAmountFocus,
|
||||||
addressFocusNode: _receiveAddressFocus,
|
addressFocusNode: _receiveAddressFocus,
|
||||||
|
|
|
@ -121,6 +121,7 @@ class ExchangeTemplatePage extends BasePage {
|
||||||
padding: EdgeInsets.fromLTRB(24, 100, 24, 32),
|
padding: EdgeInsets.fromLTRB(24, 100, 24, 32),
|
||||||
child: Observer(
|
child: Observer(
|
||||||
builder: (_) => ExchangeCard(
|
builder: (_) => ExchangeCard(
|
||||||
|
cardInstanceName: 'deposit_exchange_template_card',
|
||||||
amountFocusNode: _depositAmountFocus,
|
amountFocusNode: _depositAmountFocus,
|
||||||
key: depositKey,
|
key: depositKey,
|
||||||
title: S.of(context).you_will_send,
|
title: S.of(context).you_will_send,
|
||||||
|
@ -157,6 +158,7 @@ class ExchangeTemplatePage extends BasePage {
|
||||||
padding: EdgeInsets.only(top: 29, left: 24, right: 24),
|
padding: EdgeInsets.only(top: 29, left: 24, right: 24),
|
||||||
child: Observer(
|
child: Observer(
|
||||||
builder: (_) => ExchangeCard(
|
builder: (_) => ExchangeCard(
|
||||||
|
cardInstanceName: 'receive_exchange_template_card',
|
||||||
amountFocusNode: _receiveAmountFocus,
|
amountFocusNode: _receiveAmountFocus,
|
||||||
key: receiveKey,
|
key: receiveKey,
|
||||||
title: S.of(context).you_will_get,
|
title: S.of(context).you_will_get,
|
||||||
|
|
|
@ -12,7 +12,8 @@ class CurrencyPicker extends StatefulWidget {
|
||||||
this.title,
|
this.title,
|
||||||
this.hintText,
|
this.hintText,
|
||||||
this.isMoneroWallet = false,
|
this.isMoneroWallet = false,
|
||||||
this.isConvertFrom = false});
|
this.isConvertFrom = false,
|
||||||
|
super.key});
|
||||||
|
|
||||||
final int selectedAtIndex;
|
final int selectedAtIndex;
|
||||||
final List<Currency> items;
|
final List<Currency> items;
|
||||||
|
|
|
@ -19,34 +19,35 @@ import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||||
|
|
||||||
class ExchangeCard extends StatefulWidget {
|
class ExchangeCard extends StatefulWidget {
|
||||||
ExchangeCard(
|
ExchangeCard({
|
||||||
{Key? key,
|
Key? key,
|
||||||
required this.initialCurrency,
|
required this.initialCurrency,
|
||||||
required this.initialAddress,
|
required this.initialAddress,
|
||||||
required this.initialWalletName,
|
required this.initialWalletName,
|
||||||
required this.initialIsAmountEditable,
|
required this.initialIsAmountEditable,
|
||||||
required this.isAmountEstimated,
|
required this.isAmountEstimated,
|
||||||
required this.currencies,
|
required this.currencies,
|
||||||
required this.onCurrencySelected,
|
required this.onCurrencySelected,
|
||||||
this.imageArrow,
|
this.imageArrow,
|
||||||
this.currencyValueValidator,
|
this.currencyValueValidator,
|
||||||
this.addressTextFieldValidator,
|
this.addressTextFieldValidator,
|
||||||
this.title = '',
|
this.title = '',
|
||||||
this.initialIsAddressEditable = true,
|
this.initialIsAddressEditable = true,
|
||||||
this.hasRefundAddress = false,
|
this.hasRefundAddress = false,
|
||||||
this.isMoneroWallet = false,
|
this.isMoneroWallet = false,
|
||||||
this.currencyButtonColor = Colors.transparent,
|
this.currencyButtonColor = Colors.transparent,
|
||||||
this.addressButtonsColor = Colors.transparent,
|
this.addressButtonsColor = Colors.transparent,
|
||||||
this.borderColor = Colors.transparent,
|
this.borderColor = Colors.transparent,
|
||||||
this.hasAllAmount = false,
|
this.hasAllAmount = false,
|
||||||
this.isAllAmountEnabled = false,
|
this.isAllAmountEnabled = false,
|
||||||
this.amountFocusNode,
|
this.amountFocusNode,
|
||||||
this.addressFocusNode,
|
this.addressFocusNode,
|
||||||
this.allAmount,
|
this.allAmount,
|
||||||
this.onPushPasteButton,
|
this.onPushPasteButton,
|
||||||
this.onPushAddressBookButton,
|
this.onPushAddressBookButton,
|
||||||
this.onDispose})
|
this.onDispose,
|
||||||
: super(key: key);
|
required this.cardInstanceName,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
final List<CryptoCurrency> currencies;
|
final List<CryptoCurrency> currencies;
|
||||||
final Function(CryptoCurrency) onCurrencySelected;
|
final Function(CryptoCurrency) onCurrencySelected;
|
||||||
|
@ -74,6 +75,7 @@ class ExchangeCard extends StatefulWidget {
|
||||||
final void Function(BuildContext context)? onPushPasteButton;
|
final void Function(BuildContext context)? onPushPasteButton;
|
||||||
final void Function(BuildContext context)? onPushAddressBookButton;
|
final void Function(BuildContext context)? onPushAddressBookButton;
|
||||||
final Function()? onDispose;
|
final Function()? onDispose;
|
||||||
|
final String cardInstanceName;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ExchangeCardState createState() => ExchangeCardState();
|
ExchangeCardState createState() => ExchangeCardState();
|
||||||
|
@ -89,11 +91,13 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||||
_walletName = '',
|
_walletName = '',
|
||||||
_selectedCurrency = CryptoCurrency.btc,
|
_selectedCurrency = CryptoCurrency.btc,
|
||||||
_isAmountEstimated = false,
|
_isAmountEstimated = false,
|
||||||
_isMoneroWallet = false;
|
_isMoneroWallet = false,
|
||||||
|
_cardInstanceName = '';
|
||||||
|
|
||||||
final addressController = TextEditingController();
|
final addressController = TextEditingController();
|
||||||
final amountController = TextEditingController();
|
final amountController = TextEditingController();
|
||||||
|
|
||||||
|
String _cardInstanceName;
|
||||||
String _title;
|
String _title;
|
||||||
String? _min;
|
String? _min;
|
||||||
String? _max;
|
String? _max;
|
||||||
|
@ -106,6 +110,7 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
_cardInstanceName = widget.cardInstanceName;
|
||||||
_title = widget.title;
|
_title = widget.title;
|
||||||
_isAmountEditable = widget.initialIsAmountEditable;
|
_isAmountEditable = widget.initialIsAmountEditable;
|
||||||
_isAddressEditable = widget.initialIsAddressEditable;
|
_isAddressEditable = widget.initialIsAddressEditable;
|
||||||
|
@ -184,6 +189,7 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
|
key: ValueKey('${_cardInstanceName}_title_key'),
|
||||||
_title,
|
_title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
|
@ -193,17 +199,26 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
CurrencyAmountTextField(
|
CurrencyAmountTextField(
|
||||||
imageArrow: widget.imageArrow,
|
currencyPickerButtonKey: ValueKey('${_cardInstanceName}_currency_picker_button_key'),
|
||||||
selectedCurrency: _selectedCurrency.toString(),
|
selectedCurrencyTextKey: ValueKey('${_cardInstanceName}_selected_currency_text_key'),
|
||||||
amountFocusNode: widget.amountFocusNode,
|
selectedCurrencyTagTextKey:
|
||||||
amountController: amountController,
|
ValueKey('${_cardInstanceName}_selected_currency_tag_text_key'),
|
||||||
onTapPicker: () => _presentPicker(context),
|
amountTextfieldKey: ValueKey('${_cardInstanceName}_amount_textfield_key'),
|
||||||
isAmountEditable: _isAmountEditable,
|
sendAllButtonKey: ValueKey('${_cardInstanceName}_send_all_button_key'),
|
||||||
isPickerEnable: true,
|
currencyAmountTextFieldWidgetKey:
|
||||||
allAmountButton: widget.hasAllAmount,
|
ValueKey('${_cardInstanceName}_currency_amount_textfield_widget_key'),
|
||||||
currencyValueValidator: widget.currencyValueValidator,
|
imageArrow: widget.imageArrow,
|
||||||
tag: _selectedCurrency.tag,
|
selectedCurrency: _selectedCurrency.toString(),
|
||||||
allAmountCallback: widget.allAmount),
|
amountFocusNode: widget.amountFocusNode,
|
||||||
|
amountController: amountController,
|
||||||
|
onTapPicker: () => _presentPicker(context),
|
||||||
|
isAmountEditable: _isAmountEditable,
|
||||||
|
isPickerEnable: true,
|
||||||
|
allAmountButton: widget.hasAllAmount,
|
||||||
|
currencyValueValidator: widget.currencyValueValidator,
|
||||||
|
tag: _selectedCurrency.tag,
|
||||||
|
allAmountCallback: widget.allAmount,
|
||||||
|
),
|
||||||
Divider(height: 1, color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
|
Divider(height: 1, color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 5),
|
padding: EdgeInsets.only(top: 5),
|
||||||
|
@ -212,6 +227,7 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||||
child: Row(mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[
|
child: Row(mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[
|
||||||
_min != null
|
_min != null
|
||||||
? Text(
|
? Text(
|
||||||
|
key: ValueKey('${_cardInstanceName}_min_limit_text_key'),
|
||||||
S.of(context).min_value(_min ?? '', _selectedCurrency.toString()),
|
S.of(context).min_value(_min ?? '', _selectedCurrency.toString()),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
|
@ -221,11 +237,15 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||||
: Offstage(),
|
: Offstage(),
|
||||||
_min != null ? SizedBox(width: 10) : Offstage(),
|
_min != null ? SizedBox(width: 10) : Offstage(),
|
||||||
_max != null
|
_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(
|
style: TextStyle(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
height: 1.2,
|
height: 1.2,
|
||||||
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor))
|
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor,
|
||||||
|
),
|
||||||
|
)
|
||||||
: Offstage(),
|
: Offstage(),
|
||||||
])),
|
])),
|
||||||
),
|
),
|
||||||
|
@ -246,6 +266,7 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(top: 20),
|
padding: EdgeInsets.only(top: 20),
|
||||||
child: AddressTextField(
|
child: AddressTextField(
|
||||||
|
addressKey: ValueKey('${_cardInstanceName}_editable_address_textfield_key'),
|
||||||
focusNode: widget.addressFocusNode,
|
focusNode: widget.addressFocusNode,
|
||||||
controller: addressController,
|
controller: addressController,
|
||||||
onURIScanned: (uri) {
|
onURIScanned: (uri) {
|
||||||
|
@ -286,6 +307,8 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||||
FocusTraversalOrder(
|
FocusTraversalOrder(
|
||||||
order: NumericFocusOrder(3),
|
order: NumericFocusOrder(3),
|
||||||
child: BaseTextFormField(
|
child: BaseTextFormField(
|
||||||
|
key: ValueKey(
|
||||||
|
'${_cardInstanceName}_non_editable_address_textfield_key'),
|
||||||
controller: addressController,
|
controller: addressController,
|
||||||
borderColor: Colors.transparent,
|
borderColor: Colors.transparent,
|
||||||
suffixIcon: SizedBox(width: _isMoneroWallet ? 80 : 36),
|
suffixIcon: SizedBox(width: _isMoneroWallet ? 80 : 36),
|
||||||
|
@ -309,6 +332,8 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||||
child: Semantics(
|
child: Semantics(
|
||||||
label: S.of(context).address_book,
|
label: S.of(context).address_book,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
|
key: ValueKey(
|
||||||
|
'${_cardInstanceName}_address_book_button_key'),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final contact =
|
final contact =
|
||||||
await Navigator.of(context).pushNamed(
|
await Navigator.of(context).pushNamed(
|
||||||
|
@ -346,6 +371,8 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||||
child: Semantics(
|
child: Semantics(
|
||||||
label: S.of(context).copy_address,
|
label: S.of(context).copy_address,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
|
key: ValueKey(
|
||||||
|
'${_cardInstanceName}_copy_refund_address_button_key'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Clipboard.setData(
|
Clipboard.setData(
|
||||||
ClipboardData(text: addressController.text));
|
ClipboardData(text: addressController.text));
|
||||||
|
@ -369,6 +396,7 @@ class ExchangeCardState extends State<ExchangeCard> {
|
||||||
showPopUp<void>(
|
showPopUp<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => CurrencyPicker(
|
builder: (_) => CurrencyPicker(
|
||||||
|
key: ValueKey('${_cardInstanceName}_currency_picker_dialog_button_key'),
|
||||||
selectedAtIndex: widget.currencies.indexOf(_selectedCurrency),
|
selectedAtIndex: widget.currencies.indexOf(_selectedCurrency),
|
||||||
items: widget.currencies,
|
items: widget.currencies,
|
||||||
hintText: S.of(context).search_currency,
|
hintText: S.of(context).search_currency,
|
||||||
|
|
|
@ -83,6 +83,7 @@ class ExchangeConfirmPage extends BasePage {
|
||||||
padding: EdgeInsets.fromLTRB(10, 0, 10, 10),
|
padding: EdgeInsets.fromLTRB(10, 0, 10, 10),
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) => PrimaryButton(
|
builder: (context) => PrimaryButton(
|
||||||
|
key: ValueKey('exchange_confirm_page_copy_to_clipboard_button_key'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Clipboard.setData(ClipboardData(text: trade.id));
|
Clipboard.setData(ClipboardData(text: trade.id));
|
||||||
showBar<void>(
|
showBar<void>(
|
||||||
|
@ -117,6 +118,7 @@ class ExchangeConfirmPage extends BasePage {
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
|
key: ValueKey('exchange_confirm_page_saved_id_button_key'),
|
||||||
onPressed: () => Navigator.of(context)
|
onPressed: () => Navigator.of(context)
|
||||||
.pushReplacementNamed(Routes.exchangeTrade),
|
.pushReplacementNamed(Routes.exchangeTrade),
|
||||||
text: S.of(context).saved_the_trade_id,
|
text: S.of(context).saved_the_trade_id,
|
||||||
|
|
|
@ -39,7 +39,9 @@ void showInformation(
|
||||||
|
|
||||||
showPopUp<void>(
|
showPopUp<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => InformationPage(information: information));
|
builder: (_) => InformationPage(
|
||||||
|
key: ValueKey('information_page_dialog_key'),
|
||||||
|
information: information));
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExchangeTradePage extends BasePage {
|
class ExchangeTradePage extends BasePage {
|
||||||
|
@ -215,6 +217,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||||
return widget.exchangeTradeViewModel.isSendable &&
|
return widget.exchangeTradeViewModel.isSendable &&
|
||||||
!(sendingState is TransactionCommitted)
|
!(sendingState is TransactionCommitted)
|
||||||
? LoadingPrimaryButton(
|
? LoadingPrimaryButton(
|
||||||
|
key: ValueKey('exchange_trade_page_confirm_sending_button_key'),
|
||||||
isDisabled: trade.inputAddress == null ||
|
isDisabled: trade.inputAddress == null ||
|
||||||
trade.inputAddress!.isEmpty,
|
trade.inputAddress!.isEmpty,
|
||||||
isLoading: sendingState is IsExecutingState,
|
isLoading: sendingState is IsExecutingState,
|
||||||
|
@ -241,6 +244,8 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext popupContext) {
|
builder: (BuildContext popupContext) {
|
||||||
return AlertWithOneAction(
|
return AlertWithOneAction(
|
||||||
|
key: ValueKey('exchange_trade_page_send_failure_dialog_key'),
|
||||||
|
buttonKey: ValueKey('exchange_trade_page_send_failure_dialog_button_key'),
|
||||||
alertTitle: S.of(popupContext).error,
|
alertTitle: S.of(popupContext).error,
|
||||||
alertContent: state.error,
|
alertContent: state.error,
|
||||||
buttonText: S.of(popupContext).ok,
|
buttonText: S.of(popupContext).ok,
|
||||||
|
@ -255,6 +260,10 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext popupContext) {
|
builder: (BuildContext popupContext) {
|
||||||
return ConfirmSendingAlert(
|
return ConfirmSendingAlert(
|
||||||
|
key: ValueKey('exchange_trade_page_confirm_sending_dialog_key'),
|
||||||
|
alertLeftActionButtonKey: ValueKey('exchange_trade_page_confirm_sending_dialog_cancel_button_key'),
|
||||||
|
alertRightActionButtonKey:
|
||||||
|
ValueKey('exchange_trade_page_confirm_sending_dialog_send_button_key'),
|
||||||
alertTitle: S.of(popupContext).confirm_sending,
|
alertTitle: S.of(popupContext).confirm_sending,
|
||||||
amount: S.of(popupContext).send_amount,
|
amount: S.of(popupContext).send_amount,
|
||||||
amountValue: widget.exchangeTradeViewModel.sendViewModel
|
amountValue: widget.exchangeTradeViewModel.sendViewModel
|
||||||
|
|
|
@ -10,7 +10,7 @@ import 'package:cake_wallet/src/widgets/alert_background.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
||||||
|
|
||||||
class InformationPage extends StatelessWidget {
|
class InformationPage extends StatelessWidget {
|
||||||
InformationPage({required this.information});
|
InformationPage({required this.information, super.key});
|
||||||
|
|
||||||
final String information;
|
final String information;
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ class InformationPage extends StatelessWidget {
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(10, 0, 10, 10),
|
padding: EdgeInsets.fromLTRB(10, 0, 10, 10),
|
||||||
child: PrimaryButton(
|
child: PrimaryButton(
|
||||||
|
key: ValueKey('information_page_got_it_button_key'),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
text: S.of(context).got_it,
|
text: S.of(context).got_it,
|
||||||
color: Theme.of(context).extension<ExchangePageTheme>()!.buttonBackgroundColor,
|
color: Theme.of(context).extension<ExchangePageTheme>()!.buttonBackgroundColor,
|
||||||
|
|
|
@ -131,6 +131,7 @@ class WalletTypeFormState extends State<WalletTypeForm> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ScrollableWithBottomSection(
|
child: ScrollableWithBottomSection(
|
||||||
contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||||
|
scrollableKey: ValueKey('new_wallet_type_scrollable_key'),
|
||||||
content: Column(
|
content: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
@ -138,6 +139,7 @@ class WalletTypeFormState extends State<WalletTypeForm> {
|
||||||
(type) => Padding(
|
(type) => Padding(
|
||||||
padding: EdgeInsets.only(top: 12),
|
padding: EdgeInsets.only(top: 12),
|
||||||
child: SelectButton(
|
child: SelectButton(
|
||||||
|
key: ValueKey('new_wallet_type_${type.name}_button_key'),
|
||||||
image: Image.asset(
|
image: Image.asset(
|
||||||
walletTypeToCryptoCurrency(type).iconPath ?? '',
|
walletTypeToCryptoCurrency(type).iconPath ?? '',
|
||||||
height: 24,
|
height: 24,
|
||||||
|
@ -158,6 +160,7 @@ class WalletTypeFormState extends State<WalletTypeForm> {
|
||||||
),
|
),
|
||||||
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||||
bottomSection: PrimaryButton(
|
bottomSection: PrimaryButton(
|
||||||
|
key: ValueKey('new_wallet_type_next_button_key'),
|
||||||
onPressed: () => onTypeSelected(),
|
onPressed: () => onTypeSelected(),
|
||||||
text: S.of(context).seed_language_next,
|
text: S.of(context).seed_language_next,
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
|
|
|
@ -20,6 +20,7 @@ class SelectButton extends StatelessWidget {
|
||||||
this.deviceConnectionTypes,
|
this.deviceConnectionTypes,
|
||||||
this.borderRadius,
|
this.borderRadius,
|
||||||
this.padding,
|
this.padding,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Widget? image;
|
final Widget? image;
|
||||||
|
|
|
@ -240,6 +240,7 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.only(left: marginLeft, right: marginRight),
|
margin: EdgeInsets.only(left: marginLeft, right: marginRight),
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
|
key: ValueKey('pin_code_button_${index}_key'),
|
||||||
onPressed: () => _push(index),
|
onPressed: () => _push(index),
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
|
|
|
@ -24,8 +24,20 @@ class CurrencyAmountTextField extends StatelessWidget {
|
||||||
this.tagBackgroundColor,
|
this.tagBackgroundColor,
|
||||||
this.currencyValueValidator,
|
this.currencyValueValidator,
|
||||||
this.allAmountCallback,
|
this.allAmountCallback,
|
||||||
});
|
this.sendAllButtonKey,
|
||||||
|
this.amountTextfieldKey,
|
||||||
|
this.currencyPickerButtonKey,
|
||||||
|
this.selectedCurrencyTextKey,
|
||||||
|
this.selectedCurrencyTagTextKey,
|
||||||
|
this.currencyAmountTextFieldWidgetKey,
|
||||||
|
}) : super(key: currencyAmountTextFieldWidgetKey);
|
||||||
|
|
||||||
|
final Key? sendAllButtonKey;
|
||||||
|
final Key? amountTextfieldKey;
|
||||||
|
final Key? currencyPickerButtonKey;
|
||||||
|
final Key? selectedCurrencyTextKey;
|
||||||
|
final Key? selectedCurrencyTagTextKey;
|
||||||
|
final Key? currencyAmountTextFieldWidgetKey;
|
||||||
final Widget? imageArrow;
|
final Widget? imageArrow;
|
||||||
final String selectedCurrency;
|
final String selectedCurrency;
|
||||||
final String? tag;
|
final String? tag;
|
||||||
|
@ -54,6 +66,7 @@ class CurrencyAmountTextField extends StatelessWidget {
|
||||||
? Container(
|
? Container(
|
||||||
height: 32,
|
height: 32,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
|
key: currencyPickerButtonKey,
|
||||||
onTap: onTapPicker,
|
onTap: onTapPicker,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
@ -65,6 +78,7 @@ class CurrencyAmountTextField extends StatelessWidget {
|
||||||
Image.asset('assets/images/arrow_bottom_purple_icon.png',
|
Image.asset('assets/images/arrow_bottom_purple_icon.png',
|
||||||
color: textColor, height: 8)),
|
color: textColor, height: 8)),
|
||||||
Text(
|
Text(
|
||||||
|
key: selectedCurrencyTextKey,
|
||||||
selectedCurrency,
|
selectedCurrency,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
@ -77,6 +91,7 @@ class CurrencyAmountTextField extends StatelessWidget {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Text(
|
: Text(
|
||||||
|
key: selectedCurrencyTextKey,
|
||||||
selectedCurrency,
|
selectedCurrency,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
@ -98,6 +113,7 @@ class CurrencyAmountTextField extends StatelessWidget {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(6.0),
|
padding: const EdgeInsets.all(6.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
key: selectedCurrencyTagTextKey,
|
||||||
tag!,
|
tag!,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
@ -132,9 +148,9 @@ class CurrencyAmountTextField extends StatelessWidget {
|
||||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||||
margin: const EdgeInsets.only(right: 3),
|
margin: const EdgeInsets.only(right: 3),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: textColor,
|
color: textColor,
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(26),
|
borderRadius: BorderRadius.circular(26),
|
||||||
color: Theme.of(context).primaryColor))
|
color: Theme.of(context).primaryColor))
|
||||||
: _prefixContent,
|
: _prefixContent,
|
||||||
|
@ -146,6 +162,7 @@ class CurrencyAmountTextField extends StatelessWidget {
|
||||||
child: FocusTraversalOrder(
|
child: FocusTraversalOrder(
|
||||||
order: NumericFocusOrder(1),
|
order: NumericFocusOrder(1),
|
||||||
child: BaseTextFormField(
|
child: BaseTextFormField(
|
||||||
|
key: amountTextfieldKey,
|
||||||
focusNode: amountFocusNode,
|
focusNode: amountFocusNode,
|
||||||
controller: amountController,
|
controller: amountController,
|
||||||
enabled: isAmountEditable,
|
enabled: isAmountEditable,
|
||||||
|
@ -184,6 +201,7 @@ class CurrencyAmountTextField extends StatelessWidget {
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||||
),
|
),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
|
key: sendAllButtonKey,
|
||||||
onTap: allAmountCallback,
|
onTap: allAmountCallback,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
|
@ -59,8 +59,12 @@ class RestoreOptionsPage extends BasePage {
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
OptionTile(
|
OptionTile(
|
||||||
onPressed: () => Navigator.pushNamed(context, Routes.restoreWalletFromSeedKeys,
|
key: ValueKey('restore_options_from_seeds_button_key'),
|
||||||
arguments: isNewInstall),
|
onPressed: () => Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
Routes.restoreWalletFromSeedKeys,
|
||||||
|
arguments: isNewInstall,
|
||||||
|
),
|
||||||
image: imageSeedKeys,
|
image: imageSeedKeys,
|
||||||
title: S.of(context).restore_title_from_seed_keys,
|
title: S.of(context).restore_title_from_seed_keys,
|
||||||
description: S.of(context).restore_description_from_seed_keys,
|
description: S.of(context).restore_description_from_seed_keys,
|
||||||
|
@ -69,6 +73,7 @@ class RestoreOptionsPage extends BasePage {
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 24),
|
padding: EdgeInsets.only(top: 24),
|
||||||
child: OptionTile(
|
child: OptionTile(
|
||||||
|
key: ValueKey('restore_options_from_backup_button_key'),
|
||||||
onPressed: () => Navigator.pushNamed(context, Routes.restoreFromBackup),
|
onPressed: () => Navigator.pushNamed(context, Routes.restoreFromBackup),
|
||||||
image: imageBackup,
|
image: imageBackup,
|
||||||
title: S.of(context).restore_title_from_backup,
|
title: S.of(context).restore_title_from_backup,
|
||||||
|
@ -79,6 +84,7 @@ class RestoreOptionsPage extends BasePage {
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 24),
|
padding: EdgeInsets.only(top: 24),
|
||||||
child: OptionTile(
|
child: OptionTile(
|
||||||
|
key: ValueKey('restore_options_from_hardware_wallet_button_key'),
|
||||||
onPressed: () => Navigator.pushNamed(
|
onPressed: () => Navigator.pushNamed(
|
||||||
context, Routes.restoreWalletFromHardwareWallet,
|
context, Routes.restoreWalletFromHardwareWallet,
|
||||||
arguments: isNewInstall),
|
arguments: isNewInstall),
|
||||||
|
@ -90,10 +96,12 @@ class RestoreOptionsPage extends BasePage {
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 24),
|
padding: EdgeInsets.only(top: 24),
|
||||||
child: OptionTile(
|
child: OptionTile(
|
||||||
onPressed: () => _onScanQRCode(context),
|
key: ValueKey('restore_options_from_qr_button_key'),
|
||||||
image: qrCode,
|
onPressed: () => _onScanQRCode(context),
|
||||||
title: S.of(context).scan_qr_code,
|
image: qrCode,
|
||||||
description: S.of(context).cold_or_recover_wallet),
|
title: S.of(context).scan_qr_code,
|
||||||
|
description: S.of(context).cold_or_recover_wallet,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -112,10 +112,12 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
children: [
|
children: [
|
||||||
BaseTextFormField(
|
BaseTextFormField(
|
||||||
|
key: ValueKey('wallet_restore_from_keys_wallet_name_textfield_key'),
|
||||||
controller: nameTextEditingController,
|
controller: nameTextEditingController,
|
||||||
hintText: S.of(context).wallet_name,
|
hintText: S.of(context).wallet_name,
|
||||||
validator: WalletNameValidator(),
|
validator: WalletNameValidator(),
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
|
key: ValueKey('wallet_restore_from_keys_wallet_name_refresh_button_key'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final rName = await generateName();
|
final rName = await generateName();
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
@ -175,6 +177,7 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
|
||||||
bool nanoBased = widget.walletRestoreViewModel.type == WalletType.nano ||
|
bool nanoBased = widget.walletRestoreViewModel.type == WalletType.nano ||
|
||||||
widget.walletRestoreViewModel.type == WalletType.banano;
|
widget.walletRestoreViewModel.type == WalletType.banano;
|
||||||
return AddressTextField(
|
return AddressTextField(
|
||||||
|
addressKey: ValueKey('wallet_restore_from_key_private_key_textfield_key'),
|
||||||
controller: privateKeyController,
|
controller: privateKeyController,
|
||||||
placeholder: nanoBased ? S.of(context).seed_hex_form : S.of(context).private_key,
|
placeholder: nanoBased ? S.of(context).seed_hex_form : S.of(context).private_key,
|
||||||
options: [AddressTextFieldOption.paste],
|
options: [AddressTextFieldOption.paste],
|
||||||
|
|
|
@ -151,11 +151,13 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
children: [
|
children: [
|
||||||
BaseTextFormField(
|
BaseTextFormField(
|
||||||
|
key: ValueKey('wallet_restore_from_seed_wallet_name_textfield_key'),
|
||||||
controller: nameTextEditingController,
|
controller: nameTextEditingController,
|
||||||
hintText: S
|
hintText: S
|
||||||
.of(context)
|
.of(context)
|
||||||
.wallet_name,
|
.wallet_name,
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
|
key: ValueKey('wallet_restore_from_seed_wallet_name_refresh_button_key'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final rName = await generateName();
|
final rName = await generateName();
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
@ -190,10 +192,13 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
|
||||||
)),
|
)),
|
||||||
Container(height: 20),
|
Container(height: 20),
|
||||||
SeedWidget(
|
SeedWidget(
|
||||||
key: seedWidgetStateKey,
|
key: seedWidgetStateKey,
|
||||||
language: language,
|
language: language,
|
||||||
type: widget.type,
|
type: widget.type,
|
||||||
onSeedChange: onSeedChange),
|
onSeedChange: onSeedChange,
|
||||||
|
seedTextFieldKey: ValueKey('wallet_restore_from_seed_wallet_seeds_textfield_key'),
|
||||||
|
pasteButtonKey: ValueKey('wallet_restore_from_seed_wallet_seeds_paste_button_key'),
|
||||||
|
),
|
||||||
if (widget.type == WalletType.monero || widget.type == WalletType.wownero)
|
if (widget.type == WalletType.monero || widget.type == WalletType.wownero)
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
|
|
@ -213,6 +213,7 @@ class WalletRestorePage extends BasePage {
|
||||||
Observer(
|
Observer(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return LoadingPrimaryButton(
|
return LoadingPrimaryButton(
|
||||||
|
key: ValueKey('wallet_restore_seed_or_key_restore_button_key'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await _confirmForm(context);
|
await _confirmForm(context);
|
||||||
},
|
},
|
||||||
|
@ -230,6 +231,7 @@ class WalletRestorePage extends BasePage {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 25),
|
const SizedBox(height: 25),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
|
key: ValueKey('wallet_restore_advanced_settings_button_key'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
.pushNamed(Routes.advancedPrivacySettings, arguments: {
|
.pushNamed(Routes.advancedPrivacySettings, arguments: {
|
||||||
|
|
|
@ -250,6 +250,7 @@ class SendPage extends BasePage {
|
||||||
return Row(
|
return Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
AddTemplateButton(
|
AddTemplateButton(
|
||||||
|
key: ValueKey('send_page_add_template_button_key'),
|
||||||
onTap: () => Navigator.of(context).pushNamed(Routes.sendTemplate),
|
onTap: () => Navigator.of(context).pushNamed(Routes.sendTemplate),
|
||||||
currentTemplatesLength: templates.length,
|
currentTemplatesLength: templates.length,
|
||||||
),
|
),
|
||||||
|
@ -339,19 +340,22 @@ class SendPage extends BasePage {
|
||||||
children: [
|
children: [
|
||||||
if (sendViewModel.hasCurrecyChanger)
|
if (sendViewModel.hasCurrecyChanger)
|
||||||
Observer(
|
Observer(
|
||||||
builder: (_) => Padding(
|
builder: (_) => Padding(
|
||||||
padding: EdgeInsets.only(bottom: 12),
|
padding: EdgeInsets.only(bottom: 12),
|
||||||
child: PrimaryButton(
|
child: PrimaryButton(
|
||||||
onPressed: () => presentCurrencyPicker(context),
|
key: ValueKey('send_page_change_asset_button_key'),
|
||||||
text: 'Change your asset (${sendViewModel.selectedCryptoCurrency})',
|
onPressed: () => presentCurrencyPicker(context),
|
||||||
color: Colors.transparent,
|
text: 'Change your asset (${sendViewModel.selectedCryptoCurrency})',
|
||||||
textColor:
|
color: Colors.transparent,
|
||||||
Theme.of(context).extension<SeedWidgetTheme>()!.hintTextColor,
|
textColor: Theme.of(context).extension<SeedWidgetTheme>()!.hintTextColor,
|
||||||
))),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
if (sendViewModel.sendTemplateViewModel.hasMultiRecipient)
|
if (sendViewModel.sendTemplateViewModel.hasMultiRecipient)
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(bottom: 12),
|
padding: EdgeInsets.only(bottom: 12),
|
||||||
child: PrimaryButton(
|
child: PrimaryButton(
|
||||||
|
key: ValueKey('send_page_add_receiver_button_key'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
sendViewModel.addOutput();
|
sendViewModel.addOutput();
|
||||||
Future.delayed(const Duration(milliseconds: 250), () {
|
Future.delayed(const Duration(milliseconds: 250), () {
|
||||||
|
@ -368,6 +372,7 @@ class SendPage extends BasePage {
|
||||||
Observer(
|
Observer(
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
return LoadingPrimaryButton(
|
return LoadingPrimaryButton(
|
||||||
|
key: ValueKey('send_page_send_button_key'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (sendViewModel.state is IsExecutingState) return;
|
if (sendViewModel.state is IsExecutingState) return;
|
||||||
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
|
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
|
||||||
|
@ -451,6 +456,8 @@ class SendPage extends BasePage {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertWithOneAction(
|
return AlertWithOneAction(
|
||||||
|
key: ValueKey('send_page_send_failure_dialog_key'),
|
||||||
|
buttonKey: ValueKey('send_page_send_failure_dialog_button_key'),
|
||||||
alertTitle: S.of(context).error,
|
alertTitle: S.of(context).error,
|
||||||
alertContent: state.error,
|
alertContent: state.error,
|
||||||
buttonText: S.of(context).ok,
|
buttonText: S.of(context).ok,
|
||||||
|
@ -466,6 +473,7 @@ class SendPage extends BasePage {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext _dialogContext) {
|
builder: (BuildContext _dialogContext) {
|
||||||
return ConfirmSendingAlert(
|
return ConfirmSendingAlert(
|
||||||
|
key: ValueKey('send_page_confirm_sending_dialog_key'),
|
||||||
alertTitle: S.of(_dialogContext).confirm_sending,
|
alertTitle: S.of(_dialogContext).confirm_sending,
|
||||||
amount: S.of(_dialogContext).send_amount,
|
amount: S.of(_dialogContext).send_amount,
|
||||||
amountValue: sendViewModel.pendingTransaction!.amountFormatted,
|
amountValue: sendViewModel.pendingTransaction!.amountFormatted,
|
||||||
|
@ -480,6 +488,10 @@ class SendPage extends BasePage {
|
||||||
change: sendViewModel.pendingTransaction!.change,
|
change: sendViewModel.pendingTransaction!.change,
|
||||||
rightButtonText: S.of(_dialogContext).send,
|
rightButtonText: S.of(_dialogContext).send,
|
||||||
leftButtonText: S.of(_dialogContext).cancel,
|
leftButtonText: S.of(_dialogContext).cancel,
|
||||||
|
alertRightActionButtonKey:
|
||||||
|
ValueKey('send_page_confirm_sending_dialog_send_button_key'),
|
||||||
|
alertLeftActionButtonKey:
|
||||||
|
ValueKey('send_page_confirm_sending_dialog_cancel_button_key'),
|
||||||
actionRightButton: () async {
|
actionRightButton: () async {
|
||||||
Navigator.of(_dialogContext).pop();
|
Navigator.of(_dialogContext).pop();
|
||||||
sendViewModel.commitTransaction();
|
sendViewModel.commitTransaction();
|
||||||
|
@ -513,10 +525,15 @@ class SendPage extends BasePage {
|
||||||
|
|
||||||
if (newContactAddress != null) {
|
if (newContactAddress != null) {
|
||||||
return AlertWithTwoActions(
|
return AlertWithTwoActions(
|
||||||
|
alertDialogKey: ValueKey('send_page_sent_dialog_key'),
|
||||||
alertTitle: '',
|
alertTitle: '',
|
||||||
alertContent: alertContent,
|
alertContent: alertContent,
|
||||||
rightButtonText: S.of(_dialogContext).add_contact,
|
rightButtonText: S.of(_dialogContext).add_contact,
|
||||||
leftButtonText: S.of(_dialogContext).ignor,
|
leftButtonText: S.of(_dialogContext).ignor,
|
||||||
|
alertLeftActionButtonKey:
|
||||||
|
ValueKey('send_page_sent_dialog_ignore_button_key'),
|
||||||
|
alertRightActionButtonKey: ValueKey(
|
||||||
|
'send_page_sent_dialog_add_contact_button_key'),
|
||||||
actionRightButton: () {
|
actionRightButton: () {
|
||||||
Navigator.of(_dialogContext).pop();
|
Navigator.of(_dialogContext).pop();
|
||||||
RequestReviewHandler.requestReview();
|
RequestReviewHandler.requestReview();
|
||||||
|
|
|
@ -9,30 +9,34 @@ import 'package:cake_wallet/src/widgets/cake_scrollbar.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
|
||||||
class ConfirmSendingAlert extends BaseAlertDialog {
|
class ConfirmSendingAlert extends BaseAlertDialog {
|
||||||
ConfirmSendingAlert(
|
ConfirmSendingAlert({
|
||||||
{required this.alertTitle,
|
required this.alertTitle,
|
||||||
this.paymentId,
|
this.paymentId,
|
||||||
this.paymentIdValue,
|
this.paymentIdValue,
|
||||||
this.expirationTime,
|
this.expirationTime,
|
||||||
required this.amount,
|
required this.amount,
|
||||||
required this.amountValue,
|
required this.amountValue,
|
||||||
required this.fiatAmountValue,
|
required this.fiatAmountValue,
|
||||||
required this.fee,
|
required this.fee,
|
||||||
this.feeRate,
|
this.feeRate,
|
||||||
required this.feeValue,
|
required this.feeValue,
|
||||||
required this.feeFiatAmount,
|
required this.feeFiatAmount,
|
||||||
required this.outputs,
|
required this.outputs,
|
||||||
this.change,
|
this.change,
|
||||||
required this.leftButtonText,
|
required this.leftButtonText,
|
||||||
required this.rightButtonText,
|
required this.rightButtonText,
|
||||||
required this.actionLeftButton,
|
required this.actionLeftButton,
|
||||||
required this.actionRightButton,
|
required this.actionRightButton,
|
||||||
this.alertBarrierDismissible = true,
|
this.alertBarrierDismissible = true,
|
||||||
this.alertLeftActionButtonTextColor,
|
this.alertLeftActionButtonTextColor,
|
||||||
this.alertRightActionButtonTextColor,
|
this.alertRightActionButtonTextColor,
|
||||||
this.alertLeftActionButtonColor,
|
this.alertLeftActionButtonColor,
|
||||||
this.alertRightActionButtonColor,
|
this.alertRightActionButtonColor,
|
||||||
this.onDispose});
|
this.onDispose,
|
||||||
|
this.alertLeftActionButtonKey,
|
||||||
|
this.alertRightActionButtonKey,
|
||||||
|
Key? key,
|
||||||
|
});
|
||||||
|
|
||||||
final String alertTitle;
|
final String alertTitle;
|
||||||
final String? paymentId;
|
final String? paymentId;
|
||||||
|
@ -57,6 +61,8 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||||
final Color? alertLeftActionButtonColor;
|
final Color? alertLeftActionButtonColor;
|
||||||
final Color? alertRightActionButtonColor;
|
final Color? alertRightActionButtonColor;
|
||||||
final Function? onDispose;
|
final Function? onDispose;
|
||||||
|
final Key? alertRightActionButtonKey;
|
||||||
|
final Key? alertLeftActionButtonKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get titleText => alertTitle;
|
String get titleText => alertTitle;
|
||||||
|
@ -91,6 +97,12 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||||
@override
|
@override
|
||||||
Color? get rightActionButtonColor => alertRightActionButtonColor;
|
Color? get rightActionButtonColor => alertRightActionButtonColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Key? get leftActionButtonKey => alertLeftActionButtonKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Key? get rightActionButtonKey => alertLeftActionButtonKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget content(BuildContext context) => ConfirmSendingAlertContent(
|
Widget content(BuildContext context) => ConfirmSendingAlertContent(
|
||||||
paymentId: paymentId,
|
paymentId: paymentId,
|
||||||
|
@ -288,6 +300,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
|
key: ValueKey('confirm_sending_dialog_amount_text_value_key'),
|
||||||
amountValue,
|
amountValue,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
|
|
|
@ -158,6 +158,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||||
: sendViewModel.addressValidator;
|
: sendViewModel.addressValidator;
|
||||||
|
|
||||||
return AddressTextField(
|
return AddressTextField(
|
||||||
|
addressKey: ValueKey('send_page_address_textfield_key'),
|
||||||
focusNode: addressFocusNode,
|
focusNode: addressFocusNode,
|
||||||
controller: addressController,
|
controller: addressController,
|
||||||
onURIScanned: (uri) {
|
onURIScanned: (uri) {
|
||||||
|
@ -209,6 +210,11 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||||
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
validator: sendViewModel.addressValidator)),
|
validator: sendViewModel.addressValidator)),
|
||||||
CurrencyAmountTextField(
|
CurrencyAmountTextField(
|
||||||
|
currencyPickerButtonKey: ValueKey('send_page_currency_picker_button_key'),
|
||||||
|
amountTextfieldKey: ValueKey('send_page_amount_textfield_key'),
|
||||||
|
sendAllButtonKey: ValueKey('send_page_send_all_button_key'),
|
||||||
|
currencyAmountTextFieldWidgetKey:
|
||||||
|
ValueKey('send_page_crypto_currency_amount_textfield_widget_key'),
|
||||||
selectedCurrency: sendViewModel.selectedCryptoCurrency.title,
|
selectedCurrency: sendViewModel.selectedCryptoCurrency.title,
|
||||||
amountFocusNode: cryptoAmountFocus,
|
amountFocusNode: cryptoAmountFocus,
|
||||||
amountController: cryptoAmountController,
|
amountController: cryptoAmountController,
|
||||||
|
@ -216,7 +222,8 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||||
onTapPicker: () => _presentPicker(context),
|
onTapPicker: () => _presentPicker(context),
|
||||||
isPickerEnable: sendViewModel.hasMultipleTokens,
|
isPickerEnable: sendViewModel.hasMultipleTokens,
|
||||||
tag: sendViewModel.selectedCryptoCurrency.tag,
|
tag: sendViewModel.selectedCryptoCurrency.tag,
|
||||||
allAmountButton: !sendViewModel.isBatchSending && sendViewModel.shouldDisplaySendALL,
|
allAmountButton:
|
||||||
|
!sendViewModel.isBatchSending && sendViewModel.shouldDisplaySendALL,
|
||||||
currencyValueValidator: output.sendAll
|
currencyValueValidator: output.sendAll
|
||||||
? sendViewModel.allAmountValidator
|
? sendViewModel.allAmountValidator
|
||||||
: sendViewModel.amountValidator,
|
: sendViewModel.amountValidator,
|
||||||
|
@ -257,6 +264,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||||
),
|
),
|
||||||
if (!sendViewModel.isFiatDisabled)
|
if (!sendViewModel.isFiatDisabled)
|
||||||
CurrencyAmountTextField(
|
CurrencyAmountTextField(
|
||||||
|
amountTextfieldKey: ValueKey('send_page_fiat_amount_textfield_key'),
|
||||||
|
currencyAmountTextFieldWidgetKey:
|
||||||
|
ValueKey('send_page_fiat_currency_amount_textfield_widget_key'),
|
||||||
selectedCurrency: sendViewModel.fiat.title,
|
selectedCurrency: sendViewModel.fiat.title,
|
||||||
amountFocusNode: fiatAmountFocus,
|
amountFocusNode: fiatAmountFocus,
|
||||||
amountController: fiatAmountController,
|
amountController: fiatAmountController,
|
||||||
|
@ -269,6 +279,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 20),
|
padding: EdgeInsets.only(top: 20),
|
||||||
child: BaseTextFormField(
|
child: BaseTextFormField(
|
||||||
|
key: ValueKey('send_page_note_textfield_key'),
|
||||||
controller: noteController,
|
controller: noteController,
|
||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
|
@ -287,6 +298,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||||
if (sendViewModel.hasFees)
|
if (sendViewModel.hasFees)
|
||||||
Observer(
|
Observer(
|
||||||
builder: (_) => GestureDetector(
|
builder: (_) => GestureDetector(
|
||||||
|
key: ValueKey('send_page_select_fee_priority_button_key'),
|
||||||
onTap: sendViewModel.hasFeesPriority
|
onTap: sendViewModel.hasFeesPriority
|
||||||
? () => pickTransactionPriority(context)
|
? () => pickTransactionPriority(context)
|
||||||
: () {},
|
: () {},
|
||||||
|
@ -360,6 +372,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 6),
|
padding: EdgeInsets.only(top: 6),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
|
key: ValueKey('send_page_unspent_coin_button_key'),
|
||||||
onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsList),
|
onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsList),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
|
@ -544,11 +557,13 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||||
showPopUp<void>(
|
showPopUp<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => CurrencyPicker(
|
builder: (_) => CurrencyPicker(
|
||||||
selectedAtIndex: sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency),
|
key: ValueKey('send_page_currency_picker_dialog_button_key'),
|
||||||
items: sendViewModel.currencies,
|
selectedAtIndex: sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency),
|
||||||
hintText: S.of(context).search_currency,
|
items: sendViewModel.currencies,
|
||||||
onItemSelected: (Currency cur) =>
|
hintText: S.of(context).search_currency,
|
||||||
sendViewModel.selectedCryptoCurrency = (cur as CryptoCurrency)),
|
onItemSelected: (Currency cur) =>
|
||||||
|
sendViewModel.selectedCryptoCurrency = (cur as CryptoCurrency),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ class SetupPinCodePage extends BasePage {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertWithOneAction(
|
return AlertWithOneAction(
|
||||||
|
buttonKey: ValueKey('setup_pin_code_success_button_key'),
|
||||||
alertTitle: S.current.setup_pin,
|
alertTitle: S.current.setup_pin,
|
||||||
alertContent: S.of(context).setup_successful,
|
alertContent: S.of(context).setup_successful,
|
||||||
buttonText: S.of(context).ok,
|
buttonText: S.of(context).ok,
|
||||||
|
|
|
@ -133,6 +133,7 @@ class WelcomePage extends BasePage {
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 24),
|
padding: EdgeInsets.only(top: 24),
|
||||||
child: PrimaryImageButton(
|
child: PrimaryImageButton(
|
||||||
|
key: ValueKey('welcome_page_create_new_wallet_button_key'),
|
||||||
onPressed: () => Navigator.pushNamed(context, Routes.newWalletFromWelcome),
|
onPressed: () => Navigator.pushNamed(context, Routes.newWalletFromWelcome),
|
||||||
image: newWalletImage,
|
image: newWalletImage,
|
||||||
text: S.of(context).create_new,
|
text: S.of(context).create_new,
|
||||||
|
@ -146,6 +147,7 @@ class WelcomePage extends BasePage {
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 10),
|
padding: EdgeInsets.only(top: 10),
|
||||||
child: PrimaryImageButton(
|
child: PrimaryImageButton(
|
||||||
|
key: ValueKey('welcome_page_restore_wallet_button_key'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pushNamed(context, Routes.restoreOptions, arguments: true);
|
Navigator.pushNamed(context, Routes.restoreOptions, arguments: true);
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,28 +15,27 @@ import 'package:permission_handler/permission_handler.dart';
|
||||||
enum AddressTextFieldOption { paste, qrCode, addressBook, walletAddresses }
|
enum AddressTextFieldOption { paste, qrCode, addressBook, walletAddresses }
|
||||||
|
|
||||||
class AddressTextField extends StatelessWidget {
|
class AddressTextField extends StatelessWidget {
|
||||||
AddressTextField(
|
AddressTextField({
|
||||||
{required this.controller,
|
required this.controller,
|
||||||
this.isActive = true,
|
this.isActive = true,
|
||||||
this.placeholder,
|
this.placeholder,
|
||||||
this.options = const [
|
this.options = const [AddressTextFieldOption.qrCode, AddressTextFieldOption.addressBook],
|
||||||
AddressTextFieldOption.qrCode,
|
this.onURIScanned,
|
||||||
AddressTextFieldOption.addressBook
|
this.focusNode,
|
||||||
],
|
this.isBorderExist = true,
|
||||||
this.onURIScanned,
|
this.buttonColor,
|
||||||
this.focusNode,
|
this.borderColor,
|
||||||
this.isBorderExist = true,
|
this.iconColor,
|
||||||
this.buttonColor,
|
this.textStyle,
|
||||||
this.borderColor,
|
this.hintStyle,
|
||||||
this.iconColor,
|
this.validator,
|
||||||
this.textStyle,
|
this.onPushPasteButton,
|
||||||
this.hintStyle,
|
this.onPushAddressBookButton,
|
||||||
this.validator,
|
this.onPushAddressPickerButton,
|
||||||
this.onPushPasteButton,
|
this.onSelectedContact,
|
||||||
this.onPushAddressBookButton,
|
this.selectedCurrency,
|
||||||
this.onPushAddressPickerButton,
|
this.addressKey,
|
||||||
this.onSelectedContact,
|
});
|
||||||
this.selectedCurrency});
|
|
||||||
|
|
||||||
static const prefixIconWidth = 34.0;
|
static const prefixIconWidth = 34.0;
|
||||||
static const prefixIconHeight = 34.0;
|
static const prefixIconHeight = 34.0;
|
||||||
|
@ -60,12 +59,14 @@ class AddressTextField extends StatelessWidget {
|
||||||
final Function(BuildContext context)? onPushAddressPickerButton;
|
final Function(BuildContext context)? onPushAddressPickerButton;
|
||||||
final Function(ContactBase contact)? onSelectedContact;
|
final Function(ContactBase contact)? onSelectedContact;
|
||||||
final CryptoCurrency? selectedCurrency;
|
final CryptoCurrency? selectedCurrency;
|
||||||
|
final Key? addressKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Stack(
|
return Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
TextFormField(
|
TextFormField(
|
||||||
|
key: addressKey,
|
||||||
enableIMEPersonalizedLearning: false,
|
enableIMEPersonalizedLearning: false,
|
||||||
keyboardType: TextInputType.visiblePassword,
|
keyboardType: TextInputType.visiblePassword,
|
||||||
onFieldSubmitted: (_) => FocusScope.of(context).unfocus(),
|
onFieldSubmitted: (_) => FocusScope.of(context).unfocus(),
|
||||||
|
|
|
@ -3,7 +3,12 @@ import 'package:cake_wallet/palette.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class AlertCloseButton extends StatelessWidget {
|
class AlertCloseButton extends StatelessWidget {
|
||||||
AlertCloseButton({this.image, this.bottom, this.onTap});
|
AlertCloseButton({
|
||||||
|
this.image,
|
||||||
|
this.bottom,
|
||||||
|
this.onTap,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,9 @@ class AlertWithOneAction extends BaseAlertDialog {
|
||||||
required this.buttonAction,
|
required this.buttonAction,
|
||||||
this.alertBarrierDismissible = true,
|
this.alertBarrierDismissible = true,
|
||||||
this.headerTitleText,
|
this.headerTitleText,
|
||||||
this.headerImageProfileUrl
|
this.headerImageProfileUrl,
|
||||||
|
this.buttonKey,
|
||||||
|
Key? key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String alertTitle;
|
final String alertTitle;
|
||||||
|
@ -19,6 +21,7 @@ class AlertWithOneAction extends BaseAlertDialog {
|
||||||
final bool alertBarrierDismissible;
|
final bool alertBarrierDismissible;
|
||||||
final String? headerTitleText;
|
final String? headerTitleText;
|
||||||
final String? headerImageProfileUrl;
|
final String? headerImageProfileUrl;
|
||||||
|
final Key? buttonKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get titleText => alertTitle;
|
String get titleText => alertTitle;
|
||||||
|
@ -45,6 +48,7 @@ class AlertWithOneAction extends BaseAlertDialog {
|
||||||
child: ButtonTheme(
|
child: ButtonTheme(
|
||||||
minWidth: double.infinity,
|
minWidth: double.infinity,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
|
key: buttonKey,
|
||||||
onPressed: buttonAction,
|
onPressed: buttonAction,
|
||||||
// FIX-ME: Style
|
// FIX-ME: Style
|
||||||
//highlightColor: Colors.transparent,
|
//highlightColor: Colors.transparent,
|
||||||
|
|
|
@ -14,6 +14,9 @@ class AlertWithTwoActions extends BaseAlertDialog {
|
||||||
this.isDividerExist = false,
|
this.isDividerExist = false,
|
||||||
// this.leftActionColor,
|
// this.leftActionColor,
|
||||||
// this.rightActionColor,
|
// this.rightActionColor,
|
||||||
|
this.alertRightActionButtonKey,
|
||||||
|
this.alertLeftActionButtonKey,
|
||||||
|
this.alertDialogKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String alertTitle;
|
final String alertTitle;
|
||||||
|
@ -26,6 +29,9 @@ class AlertWithTwoActions extends BaseAlertDialog {
|
||||||
// final Color leftActionColor;
|
// final Color leftActionColor;
|
||||||
// final Color rightActionColor;
|
// final Color rightActionColor;
|
||||||
final bool isDividerExist;
|
final bool isDividerExist;
|
||||||
|
final Key? alertRightActionButtonKey;
|
||||||
|
final Key? alertLeftActionButtonKey;
|
||||||
|
final Key? alertDialogKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get titleText => alertTitle;
|
String get titleText => alertTitle;
|
||||||
|
@ -47,4 +53,13 @@ class AlertWithTwoActions extends BaseAlertDialog {
|
||||||
// Color get rightButtonColor => rightActionColor;
|
// Color get rightButtonColor => rightActionColor;
|
||||||
@override
|
@override
|
||||||
bool get isDividerExists => isDividerExist;
|
bool get isDividerExists => isDividerExist;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Key? get dialogKey => alertDialogKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Key? get leftActionButtonKey => alertLeftActionButtonKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Key? get rightActionButtonKey => alertRightActionButtonKey;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,12 @@ class BaseAlertDialog extends StatelessWidget {
|
||||||
|
|
||||||
String? get headerImageUrl => null;
|
String? get headerImageUrl => null;
|
||||||
|
|
||||||
|
Key? leftActionButtonKey;
|
||||||
|
|
||||||
|
Key? rightActionButtonKey;
|
||||||
|
|
||||||
|
Key? dialogKey;
|
||||||
|
|
||||||
Widget title(BuildContext context) {
|
Widget title(BuildContext context) {
|
||||||
return Text(
|
return Text(
|
||||||
titleText,
|
titleText,
|
||||||
|
@ -87,6 +93,7 @@ class BaseAlertDialog extends StatelessWidget {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
|
key: leftActionButtonKey,
|
||||||
onPressed: actionLeft,
|
onPressed: actionLeft,
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
|
@ -109,6 +116,7 @@ class BaseAlertDialog extends StatelessWidget {
|
||||||
const VerticalSectionDivider(),
|
const VerticalSectionDivider(),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
|
key: rightActionButtonKey,
|
||||||
onPressed: actionRight,
|
onPressed: actionRight,
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
|
@ -152,6 +160,7 @@ class BaseAlertDialog extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
|
key: key,
|
||||||
onTap: () => barrierDismissible ? Navigator.of(context).pop() : null,
|
onTap: () => barrierDismissible ? Navigator.of(context).pop() : null,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
|
|
|
@ -30,7 +30,8 @@ class BaseTextFormField extends StatelessWidget {
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
this.initialValue,
|
this.initialValue,
|
||||||
this.onSubmit,
|
this.onSubmit,
|
||||||
this.borderWidth = 1.0});
|
this.borderWidth = 1.0,
|
||||||
|
super.key});
|
||||||
|
|
||||||
final TextEditingController? controller;
|
final TextEditingController? controller;
|
||||||
final TextInputType? keyboardType;
|
final TextInputType? keyboardType;
|
||||||
|
|
|
@ -6,7 +6,8 @@ class OptionTile extends StatelessWidget {
|
||||||
{required this.onPressed,
|
{required this.onPressed,
|
||||||
required this.image,
|
required this.image,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.description});
|
required this.description,
|
||||||
|
super.key});
|
||||||
|
|
||||||
final VoidCallback onPressed;
|
final VoidCallback onPressed;
|
||||||
final Image image;
|
final Image image;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'dart:math';
|
||||||
|
|
||||||
import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
|
import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
|
||||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||||
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cw_core/currency.dart';
|
import 'package:cw_core/currency.dart';
|
||||||
import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
|
import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
|
||||||
|
@ -11,6 +12,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/cake_scrollbar_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/picker_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 {
|
class Picker<Item> extends StatefulWidget {
|
||||||
Picker({
|
Picker({
|
||||||
required this.selectedAtIndex,
|
required this.selectedAtIndex,
|
||||||
|
@ -153,6 +155,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
key: ValueKey('picker_title_text_key'),
|
||||||
widget.title!,
|
widget.title!,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
@ -189,7 +192,10 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: SearchBarWidget(
|
child: SearchBarWidget(
|
||||||
searchController: searchController, hintText: widget.hintText),
|
key: ValueKey('picker_search_bar_key'),
|
||||||
|
searchController: searchController,
|
||||||
|
hintText: widget.hintText,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Divider(
|
Divider(
|
||||||
color: Theme.of(context).extension<PickerTheme>()!.dividerColor,
|
color: Theme.of(context).extension<PickerTheme>()!.dividerColor,
|
||||||
|
@ -203,6 +209,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
filteredItems.length > 3
|
filteredItems.length > 3
|
||||||
? Scrollbar(
|
? Scrollbar(
|
||||||
|
key: ValueKey('picker_scrollbar_key'),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
child: itemsList(),
|
child: itemsList(),
|
||||||
)
|
)
|
||||||
|
@ -213,6 +220,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
left: padding,
|
left: padding,
|
||||||
right: padding,
|
right: padding,
|
||||||
child: Text(
|
child: Text(
|
||||||
|
key: ValueKey('picker_descriptinon_text_key'),
|
||||||
widget.description!,
|
widget.description!,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
@ -242,6 +250,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
|
|
||||||
if (widget.isWrapped) {
|
if (widget.isWrapped) {
|
||||||
return PickerWrapperWidget(
|
return PickerWrapperWidget(
|
||||||
|
key: ValueKey('picker_wrapper_widget_key'),
|
||||||
hasTitle: widget.title?.isNotEmpty ?? false,
|
hasTitle: widget.title?.isNotEmpty ?? false,
|
||||||
children: [content],
|
children: [content],
|
||||||
);
|
);
|
||||||
|
@ -260,6 +269,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
color: Theme.of(context).extension<PickerTheme>()!.dividerColor,
|
color: Theme.of(context).extension<PickerTheme>()!.dividerColor,
|
||||||
child: widget.isGridView
|
child: widget.isGridView
|
||||||
? GridView.builder(
|
? GridView.builder(
|
||||||
|
key: ValueKey('picker_items_grid_view_key'),
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
|
@ -275,6 +285,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
: buildItem(index),
|
: buildItem(index),
|
||||||
)
|
)
|
||||||
: ListView.separated(
|
: ListView.separated(
|
||||||
|
key: ValueKey('picker_items_list_view_key'),
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
|
@ -293,10 +304,25 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _getItemName(Item item) {
|
||||||
|
String itemName;
|
||||||
|
if (item is Currency) {
|
||||||
|
itemName = item.name;
|
||||||
|
} else if (item is TransactionPriority) {
|
||||||
|
itemName = item.title;
|
||||||
|
} else {
|
||||||
|
itemName = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemName;
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildItem(int index) {
|
Widget buildItem(int index) {
|
||||||
final item = widget.headerEnabled ? filteredItems[index] : items[index];
|
final item = widget.headerEnabled ? filteredItems[index] : items[index];
|
||||||
|
|
||||||
final tag = item is Currency ? item.tag : null;
|
final tag = item is Currency ? item.tag : null;
|
||||||
|
final itemName = _getItemName(item);
|
||||||
|
|
||||||
final icon = _getItemIcon(item);
|
final icon = _getItemIcon(item);
|
||||||
|
|
||||||
final image = images.isNotEmpty ? filteredImages[index] : icon;
|
final image = images.isNotEmpty ? filteredImages[index] : icon;
|
||||||
|
@ -316,6 +342,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
|
key: ValueKey('picker_items_index_${itemName}_text_key'),
|
||||||
widget.displayItem?.call(item) ?? item.toString(),
|
widget.displayItem?.call(item) ?? item.toString(),
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
@ -335,6 +362,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
height: 18.0,
|
height: 18.0,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
|
key: ValueKey('picker_items_index_${index}_tag_key'),
|
||||||
tag,
|
tag,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 7.0,
|
fontSize: 7.0,
|
||||||
|
@ -358,6 +386,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
);
|
);
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
|
key: ValueKey('picker_items_index_${itemName}_button_key'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (widget.closeOnItemSelected) Navigator.of(context).pop();
|
if (widget.closeOnItemSelected) Navigator.of(context).pop();
|
||||||
onItemSelected(item!);
|
onItemSelected(item!);
|
||||||
|
@ -383,6 +412,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
final item = items[index];
|
final item = items[index];
|
||||||
|
|
||||||
final tag = item is Currency ? item.tag : null;
|
final tag = item is Currency ? item.tag : null;
|
||||||
|
final itemName = _getItemName(item);
|
||||||
final icon = _getItemIcon(item);
|
final icon = _getItemIcon(item);
|
||||||
|
|
||||||
final image = images.isNotEmpty ? images[index] : icon;
|
final image = images.isNotEmpty ? images[index] : icon;
|
||||||
|
@ -390,6 +420,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
final isCustomItem = widget.customItemIndex != null && index == widget.customItemIndex;
|
final isCustomItem = widget.customItemIndex != null && index == widget.customItemIndex;
|
||||||
|
|
||||||
final itemContent = Row(
|
final itemContent = Row(
|
||||||
|
key: ValueKey('picker_selected_item_row_key'),
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: widget.mainAxisAlignment,
|
mainAxisAlignment: widget.mainAxisAlignment,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
@ -402,6 +433,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
|
key: ValueKey('picker_items_index_${itemName}_selected_item_text_key'),
|
||||||
widget.displayItem?.call(item) ?? item.toString(),
|
widget.displayItem?.call(item) ?? item.toString(),
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
@ -445,6 +477,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
||||||
);
|
);
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
|
key: ValueKey('picker_items_index_${itemName}_selected_item_button_key'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (widget.closeOnItemSelected) Navigator.of(context).pop();
|
if (widget.closeOnItemSelected) Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,12 @@ import 'package:cake_wallet/src/widgets/alert_background.dart';
|
||||||
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
|
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
|
||||||
|
|
||||||
class PickerWrapperWidget extends StatelessWidget {
|
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 List<Widget> children;
|
||||||
final bool hasTitle;
|
final bool hasTitle;
|
||||||
|
@ -29,8 +34,8 @@ class PickerWrapperWidget extends StatelessWidget {
|
||||||
final containerBottom = screenCenter - containerCenter;
|
final containerBottom = screenCenter - containerCenter;
|
||||||
|
|
||||||
// position the close button right below the search container
|
// position the close button right below the search container
|
||||||
closeButtonBottom = closeButtonBottom -
|
closeButtonBottom =
|
||||||
containerBottom + (!hasTitle ? padding : padding / 1.5);
|
closeButtonBottom - containerBottom + (!hasTitle ? padding : padding / 1.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AlertBackground(
|
return AlertBackground(
|
||||||
|
@ -46,7 +51,11 @@ class PickerWrapperWidget extends StatelessWidget {
|
||||||
children: children,
|
children: children,
|
||||||
),
|
),
|
||||||
SizedBox(height: ResponsiveLayoutUtilBase.kPopupSpaceHeight),
|
SizedBox(height: ResponsiveLayoutUtilBase.kPopupSpaceHeight),
|
||||||
AlertCloseButton(bottom: closeButtonBottom, onTap: onClose),
|
AlertCloseButton(
|
||||||
|
key: ValueKey('picker_wrapper_close_button_key'),
|
||||||
|
bottom: closeButtonBottom,
|
||||||
|
onTap: onClose,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -4,15 +4,17 @@ import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class PrimaryButton extends StatelessWidget {
|
class PrimaryButton extends StatelessWidget {
|
||||||
const PrimaryButton(
|
const PrimaryButton({
|
||||||
{required this.text,
|
required this.text,
|
||||||
required this.color,
|
required this.color,
|
||||||
required this.textColor,
|
required this.textColor,
|
||||||
this.onPressed,
|
this.onPressed,
|
||||||
this.isDisabled = false,
|
this.isDisabled = false,
|
||||||
this.isDottedBorder = false,
|
this.isDottedBorder = false,
|
||||||
this.borderColor = Colors.black,
|
this.borderColor = Colors.black,
|
||||||
this.onDisabledPressed});
|
this.onDisabledPressed,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
final VoidCallback? onPressed;
|
final VoidCallback? onPressed;
|
||||||
final VoidCallback? onDisabledPressed;
|
final VoidCallback? onDisabledPressed;
|
||||||
|
@ -31,23 +33,23 @@ class PrimaryButton extends StatelessWidget {
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 52.0,
|
height: 52.0,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: isDisabled
|
onPressed:
|
||||||
? (onDisabledPressed != null ? onDisabledPressed : null) : onPressed,
|
isDisabled ? (onDisabledPressed != null ? onDisabledPressed : null) : onPressed,
|
||||||
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color),
|
style: ButtonStyle(
|
||||||
|
backgroundColor:
|
||||||
|
MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color),
|
||||||
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
||||||
RoundedRectangleBorder(
|
RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(26.0),
|
borderRadius: BorderRadius.circular(26.0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
overlayColor: MaterialStateProperty.all(Colors.transparent)),
|
overlayColor: MaterialStateProperty.all(Colors.transparent)),
|
||||||
child: Text(text,
|
child: Text(text,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 15.0,
|
fontSize: 15.0,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: isDisabled
|
color: isDisabled ? textColor.withOpacity(0.5) : textColor)),
|
||||||
? textColor.withOpacity(0.5)
|
|
||||||
: textColor)),
|
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -64,13 +66,15 @@ class PrimaryButton extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoadingPrimaryButton extends StatelessWidget {
|
class LoadingPrimaryButton extends StatelessWidget {
|
||||||
const LoadingPrimaryButton(
|
const LoadingPrimaryButton({
|
||||||
{required this.onPressed,
|
required this.onPressed,
|
||||||
required this.text,
|
required this.text,
|
||||||
required this.color,
|
required this.color,
|
||||||
required this.textColor,
|
required this.textColor,
|
||||||
this.isDisabled = false,
|
this.isDisabled = false,
|
||||||
this.isLoading = false});
|
this.isLoading = false,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
final VoidCallback onPressed;
|
final VoidCallback onPressed;
|
||||||
final Color color;
|
final Color color;
|
||||||
|
@ -88,41 +92,38 @@ class LoadingPrimaryButton extends StatelessWidget {
|
||||||
height: 52.0,
|
height: 52.0,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: (isLoading || isDisabled) ? null : onPressed,
|
onPressed: (isLoading || isDisabled) ? null : onPressed,
|
||||||
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color),
|
style: ButtonStyle(
|
||||||
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
backgroundColor:
|
||||||
RoundedRectangleBorder(
|
MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color),
|
||||||
borderRadius: BorderRadius.circular(26.0),
|
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
||||||
),
|
RoundedRectangleBorder(
|
||||||
)),
|
borderRadius: BorderRadius.circular(26.0),
|
||||||
|
),
|
||||||
|
)),
|
||||||
child: isLoading
|
child: isLoading
|
||||||
? CupertinoActivityIndicator(animating: true)
|
? CupertinoActivityIndicator(animating: true)
|
||||||
: Text(text,
|
: Text(text,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 15.0,
|
fontSize: 15.0,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: isDisabled
|
color: isDisabled ? textColor.withOpacity(0.5) : textColor)),
|
||||||
? textColor.withOpacity(0.5)
|
|
||||||
: textColor
|
|
||||||
)),
|
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PrimaryIconButton extends StatelessWidget {
|
class PrimaryIconButton extends StatelessWidget {
|
||||||
const PrimaryIconButton({
|
const PrimaryIconButton(
|
||||||
required this.onPressed,
|
{required this.onPressed,
|
||||||
required this.iconData,
|
required this.iconData,
|
||||||
required this.text,
|
required this.text,
|
||||||
required this.color,
|
required this.color,
|
||||||
required this.borderColor,
|
required this.borderColor,
|
||||||
required this.iconColor,
|
required this.iconColor,
|
||||||
required this.iconBackgroundColor,
|
required this.iconBackgroundColor,
|
||||||
required this.textColor,
|
required this.textColor,
|
||||||
this.mainAxisAlignment = MainAxisAlignment.start,
|
this.mainAxisAlignment = MainAxisAlignment.start,
|
||||||
this.radius = 26
|
this.radius = 26, super.key});
|
||||||
});
|
|
||||||
|
|
||||||
final VoidCallback onPressed;
|
final VoidCallback onPressed;
|
||||||
final IconData iconData;
|
final IconData iconData;
|
||||||
|
@ -144,7 +145,8 @@ class PrimaryIconButton extends StatelessWidget {
|
||||||
height: 52.0,
|
height: 52.0,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(color),
|
style: ButtonStyle(
|
||||||
|
backgroundColor: MaterialStateProperty.all(color),
|
||||||
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
||||||
RoundedRectangleBorder(
|
RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(radius),
|
borderRadius: BorderRadius.circular(radius),
|
||||||
|
@ -158,21 +160,15 @@ class PrimaryIconButton extends StatelessWidget {
|
||||||
Container(
|
Container(
|
||||||
width: 26.0,
|
width: 26.0,
|
||||||
height: 52.0,
|
height: 52.0,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(shape: BoxShape.circle, color: iconBackgroundColor),
|
||||||
shape: BoxShape.circle, color: iconBackgroundColor),
|
child: Center(child: Icon(iconData, color: iconColor, size: 22.0)),
|
||||||
child: Center(
|
|
||||||
child: Icon(iconData, color: iconColor, size: 22.0)
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
height: 52.0,
|
height: 52.0,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(text,
|
child: Text(text, style: TextStyle(fontSize: 16.0, color: textColor)),
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16.0,
|
|
||||||
color: textColor)),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -189,7 +185,7 @@ class PrimaryImageButton extends StatelessWidget {
|
||||||
required this.text,
|
required this.text,
|
||||||
required this.color,
|
required this.color,
|
||||||
required this.textColor,
|
required this.textColor,
|
||||||
this.borderColor = Colors.transparent});
|
this.borderColor = Colors.transparent, super.key});
|
||||||
|
|
||||||
final VoidCallback onPressed;
|
final VoidCallback onPressed;
|
||||||
final Image image;
|
final Image image;
|
||||||
|
@ -206,31 +202,27 @@ class PrimaryImageButton extends StatelessWidget {
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 52.0,
|
height: 52.0,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(color),
|
style: ButtonStyle(
|
||||||
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
backgroundColor: MaterialStateProperty.all(color),
|
||||||
RoundedRectangleBorder(
|
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
||||||
borderRadius: BorderRadius.circular(26.0),
|
RoundedRectangleBorder(
|
||||||
),
|
borderRadius: BorderRadius.circular(26.0),
|
||||||
)),
|
|
||||||
child:Center(
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
image,
|
|
||||||
SizedBox(width: 15),
|
|
||||||
Text(
|
|
||||||
text,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 15,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: textColor
|
|
||||||
),
|
),
|
||||||
)
|
)),
|
||||||
],
|
child: Center(
|
||||||
),
|
child: Row(
|
||||||
)
|
mainAxisSize: MainAxisSize.min,
|
||||||
)),
|
children: <Widget>[
|
||||||
|
image,
|
||||||
|
SizedBox(width: 15),
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: textColor),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ class ScrollableWithBottomSection extends StatefulWidget {
|
||||||
this.contentPadding,
|
this.contentPadding,
|
||||||
this.bottomSectionPadding,
|
this.bottomSectionPadding,
|
||||||
this.topSectionPadding,
|
this.topSectionPadding,
|
||||||
|
this.scrollableKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Widget content;
|
final Widget content;
|
||||||
|
@ -17,6 +18,7 @@ class ScrollableWithBottomSection extends StatefulWidget {
|
||||||
final EdgeInsets? contentPadding;
|
final EdgeInsets? contentPadding;
|
||||||
final EdgeInsets? bottomSectionPadding;
|
final EdgeInsets? bottomSectionPadding;
|
||||||
final EdgeInsets? topSectionPadding;
|
final EdgeInsets? topSectionPadding;
|
||||||
|
final Key? scrollableKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ScrollableWithBottomSectionState createState() => ScrollableWithBottomSectionState();
|
ScrollableWithBottomSectionState createState() => ScrollableWithBottomSectionState();
|
||||||
|
@ -35,6 +37,7 @@ class ScrollableWithBottomSectionState extends State<ScrollableWithBottomSection
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
|
key: widget.scrollableKey,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: widget.contentPadding ?? EdgeInsets.only(left: 20, right: 20),
|
padding: widget.contentPadding ?? EdgeInsets.only(left: 20, right: 20),
|
||||||
child: widget.content,
|
child: widget.content,
|
||||||
|
|
|
@ -7,6 +7,7 @@ class SearchBarWidget extends StatelessWidget {
|
||||||
required this.searchController,
|
required this.searchController,
|
||||||
this.hintText,
|
this.hintText,
|
||||||
this.borderRadius = 14,
|
this.borderRadius = 14,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final TextEditingController searchController;
|
final TextEditingController searchController;
|
||||||
|
|
|
@ -9,11 +9,15 @@ import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class SeedWidget extends StatefulWidget {
|
class SeedWidget extends StatefulWidget {
|
||||||
SeedWidget({
|
SeedWidget({
|
||||||
Key? key,
|
|
||||||
required this.language,
|
required this.language,
|
||||||
required this.type,
|
required this.type,
|
||||||
this.onSeedChange}) : super(key: key);
|
this.onSeedChange,
|
||||||
|
this.pasteButtonKey,
|
||||||
|
this.seedTextFieldKey,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
final Key? seedTextFieldKey;
|
||||||
|
final Key? pasteButtonKey;
|
||||||
final String language;
|
final String language;
|
||||||
final WalletType type;
|
final WalletType type;
|
||||||
final void Function(String)? onSeedChange;
|
final void Function(String)? onSeedChange;
|
||||||
|
@ -78,11 +82,11 @@ class SeedWidgetState extends State<SeedWidget> {
|
||||||
top: 10,
|
top: 10,
|
||||||
left: 0,
|
left: 0,
|
||||||
child: Text(S.of(context).enter_seed_phrase,
|
child: Text(S.of(context).enter_seed_phrase,
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: 16.0, color: Theme.of(context).hintColor))),
|
||||||
fontSize: 16.0, color: Theme.of(context).hintColor))),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(right: 40, top: 10),
|
padding: EdgeInsets.only(right: 40, top: 10),
|
||||||
child: ValidatableAnnotatedEditableText(
|
child: ValidatableAnnotatedEditableText(
|
||||||
|
key: widget.seedTextFieldKey,
|
||||||
cursorColor: Colors.blue,
|
cursorColor: Colors.blue,
|
||||||
backgroundCursorColor: Colors.blue,
|
backgroundCursorColor: Colors.blue,
|
||||||
validStyle: TextStyle(
|
validStyle: TextStyle(
|
||||||
|
@ -112,15 +116,17 @@ class SeedWidgetState extends State<SeedWidget> {
|
||||||
width: 32,
|
width: 32,
|
||||||
height: 32,
|
height: 32,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
|
key: widget.pasteButtonKey,
|
||||||
onTap: () async => _pasteText(),
|
onTap: () async => _pasteText(),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.all(8),
|
padding: EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).hintColor,
|
color: Theme.of(context).hintColor,
|
||||||
borderRadius:
|
borderRadius: BorderRadius.all(Radius.circular(6))),
|
||||||
BorderRadius.all(Radius.circular(6))),
|
|
||||||
child: Image.asset('assets/images/paste_ios.png',
|
child: Image.asset('assets/images/paste_ios.png',
|
||||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor)),
|
color: Theme.of(context)
|
||||||
|
.extension<SendPageTheme>()!
|
||||||
|
.textFieldButtonIconColor)),
|
||||||
)))
|
)))
|
||||||
]),
|
]),
|
||||||
Container(
|
Container(
|
||||||
|
|
|
@ -107,11 +107,14 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
integration_test:
|
||||||
|
sdk: flutter
|
||||||
|
mocktail: ^1.0.4
|
||||||
build_runner: ^2.3.3
|
build_runner: ^2.3.3
|
||||||
logging: ^1.2.0
|
logging: ^1.2.0
|
||||||
mobx_codegen: ^2.1.1
|
mobx_codegen: ^2.1.1
|
||||||
build_resolvers: ^2.0.9
|
build_resolvers: ^2.0.9
|
||||||
hive_generator: ^1.1.3
|
hive_generator: ^2.0.1
|
||||||
# flutter_launcher_icons: ^0.11.0
|
# flutter_launcher_icons: ^0.11.0
|
||||||
# check flutter_launcher_icons for usage
|
# check flutter_launcher_icons for usage
|
||||||
pedantic: ^1.8.0
|
pedantic: ^1.8.0
|
||||||
|
|
33
test_driver/integration_test.dart
Normal file
33
test_driver/integration_test.dart
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:integration_test/integration_test_driver.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
|
import 'package:flutter_driver/flutter_driver.dart';
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
integrationDriver(
|
||||||
|
responseDataCallback: (Map<String, dynamic>? data) async {
|
||||||
|
await fs.directory(_destinationDirectory).create(recursive: true);
|
||||||
|
|
||||||
|
final file = fs.file(
|
||||||
|
path.join(
|
||||||
|
_destinationDirectory,
|
||||||
|
'$_testOutputFilename.json',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final resultString = _encodeJson(data);
|
||||||
|
await file.writeAsString(resultString);
|
||||||
|
},
|
||||||
|
writeResponseOnFailure: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _encodeJson(Map<String, dynamic>? jsonObject) {
|
||||||
|
return _prettyEncoder.convert(jsonObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _prettyEncoder = JsonEncoder.withIndent(' ');
|
||||||
|
const _testOutputFilename = 'integration_response_data';
|
||||||
|
const _destinationDirectory = 'integration_test';
|
|
@ -39,6 +39,7 @@ class SecretKey {
|
||||||
SecretKey('moralisApiKey', () => ''),
|
SecretKey('moralisApiKey', () => ''),
|
||||||
SecretKey('ankrApiKey', () => ''),
|
SecretKey('ankrApiKey', () => ''),
|
||||||
SecretKey('quantexExchangeMarkup', () => ''),
|
SecretKey('quantexExchangeMarkup', () => ''),
|
||||||
|
SecretKey('seeds', () => ''),
|
||||||
SecretKey('testCakePayApiKey', () => ''),
|
SecretKey('testCakePayApiKey', () => ''),
|
||||||
SecretKey('cakePayApiKey', () => ''),
|
SecretKey('cakePayApiKey', () => ''),
|
||||||
SecretKey('CSRFToken', () => ''),
|
SecretKey('CSRFToken', () => ''),
|
||||||
|
|
Loading…
Reference in a new issue