Merge pull request #1455 from cypherstack/fix/desktop-pw-reset
What changed, and why it matters
This commit fixes the desktop "forgot password" reset flow in Stack Wallet. It adds integration tests that verify the app can securely wipe its own data when a user forgets the desktop password, and it updates the Firo cache worker to close cleanly during that wipe. The change is defensive: it makes sure databases and background workers shut down before files are deleted, so leftover data or crashed workers do not leave sensitive information behind.
Review the full diff of lib/utilities/desktop_startup_and_reset.dart and the new desktop_forgot_password tests to confirm the reset ordering, error handling, and file-deletion scope are correct. Run the new integration tests on Linux, macOS, and Windows before release. Consider whether any other background services (e.g., tor, wallet daemons) need an equivalent graceful-shutdown step before file deletion.
Security signals we found
Desktop password reset now closes Firo cache workers and SQLite databases before deleting app data, reducing the risk of data leakage or corruption during wipe
New integration tests assert that a successful reset removes password store (hive/desktopdata.hive), wallet key store (isar/desktopStore.isar), and wallet files while preserving backups and tor state
Failed reset scenario leaves a .reset-pending marker and removes password/key stores first, preventing the reset from being undone after partial deletion
Unfinished reset retry path deletes data and opens a fresh first-run screen; quit path preserves everything
Test harness isolates app data in a temp folder and redirects path_provider so logs and files do not touch the real documents folder
Evidence from the diff
The merge refactors the desktop startup/reset logic and adds a dedicated integration-test suite under integration_test/desktop_forgot_password/. A new harness launches the real app against a throwaway directory, intercepts exit(), and checks which files remain after a reset. The tests cover: successful full reset, cancelled reset, failed reset leaving a pending marker, unfinished reset on quit, and unfinished reset retry. Production code changes include FiroCacheCoordinator.close() and _FiroCacheWorker.close() to terminate SQLite databases and the worker isolate before the reset deletes files. The commit also removes a large set of stale mobile integration-test bots and tests, and adjusts analysis_options.yaml to stop excluding integration_test/**.dart.
Changed components
lib/utilities/desktop_startup_and_reset.dartlib/db/sqlite/firo_cache_coordinator.dartlib/db/sqlite/firo_cache_worker.dartlib/main.dartlib/pages_desktop_specific/password/delete_password_warning_view.dartintegration_test/desktop_forgot_password/*scripts/test_desktop_forgot_password.shInspect captured patch +1077 / −2971
### analysis_options.yaml
@@ -13,8 +13,6 @@ analyzer:
- "**/*.g.dart"
- "**/*.freezed.dart"
- "test/.test_coverage.dart"
- - "integrated_test/**"
- - "integration_test/**.dart"
- "crypto_plugins/**"
- "bin/cache/**"
- "lib/generated_plugin_registrant.dart"
### integration_test/bot_runners/create_wallet_until_pin_confirmation.dart
@@ -1,46 +0,0 @@
-import '../bots/onboarding/create_pin_view_bot.dart';
-import '../bots/onboarding/name_your_wallet_view_bot.dart';
-import '../bots/onboarding/terms_and_conditions_bot.dart';
-
-Future<void> createWalletUntilPinConfirmation(
- TermsAndConditionsViewBot termsAndConditionsViewBot,
- NameYourWalletViewBot nameYourWalletViewBot,
- CreatePinViewBot createPinViewBot,
-) async {
- await termsAndConditionsViewBot.ensureVisible();
-
- // test scrolling of terms & conditions
- await termsAndConditionsViewBot.scrollDown();
- await termsAndConditionsViewBot.scrollUp();
-
- // accept terms & conditions
- await termsAndConditionsViewBot.tapIAccept();
-
- await nameYourWalletViewBot.ensureVisible();
- // test tap back
- await nameYourWalletViewBot.tapBack();
- await termsAndConditionsViewBot.ensureVisible();
- // accept terms & conditions again
- await termsAndConditionsViewBot.tapIAccept();
- await nameYourWalletViewBot.ensureVisible();
-
- // enter a wallet name
- await nameYourWalletViewBot.enterWalletName("My Firo Wallet");
-
- // tap next
- await nameYourWalletViewBot.tapNext();
-
- await createPinViewBot.ensureVisible();
- // tap back
- await createPinViewBot.tapBack();
- await nameYourWalletViewBot.ensureVisible();
-
- // tap next
- await nameYourWalletViewBot.tapNext();
- await createPinViewBot.ensureVisible();
-
- // enter a pin
- await createPinViewBot.enterPin();
- // confirm pin
- await createPinViewBot.confirmMatchedPin();
-}
### integration_test/bots/addressbook/add_address_book_entry_view_bot.dart
@@ -1,43 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/address_book_view/subviews/add_address_book_entry_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/simple_button.dart';
-
-class AddAddressBookEntryViewBot {
- final WidgetTester tester;
-
- const AddAddressBookEntryViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(AddAddressBookEntryView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byType(AppBarIconButton));
- await tester.pumpAndSettle();
- }
-
- Future<void> enterAddress(String text) async {
- await tester.enterText(
- find.byKey(Key("addAddressBookEntryViewAddressField")), text);
- await tester.pumpAndSettle();
- }
-
- Future<void> enterName(String text) async {
- await tester.enterText(
- find.byKey(Key("addAddressBookEntryViewNameField")), text);
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCancel() async {
- await tester.tap(find.byType(SimpleButton));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapSave() async {
- await tester.tap(find.byType(GradientButton));
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/addressbook/address_book_card_bot.dart
@@ -1,35 +0,0 @@
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/widgets/address_book_card.dart';
-
-class AddressBookCardBot {
- final WidgetTester tester;
-
- const AddressBookCardBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(AddressBookCard));
- }
-
- Future<void> toggleExpandCard() async {
- await tester.tap(find.byType(AddressBookCard));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapDetails() async {
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is SubButton && widget.label == "DETAILS"));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCopy() async {
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is SubButton && widget.label == "COPY"));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapSend() async {
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is SubButton && widget.label == "SEND FIRO"));
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/addressbook/address_book_entry_details_view_bot.dart
@@ -1,59 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/address_book_view/subviews/address_book_entry_details_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-
-class AddressBookEntryDetailsViewBot {
- final WidgetTester tester;
-
- const AddressBookEntryDetailsViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(AddressBookEntryDetailsView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byKey(Key("addressBookDetailsBackButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapMore() async {
- await tester.tap(find.byKey(Key("addressBookDetailsDeleteButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapDelete() async {
- await tester
- .tap(find.byKey(Key("addressBookDetailsContextMenuDeleteButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapConfirmDelete() async {
- await tester
- .tap(find.byKey(Key("deleteContactConfirmationDialogDeleteButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCancelDelete() async {
- await tester
- .tap(find.byKey(Key("deleteContactConfirmationDialogCancelButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCopyAddress() async {
- await tester
- .tap(find.byKey(Key("addressBookEntryDetailsCopyAddressButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapEdit() async {
- await tester
- .tap(find.byKey(Key("addressBookEntryDetailsEditEntryButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapSend() async {
- await tester.tap(find.byType(GradientButton));
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/addressbook/address_book_view_bot.dart
@@ -1,28 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/address_book_view/address_book_view.dart';
-
-class AddressBookViewBot {
- final WidgetTester tester;
-
- const AddressBookViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(AddressBookView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byKey(Key("addressBookBackButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapAdd() async {
- await tester.tap(find.byKey(Key("addressBookAddButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> enterQuery(String query) async {
- await tester.enterText(find.byType(TextField), query);
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/addressbook/edit_address_book_entry_view_bot.dart
@@ -1,40 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/address_book_view/subviews/edit_address_book_entry_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/simple_button.dart';
-
-class EditAddressBookEntryViewBot {
- final WidgetTester tester;
-
- const EditAddressBookEntryViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(EditAddressBookEntryView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byKey(Key("editAddressBookEntryBackButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCancel() async {
- await tester.tap(find.byType(SimpleButton));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapSave() async {
- await tester.tap(find.byType(GradientButton));
- await tester.pumpAndSettle();
- }
-
- Future<void> enterAddress(String text) async {
- await tester.enterText(
- find.byKey(Key("editAddressBookEntryAddressFieldKey")), text);
- }
-
- Future<void> enterName(String text) async {
- await tester.enterText(
- find.byKey(Key("editAddressBookEntryNameFieldKey")), text);
- }
-}
### integration_test/bots/lockscreen_view_bot.dart
@@ -1,30 +0,0 @@
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/lockscreen_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_pin_put/pin_keyboard.dart';
-
-class LockscreenViewBot {
- final WidgetTester tester;
-
- const LockscreenViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(LockscreenView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byType(AppBarIconButton));
- await tester.pumpAndSettle();
- }
-
- Future<void> enterPin(String pin) async {
- for (int i = 0; i < pin.length - 1; i++) {
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == pin[i]));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- }
- await tester.tap(find.byWidgetPredicate((widget) =>
- widget is NumberKey && widget.number == pin[pin.length - 1]));
- await tester.pumpAndSettle(Duration(seconds: 1));
- }
-}
### integration_test/bots/main_view_bot.dart
@@ -1,38 +0,0 @@
-import 'package:flutter/cupertino.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/main_view.dart';
-
-class MainViewBot {
- final WidgetTester tester;
-
- const MainViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(MainView));
- }
-
- Future<void> tapRefresh() async {
- await tester.tap(find.byKey(Key("mainViewRefreshButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapSettings() async {
- await tester.tap(find.byKey(Key("mainViewSettingsButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapSend() async {
- await tester.tap(find.byKey(Key("mainViewNavBarSendItemKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapWallet() async {
- await tester.tap(find.byKey(Key("mainViewNavBarWalletItemKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapReceive() async {
- await tester.tap(find.byKey(Key("mainViewNavBarReceiveItemKey")));
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/onboarding/backup_key_view_bot.dart
@@ -1,65 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/onboarding_view/backup_key_view.dart';
-import 'package:stackwallet/utilities/misc_global_constants.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-
-class BackupKeyViewBot {
- final WidgetTester tester;
-
- const BackupKeyViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(BackupKeyView));
- }
-
- Future<void> tapVerify() async {
- final buttonFinder = find.byType(GradientButton);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-
- Future<void> tapQrCode() async {
- final buttonFinder = find.byKey(Key("backupKeyQrCodeButtonKey"));
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-
- Future<void> tapSkip() async {
- final buttonFinder = find.byType(TextButton);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCopy() async {
- final buttonFinder = find.byKey(Key("backupKeyViewCopyButtonKey"));
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-
- Future<void> tapBack() async {
- final buttonFinder = find.byType(AppBarIconButton);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-
- Future<List<String?>> displayedMnemonic() async {
- List<String?> words = [];
- for (int i = 0; i < CampfireConstants.seedPhraseWordCount; i++) {
- final text = find
- .byKey(Key("mnemonic_word_text_$i"))
- .evaluate()
- .single
- .widget as Text;
-
- words.add(text.data);
- }
- return words;
- }
-}
### integration_test/bots/onboarding/backup_key_warning_bot.dart
@@ -1,43 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/onboarding_view/backup_key_warning_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-
-class BackupKeyWarningViewBot {
- final WidgetTester tester;
-
- const BackupKeyWarningViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(BackupKeyWarningView));
- }
-
- Future<void> tapViewBackupKey() async {
- final buttonFinder = find.byType(GradientButton);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCheckBox() async {
- final buttonFinder = find.byType(Checkbox);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-
- Future<void> tapSkip() async {
- final buttonFinder = find.byType(TextButton);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-
- Future<void> tapBack() async {
- final buttonFinder = find.byType(AppBarIconButton);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/onboarding/create_pin_view_bot.dart
@@ -1,66 +0,0 @@
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/onboarding_view/create_pin_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_pin_put/pin_keyboard.dart';
-
-class CreatePinViewBot {
- final WidgetTester tester;
-
- const CreatePinViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(CreatePinView));
- }
-
- Future<void> enterPin() async {
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "1"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "2"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "3"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "4"));
- await tester.pumpAndSettle(Duration(seconds: 1));
- }
-
- Future<void> confirmMatchedPin() async {
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "1"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "2"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "3"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "4"));
- await tester.pump(Duration(seconds: 1));
- }
-
- Future<void> confirmUnmatchedPin() async {
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "7"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "9"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "8"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "5"));
- await tester.pump(Duration(seconds: 1));
- }
-
- Future<void> tapBack() async {
- final buttonFinder = find.byType(AppBarIconButton);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/onboarding/name_your_wallet_view_bot.dart
@@ -1,36 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/onboarding_view/name_your_wallet_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-
-class NameYourWalletViewBot {
- final WidgetTester tester;
-
- const NameYourWalletViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(NameYourWalletView));
- }
-
- Future<void> enterWalletName(String name) async {
- final textFieldFinder = find.byType(TextField);
- await tester.ensureVisible(textFieldFinder);
- await tester.enterText(textFieldFinder, name);
- await tester.pumpAndSettle();
- }
-
- Future<void> tapNext() async {
- final buttonFinder = find.byType(GradientButton);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-
- Future<void> tapBack() async {
- final buttonFinder = find.byType(AppBarIconButton);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/onboarding/onboarding_view_bot.dart
@@ -1,28 +0,0 @@
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/onboarding_view/onboarding_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/simple_button.dart';
-
-class OnboardingViewBot {
- final WidgetTester tester;
-
- const OnboardingViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(OnboardingView));
- }
-
- Future<void> tapCreateNewWallet() async {
- final buttonFinder = find.byType(GradientButton);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-
- Future<void> tapRestoreWallet() async {
- final buttonFinder = find.byType(SimpleButton);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/onboarding/restore_wallet_form_view_bot.dart
@@ -1,59 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/onboarding_view/restore_wallet_form_view.dart';
-
-class RestoreWalletFormViewBot {
- final WidgetTester tester;
-
- const RestoreWalletFormViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(RestoreWalletFormView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byKey(Key("onboardingAppBarBackButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapScanQrCode() async {
- await tester.tap(find.byKey(Key("restoreWalletViewScanQRButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCancelScanQrCode() async {
- await tester.tap(find.text("CANCEL"));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapPaste() async {
- await tester.tap(find.byKey(Key("restoreWalletViewPasteButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapRestore(bool settle) async {
- await tester.tap(find.byKey(Key("restoreMnemonicViewRestoreButtonKey")));
- if (settle) {
- await tester.pumpAndSettle();
- } else {
- await tester.pump(Duration(seconds: 1));
- }
- }
-
- Future<void> tapCancelRestore() async {
- await tester
- .tap(find.byKey(Key("restoreWalletWaitingDialogCancelButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> enterWord(String word, int position) async {
- await tester.enterText(
- find.byKey(Key("restoreMnemonicFormField_$position")), word);
- }
-
- Future<void> scrollDown() async {
- await tester.fling(
- find.byType(SingleChildScrollView), Offset(0, -500), 10000);
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/onboarding/terms_and_conditions_bot.dart
@@ -1,41 +0,0 @@
-import 'package:flutter/widgets.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/onboarding_view/terms_and_conditions_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-
-class TermsAndConditionsViewBot {
- final WidgetTester tester;
-
- const TermsAndConditionsViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(TermsAndConditionsView));
- }
-
- Future<void> tapIAccept() async {
- final buttonFinder = find.byType(GradientButton);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-
- Future<void> tapBack() async {
- final buttonFinder = find.byType(AppBarIconButton);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-
- Future<void> scrollDown() async {
- await tester.fling(
- find.byType(SingleChildScrollView), Offset(0, -1000), 10000);
- await tester.pumpAndSettle();
- }
-
- Future<void> scrollUp() async {
- await tester.fling(
- find.byType(SingleChildScrollView), Offset(0, 1000), 10000);
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/onboarding/verify_backup_key_view_bot.dart
@@ -1,56 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/onboarding_view/verify_backup_key_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-
-class VerifyBackupKeyViewBot {
- final WidgetTester tester;
-
- const VerifyBackupKeyViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(VerifyBackupKeyView));
- }
-
- Future<void> tapConfirm() async {
- await tester.fling(
- find.byType(SingleChildScrollView), Offset(0, -500), 1000);
- await tester.pumpAndSettle();
- final buttonFinder = find.byType(GradientButton);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-
- Future<void> tapBack() async {
- final buttonFinder = find.byType(AppBarIconButton);
- await tester.ensureVisible(buttonFinder);
- await tester.tap(buttonFinder);
- await tester.pumpAndSettle();
- }
-
- Future<void> enterRequestedWord(List<String?> words) async {
- String text = (find
- .byKey(Key("nThWordVerificationString"))
- .evaluate()
- .single
- .widget as Text)
- .data!;
-
- int? index;
-
- if (text.length == 33) {
- index = int.parse(text.substring(9, 11));
- } else if (text.length == 32) {
- index = int.parse(text.substring(9, 10));
- }
-
- if (index == null) {
- throw Exception("Backup key verification text string failed test");
- }
-
- await tester.enterText(find.byType(TextField), words[index - 1]!);
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/receive_view_bot.dart
@@ -1,22 +0,0 @@
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/wallet_view/receive_view.dart';
-
-class ReceiveViewBot {
- final WidgetTester tester;
-
- const ReceiveViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(ReceiveView));
- }
-//
-// Future<void> tapX() async {
-// await tester.tap(find.byKey(Key("cancelTransactionSearchAppBarButton")));
-// await tester.pumpAndSettle();
-// }
-//
-// Future<void> tapCancel() async {
-// await tester.tap(find.byType(SimpleButton));
-// await tester.pumpAndSettle();
-// }
-}
### integration_test/bots/rescan_warning_view_bot.dart
@@ -1,56 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/settings_view/settings_subviews/wallet_settings_subviews/rescan_warning_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-
-class RescanWarningViewBot {
- final WidgetTester tester;
-
- const RescanWarningViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(RescanWarningView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byKey(Key("settingsAppBarBackButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapQrCode() async {
- await tester.tap(find.byKey(Key("rescanWarningShowQrCodeButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCancelQrCode() async {
- await tester
- .tap(find.byKey(Key("rescanWarningQrCodePopupCancelButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCopy() async {
- await tester.tap(find.byKey(Key("rescanWarningCopySeedButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapContinue() async {
- await tester.tap(find.byType(GradientButton));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapConfirmContinue() async {
- await tester.tap(find.byKey(Key("rescanWarningContinueRescanButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCancelContinue() async {
- await tester.tap(find.byKey(Key("rescanWarningContinueCancelButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapOkOnRescanFailedDialog() async {
- await tester
- .tap(find.byKey(Key("rescanWarningViewRescanFailedOkButtonKey")));
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/send_view_bot.dart
@@ -1,22 +0,0 @@
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/wallet_view/send_view.dart';
-
-class SendViewBot {
- final WidgetTester tester;
-
- const SendViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(SendView));
- }
- //
- // Future<void> tapX() async {
- // await tester.tap(find.byKey(Key("cancelTransactionSearchAppBarButton")));
- // await tester.pumpAndSettle();
- // }
- //
- // Future<void> tapCancel() async {
- // await tester.tap(find.byType(SimpleButton));
- // await tester.pumpAndSettle();
- // }
-}
### integration_test/bots/settings/currency_view_bot.dart
@@ -1,26 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/settings_view/settings_subviews/currency_view.dart';
-import 'package:stackwallet/utilities/cfcolors.dart';
-
-class CurrencyViewBot {
- final WidgetTester tester;
-
- const CurrencyViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(CurrencyView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byKey(Key("settingsAppBarBackButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCurrency(String code) async {
- await tester.tap(find.text(code));
- await tester.pumpAndSettle();
- final text = find.text(code).evaluate().single.widget as Text;
- expect(text.style!.color, CFColors.spark);
- }
-}
### integration_test/bots/settings/network_settings/add_custom_node_view_bot.dart
@@ -1,58 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/simple_button.dart';
-
-class AddCustomNodeViewBot {
- final WidgetTester tester;
-
- const AddCustomNodeViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(AddCustomNodeView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byType(AppBarIconButton));
- await tester.pumpAndSettle();
- }
-
- Future<void> enterNodeName(String name) async {
- await tester.enterText(
- find.byKey(Key("addCustomNodeNodeNameFieldKey")), name);
- await tester.pumpAndSettle();
- }
-
- Future<void> enterNodeAddress(String address) async {
- await tester.enterText(
- find.byKey(Key("addCustomNodeNodeAddressFieldKey")), address);
- await tester.pumpAndSettle();
- }
-
- Future<void> enterNodePort(String port) async {
- await tester.enterText(
- find.byKey(Key("addCustomNodeNodePortFieldKey")), port);
- await tester.pumpAndSettle();
- }
-
- Future<void> tapSSLCheckbox() async {
- await tester.tap(find.byType(Checkbox));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapTestConnection() async {
- await tester.tap(find.byType(SimpleButton));
- await tester.pump(Duration(milliseconds: 500));
- }
-
- Future<void> tapSave() async {
- await tester.fling(
- find.byType(SingleChildScrollView), Offset(0, -500), 10000);
- await tester.pumpAndSettle();
- await tester.tap(find.byType(GradientButton));
- await tester.pumpAndSettle();
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/settings/network_settings/network_settings_view_bot.dart
@@ -1,28 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/settings_view/settings_subviews/network_settings_view.dart';
-
-class NetworkSettingsViewBot {
- final WidgetTester tester;
-
- const NetworkSettingsViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(NetworkSettingsView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byKey(Key("settingsAppBarBackButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapAdd() async {
- await tester.tap(find.byKey(Key("networkSettingsAddNodeButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapNode(String name) async {
- await tester.tap(find.byKey(Key("networkSettingsViewNodeCard_$name")));
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/settings/network_settings/node_details_view_bot.dart
@@ -1,79 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/settings_view/settings_subviews/network_settings_subviews/node_details_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/simple_button.dart';
-
-class NodeDetailsViewBot {
- final WidgetTester tester;
-
- const NodeDetailsViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(NodeDetailsView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byKey(Key("nodeDetailsViewBackButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapMore() async {
- await tester.tap(find.byKey(Key("nodeDetailsViewMoreButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapMoreAndEdit() async {
- await tapMore();
- await tester.tap(find.text("Edit node"));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapMoreAndDelete() async {
- await tapMore();
- await tester.tap(find.text("Delete node"));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCancelDelete() async {
- await tester
- .tap(find.byKey(Key("nodeDetailsConfirmDeleteCancelButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapConfirmDelete() async {
- await tester
- .tap(find.byKey(Key("nodeDetailsConfirmDeleteConfirmButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> enterName(String name) async {
- await tester.enterText(find.byKey(Key("editNodeNodeNameFieldKey")), name);
- await tester.pump();
- }
-
- Future<void> enterAddress(String address) async {
- await tester.enterText(find.byKey(Key("editNodeAddressFieldKey")), address);
- await tester.pump();
- }
-
- Future<void> enterPort(String port) async {
- await tester.enterText(find.byKey(Key("editNodeNodePortFieldKey")), port);
- await tester.pump();
- }
-
- Future<void> tapUseSSLCheckbox() async {
- await tester.tap(find.byType(Checkbox));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapTestConnection() async {
- await tester.tap(find.byType(SimpleButton));
- await tester.pump(Duration(milliseconds: 500));
- }
-
- Future<void> tapSave() async {
- await tester.tap(find.byType(GradientButton));
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/settings/settings_view_bot.dart
@@ -1,48 +0,0 @@
-import 'package:flutter/cupertino.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/settings_view/settings_view.dart';
-
-class SettingsViewBot {
- final WidgetTester tester;
-
- const SettingsViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(SettingsView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byKey(Key("settingsAppBarBackButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapLogout() async {
- await tester.tap(find.byKey(Key("settingsLogoutAppBarButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapAddressBook() async {
- await tester.tap(find.byKey(Key("settingsOptionAddressBook")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapNetwork() async {
- await tester.tap(find.byKey(Key("settingsOptionNetwork")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapWalletBackup() async {
- await tester.tap(find.byKey(Key("settingsOptionWalletBackup")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapWalletSettings() async {
- await tester.tap(find.byKey(Key("settingsOptionWalletSettings")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCurrency() async {
- await tester.tap(find.byKey(Key("settingsOptionCurrency")));
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/settings/wallet_backup_view_bot.dart
@@ -1,34 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/settings_view/settings_subviews/wallet_backup_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-
-class WalletBackUpViewBot {
- final WidgetTester tester;
-
- const WalletBackUpViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(WalletBackUpView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byType(AppBarIconButton));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapQrCode() async {
- await tester.tap(find.byKey(Key("walletBackupQrCodeButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapQrCodeCancel() async {
- await tester.tap(find.byKey(Key("walletBackupQrCodeCancelButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCopy() async {
- await tester.tap(find.byKey(Key("walletBackupCopyButtonKey")));
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/settings/wallet_settings/change_pin_view_bot.dart
@@ -1,64 +0,0 @@
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/settings_view/settings_subviews/wallet_settings_subviews/change_pin_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_pin_put/pin_keyboard.dart';
-
-class ChangePinViewBot {
- final WidgetTester tester;
-
- const ChangePinViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(ChangePinView));
- }
-
- Future<void> enterPin() async {
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "1"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "2"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "3"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "4"));
- await tester.pumpAndSettle(Duration(seconds: 1));
- }
-
- Future<void> confirmMatchedPin() async {
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "1"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "2"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "3"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "4"));
- await tester.pump(Duration(seconds: 1));
- }
-
- Future<void> confirmUnmatchedPin() async {
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "7"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "9"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "8"));
- await tester.pumpAndSettle(Duration(milliseconds: 500));
- await tester.tap(find.byWidgetPredicate(
- (widget) => widget is NumberKey && widget.number == "5"));
- await tester.pump(Duration(seconds: 1));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byType(AppBarIconButton));
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/settings/wallet_settings/delete_wallet_warning_view_bot.dart
@@ -1,30 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/settings_view/settings_subviews/wallet_settings_subviews/delete_wallet_warning_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/simple_button.dart';
-
-class DeleteWalletWarningViewBot {
- final WidgetTester tester;
-
- const DeleteWalletWarningViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(DeleteWalletWarningView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byKey(Key("settingsAppBarBackButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCancelAndGoBack() async {
- await tester.tap(find.byType(SimpleButton));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapViewBackupKey() async {
- await tester.tap(find.byType(GradientButton));
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/settings/wallet_settings/rename_wallet_view_bot.dart
@@ -1,29 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/settings_view/settings_subviews/wallet_settings_subviews/rename_wallet_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-
-class RenameWalletViewBot {
- final WidgetTester tester;
-
- const RenameWalletViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(RenameWalletView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byKey(Key("settingsAppBarBackButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapSave() async {
- await tester.tap(find.byType(GradientButton));
- await tester.pumpAndSettle();
- }
-
- Future<void> enterWalletName(String name) async {
- await tester.enterText(find.byType(TextField), name);
- await tester.pump();
- }
-}
### integration_test/bots/settings/wallet_settings/wallet_delete_mnemonic_view_bot.dart
@@ -1,49 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/settings_view/settings_subviews/wallet_settings_subviews/wallet_delete_mnemonic_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-
-class WalletDeleteMnemonicViewBot {
- final WidgetTester tester;
-
- const WalletDeleteMnemonicViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(WalletDeleteMnemonicView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byKey(Key("settingsAppBarBackButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapQrCode() async {
- await tester.tap(find.byKey(Key("walletDeleteShowQrCodeButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCancelQrCode() async {
- await tester.tap(find.byKey(Key("deleteWalletQrCodePopupCancelButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCopy() async {
- await tester.tap(find.byKey(Key("walletDeleteCopySeedButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapContinue() async {
- await tester.tap(find.byType(GradientButton));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapConfirmContinue() async {
- await tester.tap(find.byKey(Key("walletDeleteContinueDeleteButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCancelContinue() async {
- await tester.tap(find.byKey(Key("walletDeleteContinueCancelButtonKey")));
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/settings/wallet_settings_view_bot.dart
@@ -1,135 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/notifications/campfire_alert.dart';
-import 'package:stackwallet/pages/settings_view/settings_subviews/wallet_settings_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/simple_button.dart';
-
-class WalletSettingsViewBot {
- final WidgetTester tester;
-
- const WalletSettingsViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(WalletSettingsView));
- }
-
- Future<void> tapBack() async {
- await tester.tap(find.byKey(Key("settingsAppBarBackButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapChangePIN() async {
- await tester.tap(find.byKey(Key("walletSettingsChangePinButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapToggleBiometrics() async {
- await tester
- .tap(find.byKey(Key("walletSettingsEnableBiometricsButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCancelBiometricsSystemSettingsDialog() async {
- await tester.tap(find.byType(SimpleButton));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapRenameWallet() async {
- await tester.tap(find.byKey(Key("walletSettingsRenameWalletButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapDeleteWallet() async {
- await tester.tap(find.byKey(Key("walletSettingsDeleteWalletButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapFullRescan() async {
- await tester.tap(find.byKey(Key("walletSettingsFullRescanButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCancelDeleteConfirmationDialog() async {
- expect(find.byType(ConfirmationDialog), findsOneWidget);
- expect(find.text("CANCEL"), findsOneWidget);
- expect(find.text("DELETE"), findsOneWidget);
- expect(find.textContaining("Do you want to delete "), findsOneWidget);
- await tester.tap(find.byType(SimpleButton));
- await tester.pumpAndSettle();
- expect(find.byType(ConfirmationDialog), findsNothing);
- }
-
- Future<void> tapDeleteOnDeleteConfirmationDialog() async {
- expect(find.byType(ConfirmationDialog), findsOneWidget);
- expect(find.text("CANCEL"), findsOneWidget);
- expect(find.text("DELETE"), findsOneWidget);
- expect(find.textContaining("Do you want to delete "), findsOneWidget);
- await tester.tap(find.byType(GradientButton));
- await tester.pumpAndSettle();
- expect(find.byType(ConfirmationDialog), findsNothing);
- }
-
- Future<void> tapCancelRescanConfirmationDialog() async {
- expect(find.byType(ConfirmationDialog), findsOneWidget);
- expect(find.text("CANCEL"), findsOneWidget);
- expect(find.text("RESCAN"), findsOneWidget);
- expect(find.text("Are you sure you want to do a full rescan?"),
- findsOneWidget);
- await tester.tap(find.byType(SimpleButton));
- await tester.pumpAndSettle();
- expect(find.byType(ConfirmationDialog), findsNothing);
- }
-
- Future<void> tapRescanOnRescanConfirmationDialog() async {
- expect(find.byType(ConfirmationDialog), findsOneWidget);
- expect(find.text("CANCEL"), findsOneWidget);
- expect(find.text("RESCAN"), findsOneWidget);
- expect(find.text("Are you sure you want to do a full rescan?"),
- findsOneWidget);
- await tester.tap(find.byType(GradientButton));
- await tester.pumpAndSettle();
- expect(find.byType(ConfirmationDialog), findsNothing);
- }
-
- Future<void> tapClearCache() async {
- await tester
- .tap(find.byKey(Key("walletSettingsClearSharedCacheButtonKey")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCancelClearCache() async {
- expect(find.byType(ConfirmationDialog), findsOneWidget);
- expect(find.text("CANCEL"), findsOneWidget);
- expect(find.text("CLEAR"), findsOneWidget);
- expect(
- find.textContaining(
- "Are you sure you want to clear all shared cached "),
- findsOneWidget);
- await tester.tap(find.byType(SimpleButton));
- await tester.pumpAndSettle();
- expect(find.byType(ConfirmationDialog), findsNothing);
- }
-
- Future<void> tapClearOnClearCache() async {
- expect(find.byType(ConfirmationDialog), findsOneWidget);
- expect(find.text("CANCEL"), findsOneWidget);
- expect(find.text("CLEAR"), findsOneWidget);
- expect(
- find.textContaining(
- "Are you sure you want to clear all shared cached "),
- findsOneWidget);
- await tester.tap(find.byType(GradientButton));
- await tester.pumpAndSettle();
- expect(find.byType(ConfirmationDialog), findsNothing);
- }
-
- Future<void> tapOkOnCacheClearedAlert() async {
- expect(find.byType(CampfireAlert), findsOneWidget);
- expect(find.text("Transaction cache cleared!"), findsOneWidget);
- expect(find.text("OK"), findsOneWidget);
- await tester.tap(find.byType(GradientButton));
- await tester.pumpAndSettle();
- expect(find.byType(CampfireAlert), findsNothing);
- }
-}
### integration_test/bots/transaction/transaction_search_view_bot.dart
@@ -1,24 +0,0 @@
-import 'package:flutter/cupertino.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/transaction_subviews/transaction_search_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/simple_button.dart';
-
-class TransactionSearchViewBot {
- final WidgetTester tester;
-
- const TransactionSearchViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(TransactionSearchView));
- }
-
- Future<void> tapX() async {
- await tester.tap(find.byKey(Key("cancelTransactionSearchAppBarButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> tapCancel() async {
- await tester.tap(find.byType(SimpleButton));
- await tester.pumpAndSettle();
- }
-}
### integration_test/bots/wallet_view_bot.dart
@@ -1,48 +0,0 @@
-import 'package:flutter/cupertino.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
-import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart';
-
-class WalletViewBot {
- final WidgetTester tester;
-
- const WalletViewBot(this.tester);
-
- Future<void> ensureVisible() async {
- await tester.ensureVisible(find.byType(WalletView));
- }
-
- Future<void> tapAvailableFullSwitch() async {
- await tester.tap(find.byType(DraggableSwitchButton));
- await tester.pumpAndSettle();
- }
-
- Future<void> dragAvailableFullSwitchRight() async {
- await tester.fling(
- find.byKey(Key("draggableSwitchButtonSwitch")), Offset(200, 0), 500);
- await tester.pumpAndSettle();
- }
-
- Future<void> dragAvailableFullSwitchLeft() async {
- await tester.fling(
- find.byKey(Key("draggableSwitchButtonSwitch")), Offset(-200, 0), 500);
- await tester.pumpAndSettle();
- }
-
- Future<void> tapTransactionSearch() async {
- await tester.tap(find.byKey(Key("walletViewTransactionSearchButton")));
- await tester.pumpAndSettle();
- }
-
- Future<void> checkAvailableFullSwitchIsEnabled() async {
- final state = tester.state(find.byType(DraggableSwitchButton))
- as DraggableSwitchButtonState;
- expect(state.enabled, true);
- }
-
- Future<void> checkAvailableFullSwitchIsDisabled() async {
- final state = tester.state(find.byType(DraggableSwitchButton))
- as DraggableSwitchButtonState;
- expect(state.enabled, false);
- }
-}
### integration_test/create_new_wallet_test.dart
@@ -1,134 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:integration_test/integration_test.dart';
-import 'package:stackwallet/main.dart' as campfireApp;
-import 'package:stackwallet/pages/main_view.dart';
-
-import 'bot_runners/create_wallet_until_pin_confirmation.dart';
-import 'bots/onboarding/backup_key_view_bot.dart';
-import 'bots/onboarding/backup_key_warning_bot.dart';
-import 'bots/onboarding/create_pin_view_bot.dart';
-import 'bots/onboarding/name_your_wallet_view_bot.dart';
-import 'bots/onboarding/onboarding_view_bot.dart';
-import 'bots/onboarding/terms_and_conditions_bot.dart';
-import 'bots/onboarding/verify_backup_key_view_bot.dart';
-
-void main() {
- group("Campfire app test", () {
- IntegrationTestWidgetsFlutterBinding.ensureInitialized();
- testWidgets("new wallet creation", (tester) async {
- campfireApp.main();
- await tester.pumpAndSettle(Duration(seconds: 10));
-
- final onboardingViewBot = OnboardingViewBot(tester);
- final termsAndConditionsViewBot = TermsAndConditionsViewBot(tester);
- final nameYourWalletViewBot = NameYourWalletViewBot(tester);
- final createPinViewBot = CreatePinViewBot(tester);
- final backupKeyWarningViewBot = BackupKeyWarningViewBot(tester);
- final backupKeyViewBot = BackupKeyViewBot(tester);
- final verifyBackUpViewBot = VerifyBackupKeyViewBot(tester);
-
- // tap create new wallet button
- await onboardingViewBot.ensureVisible();
- await onboardingViewBot.tapCreateNewWallet();
-
- await termsAndConditionsViewBot.ensureVisible();
- // test tap back
- await termsAndConditionsViewBot.tapBack();
- await onboardingViewBot.ensureVisible();
- // tap new again
- await onboardingViewBot.tapCreateNewWallet();
-
- await createWalletUntilPinConfirmation(
- termsAndConditionsViewBot,
- nameYourWalletViewBot,
- createPinViewBot,
- );
-
- // wait for wallet generation
- await tester.pumpAndSettle(Duration(seconds: 60));
-
- await backupKeyWarningViewBot.ensureVisible();
-
- // tap back
- await backupKeyWarningViewBot.tapBack();
- await onboardingViewBot.ensureVisible();
-
- // tap create new wallet button
- await onboardingViewBot.ensureVisible();
- await onboardingViewBot.tapCreateNewWallet();
-
- await termsAndConditionsViewBot.ensureVisible();
- // test tap back
- await termsAndConditionsViewBot.tapBack();
- await onboardingViewBot.ensureVisible();
- // tap new again
- await onboardingViewBot.tapCreateNewWallet();
-
- // run through to backup key warning again
- await createWalletUntilPinConfirmation(
- termsAndConditionsViewBot,
- nameYourWalletViewBot,
- createPinViewBot,
- );
-
- // wait for wallet generation
- await tester.pumpAndSettle(Duration(seconds: 60));
-
- await backupKeyWarningViewBot.ensureVisible();
-
- // enable checkbox
- await backupKeyWarningViewBot.tapCheckBox();
- await backupKeyWarningViewBot.tapViewBackupKey();
-
- // expect to see mnemonic displayed
- await backupKeyViewBot.ensureVisible();
-
- // tap back
- await backupKeyViewBot.tapBack();
- await backupKeyWarningViewBot.ensureVisible();
-
- // advance again
- await backupKeyWarningViewBot.tapViewBackupKey();
- // expect to see mnemonic displayed again
- await backupKeyViewBot.ensureVisible();
-
- // tap qr code
- await backupKeyViewBot.tapQrCode();
- // tap cancel to pop qr code view
- await tester.tap(find.byKey(Key("backUpKeyViewQrCodeCancelButtonKey")));
- await tester.pumpAndSettle();
-
- // tap copy
- await backupKeyViewBot.tapCopy();
-
- // fetch words to verify on next page
- final words = await backupKeyViewBot.displayedMnemonic();
-
- // tap verify to go to next screen
- await backupKeyViewBot.tapVerify();
- // expect next screen
- await verifyBackUpViewBot.ensureVisible();
-
- // ensure overlay from previous copy function has gone away
- await tester.pumpAndSettle(Duration(seconds: 2));
-
- // tap back
- await verifyBackUpViewBot.tapBack();
- await backupKeyViewBot.ensureVisible();
-
- // continue forward again
- await backupKeyViewBot.tapVerify();
- // expect next screen again
- await verifyBackUpViewBot.ensureVisible();
-
- // enter requested word
- await verifyBackUpViewBot.enterRequestedWord(words);
- // tap confirm to check validity
- await verifyBackUpViewBot.tapConfirm();
-
- // expect main wallet view
- expect(find.byType(MainView), findsOneWidget);
- });
- });
-}
### integration_test/desktop_forgot_password/harness.dart
@@ -0,0 +1,302 @@
+// Desktop forgot-password integration tests. With Flutter 3.49 or later:
+//
+// flutter test integration_test/desktop_forgot_password -d macos
+//
+// With older Flutter versions, use the temporary runner:
+//
+// bash scripts/test_desktop_forgot_password.sh macos
+//
+// (or -d linux / -d windows). Linux needs a desktop session, because
+// notifications use D-Bus, and a screen at least 800 px wide, otherwise the
+// app uses its mobile UI.
+//
+// Each test launches the real app against a throwaway folder. App data goes
+// into it, as with the app's -d <dir> flag, and path_provider points into it
+// too, so logs never reach the real documents folder. exit() is intercepted:
+// the test records the exit code and the files on disk when the app quits.
+//
+// One test per file: a reset ends by quitting and the app keeps process-wide
+// state, so every scenario needs its own launch.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+import 'package:isar_community/isar.dart';
+import 'package:path/path.dart' as p;
+import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
+import 'package:stackwallet/app_config.dart';
+import 'package:stackwallet/db/hive/db.dart';
+import 'package:stackwallet/main.dart' as app;
+import 'package:stackwallet/utilities/desktop_password_service.dart';
+import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
+import 'package:stackwallet/utilities/stack_file_system.dart';
+
+const _password = 'correct horse battery staple';
+const _secretKey = 'test_wallet_mnemonic';
+const _secret = 'test words';
+
+const _walletFiles = [
+ 'wallets/monero/test_wallet/test_wallet',
+ 'wallets/monero/test_wallet/test_wallet.keys',
+ 'epiccash/test_wallet/wallet_data/wallet.seed',
+];
+
+/// Seeded files a reset must delete.
+const seededData = {
+ 'hive/desktopdata.hive', // the password
+ 'isar/desktopStore.isar', // wallet keys
+ ..._walletFiles,
+};
+
+/// Seeded files a reset must keep.
+const seededKeep = {'wallets/backup.swb', 'tor/state'};
+
+/// Plain files the test writes. Each holds its own path.
+const seededFiles = [..._walletFiles, ...seededKeep];
+
+typedef AppExit = ({int code, Set<String> files, List<String> openDatabases});
+
+void desktopTest(String description, Future<void> Function(ResetTest t) body) {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+ testWidgets(
+ description,
+ (tester) async {
+ final temp = await Directory.systemTemp.createTemp('stack_reset_test_');
+ debugPrint('Test folder: ${temp.path}');
+ await body(ResetTest._(tester, temp.path));
+ // Only reached when the test passed, so failed runs keep the folder.
+ // On Windows the still running app holds some of it open.
+ try {
+ await temp.delete(recursive: true);
+ } on FileSystemException catch (_) {}
+ },
+ skip: !(Platform.isLinux || Platform.isMacOS || Platform.isWindows),
+ timeout: const Timeout(Duration(minutes: 3)),
+ );
+}
+
+class ResetTest {
+ ResetTest._(this.tester, this.temp);
+
+ final WidgetTester tester;
+ final String temp;
+ late final String root;
+ late final _io = _TestIO(this);
+ AppExit? _exit;
+ final _errors = <String>[];
+
+ /// Set once the app calls exit().
+ AppExit? get exited => _exit;
+
+ /// Seeds the data folder, then starts the app.
+ Future<void> launch({bool unfinishedReset = false}) async {
+ PathProviderPlatform.instance = _TestPaths(temp);
+ StackFileSystem.setDesktopOverrideDir(p.join(temp, 'data'));
+ root = (await StackFileSystem.applicationRootDirectory()).path;
+
+ DB.instance.hive.init(
+ (await StackFileSystem.applicationHiveDirectory()).path,
+ );
+ final password = DPS();
+ await password.initFromNew(_password);
+ final keys = DesktopSecureStore(password.handler);
+ await keys.init();
+ await keys.write(key: _secretKey, value: _secret);
+ await keys.close();
+
+ for (final file in seededFiles) {
+ await File(path(file)).create(recursive: true);
+ await File(path(file)).writeAsString(file);
+ }
+ // Creating links on Windows needs extra privileges.
+ if (!Platform.isWindows) {
+ await File(p.join(temp, 'outside', 'file')).create(recursive: true);
+ await Link(path('wallets/outside')).create(p.join(temp, 'outside'));
+ }
+ if (unfinishedReset) {
+ await File(path('.reset-pending')).writeAsString('reset');
+ }
+ expect(files(), containsAll({...seededData, ...seededKeep}));
+
+ // Lay out at the largest window main() picks. Smaller windows overflow the
+ // login screen in debug builds, which fails the test.
+ await tester.binding.setSurfaceSize(const Size(1220, 900));
+ // Never restored: the app runs until this test file's process ends.
+ IOOverrides.global = _io;
+ runZonedGuarded(() => app.main(const []), _onAppError);
+ }
+
+ /// Absolute path of a file in the data folder.
+ String path(String file) => p.joinAll([root, ...file.split('/')]);
+
+ /// Everything in the data folder, as paths relative to it.
+ Set<String> files() {
+ final all = Directory(root).listSync(recursive: true, followLinks: false);
+ return {
+ for (final entity in all)
+ p.split(p.relative(entity.path, from: root)).join('/'),
+ };
+ }
+
+ List<String> openDatabases() => [
+ ...Isar.instanceNames,
+ for (final box in [DB.boxNameDBInfo, DB.boxNamePrefs])
+ if (DB.instance.hive.isBoxOpen(box)) box,
+ ];
+
+ /// Makes deleting this file fail, like a file another program has open.
+ void blockDelete(String file) => _io.undeletable = path(file);
+
+ /// Checks seeded files still hold what the test wrote.
+ void expectUnchanged(Iterable<String> files) {
+ for (final file in files) {
+ expect(File(path(file)).readAsStringSync(), file, reason: file);
+ }
+ }
+
+ /// Checks the password still unlocks the seeded wallet key.
+ Future<void> expectPasswordKept() async {
+ final password = DPS();
+ await password.initFromExisting(_password);
+ final keys = DesktopSecureStore(password.handler);
+ await keys.init();
+ expect(await keys.read(key: _secretKey), _secret);
+ await keys.close();
+ }
+
+ void expectLinkTargetKept() {
+ if (Platform.isWindows) return;
+ expect(File(p.join(temp, 'outside', 'file')).existsSync(), isTrue);
+ }
+
+ void expectLogsInTestFolder() => expect(
+ Directory(p.join(temp, 'documents', '${AppConfig.prefix}_Logs'))
+ .existsSync(),
+ isTrue,
+ );
+
+ Future<void> tapText(String text) => tap(find.text(text, findRichText: true));
+
+ Future<void> tap(Finder finder) async {
+ // Exactly one, so a route that is still animating out is not tapped.
+ await _waitUntil(
+ () => finder.hitTestable().evaluate().length == 1,
+ 'Timed out waiting for one $finder',
+ );
+ // Buttons can end in exit(), which throws _AppExited from the callback.
+ await runZonedGuarded(() async {
+ try {
+ await tester.tap(finder.hitTestable());
+ } catch (error, stack) {
+ _onAppError(error, stack);
+ }
+ }, _onAppError);
+ await _pump();
+ }
+
+ Future<void> waitFor(Finder finder) => _waitUntil(
+ () => finder.hitTestable().evaluate().isNotEmpty,
+ 'Timed out waiting for $finder',
+ );
+
+ Future<AppExit> waitForExit() async {
+ await _waitUntil(() => _exit != null, 'The app did not quit');
+ return _exit!;
+ }
+
+ Future<void> _waitUntil(bool Function() done, String reason) async {
+ final deadline = DateTime.now().add(const Duration(minutes: 1));
+ while (true) {
+ await _pump();
+ if (done()) return;
+ if (DateTime.now().isAfter(deadline)) fail(reason);
+ }
+ }
+
+ Future<void> _pump() async {
+ await tester.pump(const Duration(milliseconds: 100));
+ final Object? error = tester.takeException();
+ if (error != null) _onAppError(error, StackTrace.current);
+ if (_errors.isNotEmpty) {
+ fail('The app reported an error:\n${_errors.first}');
+ }
+ }
+
+ void _onAppError(Object error, StackTrace stack) {
+ if (error is! _AppExited) _errors.add('$error\n$stack');
+ }
+}
+
+class _AppExited {
+ const _AppExited();
+}
+
+final class _TestIO extends IOOverrides {
+ _TestIO(this.test);
+
+ final ResetTest test;
+ String? undeletable;
+
+ @override
+ Never exit(int code) {
+ test._exit ??= (
+ code: code,
+ files: test.files(),
+ openDatabases: test.openDatabases(),
+ );
+ throw const _AppExited();
+ }
+
+ // Works because the reset deletes each file through a new File(path).
+ @override
+ File createFile(String path) {
+ final file = super.createFile(path);
+ final blocked = undeletable;
+ return blocked != null && p.equals(path, blocked)
+ ? _UndeletableFile(file)
+ : file;
+ }
+}
+
+class _UndeletableFile extends Fake implements File {
+ _UndeletableFile(this._file);
+
+ final File _file;
+
+ @override
+ String get path => _file.path;
+
+ @override
+ Future<File> delete({bool recursive = false}) async =>
+ throw FileSystemException('Blocked by the test', path);
+}
+
+class _TestPaths extends PathProviderPlatform {
+ _TestPaths(this.temp);
+
+ final String temp;
+
+ Future<String> _directory(String name) async =>
+ (await Directory(p.join(temp, name)).create(recursive: true)).path;
+
+ @override
+ Future<String?> getTemporaryPath() => _directory('temp');
+
+ @override
+ Future<String?> getApplicationSupportPath() => _directory('support');
+
+ @override
+ Future<String?> getLibraryPath() => _directory('library');
+
+ @override
+ Future<String?> getApplicationDocumentsPath() => _directory('documents');
+
+ @override
+ Future<String?> getApplicationCachePath() => _directory('cache');
+
+ @override
+ Future<String?> getDownloadsPath() => _directory('downloads');
+}
### integration_test/desktop_forgot_password/reset_cancelled_test.dart
@@ -0,0 +1,25 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:stackwallet/app_config.dart';
+import 'package:stackwallet/pages_desktop_specific/password/desktop_login_view.dart';
+import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
+
+import 'harness.dart';
+
+void main() {
+ desktopTest('backing out of forgot password deletes nothing', (t) async {
+ await t.launch();
+ await t.waitFor(find.byType(DesktopLoginView));
+ final before = t.files();
+
+ await t.tapText('Forgot password?');
+ await t.tapText('Create new ${AppConfig.prefix}');
+ await t.tapText('Take me back!');
+ await t.tap(find.byType(AppBarBackButton));
+ await t.waitFor(find.byType(DesktopLoginView));
+
+ expect(t.exited, isNull);
+ expect(t.files(), before);
+ t.expectUnchanged(seededFiles);
+ await t.expectPasswordKept();
+ });
+}
### integration_test/desktop_forgot_password/reset_fails_test.dart
@@ -0,0 +1,30 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:stackwallet/app_config.dart';
+
+import 'harness.dart';
+
+void main() {
+ desktopTest('a failed reset quits and stays pending', (t) async {
+ await t.launch();
+ t.blockDelete('wallets/monero/test_wallet/test_wallet.keys');
+ await t.tapText('Forgot password?');
+ await t.tapText('Restore from ${AppConfig.prefix} backup');
+ await t.tapText('Delete everything and quit');
+
+ final exit = await t.waitForExit();
+ expect(exit.code, 1);
+ expect(exit.openDatabases, isEmpty);
+ expect(
+ exit.files,
+ containsAll([
+ '.reset-pending',
+ 'wallets/monero/test_wallet/test_wallet.keys',
+ ]),
+ );
+ // The password and wallet keys go first, so the reset cannot be undone.
+ expect(exit.files, isNot(contains('hive/desktopdata.hive')));
+ expect(exit.files, isNot(contains('isar/desktopStore.isar')));
+ expect(find.textContaining('Reset could not finish'), findsOneWidget);
+ t.expectUnchanged(seededKeep);
+ });
+}
### integration_test/desktop_forgot_password/reset_succeeds_test.dart
@@ -0,0 +1,29 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:stackwallet/app_config.dart';
+
+import 'harness.dart';
+
+void main() {
+ desktopTest('forgot password deletes all app data and quits', (t) async {
+ await t.launch();
+ await t.tapText('Forgot password?');
+ expect(t.openDatabases(), isNotEmpty);
+ await t.tapText('Create new ${AppConfig.prefix}');
+ await t.tapText('Delete everything and quit');
+
+ final exit = await t.waitForExit();
+ expect(exit.code, 0);
+ expect(exit.openDatabases, isEmpty);
+ expect(exit.files, {
+ '.instance.lock',
+ 'tor',
+ 'tor/state',
+ 'wallets',
+ 'wallets/backup.swb',
+ });
+ expect(find.textContaining('was reset and will now close'), findsOneWidget);
+ t.expectUnchanged(seededKeep);
+ t.expectLinkTargetKept();
+ t.expectLogsInTestFolder();
+ });
+}
### integration_test/desktop_forgot_password/unfinished_reset_quit_test.dart
@@ -0,0 +1,20 @@
+import 'package:flutter_test/flutter_test.dart';
+
+import 'harness.dart';
+
+void main() {
+ desktopTest('quitting an unfinished reset keeps everything', (t) async {
+ await t.launch(unfinishedReset: true);
+ await t.waitFor(find.text('Unfinished reset'));
+ final before = t.files();
+ expect(before, contains('.reset-pending'));
+
+ await t.tapText('Quit');
+
+ final exit = await t.waitForExit();
+ expect(exit.code, 0);
+ expect(exit.files, before);
+ t.expectUnchanged(seededFiles);
+ await t.expectPasswordKept();
+ });
+}
### integration_test/desktop_forgot_password/unfinished_reset_retry_test.dart
@@ -0,0 +1,23 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:stackwallet/pages/intro_view.dart';
+import 'package:stackwallet/utilities/desktop_password_service.dart';
+
+import 'harness.dart';
+
+void main() {
+ desktopTest('retrying an unfinished reset deletes the data', (t) async {
+ await t.launch(unfinishedReset: true);
+ await t.tapText('Retry deleting');
+ await t.waitFor(find.byType(IntroView));
+
+ expect(t.exited, isNull);
+ final files = t.files();
+ expect(files, isNot(contains('.reset-pending')));
+ t.expectUnchanged(seededKeep);
+ // The first-run screen opens a new, empty password box.
+ expect(files.intersection(seededData), {'hive/desktopdata.hive'});
+ expect(await DPS().hasPassword(), isFalse);
+ // Logging starts after the reset deleted the settings.
+ t.expectLogsInTestFolder();
+ });
+}
### integration_test/main_wallet_views_test.dart
@@ -1,106 +0,0 @@
-import 'package:flutter_test/flutter_test.dart';
-import 'package:integration_test/integration_test.dart';
-import 'package:stackwallet/main.dart' as campfireApp;
-
-import 'bot_runners/create_wallet_until_pin_confirmation.dart';
-import 'bots/main_view_bot.dart';
-import 'bots/onboarding/backup_key_warning_bot.dart';
-import 'bots/onboarding/create_pin_view_bot.dart';
-import 'bots/onboarding/name_your_wallet_view_bot.dart';
-import 'bots/onboarding/onboarding_view_bot.dart';
-import 'bots/onboarding/terms_and_conditions_bot.dart';
-import 'bots/receive_view_bot.dart';
-import 'bots/send_view_bot.dart';
-import 'bots/settings/settings_view_bot.dart';
-import 'bots/transaction/transaction_search_view_bot.dart';
-import 'bots/wallet_view_bot.dart';
-
-void main() {
- IntegrationTestWidgetsFlutterBinding.ensureInitialized();
- testWidgets("wallet, send, and receive view test", (tester) async {
- campfireApp.main();
- await tester.pumpAndSettle(Duration(seconds: 10));
-
- // robots
- final onboardingViewBot = OnboardingViewBot(tester);
- final termsAndConditionsViewBot = TermsAndConditionsViewBot(tester);
- final nameYourWalletViewBot = NameYourWalletViewBot(tester);
- final createPinViewBot = CreatePinViewBot(tester);
- final backupKeyWarningViewBot = BackupKeyWarningViewBot(tester);
- final mainViewBot = MainViewBot(tester);
- final settingsViewBot = SettingsViewBot(tester);
- final walletViewBot = WalletViewBot(tester);
- final transactionSearchViewBot = TransactionSearchViewBot(tester);
- final sendViewBot = SendViewBot(tester);
- final receiveViewBot = ReceiveViewBot(tester);
-
- // tap create new wallet button
- await onboardingViewBot.ensureVisible();
- await onboardingViewBot.tapCreateNewWallet();
- await termsAndConditionsViewBot.ensureVisible();
-
- await createWalletUntilPinConfirmation(
- termsAndConditionsViewBot,
- nameYourWalletViewBot,
- createPinViewBot,
- );
-
- // wait for wallet generation
- await tester.pumpAndSettle(Duration(seconds: 60));
-
- await backupKeyWarningViewBot.ensureVisible();
-
- // tap skip to load into main wallet view
- await backupKeyWarningViewBot.tapSkip();
-
- await mainViewBot.ensureVisible();
-
- // tap refresh
- await mainViewBot.tapRefresh();
-
- // tap settings button
- await mainViewBot.tapSettings();
- await settingsViewBot.ensureVisible();
-
- // tap back to main wallet view
- await settingsViewBot.tapBack();
- await mainViewBot.ensureVisible();
-
- await walletViewBot.ensureVisible();
-
- // wait for refresh notification that covers switch to disappear
- await tester.pumpAndSettle(Duration(seconds: 3));
-
- // tap switch
- await walletViewBot.tapAvailableFullSwitch();
- await walletViewBot.checkAvailableFullSwitchIsDisabled();
-
- // drag switch
- await walletViewBot.dragAvailableFullSwitchRight();
- await walletViewBot.checkAvailableFullSwitchIsDisabled();
-
- // drag switch
- await walletViewBot.dragAvailableFullSwitchLeft();
- await walletViewBot.checkAvailableFullSwitchIsEnabled();
-
- // tap switch again
- await walletViewBot.tapAvailableFullSwitch();
- await walletViewBot.checkAvailableFullSwitchIsDisabled();
-
- // tap tx search
- await walletViewBot.tapTransactionSearch();
- await transactionSearchViewBot.ensureVisible();
-
- // go back
- await transactionSearchViewBot.tapX();
- await walletViewBot.ensureVisible();
-
- // go to send tab
- await mainViewBot.tapSend();
- await sendViewBot.ensureVisible();
-
- // go to receive tab
- await mainViewBot.tapReceive();
- await receiveViewBot.ensureVisible();
- });
-}
### integration_test/rescan_test.dart
@@ -1,136 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:integration_test/integration_test.dart';
-import 'package:stackwallet/main.dart' as campfireApp;
-
-import 'bot_runners/create_wallet_until_pin_confirmation.dart';
-import 'bots/lockscreen_view_bot.dart';
-import 'bots/main_view_bot.dart';
-import 'bots/onboarding/create_pin_view_bot.dart';
-import 'bots/onboarding/name_your_wallet_view_bot.dart';
-import 'bots/onboarding/onboarding_view_bot.dart';
-import 'bots/onboarding/restore_wallet_form_view_bot.dart';
-import 'bots/onboarding/terms_and_conditions_bot.dart';
-import 'bots/rescan_warning_view_bot.dart';
-import 'bots/settings/settings_view_bot.dart';
-import 'bots/settings/wallet_settings_view_bot.dart';
-import 'private.dart';
-
-void main() {
- IntegrationTestWidgetsFlutterBinding.ensureInitialized();
- testWidgets("rescan test", (tester) async {
- campfireApp.main();
- await tester.pumpAndSettle(Duration(seconds: 10));
-
- // robots
- final onboardingViewBot = OnboardingViewBot(tester);
- final termsAndConditionsViewBot = TermsAndConditionsViewBot(tester);
- final nameYourWalletViewBot = NameYourWalletViewBot(tester);
- final createPinViewBot = CreatePinViewBot(tester);
-
- // tap restore wallet button
- await onboardingViewBot.ensureVisible();
- await onboardingViewBot.tapRestoreWallet();
- await termsAndConditionsViewBot.ensureVisible();
-
- await createWalletUntilPinConfirmation(
- termsAndConditionsViewBot,
- nameYourWalletViewBot,
- createPinViewBot,
- );
-
- await tester.pumpAndSettle(Duration(seconds: 2));
- final restoreWalletFormViewBot = RestoreWalletFormViewBot(tester);
- await restoreWalletFormViewBot.ensureVisible();
-
- // paste valid mnemonic
- await Clipboard.setData(ClipboardData(text: TEST_MNEMONIC));
- await restoreWalletFormViewBot.tapPaste();
- await restoreWalletFormViewBot.scrollDown();
- await restoreWalletFormViewBot.tapRestore(true);
-
- final mainViewBot = MainViewBot(tester);
- await mainViewBot.ensureVisible();
-
- await Future.delayed(Duration(seconds: 10));
-
- expect(find.byType(ListView, skipOffstage: false), findsNWidgets(1));
- expect(find.text("0.00041252 FIRO"), findsOneWidget);
-
- // restore should have succeeded by now
- // now we test full rescan
- await mainViewBot.tapSettings();
- final settingsViewBot = SettingsViewBot(tester);
- await settingsViewBot.ensureVisible();
- await settingsViewBot.tapWalletSettings();
- final walletSettingsViewBot = WalletSettingsViewBot(tester);
- await walletSettingsViewBot.ensureVisible();
-
- // tap rescan wallet and then cancel
- await walletSettingsViewBot.tapFullRescan();
- await walletSettingsViewBot.tapCancelRescanConfirmationDialog();
-
- // tap rescan and continue running though
- // and testing back taps throughout
- await walletSettingsViewBot.tapFullRescan();
- await walletSettingsViewBot.tapRescanOnRescanConfirmationDialog();
- final lockScreenViewBot = LockscreenViewBot(tester);
- await lockScreenViewBot.ensureVisible();
- await lockScreenViewBot.tapBack();
-
- await walletSettingsViewBot.tapFullRescan();
- await walletSettingsViewBot.tapRescanOnRescanConfirmationDialog();
- await lockScreenViewBot.ensureVisible();
- await lockScreenViewBot.enterPin("9999");
- await tester.pumpAndSettle(Duration(seconds: 2));
- await lockScreenViewBot.ensureVisible();
- await lockScreenViewBot.tapBack();
-
- await walletSettingsViewBot.tapFullRescan();
- await walletSettingsViewBot.tapRescanOnRescanConfirmationDialog();
- await lockScreenViewBot.ensureVisible();
- await lockScreenViewBot.enterPin("1234");
- await tester.pumpAndSettle(Duration(seconds: 2));
- final rescanWarningViewBot = RescanWarningViewBot(tester);
- await rescanWarningViewBot.ensureVisible();
- await rescanWarningViewBot.tapBack();
- await walletSettingsViewBot.ensureVisible();
-
- // tap qr code
- await walletSettingsViewBot.tapFullRescan();
- await walletSettingsViewBot.tapRescanOnRescanConfirmationDialog();
- await lockScreenViewBot.ensureVisible();
- await lockScreenViewBot.enterPin("1234");
- await tester.pumpAndSettle(Duration(seconds: 2));
- await rescanWarningViewBot.ensureVisible();
- await rescanWarningViewBot.tapQrCode();
- await rescanWarningViewBot.tapCancelQrCode();
- await rescanWarningViewBot.ensureVisible();
-
- // tap copy
- await Clipboard.setData(ClipboardData(text: ""));
- await rescanWarningViewBot.tapCopy();
- final mnemonic = (await Clipboard.getData(Clipboard.kTextPlain))!.text;
- expect(mnemonic, TEST_MNEMONIC);
- await tester.pumpAndSettle(Duration(seconds: 2));
- await Clipboard.setData(ClipboardData(text: ""));
- await rescanWarningViewBot.ensureVisible();
-
- // tap continue and cancel
- await rescanWarningViewBot.tapContinue();
- await rescanWarningViewBot.tapCancelContinue();
- await rescanWarningViewBot.ensureVisible();
-
- // tap continue and finally confirm rescan
- await rescanWarningViewBot.tapContinue();
- await rescanWarningViewBot.tapConfirmContinue();
-
- await mainViewBot.ensureVisible();
- await tester.pumpAndSettle();
- await Future.delayed(Duration(seconds: 10));
- await tester.pumpAndSettle();
-
- expect(find.text("0.00041252 FIRO"), findsOneWidget);
- });
-}
### integration_test/restore_wallet_test.dart
@@ -1,124 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:integration_test/integration_test.dart';
-import 'package:stackwallet/main.dart' as campfireApp;
-import 'package:stackwallet/notifications/campfire_alert.dart';
-import 'package:stackwallet/pages/main_view.dart';
-
-import 'bot_runners/create_wallet_until_pin_confirmation.dart';
-import 'bots/onboarding/create_pin_view_bot.dart';
-import 'bots/onboarding/name_your_wallet_view_bot.dart';
-import 'bots/onboarding/onboarding_view_bot.dart';
-import 'bots/onboarding/restore_wallet_form_view_bot.dart';
-import 'bots/onboarding/terms_and_conditions_bot.dart';
-import 'private.dart';
-
-void main() {
- IntegrationTestWidgetsFlutterBinding.ensureInitialized();
- testWidgets("restore from seed test", (tester) async {
- campfireApp.main();
- await tester.pumpAndSettle(Duration(seconds: 10));
-
- // robots
- final onboardingViewBot = OnboardingViewBot(tester);
- final termsAndConditionsViewBot = TermsAndConditionsViewBot(tester);
- final nameYourWalletViewBot = NameYourWalletViewBot(tester);
- final createPinViewBot = CreatePinViewBot(tester);
-
- // tap restore wallet button
- await onboardingViewBot.ensureVisible();
- await onboardingViewBot.tapRestoreWallet();
- await termsAndConditionsViewBot.ensureVisible();
-
- await createWalletUntilPinConfirmation(
- termsAndConditionsViewBot,
- nameYourWalletViewBot,
- createPinViewBot,
- );
-
- await tester.pumpAndSettle(Duration(seconds: 2));
- final restoreWalletFormViewBot = RestoreWalletFormViewBot(tester);
- await restoreWalletFormViewBot.ensureVisible();
-
- // test back
- await restoreWalletFormViewBot.tapBack();
- await onboardingViewBot.ensureVisible();
- await onboardingViewBot.tapRestoreWallet();
- await termsAndConditionsViewBot.ensureVisible();
- await createWalletUntilPinConfirmation(
- termsAndConditionsViewBot,
- nameYourWalletViewBot,
- createPinViewBot,
- );
- await tester.pumpAndSettle();
- await restoreWalletFormViewBot.ensureVisible();
-
- // open qr scanner and cancel
- // this test fails due to system popup?
- // await restoreWalletFormViewBot.tapScanQrCode();
- // await tester.tap(find.text("ONLY THIS TIME"));
- // await tester.pumpAndSettle();
- // await restoreWalletFormViewBot.tapCancelScanQrCode();
- // await restoreWalletFormViewBot.ensureVisible();
-
- // paste invalid mnemonic test
- await Clipboard.setData(ClipboardData(
- text:
- "some mnemonic words some mnemonic words some mnemonic words some mnemonic words some mnemonic words some mnemonic words some mnemonic words some mnemonic words"));
- await restoreWalletFormViewBot.tapPaste();
- await restoreWalletFormViewBot.scrollDown();
- expect(find.text("Please check spelling", skipOffstage: false),
- findsNWidgets(24));
- expect(find.text("some", skipOffstage: false), findsNWidgets(8));
- expect(find.text("mnemonic", skipOffstage: false), findsNWidgets(8));
- expect(find.text("words", skipOffstage: false), findsNWidgets(8));
-
- // tap restore on invalid mnemonic words
- await restoreWalletFormViewBot.tapRestore(true);
- await restoreWalletFormViewBot.ensureVisible();
-
- // paste valid mnemonic
- await Clipboard.setData(ClipboardData(text: TEST_MNEMONIC));
- await restoreWalletFormViewBot.tapPaste();
- await restoreWalletFormViewBot.scrollDown();
-
- // enter a valid word which gives us an invalid mnemonic
- await restoreWalletFormViewBot.enterWord("old", 24);
- await restoreWalletFormViewBot.tapRestore(true);
- expect(find.byType(CampfireAlert), findsOneWidget);
- await tester.tap(find.byKey(Key("campfireAlertOKButtonKey")));
- await tester.pumpAndSettle();
- expect(find.byType(CampfireAlert), findsNothing);
-
- // re paste valid mnemonic, restore, and cancel
- await Clipboard.setData(ClipboardData(text: TEST_MNEMONIC));
- await restoreWalletFormViewBot.tapPaste();
- await restoreWalletFormViewBot.scrollDown();
- await restoreWalletFormViewBot.tapRestore(false);
- await restoreWalletFormViewBot.tapCancelRestore();
- await onboardingViewBot.ensureVisible();
-
- // full restore
- await onboardingViewBot.tapRestoreWallet();
- await termsAndConditionsViewBot.ensureVisible();
- await createWalletUntilPinConfirmation(
- termsAndConditionsViewBot,
- nameYourWalletViewBot,
- createPinViewBot,
- );
- await tester.pumpAndSettle();
- await restoreWalletFormViewBot.ensureVisible();
- await Clipboard.setData(ClipboardData(text: TEST_MNEMONIC));
- await restoreWalletFormViewBot.tapPaste();
- await restoreWalletFormViewBot.scrollDown();
- await restoreWalletFormViewBot.tapRestore(true);
-
- expect(find.byType(MainView), findsOneWidget);
-
- await Future.delayed(Duration(seconds: 10));
-
- expect(find.byType(ListView, skipOffstage: false), findsNWidgets(1));
- expect(find.text("0.00041252 FIRO"), findsOneWidget);
- });
-}
### integration_test/settings_test.dart
@@ -1,643 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:integration_test/integration_test.dart';
-import 'package:stackwallet/main.dart' as campfireApp;
-import 'package:stackwallet/notifications/modal_popup_dialog.dart';
-import 'package:stackwallet/pages/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view.dart';
-import 'package:stackwallet/utilities/address_utils.dart';
-import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/simple_button.dart';
-import 'package:stackwallet/widgets/node_card.dart';
-import 'package:pretty_qr_code/pretty_qr_code.dart';
-
-import 'bot_runners/create_wallet_until_pin_confirmation.dart';
-import 'bots/addressbook/add_address_book_entry_view_bot.dart';
-import 'bots/addressbook/address_book_card_bot.dart';
-import 'bots/addressbook/address_book_entry_details_view_bot.dart';
-import 'bots/addressbook/address_book_view_bot.dart';
-import 'bots/addressbook/edit_address_book_entry_view_bot.dart';
-import 'bots/lockscreen_view_bot.dart';
-import 'bots/main_view_bot.dart';
-import 'bots/onboarding/backup_key_warning_bot.dart';
-import 'bots/onboarding/create_pin_view_bot.dart';
-import 'bots/onboarding/name_your_wallet_view_bot.dart';
-import 'bots/onboarding/onboarding_view_bot.dart';
-import 'bots/onboarding/terms_and_conditions_bot.dart';
-import 'bots/settings/currency_view_bot.dart';
-import 'bots/settings/network_settings/add_custom_node_view_bot.dart';
-import 'bots/settings/network_settings/network_settings_view_bot.dart';
-import 'bots/settings/network_settings/node_details_view_bot.dart';
-import 'bots/settings/settings_view_bot.dart';
-import 'bots/settings/wallet_backup_view_bot.dart';
-import 'bots/settings/wallet_settings/change_pin_view_bot.dart';
-import 'bots/settings/wallet_settings/delete_wallet_warning_view_bot.dart';
-import 'bots/settings/wallet_settings/rename_wallet_view_bot.dart';
-import 'bots/settings/wallet_settings/wallet_delete_mnemonic_view_bot.dart';
-import 'bots/settings/wallet_settings_view_bot.dart';
-
-const bool TEST_ADDRESS_BOOK = true;
-const bool TEST_NETWORK_SETTINGS = true;
-const bool TEST_WALLET_BACKUP = true;
-const bool TEST_CURRENCY = true;
-const bool TEST_WALLET_SETTINGS = true;
-
-void main() {
- IntegrationTestWidgetsFlutterBinding.ensureInitialized();
- // may fail if a network connection fails somewhere
- testWidgets("settings integration test", (tester) async {
- campfireApp.main();
- await tester.pumpAndSettle(Duration(seconds: 10));
-
- // robots
- final onboardingViewBot = OnboardingViewBot(tester);
- final termsAndConditionsViewBot = TermsAndConditionsViewBot(tester);
- final nameYourWalletViewBot = NameYourWalletViewBot(tester);
- final createPinViewBot = CreatePinViewBot(tester);
- final backupKeyWarningViewBot = BackupKeyWarningViewBot(tester);
- final mainViewBot = MainViewBot(tester);
- final settingsViewBot = SettingsViewBot(tester);
-
- // tap create new wallet button
- await onboardingViewBot.ensureVisible();
- await onboardingViewBot.tapCreateNewWallet();
- await termsAndConditionsViewBot.ensureVisible();
-
- await createWalletUntilPinConfirmation(
- termsAndConditionsViewBot,
- nameYourWalletViewBot,
- createPinViewBot,
- );
-
- // wait for wallet generation
- await tester.pumpAndSettle(Duration(seconds: 60));
-
- await backupKeyWarningViewBot.ensureVisible();
-
- // tap skip to load into main wallet view
- await backupKeyWarningViewBot.tapSkip();
- await mainViewBot.ensureVisible();
-
- // tap settings button
- await mainViewBot.tapSettings();
- await settingsViewBot.ensureVisible();
-
- if (TEST_ADDRESS_BOOK) {
- // tap address book
- await settingsViewBot.tapAddressBook();
- final addressBookViewBot = AddressBookViewBot(tester);
- await addressBookViewBot.ensureVisible();
-
- // add entry
- await addressBookViewBot.tapAdd();
- final addAddressBookEntryViewBot = AddAddressBookEntryViewBot(tester);
- await addAddressBookEntryViewBot.ensureVisible();
-
- // test back and cancel
- await addAddressBookEntryViewBot.tapBack();
- await addressBookViewBot.ensureVisible();
- await addressBookViewBot.tapAdd();
- await addAddressBookEntryViewBot.tapCancel();
- await addressBookViewBot.ensureVisible();
-
- // now add an entry
- await addressBookViewBot.tapAdd();
- await addAddressBookEntryViewBot.ensureVisible();
- await addAddressBookEntryViewBot
- .enterAddress("aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh");
- await addAddressBookEntryViewBot.enterName("john doe");
-
- // save entry
- await addAddressBookEntryViewBot.tapSave();
- await addressBookViewBot.ensureVisible();
- final addressBookCardBot = AddressBookCardBot(tester);
- await addressBookCardBot.ensureVisible();
-
- // expand options
- await addressBookCardBot.toggleExpandCard();
-
- // tap copy
- await addressBookCardBot.tapCopy();
- expect((await Clipboard.getData(Clipboard.kTextPlain))!.text,
- "aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh");
- expect(find.text("Address copied to clipboard"), findsOneWidget);
- await tester.pumpAndSettle(Duration(seconds: 2));
-
- // clear clipboard
- await Clipboard.setData(ClipboardData(text: ""));
-
- // tap details
- await addressBookCardBot.tapDetails();
- final addressBookEntryDetailsViewBot =
- AddressBookEntryDetailsViewBot(tester);
- await addressBookEntryDetailsViewBot.ensureVisible();
-
- // tap back then go back to details
- await addressBookEntryDetailsViewBot.tapBack();
- await addressBookCardBot.ensureVisible();
- await addressBookCardBot.tapDetails();
- await addressBookEntryDetailsViewBot.ensureVisible();
-
- // copy address
- await addressBookEntryDetailsViewBot.tapCopyAddress();
- expect((await Clipboard.getData(Clipboard.kTextPlain))!.text,
- "aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh");
- expect(find.text("Address copied to clipboard"), findsOneWidget);
- await tester.pumpAndSettle(Duration(seconds: 2));
-
- // clear clipboard
- await Clipboard.setData(ClipboardData(text: ""));
-
- // delete and cancel
- await addressBookEntryDetailsViewBot.tapMore();
- await addressBookEntryDetailsViewBot.tapDelete();
- await addressBookEntryDetailsViewBot.tapCancelDelete();
-
- // delete and confirm
- await addressBookEntryDetailsViewBot.tapMore();
- await addressBookEntryDetailsViewBot.tapDelete();
- await addressBookEntryDetailsViewBot.tapConfirmDelete();
- await addressBookViewBot.ensureVisible();
-
- // add and save another entry, then go to details
- await addressBookViewBot.tapAdd();
- await addAddressBookEntryViewBot.ensureVisible();
- await addAddressBookEntryViewBot
- .enterAddress("aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh");
- await addAddressBookEntryViewBot.enterName("john doe");
- await addAddressBookEntryViewBot.tapSave();
- await addressBookViewBot.ensureVisible();
- await addressBookCardBot.ensureVisible();
- await addressBookCardBot.toggleExpandCard();
- await addressBookCardBot.tapDetails();
- await addressBookEntryDetailsViewBot.ensureVisible();
-
- // tap edit
- await addressBookEntryDetailsViewBot.tapEdit();
- final editAddressBookEntryViewBot = EditAddressBookEntryViewBot(tester);
- await editAddressBookEntryViewBot.ensureVisible();
-
- // tap back
- await editAddressBookEntryViewBot.tapBack();
- await addressBookEntryDetailsViewBot.ensureVisible();
- await addressBookEntryDetailsViewBot.tapEdit();
- await editAddressBookEntryViewBot.ensureVisible();
-
- // tap cancel
- await editAddressBookEntryViewBot.tapCancel();
- await addressBookEntryDetailsViewBot.ensureVisible();
- await addressBookEntryDetailsViewBot.tapEdit();
- await editAddressBookEntryViewBot.ensureVisible();
-
- // tap save without editing
- await editAddressBookEntryViewBot.tapSave();
- await addressBookEntryDetailsViewBot.ensureVisible();
- await addressBookEntryDetailsViewBot.tapEdit();
- await editAddressBookEntryViewBot.ensureVisible();
-
- // tap save with editing
- await editAddressBookEntryViewBot.enterName("jane doe");
- await editAddressBookEntryViewBot
- .enterAddress("aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh");
-
- await editAddressBookEntryViewBot.tapSave();
- await addressBookViewBot.ensureVisible();
- await addressBookCardBot.ensureVisible();
-
- // expand and tap send
- await addressBookCardBot.toggleExpandCard();
- await addressBookCardBot.tapSend();
- await mainViewBot.ensureVisible();
-
- // go to details and send
- await mainViewBot.tapSettings();
- await settingsViewBot.tapAddressBook();
- await addressBookCardBot.toggleExpandCard();
- await addressBookCardBot.tapDetails();
- await addressBookEntryDetailsViewBot.tapSend();
- await mainViewBot.ensureVisible();
-
- // open settings again to continue testing
- await mainViewBot.tapSettings();
- await settingsViewBot.ensureVisible();
- }
-
- if (TEST_NETWORK_SETTINGS) {
- // network settings
- await settingsViewBot.tapNetwork();
- final networkSettingsViewBot = NetworkSettingsViewBot(tester);
- await networkSettingsViewBot.ensureVisible();
-
- // test back
- await networkSettingsViewBot.tapBack();
- await settingsViewBot.tapNetwork();
- await networkSettingsViewBot.ensureVisible();
- expect(find.byType(NodeCard), findsOneWidget);
-
- // test add node back
- await networkSettingsViewBot.tapAdd();
- final addCustomNodeViewBot = AddCustomNodeViewBot(tester);
- await addCustomNodeViewBot.ensureVisible();
- await addCustomNodeViewBot.tapBack();
- await networkSettingsViewBot.tapAdd();
- await addCustomNodeViewBot.ensureVisible();
-
- // test add node
- await addCustomNodeViewBot.enterNodeName("MyNode");
- await addCustomNodeViewBot.enterNodeAddress("MyNodeAddress");
- await addCustomNodeViewBot.enterNodePort("0");
-
- // tap test connection
- await addCustomNodeViewBot.tapTestConnection();
- expect(find.text("Connection failed!"), findsOneWidget);
- await tester.pumpAndSettle(Duration(seconds: 2));
-
- // test save non connecting node
- await addCustomNodeViewBot.tapSave();
- expect(find.byType(CouldNotConnectOnSaveDialog), findsOneWidget);
- await tester
- .tap(find.byKey(Key("couldNotConnectOnSaveConfirmSaveButtonKey")));
- await tester.pumpAndSettle();
- await networkSettingsViewBot.ensureVisible();
- expect(find.byType(NodeCard), findsNWidgets(2));
-
- // tap default node to get context menu
- expect(find.text("Connect"), findsNothing);
- expect(find.text("Details"), findsNothing);
- expect(find.text("Edit"), findsNothing);
- expect(find.text("Delete"), findsNothing);
- await networkSettingsViewBot.tapNode("Campfire default");
- expect(find.text("Connect"), findsOneWidget);
- expect(find.text("Details"), findsOneWidget);
- expect(find.text("Edit"), findsNothing);
- expect(find.text("Delete"), findsNothing);
-
- // tap connect
- await tester.tap(find.text("Connect"));
- await tester.pumpAndSettle();
-
- // tap details
- await networkSettingsViewBot.tapNode("Campfire default");
- await tester.tap(find.text("Details"));
- await tester.pumpAndSettle();
- final nodeDetailsViewBot = NodeDetailsViewBot(tester);
- await nodeDetailsViewBot.ensureVisible();
-
- // tap test connection
- await nodeDetailsViewBot.tapTestConnection();
- await tester.pumpAndSettle(Duration(seconds: 3));
-
- // tap back
- await nodeDetailsViewBot.tapBack();
- await networkSettingsViewBot.ensureVisible();
-
- // tap custom node to get context menu
- expect(find.text("Connect"), findsNothing);
- expect(find.text("Details"), findsNothing);
- expect(find.text("Edit"), findsNothing);
- expect(find.text("Delete"), findsNothing);
- await networkSettingsViewBot.tapNode("MyNode");
- expect(find.text("Connect"), findsOneWidget);
- expect(find.text("Details"), findsOneWidget);
- expect(find.text("Edit"), findsOneWidget);
- expect(find.text("Delete"), findsOneWidget);
-
- // tap details
- await tester.tap(find.text("Details"));
- await tester.pumpAndSettle();
- await nodeDetailsViewBot.ensureVisible();
-
- // test back
- await nodeDetailsViewBot.tapBack();
- await networkSettingsViewBot.ensureVisible();
- await networkSettingsViewBot.tapNode("MyNode");
- await tester.tap(find.text("Details"));
- await tester.pumpAndSettle();
- await nodeDetailsViewBot.ensureVisible();
-
- // tap test connection
- await nodeDetailsViewBot.tapTestConnection();
- expect(find.text("Connection failed!"), findsOneWidget);
- await tester.pumpAndSettle(Duration(seconds: 2));
-
- // open context and edit
- await nodeDetailsViewBot.tapMoreAndEdit();
-
- // tap disabled save
- await nodeDetailsViewBot.tapSave();
- await nodeDetailsViewBot.ensureVisible();
-
- // edit and save node
- await nodeDetailsViewBot.tapUseSSLCheckbox();
- await nodeDetailsViewBot.enterName("MyNode2");
- await nodeDetailsViewBot.tapSave();
- await networkSettingsViewBot.ensureVisible();
- expect(find.text("MyNode2"), findsOneWidget);
- expect(find.text("MyNode"), findsNothing);
-
- // tap delete node from details view
- await networkSettingsViewBot.tapNode("MyNode2");
- await tester.tap(find.text("Details"));
- await tester.pumpAndSettle();
- await nodeDetailsViewBot.tapMoreAndDelete();
-
- // cancel delete
- expect(find.byType(ModalPopupDialog), findsOneWidget);
- await nodeDetailsViewBot.tapCancelDelete();
- expect(find.byType(ModalPopupDialog), findsNothing);
-
- // confirm delete
- await nodeDetailsViewBot.tapMoreAndDelete();
- await nodeDetailsViewBot.tapConfirmDelete();
- await networkSettingsViewBot.ensureVisible();
- // expect only the default node now
- expect(find.byType(NodeCard), findsOneWidget);
-
- // create new node to test remaining two context options
- // on the main network settings page
- await networkSettingsViewBot.tapAdd();
- await addCustomNodeViewBot.ensureVisible();
- await addCustomNodeViewBot.enterNodeName("MyNode");
- await addCustomNodeViewBot.enterNodeAddress("MyNodeAddress");
- await addCustomNodeViewBot.enterNodePort("0");
- await addCustomNodeViewBot.tapSave();
- await tester
- .tap(find.byKey(Key("couldNotConnectOnSaveConfirmSaveButtonKey")));
- await tester.pumpAndSettle();
- await networkSettingsViewBot.ensureVisible();
- expect(find.byType(NodeCard), findsNWidgets(2));
-
- // tap edit node from the main network settings page, then save
- await networkSettingsViewBot.tapNode("MyNode");
- await tester.tap(find.text("Edit"));
- await tester.pumpAndSettle();
- await nodeDetailsViewBot.ensureVisible();
- await nodeDetailsViewBot.enterName("MyNode3");
- await nodeDetailsViewBot.enterAddress("somenewaddress");
- await nodeDetailsViewBot.enterPort("00");
- await nodeDetailsViewBot.tapUseSSLCheckbox();
- await tester.tap(find.text("Use SSL"));
- await tester.pumpAndSettle();
- await nodeDetailsViewBot.tapSave();
- await networkSettingsViewBot.ensureVisible();
-
- // tap edit node from the main network settings page, then back
- await networkSettingsViewBot.tapNode("MyNode3");
- await tester.tap(find.text("Edit"));
- await tester.pumpAndSettle();
- await nodeDetailsViewBot.tapBack();
- await networkSettingsViewBot.ensureVisible();
-
- // tap delete node from the main network settings page, then cancel
- await networkSettingsViewBot.tapNode("MyNode3");
- await tester.tap(find.text("Delete"));
- await tester.pumpAndSettle();
- await tester.tap(find.byType(SimpleButton));
- await tester.pumpAndSettle();
- await networkSettingsViewBot.ensureVisible();
- expect(find.byType(NodeCard), findsNWidgets(2));
-
- // tap delete node from the main network settings page, then confirm
- await networkSettingsViewBot.tapNode("MyNode3");
- await tester.tap(find.text("Delete"));
- await tester.pumpAndSettle();
- await tester.tap(find.byType(GradientButton));
- await tester.pumpAndSettle();
- await networkSettingsViewBot.ensureVisible();
- expect(find.byType(NodeCard), findsOneWidget);
-
- // go back to main settings
- await networkSettingsViewBot.tapBack();
- await settingsViewBot.ensureVisible();
- }
-
- if (TEST_WALLET_BACKUP) {
- await settingsViewBot.tapWalletBackup();
- final lockscreenViewBot = LockscreenViewBot(tester);
- await lockscreenViewBot.ensureVisible();
-
- // test back
- await lockscreenViewBot.tapBack();
- await settingsViewBot.ensureVisible();
- await settingsViewBot.tapWalletBackup();
-
- // enter wrong pin
- await lockscreenViewBot.enterPin("2222");
- await tester.pumpAndSettle(Duration(seconds: 2));
- await lockscreenViewBot.ensureVisible();
-
- // test back again
- await lockscreenViewBot.tapBack();
- await settingsViewBot.ensureVisible();
- await settingsViewBot.tapWalletBackup();
-
- // enter correct pin
- await lockscreenViewBot.enterPin("1234");
- await tester.pumpAndSettle(Duration(seconds: 2));
- final walletBackUpViewBot = WalletBackUpViewBot(tester);
- await walletBackUpViewBot.ensureVisible();
-
- // tap copy
- await Clipboard.setData(ClipboardData(text: ""));
- await walletBackUpViewBot.tapCopy();
- final mnemonic =
- (await Clipboard.getData(Clipboard.kTextPlain))!.text!.split(" ");
- expect(mnemonic.length, 24);
- await tester.pumpAndSettle(Duration(seconds: 2));
- await Clipboard.setData(ClipboardData(text: ""));
-
- // tap qr code then cancel
- await walletBackUpViewBot.tapQrCode();
- final qr = find.byType(PrettyQr).evaluate().first.widget as PrettyQr;
- expect(qr.data, AddressUtils.encodeQRSeedData(mnemonic));
- await walletBackUpViewBot.tapQrCodeCancel();
- expect(find.byType(ModalPopupDialog), findsNothing);
-
- // tap back to settings
- await walletBackUpViewBot.tapBack();
- await settingsViewBot.ensureVisible();
- }
-
- if (TEST_CURRENCY) {
- // change to a different currency from default USD
- await settingsViewBot.tapCurrency();
- final currencyViewBot = CurrencyViewBot(tester);
- await currencyViewBot.ensureVisible();
- await currencyViewBot.tapCurrency("CHF");
-
- // go back to main view to check for "CHF"
- await currencyViewBot.tapBack();
- await settingsViewBot.ensureVisible();
- await settingsViewBot.tapBack();
- await mainViewBot.ensureVisible();
- expect(find.text("CHF"), findsOneWidget);
-
- // open settings again to continue testing
- await mainViewBot.tapSettings();
- await settingsViewBot.ensureVisible();
- }
-
- if (TEST_WALLET_SETTINGS) {
- // tap wallet settings
- await settingsViewBot.tapWalletSettings();
- final walletSettingsViewBot = WalletSettingsViewBot(tester);
- await walletSettingsViewBot.ensureVisible();
-
- // test all change pin back taps
- await walletSettingsViewBot.tapChangePIN();
- final lockScreenViewBot = LockscreenViewBot(tester);
- await lockScreenViewBot.ensureVisible();
- await lockScreenViewBot.tapBack();
- await walletSettingsViewBot.tapChangePIN();
- await lockScreenViewBot.enterPin("1234");
- await tester.pumpAndSettle(Duration(seconds: 2));
- final changePinViewBot = ChangePinViewBot(tester);
- await changePinViewBot.ensureVisible();
- await changePinViewBot.tapBack();
- await walletSettingsViewBot.tapChangePIN();
- await lockScreenViewBot.enterPin("1234");
- await tester.pumpAndSettle(Duration(seconds: 2));
- await changePinViewBot.enterPin();
- await changePinViewBot.tapBack();
- await walletSettingsViewBot.ensureVisible();
-
- // change pin with a few fails
- await walletSettingsViewBot.tapChangePIN();
- await lockScreenViewBot.enterPin("2222");
- await tester.pumpAndSettle(Duration(seconds: 2));
- await lockScreenViewBot.enterPin("1234");
- await tester.pumpAndSettle(Duration(seconds: 2));
- await changePinViewBot.enterPin();
- await changePinViewBot.confirmUnmatchedPin();
- await changePinViewBot.enterPin();
- await changePinViewBot.confirmUnmatchedPin();
- await changePinViewBot.enterPin();
- await changePinViewBot.confirmMatchedPin();
- await walletSettingsViewBot.ensureVisible();
- await tester.pumpAndSettle(Duration(seconds: 2));
-
- // change pin with no fails
- await walletSettingsViewBot.tapChangePIN();
- await lockScreenViewBot.enterPin("2222");
- await tester.pumpAndSettle(Duration(seconds: 2));
- await lockScreenViewBot.enterPin("1234");
- await tester.pumpAndSettle(Duration(seconds: 2));
- await changePinViewBot.enterPin();
- await changePinViewBot.confirmMatchedPin();
- await walletSettingsViewBot.ensureVisible();
- await tester.pumpAndSettle(Duration(seconds: 2));
-
- // biometrics doesn't test well as it prompts for system settings
- // await walletSettingsViewBot.tapToggleBiometrics();
- // await walletSettingsViewBot.tapCancelBiometricsSystemSettingsDialog();
-
- // rename wallet save
- await walletSettingsViewBot.tapRenameWallet();
- final renameWalletViewBot = RenameWalletViewBot(tester);
- await renameWalletViewBot.tapSave();
- await renameWalletViewBot.enterWalletName("My Firo Wallet2");
- await renameWalletViewBot.tapSave();
-
- await walletSettingsViewBot.tapRenameWallet();
- expect(find.text("My Firo Wallet2"), findsOneWidget);
- await renameWalletViewBot.enterWalletName("My Firo Wallet");
- await renameWalletViewBot.tapSave();
-
- // tap back
- await walletSettingsViewBot.tapRenameWallet();
- await renameWalletViewBot.tapBack();
- await walletSettingsViewBot.ensureVisible();
-
- // tap clear cache and cancel
- await walletSettingsViewBot.tapClearCache();
- await walletSettingsViewBot.tapCancelClearCache();
- await walletSettingsViewBot.ensureVisible();
-
- // tap clear cache and clear
- await walletSettingsViewBot.tapClearCache();
- await walletSettingsViewBot.tapClearOnClearCache();
- await walletSettingsViewBot.tapOkOnCacheClearedAlert();
- await walletSettingsViewBot.ensureVisible();
-
- // tap delete wallet and cancel
- await walletSettingsViewBot.tapDeleteWallet();
- await walletSettingsViewBot.tapCancelDeleteConfirmationDialog();
-
- // tap delete wallet and continue running though
- // and testing back taps throughout
- await walletSettingsViewBot.tapDeleteWallet();
- await walletSettingsViewBot.tapDeleteOnDeleteConfirmationDialog();
- await lockScreenViewBot.ensureVisible();
- await lockScreenViewBot.tapBack();
-
- await walletSettingsViewBot.tapDeleteWallet();
- await walletSettingsViewBot.tapDeleteOnDeleteConfirmationDialog();
- await lockScreenViewBot.ensureVisible();
- await lockScreenViewBot.enterPin("9999");
- await tester.pumpAndSettle(Duration(seconds: 2));
- await lockScreenViewBot.ensureVisible();
- await lockScreenViewBot.tapBack();
-
- await walletSettingsViewBot.tapDeleteWallet();
- await walletSettingsViewBot.tapDeleteOnDeleteConfirmationDialog();
- await lockScreenViewBot.ensureVisible();
- await lockScreenViewBot.enterPin("1234");
- await tester.pumpAndSettle(Duration(seconds: 2));
- final deleteWalletWarningViewBot = DeleteWalletWarningViewBot(tester);
- await deleteWalletWarningViewBot.ensureVisible();
- await deleteWalletWarningViewBot.tapBack();
- await walletSettingsViewBot.ensureVisible();
-
- await walletSettingsViewBot.tapDeleteWallet();
- await walletSettingsViewBot.tapDeleteOnDeleteConfirmationDialog();
- await lockScreenViewBot.ensureVisible();
- await lockScreenViewBot.enterPin("1234");
- await tester.pumpAndSettle(Duration(seconds: 2));
- await deleteWalletWarningViewBot.ensureVisible();
- await deleteWalletWarningViewBot.tapCancelAndGoBack();
- await walletSettingsViewBot.ensureVisible();
-
- // finally go through and tap view backup key
- await walletSettingsViewBot.tapDeleteWallet();
- await walletSettingsViewBot.tapDeleteOnDeleteConfirmationDialog();
- await lockScreenViewBot.ensureVisible();
- await lockScreenViewBot.enterPin("1234");
- await tester.pumpAndSettle(Duration(seconds: 2));
- await deleteWalletWarningViewBot.ensureVisible();
- await deleteWalletWarningViewBot.tapViewBackupKey();
- final walletDeleteMnemonicViewBot = WalletDeleteMnemonicViewBot(tester);
- await walletDeleteMnemonicViewBot.ensureVisible();
-
- // test back
- await walletDeleteMnemonicViewBot.tapBack();
- await deleteWalletWarningViewBot.ensureVisible();
- await deleteWalletWarningViewBot.tapViewBackupKey();
- await walletDeleteMnemonicViewBot.ensureVisible();
-
- // tap qr code
- await walletDeleteMnemonicViewBot.tapQrCode();
- await walletDeleteMnemonicViewBot.tapCancelQrCode();
- await walletDeleteMnemonicViewBot.ensureVisible();
-
- // tap copy
- await Clipboard.setData(ClipboardData(text: ""));
- await walletDeleteMnemonicViewBot.tapCopy();
- final mnemonic =
- (await Clipboard.getData(Clipboard.kTextPlain))!.text!.split(" ");
- expect(mnemonic.length, 24);
- await tester.pumpAndSettle(Duration(seconds: 2));
- await Clipboard.setData(ClipboardData(text: ""));
- await walletDeleteMnemonicViewBot.ensureVisible();
-
- // tap continue and cancel
- await walletDeleteMnemonicViewBot.tapContinue();
- await walletDeleteMnemonicViewBot.tapCancelContinue();
- await walletDeleteMnemonicViewBot.ensureVisible();
-
- // tap continue and finally confirm deletion
- await walletDeleteMnemonicViewBot.tapContinue();
- await walletDeleteMnemonicViewBot.tapConfirmContinue();
- await onboardingViewBot.ensureVisible();
- }
- });
-}
### lib/db/sqlite/firo_cache_coordinator.dart
@@ -23,6 +23,17 @@ abstract class FiroCacheCoordinator {
}
}
+ /// Terminal shutdown for a desktop reset, followed by process exit.
+ static Future<void> close() async {
+ await Future.wait(_workers.values.map((worker) => worker.close()));
+ for (final db in [
+ ..._FiroCache._setCacheDB.values,
+ ..._FiroCache._usedTagsCacheDB.values,
+ ]) {
+ db.close();
+ }
+ }
+
static Future<void> clearSharedCache(
CryptoCurrencyNetwork network, {
bool clearOnlyUsedTagsCache = false,
@@ -42,12 +53,12 @@ abstract class FiroCacheCoordinator {
"${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName(network)}",
);
- final setSize =
- (await setCacheFile.exists()) ? await setCacheFile.length() : 0;
- final tagsSize =
- (await usedTagsCacheFile.exists())
- ? await usedTagsCacheFile.length()
- : 0;
+ final setSize = (await setCacheFile.exists())
+ ? await setCacheFile.length()
+ : 0;
+ final tagsSize = (await usedTagsCacheFile.exists())
+ ? await usedTagsCacheFile.length()
+ : 0;
Logging.instance.d("Spark cache used tags size: $tagsSize");
Logging.instance.d("Spark cache anon set size: $setSize");
@@ -91,8 +102,7 @@ abstract class FiroCacheCoordinator {
void Function(int countFetched, int totalCount)? progressUpdated,
) async {
await _setLocks[network]!.protect(() async {
- const sectorSize =
- 1500; // chosen as a somewhat decent value. Could be changed in the future if wanted/needed
+ const sectorSize = 1500; // chosen as a somewhat decent value. Could be changed in the future if wanted/needed
final prevMeta = await FiroCacheCoordinator.getLatestSetInfoForGroupId(
groupId,
network,
@@ -141,10 +151,9 @@ abstract class FiroCacheCoordinator {
coins.addAll(data);
}
- final result =
- coins
- .map((e) => RawSparkCoin.fromRPCResponse(e as List, groupId))
- .toList();
+ final result = coins
+ .map((e) => RawSparkCoin.fromRPCResponse(e as List, groupId))
+ .toList();
await _workers[network]!.runTask(
FCTask(
@@ -224,17 +233,16 @@ abstract class FiroCacheCoordinator {
String? afterBlockHash,
required CryptoCurrencyNetwork network,
}) async {
- final resultSet =
- afterBlockHash == null
- ? await _Reader._getSetCoinsForGroupId(
- groupId,
- db: _FiroCache.setCacheDB(network),
- )
- : await _Reader._getSetCoinsForGroupIdAndBlockHash(
- groupId,
- afterBlockHash,
- db: _FiroCache.setCacheDB(network),
- );
+ final resultSet = afterBlockHash == null
+ ? await _Reader._getSetCoinsForGroupId(
+ groupId,
+ db: _FiroCache.setCacheDB(network),
+ )
+ : await _Reader._getSetCoinsForGroupIdAndBlockHash(
+ groupId,
+ afterBlockHash,
+ db: _FiroCache.setCacheDB(network),
+ );
return resultSet
.map(
### lib/db/sqlite/firo_cache_worker.dart
@@ -3,6 +3,7 @@ part of 'firo_cache.dart';
enum FCFuncName {
_updateSparkAnonSetCoinsWith,
_updateSparkUsedTagsWith,
+ _close,
}
class FCTask {
@@ -16,9 +17,29 @@ class FCTask {
class _FiroCacheWorker {
final SendPort _commands;
final ReceivePort _responses;
+ final Isolate _isolate;
final Map<String, Completer<Object?>> _activeRequests = {};
+ bool _closing = false;
+
+ Future<void> close() async {
+ final closed = runTask(FCTask(func: FCFuncName._close, data: null));
+ _closing = true;
+ try {
+ await closed.timeout(const Duration(seconds: 15));
+ } catch (_) {
+ // The worker may have exited before receiving the close request, or
+ // may be stuck in a task. The reset caller exits and retries deletion
+ // on the next launch instead of deleting files under a live worker.
+ _isolate.kill(priority: Isolate.immediate);
+ rethrow;
+ } finally {
+ _responses.close();
+ }
+ }
+
Future<Object?> runTask(FCTask task) async {
+ if (_closing) throw StateError('Firo cache worker is closing');
final completer = Completer<Object?>.sync();
_activeRequests[task.id] = completer;
_commands.send(task);
@@ -37,34 +58,30 @@ class _FiroCacheWorker {
initPort.handler = (dynamic initialMessage) {
final commandPort = initialMessage as SendPort;
- connection.complete(
- (
- ReceivePort.fromRawReceivePort(initPort),
- commandPort,
- ),
- );
+ connection.complete((
+ ReceivePort.fromRawReceivePort(initPort),
+ commandPort,
+ ));
};
+ final Isolate isolate;
try {
- await Isolate.spawn(
- _startWorkerIsolate,
- (
- initPort.sendPort,
- setCacheFilePath,
- usedTagsCacheFilePath,
- ),
- );
+ isolate = await Isolate.spawn(_startWorkerIsolate, (
+ initPort.sendPort,
+ setCacheFilePath,
+ usedTagsCacheFilePath,
+ ));
} catch (_) {
initPort.close();
rethrow;
}
final (receivePort, sendPort) = await connection.future;
- return _FiroCacheWorker._(receivePort, sendPort);
+ return _FiroCacheWorker._(receivePort, sendPort, isolate);
}
- _FiroCacheWorker._(this._responses, this._commands) {
+ _FiroCacheWorker._(this._responses, this._commands, this._isolate) {
_responses.listen(_handleResponsesFromIsolate);
}
@@ -93,6 +110,13 @@ class _FiroCacheWorker {
try {
final FCResult result;
switch (task.func) {
+ case FCFuncName._close:
+ setCacheDb.close();
+ usedTagsCacheDb.close();
+ receivePort.close();
+ sendPort.send((task.id, null));
+ return;
+
case FCFuncName._updateSparkAnonSetCoinsWith:
final data =
task.data as (SparkAnonymitySetMeta, List<RawSparkCoin>);
@@ -127,14 +151,8 @@ class _FiroCacheWorker {
final receivePort = ReceivePort();
args.$1.send(receivePort.sendPort);
final mutex = Mutex();
- final setCacheDb = sqlite3.open(
- args.$2,
- mode: OpenMode.readWrite,
- );
- final usedTagsCacheDb = sqlite3.open(
- args.$3,
- mode: OpenMode.readWrite,
- );
+ final setCacheDb = sqlite3.open(args.$2, mode: OpenMode.readWrite);
+ final usedTagsCacheDb = sqlite3.open(args.$3, mode: OpenMode.readWrite);
_handleCommandsToIsolate(
receivePort,
args.$1,
### lib/main.dart
@@ -40,7 +40,6 @@ import 'models/models.dart';
import 'models/node_model.dart';
import 'models/notification_model.dart';
import 'models/trade_wallet_lookup.dart';
-import 'pages/already_running_view.dart';
import 'pages/campfire_migrate_view.dart';
import 'pages/home_view/home_view.dart';
import 'pages/intro_view.dart';
@@ -64,9 +63,11 @@ import 'services/notifications_api.dart';
import 'services/notifications_service.dart';
import 'services/tor_service.dart';
import 'services/trade_service.dart';
+import 'themes/stack_theme_data.dart';
import 'themes/theme_providers.dart';
import 'themes/theme_service.dart';
import 'utilities/constants.dart';
+import 'utilities/desktop_startup_and_reset.dart';
import 'utilities/enums/backup_frequency_type.dart';
import 'utilities/flutter_secure_storage_interface.dart';
import 'utilities/logger.dart';
@@ -174,61 +175,37 @@ void main(List<String> args) async {
csWownero.setUseCsWowneroLoggerInternal(kDebugMode);
}
- DB.instance.hive.init(
- (await StackFileSystem.applicationHiveDirectory()).path,
- );
+ if (Util.isDesktop && !(await DesktopStartupAndReset.prepareForStartup())) {
+ return;
+ }
+
+ final hiveDir = await StackFileSystem.applicationHiveDirectory();
+ // Without any hive data this is a new install with nothing to migrate.
+ // Check before opening a box, which creates its file.
+ final isNewInstall =
+ Util.isDesktop &&
+ !hiveDir.listSync().any(
+ (file) => file.path.endsWith('.hive') || file.path.endsWith('.hivec'),
+ );
+ DB.instance.hive.init(hiveDir.path);
try {
await DB.instance.hive.openBox<dynamic>(DB.boxNameDBInfo);
await DB.instance.hive.openBox<dynamic>(DB.boxNamePrefs);
} on FileSystemException catch (e) {
- if (e.osError?.errorCode == 11 || e.message.contains('lock failed')) {
- // Another instance already holds the Hive database lock.
- // Try to bootstrap just enough of the theme system (Isar is independent
- // of Hive) so the error screen looks like a real Stack Wallet screen.
- Widget errorApp;
- try {
- await StackFileSystem.initThemesDir();
- await MainDB.instance.initMainDB();
- ThemeService.instance.init(MainDB.instance);
- errorApp = const ProviderScope(child: AlreadyRunningApp());
- } catch (_) {
- // Isar is also unavailable (e.g., another error). Fall back to a
- // minimal but still Inter-font styled screen.
- errorApp = MaterialApp(
- debugShowCheckedModeBanner: false,
- theme: ThemeData(fontFamily: GoogleFonts.inter().fontFamily),
- home: Scaffold(
- body: Center(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Text(
- AppConfig.appName,
- textAlign: TextAlign.center,
- style: GoogleFonts.inter(
- fontSize: 20,
- fontWeight: FontWeight.w600,
- ),
- ),
- const SizedBox(height: 8),
- Text(
- 'is already running.\n'
- 'Close the other window and try again.',
- textAlign: TextAlign.center,
- style: GoogleFonts.inter(fontSize: 16),
- ),
- ],
- ),
- ),
- ),
- );
- }
- runApp(errorApp);
+ if (DesktopStartupAndReset.isLockConflict(e)) {
+ await DesktopStartupAndReset.showAlreadyRunning();
return;
}
rethrow;
}
+ if (isNewInstall) {
+ await DB.instance.put<dynamic>(
+ boxName: DB.boxNameDBInfo,
+ key: 'hive_data_version',
+ value: Constants.currentDataVersion,
+ );
+ }
await Prefs.instance.init();
await Logging.instance.initialize(
@@ -758,13 +735,6 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
}
}
- InputBorder _buildOutlineInputBorder(Color color) {
- return OutlineInputBorder(
- borderSide: BorderSide(width: 1, color: color),
- borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius),
- );
- }
-
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
@@ -787,99 +757,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
navigatorKey: ref.read(pNavKey),
title: AppConfig.appName,
onGenerateRoute: RouteGenerator.generateRoute,
- theme: ThemeData(
- extensions: [colorScheme],
- highlightColor: colorScheme.highlight,
- brightness: colorScheme.brightness,
- fontFamily: GoogleFonts.inter().fontFamily,
- unselectedWidgetColor: colorScheme.radioButtonBorderDisabled,
- // textTheme: GoogleFonts.interTextTheme().copyWith(
- // button: STextStyles.button(context),
- // subtitle1: STextStyles.field(context).copyWith(
- // color: colorScheme.textDark,
- // ),
- // ),
- radioTheme: const RadioThemeData(
- splashRadius: 0,
- materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
- ),
- // splashFactory: NoSplash.splashFactory,
- splashColor: Colors.transparent,
- buttonTheme: ButtonThemeData(splashColor: colorScheme.splash),
- textButtonTheme: TextButtonThemeData(
- style: ButtonStyle(
- // splashFactory: NoSplash.splashFactory,
- overlayColor: MaterialStateProperty.all(colorScheme.splash),
- minimumSize: MaterialStateProperty.all<Size>(const Size(46, 46)),
- // textStyle: MaterialStateProperty.all<TextStyle>(
- // STextStyles.button(context)),
- foregroundColor: MaterialStateProperty.all(
- colorScheme.buttonTextSecondary,
- ),
- backgroundColor: MaterialStateProperty.all<Color>(
- colorScheme.buttonBackSecondary,
- ),
- shape: MaterialStateProperty.all<OutlinedBorder>(
- RoundedRectangleBorder(
- // 1000 to be relatively sure it keeps its pill shape
- borderRadius: BorderRadius.circular(1000),
- ),
- ),
- ),
- ),
- primaryColor: colorScheme.accentColorDark,
- primarySwatch: Util.createMaterialColor(colorScheme.accentColorDark),
- checkboxTheme: CheckboxThemeData(
- splashRadius: 0,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(
- Constants.size.checkboxBorderRadius,
- ),
- ),
- checkColor: MaterialStateColor.resolveWith((state) {
- if (state.contains(MaterialState.selected)) {
- return colorScheme.checkboxIconChecked;
- }
- return colorScheme.checkboxBGChecked;
- }),
- fillColor: MaterialStateColor.resolveWith((states) {
- if (states.contains(MaterialState.selected)) {
- return colorScheme.checkboxBGChecked;
- }
- return colorScheme.checkboxBorderEmpty;
- }),
- ),
- appBarTheme: AppBarTheme(
- centerTitle: false,
- color: colorScheme.background,
- surfaceTintColor: colorScheme.background,
- elevation: 0,
- ),
- inputDecorationTheme: InputDecorationTheme(
- focusColor: colorScheme.textFieldDefaultBG,
- fillColor: colorScheme.textFieldDefaultBG,
- filled: true,
- contentPadding: const EdgeInsets.symmetric(
- vertical: 6,
- horizontal: 12,
- ),
- // labelStyle: STextStyles.fieldLabel(context),
- // hintStyle: STextStyles.fieldLabel(context),
- enabledBorder: _buildOutlineInputBorder(
- colorScheme.textFieldDefaultBG,
- ),
- focusedBorder: _buildOutlineInputBorder(
- colorScheme.textFieldDefaultBG,
- ),
- errorBorder: _buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
- disabledBorder: _buildOutlineInputBorder(
- colorScheme.textFieldDefaultBG,
- ),
- focusedErrorBorder: _buildOutlineInputBorder(
- colorScheme.textFieldDefaultBG,
- ),
- ),
- ),
+ theme: stackThemeData(colorScheme),
home: CryptoNotifications(
child: Util.isDesktop
? FutureBuilder(
### lib/pages/already_running_view.dart
@@ -13,61 +13,15 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
-import 'package:google_fonts/google_fonts.dart';
import '../app_config.dart';
import '../themes/stack_colors.dart';
import '../themes/theme_providers.dart';
-import '../themes/theme_service.dart';
-import '../utilities/stack_file_system.dart';
import '../utilities/text_styles.dart';
import '../utilities/util.dart';
import '../widgets/app_icon.dart';
import '../widgets/background.dart';
-/// Root app widget for the "already running" error path.
-///
-/// Mirrors the theme bootstrap performed by [MaterialAppWithTheme] in main.dart
-/// but without touching Hive. Requires Isar + ThemeService to already be
-/// initialized before [runApp] is called.
-class AlreadyRunningApp extends ConsumerStatefulWidget {
- const AlreadyRunningApp({super.key});
-
- @override
- ConsumerState<AlreadyRunningApp> createState() => _AlreadyRunningAppState();
-}
-
-class _AlreadyRunningAppState extends ConsumerState<AlreadyRunningApp> {
- @override
- void initState() {
- super.initState();
- WidgetsBinding.instance.addPostFrameCallback((_) {
- ref.read(applicationThemesDirectoryPathProvider.notifier).state =
- StackFileSystem.themesDir!.path;
- // The first instance already verified/installed the light theme, so
- // getTheme cannot return null here.
- ref.read(themeProvider.state).state = ref
- .read(pThemeService)
- .getTheme(themeId: "light")!;
- });
- }
-
- @override
- Widget build(BuildContext context) {
- final colorScheme = ref.watch(colorProvider.state).state;
- return MaterialApp(
- debugShowCheckedModeBanner: false,
- title: AppConfig.appName,
- theme: ThemeData(
- extensions: [colorScheme],
- fontFamily: GoogleFonts.inter().fontFamily,
- splashColor: Colors.transparent,
- ),
- home: const AlreadyRunningView(),
- );
- }
-}
-
/// Error screen shown when this is a second instance of the app.
///
/// Mirrors [IntroView]'s layout: themed background, logo, app name heading,
@@ -159,25 +113,22 @@ class AlreadyRunningView extends ConsumerWidget {
Text(
AppConfig.appName,
textAlign: TextAlign.center,
- style: STextStyles.pageTitleH1(
- context,
- ).copyWith(fontSize: 40),
+ style: STextStyles.pageTitleH1(context)
+ .copyWith(fontSize: 40),
),
const Spacer(flex: 24),
Text(
AppConfig.shortDescriptionText,
textAlign: TextAlign.center,
- style: STextStyles.subtitle(
- context,
- ).copyWith(fontSize: 24),
+ style: STextStyles.subtitle(context)
+ .copyWith(fontSize: 24),
),
const Spacer(flex: 42),
Text(
_errorMessage,
textAlign: TextAlign.center,
- style: STextStyles.label(
- context,
- ).copyWith(fontSize: 18),
+ style: STextStyles.label(context)
+ .copyWith(fontSize: 18),
),
const Spacer(flex: 65),
],
### lib/pages_desktop_specific/password/delete_password_warning_view.dart
@@ -8,19 +8,15 @@
*
*/
-import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:isar_community/isar.dart';
import '../../app_config.dart';
-import '../../db/hive/db.dart';
-import '../../notifications/show_flush_bar.dart';
-import '../../pages/intro_view.dart';
+import '../../themes/theme_providers.dart';
+import '../../utilities/desktop_startup_and_reset.dart';
import '../../utilities/logger.dart';
-import '../../utilities/stack_file_system.dart';
import '../../utilities/text_styles.dart';
import '../../widgets/app_icon.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
@@ -45,67 +41,30 @@ class _ForgotPasswordDesktopViewState
extends ConsumerState<DeletePasswordWarningView> {
bool _deleteInProgress = false;
- Future<bool> _deleteStack() async {
- final appRoot = await StackFileSystem.applicationRootDirectory();
-
+ Future<void> _deleteStack() async {
+ // Dispose the normal UI before closing the databases it depends on.
+ await DesktopStartupAndReset.showMessage(
+ 'Resetting ${AppConfig.appName}...',
+ theme: ref.read(themeProvider),
+ );
+ await WidgetsBinding.instance.endOfFrame;
try {
- await DB.instance.hive.close();
- if (Platform.isWindows) {
- final xmrDir = Directory("${appRoot.path}/wallets");
- if (xmrDir.existsSync()) {
- await xmrDir.delete(recursive: true);
- }
- final epicDir = Directory("${appRoot.path}/epiccash");
- if (epicDir.existsSync()) {
- await epicDir.delete(recursive: true);
- }
-
- final mimblewimblecoinDir = Directory(
- "${appRoot.path}/mimblewimblecoin",
- );
- if (mimblewimblecoinDir.existsSync()) {
- await mimblewimblecoinDir.delete(recursive: true);
- }
-
- await Isar.getInstance("desktopStore")?.close(deleteFromDisk: true);
-
- await (await StackFileSystem.applicationHiveDirectory()).delete(
- recursive: true,
- );
- } else if (Platform.isLinux) {
- await appRoot.delete(recursive: true);
- } else {
- // macos in ipad mode
- final xmrDir = Directory("${appRoot.path}/wallets");
- if (xmrDir.existsSync()) {
- await xmrDir.delete(recursive: true);
- }
- final epicDir = Directory("${appRoot.path}/epiccash");
- if (epicDir.existsSync()) {
- await epicDir.delete(recursive: true);
- }
- final mimblewimblecoinDir = Directory(
- "${appRoot.path}/mimblewimblecoin",
- );
- if (mimblewimblecoinDir.existsSync()) {
- await mimblewimblecoinDir.delete(recursive: true);
- }
-
- await (await StackFileSystem.applicationHiveDirectory()).delete(
- recursive: true,
- );
- await (await StackFileSystem.applicationIsarDirectory()).delete(
- recursive: true,
- );
- }
-
- await DB.instance.init();
+ await DesktopStartupAndReset.resetBeforeExit();
} catch (e, s) {
- Logging.instance.f("$e\n$s", error: e, stackTrace: s);
- return false;
+ Logging.instance.f('$e\n$s', error: e, stackTrace: s);
+ await DesktopStartupAndReset.showMessage(
+ 'Reset could not finish. ${AppConfig.appName} will now close.\n'
+ 'Reopen the app to try again.',
+ );
+ await Future<void>.delayed(const Duration(seconds: 3));
+ exit(1);
}
-
- return true;
+ await DesktopStartupAndReset.showMessage(
+ '${AppConfig.appName} was reset and will now close.\n'
+ 'Reopen it to create or restore your wallets.',
+ );
+ await Future<void>.delayed(const Duration(seconds: 3));
+ exit(0);
}
@override
@@ -145,23 +104,28 @@ class _ForgotPasswordDesktopViewState
style: STextStyles.desktopTextSmall(context),
),
TextSpan(
- text:
- widget.shouldCreateNew
- ? "create a new ${AppConfig.prefix}"
- : "restore from backup",
+ text: widget.shouldCreateNew
+ ? "create a new ${AppConfig.prefix}"
+ : "restore from backup",
style: STextStyles.desktopTextSmallBold(context),
),
TextSpan(
text: ", we need to ",
style: STextStyles.desktopTextSmall(context),
),
TextSpan(
- text: "delete your old wallets",
+ text: "delete all wallets and app data",
style: STextStyles.desktopTextSmallBold(context),
),
TextSpan(
text:
- ". All wallets will be lost. If you have not written down your recovery phrase for EACH wallet, you may be in danger of losing funds. Continue?",
+ ". This includes contacts, notes, swap "
+ "history and settings. Files ending in .swb "
+ "and logs are not deleted. Without the recovery "
+ "phrase or a backup for EACH wallet, its "
+ "funds will be lost. ${AppConfig.appName} "
+ "will close; reopen it to create or restore "
+ "your wallets. Continue?",
style: STextStyles.desktopTextSmall(context),
),
],
@@ -170,49 +134,12 @@ class _ForgotPasswordDesktopViewState
),
const SizedBox(height: 48),
PrimaryButton(
- label: "Delete and continue",
+ label: "Delete everything and quit",
enabled: !_deleteInProgress,
onPressed: () async {
- final shouldDelete = !_deleteInProgress;
- setState(() {
- _deleteInProgress = true;
- });
-
- if (shouldDelete) {
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.info,
- message: "Deleting wallet...",
- context: context,
- ),
- );
-
- final success = await _deleteStack();
-
- if (success) {
- await showFloatingFlushBar(
- type: FlushBarType.success,
- message: "Wallet deleted",
- context: context,
- );
- if (mounted) {
- await Navigator.of(context).pushNamedAndRemoveUntil(
- IntroView.routeName,
- (_) => false,
- );
- }
- } else {
- await showFloatingFlushBar(
- type: FlushBarType.warning,
- message: "Something broke badly. Contact developer",
- context: context,
- );
-
- setState(() {
- _deleteInProgress = false;
- });
- }
- }
+ if (_deleteInProgress) return;
+ setState(() => _deleteInProgress = true);
+ await _deleteStack();
},
),
const SizedBox(height: 24),
### lib/themes/stack_theme_data.dart
@@ -0,0 +1,79 @@
+import 'package:flutter/material.dart';
+import 'package:google_fonts/google_fonts.dart';
+
+import '../utilities/constants.dart';
+import '../utilities/util.dart';
+import 'stack_colors.dart';
+
+ThemeData stackThemeData(StackColors colors) {
+ InputBorder outlineInputBorder() => OutlineInputBorder(
+ borderSide: BorderSide(width: 1, color: colors.textFieldDefaultBG),
+ borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius),
+ );
+
+ return ThemeData(
+ extensions: [colors],
+ highlightColor: colors.highlight,
+ brightness: colors.brightness,
+ fontFamily: GoogleFonts.inter().fontFamily,
+ unselectedWidgetColor: colors.radioButtonBorderDisabled,
+ radioTheme: const RadioThemeData(
+ splashRadius: 0,
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ splashColor: Colors.transparent,
+ buttonTheme: ButtonThemeData(splashColor: colors.splash),
+ textButtonTheme: TextButtonThemeData(
+ style: ButtonStyle(
+ overlayColor: MaterialStateProperty.all(colors.splash),
+ minimumSize: MaterialStateProperty.all<Size>(const Size(46, 46)),
+ foregroundColor: MaterialStateProperty.all(colors.buttonTextSecondary),
+ backgroundColor: MaterialStateProperty.all<Color>(
+ colors.buttonBackSecondary,
+ ),
+ shape: MaterialStateProperty.all<OutlinedBorder>(
+ RoundedRectangleBorder(borderRadius: BorderRadius.circular(1000)),
+ ),
+ ),
+ ),
+ primaryColor: colors.accentColorDark,
+ primarySwatch: Util.createMaterialColor(colors.accentColorDark),
+ checkboxTheme: CheckboxThemeData(
+ splashRadius: 0,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ Constants.size.checkboxBorderRadius,
+ ),
+ ),
+ checkColor: MaterialStateColor.resolveWith((state) {
+ if (state.contains(MaterialState.selected)) {
+ return colors.checkboxIconChecked;
+ }
+ return colors.checkboxBGChecked;
+ }),
+ fillColor: MaterialStateColor.resolveWith((states) {
+ if (states.contains(MaterialState.selected)) {
+ return colors.checkboxBGChecked;
+ }
+ return colors.checkboxBorderEmpty;
+ }),
+ ),
+ appBarTheme: AppBarTheme(
+ centerTitle: false,
+ color: colors.background,
+ surfaceTintColor: colors.background,
+ elevation: 0,
+ ),
+ inputDecorationTheme: InputDecorationTheme(
+ focusColor: colors.textFieldDefaultBG,
+ fillColor: colors.textFieldDefaultBG,
+ filled: true,
+ contentPadding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
+ enabledBorder: outlineInputBorder(),
+ focusedBorder: outlineInputBorder(),
+ errorBorder: outlineInputBorder(),
+ disabledBorder: outlineInputBorder(),
+ focusedErrorBorder: outlineInputBorder(),
+ ),
+ );
+}
### lib/utilities/desktop_startup_and_reset.dart
@@ -0,0 +1,394 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:archive/archive_io.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:isar_community/isar.dart';
+import 'package:path/path.dart' as path;
+
+import '../app_config.dart';
+import '../db/hive/db.dart';
+import '../db/isar/main_db.dart';
+import '../db/sqlite/firo_cache.dart';
+import '../models/isar/stack_theme.dart';
+import '../pages/already_running_view.dart';
+import '../themes/stack_colors.dart';
+import '../themes/stack_theme_data.dart';
+import '../themes/theme_providers.dart';
+import '../themes/theme_service.dart';
+import '../widgets/desktop/primary_button.dart';
+import '../widgets/desktop/secondary_button.dart';
+import 'stack_file_system.dart';
+import 'text_styles.dart';
+
+abstract final class DesktopStartupAndReset {
+ static const _dataDirectories = <String>[
+ 'isar',
+ 'wallets',
+ 'epiccash',
+ 'mimblewimblecoin',
+ 'xelis',
+ 'drift',
+ 'sqlite',
+ 'mwebd',
+ 'themes',
+ 'hive',
+ ];
+
+ // Keep this file and its handle until exit, including while deleting Hive.
+ static RandomAccessFile? _instanceLock;
+ static final Map<String, Future<_BundledAppearance>> _bundledAppearances = {};
+ static StackTheme? _displayTheme;
+
+ /// Run before opening Hive, Isar, or caches on desktop.
+ static Future<bool> prepareForStartup() async {
+ try {
+ await _acquireInstanceLock();
+ } on FileSystemException catch (e) {
+ if (isLockConflict(e)) {
+ await showAlreadyRunning();
+ return false;
+ }
+ rethrow;
+ }
+
+ if (!await isPending()) return true;
+ await askToFinishReset();
+ await showMessage('Resetting ${AppConfig.appName}...');
+ try {
+ await deleteData();
+ await clearPending();
+ } catch (e, s) {
+ debugPrint('Desktop reset failed: $e\n$s');
+ await showMessage(
+ 'Could not delete all ${AppConfig.appName} data.\n'
+ 'Close any program using it and reopen the app.',
+ );
+ return false;
+ }
+ return true;
+ }
+
+ static bool isLockConflict(FileSystemException e) =>
+ e.osError?.errorCode == 11 || e.message.contains('lock failed');
+
+ static Future<void> _acquireInstanceLock() async {
+ final root = await StackFileSystem.applicationRootDirectory();
+ _instanceLock = await File(path.join(root.path, '.instance.lock'))
+ .open(mode: FileMode.append);
+ await _instanceLock!.lock();
+ }
+
+ static Future<File> _marker() async => File(
+ path.join(
+ (await StackFileSystem.applicationRootDirectory()).path,
+ '.reset-pending',
+ ),
+ );
+
+ /// Never infer from password files that an unfinished reset was cancelled.
+ static Future<bool> isPending() async {
+ return (await _marker()).exists();
+ }
+
+ static Future<void> clearPending() async {
+ final marker = await _marker();
+ if (await marker.exists()) await marker.delete();
+ }
+
+ // Only called from the login reset flow, before wallets are loaded.
+ // The caller must exit afterwards, even if closing or deleting fails.
+ static Future<void> resetBeforeExit() async {
+ // Stays behind if the reset does not finish, so the next launch can ask.
+ await (await _marker()).writeAsString('reset', flush: true);
+
+ await _deleteKeys();
+ await FiroCacheCoordinator.close();
+ await DB.instance.hive.close();
+ for (final name in Isar.instanceNames.toList()) {
+ final isar = Isar.getInstance(name);
+ if (isar != null && !await isar.close()) {
+ throw StateError('Could not close Isar database $name');
+ }
+ }
+ await deleteData();
+ await clearPending();
+ }
+
+ static Future<void> deleteData() async {
+ final root = await StackFileSystem.applicationRootDirectory();
+ // Only app-owned paths: the root may be a custom -d directory or the
+ // iPad Library directory. Leave backups, exports, logs and the lock alone.
+ // tor/ is kept: it only holds Tor network state, and a running Tor client
+ // keeps files there open.
+ await _deleteKeys();
+ for (final name in _dataDirectories) {
+ await _deleteExceptSwb(Directory(path.join(root.path, name)));
+ }
+ }
+
+ static Future<void> _deleteExceptSwb(FileSystemEntity entity) async {
+ final type = await FileSystemEntity.type(entity.path, followLinks: false);
+ if (type == FileSystemEntityType.notFound) return;
+
+ if (type == FileSystemEntityType.directory) {
+ final directory = Directory(entity.path);
+ await for (final child in directory.list(followLinks: false)) {
+ await _deleteExceptSwb(child);
+ }
+ if (await directory.list(followLinks: false).isEmpty) {
+ await directory.delete();
+ }
+ } else if (!entity.path.toLowerCase().endsWith('.swb')) {
+ // Delete links themselves, never the files or directories they target.
+ if (type == FileSystemEntityType.link) {
+ await Link(entity.path).delete();
+ } else {
+ await File(entity.path).delete();
+ }
+ }
+ }
+
+ // The password, then the secure storage holding the wallet keys. Deleted
+ // first, so a reset is permanent once it starts. Neither is open before
+ // login.
+ static Future<void> _deleteKeys() async {
+ final root = (await StackFileSystem.applicationRootDirectory()).path;
+ for (final file in [
+ // Hive restores .hivec if .hive is missing. Remove it first so a crash
+ // cannot resurrect the password after deleting .hive.
+ for (final ext in ['hivec', 'hive', 'lock'])
+ File(path.join(root, 'hive', 'desktopdata.$ext')),
+ for (final ext in ['isar', 'isar-lck', 'isar.lock'])
+ File(path.join(root, 'isar', 'desktopStore.$ext')),
+ ]) {
+ if (await file.exists()) await file.delete();
+ }
+ }
+
+ static Future<void> askToFinishReset() async {
+ final done = Completer<void>();
+ await _show(
+ title: 'Unfinished reset',
+ message:
+ 'A reset of ${AppConfig.appName} did not finish. Continuing will '
+ 'delete current wallet data and settings, including anything '
+ 'created since the reset began. Files ending in .swb will be kept.',
+ primaryAction: (
+ label: 'Retry deleting',
+ onPressed: () {
+ if (!done.isCompleted) done.complete();
+ },
+ ),
+ secondaryAction: (label: 'Quit', onPressed: () => exit(0)),
+ );
+ return done.future;
+ }
+
+ static Future<void> showMessage(String message, {StackTheme? theme}) =>
+ _show(title: AppConfig.appName, message: message, theme: theme);
+
+ static Future<void> showAlreadyRunning() async {
+ try {
+ await StackFileSystem.initThemesDir();
+ await MainDB.instance.initMainDB();
+ ThemeService.instance.init(MainDB.instance);
+ final theme = ThemeService.instance.getTheme(themeId: 'light');
+ if (theme == null) throw StateError('Default theme is unavailable');
+ _showScreen(const AlreadyRunningView(), theme);
+ } catch (_) {
+ await _show(
+ title: AppConfig.appName,
+ message: 'is already running.\nClose the other window and try again.',
+ );
+ }
+ }
+
+ static Future<void> _show({
+ required String title,
+ required String message,
+ ({String label, VoidCallback onPressed})? primaryAction,
+ ({String label, VoidCallback onPressed})? secondaryAction,
+ StackTheme? theme,
+ }) async {
+ final screenTheme =
+ theme ?? _displayTheme ?? (await _bundledAppearance('light')).theme;
+ final logoTheme =
+ screenTheme.brightness == Brightness.dark &&
+ AppConfig.hasFeature(AppFeature.themeSelection)
+ ? 'dark'
+ : 'light';
+ final appearance = await _bundledAppearance(logoTheme);
+ _showScreen(
+ _DesktopStatusView(
+ title: title,
+ message: message,
+ logo: appearance.logo,
+ logoIsPng: appearance.logoIsPng,
+ primaryAction: primaryAction,
+ secondaryAction: secondaryAction,
+ ),
+ screenTheme,
+ );
+ }
+
+ static Future<_BundledAppearance> _bundledAppearance(String name) =>
+ _bundledAppearances.putIfAbsent(name, () => _loadBundledAppearance(name));
+
+ static Future<_BundledAppearance> _loadBundledAppearance(String name) async {
+ final data = await rootBundle.load('assets/default_themes/$name.zip');
+ final archive = ZipDecoder().decodeBytes(data.buffer.asUint8List());
+ final themeJson = archive.files.singleWhere(
+ (file) => file.name == 'theme.json',
+ );
+ final json = Map<String, dynamic>.from(
+ jsonDecode(utf8.decode(themeJson.content as List<int>)) as Map,
+ );
+ final logoPath =
+ 'assets/${(json['assets'] as Map)['stack_icon'] as String}';
+ final logo = archive.files.singleWhere((file) => file.name == logoPath);
+ return _BundledAppearance(
+ theme: StackTheme.fromJson(json: json),
+ logo: Uint8List.fromList(logo.content as List<int>),
+ logoIsPng: logoPath.toLowerCase().endsWith('.png'),
+ );
+ }
+
+ static void _showScreen(Widget home, StackTheme theme) {
+ _displayTheme = theme;
+ runApp(
+ KeyedSubtree(
+ // Dispose the normal ProviderScope before its databases are closed.
+ key: UniqueKey(),
+ child: ProviderScope(
+ overrides: [
+ themeProvider.overrideWithProvider(
+ StateProvider<StackTheme>((ref) => theme),
+ ),
+ ],
+ child: _DesktopAppShell(home: home),
+ ),
+ ),
+ );
+ }
+}
+
+class _DesktopAppShell extends ConsumerWidget {
+ const _DesktopAppShell({required this.home});
+
+ final Widget home;
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final colorScheme = ref.watch(colorProvider.state).state;
+ return MaterialApp(
+ title: AppConfig.appName,
+ theme: stackThemeData(colorScheme),
+ home: home,
+ );
+ }
+}
+
+class _BundledAppearance {
+ const _BundledAppearance({
+ required this.theme,
+ required this.logo,
+ required this.logoIsPng,
+ });
+
+ final StackTheme theme;
+ final Uint8List logo;
+ final bool logoIsPng;
+}
+
+class _DesktopStatusView extends StatelessWidget {
+ const _DesktopStatusView({
+ required this.title,
+ required this.message,
+ required this.logo,
+ required this.logoIsPng,
+ this.primaryAction,
+ this.secondaryAction,
+ });
+
+ final String title;
+ final String message;
+ final Uint8List logo;
+ final bool logoIsPng;
+ final ({String label, VoidCallback onPressed})? primaryAction;
+ final ({String label, VoidCallback onPressed})? secondaryAction;
+
+ @override
+ Widget build(BuildContext context) {
+ final colors = Theme.of(context).extension<StackColors>()!;
+ return Material(
+ color: colors.background,
+ child: DecoratedBox(
+ decoration: BoxDecoration(
+ color: colors.background,
+ gradient: colors.gradientBackground,
+ ),
+ child: SafeArea(
+ child: LayoutBuilder(
+ builder: (context, constraints) => SingleChildScrollView(
+ padding: const EdgeInsets.symmetric(horizontal: 32),
+ child: ConstrainedBox(
+ constraints: BoxConstraints(minHeight: constraints.maxHeight),
+ child: Center(
+ child: SizedBox(
+ width: 480,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ SizedBox(
+ width: 100,
+ height: 100,
+ child: logoIsPng
+ ? Image.memory(logo, fit: BoxFit.contain)
+ : SvgPicture.memory(logo, fit: BoxFit.contain),
+ ),
+ const SizedBox(height: 42),
+ Text(
+ title,
+ textAlign: TextAlign.center,
+ style: STextStyles.desktopH1(context),
+ ),
+ const SizedBox(height: 24),
+ Text(
+ message,
+ textAlign: TextAlign.center,
+ style: STextStyles.desktopTextSmall(context)
+ .copyWith(color: colors.textSubtitle1),
+ ),
+ if (primaryAction != null) ...[
+ const SizedBox(height: 48),
+ PrimaryButton(
+ label: primaryAction!.label,
+ onPressed: primaryAction!.onPressed,
+ ),
+ ],
+ if (secondaryAction != null) ...[
+ const SizedBox(height: 24),
+ SecondaryButton(
+ label: secondaryAction!.label,
+ onPressed: secondaryAction!.onPressed,
+ ),
+ ],
+ if (primaryAction != null || secondaryAction != null)
+ const SizedBox(height: 96),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
### pubspec.lock
@@ -1823,7 +1823,7 @@ packages:
source: hosted
version: "2.2.1"
path_provider_platform_interface:
- dependency: transitive
+ dependency: "direct dev"
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
### scripts/app_config/templates/pubspec.template.yaml
@@ -283,6 +283,7 @@ dev_dependencies:
sdk: flutter
integration_test:
sdk: flutter
+ path_provider_platform_interface: ^2.1.2
build_runner: ^2.15.1
flutter_launcher_icons: ^0.13.1
hive_ce_generator: ^1.11.1
### scripts/test_desktop_forgot_password.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Temporary workaround for https://github.com/flutter/flutter/issues/135673.
+# Remove this script when Flutter is updated to 3.49 or later. Then run:
+# flutter test integration_test/desktop_forgot_password -d macos
+# Usage: bash scripts/test_desktop_forgot_password.sh [device] [test options]
+# Set FLUTTER_BIN to use a Flutter executable other than the one on PATH.
+
+cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
+reset_test_flutter="${FLUTTER_BIN:-flutter}"
+reset_test_device="${1:-macos}"
+if (( $# > 0 )); then shift; fi
+
+reset_test_status=0
+for reset_test_file in integration_test/desktop_forgot_password/*_test.dart; do
+ if [[ ! -f "$reset_test_file" ]]; then
+ printf 'No desktop forgot-password test files found.\n' >&2
+ exit 2
+ fi
+ if "$reset_test_flutter" test -d "$reset_test_device" "$reset_test_file" "$@"; then
+ continue
+ else
+ reset_test_exit=$?
+ case "$reset_test_exit" in
+ 130|143) exit "$reset_test_exit" ;;
+ esac
+ reset_test_status=1
+ fi
+done
+exit "$reset_test_status"Why this scored 57/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.