diff --git a/analysis_options.yaml b/analysis_options.yaml index 02972b9f4b..3bd4cea564 100644 --- a/analysis_options.yaml +++ b/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" diff --git a/integration_test/bot_runners/create_wallet_until_pin_confirmation.dart b/integration_test/bot_runners/create_wallet_until_pin_confirmation.dart deleted file mode 100644 index 73dadf4d11..0000000000 --- a/integration_test/bot_runners/create_wallet_until_pin_confirmation.dart +++ /dev/null @@ -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 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(); -} diff --git a/integration_test/bots/addressbook/add_address_book_entry_view_bot.dart b/integration_test/bots/addressbook/add_address_book_entry_view_bot.dart deleted file mode 100644 index 95aec11003..0000000000 --- a/integration_test/bots/addressbook/add_address_book_entry_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(AddAddressBookEntryView)); - } - - Future tapBack() async { - await tester.tap(find.byType(AppBarIconButton)); - await tester.pumpAndSettle(); - } - - Future enterAddress(String text) async { - await tester.enterText( - find.byKey(Key("addAddressBookEntryViewAddressField")), text); - await tester.pumpAndSettle(); - } - - Future enterName(String text) async { - await tester.enterText( - find.byKey(Key("addAddressBookEntryViewNameField")), text); - await tester.pumpAndSettle(); - } - - Future tapCancel() async { - await tester.tap(find.byType(SimpleButton)); - await tester.pumpAndSettle(); - } - - Future tapSave() async { - await tester.tap(find.byType(GradientButton)); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/addressbook/address_book_card_bot.dart b/integration_test/bots/addressbook/address_book_card_bot.dart deleted file mode 100644 index 33dfd5068e..0000000000 --- a/integration_test/bots/addressbook/address_book_card_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(AddressBookCard)); - } - - Future toggleExpandCard() async { - await tester.tap(find.byType(AddressBookCard)); - await tester.pumpAndSettle(); - } - - Future tapDetails() async { - await tester.tap(find.byWidgetPredicate( - (widget) => widget is SubButton && widget.label == "DETAILS")); - await tester.pumpAndSettle(); - } - - Future tapCopy() async { - await tester.tap(find.byWidgetPredicate( - (widget) => widget is SubButton && widget.label == "COPY")); - await tester.pumpAndSettle(); - } - - Future tapSend() async { - await tester.tap(find.byWidgetPredicate( - (widget) => widget is SubButton && widget.label == "SEND FIRO")); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/addressbook/address_book_entry_details_view_bot.dart b/integration_test/bots/addressbook/address_book_entry_details_view_bot.dart deleted file mode 100644 index 620ae24165..0000000000 --- a/integration_test/bots/addressbook/address_book_entry_details_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(AddressBookEntryDetailsView)); - } - - Future tapBack() async { - await tester.tap(find.byKey(Key("addressBookDetailsBackButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapMore() async { - await tester.tap(find.byKey(Key("addressBookDetailsDeleteButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapDelete() async { - await tester - .tap(find.byKey(Key("addressBookDetailsContextMenuDeleteButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapConfirmDelete() async { - await tester - .tap(find.byKey(Key("deleteContactConfirmationDialogDeleteButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapCancelDelete() async { - await tester - .tap(find.byKey(Key("deleteContactConfirmationDialogCancelButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapCopyAddress() async { - await tester - .tap(find.byKey(Key("addressBookEntryDetailsCopyAddressButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapEdit() async { - await tester - .tap(find.byKey(Key("addressBookEntryDetailsEditEntryButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapSend() async { - await tester.tap(find.byType(GradientButton)); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/addressbook/address_book_view_bot.dart b/integration_test/bots/addressbook/address_book_view_bot.dart deleted file mode 100644 index 3e4d27d666..0000000000 --- a/integration_test/bots/addressbook/address_book_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(AddressBookView)); - } - - Future tapBack() async { - await tester.tap(find.byKey(Key("addressBookBackButton"))); - await tester.pumpAndSettle(); - } - - Future tapAdd() async { - await tester.tap(find.byKey(Key("addressBookAddButton"))); - await tester.pumpAndSettle(); - } - - Future enterQuery(String query) async { - await tester.enterText(find.byType(TextField), query); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/addressbook/edit_address_book_entry_view_bot.dart b/integration_test/bots/addressbook/edit_address_book_entry_view_bot.dart deleted file mode 100644 index ba1025e67d..0000000000 --- a/integration_test/bots/addressbook/edit_address_book_entry_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(EditAddressBookEntryView)); - } - - Future tapBack() async { - await tester.tap(find.byKey(Key("editAddressBookEntryBackButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapCancel() async { - await tester.tap(find.byType(SimpleButton)); - await tester.pumpAndSettle(); - } - - Future tapSave() async { - await tester.tap(find.byType(GradientButton)); - await tester.pumpAndSettle(); - } - - Future enterAddress(String text) async { - await tester.enterText( - find.byKey(Key("editAddressBookEntryAddressFieldKey")), text); - } - - Future enterName(String text) async { - await tester.enterText( - find.byKey(Key("editAddressBookEntryNameFieldKey")), text); - } -} diff --git a/integration_test/bots/lockscreen_view_bot.dart b/integration_test/bots/lockscreen_view_bot.dart deleted file mode 100644 index 7548d06ab9..0000000000 --- a/integration_test/bots/lockscreen_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(LockscreenView)); - } - - Future tapBack() async { - await tester.tap(find.byType(AppBarIconButton)); - await tester.pumpAndSettle(); - } - - Future 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)); - } -} diff --git a/integration_test/bots/main_view_bot.dart b/integration_test/bots/main_view_bot.dart deleted file mode 100644 index 9273fe743e..0000000000 --- a/integration_test/bots/main_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(MainView)); - } - - Future tapRefresh() async { - await tester.tap(find.byKey(Key("mainViewRefreshButton"))); - await tester.pumpAndSettle(); - } - - Future tapSettings() async { - await tester.tap(find.byKey(Key("mainViewSettingsButton"))); - await tester.pumpAndSettle(); - } - - Future tapSend() async { - await tester.tap(find.byKey(Key("mainViewNavBarSendItemKey"))); - await tester.pumpAndSettle(); - } - - Future tapWallet() async { - await tester.tap(find.byKey(Key("mainViewNavBarWalletItemKey"))); - await tester.pumpAndSettle(); - } - - Future tapReceive() async { - await tester.tap(find.byKey(Key("mainViewNavBarReceiveItemKey"))); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/onboarding/backup_key_view_bot.dart b/integration_test/bots/onboarding/backup_key_view_bot.dart deleted file mode 100644 index 69e18b388a..0000000000 --- a/integration_test/bots/onboarding/backup_key_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(BackupKeyView)); - } - - Future tapVerify() async { - final buttonFinder = find.byType(GradientButton); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } - - Future tapQrCode() async { - final buttonFinder = find.byKey(Key("backupKeyQrCodeButtonKey")); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } - - Future tapSkip() async { - final buttonFinder = find.byType(TextButton); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } - - Future tapCopy() async { - final buttonFinder = find.byKey(Key("backupKeyViewCopyButtonKey")); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } - - Future tapBack() async { - final buttonFinder = find.byType(AppBarIconButton); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } - - Future> displayedMnemonic() async { - List 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; - } -} diff --git a/integration_test/bots/onboarding/backup_key_warning_bot.dart b/integration_test/bots/onboarding/backup_key_warning_bot.dart deleted file mode 100644 index 3bf57beb1f..0000000000 --- a/integration_test/bots/onboarding/backup_key_warning_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(BackupKeyWarningView)); - } - - Future tapViewBackupKey() async { - final buttonFinder = find.byType(GradientButton); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } - - Future tapCheckBox() async { - final buttonFinder = find.byType(Checkbox); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } - - Future tapSkip() async { - final buttonFinder = find.byType(TextButton); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } - - Future tapBack() async { - final buttonFinder = find.byType(AppBarIconButton); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/onboarding/create_pin_view_bot.dart b/integration_test/bots/onboarding/create_pin_view_bot.dart deleted file mode 100644 index c92ece57d3..0000000000 --- a/integration_test/bots/onboarding/create_pin_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(CreatePinView)); - } - - Future 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 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 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 tapBack() async { - final buttonFinder = find.byType(AppBarIconButton); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/onboarding/name_your_wallet_view_bot.dart b/integration_test/bots/onboarding/name_your_wallet_view_bot.dart deleted file mode 100644 index 059b2c613f..0000000000 --- a/integration_test/bots/onboarding/name_your_wallet_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(NameYourWalletView)); - } - - Future enterWalletName(String name) async { - final textFieldFinder = find.byType(TextField); - await tester.ensureVisible(textFieldFinder); - await tester.enterText(textFieldFinder, name); - await tester.pumpAndSettle(); - } - - Future tapNext() async { - final buttonFinder = find.byType(GradientButton); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } - - Future tapBack() async { - final buttonFinder = find.byType(AppBarIconButton); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/onboarding/onboarding_view_bot.dart b/integration_test/bots/onboarding/onboarding_view_bot.dart deleted file mode 100644 index 062df6f254..0000000000 --- a/integration_test/bots/onboarding/onboarding_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(OnboardingView)); - } - - Future tapCreateNewWallet() async { - final buttonFinder = find.byType(GradientButton); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } - - Future tapRestoreWallet() async { - final buttonFinder = find.byType(SimpleButton); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/onboarding/restore_wallet_form_view_bot.dart b/integration_test/bots/onboarding/restore_wallet_form_view_bot.dart deleted file mode 100644 index cfdffb8e84..0000000000 --- a/integration_test/bots/onboarding/restore_wallet_form_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(RestoreWalletFormView)); - } - - Future tapBack() async { - await tester.tap(find.byKey(Key("onboardingAppBarBackButton"))); - await tester.pumpAndSettle(); - } - - Future tapScanQrCode() async { - await tester.tap(find.byKey(Key("restoreWalletViewScanQRButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapCancelScanQrCode() async { - await tester.tap(find.text("CANCEL")); - await tester.pumpAndSettle(); - } - - Future tapPaste() async { - await tester.tap(find.byKey(Key("restoreWalletViewPasteButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapRestore(bool settle) async { - await tester.tap(find.byKey(Key("restoreMnemonicViewRestoreButtonKey"))); - if (settle) { - await tester.pumpAndSettle(); - } else { - await tester.pump(Duration(seconds: 1)); - } - } - - Future tapCancelRestore() async { - await tester - .tap(find.byKey(Key("restoreWalletWaitingDialogCancelButtonKey"))); - await tester.pumpAndSettle(); - } - - Future enterWord(String word, int position) async { - await tester.enterText( - find.byKey(Key("restoreMnemonicFormField_$position")), word); - } - - Future scrollDown() async { - await tester.fling( - find.byType(SingleChildScrollView), Offset(0, -500), 10000); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/onboarding/terms_and_conditions_bot.dart b/integration_test/bots/onboarding/terms_and_conditions_bot.dart deleted file mode 100644 index 270f839658..0000000000 --- a/integration_test/bots/onboarding/terms_and_conditions_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(TermsAndConditionsView)); - } - - Future tapIAccept() async { - final buttonFinder = find.byType(GradientButton); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } - - Future tapBack() async { - final buttonFinder = find.byType(AppBarIconButton); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } - - Future scrollDown() async { - await tester.fling( - find.byType(SingleChildScrollView), Offset(0, -1000), 10000); - await tester.pumpAndSettle(); - } - - Future scrollUp() async { - await tester.fling( - find.byType(SingleChildScrollView), Offset(0, 1000), 10000); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/onboarding/verify_backup_key_view_bot.dart b/integration_test/bots/onboarding/verify_backup_key_view_bot.dart deleted file mode 100644 index 71844a7c17..0000000000 --- a/integration_test/bots/onboarding/verify_backup_key_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(VerifyBackupKeyView)); - } - - Future 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 tapBack() async { - final buttonFinder = find.byType(AppBarIconButton); - await tester.ensureVisible(buttonFinder); - await tester.tap(buttonFinder); - await tester.pumpAndSettle(); - } - - Future enterRequestedWord(List 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(); - } -} diff --git a/integration_test/bots/receive_view_bot.dart b/integration_test/bots/receive_view_bot.dart deleted file mode 100644 index c895b96759..0000000000 --- a/integration_test/bots/receive_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(ReceiveView)); - } -// -// Future tapX() async { -// await tester.tap(find.byKey(Key("cancelTransactionSearchAppBarButton"))); -// await tester.pumpAndSettle(); -// } -// -// Future tapCancel() async { -// await tester.tap(find.byType(SimpleButton)); -// await tester.pumpAndSettle(); -// } -} diff --git a/integration_test/bots/rescan_warning_view_bot.dart b/integration_test/bots/rescan_warning_view_bot.dart deleted file mode 100644 index 797b7338d5..0000000000 --- a/integration_test/bots/rescan_warning_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(RescanWarningView)); - } - - Future tapBack() async { - await tester.tap(find.byKey(Key("settingsAppBarBackButton"))); - await tester.pumpAndSettle(); - } - - Future tapQrCode() async { - await tester.tap(find.byKey(Key("rescanWarningShowQrCodeButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapCancelQrCode() async { - await tester - .tap(find.byKey(Key("rescanWarningQrCodePopupCancelButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapCopy() async { - await tester.tap(find.byKey(Key("rescanWarningCopySeedButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapContinue() async { - await tester.tap(find.byType(GradientButton)); - await tester.pumpAndSettle(); - } - - Future tapConfirmContinue() async { - await tester.tap(find.byKey(Key("rescanWarningContinueRescanButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapCancelContinue() async { - await tester.tap(find.byKey(Key("rescanWarningContinueCancelButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapOkOnRescanFailedDialog() async { - await tester - .tap(find.byKey(Key("rescanWarningViewRescanFailedOkButtonKey"))); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/send_view_bot.dart b/integration_test/bots/send_view_bot.dart deleted file mode 100644 index 840c0bb124..0000000000 --- a/integration_test/bots/send_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(SendView)); - } - // - // Future tapX() async { - // await tester.tap(find.byKey(Key("cancelTransactionSearchAppBarButton"))); - // await tester.pumpAndSettle(); - // } - // - // Future tapCancel() async { - // await tester.tap(find.byType(SimpleButton)); - // await tester.pumpAndSettle(); - // } -} diff --git a/integration_test/bots/settings/currency_view_bot.dart b/integration_test/bots/settings/currency_view_bot.dart deleted file mode 100644 index 822745b205..0000000000 --- a/integration_test/bots/settings/currency_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(CurrencyView)); - } - - Future tapBack() async { - await tester.tap(find.byKey(Key("settingsAppBarBackButton"))); - await tester.pumpAndSettle(); - } - - Future 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); - } -} diff --git a/integration_test/bots/settings/network_settings/add_custom_node_view_bot.dart b/integration_test/bots/settings/network_settings/add_custom_node_view_bot.dart deleted file mode 100644 index c2d2b5a3da..0000000000 --- a/integration_test/bots/settings/network_settings/add_custom_node_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(AddCustomNodeView)); - } - - Future tapBack() async { - await tester.tap(find.byType(AppBarIconButton)); - await tester.pumpAndSettle(); - } - - Future enterNodeName(String name) async { - await tester.enterText( - find.byKey(Key("addCustomNodeNodeNameFieldKey")), name); - await tester.pumpAndSettle(); - } - - Future enterNodeAddress(String address) async { - await tester.enterText( - find.byKey(Key("addCustomNodeNodeAddressFieldKey")), address); - await tester.pumpAndSettle(); - } - - Future enterNodePort(String port) async { - await tester.enterText( - find.byKey(Key("addCustomNodeNodePortFieldKey")), port); - await tester.pumpAndSettle(); - } - - Future tapSSLCheckbox() async { - await tester.tap(find.byType(Checkbox)); - await tester.pumpAndSettle(); - } - - Future tapTestConnection() async { - await tester.tap(find.byType(SimpleButton)); - await tester.pump(Duration(milliseconds: 500)); - } - - Future 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(); - } -} diff --git a/integration_test/bots/settings/network_settings/network_settings_view_bot.dart b/integration_test/bots/settings/network_settings/network_settings_view_bot.dart deleted file mode 100644 index 902546fefd..0000000000 --- a/integration_test/bots/settings/network_settings/network_settings_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(NetworkSettingsView)); - } - - Future tapBack() async { - await tester.tap(find.byKey(Key("settingsAppBarBackButton"))); - await tester.pumpAndSettle(); - } - - Future tapAdd() async { - await tester.tap(find.byKey(Key("networkSettingsAddNodeButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapNode(String name) async { - await tester.tap(find.byKey(Key("networkSettingsViewNodeCard_$name"))); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/settings/network_settings/node_details_view_bot.dart b/integration_test/bots/settings/network_settings/node_details_view_bot.dart deleted file mode 100644 index 6a0b6c8fcf..0000000000 --- a/integration_test/bots/settings/network_settings/node_details_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(NodeDetailsView)); - } - - Future tapBack() async { - await tester.tap(find.byKey(Key("nodeDetailsViewBackButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapMore() async { - await tester.tap(find.byKey(Key("nodeDetailsViewMoreButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapMoreAndEdit() async { - await tapMore(); - await tester.tap(find.text("Edit node")); - await tester.pumpAndSettle(); - } - - Future tapMoreAndDelete() async { - await tapMore(); - await tester.tap(find.text("Delete node")); - await tester.pumpAndSettle(); - } - - Future tapCancelDelete() async { - await tester - .tap(find.byKey(Key("nodeDetailsConfirmDeleteCancelButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapConfirmDelete() async { - await tester - .tap(find.byKey(Key("nodeDetailsConfirmDeleteConfirmButtonKey"))); - await tester.pumpAndSettle(); - } - - Future enterName(String name) async { - await tester.enterText(find.byKey(Key("editNodeNodeNameFieldKey")), name); - await tester.pump(); - } - - Future enterAddress(String address) async { - await tester.enterText(find.byKey(Key("editNodeAddressFieldKey")), address); - await tester.pump(); - } - - Future enterPort(String port) async { - await tester.enterText(find.byKey(Key("editNodeNodePortFieldKey")), port); - await tester.pump(); - } - - Future tapUseSSLCheckbox() async { - await tester.tap(find.byType(Checkbox)); - await tester.pumpAndSettle(); - } - - Future tapTestConnection() async { - await tester.tap(find.byType(SimpleButton)); - await tester.pump(Duration(milliseconds: 500)); - } - - Future tapSave() async { - await tester.tap(find.byType(GradientButton)); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/settings/settings_view_bot.dart b/integration_test/bots/settings/settings_view_bot.dart deleted file mode 100644 index 0c6d9b8aac..0000000000 --- a/integration_test/bots/settings/settings_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(SettingsView)); - } - - Future tapBack() async { - await tester.tap(find.byKey(Key("settingsAppBarBackButton"))); - await tester.pumpAndSettle(); - } - - Future tapLogout() async { - await tester.tap(find.byKey(Key("settingsLogoutAppBarButton"))); - await tester.pumpAndSettle(); - } - - Future tapAddressBook() async { - await tester.tap(find.byKey(Key("settingsOptionAddressBook"))); - await tester.pumpAndSettle(); - } - - Future tapNetwork() async { - await tester.tap(find.byKey(Key("settingsOptionNetwork"))); - await tester.pumpAndSettle(); - } - - Future tapWalletBackup() async { - await tester.tap(find.byKey(Key("settingsOptionWalletBackup"))); - await tester.pumpAndSettle(); - } - - Future tapWalletSettings() async { - await tester.tap(find.byKey(Key("settingsOptionWalletSettings"))); - await tester.pumpAndSettle(); - } - - Future tapCurrency() async { - await tester.tap(find.byKey(Key("settingsOptionCurrency"))); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/settings/wallet_backup_view_bot.dart b/integration_test/bots/settings/wallet_backup_view_bot.dart deleted file mode 100644 index 96b6c54218..0000000000 --- a/integration_test/bots/settings/wallet_backup_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(WalletBackUpView)); - } - - Future tapBack() async { - await tester.tap(find.byType(AppBarIconButton)); - await tester.pumpAndSettle(); - } - - Future tapQrCode() async { - await tester.tap(find.byKey(Key("walletBackupQrCodeButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapQrCodeCancel() async { - await tester.tap(find.byKey(Key("walletBackupQrCodeCancelButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapCopy() async { - await tester.tap(find.byKey(Key("walletBackupCopyButtonKey"))); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/settings/wallet_settings/change_pin_view_bot.dart b/integration_test/bots/settings/wallet_settings/change_pin_view_bot.dart deleted file mode 100644 index 009b71d449..0000000000 --- a/integration_test/bots/settings/wallet_settings/change_pin_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(ChangePinView)); - } - - Future 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 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 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 tapBack() async { - await tester.tap(find.byType(AppBarIconButton)); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/settings/wallet_settings/delete_wallet_warning_view_bot.dart b/integration_test/bots/settings/wallet_settings/delete_wallet_warning_view_bot.dart deleted file mode 100644 index 319212954f..0000000000 --- a/integration_test/bots/settings/wallet_settings/delete_wallet_warning_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(DeleteWalletWarningView)); - } - - Future tapBack() async { - await tester.tap(find.byKey(Key("settingsAppBarBackButton"))); - await tester.pumpAndSettle(); - } - - Future tapCancelAndGoBack() async { - await tester.tap(find.byType(SimpleButton)); - await tester.pumpAndSettle(); - } - - Future tapViewBackupKey() async { - await tester.tap(find.byType(GradientButton)); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/settings/wallet_settings/rename_wallet_view_bot.dart b/integration_test/bots/settings/wallet_settings/rename_wallet_view_bot.dart deleted file mode 100644 index b1f45cb6ac..0000000000 --- a/integration_test/bots/settings/wallet_settings/rename_wallet_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(RenameWalletView)); - } - - Future tapBack() async { - await tester.tap(find.byKey(Key("settingsAppBarBackButton"))); - await tester.pumpAndSettle(); - } - - Future tapSave() async { - await tester.tap(find.byType(GradientButton)); - await tester.pumpAndSettle(); - } - - Future enterWalletName(String name) async { - await tester.enterText(find.byType(TextField), name); - await tester.pump(); - } -} diff --git a/integration_test/bots/settings/wallet_settings/wallet_delete_mnemonic_view_bot.dart b/integration_test/bots/settings/wallet_settings/wallet_delete_mnemonic_view_bot.dart deleted file mode 100644 index b7394fc994..0000000000 --- a/integration_test/bots/settings/wallet_settings/wallet_delete_mnemonic_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(WalletDeleteMnemonicView)); - } - - Future tapBack() async { - await tester.tap(find.byKey(Key("settingsAppBarBackButton"))); - await tester.pumpAndSettle(); - } - - Future tapQrCode() async { - await tester.tap(find.byKey(Key("walletDeleteShowQrCodeButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapCancelQrCode() async { - await tester.tap(find.byKey(Key("deleteWalletQrCodePopupCancelButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapCopy() async { - await tester.tap(find.byKey(Key("walletDeleteCopySeedButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapContinue() async { - await tester.tap(find.byType(GradientButton)); - await tester.pumpAndSettle(); - } - - Future tapConfirmContinue() async { - await tester.tap(find.byKey(Key("walletDeleteContinueDeleteButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapCancelContinue() async { - await tester.tap(find.byKey(Key("walletDeleteContinueCancelButtonKey"))); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/settings/wallet_settings_view_bot.dart b/integration_test/bots/settings/wallet_settings_view_bot.dart deleted file mode 100644 index 8bd12e1ad9..0000000000 --- a/integration_test/bots/settings/wallet_settings_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(WalletSettingsView)); - } - - Future tapBack() async { - await tester.tap(find.byKey(Key("settingsAppBarBackButton"))); - await tester.pumpAndSettle(); - } - - Future tapChangePIN() async { - await tester.tap(find.byKey(Key("walletSettingsChangePinButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapToggleBiometrics() async { - await tester - .tap(find.byKey(Key("walletSettingsEnableBiometricsButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapCancelBiometricsSystemSettingsDialog() async { - await tester.tap(find.byType(SimpleButton)); - await tester.pumpAndSettle(); - } - - Future tapRenameWallet() async { - await tester.tap(find.byKey(Key("walletSettingsRenameWalletButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapDeleteWallet() async { - await tester.tap(find.byKey(Key("walletSettingsDeleteWalletButtonKey"))); - await tester.pumpAndSettle(); - } - - Future tapFullRescan() async { - await tester.tap(find.byKey(Key("walletSettingsFullRescanButtonKey"))); - await tester.pumpAndSettle(); - } - - Future 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 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 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 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 tapClearCache() async { - await tester - .tap(find.byKey(Key("walletSettingsClearSharedCacheButtonKey"))); - await tester.pumpAndSettle(); - } - - Future 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 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 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); - } -} diff --git a/integration_test/bots/transaction/transaction_search_view_bot.dart b/integration_test/bots/transaction/transaction_search_view_bot.dart deleted file mode 100644 index 36927943c9..0000000000 --- a/integration_test/bots/transaction/transaction_search_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(TransactionSearchView)); - } - - Future tapX() async { - await tester.tap(find.byKey(Key("cancelTransactionSearchAppBarButton"))); - await tester.pumpAndSettle(); - } - - Future tapCancel() async { - await tester.tap(find.byType(SimpleButton)); - await tester.pumpAndSettle(); - } -} diff --git a/integration_test/bots/wallet_view_bot.dart b/integration_test/bots/wallet_view_bot.dart deleted file mode 100644 index 5dfa67b57d..0000000000 --- a/integration_test/bots/wallet_view_bot.dart +++ /dev/null @@ -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 ensureVisible() async { - await tester.ensureVisible(find.byType(WalletView)); - } - - Future tapAvailableFullSwitch() async { - await tester.tap(find.byType(DraggableSwitchButton)); - await tester.pumpAndSettle(); - } - - Future dragAvailableFullSwitchRight() async { - await tester.fling( - find.byKey(Key("draggableSwitchButtonSwitch")), Offset(200, 0), 500); - await tester.pumpAndSettle(); - } - - Future dragAvailableFullSwitchLeft() async { - await tester.fling( - find.byKey(Key("draggableSwitchButtonSwitch")), Offset(-200, 0), 500); - await tester.pumpAndSettle(); - } - - Future tapTransactionSearch() async { - await tester.tap(find.byKey(Key("walletViewTransactionSearchButton"))); - await tester.pumpAndSettle(); - } - - Future checkAvailableFullSwitchIsEnabled() async { - final state = tester.state(find.byType(DraggableSwitchButton)) - as DraggableSwitchButtonState; - expect(state.enabled, true); - } - - Future checkAvailableFullSwitchIsDisabled() async { - final state = tester.state(find.byType(DraggableSwitchButton)) - as DraggableSwitchButtonState; - expect(state.enabled, false); - } -} diff --git a/integration_test/create_new_wallet_test.dart b/integration_test/create_new_wallet_test.dart deleted file mode 100644 index 7272d06608..0000000000 --- a/integration_test/create_new_wallet_test.dart +++ /dev/null @@ -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); - }); - }); -} diff --git a/integration_test/desktop_forgot_password/harness.dart b/integration_test/desktop_forgot_password/harness.dart new file mode 100644 index 0000000000..32c5d966b8 --- /dev/null +++ b/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 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 files, List openDatabases}); + +void desktopTest(String description, Future 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 = []; + + /// Set once the app calls exit(). + AppExit? get exited => _exit; + + /// Seeds the data folder, then starts the app. + Future 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 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 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 files) { + for (final file in files) { + expect(File(path(file)).readAsStringSync(), file, reason: file); + } + } + + /// Checks the password still unlocks the seeded wallet key. + Future 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 tapText(String text) => tap(find.text(text, findRichText: true)); + + Future 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 waitFor(Finder finder) => _waitUntil( + () => finder.hitTestable().evaluate().isNotEmpty, + 'Timed out waiting for $finder', + ); + + Future waitForExit() async { + await _waitUntil(() => _exit != null, 'The app did not quit'); + return _exit!; + } + + Future _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 _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 delete({bool recursive = false}) async => + throw FileSystemException('Blocked by the test', path); +} + +class _TestPaths extends PathProviderPlatform { + _TestPaths(this.temp); + + final String temp; + + Future _directory(String name) async => + (await Directory(p.join(temp, name)).create(recursive: true)).path; + + @override + Future getTemporaryPath() => _directory('temp'); + + @override + Future getApplicationSupportPath() => _directory('support'); + + @override + Future getLibraryPath() => _directory('library'); + + @override + Future getApplicationDocumentsPath() => _directory('documents'); + + @override + Future getApplicationCachePath() => _directory('cache'); + + @override + Future getDownloadsPath() => _directory('downloads'); +} diff --git a/integration_test/desktop_forgot_password/reset_cancelled_test.dart b/integration_test/desktop_forgot_password/reset_cancelled_test.dart new file mode 100644 index 0000000000..8fff7dc443 --- /dev/null +++ b/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(); + }); +} diff --git a/integration_test/desktop_forgot_password/reset_fails_test.dart b/integration_test/desktop_forgot_password/reset_fails_test.dart new file mode 100644 index 0000000000..7b8f51ac6b --- /dev/null +++ b/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); + }); +} diff --git a/integration_test/desktop_forgot_password/reset_succeeds_test.dart b/integration_test/desktop_forgot_password/reset_succeeds_test.dart new file mode 100644 index 0000000000..3f3840af85 --- /dev/null +++ b/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(); + }); +} diff --git a/integration_test/desktop_forgot_password/unfinished_reset_quit_test.dart b/integration_test/desktop_forgot_password/unfinished_reset_quit_test.dart new file mode 100644 index 0000000000..e9b4e52a67 --- /dev/null +++ b/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(); + }); +} diff --git a/integration_test/desktop_forgot_password/unfinished_reset_retry_test.dart b/integration_test/desktop_forgot_password/unfinished_reset_retry_test.dart new file mode 100644 index 0000000000..9fbb871b55 --- /dev/null +++ b/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(); + }); +} diff --git a/integration_test/main_wallet_views_test.dart b/integration_test/main_wallet_views_test.dart deleted file mode 100644 index 6a788c092c..0000000000 --- a/integration_test/main_wallet_views_test.dart +++ /dev/null @@ -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(); - }); -} diff --git a/integration_test/rescan_test.dart b/integration_test/rescan_test.dart deleted file mode 100644 index dfd858346d..0000000000 --- a/integration_test/rescan_test.dart +++ /dev/null @@ -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); - }); -} diff --git a/integration_test/restore_wallet_test.dart b/integration_test/restore_wallet_test.dart deleted file mode 100644 index 6a41b05301..0000000000 --- a/integration_test/restore_wallet_test.dart +++ /dev/null @@ -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); - }); -} diff --git a/integration_test/settings_test.dart b/integration_test/settings_test.dart deleted file mode 100644 index 5672d280fc..0000000000 --- a/integration_test/settings_test.dart +++ /dev/null @@ -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(); - } - }); -} diff --git a/lib/db/sqlite/firo_cache_coordinator.dart b/lib/db/sqlite/firo_cache_coordinator.dart index 5ecd7534b9..9a474fce32 100644 --- a/lib/db/sqlite/firo_cache_coordinator.dart +++ b/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 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 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( diff --git a/lib/db/sqlite/firo_cache_worker.dart b/lib/db/sqlite/firo_cache_worker.dart index abaceb288b..ecf60e1c23 100644 --- a/lib/db/sqlite/firo_cache_worker.dart +++ b/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> _activeRequests = {}; + bool _closing = false; + + Future 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 runTask(FCTask task) async { + if (_closing) throw StateError('Firo cache worker is closing'); final completer = Completer.sync(); _activeRequests[task.id] = completer; _commands.send(task); @@ -37,23 +58,19 @@ 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; @@ -61,10 +78,10 @@ class _FiroCacheWorker { 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); @@ -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, diff --git a/lib/main.dart b/lib/main.dart index 9ed09c185f..c44484cb26 100644 --- a/lib/main.dart +++ b/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 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(DB.boxNameDBInfo); await DB.instance.hive.openBox(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( + 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 } } - 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 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(const Size(46, 46)), - // textStyle: MaterialStateProperty.all( - // STextStyles.button(context)), - foregroundColor: MaterialStateProperty.all( - colorScheme.buttonTextSecondary, - ), - backgroundColor: MaterialStateProperty.all( - colorScheme.buttonBackSecondary, - ), - shape: MaterialStateProperty.all( - 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( diff --git a/lib/pages/already_running_view.dart b/lib/pages/already_running_view.dart index 1678276e55..e14a7f946b 100644 --- a/lib/pages/already_running_view.dart +++ b/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 createState() => _AlreadyRunningAppState(); -} - -class _AlreadyRunningAppState extends ConsumerState { - @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), ], diff --git a/lib/pages_desktop_specific/password/delete_password_warning_view.dart b/lib/pages_desktop_specific/password/delete_password_warning_view.dart index 984784fe35..0f9ff5e555 100644 --- a/lib/pages_desktop_specific/password/delete_password_warning_view.dart +++ b/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 { bool _deleteInProgress = false; - Future _deleteStack() async { - final appRoot = await StackFileSystem.applicationRootDirectory(); - + Future _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.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.delayed(const Duration(seconds: 3)); + exit(0); } @override @@ -145,10 +104,9 @@ 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( @@ -156,12 +114,18 @@ class _ForgotPasswordDesktopViewState 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), diff --git a/lib/themes/stack_theme_data.dart b/lib/themes/stack_theme_data.dart new file mode 100644 index 0000000000..9b64154116 --- /dev/null +++ b/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(const Size(46, 46)), + foregroundColor: MaterialStateProperty.all(colors.buttonTextSecondary), + backgroundColor: MaterialStateProperty.all( + colors.buttonBackSecondary, + ), + shape: MaterialStateProperty.all( + 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(), + ), + ); +} diff --git a/lib/utilities/desktop_startup_and_reset.dart b/lib/utilities/desktop_startup_and_reset.dart new file mode 100644 index 0000000000..41db48fb37 --- /dev/null +++ b/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 = [ + '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> _bundledAppearances = {}; + static StackTheme? _displayTheme; + + /// Run before opening Hive, Isar, or caches on desktop. + static Future 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 _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 _marker() async => File( + path.join( + (await StackFileSystem.applicationRootDirectory()).path, + '.reset-pending', + ), + ); + + /// Never infer from password files that an unfinished reset was cancelled. + static Future isPending() async { + return (await _marker()).exists(); + } + + static Future 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 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 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 _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 _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 askToFinishReset() async { + final done = Completer(); + 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 showMessage(String message, {StackTheme? theme}) => + _show(title: AppConfig.appName, message: message, theme: theme); + + static Future 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 _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.from( + jsonDecode(utf8.decode(themeJson.content as List)) 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), + 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((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()!; + 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), + ], + ), + ), + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 750c80fcd7..96f2e138da 100644 --- a/pubspec.lock +++ b/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" diff --git a/scripts/app_config/templates/pubspec.template.yaml b/scripts/app_config/templates/pubspec.template.yaml index 12a7efaa39..b9b8065d5c 100644 --- a/scripts/app_config/templates/pubspec.template.yaml +++ b/scripts/app_config/templates/pubspec.template.yaml @@ -282,6 +282,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 diff --git a/scripts/test_desktop_forgot_password.sh b/scripts/test_desktop_forgot_password.sh new file mode 100755 index 0000000000..46033e73e1 --- /dev/null +++ b/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"