From 26dd0ea988f2e1b65d1860c2f9d846a5e54acbc6 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Wed, 15 Feb 2023 18:18:18 -0300 Subject: [PATCH] Add crash reporter to secant (#531) * [#525] Adds functions to configure, testCrash and check if it can start. This adds a build phase where a dummy file is added to the project to make the build and Plist copy happy. When building in the CI there will be a script to replace this Plist file with the real one that then will be copied to the bundle Crashlytics will be "off" by default and then be turned on when starting up to be an Opt-Out thing. This is the only way it can be turned off later. reference: https://firebase.google.com/docs/crashlytics/customize-crash-reports?platform=ios#enable_opt-in_reporting The app will start with crash reporting turned off and will set it up on by default on the application's code. Then if the user wants to opt-out of crash reporting, it can. Otherwise, it won't be possible. Adds opting out of crash reporting as a stored user preference. This adds a value inside UserPreferencesStorage and its live and mock counterparts. also creates a builer for `CrashReporterClient` that has a Dependency to `@Dependency(\.userStoredPreferences)` and sets the references for the client to set the appropriate values into the user storage `UserPreferencesStorage` as been adapted to be a TCA Dependency. `SettingsStore` now as a `Toogle()` to turn off and on crash reporting. But it doesn't work yet because I haven't found out how to make a TCA Binding that can rely on an initial value that is not hardcoded but injected from somewhere else. See https://www.pointfree.co/episodes/ep158-safer-conciser-forms-part-1 https://www.pointfree.co/episodes/ep158-safer-conciser-forms-part-2 Adds Test Crash button and enable crash reporting Adds upload-symbols run script phase Closes #525 Add a custom build environment variable "UPLOAD_CRASHLYTICS_SYMBOLS" that will let Xcode skip the upload_symbols script for debug builds Fix Initialization tests * bump build --- .gitignore | 5 +- secant.xcodeproj/project.pbxproj | 91 ++++++++++++++++- .../xcshareddata/swiftpm/Package.resolved | 99 +++++++++++++++++++ .../CrashReporter/CrashReporterLiveKey.swift | 41 ++++++++ .../CrashReporter/CrashReporterTestKey.swift | 16 +++ .../CrashReportingInterface.swift | 31 ++++++ .../UserPreferencesStorage.swift | 13 +++ .../UserPreferencesStorageInterface.swift | 38 +++++++ .../UserPreferencesStorageLive.swift | 29 ++++++ .../UserPreferencesStorageMocks.swift | 28 ++++++ secant/Features/Root/RootInitialization.swift | 18 +++- secant/Features/Root/RootStore.swift | 4 +- secant/Features/Settings/SettingsStore.swift | 37 ++++++- secant/Features/Settings/SettingsView.swift | 17 +++- secant/Info.plist | 9 +- .../RootTests/AppInitializationTests.swift | 23 +++-- secantTests/SettingsTests/SettingsTests.swift | 20 ++-- .../UserPreferencesStorageTests.swift | 6 ++ 18 files changed, 487 insertions(+), 38 deletions(-) create mode 100644 secant/Dependencies/CrashReporter/CrashReporterLiveKey.swift create mode 100644 secant/Dependencies/CrashReporter/CrashReporterTestKey.swift create mode 100644 secant/Dependencies/CrashReporter/CrashReportingInterface.swift create mode 100644 secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageInterface.swift diff --git a/.gitignore b/.gitignore index 15d22de..f6b09aa 100644 --- a/.gitignore +++ b/.gitignore @@ -71,9 +71,10 @@ iOSInjectionProject/ #ignore Pods directory for example project Pods +# do not commit Google Firebase config PLIST file +GoogleService-info.plist + # do not commit generated libraries to this repo lib *.a *.generated.swift -env-vars.sh -wallet/wallet/Generated/Constants.generated.swift diff --git a/secant.xcodeproj/project.pbxproj b/secant.xcodeproj/project.pbxproj index 72a4a9e..4f77db6 100644 --- a/secant.xcodeproj/project.pbxproj +++ b/secant.xcodeproj/project.pbxproj @@ -12,8 +12,13 @@ 0D185819272723FF0046B928 /* ColoredChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D185818272723FF0046B928 /* ColoredChip.swift */; }; 0D18581B272728D60046B928 /* PhraseChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D18581A272728D60046B928 /* PhraseChip.swift */; }; 0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */; }; + 0D26103A298C3DCD00CC9DE9 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = 0D261039298C3DCD00CC9DE9 /* FirebaseCrashlytics */; }; + 0D26103C298C3E4800CC9DE9 /* CrashReportingInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D26103B298C3E4800CC9DE9 /* CrashReportingInterface.swift */; }; + 0D26103E298C3FA600CC9DE9 /* CrashReporterLiveKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D26103D298C3FA600CC9DE9 /* CrashReporterLiveKey.swift */; }; + 0D261040298C406F00CC9DE9 /* CrashReporterTestKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D26103F298C406F00CC9DE9 /* CrashReporterTestKey.swift */; }; 0D2ACE8026C2C67100D62E3C /* Zboto.otf in Resources */ = {isa = PBXBuildFile; fileRef = 0D2ACE7F26C2C67100D62E3C /* Zboto.otf */; }; 0D35CC46277A36E00074316A /* ScrollableWhenScaled.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D35CC45277A36E00074316A /* ScrollableWhenScaled.swift */; }; + 0D3B01EC298DAF89007EBCDA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0D3B01EB298DAF89007EBCDA /* GoogleService-Info.plist */; }; 0D3D04082728B3440032ABC1 /* RecoveryPhraseDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D3D04072728B3440032ABC1 /* RecoveryPhraseDisplayView.swift */; }; 0D3D040A2728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D3D04092728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift */; }; 0D4E7A0926B364170058B01E /* SecantApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D4E7A0826B364170058B01E /* SecantApp.swift */; }; @@ -24,6 +29,7 @@ 0D535FDF271F4214009A9E3E /* Rubik-VariableFont_wght.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0D535FDD271F4214009A9E3E /* Rubik-VariableFont_wght.ttf */; }; 0D535FE2271F9476009A9E3E /* EnumeratedChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D535FE1271F9476009A9E3E /* EnumeratedChip.swift */; }; 0D5D9B8F2914620700DBD03F /* URLRouting in Frameworks */ = {isa = PBXBuildFile; productRef = 0D5D9B8E2914620700DBD03F /* URLRouting */; }; + 0D63170029919970007D873F /* UserPreferencesStorageInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D6316FF29919970007D873F /* UserPreferencesStorageInterface.swift */; }; 0D6D628B276A528E002FB4CC /* DropDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D6D628A276A528D002FB4CC /* DropDelegate.swift */; }; 0D7CE63427349B5D0020E050 /* View+WhenDraggable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7CE63327349B5D0020E050 /* View+WhenDraggable.swift */; }; 0D7DF08C271DCC0E00530046 /* ScreenBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7DF08B271DCC0E00530046 /* ScreenBackground.swift */; }; @@ -315,8 +321,12 @@ 0D185818272723FF0046B928 /* ColoredChip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColoredChip.swift; sourceTree = ""; }; 0D18581A272728D60046B928 /* PhraseChip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhraseChip.swift; sourceTree = ""; }; 0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplayReducerTests.swift; sourceTree = ""; }; + 0D26103B298C3E4800CC9DE9 /* CrashReportingInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportingInterface.swift; sourceTree = ""; }; + 0D26103D298C3FA600CC9DE9 /* CrashReporterLiveKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReporterLiveKey.swift; sourceTree = ""; }; + 0D26103F298C406F00CC9DE9 /* CrashReporterTestKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReporterTestKey.swift; sourceTree = ""; }; 0D2ACE7F26C2C67100D62E3C /* Zboto.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Zboto.otf; sourceTree = ""; }; 0D35CC45277A36E00074316A /* ScrollableWhenScaled.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableWhenScaled.swift; sourceTree = ""; }; + 0D3B01EB298DAF89007EBCDA /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 0D3D04072728B3440032ABC1 /* RecoveryPhraseDisplayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplayView.swift; sourceTree = ""; }; 0D3D04092728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplayStore.swift; sourceTree = ""; }; 0D4E7A0526B364170058B01E /* secant-testnet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "secant-testnet.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -333,6 +343,7 @@ 0D535FDC271F4214009A9E3E /* Rubik-Italic-VariableFont_wght.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Rubik-Italic-VariableFont_wght.ttf"; sourceTree = ""; }; 0D535FDD271F4214009A9E3E /* Rubik-VariableFont_wght.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Rubik-VariableFont_wght.ttf"; sourceTree = ""; }; 0D535FE1271F9476009A9E3E /* EnumeratedChip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnumeratedChip.swift; sourceTree = ""; }; + 0D6316FF29919970007D873F /* UserPreferencesStorageInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorageInterface.swift; sourceTree = ""; }; 0D6D628A276A528D002FB4CC /* DropDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDelegate.swift; sourceTree = ""; }; 0D7CE63327349B5D0020E050 /* View+WhenDraggable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+WhenDraggable.swift"; sourceTree = ""; }; 0D7DF08B271DCC0E00530046 /* ScreenBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenBackground.swift; sourceTree = ""; }; @@ -603,6 +614,7 @@ 9E6612312878337F00C75B70 /* Lottie in Frameworks */, 0D5D9B8F2914620700DBD03F /* URLRouting in Frameworks */, 0DB4E0B42881FD9100947B78 /* ZcashLightClientKit in Frameworks */, + 0D26103A298C3DCD00CC9DE9 /* FirebaseCrashlytics in Frameworks */, 9E2AC0FF27D8EC120042AA47 /* MnemonicSwift in Frameworks */, 6654C73A2715A38000901167 /* ComposableArchitecture in Frameworks */, 9EAB466D285A0468002904A0 /* Parsing in Frameworks */, @@ -766,6 +778,16 @@ path = Chips; sourceTree = ""; }; + 0D767873298C374F0047E085 /* CrashReporter */ = { + isa = PBXGroup; + children = ( + 0D26103B298C3E4800CC9DE9 /* CrashReportingInterface.swift */, + 0D26103D298C3FA600CC9DE9 /* CrashReporterLiveKey.swift */, + 0D26103F298C406F00CC9DE9 /* CrashReporterTestKey.swift */, + ); + path = CrashReporter; + sourceTree = ""; + }; 0D8A43C2272AEEA7005A6414 /* FontStyles */ = { isa = PBXGroup; children = ( @@ -1267,6 +1289,7 @@ children = ( 9E6612342878341F00C75B70 /* Lotties */, 0D4E7A0C26B364180058B01E /* Assets.xcassets */, + 0D3B01EB298DAF89007EBCDA /* GoogleService-Info.plist */, 660558E8270C7A54009D6954 /* Colors.xcassets */, 9E37A2B727C8F59F00AE57B3 /* Localizable.strings */, 0D2ACE7E26C2C65E00D62E3C /* Fonts */, @@ -1315,6 +1338,7 @@ 9EBDF978291F7E85000A1A05 /* AppVersion */, 9EBDF962291ECD42000A1A05 /* AudioServices */, 9EBDF969291ECEAC000A1A05 /* CaptureDevice */, + 0D767873298C374F0047E085 /* CrashReporter */, 9EBDF94E291E5E5F000A1A05 /* DatabaseFiles */, 9EBDF959291E654F000A1A05 /* Deeplink */, 9EBDF971291F79C9000A1A05 /* DerivationTool */, @@ -1614,6 +1638,7 @@ 9E3911442848EEB90073DD9A /* UserPreferencesStorage.swift */, 9EB863C62923C93B003D0F8B /* UserPreferencesStorageLive.swift */, 9EB863C82923C953003D0F8B /* UserPreferencesStorageMocks.swift */, + 0D6316FF29919970007D873F /* UserPreferencesStorageInterface.swift */, ); path = UserPreferencesStorage; sourceTree = ""; @@ -1834,11 +1859,13 @@ isa = PBXNativeTarget; buildConfigurationList = 0D4E7A2A26B364180058B01E /* Build configuration list for PBXNativeTarget "secant-testnet" */; buildPhases = ( + 0D3B01ED298DB0FE007EBCDA /* ShellScript */, 664E39ED270C693C0044AD7E /* SwiftGen */, 6696BA8726F0B1D200D5C875 /* SwiftLint */, 0D4E7A0126B364170058B01E /* Sources */, 0D4E7A0226B364170058B01E /* Frameworks */, 0D4E7A0326B364170058B01E /* Resources */, + 0D300FA72996EAF200576003 /* ShellScript */, ); buildRules = ( ); @@ -1852,6 +1879,7 @@ 9E6612302878337F00C75B70 /* Lottie */, 0DB4E0B32881FD9100947B78 /* ZcashLightClientKit */, 0D5D9B8E2914620700DBD03F /* URLRouting */, + 0D261039298C3DCD00CC9DE9 /* FirebaseCrashlytics */, ); productName = secant; productReference = 0D4E7A0526B364170058B01E /* secant-testnet.app */; @@ -1933,6 +1961,7 @@ 9E66122F2878337F00C75B70 /* XCRemoteSwiftPackageReference "lottie-ios" */, 0DB4E0B22881FD9100947B78 /* XCRemoteSwiftPackageReference "ZcashLightClientKit" */, 0D5D9B8D2914620700DBD03F /* XCRemoteSwiftPackageReference "swift-url-routing" */, + 0D261038298C3DCD00CC9DE9 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, ); productRefGroup = 0D4E7A0626B364170058B01E /* Products */; projectDirPath = ""; @@ -1969,6 +1998,7 @@ 0D4E7A0D26B364180058B01E /* Assets.xcassets in Resources */, 0DACFA9727209FA70039EEA5 /* Roboto-Black.ttf in Resources */, 0DACFA9C27209FA70039EEA5 /* Roboto-ThinItalic.ttf in Resources */, + 0D3B01EC298DAF89007EBCDA /* GoogleService-Info.plist in Resources */, 9E37A2B827C8F59F00AE57B3 /* Localizable.strings in Resources */, 0DACFA9627209FA70039EEA5 /* Roboto-Thin.ttf in Resources */, 0D2ACE8026C2C67100D62E3C /* Zboto.otf in Resources */, @@ -1992,6 +2022,43 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 0D300FA72996EAF200576003 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 12; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif [[ $UPLOAD_CRASHLYTICS_SYMBOLS = \"NO\" ]]; then\n echo \"DEBUG BUILD: SKIPPING UPLOAD SYMBOLS STEP\"\nelse\n${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\nfi\n"; + }; + 0D3B01ED298DB0FE007EBCDA /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Resources/GoogleService-Info.plist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/zsh; + shellScript = "# this creates an empty file for the firebase SDK\nCRASH_REPORTER_FILE=\"secant/Resources/GoogleService-Info.plist\"\nif [[ -f $CRASH_REPORTER_FILE ]]; then\n echo \"$CRASH_REPORTER_FILE Exists. Not doing anything.\"\nelse \n echo \"$CRASH_REPORTER_FILE does not exist. Will insert a DUMMY FILE\"\n\n echo \"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KICAgIDxrZXk+SVNfRFVNTVlfRklMRTwva2V5PgogICAgPHRydWU+PC90cnVlPgo8L2RpY3Q+CjwvcGxpc3Q+Cg==\" | base64 --decode > $CRASH_REPORTER_FILE\nfi\n"; + }; 664E39ED270C693C0044AD7E /* SwiftGen */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -2042,6 +2109,7 @@ files = ( 2EB660E02747EAB900A06A07 /* OnboardingFlowView.swift in Sources */, 9E7FE0DF282D2DD600C374E8 /* ZcashBadge.swift in Sources */, + 0D261040298C406F00CC9DE9 /* CrashReporterTestKey.swift in Sources */, 9EBDF975291F79F9000A1A05 /* DerivationToolInterface.swift in Sources */, 660558F8270C862F009D6954 /* XCAssets+Generated.swift in Sources */, 9EAFEB902808183D00199FC9 /* SandboxStore.swift in Sources */, @@ -2054,6 +2122,7 @@ 9EBDF96E291ECED4000A1A05 /* CaptureDeviceLiveKey.swift in Sources */, 9EBDF968291ECDA2000A1A05 /* AudioServicesInterface.swift in Sources */, 9EB863BD2923C704003D0F8B /* NotificationCenterTest.swift in Sources */, + 0D26103E298C3FA600CC9DE9 /* CrashReporterLiveKey.swift in Sources */, 9EB863A829239DCB003D0F8B /* RecoveryPhraseRandomizerLiveKey.swift in Sources */, 2EDA07A027EDE18C00D6F09B /* TCATextField.swift in Sources */, 9EBDF961291E657B000A1A05 /* DeeplinkInterface.swift in Sources */, @@ -2097,6 +2166,7 @@ 9E66122C2877188700C75B70 /* SyncStatusSnapshot.swift in Sources */, 9E4DC6E227C4C6B700E657F4 /* SecantButtonStyles.swift in Sources */, 0DDB6A5127737D4A0012A410 /* RecoveryPhraseBackupFailedView.swift in Sources */, + 0D63170029919970007D873F /* UserPreferencesStorageInterface.swift in Sources */, 9EBDF94D291D773A000A1A05 /* DiskSpaceCheckerMocks.swift in Sources */, 0D6D628B276A528E002FB4CC /* DropDelegate.swift in Sources */, 9EBDF986291F91EF000A1A05 /* LocalAuthenticationLiveKey.swift in Sources */, @@ -2222,6 +2292,7 @@ 9EB863D02923D3FC003D0F8B /* SDKSynchronizerMocks.swift in Sources */, 9E612C7629880FC900D09B09 /* LogsHandlerTest.swift in Sources */, 2EDA07A227EDE1AE00D6F09B /* TextFieldFooter.swift in Sources */, + 0D26103C298C3E4800CC9DE9 /* CrashReportingInterface.swift in Sources */, F9971A5427680DD000A2DB75 /* ProfileView.swift in Sources */, F9971A6027680DF600A2DB75 /* ScanStore.swift in Sources */, 9EB863952922D036003D0F8B /* NumberFormatterTestKey.swift in Sources */, @@ -2452,7 +2523,8 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 37; + CURRENT_PROJECT_VERSION = 39; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\""; DEVELOPMENT_TEAM = RLPRR8CPQG; ENABLE_BITCODE = NO; @@ -2469,6 +2541,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG UNREDACTED"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; + UPLOAD_CRASHLYTICS_SYMBOLS = NO; }; name = Debug; }; @@ -2478,7 +2551,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 37; + CURRENT_PROJECT_VERSION = 39; DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\""; DEVELOPMENT_TEAM = RLPRR8CPQG; ENABLE_BITCODE = NO; @@ -2494,6 +2567,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; + UPLOAD_CRASHLYTICS_SYMBOLS = YES; }; name = Release; }; @@ -2626,6 +2700,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 0D261038298C3DCD00CC9DE9 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; + requirement = { + kind = upToNextMinorVersion; + minimumVersion = 9.6.0; + }; + }; 0D5D9B8D2914620700DBD03F /* XCRemoteSwiftPackageReference "swift-url-routing" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/pointfreeco/swift-url-routing"; @@ -2677,6 +2759,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 0D261039298C3DCD00CC9DE9 /* FirebaseCrashlytics */ = { + isa = XCSwiftPackageProductDependency; + package = 0D261038298C3DCD00CC9DE9 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseCrashlytics; + }; 0D5D9B8E2914620700DBD03F /* URLRouting */ = { isa = XCSwiftPackageProductDependency; package = 0D5D9B8D2914620700DBD03F /* XCRemoteSwiftPackageReference "swift-url-routing" */; diff --git a/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3ae7882..8f25fd0 100644 --- a/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,23 @@ { "pins" : [ + { + "identity" : "abseil-cpp-swiftpm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git", + "state" : { + "revision" : "583de9bd60f66b40e78d08599cc92036c2e7e4e1", + "version" : "0.20220203.2" + } + }, + { + "identity" : "boringssl-swiftpm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/boringssl-SwiftPM.git", + "state" : { + "revision" : "dd3eda2b05a3f459fc3073695ad1b28659066eab", + "version" : "0.9.1" + } + }, { "identity" : "combine-schedulers", "kind" : "remoteSourceControl", @@ -9,6 +27,51 @@ "version" : "0.9.1" } }, + { + "identity" : "firebase-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/firebase-ios-sdk.git", + "state" : { + "revision" : "7e80c25b51c2ffa238879b07fbfc5baa54bb3050", + "version" : "9.6.0" + } + }, + { + "identity" : "googleappmeasurement", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleAppMeasurement.git", + "state" : { + "revision" : "c1cfde8067668027b23a42c29d11c246152fe046", + "version" : "9.6.0" + } + }, + { + "identity" : "googledatatransport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleDataTransport.git", + "state" : { + "revision" : "f6b558e3f801f2cac336b04f615ce111fa9ddaa0", + "version" : "9.2.1" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "0543562f85620b5b7c510c6bcbef75b562a5127b", + "version" : "7.11.0" + } + }, + { + "identity" : "grpc-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/grpc/grpc-ios.git", + "state" : { + "revision" : "8440b914756e0d26d4f4d054a1c1581daedfc5b6", + "version" : "1.44.3-grpc" + } + }, { "identity" : "grpc-swift", "kind" : "remoteSourceControl", @@ -18,6 +81,24 @@ "version" : "1.14.0" } }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "5ccda3981422a84186387dbb763ba739178b529c", + "version" : "2.3.0" + } + }, + { + "identity" : "leveldb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/leveldb.git", + "state" : { + "revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b", + "version" : "1.22.2" + } + }, { "identity" : "lottie-ios", "kind" : "remoteSourceControl", @@ -36,6 +117,24 @@ "version" : "2.2.4" } }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "819d0a2173aff699fb8c364b6fb906f7cdb1a692", + "version" : "2.30909.0" + } + }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "3e4e743631e86c8c70dbc6efdc7beaa6e90fd3bb", + "version" : "2.1.1" + } + }, { "identity" : "sqlite.swift", "kind" : "remoteSourceControl", diff --git a/secant/Dependencies/CrashReporter/CrashReporterLiveKey.swift b/secant/Dependencies/CrashReporter/CrashReporterLiveKey.swift new file mode 100644 index 0000000..ccde58d --- /dev/null +++ b/secant/Dependencies/CrashReporter/CrashReporterLiveKey.swift @@ -0,0 +1,41 @@ +// +// CrashReporterLiveKey.swift +// secant-testnet +// +// Created by Francisco Gindre on 2/2/23. +// +import ComposableArchitecture +import FirebaseCore +import FirebaseCrashlytics +extension CrashReporterClient: DependencyKey { + static let liveValue: CrashReporterClient = CrashReporterClient( + configure: { canConfigure in + let fileName = "GoogleService-Info.plist" + + // checks whether the crash reporter's config file is a dummy_file purposedly placed by the build job or the real one. + // this does not check the integrity of the Plist file for Firebase. + // that's a problem for the library itself. + guard + let configFile = Bundle.main.url(forResource: fileName, withExtension: nil), + let properties = NSDictionary(contentsOf: configFile), + properties["IS_DUMMY_FILE"] == nil, + canConfigure + else { + return + } + + FirebaseApp.configure() + Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true) + }, + testCrash: { + fatalError("Crash was triggered to test the crash reporter") + }, + optIn: { + Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true) + }, + optOut: { + Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(false) + + } + ) +} diff --git a/secant/Dependencies/CrashReporter/CrashReporterTestKey.swift b/secant/Dependencies/CrashReporter/CrashReporterTestKey.swift new file mode 100644 index 0000000..295b757 --- /dev/null +++ b/secant/Dependencies/CrashReporter/CrashReporterTestKey.swift @@ -0,0 +1,16 @@ +// +// CrashReporterTestKey.swift +// secant-testnet +// +// Created by Francisco Gindre on 2/2/23. +// + +import ComposableArchitecture +extension CrashReporterClient: TestDependencyKey { + static let testValue: CrashReporterClient = CrashReporterClient( + configure: { _ in }, + testCrash: {}, + optIn: {}, + optOut: {} + ) +} diff --git a/secant/Dependencies/CrashReporter/CrashReportingInterface.swift b/secant/Dependencies/CrashReporter/CrashReportingInterface.swift new file mode 100644 index 0000000..9d85c54 --- /dev/null +++ b/secant/Dependencies/CrashReporter/CrashReportingInterface.swift @@ -0,0 +1,31 @@ +// +// CrashReportingInterface.swift +// secant-testnet +// +// Created by Francisco Gindre on 2/2/23. +// +import ComposableArchitecture +import Foundation + +extension DependencyValues { + var crashReporter: CrashReporterClient { + get { self[CrashReporterClient.self] } + set { self[CrashReporterClient.self] = newValue } + } +} + +struct CrashReporterClient { + /// Configures the crash reporter if possible. + /// if it can't be configured this will fail silently + var configure: (Bool) -> Void + + /// this will test the crash reporter + /// - Note: depending of the crash reporter this may or may not crash your app. + var testCrash: () -> Void + + /// this will tell the crash reporter that the user a has decided to opt-in crash reporting + var optIn: () -> Void + + /// this will tell the crash reporter that the user has decided to opt-out of crash reporting + var optOut: () -> Void +} diff --git a/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorage.swift b/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorage.swift index ac31974..7f11cb9 100644 --- a/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorage.swift +++ b/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorage.swift @@ -17,6 +17,7 @@ struct UserPreferencesStorage { case zcashFiatConverted case zcashRecoveryPhraseTestCompleted case zcashSessionAutoshielded + case zcashUserOptedOutOfCrashReporting } /// Default values for all preferences in case there is no value stored (counterparts to `Constants`) @@ -25,6 +26,7 @@ struct UserPreferencesStorage { private let fiatConvertion: Bool private let recoveryPhraseTestCompleted: Bool private let sessionAutoshielded: Bool + private let userOptedOutOfCrashReporting: Bool private let userDefaults: UserDefaultsClient @@ -34,6 +36,7 @@ struct UserPreferencesStorage { fiatConvertion: Bool, recoveryPhraseTestCompleted: Bool, sessionAutoshielded: Bool, + userOptedOutOfCrashReporting: Bool, userDefaults: UserDefaultsClient ) { self.appSessionFrom = appSessionFrom @@ -41,6 +44,7 @@ struct UserPreferencesStorage { self.fiatConvertion = fiatConvertion self.recoveryPhraseTestCompleted = recoveryPhraseTestCompleted self.sessionAutoshielded = sessionAutoshielded + self.userOptedOutOfCrashReporting = userOptedOutOfCrashReporting self.userDefaults = userDefaults } @@ -89,6 +93,15 @@ struct UserPreferencesStorage { await setValue(bool, forKey: Constants.zcashSessionAutoshielded.rawValue) } + /// Whether the user has opted out of crash reporting + var isUserOptedOutOfCrashReporting: Bool { + getValue(forKey: Constants.zcashUserOptedOutOfCrashReporting.rawValue, default: false) + } + + func setIsUserOptedOutOfCrashReporting(_ bool: Bool) async { + await setValue(bool, forKey: Constants.zcashUserOptedOutOfCrashReporting.rawValue) + } + /// Use carefully: Deletes all user preferences from the User Defaults func removeAll() async { for key in Constants.allCases { diff --git a/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageInterface.swift b/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageInterface.swift new file mode 100644 index 0000000..9148449 --- /dev/null +++ b/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageInterface.swift @@ -0,0 +1,38 @@ +// +// UserPreferencesStorageInterface.swift +// secant-testnet +// +// Created by Francisco Gindre on 2/6/23. +// + +import Foundation +import ComposableArchitecture + +extension DependencyValues { + var userStoredPreferences: UserPreferencesStorageClient { + get { self [UserPreferencesStorageClient.self] } + set { self[UserPreferencesStorageClient.self] = newValue } + } +} + +struct UserPreferencesStorageClient { + var activeAppSessionFrom: () -> TimeInterval + var setActiveAppSessionFrom: (TimeInterval) async -> Void + + var currency: () -> String + var setCurrenty: (String) async -> Void + + var isFiatConverted: () -> Bool + var setIsFiatConverted: (Bool) async -> Void + + var isRecoveryPhraseTestCompleted: () -> Bool + var setIsRecoveryPhraseTestCompleted: (Bool) async -> Void + + var isSessionAutoshielded: () -> Bool + var setIsSessionAutoshielded: (Bool) async -> Void + + var isUserOptedOutOfCrashReporting: () -> Bool + var setIsUserOptedOutOfCrashReporting: (Bool) async -> Void + + var removeAll: () async -> Void +} diff --git a/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageLive.swift b/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageLive.swift index 5bd5a90..f43b1b7 100644 --- a/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageLive.swift +++ b/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageLive.swift @@ -6,6 +6,34 @@ // import Foundation +import ComposableArchitecture + +extension UserPreferencesStorageClient: DependencyKey { + static var liveValue: UserPreferencesStorageClient = { + let live = UserPreferencesStorage.live + + return UserPreferencesStorageClient( + activeAppSessionFrom: { live.activeAppSessionFrom }, + setActiveAppSessionFrom: live.setActiveAppSessionFrom(_:), + currency: { live.currency }, + setCurrenty: live.setCurrency(_:), + isFiatConverted: { live.isFiatConverted }, + setIsFiatConverted: live.setIsFiatConverted(_:), + isRecoveryPhraseTestCompleted: { + live.isRecoveryPhraseTestCompleted + }, + setIsRecoveryPhraseTestCompleted: live.setIsRecoveryPhraseTestCompleted(_:), + isSessionAutoshielded: { live.isSessionAutoshielded }, + setIsSessionAutoshielded: live.setIsSessionAutoshielded(_:), + isUserOptedOutOfCrashReporting: { + live.isUserOptedOutOfCrashReporting + }, + setIsUserOptedOutOfCrashReporting: live.setIsUserOptedOutOfCrashReporting(_:), + removeAll: live.removeAll + ) + }() +} + extension UserPreferencesStorage { static let live = UserPreferencesStorage( @@ -14,6 +42,7 @@ extension UserPreferencesStorage { fiatConvertion: true, recoveryPhraseTestCompleted: false, sessionAutoshielded: true, + userOptedOutOfCrashReporting: false, userDefaults: .live() ) } diff --git a/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageMocks.swift b/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageMocks.swift index 0b5d38e..28d58e8 100644 --- a/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageMocks.swift +++ b/secant/Dependencies/UserPreferencesStorage/UserPreferencesStorageMocks.swift @@ -6,6 +6,33 @@ // import Foundation +import ComposableArchitecture + +extension UserPreferencesStorageClient: TestDependencyKey { + static var testValue = { + let mock = UserPreferencesStorage.mock + + return UserPreferencesStorageClient( + activeAppSessionFrom: { mock.activeAppSessionFrom }, + setActiveAppSessionFrom: mock.setActiveAppSessionFrom(_:), + currency: { mock.currency }, + setCurrenty: mock.setCurrency(_:), + isFiatConverted: { mock.isFiatConverted }, + setIsFiatConverted: mock.setIsFiatConverted(_:), + isRecoveryPhraseTestCompleted: { + mock.isRecoveryPhraseTestCompleted + }, + setIsRecoveryPhraseTestCompleted: mock.setIsRecoveryPhraseTestCompleted(_:), + isSessionAutoshielded: { mock.isSessionAutoshielded }, + setIsSessionAutoshielded: mock.setIsSessionAutoshielded(_:), + isUserOptedOutOfCrashReporting: { + mock.isUserOptedOutOfCrashReporting + }, + setIsUserOptedOutOfCrashReporting: mock.setIsUserOptedOutOfCrashReporting(_:), + removeAll: mock.removeAll + ) + }() +} extension UserPreferencesStorage { static let mock = UserPreferencesStorage( @@ -14,6 +41,7 @@ extension UserPreferencesStorage { fiatConvertion: true, recoveryPhraseTestCompleted: false, sessionAutoshielded: true, + userOptedOutOfCrashReporting: false, userDefaults: .noOp ) } diff --git a/secant/Features/Root/RootInitialization.swift b/secant/Features/Root/RootInitialization.swift index d6c9d12..0a05bb3 100644 --- a/secant/Features/Root/RootInitialization.swift +++ b/secant/Features/Root/RootInitialization.swift @@ -14,6 +14,7 @@ extension RootReducer { case appDelegate(AppDelegateAction) case checkBackupPhraseValidation case checkWalletInitialization + case configureCrashReporter case createNewWallet case initializeSDK case nukeWallet @@ -27,10 +28,13 @@ extension RootReducer { case .initialization(.appDelegate(.didFinishLaunching)): // TODO: [#524] finish all the wallet events according to definition, https://github.com/zcash/secant-ios-wallet/issues/524 LoggerProxy.event(".appDelegate(.didFinishLaunching)") - /// We need to fetch data from keychain, in order to be 100% sure the kecyhain can be read we delay the check a bit - return EffectTask(value: .initialization(.checkWalletInitialization)) - .delay(for: 0.02, scheduler: mainQueue) - .eraseToEffect() + /// We need to fetch data from keychain, in order to be 100% sure the keychain can be read we delay the check a bit + return .concatenate( + EffectTask(value: .initialization(.configureCrashReporter)), + EffectTask(value: .initialization(.checkWalletInitialization)) + .delay(for: 0.02, scheduler: mainQueue) + .eraseToEffect() + ) /// Evaluate the wallet's state based on keychain keys and database files presence case .initialization(.checkWalletInitialization): @@ -193,6 +197,12 @@ extension RootReducer { case .home, .destination, .onboarding, .phraseDisplay, .phraseValidation, .sandbox, .welcome: return .none + + case .initialization(.configureCrashReporter): + crashReporter.configure( + !userStoredPreferences.isUserOptedOutOfCrashReporting() + ) + return .none } } } diff --git a/secant/Features/Root/RootStore.swift b/secant/Features/Root/RootStore.swift index 1af2ecf..db599b6 100644 --- a/secant/Features/Root/RootStore.swift +++ b/secant/Features/Root/RootStore.swift @@ -29,7 +29,8 @@ struct RootReducer: ReducerProtocol { case sandbox(SandboxReducer.Action) case welcome(WelcomeReducer.Action) } - + + @Dependency(\.crashReporter) var crashReporter @Dependency(\.databaseFiles) var databaseFiles @Dependency(\.deeplink) var deeplink @Dependency(\.derivationTool) var derivationTool @@ -37,6 +38,7 @@ struct RootReducer: ReducerProtocol { @Dependency(\.mnemonic) var mnemonic @Dependency(\.randomRecoveryPhrase) var randomRecoveryPhrase @Dependency(\.sdkSynchronizer) var sdkSynchronizer + @Dependency(\.userStoredPreferences) var userStoredPreferences @Dependency(\.walletStorage) var walletStorage @Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment diff --git a/secant/Features/Settings/SettingsStore.swift b/secant/Features/Settings/SettingsStore.swift index 62d1587..6228322 100644 --- a/secant/Features/Settings/SettingsStore.swift +++ b/secant/Features/Settings/SettingsStore.swift @@ -15,7 +15,9 @@ struct SettingsReducer: ReducerProtocol { var isSharingLogs = false var phraseDisplayState: RecoveryPhraseDisplayReducer.State var rescanDialog: ConfirmationDialogState? - + + @BindableState var isCrashReportingOn: Bool + var tempSDKDir: URL { let tempDir = FileManager.default.temporaryDirectory let sdkFileName = "sdkLogs.txt" @@ -35,18 +37,21 @@ struct SettingsReducer: ReducerProtocol { } } - enum Action: Equatable { + enum Action: BindableAction, Equatable { case backupWallet case backupWalletAccessRequest + case binding(BindingAction) case cancelRescan case exportLogs case fullRescan case logsExported case logsShareFinished + case onAppear case phraseDisplay(RecoveryPhraseDisplayReducer.Action) case quickRescan case rescanBlockchain case updateDestination(SettingsReducer.State.Destination?) + case testCrashReporter // this will crash the app if live. } @Dependency(\.localAuthentication) var localAuthentication @@ -54,10 +59,15 @@ struct SettingsReducer: ReducerProtocol { @Dependency(\.sdkSynchronizer) var sdkSynchronizer @Dependency(\.logsHandler) var logsHandler @Dependency(\.walletStorage) var walletStorage + @Dependency(\.userStoredPreferences) var userStoredPreferences + @Dependency(\.crashReporter) var crashReporter var body: some ReducerProtocol { Reduce { state, action in switch action { + case .onAppear: + state.isCrashReportingOn = !userStoredPreferences.isUserOptedOutOfCrashReporting() + return .none case .backupWalletAccessRequest: return .run { send in if await localAuthentication.authenticate() { @@ -76,6 +86,17 @@ struct SettingsReducer: ReducerProtocol { // TODO: [#221] - merge with issue 221 (https://github.com/zcash/secant-ios-wallet/issues/221) and its Error States return .none } + + case .binding(\.$isCrashReportingOn): + if state.isCrashReportingOn { + crashReporter.optOut() + } else { + crashReporter.optIn() + } + + return .run { [state] send in + await userStoredPreferences.setIsUserOptedOutOfCrashReporting(state.isCrashReportingOn) + } case .cancelRescan, .quickRescan, .fullRescan: state.rescanDialog = nil @@ -112,7 +133,7 @@ struct SettingsReducer: ReducerProtocol { ] ) return .none - + case .phraseDisplay: state.destination = nil return .none @@ -120,6 +141,13 @@ struct SettingsReducer: ReducerProtocol { case .updateDestination(let destination): state.destination = destination return .none + + case .testCrashReporter: + crashReporter.testCrash() + return .none + + case .binding: + return .none } } @@ -164,7 +192,8 @@ extension SettingsReducer.State { static let placeholder = SettingsReducer.State( phraseDisplayState: RecoveryPhraseDisplayReducer.State( phrase: .placeholder - ) + ), + isCrashReportingOn: true ) } diff --git a/secant/Features/Settings/SettingsView.swift b/secant/Features/Settings/SettingsView.swift index 3ac1ff2..d16609a 100644 --- a/secant/Features/Settings/SettingsView.swift +++ b/secant/Features/Settings/SettingsView.swift @@ -6,14 +6,16 @@ struct SettingsView: View { var body: some View { WithViewStore(store) { viewStore in - VStack { + VStack(spacing: 40) { + Toggle("Enable Crash Reporting", + isOn: viewStore.binding(\.$isCrashReportingOn) + ) Button( action: { viewStore.send(.backupWalletAccessRequest) }, label: { Text("Backup Wallet") } ) .activeButtonStyle .frame(height: 50) - .padding(30) Button( action: { viewStore.send(.rescanBlockchain) }, @@ -21,7 +23,6 @@ struct SettingsView: View { ) .primaryButtonStyle .frame(height: 50) - .padding(.horizontal, 30) Button( action: { viewStore.send(.exportLogs) }, @@ -38,12 +39,17 @@ struct SettingsView: View { ) .primaryButtonStyle .frame(height: 50) - .padding(.horizontal, 30) - .padding(.top, 30) .disabled(viewStore.exportLogsDisabled) + Button( + action: { viewStore.send(.testCrashReporter) }, + label: { Text("Test Crash Reporter") } + ) + .primaryButtonStyle + .frame(height: 50) Spacer() } + .padding(.horizontal, 30) .navigationTitle("Settings") .applyScreenBackground() .confirmationDialog( @@ -56,6 +62,7 @@ struct SettingsView: View { RecoveryPhraseDisplayView(store: store.backupPhraseStore()) } ) + .onAppear { viewStore.send(.onAppear) } if viewStore.isSharingLogs { UIShareDialogView( diff --git a/secant/Info.plist b/secant/Info.plist index 4de661d..34d813c 100644 --- a/secant/Info.plist +++ b/secant/Info.plist @@ -86,12 +86,7 @@ UIInterfaceOrientationPortrait - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - + FirebaseCrashlyticsCollectionEnabled + diff --git a/secantTests/RootTests/AppInitializationTests.swift b/secantTests/RootTests/AppInitializationTests.swift index 7c9e2e2..aa7454b 100644 --- a/secantTests/RootTests/AppInitializationTests.swift +++ b/secantTests/RootTests/AppInitializationTests.swift @@ -105,16 +105,19 @@ class AppInitializationTests: XCTestCase { await testScheduler.advance(by: 0.02) // ad 1. - await store.receive(.initialization(.checkWalletInitialization)) + await store.receive(.initialization(.configureCrashReporter)) // ad 2. - await store.receive(.initialization(.respondToWalletInitializationState(.initialized))) + await store.receive(.initialization(.checkWalletInitialization)) // ad 3. + await store.receive(.initialization(.respondToWalletInitializationState(.initialized))) + + // ad 4. await store.receive(.initialization(.initializeSDK)) { state in state.storedWallet = .placeholder } - // ad 4. + // ad 5. await store.receive(.initialization(.checkBackupPhraseValidation)) { state in state.appInitializationState = .initialized } @@ -153,9 +156,12 @@ class AppInitializationTests: XCTestCase { testScheduler.advance(by: 0.02) // ad 1. + store.receive(.initialization(.configureCrashReporter)) + + // ad 2 store.receive(.initialization(.checkWalletInitialization)) - // ad 2. + // ad 3. store.receive(.initialization(.respondToWalletInitializationState(.keysMissing))) { state in state.appInitializationState = .keysMissing } @@ -184,14 +190,17 @@ class AppInitializationTests: XCTestCase { // the 0.02 delay ensures keychain is ready // the 3.0 delay ensures the welcome screen is visible till the initialization check is done testScheduler.advance(by: 3.02) - + // ad 1. - store.receive(.initialization(.checkWalletInitialization)) + store.receive(.initialization(.configureCrashReporter)) // ad 2. + store.receive(.initialization(.checkWalletInitialization)) + + // ad 3. store.receive(.initialization(.respondToWalletInitializationState(.uninitialized))) - // ad 3. + // ad 4. store.receive(.destination(.updateDestination(.onboarding))) { state in state.destinationState.previousDestination = .welcome state.destinationState.internalDestination = .onboarding diff --git a/secantTests/SettingsTests/SettingsTests.swift b/secantTests/SettingsTests/SettingsTests.swift index c8f9dfe..a328de4 100644 --- a/secantTests/SettingsTests/SettingsTests.swift +++ b/secantTests/SettingsTests/SettingsTests.swift @@ -45,7 +45,10 @@ class SettingsTests: XCTestCase { ) let store = TestStore( - initialState: SettingsReducer.State(phraseDisplayState: RecoveryPhraseDisplayReducer.State(phrase: nil)), + initialState: SettingsReducer.State( + phraseDisplayState: RecoveryPhraseDisplayReducer.State(phrase: nil), + isCrashReportingOn: false + ), reducer: SettingsReducer() ) { dependencies in dependencies.localAuthentication = .mockAuthenticationSucceeded @@ -109,7 +112,8 @@ class SettingsTests: XCTestCase { .default(TextState("Full rescan"), action: .send(.fullRescan)), .cancel(TextState("Cancel")) ] - ) + ), + isCrashReportingOn: false ), reducer: SettingsReducer() ) @@ -132,7 +136,8 @@ class SettingsTests: XCTestCase { .default(TextState("Full rescan"), action: .send(.fullRescan)), .cancel(TextState("Cancel")) ] - ) + ), + isCrashReportingOn: false ), reducer: SettingsReducer() ) @@ -155,7 +160,8 @@ class SettingsTests: XCTestCase { .default(TextState("Full rescan"), action: .send(.fullRescan)), .cancel(TextState("Cancel")) ] - ) + ), + isCrashReportingOn: false ), reducer: SettingsReducer() ) @@ -178,7 +184,8 @@ class SettingsTests: XCTestCase { .default(TextState("Full rescan"), action: .send(.fullRescan)), .cancel(TextState("Cancel")) ] - ) + ), + isCrashReportingOn: false ), reducer: SettingsReducer() ) @@ -209,7 +216,8 @@ class SettingsTests: XCTestCase { .default(TextState("Full rescan"), action: .send(.fullRescan)), .cancel(TextState("Cancel")) ] - ) + ), + isCrashReportingOn: false ), reducer: SettingsReducer() ) diff --git a/secantTests/UtilTests/UserPreferencesStorageTests.swift b/secantTests/UtilTests/UserPreferencesStorageTests.swift index 6b666b8..a518cd0 100644 --- a/secantTests/UtilTests/UserPreferencesStorageTests.swift +++ b/secantTests/UtilTests/UserPreferencesStorageTests.swift @@ -26,6 +26,7 @@ class UserPreferencesStorageTests: XCTestCase { fiatConvertion: true, recoveryPhraseTestCompleted: true, sessionAutoshielded: false, + userOptedOutOfCrashReporting: true, userDefaults: .live(userDefaults: userDefaults) ) await storage.removeAll() @@ -107,6 +108,7 @@ class UserPreferencesStorageTests: XCTestCase { fiatConvertion: true, recoveryPhraseTestCompleted: true, sessionAutoshielded: false, + userOptedOutOfCrashReporting: true, userDefaults: mockedUD ) @@ -127,6 +129,7 @@ class UserPreferencesStorageTests: XCTestCase { fiatConvertion: true, recoveryPhraseTestCompleted: true, sessionAutoshielded: false, + userOptedOutOfCrashReporting: true, userDefaults: mockedUD ) @@ -147,6 +150,7 @@ class UserPreferencesStorageTests: XCTestCase { fiatConvertion: true, recoveryPhraseTestCompleted: true, sessionAutoshielded: false, + userOptedOutOfCrashReporting: true, userDefaults: mockedUD ) @@ -167,6 +171,7 @@ class UserPreferencesStorageTests: XCTestCase { fiatConvertion: true, recoveryPhraseTestCompleted: true, sessionAutoshielded: false, + userOptedOutOfCrashReporting: true, userDefaults: mockedUD ) @@ -187,6 +192,7 @@ class UserPreferencesStorageTests: XCTestCase { fiatConvertion: true, recoveryPhraseTestCompleted: true, sessionAutoshielded: false, + userOptedOutOfCrashReporting: true, userDefaults: mockedUD )