Automated Integration Tests Flows (#1686)

* feat: Integration tests setup and tests for Disclaimer, Welcome and Setup Pin Code pages * feat: Integration test flow from start to restoring a wallet successfully done * test: Dashboard view test and linking to flow * feat: Testing the Exchange flow section, selecting sending and receiving currencies * test: Successfully create an exchange section * feat: Implement flow up to sending section * test: Complete Exchange flow * fix dependency issue * test: Final cleanups * feat: Add CI to run automated integration tests withan android emulator * feat: Adjust Automated integration test CI to run on ubuntu 20.04-a * fix: Move integration test CI into PR test build CI * ci: Add automated test ci which is a streamlined replica of pr test build ci * ci: Re-add step to access branch name * ci: Add KVM * ci: Add filepath to trigger the test run from * ci: Add required key * ci: Add required key * ci: Add missing secret key * ci: Add missing secret key * ci: Add nano secrets to workflow * ci: Switch step to free space on runner * ci: Remove timeout from workflow * ci: Confirm impact that removing copy_monero_deps would have on entire workflow time * ci: Update CI and temporarily remove cache related to emulator * ci: Remove dynamic java version * ci: Temporarily switch CI * ci: Switch to 11.x jdk * ci: Temporarily switch CI * ci: Revert ubuntu version * ci: Add more api levels * ci: Add more target options * ci: Settled on stable emulator matrix options * ci: Add more target options * ci: Modify flow * ci: Streamline api levels to 28 and 29 * ci: One more trial * ci: Switch to flutter drive * ci: Reduce options * ci: Remove haven from test * ci: Check for solana in list * ci: Adjust amounts and currencies for exchange flow * ci: Set write response on failure to true * ci: Split ci to funds and non funds related tests * test: Test for Send flow scenario and minor restructuring for test folders and files * chore: cleanup * ci: Pause CI for now * ci: Pause CI for now * ci: Pause CI for now * test: Restore wallets integration automated tests * Fix: Add keys back to currency amount textfield widget * fix: Switch variable name * fix: remove automation for now * tests: Automated tests for Create wallets flow * tests: Further optimize common flows * tests: Add missing await for call * tests: Confirm Seeds Display Properly WIP * tests: Confirm Seeds Display Correctly Automated Tests * fix: Add missing pubspec params for bitcoin and bitcoin_cash * feat: Automated Tests for Transaction History Flow * fix: Add missing pubspec parameter * feat: Automated Integration Tests for Transaction History flow * test: Updating send page robot and also syncing branch with main * test: Modifying tests to flow with wallet grouping implementation * fix: Issue with transaction history test * fix: Modifications to the PR and add automated confirmation for checking that all wallet types are restored or created correctly * test: Attempting automation for testing * fix: Issue from merge conflicts * test: Remove automation of test in this PR --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>

David Adegoke committed Nov 7, 2024 at 15:46 UTC 0fcfd76afd75b91665e25804859497aa46a051aa
84 files changed +2729 -758
cw_bitcoin/pubspec.yaml
+1
@@ -73,6 +73,7 @@ dependency_overrides:
73
74 # The following section is specific to Flutter.
75 flutter:
76 + uses-material-design: true
77
78 # To add assets to your package, add an assets section, like this:
79 # assets:
cw_bitcoin_cash/pubspec.yaml
+1
@@ -49,6 +49,7 @@ dependency_overrides:
49
50 # The following section is specific to Flutter packages.
51 flutter:
52 + uses-material-design: true
53
54 # To add assets to your package, add an assets section, like this:
55 # assets:
cw_core/pubspec.yaml
+1
@@ -47,6 +47,7 @@ dependency_overrides:
47
48 # The following section is specific to Flutter.
49 flutter:
50 + uses-material-design: true
51
52 # To add assets to your package, add an assets section, like this:
53 # assets:
integration_test/components/common_test_cases.dart
+97 -24
@@ -10,10 +10,16 @@ class CommonTestCases {
10 hasType<T>();
11 }
12
13 - Future<void> tapItemByKey(String key, {bool shouldPumpAndSettle = true}) async {
13 + Future<void> tapItemByKey(
14 + String key, {
15 + bool shouldPumpAndSettle = true,
16 + int pumpDuration = 100,
17 + }) async {
18 final widget = find.byKey(ValueKey(key));
19 await tester.tap(widget);
16 - shouldPumpAndSettle ? await tester.pumpAndSettle() : await tester.pump();
20 + shouldPumpAndSettle
21 + ? await tester.pumpAndSettle(Duration(milliseconds: pumpDuration))
22 + : await tester.pump();
23 }
24
25 Future<void> tapItemByFinder(Finder finder, {bool shouldPumpAndSettle = true}) async {
@@ -31,6 +37,11 @@ class CommonTestCases {
37 expect(typeWidget, findsOneWidget);
38 }
39
40 + bool isKeyPresent(String key) {
41 + final typeWidget = find.byKey(ValueKey(key));
42 + return typeWidget.tryEvaluate();
43 + }
44 +
45 void hasValueKey(String key) {
46 final typeWidget = find.byKey(ValueKey(key));
47 expect(typeWidget, findsOneWidget);
@@ -53,33 +64,86 @@ class CommonTestCases {
64 await tester.pumpAndSettle();
65 }
66
56 - Future<void> scrollUntilVisible(String childKey, String parentScrollableKey,
57 - {double delta = 300}) async {
58 - final scrollableWidget = find.descendant(
59 - of: find.byKey(Key(parentScrollableKey)),
60 - matching: find.byType(Scrollable),
61 - );
67 + Future<void> dragUntilVisible(String childKey, String parentKey) async {
68 + await tester.pumpAndSettle();
69 +
70 + final itemFinder = find.byKey(ValueKey(childKey));
71 + final listFinder = find.byKey(ValueKey(parentKey));
72 +
73 + // Check if the widget is already in the widget tree
74 + if (tester.any(itemFinder)) {
75 + // Widget is already built and in the tree
76 + tester.printToConsole('Child is already present');
77 + return;
78 + }
79 +
80 + // We can adjust this as needed
81 + final maxScrolls = 200;
82
63 - final isAlreadyVisibile = isWidgetVisible(find.byKey(ValueKey(childKey)));
83 + int scrolls = 0;
84 + bool found = false;
85
65 - if (isAlreadyVisibile) return;
86 + // We start by scrolling down
87 + bool scrollDown = true;
88
67 - await tester.scrollUntilVisible(
68 - find.byKey(ValueKey(childKey)),
69 - delta,
70 - scrollable: scrollableWidget,
89 + // Flag to check if we've already reversed direction
90 + bool reversedDirection = false;
91 +
92 + // Find the Scrollable associated with the Parent Ad
93 + final scrollableFinder = find.descendant(
94 + of: listFinder,
95 + matching: find.byType(Scrollable),
96 + );
97 +
98 + // Ensure that the Scrollable is found
99 + expect(
100 + scrollableFinder,
101 + findsOneWidget,
102 + reason: 'Scrollable descendant of the Parent Widget not found.',
103 );
72 - }
104
74 - bool isWidgetVisible(Finder finder) {
75 - try {
76 - final Element element = finder.evaluate().single;
77 - final RenderBox renderBox = element.renderObject as RenderBox;
78 - return renderBox.paintBounds
79 - .shift(renderBox.localToGlobal(Offset.zero))
80 - .overlaps(tester.binding.renderViews.first.paintBounds);
81 - } catch (e) {
82 - return false;
105 + // Get the initial scroll position
106 + final scrollableState = tester.state<ScrollableState>(scrollableFinder);
107 + double previousScrollPosition = scrollableState.position.pixels;
108 +
109 + while (!found && scrolls < maxScrolls) {
110 + tester.printToConsole('Scrolling ${scrollDown ? 'down' : 'up'}, attempt $scrolls');
111 +
112 + // Perform the drag in the current direction
113 + await tester.drag(
114 + scrollableFinder,
115 + scrollDown ? const Offset(0, -100) : const Offset(0, 100),
116 + );
117 + await tester.pumpAndSettle();
118 + scrolls++;
119 +
120 + // Update the scroll position after the drag
121 + final currentScrollPosition = scrollableState.position.pixels;
122 +
123 + if (currentScrollPosition == previousScrollPosition) {
124 + // Cannot scroll further in this direction
125 + if (reversedDirection) {
126 + // We've already tried both directions
127 + tester.printToConsole('Cannot scroll further in both directions. Widget not found.');
128 + break;
129 + } else {
130 + // Reverse the scroll direction
131 + scrollDown = !scrollDown;
132 + reversedDirection = true;
133 + tester.printToConsole('Reached the end, reversing direction');
134 + }
135 + } else {
136 + // Continue scrolling in the current direction
137 + previousScrollPosition = currentScrollPosition;
138 + }
139 +
140 + // Check if the widget is now in the widget tree
141 + found = tester.any(itemFinder);
142 + }
143 +
144 + if (!found) {
145 + tester.printToConsole('Widget not found after scrolling in both directions.');
146 + return;
147 }
148 }
149
@@ -91,6 +155,15 @@ class CommonTestCases {
155 await tester.pumpAndSettle();
156 }
157
158 + void findWidgetViaDescendant({
159 + required FinderBase<Element> of,
160 + required FinderBase<Element> matching,
161 + }) {
162 + final textWidget = find.descendant(of: of, matching: matching);
163 +
164 + expect(textWidget, findsOneWidget);
165 + }
166 +
167 Future<void> defaultSleepTime({int seconds = 2}) async =>
168 await Future.delayed(Duration(seconds: seconds));
169 }
integration_test/components/common_test_constants.dart
+1 -1
@@ -9,5 +9,5 @@ class CommonTestConstants {
9 static final String testWalletName = 'Integrated Testing Wallet';
10 static final CryptoCurrency testReceiveCurrency = CryptoCurrency.sol;
11 static final CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol;
12 - static final String testWalletAddress = 'An2Y2fsUYKfYvN1zF89GAqR1e6GUMBg3qA83Y5ZWDf8L';
12 + static final String testWalletAddress = '5v9gTW1yWPffhnbNKuvtL2frevAf4HpBMw8oYnfqUjhm';
13 }
integration_test/components/common_test_flows.dart
+267 -24
@@ -1,41 +1,65 @@
1 -import 'package:flutter/foundation.dart';
1 +import 'package:cake_wallet/entities/seed_type.dart';
2 +import 'package:cake_wallet/reactions/bip39_wallet_utils.dart';
3 +import 'package:cake_wallet/wallet_types.g.dart';
4 +import 'package:cw_core/wallet_type.dart';
5 +import 'package:flutter/material.dart';
6 import 'package:flutter_test/flutter_test.dart';
7
4 -import 'package:cake_wallet/.secrets.g.dart' as secrets;
8 import 'package:cake_wallet/main.dart' as app;
9
10 +import '../robots/dashboard_page_robot.dart';
11 import '../robots/disclaimer_page_robot.dart';
12 +import '../robots/new_wallet_page_robot.dart';
13 import '../robots/new_wallet_type_page_robot.dart';
14 +import '../robots/pre_seed_page_robot.dart';
15 import '../robots/restore_from_seed_or_key_robot.dart';
16 import '../robots/restore_options_page_robot.dart';
17 import '../robots/setup_pin_code_robot.dart';
18 +import '../robots/wallet_group_description_page_robot.dart';
19 +import '../robots/wallet_list_page_robot.dart';
20 +import '../robots/wallet_seed_page_robot.dart';
21 import '../robots/welcome_page_robot.dart';
22 import 'common_test_cases.dart';
23 +import 'package:cake_wallet/.secrets.g.dart' as secrets;
24 +
25 import 'common_test_constants.dart';
26
27 class CommonTestFlows {
28 CommonTestFlows(this._tester)
29 : _commonTestCases = CommonTestCases(_tester),
30 _welcomePageRobot = WelcomePageRobot(_tester),
31 + _preSeedPageRobot = PreSeedPageRobot(_tester),
32 _setupPinCodeRobot = SetupPinCodeRobot(_tester),
33 + _dashboardPageRobot = DashboardPageRobot(_tester),
34 + _newWalletPageRobot = NewWalletPageRobot(_tester),
35 _disclaimerPageRobot = DisclaimerPageRobot(_tester),
36 + _walletSeedPageRobot = WalletSeedPageRobot(_tester),
37 + _walletListPageRobot = WalletListPageRobot(_tester),
38 _newWalletTypePageRobot = NewWalletTypePageRobot(_tester),
39 _restoreOptionsPageRobot = RestoreOptionsPageRobot(_tester),
24 - _restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester);
40 + _restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester),
41 + _walletGroupDescriptionPageRobot = WalletGroupDescriptionPageRobot(_tester);
42
43 final WidgetTester _tester;
44 final CommonTestCases _commonTestCases;
45
46 final WelcomePageRobot _welcomePageRobot;
47 + final PreSeedPageRobot _preSeedPageRobot;
48 final SetupPinCodeRobot _setupPinCodeRobot;
49 + final NewWalletPageRobot _newWalletPageRobot;
50 + final DashboardPageRobot _dashboardPageRobot;
51 final DisclaimerPageRobot _disclaimerPageRobot;
52 + final WalletSeedPageRobot _walletSeedPageRobot;
53 + final WalletListPageRobot _walletListPageRobot;
54 final NewWalletTypePageRobot _newWalletTypePageRobot;
55 final RestoreOptionsPageRobot _restoreOptionsPageRobot;
56 final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot;
57 + final WalletGroupDescriptionPageRobot _walletGroupDescriptionPageRobot;
58
59 + //* ========== Handles flow to start the app afresh and accept disclaimer =============
60 Future<void> startAppFlow(Key key) async {
61 await app.main(topLevelKey: ValueKey('send_flow_test_app_key'));
38 -
62 +
63 await _tester.pumpAndSettle();
64
65 // --------- Disclaimer Page ------------
@@ -46,56 +70,275 @@ class CommonTestFlows {
70 await _disclaimerPageRobot.tapAcceptButton();
71 }
72
49 - Future<void> restoreWalletThroughSeedsFlow() async {
50 - await _welcomeToRestoreFromSeedsPath();
51 - await _restoreFromSeeds();
73 + //* ========== Handles flow from welcome to creating a new wallet ===============
74 + Future<void> welcomePageToCreateNewWalletFlow(
75 + WalletType walletTypeToCreate,
76 + List<int> walletPin,
77 + ) async {
78 + await _welcomeToCreateWalletPath(walletTypeToCreate, walletPin);
79 +
80 + await _generateNewWalletDetails();
81 +
82 + await _confirmPreSeedInfo();
83 +
84 + await _confirmWalletDetails();
85 + }
86 +
87 + //* ========== Handles flow from welcome to restoring wallet from seeds ===============
88 + Future<void> welcomePageToRestoreWalletThroughSeedsFlow(
89 + WalletType walletTypeToRestore,
90 + String walletSeed,
91 + List<int> walletPin,
92 + ) async {
93 + await _welcomeToRestoreFromSeedsOrKeysPath(walletTypeToRestore, walletPin);
94 + await _restoreFromSeeds(walletTypeToRestore, walletSeed);
95 }
96
54 - Future<void> restoreWalletThroughKeysFlow() async {
55 - await _welcomeToRestoreFromSeedsPath();
97 + //* ========== Handles flow from welcome to restoring wallet from keys ===============
98 + Future<void> welcomePageToRestoreWalletThroughKeysFlow(
99 + WalletType walletTypeToRestore,
100 + List<int> walletPin,
101 + ) async {
102 + await _welcomeToRestoreFromSeedsOrKeysPath(walletTypeToRestore, walletPin);
103 await _restoreFromKeys();
104 }
105
59 - Future<void> _welcomeToRestoreFromSeedsPath() async {
60 - // --------- Welcome Page ---------------
61 - await _welcomePageRobot.navigateToRestoreWalletPage();
106 + //* ========== Handles switching to wallet list or menu from dashboard ===============
107 + Future<void> switchToWalletMenuFromDashboardPage() async {
108 + _tester.printToConsole('Switching to Wallet Menu');
109 + await _dashboardPageRobot.openDrawerMenu();
110 +
111 + await _dashboardPageRobot.dashboardMenuWidgetRobot.navigateToWalletMenu();
112 + }
113 +
114 + void confirmAllAvailableWalletTypeIconsDisplayCorrectly() {
115 + for (var walletType in availableWalletTypes) {
116 + final imageUrl = walletTypeToCryptoCurrency(walletType).iconPath;
117 +
118 + final walletIconFinder = find.image(
119 + Image.asset(
120 + imageUrl!,
121 + width: 32,
122 + height: 32,
123 + ).image,
124 + );
125 +
126 + expect(walletIconFinder, findsAny);
127 + }
128 + }
129 +
130 + //* ========== Handles creating new wallet flow from wallet list/menu ===============
131 + Future<void> createNewWalletFromWalletMenu(WalletType walletTypeToCreate) async {
132 + _tester.printToConsole('Creating ${walletTypeToCreate.name} Wallet');
133 + await _walletListPageRobot.navigateToCreateNewWalletPage();
134 + await _commonTestCases.defaultSleepTime();
135 +
136 + await _selectWalletTypeForWallet(walletTypeToCreate);
137 + await _commonTestCases.defaultSleepTime();
138 +
139 + // ---- Wallet Group/New Seed Implementation Comes here
140 + await _walletGroupDescriptionPageFlow(true, walletTypeToCreate);
141 +
142 + await _generateNewWalletDetails();
143 +
144 + await _confirmPreSeedInfo();
145 +
146 + await _confirmWalletDetails();
147 + await _commonTestCases.defaultSleepTime();
148 + }
149 +
150 + Future<void> _walletGroupDescriptionPageFlow(bool isNewSeed, WalletType walletType) async {
151 + if (!isBIP39Wallet(walletType)) return;
152 +
153 + await _walletGroupDescriptionPageRobot.isWalletGroupDescriptionPage();
154 +
155 + if (isNewSeed) {
156 + await _walletGroupDescriptionPageRobot.navigateToCreateNewSeedPage();
157 + } else {
158 + await _walletGroupDescriptionPageRobot.navigateToChooseWalletGroup();
159 + }
160 + }
161 +
162 + //* ========== Handles restore wallet flow from wallet list/menu ===============
163 + Future<void> restoreWalletFromWalletMenu(WalletType walletType, String walletSeed) async {
164 + _tester.printToConsole('Restoring ${walletType.name} Wallet');
165 + await _walletListPageRobot.navigateToRestoreWalletOptionsPage();
166 + await _commonTestCases.defaultSleepTime();
167 +
168 + await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage();
169 + await _commonTestCases.defaultSleepTime();
170 +
171 + await _selectWalletTypeForWallet(walletType);
172 + await _commonTestCases.defaultSleepTime();
173
63 - // ----------- Restore Options Page -----------
64 - // Route to restore from seeds page to continue flow
65 - await _restoreOptionsPageRobot.navigateToRestoreFromSeedsPage();
174 + await _restoreFromSeeds(walletType, walletSeed);
175 + await _commonTestCases.defaultSleepTime();
176 + }
177
178 + //* ========== Handles setting up pin code for wallet on first install ===============
179 + Future<void> setupPinCodeForWallet(List<int> pin) async {
180 // ----------- SetupPinCode Page -------------
181 // Confirm initial defaults - Widgets to be displayed etc
182 await _setupPinCodeRobot.isSetupPinCodePage();
183
71 - await _setupPinCodeRobot.enterPinCode(CommonTestConstants.pin, true);
72 - await _setupPinCodeRobot.enterPinCode(CommonTestConstants.pin, false);
184 + await _setupPinCodeRobot.enterPinCode(pin);
185 + await _setupPinCodeRobot.enterPinCode(pin);
186 await _setupPinCodeRobot.tapSuccessButton();
187 + }
188 +
189 + Future<void> _welcomeToCreateWalletPath(
190 + WalletType walletTypeToCreate,
191 + List<int> pin,
192 + ) async {
193 + await _welcomePageRobot.navigateToCreateNewWalletPage();
194 +
195 + await setupPinCodeForWallet(pin);
196 +
197 + await _selectWalletTypeForWallet(walletTypeToCreate);
198 + }
199 +
200 + Future<void> _welcomeToRestoreFromSeedsOrKeysPath(
201 + WalletType walletTypeToRestore,
202 + List<int> pin,
203 + ) async {
204 + await _welcomePageRobot.navigateToRestoreWalletPage();
205
206 + await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage();
207 +
208 + await setupPinCodeForWallet(pin);
209 +
210 + await _selectWalletTypeForWallet(walletTypeToRestore);
211 + }
212 +
213 + //* ============ Handles New Wallet Type Page ==================
214 + Future<void> _selectWalletTypeForWallet(WalletType type) async {
215 // ----------- NewWalletType Page -------------
216 // Confirm scroll behaviour works properly
77 - await _newWalletTypePageRobot
78 - .findParticularWalletTypeInScrollableList(CommonTestConstants.testWalletType);
217 + await _newWalletTypePageRobot.findParticularWalletTypeInScrollableList(type);
218
219 // Select a wallet and route to next page
81 - await _newWalletTypePageRobot.selectWalletType(CommonTestConstants.testWalletType);
220 + await _newWalletTypePageRobot.selectWalletType(type);
221 await _newWalletTypePageRobot.onNextButtonPressed();
222 }
223
85 - Future<void> _restoreFromSeeds() async {
224 + //* ============ Handles New Wallet Page ==================
225 + Future<void> _generateNewWalletDetails() async {
226 + await _newWalletPageRobot.isNewWalletPage();
227 +
228 + await _newWalletPageRobot.generateWalletName();
229 +
230 + await _newWalletPageRobot.onNextButtonPressed();
231 + }
232 +
233 + //* ============ Handles Pre Seed Page =====================
234 + Future<void> _confirmPreSeedInfo() async {
235 + await _preSeedPageRobot.isPreSeedPage();
236 +
237 + await _preSeedPageRobot.onConfirmButtonPressed();
238 + }
239 +
240 + //* ============ Handles Wallet Seed Page ==================
241 + Future<void> _confirmWalletDetails() async {
242 + await _walletSeedPageRobot.isWalletSeedPage();
243 +
244 + _walletSeedPageRobot.confirmWalletDetailsDisplayCorrectly();
245 +
246 + _walletSeedPageRobot.confirmWalletSeedReminderDisplays();
247 +
248 + await _walletSeedPageRobot.onCopySeedsButtonPressed();
249 +
250 + await _walletSeedPageRobot.onNextButtonPressed();
251 +
252 + await _walletSeedPageRobot.onConfirmButtonOnSeedAlertDialogPressed();
253 + }
254 +
255 + //* Main Restore Actions - On the RestoreFromSeed/Keys Page - Restore from Seeds Action
256 + Future<void> _restoreFromSeeds(WalletType type, String walletSeed) async {
257 // ----------- RestoreFromSeedOrKeys Page -------------
87 - await _restoreFromSeedOrKeysPageRobot.enterWalletNameText(CommonTestConstants.testWalletName);
88 - await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(secrets.solanaTestWalletSeeds);
258 +
259 + await _restoreFromSeedOrKeysPageRobot.selectWalletNameFromAvailableOptions();
260 + await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(walletSeed);
261 +
262 + final numberOfWords = walletSeed.split(' ').length;
263 +
264 + if (numberOfWords == 25 && (type == WalletType.monero)) {
265 + await _restoreFromSeedOrKeysPageRobot
266 + .chooseSeedTypeForMoneroOrWowneroWallets(MoneroSeedType.legacy);
267 +
268 + // Using a constant value of 2831400 for the blockheight as its the restore blockheight for our testing wallet
269 + await _restoreFromSeedOrKeysPageRobot
270 + .enterBlockHeightForWalletRestore(secrets.moneroTestWalletBlockHeight);
271 + }
272 +
273 await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed();
274 }
275
276 + //* Main Restore Actions - On the RestoreFromSeed/Keys Page - Restore from Keys Action
277 Future<void> _restoreFromKeys() async {
278 await _commonTestCases.swipePage();
279 await _commonTestCases.defaultSleepTime();
280
96 - await _restoreFromSeedOrKeysPageRobot.enterWalletNameText(CommonTestConstants.testWalletName);
281 + await _restoreFromSeedOrKeysPageRobot.selectWalletNameFromAvailableOptions(
282 + isSeedFormEntry: false,
283 + );
284
285 await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore('');
286 await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed();
287 }
288 +
289 + //* ====== Utility Function to get test wallet seeds for each wallet type ========
290 + String getWalletSeedsByWalletType(WalletType walletType) {
291 + switch (walletType) {
292 + case WalletType.monero:
293 + return secrets.moneroTestWalletSeeds;
294 + case WalletType.bitcoin:
295 + return secrets.bitcoinTestWalletSeeds;
296 + case WalletType.ethereum:
297 + return secrets.ethereumTestWalletSeeds;
298 + case WalletType.litecoin:
299 + return secrets.litecoinTestWalletSeeds;
300 + case WalletType.bitcoinCash:
301 + return secrets.bitcoinCashTestWalletSeeds;
302 + case WalletType.polygon:
303 + return secrets.polygonTestWalletSeeds;
304 + case WalletType.solana:
305 + return secrets.solanaTestWalletSeeds;
306 + case WalletType.tron:
307 + return secrets.tronTestWalletSeeds;
308 + case WalletType.nano:
309 + return secrets.nanoTestWalletSeeds;
310 + case WalletType.wownero:
311 + return secrets.wowneroTestWalletSeeds;
312 + default:
313 + return '';
314 + }
315 + }
316 +
317 + //* ====== Utility Function to get test receive address for each wallet type ========
318 + String getReceiveAddressByWalletType(WalletType walletType) {
319 + switch (walletType) {
320 + case WalletType.monero:
321 + return secrets.moneroTestWalletReceiveAddress;
322 + case WalletType.bitcoin:
323 + return secrets.bitcoinTestWalletReceiveAddress;
324 + case WalletType.ethereum:
325 + return secrets.ethereumTestWalletReceiveAddress;
326 + case WalletType.litecoin:
327 + return secrets.litecoinTestWalletReceiveAddress;
328 + case WalletType.bitcoinCash:
329 + return secrets.bitcoinCashTestWalletReceiveAddress;
330 + case WalletType.polygon:
331 + return secrets.polygonTestWalletReceiveAddress;
332 + case WalletType.solana:
333 + return secrets.solanaTestWalletReceiveAddress;
334 + case WalletType.tron:
335 + return secrets.tronTestWalletReceiveAddress;
336 + case WalletType.nano:
337 + return secrets.nanoTestWalletReceiveAddress;
338 + case WalletType.wownero:
339 + return secrets.wowneroTestWalletReceiveAddress;
340 + default:
341 + return '';
342 + }
343 + }
344 }
integration_test/funds_related_tests.dart
+7 -2
@@ -9,6 +9,7 @@ import 'robots/dashboard_page_robot.dart';
9 import 'robots/exchange_confirm_page_robot.dart';
10 import 'robots/exchange_page_robot.dart';
11 import 'robots/exchange_trade_page_robot.dart';
12 +import 'package:cake_wallet/.secrets.g.dart' as secrets;
13
14 void main() {
15 IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -32,7 +33,11 @@ void main() {
33
34 await commonTestFlows.startAppFlow(ValueKey('funds_exchange_test_app_key'));
35
35 - await commonTestFlows.restoreWalletThroughSeedsFlow();
36 + await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
37 + CommonTestConstants.testWalletType,
38 + secrets.solanaTestWalletSeeds,
39 + CommonTestConstants.pin,
40 + );
41
42 // ----------- RestoreFromSeedOrKeys Page -------------
43 await dashboardPageRobot.navigateToExchangePage();
@@ -59,7 +64,7 @@ void main() {
64
65 final onAuthPage = authPageRobot.onAuthPage();
66 if (onAuthPage) {
62 - await authPageRobot.enterPinCode(CommonTestConstants.pin, false);
67 + await authPageRobot.enterPinCode(CommonTestConstants.pin);
68 }
69
70 // ----------- Exchange Confirm Page -------------
integration_test/robots/dashboard_menu_widget_robot.dart new
+39
@@ -0,0 +1,39 @@
1 +import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
2 +import 'package:flutter_test/flutter_test.dart';
3 +
4 +import '../components/common_test_cases.dart';
5 +
6 +class DashboardMenuWidgetRobot {
7 + DashboardMenuWidgetRobot(this.tester) : commonTestCases = CommonTestCases(tester);
8 +
9 + final WidgetTester tester;
10 + late CommonTestCases commonTestCases;
11 +
12 + Future<void> hasMenuWidget() async {
13 + commonTestCases.hasType<MenuWidget>();
14 + }
15 +
16 + void displaysTheCorrectWalletNameAndSubName() {
17 + final menuWidgetState = tester.state<MenuWidgetState>(find.byType(MenuWidget));
18 +
19 + final walletName = menuWidgetState.widget.dashboardViewModel.name;
20 + commonTestCases.hasText(walletName);
21 +
22 + final walletSubName = menuWidgetState.widget.dashboardViewModel.subname;
23 + if (walletSubName.isNotEmpty) {
24 + commonTestCases.hasText(walletSubName);
25 + }
26 + }
27 +
28 + Future<void> navigateToWalletMenu() async {
29 + await commonTestCases.tapItemByKey('dashboard_page_menu_widget_wallet_menu_button_key');
30 + await commonTestCases.defaultSleepTime();
31 + }
32 +
33 + Future<void> navigateToSecurityAndBackupPage() async {
34 + await commonTestCases.tapItemByKey(
35 + 'dashboard_page_menu_widget_security_and_backup_button_key',
36 + );
37 + await commonTestCases.defaultSleepTime();
38 + }
39 +}
integration_test/robots/dashboard_page_robot.dart
+48 -14
@@ -1,20 +1,44 @@
1 import 'package:cake_wallet/generated/i18n.dart';
2 import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
3 +import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart';
4 import 'package:cw_core/wallet_type.dart';
5 import 'package:flutter_test/flutter_test.dart';
6
7 import '../components/common_test_cases.dart';
8 +import 'dashboard_menu_widget_robot.dart';
9
10 class DashboardPageRobot {
9 - DashboardPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
11 + DashboardPageRobot(this.tester)
12 + : commonTestCases = CommonTestCases(tester),
13 + dashboardMenuWidgetRobot = DashboardMenuWidgetRobot(tester);
14
15 final WidgetTester tester;
16 + final DashboardMenuWidgetRobot dashboardMenuWidgetRobot;
17 late CommonTestCases commonTestCases;
18
19 Future<void> isDashboardPage() async {
20 await commonTestCases.isSpecificPage<DashboardPage>();
21 }
22
23 + Future<void> confirmWalletTypeIsDisplayedCorrectly(
24 + WalletType type, {
25 + bool isHaven = false,
26 + }) async {
27 + final cryptoBalanceWidget =
28 + tester.widget<CryptoBalanceWidget>(find.byType(CryptoBalanceWidget));
29 + final hasAccounts = cryptoBalanceWidget.dashboardViewModel.balanceViewModel.hasAccounts;
30 +
31 + if (hasAccounts) {
32 + final walletName = cryptoBalanceWidget.dashboardViewModel.name;
33 + commonTestCases.hasText(walletName);
34 + } else {
35 + final walletName = walletTypeToString(type);
36 + final assetName = isHaven ? '$walletName Assets' : walletName;
37 + commonTestCases.hasText(assetName);
38 + }
39 + await commonTestCases.defaultSleepTime(seconds: 5);
40 + }
41 +
42 void confirmServiceUpdateButtonDisplays() {
43 commonTestCases.hasValueKey('dashboard_page_services_update_button_key');
44 }
@@ -27,30 +51,40 @@ class DashboardPageRobot {
51 commonTestCases.hasValueKey('dashboard_page_wallet_menu_button_key');
52 }
53
30 - Future<void> confirmRightCryptoAssetTitleDisplaysPerPageView(WalletType type,
31 - {bool isHaven = false}) async {
54 + Future<void> confirmRightCryptoAssetTitleDisplaysPerPageView(
55 + WalletType type, {
56 + bool isHaven = false,
57 + }) async {
58 //Balance Page
33 - final walletName = walletTypeToString(type);
34 - final assetName = isHaven ? '$walletName Assets' : walletName;
35 - commonTestCases.hasText(assetName);
59 + await confirmWalletTypeIsDisplayedCorrectly(type, isHaven: isHaven);
60
61 // Swipe to Cake features Page
38 - await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key', swipeRight: false);
39 - await commonTestCases.defaultSleepTime();
62 + await swipeDashboardTab(false);
63 commonTestCases.hasText('Cake ${S.current.features}');
64
65 // Swipe back to balance
43 - await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key');
44 - await commonTestCases.defaultSleepTime();
66 + await swipeDashboardTab(true);
67
68 // Swipe to Transactions Page
47 - await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key');
48 - await commonTestCases.defaultSleepTime();
69 + await swipeDashboardTab(true);
70 commonTestCases.hasText(S.current.transactions);
71
72 // Swipe back to balance
52 - await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key', swipeRight: false);
53 - await commonTestCases.defaultSleepTime(seconds: 5);
73 + await swipeDashboardTab(false);
74 + await commonTestCases.defaultSleepTime(seconds: 3);
75 + }
76 +
77 + Future<void> swipeDashboardTab(bool swipeRight) async {
78 + await commonTestCases.swipeByPageKey(
79 + key: 'dashboard_page_view_key',
80 + swipeRight: swipeRight,
81 + );
82 + await commonTestCases.defaultSleepTime();
83 + }
84 +
85 + Future<void> openDrawerMenu() async {
86 + await commonTestCases.tapItemByKey('dashboard_page_wallet_menu_button_key');
87 + await commonTestCases.defaultSleepTime();
88 }
89
90 Future<void> navigateToBuyPage() async {
integration_test/robots/exchange_page_robot.dart
+2 -2
@@ -123,7 +123,7 @@ class ExchangePageRobot {
123 return;
124 }
125
126 - await commonTestCases.scrollUntilVisible(
126 + await commonTestCases.dragUntilVisible(
127 'picker_items_index_${depositCurrency.name}_button_key',
128 'picker_scrollbar_key',
129 );
@@ -149,7 +149,7 @@ class ExchangePageRobot {
149 return;
150 }
151
152 - await commonTestCases.scrollUntilVisible(
152 + await commonTestCases.dragUntilVisible(
153 'picker_items_index_${receiveCurrency.name}_button_key',
154 'picker_scrollbar_key',
155 );
integration_test/robots/new_wallet_page_robot.dart new
+35
@@ -0,0 +1,35 @@
1 +import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart';
2 +import 'package:flutter_test/flutter_test.dart';
3 +
4 +import '../components/common_test_cases.dart';
5 +
6 +class NewWalletPageRobot {
7 + NewWalletPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
8 +
9 + final WidgetTester tester;
10 + late CommonTestCases commonTestCases;
11 +
12 + Future<void> isNewWalletPage() async {
13 + await commonTestCases.isSpecificPage<NewWalletPage>();
14 + }
15 +
16 + Future<void> enterWalletName(String walletName) async {
17 + await commonTestCases.enterText(
18 + walletName,
19 + 'new_wallet_page_wallet_name_textformfield_key',
20 + );
21 + await commonTestCases.defaultSleepTime();
22 + }
23 +
24 + Future<void> generateWalletName() async {
25 + await commonTestCases.tapItemByKey(
26 + 'new_wallet_page_wallet_name_textformfield_generate_name_button_key',
27 + );
28 + await commonTestCases.defaultSleepTime();
29 + }
30 +
31 + Future<void> onNextButtonPressed() async {
32 + await commonTestCases.tapItemByKey('new_wallet_page_confirm_button_key');
33 + await commonTestCases.defaultSleepTime();
34 + }
35 +}
integration_test/robots/pin_code_widget_robot.dart
+5 -6
@@ -24,13 +24,12 @@ class PinCodeWidgetRobot {
24 commonTestCases.hasValueKey('pin_code_button_0_key');
25 }
26
27 - Future<void> pushPinButton(int index) async {
28 - await commonTestCases.tapItemByKey('pin_code_button_${index}_key');
29 - }
30 -
31 - Future<void> enterPinCode(List<int> pinCode, bool isFirstEntry) async {
27 + Future<void> enterPinCode(List<int> pinCode, {int pumpDuration = 100}) async {
28 for (int pin in pinCode) {
33 - await pushPinButton(pin);
29 + await commonTestCases.tapItemByKey(
30 + 'pin_code_button_${pin}_key',
31 + pumpDuration: pumpDuration,
32 + );
33 }
34
35 await commonTestCases.defaultSleepTime();
integration_test/robots/pre_seed_page_robot.dart new
+20
@@ -0,0 +1,20 @@
1 +import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
2 +import 'package:flutter_test/flutter_test.dart';
3 +
4 +import '../components/common_test_cases.dart';
5 +
6 +class PreSeedPageRobot {
7 + PreSeedPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
8 +
9 + final WidgetTester tester;
10 + late CommonTestCases commonTestCases;
11 +
12 + Future<void> isPreSeedPage() async {
13 + await commonTestCases.isSpecificPage<PreSeedPage>();
14 + }
15 +
16 + Future<void> onConfirmButtonPressed() async {
17 + await commonTestCases.tapItemByKey('pre_seed_page_button_key');
18 + await commonTestCases.defaultSleepTime();
19 + }
20 +}
integration_test/robots/restore_from_seed_or_key_robot.dart
+17
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/entities/seed_type.dart';
2 import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
4 import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart';
@@ -70,6 +71,22 @@ class RestoreFromSeedOrKeysPageRobot {
71 await tester.pumpAndSettle();
72 }
73
74 + Future<void> enterBlockHeightForWalletRestore(String blockHeight) async {
75 + await commonTestCases.enterText(
76 + blockHeight,
77 + 'wallet_restore_from_seed_blockheight_textfield_key',
78 + );
79 + await tester.pumpAndSettle();
80 + }
81 +
82 + Future<void> chooseSeedTypeForMoneroOrWowneroWallets(MoneroSeedType selectedType) async {
83 + await commonTestCases.tapItemByKey('wallet_restore_from_seed_seedtype_picker_button_key');
84 +
85 + await commonTestCases.defaultSleepTime();
86 +
87 + await commonTestCases.tapItemByKey('picker_items_index_${selectedType.title}_button_key');
88 + }
89 +
90 Future<void> onPasteSeedPhraseButtonPressed() async {
91 await commonTestCases.tapItemByKey('wallet_restore_from_seed_wallet_seeds_paste_button_key');
92 }
integration_test/robots/restore_options_page_robot.dart
+3 -3
@@ -14,14 +14,14 @@ class RestoreOptionsPageRobot {
14 }
15
16 void hasRestoreOptionsButton() {
17 - commonTestCases.hasValueKey('restore_options_from_seeds_button_key');
17 + commonTestCases.hasValueKey('restore_options_from_seeds_or_keys_button_key');
18 commonTestCases.hasValueKey('restore_options_from_backup_button_key');
19 commonTestCases.hasValueKey('restore_options_from_hardware_wallet_button_key');
20 commonTestCases.hasValueKey('restore_options_from_qr_button_key');
21 }
22
23 - Future<void> navigateToRestoreFromSeedsPage() async {
24 - await commonTestCases.tapItemByKey('restore_options_from_seeds_button_key');
23 + Future<void> navigateToRestoreFromSeedsOrKeysPage() async {
24 + await commonTestCases.tapItemByKey('restore_options_from_seeds_or_keys_button_key');
25 await commonTestCases.defaultSleepTime();
26 }
27
integration_test/robots/security_and_backup_page_robot.dart new
+24
@@ -0,0 +1,24 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
3 +import 'package:flutter_test/flutter_test.dart';
4 +
5 +import '../components/common_test_cases.dart';
6 +
7 +class SecurityAndBackupPageRobot {
8 + SecurityAndBackupPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
9 +
10 + final WidgetTester tester;
11 + final CommonTestCases commonTestCases;
12 +
13 + Future<void> isSecurityAndBackupPage() async {
14 + await commonTestCases.isSpecificPage<SecurityBackupPage>();
15 + }
16 +
17 + void hasTitle() {
18 + commonTestCases.hasText(S.current.security_and_backup);
19 + }
20 +
21 + Future<void> navigateToShowKeysPage() async {
22 + await commonTestCases.tapItemByKey('security_backup_page_show_keys_button_key');
23 + }
24 +}
integration_test/robots/send_page_robot.dart
+6 -3
@@ -84,7 +84,7 @@ class SendPageRobot {
84 return;
85 }
86
87 - await commonTestCases.scrollUntilVisible(
87 + await commonTestCases.dragUntilVisible(
88 'picker_items_index_${receiveCurrency.name}_button_key',
89 'picker_scrollbar_key',
90 );
@@ -117,7 +117,7 @@ class SendPageRobot {
117 return;
118 }
119
120 - await commonTestCases.scrollUntilVisible(
120 + await commonTestCases.dragUntilVisible(
121 'picker_items_index_${priority.title}_button_key',
122 'picker_scrollbar_key',
123 );
@@ -198,7 +198,7 @@ class SendPageRobot {
198 tester.printToConsole('Starting inner _handleAuth loop checks');
199
200 try {
201 - await authPageRobot.enterPinCode(CommonTestConstants.pin, false);
201 + await authPageRobot.enterPinCode(CommonTestConstants.pin, pumpDuration: 500);
202 tester.printToConsole('Auth done');
203
204 await tester.pump();
@@ -213,6 +213,7 @@ class SendPageRobot {
213 }
214
215 Future<void> handleSendResult() async {
216 + await tester.pump();
217 tester.printToConsole('Inside handle function');
218
219 bool hasError = false;
@@ -287,6 +288,8 @@ class SendPageRobot {
288 // Loop to wait for the operation to commit transaction
289 await _waitForCommitTransactionCompletion();
290
291 + await tester.pump();
292 +
293 await commonTestCases.defaultSleepTime(seconds: 4);
294 } else {
295 await commonTestCases.defaultSleepTime();
integration_test/robots/transactions_page_robot.dart new
+286
@@ -0,0 +1,286 @@
1 +import 'dart:async';
2 +
3 +import 'package:cake_wallet/generated/i18n.dart';
4 +import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
5 +import 'package:cake_wallet/utils/date_formatter.dart';
6 +import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
7 +import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
8 +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
9 +import 'package:cake_wallet/view_model/dashboard/date_section_item.dart';
10 +import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
11 +import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
12 +import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
13 +import 'package:cw_core/crypto_currency.dart';
14 +import 'package:cw_core/sync_status.dart';
15 +import 'package:cw_core/transaction_direction.dart';
16 +import 'package:flutter/material.dart';
17 +import 'package:flutter_test/flutter_test.dart';
18 +import 'package:intl/intl.dart';
19 +
20 +import '../components/common_test_cases.dart';
21 +
22 +class TransactionsPageRobot {
23 + TransactionsPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
24 +
25 + final WidgetTester tester;
26 + late CommonTestCases commonTestCases;
27 +
28 + Future<void> isTransactionsPage() async {
29 + await commonTestCases.isSpecificPage<TransactionsPage>();
30 + }
31 +
32 + Future<void> confirmTransactionsPageConstantsDisplayProperly() async {
33 + await commonTestCases.defaultSleepTime();
34 +
35 + final transactionsPage = tester.widget<TransactionsPage>(find.byType(TransactionsPage));
36 + final dashboardViewModel = transactionsPage.dashboardViewModel;
37 + if (dashboardViewModel.status is SyncingSyncStatus) {
38 + commonTestCases.hasValueKey('transactions_page_syncing_alert_card_key');
39 + commonTestCases.hasText(S.current.syncing_wallet_alert_title);
40 + commonTestCases.hasText(S.current.syncing_wallet_alert_content);
41 + }
42 +
43 + commonTestCases.hasValueKey('transactions_page_header_row_key');
44 + commonTestCases.hasText(S.current.transactions);
45 + commonTestCases.hasValueKey('transactions_page_header_row_transaction_filter_button_key');
46 + }
47 +
48 + Future<void> confirmTransactionHistoryListDisplaysCorrectly(bool hasTxHistoryWhileSyncing) async {
49 + // Retrieve the TransactionsPage widget and its DashboardViewModel
50 + final transactionsPage = tester.widget<TransactionsPage>(find.byType(TransactionsPage));
51 + final dashboardViewModel = transactionsPage.dashboardViewModel;
52 +
53 + // Define a timeout to prevent infinite loops
54 + // Putting at one hour for cases like monero that takes time to sync
55 + final timeout = Duration(hours: 1);
56 + final pollingInterval = Duration(seconds: 2);
57 + final endTime = DateTime.now().add(timeout);
58 +
59 + while (DateTime.now().isBefore(endTime)) {
60 + final isSynced = dashboardViewModel.status is SyncedSyncStatus;
61 + final itemsLoaded = dashboardViewModel.items.isNotEmpty;
62 +
63 + // Perform item checks if items are loaded
64 + if (itemsLoaded) {
65 + await _performItemChecks(dashboardViewModel);
66 + } else {
67 + // Verify placeholder when items are not loaded
68 + _verifyPlaceholder();
69 + }
70 +
71 + // Determine if we should exit the loop
72 + if (_shouldExitLoop(hasTxHistoryWhileSyncing, isSynced, itemsLoaded)) {
73 + break;
74 + }
75 +
76 + // Pump the UI and wait for the next polling interval
77 + await tester.pump(pollingInterval);
78 + }
79 +
80 + // After the loop, verify that both status is synced and items are loaded
81 + if (!_isFinalStateValid(dashboardViewModel)) {
82 + throw TimeoutException('Dashboard did not sync and load items within the allotted time.');
83 + }
84 + }
85 +
86 + bool _shouldExitLoop(bool hasTxHistoryWhileSyncing, bool isSynced, bool itemsLoaded) {
87 + if (hasTxHistoryWhileSyncing) {
88 + // When hasTxHistoryWhileSyncing is true, exit when status is synced
89 + return isSynced;
90 + } else {
91 + // When hasTxHistoryWhileSyncing is false, exit when status is synced and items are loaded
92 + return isSynced && itemsLoaded;
93 + }
94 + }
95 +
96 + void _verifyPlaceholder() {
97 + commonTestCases.hasValueKey('transactions_page_placeholder_transactions_text_key');
98 + commonTestCases.hasText(S.current.placeholder_transactions);
99 + }
100 +
101 + bool _isFinalStateValid(DashboardViewModel dashboardViewModel) {
102 + final isSynced = dashboardViewModel.status is SyncedSyncStatus;
103 + final itemsLoaded = dashboardViewModel.items.isNotEmpty;
104 + return isSynced && itemsLoaded;
105 + }
106 +
107 + Future<void> _performItemChecks(DashboardViewModel dashboardViewModel) async {
108 + List<ActionListItem> items = dashboardViewModel.items;
109 + for (var item in items) {
110 + final keyId = (item.key as ValueKey<String>).value;
111 + tester.printToConsole('\n');
112 + tester.printToConsole(keyId);
113 +
114 + await commonTestCases.dragUntilVisible(keyId, 'transactions_page_list_view_builder_key');
115 + await tester.pump();
116 +
117 + final isWidgetVisible = tester.any(find.byKey(ValueKey(keyId)));
118 + if (!isWidgetVisible) {
119 + tester.printToConsole('Moving to next visible item on list');
120 + continue;
121 + }
122 + ;
123 + await tester.pump();
124 +
125 + if (item is DateSectionItem) {
126 + await _verifyDateSectionItem(item);
127 + } else if (item is TransactionListItem) {
128 + tester.printToConsole(item.formattedTitle);
129 + tester.printToConsole(item.formattedFiatAmount);
130 + tester.printToConsole('\n');
131 + await _verifyTransactionListItemDisplay(item, dashboardViewModel);
132 + } else if (item is AnonpayTransactionListItem) {
133 + await _verifyAnonpayTransactionListItemDisplay(item);
134 + } else if (item is TradeListItem) {
135 + await _verifyTradeListItemDisplay(item);
136 + } else if (item is OrderListItem) {
137 + await _verifyOrderListItemDisplay(item);
138 + }
139 + }
140 + }
141 +
142 + Future<void> _verifyDateSectionItem(DateSectionItem item) async {
143 + final title = DateFormatter.convertDateTimeToReadableString(item.date);
144 + tester.printToConsole(title);
145 + await tester.pump();
146 +
147 + commonTestCases.findWidgetViaDescendant(
148 + of: find.byKey(item.key),
149 + matching: find.text(title),
150 + );
151 + }
152 +
153 + Future<void> _verifyTransactionListItemDisplay(
154 + TransactionListItem item,
155 + DashboardViewModel dashboardViewModel,
156 + ) async {
157 + final keyId =
158 + '${dashboardViewModel.type.name}_transaction_history_item_${item.transaction.id}_key';
159 +
160 + if (item.hasTokens && item.assetOfTransaction == null) return;
161 +
162 + //* ==============Confirm it has the right key for this item ========
163 + commonTestCases.hasValueKey(keyId);
164 +
165 + //* ======Confirm it displays the properly formatted amount==========
166 + commonTestCases.findWidgetViaDescendant(
167 + of: find.byKey(ValueKey(keyId)),
168 + matching: find.text(item.formattedCryptoAmount),
169 + );
170 +
171 + //* ======Confirm it displays the properly formatted title===========
172 + final transactionType = dashboardViewModel.getTransactionType(item.transaction);
173 +
174 + final title = item.formattedTitle + item.formattedStatus + transactionType;
175 +
176 + commonTestCases.findWidgetViaDescendant(
177 + of: find.byKey(ValueKey(keyId)),
178 + matching: find.text(title),
179 + );
180 +
181 + //* ======Confirm it displays the properly formatted date============
182 + final formattedDate = DateFormat('HH:mm').format(item.transaction.date);
183 + commonTestCases.findWidgetViaDescendant(
184 + of: find.byKey(ValueKey(keyId)),
185 + matching: find.text(formattedDate),
186 + );
187 +
188 + //* ======Confirm it displays the properly formatted fiat amount=====
189 + final formattedFiatAmount =
190 + dashboardViewModel.balanceViewModel.isFiatDisabled ? '' : item.formattedFiatAmount;
191 + if (formattedFiatAmount.isNotEmpty) {
192 + commonTestCases.findWidgetViaDescendant(
193 + of: find.byKey(ValueKey(keyId)),
194 + matching: find.text(formattedFiatAmount),
195 + );
196 + }
197 +
198 + //* ======Confirm it displays the right image based on the transaction direction=====
199 + final imageToUse = item.transaction.direction == TransactionDirection.incoming
200 + ? 'assets/images/down_arrow.png'
201 + : 'assets/images/up_arrow.png';
202 +
203 + find.widgetWithImage(Container, AssetImage(imageToUse));
204 + }
205 +
206 + Future<void> _verifyAnonpayTransactionListItemDisplay(AnonpayTransactionListItem item) async {
207 + final keyId = 'anonpay_invoice_transaction_list_item_${item.transaction.invoiceId}_key';
208 +
209 + //* ==============Confirm it has the right key for this item ========
210 + commonTestCases.hasValueKey(keyId);
211 +
212 + //* ==============Confirm it displays the correct provider =========================
213 + commonTestCases.hasText(item.transaction.provider);
214 +
215 + //* ===========Confirm it displays the properly formatted amount with currency ========
216 + final currency = item.transaction.fiatAmount != null
217 + ? item.transaction.fiatEquiv ?? ''
218 + : CryptoCurrency.fromFullName(item.transaction.coinTo).name.toUpperCase();
219 +
220 + final amount =
221 + item.transaction.fiatAmount?.toString() ?? (item.transaction.amountTo?.toString() ?? '');
222 +
223 + final amountCurrencyText = amount + ' ' + currency;
224 +
225 + commonTestCases.hasText(amountCurrencyText);
226 +
227 + //* ======Confirm it displays the properly formatted date=================
228 + final formattedDate = DateFormat('HH:mm').format(item.transaction.createdAt);
229 + commonTestCases.hasText(formattedDate);
230 +
231 + //* ===============Confirm it displays the right image====================
232 + find.widgetWithImage(ClipRRect, AssetImage('assets/images/trocador.png'));
233 + }
234 +
235 + Future<void> _verifyTradeListItemDisplay(TradeListItem item) async {
236 + final keyId = 'trade_list_item_${item.trade.id}_key';
237 +
238 + //* ==============Confirm it has the right key for this item ========
239 + commonTestCases.hasValueKey(keyId);
240 +
241 + //* ==============Confirm it displays the correct provider =========================
242 + final conversionFlow = '${item.trade.from.toString()} → ${item.trade.to.toString()}';
243 +
244 + commonTestCases.hasText(conversionFlow);
245 +
246 + //* ===========Confirm it displays the properly formatted amount with its crypto tag ========
247 +
248 + final amountCryptoText = item.tradeFormattedAmount + ' ' + item.trade.from.toString();
249 +
250 + commonTestCases.hasText(amountCryptoText);
251 +
252 + //* ======Confirm it displays the properly formatted date=================
253 + final createdAtFormattedDate =
254 + item.trade.createdAt != null ? DateFormat('HH:mm').format(item.trade.createdAt!) : null;
255 +
256 + if (createdAtFormattedDate != null) {
257 + commonTestCases.hasText(createdAtFormattedDate);
258 + }
259 +
260 + //* ===============Confirm it displays the right image====================
261 + commonTestCases.hasValueKey(item.trade.provider.image);
262 + }
263 +
264 + Future<void> _verifyOrderListItemDisplay(OrderListItem item) async {
265 + final keyId = 'order_list_item_${item.order.id}_key';
266 +
267 + //* ==============Confirm it has the right key for this item ========
268 + commonTestCases.hasValueKey(keyId);
269 +
270 + //* ==============Confirm it displays the correct provider =========================
271 + final orderFlow = '${item.order.from!} → ${item.order.to}';
272 +
273 + commonTestCases.hasText(orderFlow);
274 +
275 + //* ===========Confirm it displays the properly formatted amount with its crypto tag ========
276 +
277 + final amountCryptoText = item.orderFormattedAmount + ' ' + item.order.to!;
278 +
279 + commonTestCases.hasText(amountCryptoText);
280 +
281 + //* ======Confirm it displays the properly formatted date=================
282 + final createdAtFormattedDate = DateFormat('HH:mm').format(item.order.createdAt);
283 +
284 + commonTestCases.hasText(createdAtFormattedDate);
285 + }
286 +}
integration_test/robots/wallet_group_description_page_robot.dart new
+32
@@ -0,0 +1,32 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/src/screens/new_wallet/wallet_group_description_page.dart';
3 +import 'package:flutter_test/flutter_test.dart';
4 +
5 +import '../components/common_test_cases.dart';
6 +
7 +class WalletGroupDescriptionPageRobot {
8 + WalletGroupDescriptionPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
9 +
10 + final WidgetTester tester;
11 + final CommonTestCases commonTestCases;
12 +
13 + Future<void> isWalletGroupDescriptionPage() async {
14 + await commonTestCases.isSpecificPage<WalletGroupDescriptionPage>();
15 + }
16 +
17 + void hasTitle() {
18 + commonTestCases.hasText(S.current.wallet_group);
19 + }
20 +
21 + Future<void> navigateToCreateNewSeedPage() async {
22 + await commonTestCases.tapItemByKey(
23 + 'wallet_group_description_page_create_new_seed_button_key',
24 + );
25 + }
26 +
27 + Future<void> navigateToChooseWalletGroup() async {
28 + await commonTestCases.tapItemByKey(
29 + 'wallet_group_description_page_choose_wallet_group_button_key',
30 + );
31 + }
32 +}
integration_test/robots/wallet_keys_robot.dart new
+162
@@ -0,0 +1,162 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/reactions/wallet_connect.dart';
3 +import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
4 +import 'package:cake_wallet/store/app_store.dart';
5 +import 'package:cw_core/monero_wallet_keys.dart';
6 +import 'package:cw_core/wallet_type.dart';
7 +import 'package:cw_monero/monero_wallet.dart';
8 +import 'package:cw_wownero/wownero_wallet.dart';
9 +import 'package:flutter_test/flutter_test.dart';
10 +import 'package:polyseed/polyseed.dart';
11 +
12 +import '../components/common_test_cases.dart';
13 +
14 +class WalletKeysAndSeedPageRobot {
15 + WalletKeysAndSeedPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
16 +
17 + final WidgetTester tester;
18 + final CommonTestCases commonTestCases;
19 +
20 + Future<void> isWalletKeysAndSeedPage() async {
21 + await commonTestCases.isSpecificPage<WalletKeysPage>();
22 + }
23 +
24 + void hasTitle() {
25 + final walletKeysPage = tester.widget<WalletKeysPage>(find.byType(WalletKeysPage));
26 + final walletKeysViewModel = walletKeysPage.walletKeysViewModel;
27 + commonTestCases.hasText(walletKeysViewModel.title);
28 + }
29 +
30 + void hasShareWarning() {
31 + commonTestCases.hasText(S.current.do_not_share_warning_text.toUpperCase());
32 + }
33 +
34 + Future<void> confirmWalletCredentials(WalletType walletType) async {
35 + final walletKeysPage = tester.widget<WalletKeysPage>(find.byType(WalletKeysPage));
36 + final walletKeysViewModel = walletKeysPage.walletKeysViewModel;
37 +
38 + final appStore = walletKeysViewModel.appStore;
39 + final walletName = walletType.name;
40 + bool hasSeed = appStore.wallet!.seed != null;
41 + bool hasHexSeed = appStore.wallet!.hexSeed != null;
42 + bool hasPrivateKey = appStore.wallet!.privateKey != null;
43 +
44 + if (walletType == WalletType.monero) {
45 + final moneroWallet = appStore.wallet as MoneroWallet;
46 + final lang = PolyseedLang.getByPhrase(moneroWallet.seed);
47 + final legacySeed = moneroWallet.seedLegacy(lang.nameEnglish);
48 +
49 + _confirmMoneroWalletCredentials(
50 + appStore,
51 + walletName,
52 + moneroWallet.seed,
53 + legacySeed,
54 + );
55 + }
56 +
57 + if (walletType == WalletType.wownero) {
58 + final wowneroWallet = appStore.wallet as WowneroWallet;
59 + final lang = PolyseedLang.getByPhrase(wowneroWallet.seed);
60 + final legacySeed = wowneroWallet.seedLegacy(lang.nameEnglish);
61 +
62 + _confirmMoneroWalletCredentials(
63 + appStore,
64 + walletName,
65 + wowneroWallet.seed,
66 + legacySeed,
67 + );
68 + }
69 +
70 + if (walletType == WalletType.bitcoin ||
71 + walletType == WalletType.litecoin ||
72 + walletType == WalletType.bitcoinCash) {
73 + commonTestCases.hasText(appStore.wallet!.seed!);
74 + tester.printToConsole('$walletName wallet has seeds properly displayed');
75 + }
76 +
77 + if (isEVMCompatibleChain(walletType) ||
78 + walletType == WalletType.solana ||
79 + walletType == WalletType.tron) {
80 + if (hasSeed) {
81 + commonTestCases.hasText(appStore.wallet!.seed!);
82 + tester.printToConsole('$walletName wallet has seeds properly displayed');
83 + }
84 + if (hasPrivateKey) {
85 + commonTestCases.hasText(appStore.wallet!.privateKey!);
86 + tester.printToConsole('$walletName wallet has private key properly displayed');
87 + }
88 + }
89 +
90 + if (walletType == WalletType.nano || walletType == WalletType.banano) {
91 + if (hasSeed) {
92 + commonTestCases.hasText(appStore.wallet!.seed!);
93 + tester.printToConsole('$walletName wallet has seeds properly displayed');
94 + }
95 + if (hasHexSeed) {
96 + commonTestCases.hasText(appStore.wallet!.hexSeed!);
97 + tester.printToConsole('$walletName wallet has hexSeed properly displayed');
98 + }
99 + if (hasPrivateKey) {
100 + commonTestCases.hasText(appStore.wallet!.privateKey!);
101 + tester.printToConsole('$walletName wallet has private key properly displayed');
102 + }
103 + }
104 +
105 + await commonTestCases.defaultSleepTime(seconds: 5);
106 + }
107 +
108 + void _confirmMoneroWalletCredentials(
109 + AppStore appStore,
110 + String walletName,
111 + String seed,
112 + String legacySeed,
113 + ) {
114 + final keys = appStore.wallet!.keys as MoneroWalletKeys;
115 +
116 + final hasPublicSpendKey = commonTestCases.isKeyPresent(
117 + '${walletName}_wallet_public_spend_key_item_key',
118 + );
119 + final hasPrivateSpendKey = commonTestCases.isKeyPresent(
120 + '${walletName}_wallet_private_spend_key_item_key',
121 + );
122 + final hasPublicViewKey = commonTestCases.isKeyPresent(
123 + '${walletName}_wallet_public_view_key_item_key',
124 + );
125 + final hasPrivateViewKey = commonTestCases.isKeyPresent(
126 + '${walletName}_wallet_private_view_key_item_key',
127 + );
128 + final hasSeeds = seed.isNotEmpty;
129 + final hasSeedLegacy = Polyseed.isValidSeed(seed);
130 +
131 + if (hasPublicSpendKey) {
132 + commonTestCases.hasText(keys.publicSpendKey);
133 + tester.printToConsole('$walletName wallet has public spend key properly displayed');
134 + }
135 + if (hasPrivateSpendKey) {
136 + commonTestCases.hasText(keys.privateSpendKey);
137 + tester.printToConsole('$walletName wallet has private spend key properly displayed');
138 + }
139 + if (hasPublicViewKey) {
140 + commonTestCases.hasText(keys.publicViewKey);
141 + tester.printToConsole('$walletName wallet has public view key properly displayed');
142 + }
143 + if (hasPrivateViewKey) {
144 + commonTestCases.hasText(keys.privateViewKey);
145 + tester.printToConsole('$walletName wallet has private view key properly displayed');
146 + }
147 + if (hasSeeds) {
148 + commonTestCases.hasText(seed);
149 + tester.printToConsole('$walletName wallet has seeds properly displayed');
150 + }
151 + if (hasSeedLegacy) {
152 + commonTestCases.hasText(legacySeed);
153 + tester.printToConsole('$walletName wallet has legacy seeds properly displayed');
154 + }
155 + }
156 +
157 + Future<void> backToDashboard() async {
158 + tester.printToConsole('Going back to dashboard from credentials page');
159 + await commonTestCases.goBack();
160 + await commonTestCases.goBack();
161 + }
162 +}
integration_test/robots/wallet_list_page_robot.dart new
+27
@@ -0,0 +1,27 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:flutter_test/flutter_test.dart';
3 +
4 +import '../components/common_test_cases.dart';
5 +
6 +class WalletListPageRobot {
7 + WalletListPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
8 +
9 + final WidgetTester tester;
10 + late CommonTestCases commonTestCases;
11 +
12 + Future<void> isWalletListPage() async {
13 + await commonTestCases.isSpecificPage<WalletListPageRobot>();
14 + }
15 +
16 + void displaysCorrectTitle() {
17 + commonTestCases.hasText(S.current.wallets);
18 + }
19 +
20 + Future<void> navigateToCreateNewWalletPage() async {
21 + commonTestCases.tapItemByKey('wallet_list_page_create_new_wallet_button_key');
22 + }
23 +
24 + Future<void> navigateToRestoreWalletOptionsPage() async {
25 + commonTestCases.tapItemByKey('wallet_list_page_restore_wallet_button_key');
26 + }
27 +}
integration_test/robots/wallet_seed_page_robot.dart new
+57
@@ -0,0 +1,57 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
3 +import 'package:flutter_test/flutter_test.dart';
4 +
5 +import '../components/common_test_cases.dart';
6 +
7 +class WalletSeedPageRobot {
8 + WalletSeedPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
9 +
10 + final WidgetTester tester;
11 + late CommonTestCases commonTestCases;
12 +
13 + Future<void> isWalletSeedPage() async {
14 + await commonTestCases.isSpecificPage<WalletSeedPage>();
15 + }
16 +
17 + Future<void> onNextButtonPressed() async {
18 + await commonTestCases.tapItemByKey('wallet_seed_page_next_button_key');
19 + await commonTestCases.defaultSleepTime();
20 + }
21 +
22 + Future<void> onConfirmButtonOnSeedAlertDialogPressed() async {
23 + await commonTestCases.tapItemByKey('wallet_seed_page_seed_alert_confirm_button_key');
24 + await commonTestCases.defaultSleepTime();
25 + }
26 +
27 + Future<void> onBackButtonOnSeedAlertDialogPressed() async {
28 + await commonTestCases.tapItemByKey('wallet_seed_page_seed_alert_back_button_key');
29 + await commonTestCases.defaultSleepTime();
30 + }
31 +
32 + void confirmWalletDetailsDisplayCorrectly() {
33 + final walletSeedPage = tester.widget<WalletSeedPage>(find.byType(WalletSeedPage));
34 +
35 + final walletSeedViewModel = walletSeedPage.walletSeedViewModel;
36 +
37 + final walletName = walletSeedViewModel.name;
38 + final walletSeeds = walletSeedViewModel.seed;
39 +
40 + commonTestCases.hasText(walletName);
41 + commonTestCases.hasText(walletSeeds);
42 + }
43 +
44 + void confirmWalletSeedReminderDisplays() {
45 + commonTestCases.hasText(S.current.seed_reminder);
46 + }
47 +
48 + Future<void> onSaveSeedsButtonPressed() async {
49 + await commonTestCases.tapItemByKey('wallet_seed_page_save_seeds_button_key');
50 + await commonTestCases.defaultSleepTime();
51 + }
52 +
53 + Future<void> onCopySeedsButtonPressed() async {
54 + await commonTestCases.tapItemByKey('wallet_seed_page_copy_seeds_button_key');
55 + await commonTestCases.defaultSleepTime();
56 + }
57 +}
integration_test/test_suites/confirm_seeds_flow_test.dart new
+107
@@ -0,0 +1,107 @@
1 +import 'package:cake_wallet/wallet_types.g.dart';
2 +import 'package:cw_core/wallet_type.dart';
3 +import 'package:flutter/foundation.dart';
4 +import 'package:flutter_test/flutter_test.dart';
5 +import 'package:integration_test/integration_test.dart';
6 +
7 +import '../components/common_test_constants.dart';
8 +import '../components/common_test_flows.dart';
9 +import '../robots/auth_page_robot.dart';
10 +import '../robots/dashboard_page_robot.dart';
11 +import '../robots/security_and_backup_page_robot.dart';
12 +import '../robots/wallet_keys_robot.dart';
13 +
14 +void main() {
15 + IntegrationTestWidgetsFlutterBinding.ensureInitialized();
16 +
17 + AuthPageRobot authPageRobot;
18 + CommonTestFlows commonTestFlows;
19 + DashboardPageRobot dashboardPageRobot;
20 + WalletKeysAndSeedPageRobot walletKeysAndSeedPageRobot;
21 + SecurityAndBackupPageRobot securityAndBackupPageRobot;
22 +
23 + testWidgets(
24 + 'Confirm if the seeds display properly',
25 + (tester) async {
26 + authPageRobot = AuthPageRobot(tester);
27 + commonTestFlows = CommonTestFlows(tester);
28 + dashboardPageRobot = DashboardPageRobot(tester);
29 + walletKeysAndSeedPageRobot = WalletKeysAndSeedPageRobot(tester);
30 + securityAndBackupPageRobot = SecurityAndBackupPageRobot(tester);
31 +
32 + // Start the app
33 + await commonTestFlows.startAppFlow(
34 + ValueKey('confirm_creds_display_correctly_flow_app_key'),
35 + );
36 +
37 + await commonTestFlows.welcomePageToCreateNewWalletFlow(
38 + WalletType.solana,
39 + CommonTestConstants.pin,
40 + );
41 +
42 + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana);
43 +
44 + await _confirmSeedsFlowForWalletType(
45 + WalletType.solana,
46 + authPageRobot,
47 + dashboardPageRobot,
48 + securityAndBackupPageRobot,
49 + walletKeysAndSeedPageRobot,
50 + tester,
51 + );
52 +
53 + // Do the same for other available wallet types
54 + for (var walletType in availableWalletTypes) {
55 + if (walletType == WalletType.solana) {
56 + continue;
57 + }
58 +
59 + await commonTestFlows.switchToWalletMenuFromDashboardPage();
60 +
61 + await commonTestFlows.createNewWalletFromWalletMenu(walletType);
62 +
63 + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(walletType);
64 +
65 + await _confirmSeedsFlowForWalletType(
66 + walletType,
67 + authPageRobot,
68 + dashboardPageRobot,
69 + securityAndBackupPageRobot,
70 + walletKeysAndSeedPageRobot,
71 + tester,
72 + );
73 + }
74 +
75 + await Future.delayed(Duration(seconds: 15));
76 + },
77 + );
78 +}
79 +
80 +Future<void> _confirmSeedsFlowForWalletType(
81 + WalletType walletType,
82 + AuthPageRobot authPageRobot,
83 + DashboardPageRobot dashboardPageRobot,
84 + SecurityAndBackupPageRobot securityAndBackupPageRobot,
85 + WalletKeysAndSeedPageRobot walletKeysAndSeedPageRobot,
86 + WidgetTester tester,
87 +) async {
88 + await dashboardPageRobot.openDrawerMenu();
89 + await dashboardPageRobot.dashboardMenuWidgetRobot.navigateToSecurityAndBackupPage();
90 +
91 + await securityAndBackupPageRobot.navigateToShowKeysPage();
92 +
93 + final onAuthPage = authPageRobot.onAuthPage();
94 + if (onAuthPage) {
95 + await authPageRobot.enterPinCode(CommonTestConstants.pin);
96 + }
97 +
98 + await tester.pumpAndSettle();
99 +
100 + await walletKeysAndSeedPageRobot.isWalletKeysAndSeedPage();
101 + walletKeysAndSeedPageRobot.hasTitle();
102 + walletKeysAndSeedPageRobot.hasShareWarning();
103 +
104 + walletKeysAndSeedPageRobot.confirmWalletCredentials(walletType);
105 +
106 + await walletKeysAndSeedPageRobot.backToDashboard();
107 +}
integration_test/test_suites/create_wallet_flow_test.dart new
+57
@@ -0,0 +1,57 @@
1 +import 'package:cake_wallet/wallet_types.g.dart';
2 +import 'package:cw_core/wallet_type.dart';
3 +import 'package:flutter/material.dart';
4 +import 'package:flutter_test/flutter_test.dart';
5 +import 'package:integration_test/integration_test.dart';
6 +
7 +import '../components/common_test_constants.dart';
8 +import '../components/common_test_flows.dart';
9 +import '../robots/dashboard_page_robot.dart';
10 +
11 +void main() {
12 + IntegrationTestWidgetsFlutterBinding.ensureInitialized();
13 +
14 + CommonTestFlows commonTestFlows;
15 + DashboardPageRobot dashboardPageRobot;
16 +
17 + testWidgets(
18 + 'Create Wallet Flow',
19 + (tester) async {
20 + commonTestFlows = CommonTestFlows(tester);
21 + dashboardPageRobot = DashboardPageRobot(tester);
22 +
23 + // Start the app
24 + await commonTestFlows.startAppFlow(
25 + ValueKey('create_wallets_through_seeds_test_app_key'),
26 + );
27 +
28 + await commonTestFlows.welcomePageToCreateNewWalletFlow(
29 + WalletType.solana,
30 + CommonTestConstants.pin,
31 + );
32 +
33 + // Confirm it actually restores a solana wallet
34 + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana);
35 +
36 + // Do the same for other available wallet types
37 + for (var walletType in availableWalletTypes) {
38 + if (walletType == WalletType.solana) {
39 + continue;
40 + }
41 +
42 + await commonTestFlows.switchToWalletMenuFromDashboardPage();
43 +
44 + await commonTestFlows.createNewWalletFromWalletMenu(walletType);
45 +
46 + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(walletType);
47 + }
48 +
49 + // Goes to the wallet menu and provides a confirmation that all the wallets were correctly restored
50 + await commonTestFlows.switchToWalletMenuFromDashboardPage();
51 +
52 + commonTestFlows.confirmAllAvailableWalletTypeIconsDisplayCorrectly();
53 +
54 + await Future.delayed(Duration(seconds: 5));
55 + },
56 + );
57 +}
integration_test/test_suites/exchange_flow_test.dart
+38 -35
@@ -9,6 +9,7 @@ import '../robots/dashboard_page_robot.dart';
9 import '../robots/exchange_confirm_page_robot.dart';
10 import '../robots/exchange_page_robot.dart';
11 import '../robots/exchange_trade_page_robot.dart';
12 +import 'package:cake_wallet/.secrets.g.dart' as secrets;
13
14 void main() {
15 IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -20,40 +21,42 @@ void main() {
21 ExchangeTradePageRobot exchangeTradePageRobot;
22 ExchangeConfirmPageRobot exchangeConfirmPageRobot;
23
23 - group('Exchange Flow Tests', () {
24 - testWidgets('Exchange flow', (tester) async {
25 - authPageRobot = AuthPageRobot(tester);
26 - commonTestFlows = CommonTestFlows(tester);
27 - exchangePageRobot = ExchangePageRobot(tester);
28 - dashboardPageRobot = DashboardPageRobot(tester);
29 - exchangeTradePageRobot = ExchangeTradePageRobot(tester);
30 - exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester);
31 -
32 - await commonTestFlows.startAppFlow(ValueKey('exchange_app_test_key'));
33 - await commonTestFlows.restoreWalletThroughSeedsFlow();
34 - await dashboardPageRobot.navigateToExchangePage();
35 -
36 - // ----------- Exchange Page -------------
37 - await exchangePageRobot.selectDepositCurrency(CommonTestConstants.testDepositCurrency);
38 - await exchangePageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency);
39 -
40 - await exchangePageRobot.enterDepositAmount(CommonTestConstants.exchangeTestAmount);
41 - await exchangePageRobot.enterDepositRefundAddress(
42 - depositAddress: CommonTestConstants.testWalletAddress,
43 - );
44 - await exchangePageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress);
45 -
46 - await exchangePageRobot.onExchangeButtonPressed();
47 -
48 - await exchangePageRobot.handleErrors(CommonTestConstants.exchangeTestAmount);
49 -
50 - final onAuthPage = authPageRobot.onAuthPage();
51 - if (onAuthPage) {
52 - await authPageRobot.enterPinCode(CommonTestConstants.pin, false);
53 - }
54 -
55 - await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed();
56 - await exchangeTradePageRobot.onGotItButtonPressed();
57 - });
24 + testWidgets('Exchange flow', (tester) async {
25 + authPageRobot = AuthPageRobot(tester);
26 + commonTestFlows = CommonTestFlows(tester);
27 + exchangePageRobot = ExchangePageRobot(tester);
28 + dashboardPageRobot = DashboardPageRobot(tester);
29 + exchangeTradePageRobot = ExchangeTradePageRobot(tester);
30 + exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester);
31 +
32 + await commonTestFlows.startAppFlow(ValueKey('exchange_app_test_key'));
33 + await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
34 + CommonTestConstants.testWalletType,
35 + secrets.solanaTestWalletSeeds,
36 + CommonTestConstants.pin,
37 + );
38 + await dashboardPageRobot.navigateToExchangePage();
39 +
40 + // ----------- Exchange Page -------------
41 + await exchangePageRobot.selectDepositCurrency(CommonTestConstants.testDepositCurrency);
42 + await exchangePageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency);
43 +
44 + await exchangePageRobot.enterDepositAmount(CommonTestConstants.exchangeTestAmount);
45 + await exchangePageRobot.enterDepositRefundAddress(
46 + depositAddress: CommonTestConstants.testWalletAddress,
47 + );
48 + await exchangePageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress);
49 +
50 + await exchangePageRobot.onExchangeButtonPressed();
51 +
52 + await exchangePageRobot.handleErrors(CommonTestConstants.exchangeTestAmount);
53 +
54 + final onAuthPage = authPageRobot.onAuthPage();
55 + if (onAuthPage) {
56 + await authPageRobot.enterPinCode(CommonTestConstants.pin);
57 + }
58 +
59 + await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed();
60 + await exchangeTradePageRobot.onGotItButtonPressed();
61 });
62 }
integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart new
+63
@@ -0,0 +1,63 @@
1 +import 'package:cake_wallet/wallet_types.g.dart';
2 +import 'package:cw_core/wallet_type.dart';
3 +import 'package:flutter/material.dart';
4 +import 'package:flutter_test/flutter_test.dart';
5 +import 'package:integration_test/integration_test.dart';
6 +
7 +import '../components/common_test_constants.dart';
8 +import '../components/common_test_flows.dart';
9 +import '../robots/dashboard_page_robot.dart';
10 +import 'package:cake_wallet/.secrets.g.dart' as secrets;
11 +
12 +void main() {
13 + IntegrationTestWidgetsFlutterBinding.ensureInitialized();
14 +
15 + CommonTestFlows commonTestFlows;
16 + DashboardPageRobot dashboardPageRobot;
17 +
18 + testWidgets(
19 + 'Restoring Wallets Through Seeds',
20 + (tester) async {
21 + commonTestFlows = CommonTestFlows(tester);
22 + dashboardPageRobot = DashboardPageRobot(tester);
23 +
24 + // Start the app
25 + await commonTestFlows.startAppFlow(
26 + ValueKey('restore_wallets_through_seeds_test_app_key'),
27 + );
28 +
29 + // Restore the first wallet type: Solana
30 + await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
31 + WalletType.solana,
32 + secrets.solanaTestWalletSeeds,
33 + CommonTestConstants.pin,
34 + );
35 +
36 + // Confirm it actually restores a solana wallet
37 + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana);
38 +
39 + // Do the same for other available wallet types
40 + for (var walletType in availableWalletTypes) {
41 + if (walletType == WalletType.solana) {
42 + continue;
43 + }
44 +
45 + await commonTestFlows.switchToWalletMenuFromDashboardPage();
46 +
47 + await commonTestFlows.restoreWalletFromWalletMenu(
48 + walletType,
49 + commonTestFlows.getWalletSeedsByWalletType(walletType),
50 + );
51 +
52 + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(walletType);
53 + }
54 +
55 + // Goes to the wallet menu and provides a visual confirmation that all the wallets were correctly restored
56 + await commonTestFlows.switchToWalletMenuFromDashboardPage();
57 +
58 + commonTestFlows.confirmAllAvailableWalletTypeIconsDisplayCorrectly();
59 +
60 + await Future.delayed(Duration(seconds: 5));
61 + },
62 + );
63 +}
integration_test/test_suites/send_flow_test.dart
+20 -17
@@ -6,6 +6,7 @@ import '../components/common_test_constants.dart';
6 import '../components/common_test_flows.dart';
7 import '../robots/dashboard_page_robot.dart';
8 import '../robots/send_page_robot.dart';
9 +import 'package:cake_wallet/.secrets.g.dart' as secrets;
10
11 void main() {
12 IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -14,28 +15,30 @@ void main() {
15 CommonTestFlows commonTestFlows;
16 DashboardPageRobot dashboardPageRobot;
17
17 - group('Send Flow Tests', () {
18 - testWidgets('Send flow', (tester) async {
19 - commonTestFlows = CommonTestFlows(tester);
20 - sendPageRobot = SendPageRobot(tester: tester);
21 - dashboardPageRobot = DashboardPageRobot(tester);
18 + testWidgets('Send flow', (tester) async {
19 + commonTestFlows = CommonTestFlows(tester);
20 + sendPageRobot = SendPageRobot(tester: tester);
21 + dashboardPageRobot = DashboardPageRobot(tester);
22
23 - await commonTestFlows.startAppFlow(ValueKey('send_test_app_key'));
24 - await commonTestFlows.restoreWalletThroughSeedsFlow();
25 - await dashboardPageRobot.navigateToSendPage();
23 + await commonTestFlows.startAppFlow(ValueKey('send_test_app_key'));
24 + await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
25 + CommonTestConstants.testWalletType,
26 + secrets.solanaTestWalletSeeds,
27 + CommonTestConstants.pin,
28 + );
29 + await dashboardPageRobot.navigateToSendPage();
30
27 - await sendPageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress);
28 - await sendPageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency);
29 - await sendPageRobot.enterAmount(CommonTestConstants.sendTestAmount);
30 - await sendPageRobot.selectTransactionPriority();
31 + await sendPageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress);
32 + await sendPageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency);
33 + await sendPageRobot.enterAmount(CommonTestConstants.sendTestAmount);
34 + await sendPageRobot.selectTransactionPriority();
35
32 - await sendPageRobot.onSendButtonPressed();
36 + await sendPageRobot.onSendButtonPressed();
37
34 - await sendPageRobot.handleSendResult();
38 + await sendPageRobot.handleSendResult();
39
36 - await sendPageRobot.onSendButtonOnConfirmSendingDialogPressed();
40 + await sendPageRobot.onSendButtonOnConfirmSendingDialogPressed();
41
38 - await sendPageRobot.onSentDialogPopUp();
39 - });
42 + await sendPageRobot.onSentDialogPopUp();
43 });
44 }
integration_test/test_suites/transaction_history_flow_test.dart new
+70
@@ -0,0 +1,70 @@
1 +import 'package:cw_core/wallet_type.dart';
2 +import 'package:flutter/material.dart';
3 +import 'package:flutter_test/flutter_test.dart';
4 +import 'package:integration_test/integration_test.dart';
5 +
6 +import '../components/common_test_constants.dart';
7 +import '../components/common_test_flows.dart';
8 +import '../robots/dashboard_page_robot.dart';
9 +import '../robots/transactions_page_robot.dart';
10 +import 'package:cake_wallet/.secrets.g.dart' as secrets;
11 +
12 +void main() {
13 + IntegrationTestWidgetsFlutterBinding.ensureInitialized();
14 +
15 + CommonTestFlows commonTestFlows;
16 + DashboardPageRobot dashboardPageRobot;
17 + TransactionsPageRobot transactionsPageRobot;
18 +
19 + /// Two Test Scenarios
20 + /// - Fully Synchronizes and display the transaction history either immediately or few seconds after fully synchronizing
21 + /// - Displays the transaction history progressively as synchronizing happens
22 + testWidgets('Transaction history flow', (tester) async {
23 + commonTestFlows = CommonTestFlows(tester);
24 + dashboardPageRobot = DashboardPageRobot(tester);
25 + transactionsPageRobot = TransactionsPageRobot(tester);
26 +
27 + await commonTestFlows.startAppFlow(
28 + ValueKey('confirm_creds_display_correctly_flow_app_key'),
29 + );
30 +
31 + /// Test Scenario 1 - Displays transaction history list after fully synchronizing.
32 + ///
33 + /// For Solana/Tron WalletTypes.
34 + await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
35 + WalletType.solana,
36 + secrets.solanaTestWalletSeeds,
37 + CommonTestConstants.pin,
38 + );
39 +
40 + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana);
41 +
42 + await dashboardPageRobot.swipeDashboardTab(true);
43 +
44 + await transactionsPageRobot.isTransactionsPage();
45 +
46 + await transactionsPageRobot.confirmTransactionsPageConstantsDisplayProperly();
47 +
48 + await transactionsPageRobot.confirmTransactionHistoryListDisplaysCorrectly(false);
49 +
50 + /// Test Scenario 2 - Displays transaction history list while synchronizing.
51 + ///
52 + /// For bitcoin/Monero/Wownero WalletTypes.
53 + await commonTestFlows.switchToWalletMenuFromDashboardPage();
54 +
55 + await commonTestFlows.restoreWalletFromWalletMenu(
56 + WalletType.bitcoin,
57 + secrets.bitcoinTestWalletSeeds,
58 + );
59 +
60 + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.bitcoin);
61 +
62 + await dashboardPageRobot.swipeDashboardTab(true);
63 +
64 + await transactionsPageRobot.isTransactionsPage();
65 +
66 + await transactionsPageRobot.confirmTransactionsPageConstantsDisplayProperly();
67 +
68 + await transactionsPageRobot.confirmTransactionHistoryListDisplaysCorrectly(true);
69 + });
70 +}
ios/Podfile.lock
+6 -6
@@ -109,15 +109,15 @@ PODS:
109 - FlutterMacOS
110 - permission_handler_apple (9.1.1):
111 - Flutter
112 - - Protobuf (3.27.2)
112 + - Protobuf (3.28.2)
113 - ReachabilitySwift (5.2.3)
114 - reactive_ble_mobile (0.0.1):
115 - Flutter
116 - Protobuf (~> 3.5)
117 - SwiftProtobuf (~> 1.0)
118 - - SDWebImage (5.19.4):
119 - - SDWebImage/Core (= 5.19.4)
120 - - SDWebImage/Core (5.19.4)
118 + - SDWebImage (5.19.7):
119 + - SDWebImage/Core (= 5.19.7)
120 + - SDWebImage/Core (5.19.7)
121 - sensitive_clipboard (0.0.1):
122 - Flutter
123 - share_plus (0.0.1):
@@ -271,10 +271,10 @@ SPEC CHECKSUMS:
271 package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
272 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
273 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
274 - Protobuf: fb2c13674723f76ff6eede14f78847a776455fa2
274 + Protobuf: 28c89b24435762f60244e691544ed80f50d82701
275 ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979
276 reactive_ble_mobile: 9ce6723d37ccf701dbffd202d487f23f5de03b4c
277 - SDWebImage: 066c47b573f408f18caa467d71deace7c0f8280d
277 + SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
278 sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
279 share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
280 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
lib/src/screens/InfoPage.dart
+6 -7
@@ -21,6 +21,7 @@ abstract class InfoPage extends BasePage {
21 String get pageTitle;
22 String get pageDescription;
23 String get buttonText;
24 + Key? get buttonKey;
25 void Function(BuildContext) get onPressed;
26
27 @override
@@ -39,15 +40,14 @@ abstract class InfoPage extends BasePage {
40 alignment: Alignment.center,
41 padding: EdgeInsets.all(24),
42 child: ConstrainedBox(
42 - constraints: BoxConstraints(
43 - maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
43 + constraints:
44 + BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
45 child: Column(
46 mainAxisAlignment: MainAxisAlignment.spaceBetween,
47 children: <Widget>[
48 Expanded(
49 child: ConstrainedBox(
49 - constraints: BoxConstraints(
50 - maxHeight: MediaQuery.of(context).size.height * 0.3),
50 + constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.3),
51 child: AspectRatio(aspectRatio: 1, child: image),
52 ),
53 ),
@@ -61,14 +61,13 @@ abstract class InfoPage extends BasePage {
61 height: 1.7,
62 fontSize: 14,
63 fontWeight: FontWeight.normal,
64 - color: Theme.of(context)
65 - .extension<CakeTextTheme>()!
66 - .secondaryTextColor,
64 + color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
65 ),
66 ),
67 ),
68 ),
69 PrimaryButton(
70 + key: buttonKey,
71 onPressed: () => onPressed(context),
72 text: buttonText,
73 color: Theme.of(context).primaryColor,
lib/src/screens/dashboard/dashboard_page.dart
+1 -1
@@ -140,7 +140,7 @@ class _DashboardPageView extends BasePage {
140 bool get resizeToAvoidBottomInset => false;
141
142 @override
143 - Widget get endDrawer => MenuWidget(dashboardViewModel);
143 + Widget get endDrawer => MenuWidget(dashboardViewModel, ValueKey('dashboard_page_drawer_menu_widget_key'));
144
145 @override
146 Widget leading(BuildContext context) {
lib/src/screens/dashboard/pages/nft_details_page.dart
+4 -1
@@ -28,7 +28,10 @@ class NFTDetailsPage extends BasePage {
28 bool get resizeToAvoidBottomInset => false;
29
30 @override
31 - Widget get endDrawer => MenuWidget(dashboardViewModel);
31 + Widget get endDrawer => MenuWidget(
32 + dashboardViewModel,
33 + ValueKey('nft_details_page_menu_widget_key'),
34 + );
35
36 @override
37 Widget trailing(BuildContext context) {
lib/src/screens/dashboard/pages/transactions_page.dart
+126 -106
@@ -1,11 +1,13 @@
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_row.dart';
3 import 'package:cake_wallet/src/screens/dashboard/widgets/order_row.dart';
4 +import 'package:cake_wallet/src/screens/dashboard/widgets/trade_row.dart';
5 import 'package:cake_wallet/themes/extensions/placeholder_theme.dart';
6 import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
7 import 'package:cake_wallet/utils/responsive_layout_util.dart';
8 import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
9 import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
10 +import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
11 import 'package:cw_core/crypto_currency.dart';
12 import 'package:cw_core/sync_status.dart';
13 import 'package:cw_core/wallet_type.dart';
@@ -14,9 +16,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
16 import 'package:flutter_mobx/flutter_mobx.dart';
17 import 'package:cake_wallet/src/screens/dashboard/widgets/header_row.dart';
18 import 'package:cake_wallet/src/screens/dashboard/widgets/date_section_raw.dart';
17 -import 'package:cake_wallet/src/screens/dashboard/widgets/trade_row.dart';
19 import 'package:cake_wallet/src/screens/dashboard/widgets/transaction_raw.dart';
19 -import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
20 import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
21 import 'package:cake_wallet/view_model/dashboard/date_section_item.dart';
22 import 'package:intl/intl.dart';
@@ -49,6 +49,7 @@ class TransactionsPage extends StatelessWidget {
49 return Padding(
50 padding: const EdgeInsets.fromLTRB(24, 0, 24, 8),
51 child: DashBoardRoundedCardWidget(
52 + key: ValueKey('transactions_page_syncing_alert_card_key'),
53 onTap: () {
54 try {
55 final uri = Uri.parse(
@@ -64,82 +65,93 @@ class TransactionsPage extends StatelessWidget {
65 return Container();
66 }
67 }),
67 - HeaderRow(dashboardViewModel: dashboardViewModel),
68 - Expanded(child: Observer(builder: (_) {
69 - final items = dashboardViewModel.items;
70 -
71 - return items.isNotEmpty
72 - ? ListView.builder(
73 - itemCount: items.length,
74 - itemBuilder: (context, index) {
75 - final item = items[index];
76 -
77 - if (item is DateSectionItem) {
78 - return DateSectionRaw(date: item.date);
79 - }
80 -
81 - if (item is TransactionListItem) {
82 - if (item.hasTokens && item.assetOfTransaction == null) {
83 - return Container();
84 - }
85 -
86 - final transaction = item.transaction;
87 - final transactionType = dashboardViewModel.getTransactionType(transaction);
88 -
89 - List<String> tags = [];
90 - if (dashboardViewModel.type == WalletType.bitcoin) {
91 - if (bitcoin!.txIsReceivedSilentPayment(transaction)) {
92 - tags.add(S.of(context).silent_payment);
68 + HeaderRow(
69 + dashboardViewModel: dashboardViewModel,
70 + key: ValueKey('transactions_page_header_row_key'),
71 + ),
72 + Expanded(
73 + child: Observer(
74 + builder: (_) {
75 + final items = dashboardViewModel.items;
76 +
77 + return items.isNotEmpty
78 + ? ListView.builder(
79 + key: ValueKey('transactions_page_list_view_builder_key'),
80 + itemCount: items.length,
81 + itemBuilder: (context, index) {
82 + final item = items[index];
83 +
84 + if (item is DateSectionItem) {
85 + return DateSectionRaw(date: item.date, key: item.key);
86 }
94 - }
95 - if (dashboardViewModel.type == WalletType.litecoin) {
96 - if (bitcoin!.txIsMweb(transaction)) {
97 - tags.add("MWEB");
87 +
88 + if (item is TransactionListItem) {
89 + if (item.hasTokens && item.assetOfTransaction == null) {
90 + return Container();
91 + }
92 +
93 + final transaction = item.transaction;
94 + final transactionType =
95 + dashboardViewModel.getTransactionType(transaction);
96 +
97 + List<String> tags = [];
98 + if (dashboardViewModel.type == WalletType.bitcoin) {
99 + if (bitcoin!.txIsReceivedSilentPayment(transaction)) {
100 + tags.add(S.of(context).silent_payment);
101 + }
102 + }
103 + if (dashboardViewModel.type == WalletType.litecoin) {
104 + if (bitcoin!.txIsMweb(transaction)) {
105 + tags.add("MWEB");
106 + }
107 + }
108 +
109 + return Observer(
110 + builder: (_) => TransactionRow(
111 + key: item.key,
112 + onTap: () => Navigator.of(context)
113 + .pushNamed(Routes.transactionDetails, arguments: transaction),
114 + direction: transaction.direction,
115 + formattedDate: DateFormat('HH:mm').format(transaction.date),
116 + formattedAmount: item.formattedCryptoAmount,
117 + formattedFiatAmount:
118 + dashboardViewModel.balanceViewModel.isFiatDisabled
119 + ? ''
120 + : item.formattedFiatAmount,
121 + isPending: transaction.isPending,
122 + title:
123 + item.formattedTitle + item.formattedStatus + transactionType,
124 + tags: tags,
125 + ),
126 + );
127 }
99 - }
100 -
101 - return Observer(
102 - builder: (_) => TransactionRow(
103 - onTap: () => Navigator.of(context)
104 - .pushNamed(Routes.transactionDetails, arguments: transaction),
105 - direction: transaction.direction,
106 - formattedDate: DateFormat('HH:mm').format(transaction.date),
107 - formattedAmount: item.formattedCryptoAmount,
108 - formattedFiatAmount:
109 - dashboardViewModel.balanceViewModel.isFiatDisabled
110 - ? ''
111 - : item.formattedFiatAmount,
112 - isPending: transaction.isPending,
113 - title:
114 - item.formattedTitle + item.formattedStatus + transactionType,
115 - tags: tags,
116 - ),
117 - );
118 - }
119 -
120 - if (item is AnonpayTransactionListItem) {
121 - final transactionInfo = item.transaction;
122 -
123 - return AnonpayTransactionRow(
124 - onTap: () => Navigator.of(context)
125 - .pushNamed(Routes.anonPayDetailsPage, arguments: transactionInfo),
126 - currency: transactionInfo.fiatAmount != null
127 - ? transactionInfo.fiatEquiv ?? ''
128 - : CryptoCurrency.fromFullName(transactionInfo.coinTo)
129 - .name
130 - .toUpperCase(),
131 - provider: transactionInfo.provider,
132 - amount: transactionInfo.fiatAmount?.toString() ??
133 - (transactionInfo.amountTo?.toString() ?? ''),
134 - createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt),
135 - );
136 - }
137 -
138 - if (item is TradeListItem) {
139 - final trade = item.trade;
140 -
141 - return Observer(
142 - builder: (_) => TradeRow(
128 +
129 + if (item is AnonpayTransactionListItem) {
130 + final transactionInfo = item.transaction;
131 +
132 + return AnonpayTransactionRow(
133 + key: item.key,
134 + onTap: () => Navigator.of(context).pushNamed(
135 + Routes.anonPayDetailsPage,
136 + arguments: transactionInfo),
137 + currency: transactionInfo.fiatAmount != null
138 + ? transactionInfo.fiatEquiv ?? ''
139 + : CryptoCurrency.fromFullName(transactionInfo.coinTo)
140 + .name
141 + .toUpperCase(),
142 + provider: transactionInfo.provider,
143 + amount: transactionInfo.fiatAmount?.toString() ??
144 + (transactionInfo.amountTo?.toString() ?? ''),
145 + createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt),
146 + );
147 + }
148 +
149 + if (item is TradeListItem) {
150 + final trade = item.trade;
151 +
152 + return Observer(
153 + builder: (_) => TradeRow(
154 + key: item.key,
155 onTap: () => Navigator.of(context)
156 .pushNamed(Routes.tradeDetails, arguments: trade),
157 provider: trade.provider,
@@ -148,36 +160,44 @@ class TransactionsPage extends StatelessWidget {
160 createdAtFormattedDate: trade.createdAt != null
161 ? DateFormat('HH:mm').format(trade.createdAt!)
162 : null,
151 - formattedAmount: item.tradeFormattedAmount));
152 - }
153 -
154 - if (item is OrderListItem) {
155 - final order = item.order;
156 -
157 - return Observer(
158 - builder: (_) => OrderRow(
159 - onTap: () => Navigator.of(context)
160 - .pushNamed(Routes.orderDetails, arguments: order),
161 - provider: order.provider,
162 - from: order.from!,
163 - to: order.to!,
164 - createdAtFormattedDate:
165 - DateFormat('HH:mm').format(order.createdAt),
166 - formattedAmount: item.orderFormattedAmount,
167 - ));
168 - }
169 -
170 - return Container(color: Colors.transparent, height: 1);
171 - })
172 - : Center(
173 - child: Text(
174 - S.of(context).placeholder_transactions,
175 - style: TextStyle(
176 - fontSize: 14,
177 - color: Theme.of(context).extension<PlaceholderTheme>()!.color),
178 - ),
179 - );
180 - }))
163 + formattedAmount: item.tradeFormattedAmount,
164 + ),
165 + );
166 + }
167 +
168 + if (item is OrderListItem) {
169 + final order = item.order;
170 +
171 + return Observer(
172 + builder: (_) => OrderRow(
173 + key: item.key,
174 + onTap: () => Navigator.of(context)
175 + .pushNamed(Routes.orderDetails, arguments: order),
176 + provider: order.provider,
177 + from: order.from!,
178 + to: order.to!,
179 + createdAtFormattedDate:
180 + DateFormat('HH:mm').format(order.createdAt),
181 + formattedAmount: item.orderFormattedAmount,
182 + ),
183 + );
184 + }
185 +
186 + return Container(color: Colors.transparent, height: 1);
187 + })
188 + : Center(
189 + child: Text(
190 + key: ValueKey('transactions_page_placeholder_transactions_text_key'),
191 + S.of(context).placeholder_transactions,
192 + style: TextStyle(
193 + fontSize: 14,
194 + color: Theme.of(context).extension<PlaceholderTheme>()!.color,
195 + ),
196 + ),
197 + );
198 + },
199 + ),
200 + )
201 ],
202 ),
203 ),
lib/src/screens/dashboard/widgets/anonpay_transaction_row.dart
+1
@@ -9,6 +9,7 @@ class AnonpayTransactionRow extends StatelessWidget {
9 required this.currency,
10 required this.onTap,
11 required this.amount,
12 + super.key,
13 });
14
15 final VoidCallback? onTap;
lib/src/screens/dashboard/widgets/date_section_raw.dart
+13 -28
@@ -1,42 +1,27 @@
1 import 'package:flutter/material.dart';
2 import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
3 -import 'package:intl/intl.dart';
4 -import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cake_wallet/utils/date_formatter.dart';
4
5 class DateSectionRaw extends StatelessWidget {
8 - DateSectionRaw({required this.date});
6 + DateSectionRaw({required this.date, super.key});
7
8 final DateTime date;
9
10 @override
11 Widget build(BuildContext context) {
14 - final nowDate = DateTime.now();
15 - final diffDays = date.difference(nowDate).inDays;
16 - final isToday = nowDate.day == date.day &&
17 - nowDate.month == date.month &&
18 - nowDate.year == date.year;
19 - final dateSectionDateFormat = DateFormatter.withCurrentLocal(hasTime: false);
20 - var title = "";
21 -
22 - if (isToday) {
23 - title = S.of(context).today;
24 - } else if (diffDays == 0) {
25 - title = S.of(context).yesterday;
26 - } else if (diffDays > -7 && diffDays < 0) {
27 - final dateFormat = DateFormat.EEEE();
28 - title = dateFormat.format(date);
29 - } else {
30 - title = dateSectionDateFormat.format(date);
31 - }
12 + final title = DateFormatter.convertDateTimeToReadableString(date);
13
14 return Container(
34 - height: 35,
35 - alignment: Alignment.center,
36 - color: Colors.transparent,
37 - child: Text(title,
38 - style: TextStyle(
39 - fontSize: 12,
40 - color: Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor)));
15 + height: 35,
16 + alignment: Alignment.center,
17 + color: Colors.transparent,
18 + child: Text(
19 + title,
20 + style: TextStyle(
21 + fontSize: 12,
22 + color: Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor,
23 + ),
24 + ),
25 + );
26 }
27 }
lib/src/screens/dashboard/widgets/header_row.dart
+2 -1
@@ -7,7 +7,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
7 import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
8
9 class HeaderRow extends StatelessWidget {
10 - HeaderRow({required this.dashboardViewModel});
10 + HeaderRow({required this.dashboardViewModel, super.key});
11
12 final DashboardViewModel dashboardViewModel;
13
@@ -34,6 +34,7 @@ class HeaderRow extends StatelessWidget {
34 Semantics(
35 container: true,
36 child: GestureDetector(
37 + key: ValueKey('transactions_page_header_row_transaction_filter_button_key'),
38 onTap: () {
39 showPopUp<void>(
40 context: context,
lib/src/screens/dashboard/widgets/menu_widget.dart
+2 -1
@@ -9,7 +9,7 @@ import 'package:cw_core/wallet_type.dart';
9 import 'package:flutter_mobx/flutter_mobx.dart';
10
11 class MenuWidget extends StatefulWidget {
12 - MenuWidget(this.dashboardViewModel);
12 + MenuWidget(this.dashboardViewModel, Key? key);
13
14 final DashboardViewModel dashboardViewModel;
15
@@ -193,6 +193,7 @@ class MenuWidgetState extends State<MenuWidget> {
193 final isLastTile = index == itemCount - 1;
194
195 return SettingActionButton(
196 + key: item.key,
197 isLastTile: isLastTile,
198 tileHeight: tileHeight,
199 selectionActive: false,
lib/src/screens/dashboard/widgets/order_row.dart
+38 -40
@@ -12,7 +12,10 @@ class OrderRow extends StatelessWidget {
12 required this.to,
13 required this.createdAtFormattedDate,
14 this.onTap,
15 - this.formattedAmount});
15 + this.formattedAmount,
16 + super.key,
17 + });
18 +
19 final VoidCallback? onTap;
20 final BuyProviderDescription provider;
21 final String from;
@@ -22,8 +25,7 @@ class OrderRow extends StatelessWidget {
25
26 @override
27 Widget build(BuildContext context) {
25 - final iconColor =
26 - Theme.of(context).extension<OrderTheme>()!.iconColor;
28 + final iconColor = Theme.of(context).extension<OrderTheme>()!.iconColor;
29
30 final providerIcon = getBuyProviderIcon(provider, iconColor: iconColor);
31
@@ -36,46 +38,42 @@ class OrderRow extends StatelessWidget {
38 mainAxisSize: MainAxisSize.max,
39 crossAxisAlignment: CrossAxisAlignment.center,
40 children: [
39 - if (providerIcon != null) Padding(
40 - padding: EdgeInsets.only(right: 12),
41 - child: providerIcon,
42 - ),
41 + if (providerIcon != null)
42 + Padding(
43 + padding: EdgeInsets.only(right: 12),
44 + child: providerIcon,
45 + ),
46 Expanded(
47 child: Column(
45 - mainAxisSize: MainAxisSize.min,
46 - children: [
47 - Row(
48 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
49 - children: <Widget>[
50 - Text('$from → $to',
51 - style: TextStyle(
52 - fontSize: 16,
53 - fontWeight: FontWeight.w500,
54 - color: Theme.of(context).extension<DashboardPageTheme>()!.textColor
55 - )),
56 - formattedAmount != null
57 - ? Text(formattedAmount! + ' ' + to,
58 - style: TextStyle(
59 - fontSize: 16,
60 - fontWeight: FontWeight.w500,
61 - color: Theme.of(context).extension<DashboardPageTheme>()!.textColor
62 - ))
63 - : Container()
64 - ]),
65 - SizedBox(height: 5),
66 - Row(
67 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
68 - children: <Widget>[
69 - Text(createdAtFormattedDate,
70 - style: TextStyle(
71 - fontSize: 14,
72 - color: Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor))
73 - ])
74 - ],
75 - )
76 - )
48 + mainAxisSize: MainAxisSize.min,
49 + children: [
50 + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
51 + Text('$from → $to',
52 + style: TextStyle(
53 + fontSize: 16,
54 + fontWeight: FontWeight.w500,
55 + color: Theme.of(context).extension<DashboardPageTheme>()!.textColor)),
56 + formattedAmount != null
57 + ? Text(formattedAmount! + ' ' + to,
58 + style: TextStyle(
59 + fontSize: 16,
60 + fontWeight: FontWeight.w500,
61 + color:
62 + Theme.of(context).extension<DashboardPageTheme>()!.textColor))
63 + : Container()
64 + ]),
65 + SizedBox(height: 5),
66 + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
67 + Text(createdAtFormattedDate,
68 + style: TextStyle(
69 + fontSize: 14,
70 + color:
71 + Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor))
72 + ])
73 + ],
74 + ))
75 ],
76 ),
77 ));
78 }
81 -}
\ No newline at end of file
79 +}
lib/src/screens/dashboard/widgets/trade_row.dart
+1
@@ -13,6 +13,7 @@ class TradeRow extends StatelessWidget {
13 required this.createdAtFormattedDate,
14 this.onTap,
15 this.formattedAmount,
16 + super.key,
17 });
18
19 final VoidCallback? onTap;
lib/src/screens/dashboard/widgets/transaction_raw.dart
+59 -44
@@ -14,6 +14,7 @@ class TransactionRow extends StatelessWidget {
14 required this.tags,
15 required this.title,
16 required this.onTap,
17 + super.key,
18 });
19
20 final VoidCallback onTap;
@@ -28,33 +29,36 @@ class TransactionRow extends StatelessWidget {
29 @override
30 Widget build(BuildContext context) {
31 return InkWell(
31 - onTap: onTap,
32 - child: Container(
33 - padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
34 - color: Colors.transparent,
35 - child: Row(
36 - mainAxisSize: MainAxisSize.max,
37 - crossAxisAlignment: CrossAxisAlignment.center,
38 - children: [
39 - Container(
40 - height: 36,
41 - width: 36,
42 - decoration: BoxDecoration(
43 - shape: BoxShape.circle,
44 - color: Theme.of(context).extension<TransactionTradeTheme>()!.rowsColor),
45 - child: Image.asset(direction == TransactionDirection.incoming
46 - ? 'assets/images/down_arrow.png'
47 - : 'assets/images/up_arrow.png'),
48 - ),
49 - SizedBox(width: 12),
50 - Expanded(
51 - child: Column(
32 + onTap: onTap,
33 + child: Container(
34 + padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
35 + color: Colors.transparent,
36 + child: Row(
37 + mainAxisSize: MainAxisSize.max,
38 + crossAxisAlignment: CrossAxisAlignment.center,
39 + children: [
40 + Container(
41 + height: 36,
42 + width: 36,
43 + decoration: BoxDecoration(
44 + shape: BoxShape.circle,
45 + color: Theme.of(context).extension<TransactionTradeTheme>()!.rowsColor),
46 + child: Image.asset(direction == TransactionDirection.incoming
47 + ? 'assets/images/down_arrow.png'
48 + : 'assets/images/up_arrow.png'),
49 + ),
50 + SizedBox(width: 12),
51 + Expanded(
52 + child: Column(
53 mainAxisSize: MainAxisSize.min,
54 children: [
54 - Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
55 - Row(
56 - children: [
57 - Text(title,
55 + Row(
56 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
57 + children: <Widget>[
58 + Row(
59 + children: [
60 + Text(
61 + title,
62 style: TextStyle(
63 fontSize: 16,
64 fontWeight: FontWeight.w500,
@@ -65,28 +69,39 @@ class TransactionRow extends StatelessWidget {
69 ),
70 Text(formattedAmount,
71 style: TextStyle(
68 - fontSize: 16,
69 - fontWeight: FontWeight.w500,
70 - color: Theme.of(context).extension<DashboardPageTheme>()!.textColor))
71 - ]),
72 + fontSize: 16,
73 + fontWeight: FontWeight.w500,
74 + color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
75 + ),
76 + )
77 + ],
78 + ),
79 SizedBox(height: 5),
73 - Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
74 - Text(formattedDate,
75 - style: TextStyle(
76 - fontSize: 14,
77 - color:
78 - Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor)),
79 - Text(formattedFiatAmount,
80 + Row(
81 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
82 + children: <Widget>[
83 + Text(formattedDate,
84 + style: TextStyle(
85 + fontSize: 14,
86 + color: Theme.of(context)
87 + .extension<CakeTextTheme>()!
88 + .dateSectionRowColor)),
89 + Text(
90 + formattedFiatAmount,
91 style: TextStyle(
81 - fontSize: 14,
82 - color:
83 - Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor))
84 - ])
92 + fontSize: 14,
93 + color: Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor,
94 + ),
95 + )
96 + ],
97 + ),
98 ],
86 - ))
87 - ],
88 - ),
89 - ));
99 + ),
100 + )
101 + ],
102 + ),
103 + ),
104 + );
105 }
106 }
107
lib/src/screens/new_wallet/new_wallet_page.dart
+14 -4
@@ -112,10 +112,13 @@ class _WalletNameFormState extends State<WalletNameForm> {
112 context: context,
113 builder: (_) {
114 return AlertWithOneAction(
115 - alertTitle: S.current.new_wallet,
116 - alertContent: state.error,
117 - buttonText: S.of(context).ok,
118 - buttonAction: () => Navigator.of(context).pop());
115 + key: ValueKey('new_wallet_page_failure_dialog_key'),
116 + buttonKey: ValueKey('new_wallet_page_failure_dialog_button_key'),
117 + alertTitle: S.current.new_wallet,
118 + alertContent: state.error,
119 + buttonText: S.of(context).ok,
120 + buttonAction: () => Navigator.of(context).pop(),
121 + );
122 });
123 }
124 });
@@ -152,6 +155,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
155 child: Column(
156 children: [
157 TextFormField(
158 + key: ValueKey('new_wallet_page_wallet_name_textformfield_key'),
159 onChanged: (value) => _walletNewVM.name = value,
160 controller: _nameController,
161 textAlign: TextAlign.center,
@@ -182,6 +186,8 @@ class _WalletNameFormState extends State<WalletNameForm> {
186 suffixIcon: Semantics(
187 label: S.of(context).generate_name,
188 child: IconButton(
189 + key: ValueKey(
190 + 'new_wallet_page_wallet_name_textformfield_generate_name_button_key'),
191 onPressed: () async {
192 final rName = await generateName();
193 FocusManager.instance.primaryFocus?.unfocus();
@@ -297,6 +303,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
303 builder: (BuildContext build) => Padding(
304 padding: EdgeInsets.only(top: 24),
305 child: SelectButton(
306 + key: ValueKey('new_wallet_page_monero_seed_type_button_key'),
307 text: widget._seedSettingsViewModel.moneroSeedType.title,
308 onTap: () async {
309 await showPopUp<void>(
@@ -318,6 +325,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
325 padding: EdgeInsets.only(top: 10),
326 child: SeedLanguageSelector(
327 key: _languageSelectorKey,
328 + buttonKey: ValueKey('new_wallet_page_seed_language_selector_button_key'),
329 initialSelected: defaultSeedLanguage,
330 seedType: _walletNewVM.hasSeedType
331 ? widget._seedSettingsViewModel.moneroSeedType
@@ -336,6 +344,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
344 Observer(
345 builder: (context) {
346 return LoadingPrimaryButton(
347 + key: ValueKey('new_wallet_page_confirm_button_key'),
348 onPressed: _confirmForm,
349 text: S.of(context).seed_language_next,
350 color: Colors.green,
@@ -347,6 +356,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
356 ),
357 const SizedBox(height: 25),
358 GestureDetector(
359 + key: ValueKey('new_wallet_page_advanced_settings_button_key'),
360 onTap: () {
361 Navigator.of(context).pushNamed(Routes.advancedPrivacySettings, arguments: {
362 "type": _walletNewVM.type,
lib/src/screens/new_wallet/wallet_group_description_page.dart
+2
@@ -66,6 +66,7 @@ class WalletGroupDescriptionPage extends BasePage {
66 ),
67 ),
68 PrimaryButton(
69 + key: ValueKey('wallet_group_description_page_create_new_seed_button_key'),
70 onPressed: () => Navigator.of(context).pushNamed(
71 Routes.newWallet,
72 arguments: NewWalletArguments(type: selectedWalletType),
@@ -76,6 +77,7 @@ class WalletGroupDescriptionPage extends BasePage {
77 ),
78 SizedBox(height: 12),
79 PrimaryButton(
80 + key: ValueKey('wallet_group_description_page_choose_wallet_group_button_key'),
81 onPressed: () => Navigator.of(context).pushNamed(
82 Routes.walletGroupsDisplayPage,
83 arguments: selectedWalletType,
lib/src/screens/restore/restore_options_page.dart
+20 -18
@@ -56,7 +56,8 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
56 }
57
58 if (isMoneroOnly) {
59 - return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS).isNotEmpty;
59 + return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
60 + .isNotEmpty;
61 }
62
63 return true;
@@ -80,13 +81,12 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
81 child: Column(
82 children: <Widget>[
83 OptionTile(
83 - key: ValueKey('restore_options_from_seeds_button_key'),
84 - onPressed: () =>
85 - Navigator.pushNamed(
86 - context,
87 - Routes.restoreWalletFromSeedKeys,
88 - arguments: widget.isNewInstall,
89 - ),
84 + key: ValueKey('restore_options_from_seeds_or_keys_button_key'),
85 + onPressed: () => Navigator.pushNamed(
86 + context,
87 + Routes.restoreWalletFromSeedKeys,
88 + arguments: widget.isNewInstall,
89 + ),
90 image: imageSeedKeys,
91 title: S.of(context).restore_title_from_seed_keys,
92 description: S.of(context).restore_description_from_seed_keys,
@@ -107,7 +107,8 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
107 padding: EdgeInsets.only(top: 24),
108 child: OptionTile(
109 key: ValueKey('restore_options_from_hardware_wallet_button_key'),
110 - onPressed: () => Navigator.pushNamed(context, Routes.restoreWalletFromHardwareWallet,
110 + onPressed: () => Navigator.pushNamed(
111 + context, Routes.restoreWalletFromHardwareWallet,
112 arguments: widget.isNewInstall),
113 image: imageLedger,
114 title: S.of(context).restore_title_from_hardware_wallet,
@@ -120,9 +121,9 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
121 key: ValueKey('restore_options_from_qr_button_key'),
122 onPressed: () => _onScanQRCode(context),
123 icon: Icon(
123 - Icons.qr_code_rounded,
124 - color: imageColor,
125 - size: 50,
124 + Icons.qr_code_rounded,
125 + color: imageColor,
126 + size: 50,
127 ),
128 title: S.of(context).scan_qr_code,
129 description: S.of(context).cold_or_recover_wallet),
@@ -149,20 +150,20 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
150 buttonAction: () => Navigator.of(context).pop());
151 });
152 });
152 -
153 }
154
155 Future<void> _onScanQRCode(BuildContext context) async {
156 - final isCameraPermissionGranted = await PermissionHandler.checkPermission(Permission.camera, context);
156 + final isCameraPermissionGranted =
157 + await PermissionHandler.checkPermission(Permission.camera, context);
158
159 if (!isCameraPermissionGranted) return;
160 bool isPinSet = false;
161 if (widget.isNewInstall) {
162 await Navigator.pushNamed(context, Routes.setupPin,
163 arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
163 - setupPinContext.close();
164 - isPinSet = true;
165 - });
164 + setupPinContext.close();
165 + isPinSet = true;
166 + });
167 }
168 if (!widget.isNewInstall || isPinSet) {
169 try {
@@ -174,7 +175,8 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
175 });
176 final restoreWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
177
177 - final restoreFromQRViewModel = getIt.get<WalletRestorationFromQRVM>(param1: restoreWallet.type);
178 + final restoreFromQRViewModel =
179 + getIt.get<WalletRestorationFromQRVM>(param1: restoreWallet.type);
180
181 await restoreFromQRViewModel.create(restoreWallet: restoreWallet);
182 if (restoreFromQRViewModel.state is FailureState) {
lib/src/screens/restore/wallet_restore_from_seed_form.dart
+2
@@ -191,6 +191,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
191 ),
192 if (widget.type == WalletType.monero || widget.type == WalletType.wownero)
193 GestureDetector(
194 + key: ValueKey('wallet_restore_from_seed_seedtype_picker_button_key'),
195 onTap: () async {
196 await showPopUp<void>(
197 context: context,
@@ -264,6 +265,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
265 BlockchainHeightWidget(
266 focusNode: widget.blockHeightFocusNode,
267 key: blockchainHeightKey,
268 + blockHeightTextFieldKey: ValueKey('wallet_restore_from_seed_blockheight_textfield_key'),
269 onHeightOrDateEntered: widget.onHeightOrDateEntered,
270 hasDatePicker: widget.type == WalletType.monero || widget.type == WalletType.wownero,
271 walletType: widget.type,
lib/src/screens/seed/pre_seed_page.dart
+6 -4
@@ -15,13 +15,15 @@ class PreSeedPage extends InfoPage {
15 String get pageTitle => S.current.pre_seed_title;
16
17 @override
18 - String get pageDescription =>
19 - S.current.pre_seed_description(seedPhraseLength.toString());
18 + String get pageDescription => S.current.pre_seed_description(seedPhraseLength.toString());
19
20 @override
21 String get buttonText => S.current.pre_seed_button_text;
22
23 @override
25 - void Function(BuildContext) get onPressed => (BuildContext context) =>
26 - Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true);
24 + Key? get buttonKey => ValueKey('pre_seed_page_button_key');
25 +
26 + @override
27 + void Function(BuildContext) get onPressed =>
28 + (BuildContext context) => Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true);
29 }
lib/src/screens/seed/wallet_seed_page.dart
+60 -35
@@ -33,16 +33,22 @@ class WalletSeedPage extends BasePage {
33 void onClose(BuildContext context) async {
34 if (isNewWalletCreated) {
35 final confirmed = await showPopUp<bool>(
36 - context: context,
37 - builder: (BuildContext context) {
38 - return AlertWithTwoActions(
39 - alertTitle: S.of(context).seed_alert_title,
40 - alertContent: S.of(context).seed_alert_content,
41 - leftButtonText: S.of(context).seed_alert_back,
42 - rightButtonText: S.of(context).seed_alert_yes,
43 - actionLeftButton: () => Navigator.of(context).pop(false),
44 - actionRightButton: () => Navigator.of(context).pop(true));
45 - }) ??
36 + context: context,
37 + builder: (BuildContext context) {
38 + return AlertWithTwoActions(
39 + alertDialogKey: ValueKey('wallet_seed_page_seed_alert_dialog_key'),
40 + alertRightActionButtonKey:
41 + ValueKey('wallet_seed_page_seed_alert_confirm_button_key'),
42 + alertLeftActionButtonKey: ValueKey('wallet_seed_page_seed_alert_back_button_key'),
43 + alertTitle: S.of(context).seed_alert_title,
44 + alertContent: S.of(context).seed_alert_content,
45 + leftButtonText: S.of(context).seed_alert_back,
46 + rightButtonText: S.of(context).seed_alert_yes,
47 + actionLeftButton: () => Navigator.of(context).pop(false),
48 + actionRightButton: () => Navigator.of(context).pop(true),
49 + );
50 + },
51 + ) ??
52 false;
53
54 if (confirmed) {
@@ -62,6 +68,7 @@ class WalletSeedPage extends BasePage {
68 Widget trailing(BuildContext context) {
69 return isNewWalletCreated
70 ? GestureDetector(
71 + key: ValueKey('wallet_seed_page_next_button_key'),
72 onTap: () => onClose(context),
73 child: Container(
74 width: 100,
@@ -74,9 +81,9 @@ class WalletSeedPage extends BasePage {
81 child: Text(
82 S.of(context).seed_language_next,
83 style: TextStyle(
77 - fontSize: 14, fontWeight: FontWeight.w600, color: Theme.of(context)
78 - .extension<CakeTextTheme>()!
79 - .buttonTextColor),
84 + fontSize: 14,
85 + fontWeight: FontWeight.w600,
86 + color: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor),
87 ),
88 ),
89 )
@@ -93,7 +100,8 @@ class WalletSeedPage extends BasePage {
100 padding: EdgeInsets.all(24),
101 alignment: Alignment.center,
102 child: ConstrainedBox(
96 - constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
103 + constraints:
104 + BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
105 child: Column(
106 mainAxisAlignment: MainAxisAlignment.spaceBetween,
107 children: <Widget>[
@@ -106,6 +114,7 @@ class WalletSeedPage extends BasePage {
114 crossAxisAlignment: CrossAxisAlignment.center,
115 children: <Widget>[
116 Text(
117 + key: ValueKey('wallet_seed_page_wallet_name_text_key'),
118 walletSeedViewModel.name,
119 style: TextStyle(
120 fontSize: 20,
@@ -115,12 +124,14 @@ class WalletSeedPage extends BasePage {
124 Padding(
125 padding: EdgeInsets.only(top: 20, left: 16, right: 16),
126 child: Text(
127 + key: ValueKey('wallet_seed_page_wallet_seed_text_key'),
128 walletSeedViewModel.seed,
129 textAlign: TextAlign.center,
130 style: TextStyle(
131 fontSize: 14,
132 fontWeight: FontWeight.normal,
123 - color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
133 + color:
134 + Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
135 ),
136 )
137 ],
@@ -132,12 +143,18 @@ class WalletSeedPage extends BasePage {
143 ? Padding(
144 padding: EdgeInsets.only(bottom: 43, left: 43, right: 43),
145 child: Text(
146 + key: ValueKey(
147 + 'wallet_seed_page_wallet_seed_reminder_text_key',
148 + ),
149 S.of(context).seed_reminder,
150 textAlign: TextAlign.center,
151 style: TextStyle(
138 - fontSize: 12,
139 - fontWeight: FontWeight.normal,
140 - color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor),
152 + fontSize: 12,
153 + fontWeight: FontWeight.normal,
154 + color: Theme.of(context)
155 + .extension<TransactionTradeTheme>()!
156 + .detailsTitlesColor,
157 + ),
158 ),
159 )
160 : Offstage(),
@@ -145,9 +162,10 @@ class WalletSeedPage extends BasePage {
162 mainAxisSize: MainAxisSize.max,
163 children: <Widget>[
164 Flexible(
148 - child: Container(
149 - padding: EdgeInsets.only(right: 8.0),
150 - child: PrimaryButton(
165 + child: Container(
166 + padding: EdgeInsets.only(right: 8.0),
167 + child: PrimaryButton(
168 + key: ValueKey('wallet_seed_page_save_seeds_button_key'),
169 onPressed: () {
170 ShareUtil.share(
171 text: walletSeedViewModel.seed,
@@ -156,22 +174,29 @@ class WalletSeedPage extends BasePage {
174 },
175 text: S.of(context).save,
176 color: Colors.green,
159 - textColor: Colors.white),
160 - )),
177 + textColor: Colors.white,
178 + ),
179 + ),
180 + ),
181 Flexible(
162 - child: Container(
163 - padding: EdgeInsets.only(left: 8.0),
164 - child: Builder(
182 + child: Container(
183 + padding: EdgeInsets.only(left: 8.0),
184 + child: Builder(
185 builder: (context) => PrimaryButton(
166 - onPressed: () {
167 - ClipboardUtil.setSensitiveDataToClipboard(
168 - ClipboardData(text: walletSeedViewModel.seed));
169 - showBar<void>(context, S.of(context).copied_to_clipboard);
170 - },
171 - text: S.of(context).copy,
172 - color: Theme.of(context).extension<PinCodeTheme>()!.indicatorsColor,
173 - textColor: Colors.white)),
174 - ))
186 + key: ValueKey('wallet_seed_page_copy_seeds_button_key'),
187 + onPressed: () {
188 + ClipboardUtil.setSensitiveDataToClipboard(
189 + ClipboardData(text: walletSeedViewModel.seed),
190 + );
191 + showBar<void>(context, S.of(context).copied_to_clipboard);
192 + },
193 + text: S.of(context).copy,
194 + color: Theme.of(context).extension<PinCodeTheme>()!.indicatorsColor,
195 + textColor: Colors.white,
196 + ),
197 + ),
198 + ),
199 + )
200 ],
201 )
202 ],
lib/src/screens/settings/security_backup_page.dart
+30 -20
@@ -16,7 +16,8 @@ import 'package:flutter/material.dart';
16 import 'package:flutter_mobx/flutter_mobx.dart';
17
18 class SecurityBackupPage extends BasePage {
19 - SecurityBackupPage(this._securitySettingsViewModel, this._authService, [this._isHardwareWallet = false]);
19 + SecurityBackupPage(this._securitySettingsViewModel, this._authService,
20 + [this._isHardwareWallet = false]);
21
22 final AuthService _authService;
23
@@ -30,10 +31,13 @@ class SecurityBackupPage extends BasePage {
31 @override
32 Widget body(BuildContext context) {
33 return Container(
33 - padding: EdgeInsets.only(top: 10),
34 - child: Column(mainAxisSize: MainAxisSize.min, children: [
34 + padding: EdgeInsets.only(top: 10),
35 + child: Column(
36 + mainAxisSize: MainAxisSize.min,
37 + children: [
38 if (!_isHardwareWallet)
39 SettingsCellWithArrow(
40 + key: ValueKey('security_backup_page_show_keys_button_key'),
41 title: S.current.show_keys,
42 handler: (_) => _authService.authenticateAction(
43 context,
@@ -44,15 +48,17 @@ class SecurityBackupPage extends BasePage {
48 ),
49 if (!SettingsStoreBase.walletPasswordDirectInput)
50 SettingsCellWithArrow(
51 + key: ValueKey('security_backup_page_create_backup_button_key'),
52 title: S.current.create_backup,
53 handler: (_) => _authService.authenticateAction(
54 context,
55 route: Routes.backup,
51 - conditionToDetermineIfToUse2FA: _securitySettingsViewModel
52 - .shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
56 + conditionToDetermineIfToUse2FA:
57 + _securitySettingsViewModel.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
58 ),
59 ),
60 SettingsCellWithArrow(
61 + key: ValueKey('security_backup_page_change_pin_button_key'),
62 title: S.current.settings_change_pin,
63 handler: (_) => _authService.authenticateAction(
64 context,
@@ -60,28 +66,30 @@ class SecurityBackupPage extends BasePage {
66 arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
67 setupPinContext.close();
68 },
63 - conditionToDetermineIfToUse2FA: _securitySettingsViewModel
64 - .shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
69 + conditionToDetermineIfToUse2FA:
70 + _securitySettingsViewModel.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
71 ),
72 ),
73 if (DeviceInfo.instance.isMobile || Platform.isMacOS || Platform.isLinux)
74 Observer(builder: (_) {
75 return SettingsSwitcherCell(
76 + key: ValueKey('security_backup_page_allow_biometrics_button_key'),
77 title: S.current.settings_allow_biometrical_authentication,
78 value: _securitySettingsViewModel.allowBiometricalAuthentication,
79 onValueChange: (BuildContext context, bool value) {
80 if (value) {
74 - _authService.authenticateAction(context,
75 - onAuthSuccess: (isAuthenticatedSuccessfully) async {
76 - if (isAuthenticatedSuccessfully) {
77 - if (await _securitySettingsViewModel.biometricAuthenticated()) {
81 + _authService.authenticateAction(
82 + context,
83 + onAuthSuccess: (isAuthenticatedSuccessfully) async {
84 + if (isAuthenticatedSuccessfully) {
85 + if (await _securitySettingsViewModel.biometricAuthenticated()) {
86 + _securitySettingsViewModel
87 + .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
88 + }
89 + } else {
90 _securitySettingsViewModel
91 .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
92 }
81 - } else {
82 - _securitySettingsViewModel
83 - .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
84 - }
93 },
94 conditionToDetermineIfToUse2FA: _securitySettingsViewModel
95 .shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
@@ -93,6 +101,7 @@ class SecurityBackupPage extends BasePage {
101 }),
102 Observer(builder: (_) {
103 return SettingsPickerCell<PinCodeRequiredDuration>(
104 + key: ValueKey('security_backup_page_require_pin_after_button_key'),
105 title: S.current.require_pin_after,
106 items: PinCodeRequiredDuration.values,
107 selectedItem: _securitySettingsViewModel.pinCodeRequiredDuration,
@@ -104,14 +113,15 @@ class SecurityBackupPage extends BasePage {
113 Observer(
114 builder: (context) {
115 return SettingsCellWithArrow(
116 + key: ValueKey('security_backup_page_totp_2fa_button_key'),
117 title: _securitySettingsViewModel.useTotp2FA
118 ? S.current.modify_2fa
119 : S.current.setup_2fa,
110 - handler: (_) => _authService.authenticateAction(
111 - context,
112 - route: _securitySettingsViewModel.useTotp2FA
113 - ? Routes.modify2FAPage
114 - : Routes.setup2faInfoPage,
120 + handler: (_) => _authService.authenticateAction(
121 + context,
122 + route: _securitySettingsViewModel.useTotp2FA
123 + ? Routes.modify2FAPage
124 + : Routes.setup2faInfoPage,
125 conditionToDetermineIfToUse2FA: _securitySettingsViewModel
126 .shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
127 ),
lib/src/screens/settings/widgets/settings_cell_with_arrow.dart
+5 -2
@@ -3,8 +3,11 @@ import 'package:cake_wallet/src/widgets/standard_list.dart';
3 import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
4
5 class SettingsCellWithArrow extends StandardListRow {
6 - SettingsCellWithArrow({required String title, required Function(BuildContext context)? handler})
7 - : super(title: title, isSelected: false, onTap: handler);
6 + SettingsCellWithArrow({
7 + required String title,
8 + required Function(BuildContext context)? handler,
9 + Key? key,
10 + }) : super(title: title, isSelected: false, onTap: handler, key: key);
11
12 @override
13 Widget buildTrailing(BuildContext context) => Image.asset('assets/images/select_arrow.png',
lib/src/screens/settings/widgets/settings_picker_cell.dart
+13 -11
@@ -5,19 +5,21 @@ import 'package:cake_wallet/src/widgets/picker.dart';
5 import 'package:cake_wallet/src/widgets/standard_list.dart';
6
7 class SettingsPickerCell<ItemType> extends StandardListRow {
8 - SettingsPickerCell(
9 - {required String title,
10 - required this.selectedItem,
11 - required this.items,
12 - this.displayItem,
13 - this.images,
14 - this.searchHintText,
15 - this.isGridView = false,
16 - this.matchingCriteria,
17 - this.onItemSelected})
18 - : super(
8 + SettingsPickerCell({
9 + required String title,
10 + required this.selectedItem,
11 + required this.items,
12 + this.displayItem,
13 + this.images,
14 + this.searchHintText,
15 + this.isGridView = false,
16 + this.matchingCriteria,
17 + this.onItemSelected,
18 + Key? key,
19 + }) : super(
20 title: title,
21 isSelected: false,
22 + key: key,
23 onTap: (BuildContext context) async {
24 final selectedAtIndex = items.indexOf(selectedItem);
25
lib/src/screens/settings/widgets/settings_switcher_cell.dart
+2 -1
@@ -10,7 +10,8 @@ class SettingsSwitcherCell extends StandardListRow {
10 Decoration? decoration,
11 this.leading,
12 void Function(BuildContext context)? onTap,
13 - }) : super(title: title, isSelected: false, decoration: decoration, onTap: onTap);
13 + Key? key,
14 + }) : super(title: title, isSelected: false, decoration: decoration, onTap: onTap, key: key);
15
16 final bool value;
17 final void Function(BuildContext context, bool value)? onValueChange;
lib/src/screens/setup_2fa/setup_2fa_info_page.dart
+5 -3
@@ -4,7 +4,6 @@ import 'package:cake_wallet/src/screens/InfoPage.dart';
4 import 'package:flutter/cupertino.dart';
5
6 class Setup2FAInfoPage extends InfoPage {
7 -
7 @override
8 String get pageTitle => S.current.pre_seed_title;
9
@@ -15,6 +14,9 @@ class Setup2FAInfoPage extends InfoPage {
14 String get buttonText => S.current.understand;
15
16 @override
18 - void Function(BuildContext) get onPressed => (BuildContext context) =>
19 - Navigator.of(context).popAndPushNamed(Routes.setup_2faPage);
17 + Key? get buttonKey => ValueKey('setup_2fa_info_page_button_key');
18 +
19 + @override
20 + void Function(BuildContext) get onPressed =>
21 + (BuildContext context) => Navigator.of(context).popAndPushNamed(Routes.setup_2faPage);
22 }
lib/src/screens/transaction_details/blockexplorer_list_item.dart
+7 -2
@@ -1,7 +1,12 @@
1 import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
2 +import 'package:flutter/foundation.dart';
3
4 class BlockExplorerListItem extends TransactionDetailsListItem {
4 - BlockExplorerListItem({required String title, required String value, required this.onTap})
5 - : super(title: title, value: value);
5 + BlockExplorerListItem({
6 + required String title,
7 + required String value,
8 + required this.onTap,
9 + Key? key,
10 + }) : super(title: title, value: value, key: key);
11 final Function() onTap;
12 }
lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart
+14 -12
@@ -1,18 +1,20 @@
1 import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
2 +import 'package:flutter/widgets.dart';
3
4 class StandardPickerListItem<T> extends TransactionDetailsListItem {
4 - StandardPickerListItem(
5 - {required String title,
6 - required String value,
7 - required this.items,
8 - required this.displayItem,
9 - required this.onSliderChanged,
10 - required this.onItemSelected,
11 - required this.selectedIdx,
12 - required this.customItemIndex,
13 - this.maxValue,
14 - required this.customValue})
15 - : super(title: title, value: value);
5 + StandardPickerListItem({
6 + required String title,
7 + required String value,
8 + required this.items,
9 + required this.displayItem,
10 + required this.onSliderChanged,
11 + required this.onItemSelected,
12 + required this.selectedIdx,
13 + required this.customItemIndex,
14 + this.maxValue,
15 + required this.customValue,
16 + Key? key,
17 + }) : super(title: title, value: value, key: key);
18
19 final List<T> items;
20 final String Function(T item, double sliderValue) displayItem;
lib/src/screens/transaction_details/standart_list_item.dart
+5 -2
@@ -1,6 +1,9 @@
1 import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
2
3 class StandartListItem extends TransactionDetailsListItem {
4 - StandartListItem({required String title, required String value})
5 - : super(title: title, value: value);
4 + StandartListItem({
5 + required String super.title,
6 + required String super.value,
7 + super.key,
8 + });
9 }
lib/src/screens/transaction_details/textfield_list_item.dart
+8 -2
@@ -1,11 +1,17 @@
1 import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
2 +import 'package:flutter/foundation.dart';
3
4 class TextFieldListItem extends TransactionDetailsListItem {
5 TextFieldListItem({
6 required String title,
7 required String value,
7 - required this.onSubmitted})
8 - : super(title: title, value: value);
8 + required this.onSubmitted,
9 + Key? key,
10 + }) : super(
11 + title: title,
12 + value: value,
13 + key: key,
14 + );
15
16 final Function(String value) onSubmitted;
17 }
\ No newline at end of file
lib/src/screens/transaction_details/transaction_details_list_item.dart
+5 -2
@@ -1,6 +1,9 @@
1 +import 'package:flutter/foundation.dart';
2 +
3 abstract class TransactionDetailsListItem {
2 - TransactionDetailsListItem({required this.title, required this.value});
4 + TransactionDetailsListItem({required this.title, required this.value, this.key});
5
6 final String title;
7 final String value;
6 -}
\ No newline at end of file
8 + final Key? key;
9 +}
lib/src/screens/transaction_details/transaction_details_page.dart
+32 -28
@@ -33,38 +33,42 @@ class TransactionDetailsPage extends BasePage {
33 children: [
34 Expanded(
35 child: SectionStandardList(
36 - sectionCount: 1,
37 - itemCounter: (int _) => transactionDetailsViewModel.items.length,
38 - itemBuilder: (__, index) {
39 - final item = transactionDetailsViewModel.items[index];
36 + sectionCount: 1,
37 + itemCounter: (int _) => transactionDetailsViewModel.items.length,
38 + itemBuilder: (__, index) {
39 + final item = transactionDetailsViewModel.items[index];
40
41 - if (item is StandartListItem) {
42 - return GestureDetector(
43 - onTap: () {
44 - Clipboard.setData(ClipboardData(text: item.value));
45 - showBar<void>(context, S.of(context).transaction_details_copied(item.title));
46 - },
47 - child: ListRow(title: '${item.title}:', value: item.value),
48 - );
49 - }
41 + if (item is StandartListItem) {
42 + return GestureDetector(
43 + key: item.key,
44 + onTap: () {
45 + Clipboard.setData(ClipboardData(text: item.value));
46 + showBar<void>(context, S.of(context).transaction_details_copied(item.title));
47 + },
48 + child: ListRow(title: '${item.title}:', value: item.value),
49 + );
50 + }
51
51 - if (item is BlockExplorerListItem) {
52 - return GestureDetector(
53 - onTap: item.onTap,
54 - child: ListRow(title: '${item.title}:', value: item.value),
55 - );
56 - }
52 + if (item is BlockExplorerListItem) {
53 + return GestureDetector(
54 + key: item.key,
55 + onTap: item.onTap,
56 + child: ListRow(title: '${item.title}:', value: item.value),
57 + );
58 + }
59
58 - if (item is TextFieldListItem) {
59 - return TextFieldListRow(
60 - title: item.title,
61 - value: item.value,
62 - onSubmitted: item.onSubmitted,
63 - );
64 - }
60 + if (item is TextFieldListItem) {
61 + return TextFieldListRow(
62 + key: item.key,
63 + title: item.title,
64 + value: item.value,
65 + onSubmitted: item.onSubmitted,
66 + );
67 + }
68
66 - return Container();
67 - }),
69 + return Container();
70 + },
71 + ),
72 ),
73 Observer(
74 builder: (_) {
lib/src/screens/transaction_details/transaction_expandable_list_item.dart
+7 -2
@@ -1,7 +1,12 @@
1 import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
2 +import 'package:flutter/foundation.dart';
3
4 class StandardExpandableListItem<T> extends TransactionDetailsListItem {
4 - StandardExpandableListItem({required String title, required this.expandableItems})
5 - : super(title: title, value: '');
5 + StandardExpandableListItem({
6 + required String title,
7 + required this.expandableItems,
8 + Key? key,
9 + }) : super(title: title, value: '', key: key);
10 +
11 final List<T> expandableItems;
12 }
lib/src/screens/transaction_details/widgets/textfield_list_row.dart
+8 -6
@@ -4,12 +4,14 @@ import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
4 import 'package:flutter/material.dart';
5
6 class TextFieldListRow extends StatefulWidget {
7 - TextFieldListRow(
8 - {required this.title,
9 - required this.value,
10 - this.titleFontSize = 14,
11 - this.valueFontSize = 16,
12 - this.onSubmitted});
7 + TextFieldListRow({
8 + required this.title,
9 + required this.value,
10 + this.titleFontSize = 14,
11 + this.valueFontSize = 16,
12 + this.onSubmitted,
13 + super.key,
14 + });
15
16 final String title;
17 final String value;
lib/src/screens/wallet_keys/wallet_keys_page.dart
+20 -16
@@ -25,23 +25,25 @@ class WalletKeysPage extends BasePage {
25
26 @override
27 Widget trailing(BuildContext context) => IconButton(
28 - onPressed: () async {
29 - final url = await walletKeysViewModel.url;
28 + key: ValueKey('wallet_keys_page_fullscreen_qr_button_key'),
29 + onPressed: () async {
30 + final url = await walletKeysViewModel.url;
31
31 - BrightnessUtil.changeBrightnessForFunction(() async {
32 - await Navigator.pushNamed(
33 - context,
34 - Routes.fullscreenQR,
35 - arguments: QrViewData(data: url.toString(), version: QrVersions.auto),
36 - );
37 - });
38 - },
39 - splashColor: Colors.transparent,
40 - highlightColor: Colors.transparent,
41 - hoverColor: Colors.transparent,
42 - icon: Image.asset(
43 - 'assets/images/qr_code_icon.png',
44 - ));
32 + BrightnessUtil.changeBrightnessForFunction(() async {
33 + await Navigator.pushNamed(
34 + context,
35 + Routes.fullscreenQR,
36 + arguments: QrViewData(data: url.toString(), version: QrVersions.auto),
37 + );
38 + });
39 + },
40 + splashColor: Colors.transparent,
41 + highlightColor: Colors.transparent,
42 + hoverColor: Colors.transparent,
43 + icon: Image.asset(
44 + 'assets/images/qr_code_icon.png',
45 + ),
46 + );
47
48 @override
49 Widget body(BuildContext context) {
@@ -60,6 +62,7 @@ class WalletKeysPage extends BasePage {
62 child: Padding(
63 padding: const EdgeInsets.all(8.0),
64 child: AutoSizeText(
65 + key: ValueKey('wallet_keys_page_share_warning_text_key'),
66 S.of(context).do_not_share_warning_text.toUpperCase(),
67 textAlign: TextAlign.center,
68 maxLines: 4,
@@ -92,6 +95,7 @@ class WalletKeysPage extends BasePage {
95 final item = walletKeysViewModel.items[index];
96
97 return GestureDetector(
98 + key: item.key,
99 onTap: () {
100 ClipboardUtil.setSensitiveDataToClipboard(ClipboardData(text: item.value));
101 showBar<void>(context, S.of(context).copied_key_to_clipboard(item.title));
lib/src/screens/wallet_list/wallet_list_page.dart
+2
@@ -318,6 +318,7 @@ class WalletListBodyState extends State<WalletListBody> {
318 child: Column(
319 children: <Widget>[
320 PrimaryImageButton(
321 + key: ValueKey('wallet_list_page_create_new_wallet_button_key'),
322 onPressed: () {
323 //TODO(David): Find a way to optimize this
324 if (isSingleCoin) {
@@ -359,6 +360,7 @@ class WalletListBodyState extends State<WalletListBody> {
360 ),
361 SizedBox(height: 10.0),
362 PrimaryImageButton(
363 + key: ValueKey('wallet_list_page_restore_wallet_button_key'),
364 onPressed: () {
365 if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
366 widget.authService.authenticateAction(
lib/src/widgets/blockchain_height_widget.dart
+3
@@ -23,6 +23,7 @@ class BlockchainHeightWidget extends StatefulWidget {
23 this.doSingleScan = false,
24 this.bitcoinMempoolAPIEnabled,
25 required this.walletType,
26 + this.blockHeightTextFieldKey,
27 }) : super(key: key);
28
29 final Function(int)? onHeightChange;
@@ -35,6 +36,7 @@ class BlockchainHeightWidget extends StatefulWidget {
36 final Future<bool>? bitcoinMempoolAPIEnabled;
37 final Function()? toggleSingleScan;
38 final WalletType walletType;
39 + final Key? blockHeightTextFieldKey;
40
41 @override
42 State<StatefulWidget> createState() => BlockchainHeightState();
@@ -81,6 +83,7 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
83 child: Container(
84 padding: EdgeInsets.only(top: 20.0, bottom: 10.0),
85 child: BaseTextFormField(
86 + key: widget.blockHeightTextFieldKey,
87 focusNode: widget.focusNode,
88 controller: restoreHeightController,
89 keyboardType:
lib/src/widgets/dashboard_card_widget.dart
+1
@@ -15,6 +15,7 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
15 this.icon,
16 this.onClose,
17 this.customBorder,
18 + super.key,
19 });
20
21 final VoidCallback onTap;
lib/src/widgets/option_tile.dart
+9 -9
@@ -2,14 +2,14 @@ import 'package:cake_wallet/themes/extensions/option_tile_theme.dart';
2 import 'package:flutter/material.dart';
3
4 class OptionTile extends StatelessWidget {
5 - const OptionTile(
6 - {required this.onPressed,
7 - this.image,
8 - this.icon,
9 - required this.title,
10 - required this.description,
11 - super.key})
12 - : assert(image!=null || icon!=null);
5 + const OptionTile({
6 + required this.onPressed,
7 + this.image,
8 + this.icon,
9 + required this.title,
10 + required this.description,
11 + super.key,
12 + }) : assert(image != null || icon != null);
13
14 final VoidCallback onPressed;
15 final Image? image;
@@ -34,7 +34,7 @@ class OptionTile extends StatelessWidget {
34 mainAxisAlignment: MainAxisAlignment.center,
35 crossAxisAlignment: CrossAxisAlignment.start,
36 children: <Widget>[
37 - icon ?? image!,
37 + icon ?? image!,
38 Expanded(
39 child: Padding(
40 padding: EdgeInsets.only(left: 16),
lib/src/widgets/picker.dart
+3
@@ -2,6 +2,7 @@
2
3 import 'dart:math';
4
5 +import 'package:cake_wallet/entities/seed_type.dart';
6 import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
7 import 'package:cake_wallet/utils/responsive_layout_util.dart';
8 import 'package:cw_core/transaction_priority.dart';
@@ -310,6 +311,8 @@ class _PickerState<Item> extends State<Picker<Item>> {
311 itemName = item.name;
312 } else if (item is TransactionPriority) {
313 itemName = item.title;
314 + } else if (item is MoneroSeedType) {
315 + itemName = item.title;
316 } else {
317 itemName = '';
318 }
lib/src/widgets/seed_language_selector.dart
+8 -3
@@ -6,12 +6,16 @@ import 'package:cake_wallet/utils/show_pop_up.dart';
6 import 'package:flutter/material.dart';
7
8 class SeedLanguageSelector extends StatefulWidget {
9 - SeedLanguageSelector(
10 - {Key? key, required this.initialSelected, this.seedType = MoneroSeedType.defaultSeedType})
11 - : super(key: key);
9 + SeedLanguageSelector({
10 + required this.initialSelected,
11 + this.seedType = MoneroSeedType.defaultSeedType,
12 + this.buttonKey,
13 + Key? key,
14 + }) : super(key: key);
15
16 final String initialSelected;
17 final MoneroSeedType seedType;
18 + final Key? buttonKey;
19
20 @override
21 SeedLanguageSelectorState createState() => SeedLanguageSelectorState(selected: initialSelected);
@@ -25,6 +29,7 @@ class SeedLanguageSelectorState extends State<SeedLanguageSelector> {
29 @override
30 Widget build(BuildContext context) {
31 return SelectButton(
32 + key: widget.buttonKey,
33 image: null,
34 text:
35 "${seedLanguages.firstWhere((e) => e.name == selected).nameLocalized} (${S.of(context).seed_language})",
lib/src/widgets/setting_actions.dart
+12
@@ -5,9 +5,11 @@ import 'package:flutter/material.dart';
5 class SettingActions {
6 final String Function(BuildContext) name;
7 final String image;
8 + final Key key;
9 final void Function(BuildContext) onTap;
10
11 SettingActions._({
12 + required this.key,
13 required this.name,
14 required this.image,
15 required this.onTap,
@@ -39,6 +41,7 @@ class SettingActions {
41 ];
42
43 static SettingActions silentPaymentsSettingAction = SettingActions._(
44 + key: ValueKey('dashboard_page_menu_widget_silent_payment_settings_button_key'),
45 name: (context) => S.of(context).silent_payments_settings,
46 image: 'assets/images/bitcoin_menu.png',
47 onTap: (BuildContext context) {
@@ -48,6 +51,7 @@ class SettingActions {
51 );
52
53 static SettingActions litecoinMwebSettingAction = SettingActions._(
54 + key: ValueKey('dashboard_page_menu_widget_litecoin_mweb_settings_button_key'),
55 name: (context) => S.of(context).litecoin_mweb_settings,
56 image: 'assets/images/litecoin_menu.png',
57 onTap: (BuildContext context) {
@@ -57,6 +61,7 @@ class SettingActions {
61 );
62
63 static SettingActions connectionSettingAction = SettingActions._(
64 + key: ValueKey('dashboard_page_menu_widget_connection_and_sync_settings_button_key'),
65 name: (context) => S.of(context).connection_sync,
66 image: 'assets/images/nodes_menu.png',
67 onTap: (BuildContext context) {
@@ -66,6 +71,7 @@ class SettingActions {
71 );
72
73 static SettingActions walletSettingAction = SettingActions._(
74 + key: ValueKey('dashboard_page_menu_widget_wallet_menu_button_key'),
75 name: (context) => S.of(context).wallets,
76 image: 'assets/images/wallet_menu.png',
77 onTap: (BuildContext context) {
@@ -75,6 +81,7 @@ class SettingActions {
81 );
82
83 static SettingActions addressBookSettingAction = SettingActions._(
84 + key: ValueKey('dashboard_page_menu_widget_address_book_button_key'),
85 name: (context) => S.of(context).address_book_menu,
86 image: 'assets/images/open_book_menu.png',
87 onTap: (BuildContext context) {
@@ -84,6 +91,7 @@ class SettingActions {
91 );
92
93 static SettingActions securityBackupSettingAction = SettingActions._(
94 + key: ValueKey('dashboard_page_menu_widget_security_and_backup_button_key'),
95 name: (context) => S.of(context).security_and_backup,
96 image: 'assets/images/key_menu.png',
97 onTap: (BuildContext context) {
@@ -93,6 +101,7 @@ class SettingActions {
101 );
102
103 static SettingActions privacySettingAction = SettingActions._(
104 + key: ValueKey('dashboard_page_menu_widget_privacy_settings_button_key'),
105 name: (context) => S.of(context).privacy,
106 image: 'assets/images/privacy_menu.png',
107 onTap: (BuildContext context) {
@@ -102,6 +111,7 @@ class SettingActions {
111 );
112
113 static SettingActions displaySettingAction = SettingActions._(
114 + key: ValueKey('dashboard_page_menu_widget_display_settings_button_key'),
115 name: (context) => S.of(context).display_settings,
116 image: 'assets/images/eye_menu.png',
117 onTap: (BuildContext context) {
@@ -111,6 +121,7 @@ class SettingActions {
121 );
122
123 static SettingActions otherSettingAction = SettingActions._(
124 + key: ValueKey('dashboard_page_menu_widget_other_settings_button_key'),
125 name: (context) => S.of(context).other_settings,
126 image: 'assets/images/settings_menu.png',
127 onTap: (BuildContext context) {
@@ -120,6 +131,7 @@ class SettingActions {
131 );
132
133 static SettingActions supportSettingAction = SettingActions._(
134 + key: ValueKey('dashboard_page_menu_widget_support_settings_button_key'),
135 name: (context) => S.of(context).settings_support,
136 image: 'assets/images/question_mark.png',
137 onTap: (BuildContext context) {
lib/src/widgets/standard_list.dart
+7 -1
@@ -4,7 +4,13 @@ import 'package:cake_wallet/src/widgets/standard_list_status_row.dart';
4 import 'package:flutter/material.dart';
5
6 class StandardListRow extends StatelessWidget {
7 - StandardListRow({required this.title, required this.isSelected, this.onTap, this.decoration});
7 + StandardListRow({
8 + required this.title,
9 + required this.isSelected,
10 + this.onTap,
11 + this.decoration,
12 + super.key,
13 + });
14
15 final String title;
16 final bool isSelected;
lib/store/anonpay/anonpay_transactions_store.dart
+5 -1
@@ -1,6 +1,7 @@
1 import 'dart:async';
2 import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
3 import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
4 +import 'package:flutter/foundation.dart';
5 import 'package:hive/hive.dart';
6 import 'package:mobx/mobx.dart';
7
@@ -27,7 +28,10 @@ abstract class AnonpayTransactionsStoreBase with Store {
28 Future<void> updateTransactionList() async {
29 transactions = anonpayInvoiceInfoSource.values
30 .map(
30 - (transaction) => AnonpayTransactionListItem(transaction: transaction),
31 + (transaction) => AnonpayTransactionListItem(
32 + transaction: transaction,
33 + key: ValueKey('anonpay_invoice_transaction_list_item_${transaction.invoiceId}_key'),
34 + ),
35 )
36 .toList();
37 }
lib/store/dashboard/orders_store.dart
+13 -11
@@ -1,6 +1,7 @@
1 import 'dart:async';
2 import 'package:cake_wallet/buy/order.dart';
3 import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
4 +import 'package:flutter/foundation.dart';
5 import 'package:hive/hive.dart';
6 import 'package:mobx/mobx.dart';
7 import 'package:cake_wallet/store/settings_store.dart';
@@ -10,12 +11,10 @@ part 'orders_store.g.dart';
11 class OrdersStore = OrdersStoreBase with _$OrdersStore;
12
13 abstract class OrdersStoreBase with Store {
13 - OrdersStoreBase({required this.ordersSource,
14 - required this.settingsStore})
15 - : orders = <OrderListItem>[],
16 - orderId = '' {
17 - _onOrdersChanged =
18 - ordersSource.watch().listen((_) async => await updateOrderList());
14 + OrdersStoreBase({required this.ordersSource, required this.settingsStore})
15 + : orders = <OrderListItem>[],
16 + orderId = '' {
17 + _onOrdersChanged = ordersSource.watch().listen((_) async => await updateOrderList());
18 updateOrderList();
19 }
20
@@ -38,8 +37,11 @@ abstract class OrdersStoreBase with Store {
37 void setOrder(Order order) => this.order = order;
38
39 @action
41 - Future updateOrderList() async => orders =
42 - ordersSource.values.map((order) => OrderListItem(
43 - order: order,
44 - settingsStore: settingsStore)).toList();
45 -}
\ No newline at end of file
40 + Future updateOrderList() async => orders = ordersSource.values
41 + .map((order) => OrderListItem(
42 + order: order,
43 + settingsStore: settingsStore,
44 + key: ValueKey('order_list_item_${order.id}_key'),
45 + ))
46 + .toList();
47 +}
lib/store/dashboard/trades_store.dart
+11 -8
@@ -1,6 +1,7 @@
1 import 'dart:async';
2 import 'package:cake_wallet/exchange/trade.dart';
3 import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
4 +import 'package:flutter/foundation.dart';
5 import 'package:hive/hive.dart';
6 import 'package:mobx/mobx.dart';
7 import 'package:cake_wallet/store/settings_store.dart';
@@ -11,9 +12,8 @@ class TradesStore = TradesStoreBase with _$TradesStore;
12
13 abstract class TradesStoreBase with Store {
14 TradesStoreBase({required this.tradesSource, required this.settingsStore})
14 - : trades = <TradeListItem>[] {
15 - _onTradesChanged =
16 - tradesSource.watch().listen((_) async => await updateTradeList());
15 + : trades = <TradeListItem>[] {
16 + _onTradesChanged = tradesSource.watch().listen((_) async => await updateTradeList());
17 updateTradeList();
18 }
19
@@ -31,8 +31,11 @@ abstract class TradesStoreBase with Store {
31 void setTrade(Trade trade) => this.trade = trade;
32
33 @action
34 - Future<void> updateTradeList() async => trades =
35 - tradesSource.values.map((trade) => TradeListItem(
36 - trade: trade,
37 - settingsStore: settingsStore)).toList();
38 -}
\ No newline at end of file
34 + Future<void> updateTradeList() async => trades = tradesSource.values
35 + .map((trade) => TradeListItem(
36 + trade: trade,
37 + settingsStore: settingsStore,
38 + key: ValueKey('trade_list_item_${trade.id}_key'),
39 + ))
40 + .toList();
41 +}
lib/utils/date_formatter.dart
+24 -2
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 import 'package:intl/intl.dart';
3 import 'package:cake_wallet/di.dart';
4 import 'package:cake_wallet/store/settings_store.dart';
@@ -5,8 +6,7 @@ import 'package:cake_wallet/store/settings_store.dart';
6 class DateFormatter {
7 static String currentLocalFormat({bool hasTime = true, bool reverse = false}) {
8 final isUSA = getIt.get<SettingsStore>().languageCode.toLowerCase() == 'en';
8 - final format =
9 - isUSA ? usaStyleFormat(hasTime, reverse) : regularStyleFormat(hasTime, reverse);
9 + final format = isUSA ? usaStyleFormat(hasTime, reverse) : regularStyleFormat(hasTime, reverse);
10
11 return format;
12 }
@@ -20,4 +20,26 @@ class DateFormatter {
20
21 static String regularStyleFormat(bool hasTime, bool reverse) =>
22 hasTime ? (reverse ? 'HH:mm dd.MM.yyyy' : 'dd.MM.yyyy, HH:mm') : 'dd.MM.yyyy';
23 +
24 + static String convertDateTimeToReadableString(DateTime date) {
25 + final nowDate = DateTime.now();
26 + final diffDays = date.difference(nowDate).inDays;
27 + final isToday =
28 + nowDate.day == date.day && nowDate.month == date.month && nowDate.year == date.year;
29 + final dateSectionDateFormat = withCurrentLocal(hasTime: false);
30 + var title = "";
31 +
32 + if (isToday) {
33 + title = S.current.today;
34 + } else if (diffDays == 0) {
35 + title = S.current.yesterday;
36 + } else if (diffDays > -7 && diffDays < 0) {
37 + final dateFormat = DateFormat.EEEE();
38 + title = dateFormat.format(date);
39 + } else {
40 + title = dateSectionDateFormat.format(date);
41 + }
42 +
43 + return title;
44 + }
45 }
lib/utils/image_utill.dart
+4
@@ -11,6 +11,7 @@ class ImageUtil {
11 if (isNetworkImage) {
12 return isSvg
13 ? SvgPicture.network(
14 + key: ValueKey(imagePath),
15 imagePath,
16 height: _height,
17 width: _width,
@@ -23,6 +24,7 @@ class ImageUtil {
24 ),
25 )
26 : Image.network(
27 + key: ValueKey(imagePath),
28 imagePath,
29 height: _height,
30 width: _width,
@@ -58,12 +60,14 @@ class ImageUtil {
60 height: _height,
61 width: _width,
62 placeholderBuilder: (_) => Icon(Icons.error),
63 + key: ValueKey(imagePath),
64 )
65 : Image.asset(
66 imagePath,
67 height: _height,
68 width: _width,
69 errorBuilder: (_, __, ___) => Icon(Icons.error),
70 + key: ValueKey(imagePath),
71 );
72 }
73 }
lib/view_model/dashboard/action_list_item.dart
+5
@@ -1,3 +1,8 @@
1 +import 'package:flutter/foundation.dart';
2 +
3 abstract class ActionListItem {
4 + ActionListItem({required this.key});
5 +
6 DateTime get date;
7 + Key key;
8 }
\ No newline at end of file
lib/view_model/dashboard/anonpay_transaction_list_item.dart
+1 -1
@@ -2,7 +2,7 @@ import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
2 import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
3
4 class AnonpayTransactionListItem extends ActionListItem {
5 - AnonpayTransactionListItem({required this.transaction});
5 + AnonpayTransactionListItem({required this.transaction, required super.key});
6
7 final AnonpayInvoiceInfo transaction;
8
lib/view_model/dashboard/dashboard_view_model.dart
+75 -31
@@ -48,6 +48,7 @@ import 'package:cw_core/wallet_base.dart';
48 import 'package:cw_core/wallet_info.dart';
49 import 'package:cw_core/wallet_type.dart';
50 import 'package:eth_sig_util/util/utils.dart';
51 +import 'package:flutter/foundation.dart';
52 import 'package:flutter/services.dart';
53 import 'package:http/http.dart' as http;
54 import 'package:mobx/mobx.dart';
@@ -182,10 +183,16 @@ abstract class DashboardViewModelBase with Store {
183 final sortedTransactions = [..._accountTransactions];
184 sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
185
185 - transactions = ObservableList.of(sortedTransactions.map((transaction) => TransactionListItem(
186 - transaction: transaction,
187 - balanceViewModel: balanceViewModel,
188 - settingsStore: appStore.settingsStore)));
186 + transactions = ObservableList.of(
187 + sortedTransactions.map(
188 + (transaction) => TransactionListItem(
189 + transaction: transaction,
190 + balanceViewModel: balanceViewModel,
191 + settingsStore: appStore.settingsStore,
192 + key: ValueKey('monero_transaction_history_item_${transaction.id}_key'),
193 + ),
194 + ),
195 + );
196 } else if (_wallet.type == WalletType.wownero) {
197 subname = wow.wownero!.getCurrentAccount(_wallet).label;
198
@@ -206,18 +213,30 @@ abstract class DashboardViewModelBase with Store {
213 final sortedTransactions = [..._accountTransactions];
214 sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
215
209 - transactions = ObservableList.of(sortedTransactions.map((transaction) => TransactionListItem(
210 - transaction: transaction,
211 - balanceViewModel: balanceViewModel,
212 - settingsStore: appStore.settingsStore)));
216 + transactions = ObservableList.of(
217 + sortedTransactions.map(
218 + (transaction) => TransactionListItem(
219 + transaction: transaction,
220 + balanceViewModel: balanceViewModel,
221 + settingsStore: appStore.settingsStore,
222 + key: ValueKey('wownero_transaction_history_item_${transaction.id}_key'),
223 + ),
224 + ),
225 + );
226 } else {
227 final sortedTransactions = [...wallet.transactionHistory.transactions.values];
228 sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
229
217 - transactions = ObservableList.of(sortedTransactions.map((transaction) => TransactionListItem(
218 - transaction: transaction,
219 - balanceViewModel: balanceViewModel,
220 - settingsStore: appStore.settingsStore)));
230 + transactions = ObservableList.of(
231 + sortedTransactions.map(
232 + (transaction) => TransactionListItem(
233 + transaction: transaction,
234 + balanceViewModel: balanceViewModel,
235 + settingsStore: appStore.settingsStore,
236 + key: ValueKey('${_wallet.type.name}_transaction_history_item_${transaction.id}_key'),
237 + ),
238 + ),
239 + );
240 }
241
242 // TODO: nano sub-account generation is disabled:
@@ -234,9 +253,13 @@ abstract class DashboardViewModelBase with Store {
253 appStore.wallet!.transactionHistory.transactions,
254 transactions,
255 (TransactionInfo? transaction) => TransactionListItem(
237 - transaction: transaction!,
238 - balanceViewModel: balanceViewModel,
239 - settingsStore: appStore.settingsStore), filter: (TransactionInfo? transaction) {
256 + transaction: transaction!,
257 + balanceViewModel: balanceViewModel,
258 + settingsStore: appStore.settingsStore,
259 + key: ValueKey(
260 + '${_wallet.type.name}_transaction_history_item_${transaction.id}_key',
261 + ),
262 + ), filter: (TransactionInfo? transaction) {
263 if (transaction == null) {
264 return false;
265 }
@@ -650,20 +673,29 @@ abstract class DashboardViewModelBase with Store {
673
674 transactions.clear();
675
653 - transactions.addAll(wallet.transactionHistory.transactions.values.map((transaction) =>
654 - TransactionListItem(
655 - transaction: transaction,
656 - balanceViewModel: balanceViewModel,
657 - settingsStore: appStore.settingsStore)));
676 + transactions.addAll(
677 + wallet.transactionHistory.transactions.values.map(
678 + (transaction) => TransactionListItem(
679 + transaction: transaction,
680 + balanceViewModel: balanceViewModel,
681 + settingsStore: appStore.settingsStore,
682 + key: ValueKey('${wallet.type.name}_transaction_history_item_${transaction.id}_key'),
683 + ),
684 + ),
685 + );
686 }
687
688 connectMapToListWithTransform(
689 appStore.wallet!.transactionHistory.transactions,
690 transactions,
691 (TransactionInfo? transaction) => TransactionListItem(
664 - transaction: transaction!,
665 - balanceViewModel: balanceViewModel,
666 - settingsStore: appStore.settingsStore), filter: (TransactionInfo? tx) {
692 + transaction: transaction!,
693 + balanceViewModel: balanceViewModel,
694 + settingsStore: appStore.settingsStore,
695 + key: ValueKey(
696 + '${wallet.type.name}_transaction_history_item_${transaction.id}_key',
697 + ),
698 + ), filter: (TransactionInfo? tx) {
699 if (tx == null) {
700 return false;
701 }
@@ -703,10 +735,16 @@ abstract class DashboardViewModelBase with Store {
735 monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id)
736 .toList();
737
706 - transactions.addAll(_accountTransactions.map((transaction) => TransactionListItem(
707 - transaction: transaction,
708 - balanceViewModel: balanceViewModel,
709 - settingsStore: appStore.settingsStore)));
738 + transactions.addAll(
739 + _accountTransactions.map(
740 + (transaction) => TransactionListItem(
741 + transaction: transaction,
742 + balanceViewModel: balanceViewModel,
743 + settingsStore: appStore.settingsStore,
744 + key: ValueKey('monero_transaction_history_item_${transaction.id}_key'),
745 + ),
746 + ),
747 + );
748 } else if (wallet.type == WalletType.wownero) {
749 final _accountTransactions = wow.wownero!
750 .getTransactionHistory(wallet)
@@ -717,10 +755,16 @@ abstract class DashboardViewModelBase with Store {
755 wow.wownero!.getCurrentAccount(wallet).id)
756 .toList();
757
720 - transactions.addAll(_accountTransactions.map((transaction) => TransactionListItem(
721 - transaction: transaction,
722 - balanceViewModel: balanceViewModel,
723 - settingsStore: appStore.settingsStore)));
758 + transactions.addAll(
759 + _accountTransactions.map(
760 + (transaction) => TransactionListItem(
761 + transaction: transaction,
762 + balanceViewModel: balanceViewModel,
763 + settingsStore: appStore.settingsStore,
764 + key: ValueKey('wownero_transaction_history_item_${transaction.id}_key'),
765 + ),
766 + ),
767 + );
768 }
769 }
770
lib/view_model/dashboard/date_section_item.dart
+1 -1
@@ -1,7 +1,7 @@
1 import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
2
3 class DateSectionItem extends ActionListItem {
4 - DateSectionItem(this.date);
4 + DateSectionItem(this.date, {required super.key});
5
6 @override
7 final DateTime date;
lib/view_model/dashboard/formatted_item_list.dart
+13 -2
@@ -1,5 +1,6 @@
1 import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
2 import 'package:cake_wallet/view_model/dashboard/date_section_item.dart';
3 +import 'package:flutter/foundation.dart';
4
5 List<ActionListItem> formattedItemsList(List<ActionListItem> items) {
6 final formattedList = <ActionListItem>[];
@@ -11,7 +12,12 @@ List<ActionListItem> formattedItemsList(List<ActionListItem> items) {
12
13 if (lastDate == null) {
14 lastDate = transaction.date;
14 - formattedList.add(DateSectionItem(transaction.date));
15 + formattedList.add(
16 + DateSectionItem(
17 + transaction.date,
18 + key: ValueKey('date_section_item_${transaction.date.microsecondsSinceEpoch}_key'),
19 + ),
20 + );
21 formattedList.add(transaction);
22 continue;
23 }
@@ -26,7 +32,12 @@ List<ActionListItem> formattedItemsList(List<ActionListItem> items) {
32 }
33
34 lastDate = transaction.date;
29 - formattedList.add(DateSectionItem(transaction.date));
35 + formattedList.add(
36 + DateSectionItem(
37 + transaction.date,
38 + key: ValueKey('date_section_item_${transaction.date.microsecondsSinceEpoch}_key'),
39 + ),
40 + );
41 formattedList.add(transaction);
42 }
43
lib/view_model/dashboard/order_list_item.dart
+3 -1
@@ -6,7 +6,9 @@ import 'package:cake_wallet/entities/balance_display_mode.dart';
6 class OrderListItem extends ActionListItem {
7 OrderListItem({
8 required this.order,
9 - required this.settingsStore});
9 + required this.settingsStore,
10 + required super.key,
11 + });
12
13 final Order order;
14 final SettingsStore settingsStore;
lib/view_model/dashboard/trade_list_item.dart
+5 -1
@@ -4,7 +4,11 @@ import 'package:cake_wallet/store/settings_store.dart';
4 import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
5
6 class TradeListItem extends ActionListItem {
7 - TradeListItem({required this.trade, required this.settingsStore});
7 + TradeListItem({
8 + required this.trade,
9 + required this.settingsStore,
10 + required super.key,
11 + });
12
13 final Trade trade;
14 final SettingsStore settingsStore;
lib/view_model/dashboard/transaction_list_item.dart
+6 -2
@@ -22,8 +22,12 @@ import 'package:cw_core/keyable.dart';
22 import 'package:cw_core/wallet_type.dart';
23
24 class TransactionListItem extends ActionListItem with Keyable {
25 - TransactionListItem(
26 - {required this.transaction, required this.balanceViewModel, required this.settingsStore});
25 + TransactionListItem({
26 + required this.transaction,
27 + required this.balanceViewModel,
28 + required this.settingsStore,
29 + required super.key,
30 + });
31
32 final TransactionInfo transaction;
33 final BalanceViewModel balanceViewModel;
lib/view_model/transaction_details_view_model.dart
+357 -88
@@ -20,6 +20,7 @@ import 'package:cake_wallet/view_model/send/send_view_model.dart';
20 import 'package:collection/collection.dart';
21 import 'package:cw_core/transaction_direction.dart';
22 import 'package:cw_core/transaction_priority.dart';
23 +import 'package:flutter/foundation.dart';
24 import 'package:hive/hive.dart';
25 import 'package:intl/src/intl/date_format.dart';
26 import 'package:mobx/mobx.dart';
@@ -52,7 +53,7 @@ abstract class TransactionDetailsViewModelBase with Store {
53 break;
54 case WalletType.bitcoin:
55 _addElectrumListItems(tx, dateFormat);
55 - if(!canReplaceByFee)_checkForRBF(tx);
56 + if (!canReplaceByFee) _checkForRBF(tx);
57 break;
58 case WalletType.litecoin:
59 case WalletType.bitcoinCash:
@@ -83,8 +84,7 @@ abstract class TransactionDetailsViewModelBase with Store {
84 break;
85 }
86
86 - final descriptionKey =
87 - '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
87 + final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
88 final description = transactionDescriptionBox.values.firstWhere(
89 (val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
90 orElse: () => TransactionDescription(id: descriptionKey));
@@ -93,15 +93,20 @@ abstract class TransactionDetailsViewModelBase with Store {
93 final recipientAddress = description.recipientAddress;
94
95 if (recipientAddress?.isNotEmpty ?? false) {
96 - items.add(StandartListItem(
97 - title: S.current.transaction_details_recipient_address,
98 - value: recipientAddress!));
96 + items.add(
97 + StandartListItem(
98 + title: S.current.transaction_details_recipient_address,
99 + value: recipientAddress!,
100 + key: ValueKey('standard_list_item_${recipientAddress}_key'),
101 + ),
102 + );
103 }
104 }
105
106 final type = wallet.type;
107
104 - items.add(BlockExplorerListItem(
108 + items.add(
109 + BlockExplorerListItem(
110 title: S.current.view_in_block_explorer,
111 value: _explorerDescription(type),
112 onTap: () async {
@@ -109,9 +114,13 @@ abstract class TransactionDetailsViewModelBase with Store {
114 final uri = Uri.parse(_explorerUrl(type, tx.txHash));
115 if (await canLaunchUrl(uri)) await launchUrl(uri, mode: LaunchMode.externalApplication);
116 } catch (e) {}
112 - }));
117 + },
118 + key: ValueKey('block_explorer_list_item_${type.name}_wallet_type_key'),
119 + ),
120 + );
121
114 - items.add(TextFieldListItem(
122 + items.add(
123 + TextFieldListItem(
124 title: S.current.note_tap_to_change,
125 value: description.note,
126 onSubmitted: (value) {
@@ -122,7 +131,10 @@ abstract class TransactionDetailsViewModelBase with Store {
131 } else {
132 transactionDescriptionBox.add(description);
133 }
125 - }));
134 + },
135 + key: ValueKey('textfield_list_item_note_entry_key'),
136 + ),
137 + );
138 }
139
140 final TransactionInfo transactionInfo;
@@ -209,14 +221,38 @@ abstract class TransactionDetailsViewModelBase with Store {
221 final addressIndex = tx.additionalInfo['addressIndex'] as int;
222 final feeFormatted = tx.feeFormatted();
223 final _items = [
212 - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
224 StandartListItem(
214 - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
215 - StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
216 - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
225 + title: S.current.transaction_details_transaction_id,
226 + value: tx.txHash,
227 + key: ValueKey('standard_list_item_transaction_details_id_key'),
228 + ),
229 + StandartListItem(
230 + title: S.current.transaction_details_date,
231 + value: dateFormat.format(tx.date),
232 + key: ValueKey('standard_list_item_transaction_details_date_key'),
233 + ),
234 + StandartListItem(
235 + title: S.current.transaction_details_height,
236 + value: '${tx.height}',
237 + key: ValueKey('standard_list_item_transaction_details_height_key'),
238 + ),
239 + StandartListItem(
240 + title: S.current.transaction_details_amount,
241 + value: tx.amountFormatted(),
242 + key: ValueKey('standard_list_item_transaction_details_amount_key'),
243 + ),
244 if (feeFormatted != null)
218 - StandartListItem(title: S.current.transaction_details_fee, value: feeFormatted),
219 - if (key?.isNotEmpty ?? false) StandartListItem(title: S.current.transaction_key, value: key!),
245 + StandartListItem(
246 + title: S.current.transaction_details_fee,
247 + value: feeFormatted,
248 + key: ValueKey('standard_list_item_transaction_details_fee_key'),
249 + ),
250 + if (key?.isNotEmpty ?? false)
251 + StandartListItem(
252 + title: S.current.transaction_key,
253 + value: key!,
254 + key: ValueKey('standard_list_item_transaction_key'),
255 + ),
256 ];
257
258 if (tx.direction == TransactionDirection.incoming) {
@@ -226,14 +262,21 @@ abstract class TransactionDetailsViewModelBase with Store {
262
263 if (address.isNotEmpty) {
264 isRecipientAddressShown = true;
229 - _items.add(StandartListItem(
230 - title: S.current.transaction_details_recipient_address,
231 - value: address,
232 - ));
265 + _items.add(
266 + StandartListItem(
267 + title: S.current.transaction_details_recipient_address,
268 + value: address,
269 + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
270 + ),
271 + );
272 }
273
274 if (label.isNotEmpty) {
236 - _items.add(StandartListItem(title: S.current.address_label, value: label));
275 + _items.add(StandartListItem(
276 + title: S.current.address_label,
277 + value: label,
278 + key: ValueKey('standard_list_item_address_label_key'),
279 + ));
280 }
281 } catch (e) {
282 print(e.toString());
@@ -245,14 +288,37 @@ abstract class TransactionDetailsViewModelBase with Store {
288
289 void _addElectrumListItems(TransactionInfo tx, DateFormat dateFormat) {
290 final _items = [
248 - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
291 StandartListItem(
250 - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
251 - StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
252 - StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
253 - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
292 + title: S.current.transaction_details_transaction_id,
293 + value: tx.txHash,
294 + key: ValueKey('standard_list_item_transaction_details_id_key'),
295 + ),
296 + StandartListItem(
297 + title: S.current.transaction_details_date,
298 + value: dateFormat.format(tx.date),
299 + key: ValueKey('standard_list_item_transaction_details_date_key'),
300 + ),
301 + StandartListItem(
302 + title: S.current.confirmations,
303 + value: tx.confirmations.toString(),
304 + key: ValueKey('standard_list_item_transaction_confirmations_key'),
305 + ),
306 + StandartListItem(
307 + title: S.current.transaction_details_height,
308 + value: '${tx.height}',
309 + key: ValueKey('standard_list_item_transaction_details_height_key'),
310 + ),
311 + StandartListItem(
312 + title: S.current.transaction_details_amount,
313 + value: tx.amountFormatted(),
314 + key: ValueKey('standard_list_item_transaction_details_amount_key'),
315 + ),
316 if (tx.feeFormatted()?.isNotEmpty ?? false)
255 - StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
317 + StandartListItem(
318 + title: S.current.transaction_details_fee,
319 + value: tx.feeFormatted()!,
320 + key: ValueKey('standard_list_item_transaction_details_fee_key'),
321 + ),
322 ];
323
324 items.addAll(_items);
@@ -260,30 +326,80 @@ abstract class TransactionDetailsViewModelBase with Store {
326
327 void _addHavenListItems(TransactionInfo tx, DateFormat dateFormat) {
328 items.addAll([
263 - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
329 StandartListItem(
265 - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
266 - StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
267 - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
330 + title: S.current.transaction_details_transaction_id,
331 + value: tx.txHash,
332 + key: ValueKey('standard_list_item_transaction_details_id_key'),
333 + ),
334 + StandartListItem(
335 + title: S.current.transaction_details_date,
336 + value: dateFormat.format(tx.date),
337 + key: ValueKey('standard_list_item_transaction_details_date_key'),
338 + ),
339 + StandartListItem(
340 + title: S.current.transaction_details_height,
341 + value: '${tx.height}',
342 + key: ValueKey('standard_list_item_transaction_details_height_key'),
343 + ),
344 + StandartListItem(
345 + title: S.current.transaction_details_amount,
346 + value: tx.amountFormatted(),
347 + key: ValueKey('standard_list_item_transaction_details_amount_key'),
348 + ),
349 if (tx.feeFormatted()?.isNotEmpty ?? false)
269 - StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
350 + StandartListItem(
351 + title: S.current.transaction_details_fee,
352 + value: tx.feeFormatted()!,
353 + key: ValueKey('standard_list_item_transaction_details_fee_key'),
354 + ),
355 ]);
356 }
357
358 void _addEthereumListItems(TransactionInfo tx, DateFormat dateFormat) {
359 final _items = [
275 - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
360 StandartListItem(
277 - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
278 - StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
279 - StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
280 - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
361 + title: S.current.transaction_details_transaction_id,
362 + value: tx.txHash,
363 + key: ValueKey('standard_list_item_transaction_details_id_key'),
364 + ),
365 + StandartListItem(
366 + title: S.current.transaction_details_date,
367 + value: dateFormat.format(tx.date),
368 + key: ValueKey('standard_list_item_transaction_details_date_key'),
369 + ),
370 + StandartListItem(
371 + title: S.current.confirmations,
372 + value: tx.confirmations.toString(),
373 + key: ValueKey('standard_list_item_transaction_confirmations_key'),
374 + ),
375 + StandartListItem(
376 + title: S.current.transaction_details_height,
377 + value: '${tx.height}',
378 + key: ValueKey('standard_list_item_transaction_details_height_key'),
379 + ),
380 + StandartListItem(
381 + title: S.current.transaction_details_amount,
382 + value: tx.amountFormatted(),
383 + key: ValueKey('standard_list_item_transaction_details_amount_key'),
384 + ),
385 if (tx.feeFormatted()?.isNotEmpty ?? false)
282 - StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
386 + StandartListItem(
387 + title: S.current.transaction_details_fee,
388 + value: tx.feeFormatted()!,
389 + key: ValueKey('standard_list_item_transaction_details_fee_key'),
390 + ),
391 if (showRecipientAddress && tx.to != null)
284 - StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!),
392 + StandartListItem(
393 + title: S.current.transaction_details_recipient_address,
394 + value: tx.to!,
395 + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
396 + ),
397 if (tx.direction == TransactionDirection.incoming && tx.from != null)
286 - StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!),
398 + StandartListItem(
399 + title: S.current.transaction_details_source_address,
400 + value: tx.from!,
401 + key: ValueKey('standard_list_item_transaction_details_source_address_key'),
402 + ),
403 ];
404
405 items.addAll(_items);
@@ -291,16 +407,43 @@ abstract class TransactionDetailsViewModelBase with Store {
407
408 void _addNanoListItems(TransactionInfo tx, DateFormat dateFormat) {
409 final _items = [
294 - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
410 + StandartListItem(
411 + title: S.current.transaction_details_transaction_id,
412 + value: tx.txHash,
413 + key: ValueKey('standard_list_item_transaction_details_id_key'),
414 + ),
415 if (showRecipientAddress && tx.to != null)
296 - StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!),
416 + StandartListItem(
417 + title: S.current.transaction_details_recipient_address,
418 + value: tx.to!,
419 + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
420 + ),
421 if (showRecipientAddress && tx.from != null)
298 - StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!),
299 - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
422 + StandartListItem(
423 + title: S.current.transaction_details_source_address,
424 + value: tx.from!,
425 + key: ValueKey('standard_list_item_transaction_details_source_address_key'),
426 + ),
427 StandartListItem(
301 - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
302 - StandartListItem(title: S.current.confirmed_tx, value: (tx.confirmations > 0).toString()),
303 - StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
428 + title: S.current.transaction_details_amount,
429 + value: tx.amountFormatted(),
430 + key: ValueKey('standard_list_item_transaction_details_amount_key'),
431 + ),
432 + StandartListItem(
433 + title: S.current.transaction_details_date,
434 + value: dateFormat.format(tx.date),
435 + key: ValueKey('standard_list_item_transaction_details_date_key'),
436 + ),
437 + StandartListItem(
438 + title: S.current.confirmed_tx,
439 + value: (tx.confirmations > 0).toString(),
440 + key: ValueKey('standard_list_item_transaction_confirmed_key'),
441 + ),
442 + StandartListItem(
443 + title: S.current.transaction_details_height,
444 + value: '${tx.height}',
445 + key: ValueKey('standard_list_item_transaction_details_height_key'),
446 + ),
447 ];
448
449 items.addAll(_items);
@@ -308,18 +451,49 @@ abstract class TransactionDetailsViewModelBase with Store {
451
452 void _addPolygonListItems(TransactionInfo tx, DateFormat dateFormat) {
453 final _items = [
311 - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
454 StandartListItem(
313 - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
314 - StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
315 - StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
316 - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
455 + title: S.current.transaction_details_transaction_id,
456 + value: tx.txHash,
457 + key: ValueKey('standard_list_item_transaction_details_id_key'),
458 + ),
459 + StandartListItem(
460 + title: S.current.transaction_details_date,
461 + value: dateFormat.format(tx.date),
462 + key: ValueKey('standard_list_item_transaction_details_date_key'),
463 + ),
464 + StandartListItem(
465 + title: S.current.confirmations,
466 + value: tx.confirmations.toString(),
467 + key: ValueKey('standard_list_item_transaction_confirmations_key'),
468 + ),
469 + StandartListItem(
470 + title: S.current.transaction_details_height,
471 + value: '${tx.height}',
472 + key: ValueKey('standard_list_item_transaction_details_height_key'),
473 + ),
474 + StandartListItem(
475 + title: S.current.transaction_details_amount,
476 + value: tx.amountFormatted(),
477 + key: ValueKey('standard_list_item_transaction_details_amount_key'),
478 + ),
479 if (tx.feeFormatted()?.isNotEmpty ?? false)
318 - StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
480 + StandartListItem(
481 + title: S.current.transaction_details_fee,
482 + value: tx.feeFormatted()!,
483 + key: ValueKey('standard_list_item_transaction_details_fee_key'),
484 + ),
485 if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing)
320 - StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!),
486 + StandartListItem(
487 + title: S.current.transaction_details_recipient_address,
488 + value: tx.to!,
489 + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
490 + ),
491 if (tx.direction == TransactionDirection.incoming && tx.from != null)
322 - StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!),
492 + StandartListItem(
493 + title: S.current.transaction_details_source_address,
494 + value: tx.from!,
495 + key: ValueKey('standard_list_item_transaction_details_source_address_key'),
496 + ),
497 ];
498
499 items.addAll(_items);
@@ -327,16 +501,39 @@ abstract class TransactionDetailsViewModelBase with Store {
501
502 void _addSolanaListItems(TransactionInfo tx, DateFormat dateFormat) {
503 final _items = [
330 - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
504 StandartListItem(
332 - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
333 - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
505 + title: S.current.transaction_details_transaction_id,
506 + value: tx.txHash,
507 + key: ValueKey('standard_list_item_transaction_details_id_key'),
508 + ),
509 + StandartListItem(
510 + title: S.current.transaction_details_date,
511 + value: dateFormat.format(tx.date),
512 + key: ValueKey('standard_list_item_transaction_details_date_key'),
513 + ),
514 + StandartListItem(
515 + title: S.current.transaction_details_amount,
516 + value: tx.amountFormatted(),
517 + key: ValueKey('standard_list_item_transaction_details_amount_key'),
518 + ),
519 if (tx.feeFormatted()?.isNotEmpty ?? false)
335 - StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
520 + StandartListItem(
521 + title: S.current.transaction_details_fee,
522 + value: tx.feeFormatted()!,
523 + key: ValueKey('standard_list_item_transaction_details_fee_key'),
524 + ),
525 if (showRecipientAddress && tx.to != null)
337 - StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!),
526 + StandartListItem(
527 + title: S.current.transaction_details_recipient_address,
528 + value: tx.to!,
529 + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
530 + ),
531 if (tx.from != null)
339 - StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!),
532 + StandartListItem(
533 + title: S.current.transaction_details_source_address,
534 + value: tx.from!,
535 + key: ValueKey('standard_list_item_transaction_details_source_address_key'),
536 + ),
537 ];
538
539 items.addAll(_items);
@@ -354,7 +551,13 @@ abstract class TransactionDetailsViewModelBase with Store {
551 newFee = bitcoin!.getFeeAmountForPriority(
552 wallet, bitcoin!.getBitcoinTransactionPriorityMedium(), inputsCount, outputsCount);
553
357 - RBFListItems.add(StandartListItem(title: S.current.old_fee, value: tx.feeFormatted() ?? '0.0'));
554 + RBFListItems.add(
555 + StandartListItem(
556 + title: S.current.old_fee,
557 + value: tx.feeFormatted() ?? '0.0',
558 + key: ValueKey('standard_list_item_rbf_old_fee_key'),
559 + ),
560 + );
561
562 if (transactionInfo.fee != null && rawTransaction.isNotEmpty) {
563 final size = bitcoin!.getTransactionVSize(wallet, rawTransaction);
@@ -371,7 +574,9 @@ abstract class TransactionDetailsViewModelBase with Store {
574 final customItemIndex = customItem != null ? priorities.indexOf(customItem) : null;
575 final maxCustomFeeRate = sendViewModel.maxCustomFeeRate?.toDouble();
576
374 - RBFListItems.add(StandardPickerListItem(
577 + RBFListItems.add(
578 + StandardPickerListItem(
579 + key: ValueKey('standard_picker_list_item_transaction_priorities_key'),
580 title: S.current.estimated_new_fee,
581 value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) +
582 ' ${walletTypeToCryptoCurrency(wallet.type)}',
@@ -387,42 +592,73 @@ abstract class TransactionDetailsViewModelBase with Store {
592 onItemSelected: (dynamic item, double sliderValue) {
593 transactionPriority = item as TransactionPriority;
594 return setNewFee(value: sliderValue, priority: transactionPriority!);
390 - }));
595 + },
596 + ),
597 + );
598
599 if (transactionInfo.inputAddresses != null && transactionInfo.inputAddresses!.isNotEmpty) {
393 - RBFListItems.add(StandardExpandableListItem(
394 - title: S.current.inputs, expandableItems: transactionInfo.inputAddresses!));
600 + RBFListItems.add(
601 + StandardExpandableListItem(
602 + key: ValueKey('standard_expandable_list_item_transaction_input_addresses_key'),
603 + title: S.current.inputs,
604 + expandableItems: transactionInfo.inputAddresses!,
605 + ),
606 + );
607 }
608
609 if (transactionInfo.outputAddresses != null && transactionInfo.outputAddresses!.isNotEmpty) {
610 final outputAddresses = transactionInfo.outputAddresses!.map((element) {
611 if (element.contains('OP_RETURN:') && element.length > 40) {
400 - return element.substring(0, 40) + '...';
612 + return element.substring(0, 40) + '...';
613 }
614 return element;
615 }).toList();
616
617 RBFListItems.add(
406 - StandardExpandableListItem(title: S.current.outputs, expandableItems: outputAddresses));
618 + StandardExpandableListItem(
619 + title: S.current.outputs,
620 + expandableItems: outputAddresses,
621 + key: ValueKey('standard_expandable_list_item_transaction_output_addresses_key'),
622 + ),
623 + );
624 }
625 }
626
627 void _addTronListItems(TransactionInfo tx, DateFormat dateFormat) {
628 final _items = [
412 - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
629 StandartListItem(
414 - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
415 - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
630 + title: S.current.transaction_details_transaction_id,
631 + value: tx.txHash,
632 + key: ValueKey('standard_list_item_transaction_details_id_key'),
633 + ),
634 + StandartListItem(
635 + title: S.current.transaction_details_date,
636 + value: dateFormat.format(tx.date),
637 + key: ValueKey('standard_list_item_transaction_details_date_key'),
638 + ),
639 + StandartListItem(
640 + title: S.current.transaction_details_amount,
641 + value: tx.amountFormatted(),
642 + key: ValueKey('standard_list_item_transaction_details_amount_key'),
643 + ),
644 if (tx.feeFormatted()?.isNotEmpty ?? false)
417 - StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
645 + StandartListItem(
646 + title: S.current.transaction_details_fee,
647 + value: tx.feeFormatted()!,
648 + key: ValueKey('standard_list_item_transaction_details_fee_key'),
649 + ),
650 if (showRecipientAddress && tx.to != null)
651 StandartListItem(
420 - title: S.current.transaction_details_recipient_address,
421 - value: tron!.getTronBase58Address(tx.to!, wallet)),
652 + title: S.current.transaction_details_recipient_address,
653 + value: tron!.getTronBase58Address(tx.to!, wallet),
654 + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
655 + ),
656 if (tx.from != null)
657 StandartListItem(
424 - title: S.current.transaction_details_source_address,
425 - value: tron!.getTronBase58Address(tx.from!, wallet)),
658 + title: S.current.transaction_details_source_address,
659 + value: tron!.getTronBase58Address(tx.from!, wallet),
660 + key: ValueKey('standard_list_item_transaction_details_source_address_key'),
661 + ),
662 ];
663
664 items.addAll(_items);
@@ -455,7 +691,7 @@ abstract class TransactionDetailsViewModelBase with Store {
691 return bitcoin!.formatterBitcoinAmountToString(amount: newFee);
692 }
693
458 - void replaceByFee(String newFee) => sendViewModel.replaceByFee(transactionInfo, newFee,);
694 + void replaceByFee(String newFee) => sendViewModel.replaceByFee(transactionInfo, newFee);
695
696 @computed
697 String get pendingTransactionFiatAmountValueFormatted => sendViewModel.isFiatDisabled
@@ -473,14 +709,38 @@ abstract class TransactionDetailsViewModelBase with Store {
709 final addressIndex = tx.additionalInfo['addressIndex'] as int;
710 final feeFormatted = tx.feeFormatted();
711 final _items = [
476 - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash),
712 StandartListItem(
478 - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
479 - StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
480 - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
713 + title: S.current.transaction_details_transaction_id,
714 + value: tx.txHash,
715 + key: ValueKey('standard_list_item_transaction_details_id_key'),
716 + ),
717 + StandartListItem(
718 + title: S.current.transaction_details_date,
719 + value: dateFormat.format(tx.date),
720 + key: ValueKey('standard_list_item_transaction_details_date_key'),
721 + ),
722 + StandartListItem(
723 + title: S.current.transaction_details_height,
724 + value: '${tx.height}',
725 + key: ValueKey('standard_list_item_transaction_details_height_key'),
726 + ),
727 + StandartListItem(
728 + title: S.current.transaction_details_amount,
729 + value: tx.amountFormatted(),
730 + key: ValueKey('standard_list_item_transaction_details_amount_key'),
731 + ),
732 if (feeFormatted != null)
482 - StandartListItem(title: S.current.transaction_details_fee, value: feeFormatted),
483 - if (key?.isNotEmpty ?? false) StandartListItem(title: S.current.transaction_key, value: key!),
733 + StandartListItem(
734 + title: S.current.transaction_details_fee,
735 + value: feeFormatted,
736 + key: ValueKey('standard_list_item_transaction_details_fee_key'),
737 + ),
738 + if (key?.isNotEmpty ?? false)
739 + StandartListItem(
740 + title: S.current.transaction_key,
741 + value: key!,
742 + key: ValueKey('standard_list_item_transaction_key'),
743 + ),
744 ];
745
746 if (tx.direction == TransactionDirection.incoming) {
@@ -490,14 +750,23 @@ abstract class TransactionDetailsViewModelBase with Store {
750
751 if (address.isNotEmpty) {
752 isRecipientAddressShown = true;
493 - _items.add(StandartListItem(
494 - title: S.current.transaction_details_recipient_address,
495 - value: address,
496 - ));
753 + _items.add(
754 + StandartListItem(
755 + title: S.current.transaction_details_recipient_address,
756 + value: address,
757 + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
758 + ),
759 + );
760 }
761
762 if (label.isNotEmpty) {
500 - _items.add(StandartListItem(title: S.current.address_label, value: label));
763 + _items.add(
764 + StandartListItem(
765 + title: S.current.address_label,
766 + value: label,
767 + key: ValueKey('standard_list_item_address_label_key'),
768 + ),
769 + );
770 }
771 } catch (e) {
772 print(e.toString());
lib/view_model/wallet_keys_view_model.dart
+121 -60
@@ -10,6 +10,7 @@ import 'package:cw_core/transaction_info.dart';
10 import 'package:cw_core/wallet_base.dart';
11 import 'package:cw_core/wallet_type.dart';
12 import 'package:cw_monero/monero_wallet.dart';
13 +import 'package:flutter/foundation.dart';
14 import 'package:mobx/mobx.dart';
15 import 'package:polyseed/polyseed.dart';
16
@@ -24,6 +25,7 @@ abstract class WalletKeysViewModelBase with Store {
25 _appStore.wallet!.type == WalletType.bitcoinCash
26 ? S.current.wallet_seed
27 : S.current.wallet_keys,
28 + _walletName = _appStore.wallet!.type.name,
29 _restoreHeight = _appStore.wallet!.walletInfo.restoreHeight,
30 _restoreHeightByTransactions = 0,
31 items = ObservableList<StandartListItem>() {
@@ -38,12 +40,10 @@ abstract class WalletKeysViewModelBase with Store {
40 _appStore.wallet!.type == WalletType.wownero) {
41 final accountTransactions = _getWalletTransactions(_appStore.wallet!);
42 if (accountTransactions.isNotEmpty) {
41 - final incomingAccountTransactions = accountTransactions
42 - .where((tx) => tx.direction == TransactionDirection.incoming);
43 + final incomingAccountTransactions =
44 + accountTransactions.where((tx) => tx.direction == TransactionDirection.incoming);
45 if (incomingAccountTransactions.isNotEmpty) {
44 - incomingAccountTransactions
45 - .toList()
46 - .sort((a, b) => a.date.compareTo(b.date));
46 + incomingAccountTransactions.toList().sort((a, b) => a.date.compareTo(b.date));
47 _restoreHeightByTransactions = _getRestoreHeightByTransactions(
48 _appStore.wallet!.type, incomingAccountTransactions.first.date);
49 }
@@ -55,6 +55,10 @@ abstract class WalletKeysViewModelBase with Store {
55
56 final String title;
57
58 + final String _walletName;
59 +
60 + AppStore get appStore => _appStore;
61 +
62 final AppStore _appStore;
63
64 final int _restoreHeight;
@@ -70,37 +74,56 @@ abstract class WalletKeysViewModelBase with Store {
74 items.addAll([
75 if (keys['publicSpendKey'] != null)
76 StandartListItem(
73 - title: S.current.spend_key_public,
74 - value: keys['publicSpendKey']!),
77 + key: ValueKey('${_walletName}_wallet_public_spend_key_item_key'),
78 + title: S.current.spend_key_public,
79 + value: keys['publicSpendKey']!,
80 + ),
81 if (keys['privateSpendKey'] != null)
82 StandartListItem(
77 - title: S.current.spend_key_private,
78 - value: keys['privateSpendKey']!),
83 + key: ValueKey('${_walletName}_wallet_private_spend_key_item_key'),
84 + title: S.current.spend_key_private,
85 + value: keys['privateSpendKey']!,
86 + ),
87 if (keys['publicViewKey'] != null)
88 StandartListItem(
81 - title: S.current.view_key_public, value: keys['publicViewKey']!),
89 + key: ValueKey('${_walletName}_wallet_public_view_key_item_key'),
90 + title: S.current.view_key_public,
91 + value: keys['publicViewKey']!,
92 + ),
93 if (keys['privateViewKey'] != null)
94 StandartListItem(
84 - title: S.current.view_key_private,
85 - value: keys['privateViewKey']!),
95 + key: ValueKey('${_walletName}_wallet_private_view_key_item_key'),
96 + title: S.current.view_key_private,
97 + value: keys['privateViewKey']!,
98 + ),
99 if (_appStore.wallet!.seed!.isNotEmpty)
87 - StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
100 + StandartListItem(
101 + key: ValueKey('${_walletName}_wallet_seed_item_key'),
102 + title: S.current.wallet_seed,
103 + value: _appStore.wallet!.seed!,
104 + ),
105 ]);
106
90 - if (_appStore.wallet?.seed != null &&
91 - Polyseed.isValidSeed(_appStore.wallet!.seed!)) {
107 + if (_appStore.wallet?.seed != null && Polyseed.isValidSeed(_appStore.wallet!.seed!)) {
108 final lang = PolyseedLang.getByPhrase(_appStore.wallet!.seed!);
93 - items.add(StandartListItem(
109 + items.add(
110 + StandartListItem(
111 + key: ValueKey('${_walletName}_wallet_seed_legacy_item_key'),
112 title: S.current.wallet_seed_legacy,
95 - value: (_appStore.wallet as MoneroWalletBase)
96 - .seedLegacy(lang.nameEnglish)));
113 + value: (_appStore.wallet as MoneroWalletBase).seedLegacy(lang.nameEnglish),
114 + ),
115 + );
116 }
117
118 final restoreHeight = monero!.getRestoreHeight(_appStore.wallet!);
119 if (restoreHeight != null) {
101 - items.add(StandartListItem(
120 + items.add(
121 + StandartListItem(
122 + key: ValueKey('${_walletName}_wallet_restore_height_item_key'),
123 title: S.current.wallet_recovery_height,
103 - value: restoreHeight.toString()));
124 + value: restoreHeight.toString(),
125 + ),
126 + );
127 }
128 }
129
@@ -110,21 +133,34 @@ abstract class WalletKeysViewModelBase with Store {
133 items.addAll([
134 if (keys['publicSpendKey'] != null)
135 StandartListItem(
113 - title: S.current.spend_key_public,
114 - value: keys['publicSpendKey']!),
136 + key: ValueKey('${_walletName}_wallet_public_spend_key_item_key'),
137 + title: S.current.spend_key_public,
138 + value: keys['publicSpendKey']!,
139 + ),
140 if (keys['privateSpendKey'] != null)
141 StandartListItem(
117 - title: S.current.spend_key_private,
118 - value: keys['privateSpendKey']!),
142 + key: ValueKey('${_walletName}_wallet_private_spend_key_item_key'),
143 + title: S.current.spend_key_private,
144 + value: keys['privateSpendKey']!,
145 + ),
146 if (keys['publicViewKey'] != null)
147 StandartListItem(
121 - title: S.current.view_key_public, value: keys['publicViewKey']!),
148 + key: ValueKey('${_walletName}_wallet_public_view_key_item_key'),
149 + title: S.current.view_key_public,
150 + value: keys['publicViewKey']!,
151 + ),
152 if (keys['privateViewKey'] != null)
153 StandartListItem(
124 - title: S.current.view_key_private,
125 - value: keys['privateViewKey']!),
154 + key: ValueKey('${_walletName}_wallet_private_view_key_item_key'),
155 + title: S.current.view_key_private,
156 + value: keys['privateViewKey']!,
157 + ),
158 if (_appStore.wallet!.seed!.isNotEmpty)
127 - StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
159 + StandartListItem(
160 + key: ValueKey('${_walletName}_wallet_seed_item_key'),
161 + title: S.current.wallet_seed,
162 + value: _appStore.wallet!.seed!,
163 + ),
164 ]);
165 }
166
@@ -134,29 +170,45 @@ abstract class WalletKeysViewModelBase with Store {
170 items.addAll([
171 if (keys['publicSpendKey'] != null)
172 StandartListItem(
137 - title: S.current.spend_key_public,
138 - value: keys['publicSpendKey']!),
173 + key: ValueKey('${_walletName}_wallet_public_spend_key_item_key'),
174 + title: S.current.spend_key_public,
175 + value: keys['publicSpendKey']!,
176 + ),
177 if (keys['privateSpendKey'] != null)
178 StandartListItem(
141 - title: S.current.spend_key_private,
142 - value: keys['privateSpendKey']!),
179 + key: ValueKey('${_walletName}_wallet_private_spend_key_item_key'),
180 + title: S.current.spend_key_private,
181 + value: keys['privateSpendKey']!,
182 + ),
183 if (keys['publicViewKey'] != null)
184 StandartListItem(
145 - title: S.current.view_key_public, value: keys['publicViewKey']!),
185 + key: ValueKey('${_walletName}_wallet_public_view_key_item_key'),
186 + title: S.current.view_key_public,
187 + value: keys['publicViewKey']!,
188 + ),
189 if (keys['privateViewKey'] != null)
190 StandartListItem(
148 - title: S.current.view_key_private,
149 - value: keys['privateViewKey']!),
191 + key: ValueKey('${_walletName}_wallet_private_view_key_item_key'),
192 + title: S.current.view_key_private,
193 + value: keys['privateViewKey']!,
194 + ),
195 if (_appStore.wallet!.seed!.isNotEmpty)
151 - StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
196 + StandartListItem(
197 + key: ValueKey('${_walletName}_wallet_seed_item_key'),
198 + title: S.current.wallet_seed,
199 + value: _appStore.wallet!.seed!,
200 + ),
201 ]);
202
154 - if (_appStore.wallet?.seed != null &&
155 - Polyseed.isValidSeed(_appStore.wallet!.seed!)) {
203 + if (_appStore.wallet?.seed != null && Polyseed.isValidSeed(_appStore.wallet!.seed!)) {
204 final lang = PolyseedLang.getByPhrase(_appStore.wallet!.seed!);
157 - items.add(StandartListItem(
205 + items.add(
206 + StandartListItem(
207 + key: ValueKey('${_walletName}_wallet_seed_legacy_item_key'),
208 title: S.current.wallet_seed_legacy,
159 - value: wownero!.getLegacySeed(_appStore.wallet!, lang.nameEnglish)));
209 + value: wownero!.getLegacySeed(_appStore.wallet!, lang.nameEnglish),
210 + ),
211 + );
212 }
213 }
214
@@ -173,7 +225,10 @@ abstract class WalletKeysViewModelBase with Store {
225 // if (keys['publicKey'] != null)
226 // StandartListItem(title: S.current.public_key, value: keys['publicKey']!),
227 StandartListItem(
176 - title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
228 + key: ValueKey('${_walletName}_wallet_seed_item_key'),
229 + title: S.current.wallet_seed,
230 + value: _appStore.wallet!.seed!,
231 + ),
232 ]);
233 }
234
@@ -183,31 +238,43 @@ abstract class WalletKeysViewModelBase with Store {
238 items.addAll([
239 if (_appStore.wallet!.privateKey != null)
240 StandartListItem(
186 - title: S.current.private_key,
187 - value: _appStore.wallet!.privateKey!),
241 + key: ValueKey('${_walletName}_wallet_private_key_item_key'),
242 + title: S.current.private_key,
243 + value: _appStore.wallet!.privateKey!,
244 + ),
245 if (_appStore.wallet!.seed != null)
246 StandartListItem(
190 - title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
247 + key: ValueKey('${_walletName}_wallet_seed_item_key'),
248 + title: S.current.wallet_seed,
249 + value: _appStore.wallet!.seed!,
250 + ),
251 ]);
252 }
253
194 - bool nanoBased = _appStore.wallet!.type == WalletType.nano ||
195 - _appStore.wallet!.type == WalletType.banano;
254 + bool nanoBased =
255 + _appStore.wallet!.type == WalletType.nano || _appStore.wallet!.type == WalletType.banano;
256
257 if (nanoBased) {
258 // we always have the hex version of the seed and private key:
259 items.addAll([
260 if (_appStore.wallet!.seed != null)
261 StandartListItem(
202 - title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
262 + key: ValueKey('${_walletName}_wallet_seed_item_key'),
263 + title: S.current.wallet_seed,
264 + value: _appStore.wallet!.seed!,
265 + ),
266 if (_appStore.wallet!.hexSeed != null)
267 StandartListItem(
205 - title: S.current.seed_hex_form,
206 - value: _appStore.wallet!.hexSeed!),
268 + key: ValueKey('${_walletName}_wallet_hex_seed_key'),
269 + title: S.current.seed_hex_form,
270 + value: _appStore.wallet!.hexSeed!,
271 + ),
272 if (_appStore.wallet!.privateKey != null)
273 StandartListItem(
209 - title: S.current.private_key,
210 - value: _appStore.wallet!.privateKey!),
274 + key: ValueKey('${_walletName}_wallet_private_key_item_key'),
275 + title: S.current.private_key,
276 + value: _appStore.wallet!.privateKey!,
277 + ),
278 ]);
279 }
280 }
@@ -273,8 +340,7 @@ abstract class WalletKeysViewModelBase with Store {
340 if (_appStore.wallet!.seed != null) 'seed': _appStore.wallet!.seed!,
341 if (_appStore.wallet!.seed == null && _appStore.wallet!.hexSeed != null)
342 'hexSeed': _appStore.wallet!.hexSeed!,
276 - if (_appStore.wallet!.seed == null &&
277 - _appStore.wallet!.privateKey != null)
343 + if (_appStore.wallet!.seed == null && _appStore.wallet!.privateKey != null)
344 'private_key': _appStore.wallet!.privateKey!,
345 if (restoreHeightResult != null) ...{'height': restoreHeightResult},
346 if (_appStore.wallet!.passphrase != null) 'passphrase': _appStore.wallet!.passphrase!
@@ -292,11 +358,7 @@ abstract class WalletKeysViewModelBase with Store {
358 } else if (wallet.type == WalletType.haven) {
359 return haven!.getTransactionHistory(wallet).transactions.values.toList();
360 } else if (wallet.type == WalletType.wownero) {
295 - return wownero!
296 - .getTransactionHistory(wallet)
297 - .transactions
298 - .values
299 - .toList();
361 + return wownero!.getTransactionHistory(wallet).transactions.values.toList();
362 }
363 return [];
364 }
@@ -312,6 +374,5 @@ abstract class WalletKeysViewModelBase with Store {
374 return 0;
375 }
376
315 - String getRoundedRestoreHeight(int height) =>
316 - ((height / 1000).floor() * 1000).toString();
377 + String getRoundedRestoreHeight(int height) => ((height / 1000).floor() * 1000).toString();
378 }
tool/utils/secret_key.dart
+23
@@ -44,12 +44,35 @@ class SecretKey {
44 SecretKey('cakePayApiKey', () => ''),
45 SecretKey('CSRFToken', () => ''),
46 SecretKey('authorization', () => ''),
47 + SecretKey('moneroTestWalletSeeds', () => ''),
48 + SecretKey('moneroLegacyTestWalletSeeds ', () => ''),
49 + SecretKey('bitcoinTestWalletSeeds', () => ''),
50 + SecretKey('ethereumTestWalletSeeds', () => ''),
51 + SecretKey('litecoinTestWalletSeeds', () => ''),
52 + SecretKey('bitcoinCashTestWalletSeeds', () => ''),
53 + SecretKey('polygonTestWalletSeeds', () => ''),
54 + SecretKey('solanaTestWalletSeeds', () => ''),
55 + SecretKey('polygonTestWalletSeeds', () => ''),
56 + SecretKey('tronTestWalletSeeds', () => ''),
57 + SecretKey('nanoTestWalletSeeds', () => ''),
58 + SecretKey('wowneroTestWalletSeeds', () => ''),
59 + SecretKey('moneroTestWalletReceiveAddress', () => ''),
60 + SecretKey('bitcoinTestWalletReceiveAddress', () => ''),
61 + SecretKey('ethereumTestWalletReceiveAddress', () => ''),
62 + SecretKey('litecoinTestWalletReceiveAddress', () => ''),
63 + SecretKey('bitco inCashTestWalletReceiveAddress', () => ''),
64 + SecretKey('polygonTestWalletReceiveAddress', () => ''),
65 + SecretKey('solanaTestWalletReceiveAddress', () => ''),
66 + SecretKey('tronTestWalletReceiveAddress', () => ''),
67 + SecretKey('nanoTestWalletReceiveAddress', () => ''),
68 + SecretKey('wowneroTestWalletReceiveAddress', () => ''),
69 SecretKey('etherScanApiKey', () => ''),
70 SecretKey('polygonScanApiKey', () => ''),
71 SecretKey('letsExchangeBearerToken', () => ''),
72 SecretKey('letsExchangeAffiliateId', () => ''),
73 SecretKey('stealthExBearerToken', () => ''),
74 SecretKey('stealthExAdditionalFeePercent', () => ''),
75 + SecretKey('moneroTestWalletBlockHeight', () => ''),
76 ];
77
78 static final evmChainsSecrets = [