From 98f3898ca48b20a8a04940b3426bcf69589a89fd Mon Sep 17 00:00:00 2001 From: cyndichin Date: Mon, 13 May 2024 15:45:52 -0400 Subject: [PATCH] Add FXIOS-8988 [Microsurvey] Redux architecture --- firefox-ios/Client.xcodeproj/project.pbxproj | 38 ++++++- .../xcshareddata/swiftpm/Package.resolved | 99 ------------------- .../State/BrowserViewControllerState.swift | 47 +++++++-- .../Views/BrowserViewController.swift | 26 +++-- .../Frontend/Fakespot/FakespotState.swift | 11 --- .../Microsurvey/MicrosurveyViewModel.swift | 18 ---- .../{ => Prompt}/MicrosurveyPrompt.swift | 36 ++++--- .../Prompt/MicrosurveyPromptAction.swift | 31 ++++++ .../Prompt/MicrosurveyPromptMiddleware.swift | 58 +++++++++++ .../Prompt/MicrosurveyPromptState.swift | 66 +++++++++++++ .../Client/Redux/GlobalState/AppState.swift | 3 +- .../Microsurvey/MicrosurveyStateTests.swift | 77 +++++++++++++++ .../nimbus-features/microsurveyFeature.yaml | 2 +- 13 files changed, 343 insertions(+), 169 deletions(-) delete mode 100644 firefox-ios/Client/Frontend/Microsurvey/MicrosurveyViewModel.swift rename firefox-ios/Client/Frontend/Microsurvey/{ => Prompt}/MicrosurveyPrompt.swift (86%) create mode 100644 firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptAction.swift create mode 100644 firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptMiddleware.swift create mode 100644 firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptState.swift create mode 100644 firefox-ios/firefox-ios-tests/Tests/ClientTests/Microsurvey/MicrosurveyStateTests.swift diff --git a/firefox-ios/Client.xcodeproj/project.pbxproj b/firefox-ios/Client.xcodeproj/project.pbxproj index 6d0695a035178..31ed4045449be 100644 --- a/firefox-ios/Client.xcodeproj/project.pbxproj +++ b/firefox-ios/Client.xcodeproj/project.pbxproj @@ -618,6 +618,9 @@ 8A1A93592B757C7C0069C190 /* landscape.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A1A93532B757C7B0069C190 /* landscape.json */; }; 8A1A935A2B757C7C0069C190 /* portrait.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A1A93542B757C7B0069C190 /* portrait.json */; }; 8A1A935B2B757C7C0069C190 /* wave.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A1A93552B757C7B0069C190 /* wave.json */; }; + 8A1CBB952BE017D3008BE4D4 /* MicrosurveyPromptAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1CBB942BE017D3008BE4D4 /* MicrosurveyPromptAction.swift */; }; + 8A1CBB972BE0182C008BE4D4 /* MicrosurveyPromptMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1CBB962BE0182C008BE4D4 /* MicrosurveyPromptMiddleware.swift */; }; + 8A1CBB992BE01839008BE4D4 /* MicrosurveyPromptState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1CBB982BE01839008BE4D4 /* MicrosurveyPromptState.swift */; }; 8A1E3BDF28CBA81E003388C4 /* SponsoredContentFilterUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1E3BDE28CBA81E003388C4 /* SponsoredContentFilterUtility.swift */; }; 8A1E3BE328CBACDD003388C4 /* SponsoredContentFilterUtilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1E3BE128CBACD7003388C4 /* SponsoredContentFilterUtilityTests.swift */; }; 8A1E93EA2A3CDC6100DD540A /* BaseCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1E93E92A3CDC6100DD540A /* BaseCoordinator.swift */; }; @@ -630,7 +633,6 @@ 8A2825352760399B00395E66 /* KeyboardPressesHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A2825342760399B00395E66 /* KeyboardPressesHandlerTests.swift */; }; 8A285B08294A5D4C00149B0F /* HomepageHeroImageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A285B07294A5D4C00149B0F /* HomepageHeroImageViewModel.swift */; }; 8A28C628291028870078A81A /* CanRemoveQuickActionBookmarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A28C627291028870078A81A /* CanRemoveQuickActionBookmarkTests.swift */; }; - 8A28F3FD2BD6B7A400A93410 /* MicrosurveyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A28F3FC2BD6B7A400A93410 /* MicrosurveyViewModel.swift */; }; 8A2B1A5D28216C4D0061216B /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 8A2B1A5A28216C4C0061216B /* Debug.xcconfig */; }; 8A2B1A5E28216C4D0061216B /* Common.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 8A2B1A5B28216C4C0061216B /* Common.xcconfig */; }; 8A2B1A5F28216C4D0061216B /* Release.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 8A2B1A5C28216C4D0061216B /* Release.xcconfig */; }; @@ -740,6 +742,7 @@ 8A83B7482A264FB7002FF9AC /* LibraryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A83B7472A264FB7002FF9AC /* LibraryCoordinator.swift */; }; 8A83B74A2A265044002FF9AC /* SettingsCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A83B7492A265044002FF9AC /* SettingsCoordinatorTests.swift */; }; 8A83B74C2A265061002FF9AC /* LibraryCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A83B74B2A265061002FF9AC /* LibraryCoordinatorTests.swift */; }; + 8A8482F02BE1602500F9007B /* MicrosurveyStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8482EE2BE15FFE00F9007B /* MicrosurveyStateTests.swift */; }; 8A8629E2288096C40096DDB1 /* BookmarksFolderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8629E1288096C40096DDB1 /* BookmarksFolderCell.swift */; }; 8A8629E72880B7330096DDB1 /* BookmarksPanelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8629E52880B69C0096DDB1 /* BookmarksPanelTests.swift */; }; 8A86DAD8277298DE00D7BFFF /* ClosedTabsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A86DAD7277298DE00D7BFFF /* ClosedTabsStoreTests.swift */; }; @@ -5805,6 +5808,9 @@ 8A1A93532B757C7B0069C190 /* landscape.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = landscape.json; sourceTree = ""; }; 8A1A93542B757C7B0069C190 /* portrait.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = portrait.json; sourceTree = ""; }; 8A1A93552B757C7B0069C190 /* wave.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = wave.json; sourceTree = ""; }; + 8A1CBB942BE017D3008BE4D4 /* MicrosurveyPromptAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosurveyPromptAction.swift; sourceTree = ""; }; + 8A1CBB962BE0182C008BE4D4 /* MicrosurveyPromptMiddleware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosurveyPromptMiddleware.swift; sourceTree = ""; }; + 8A1CBB982BE01839008BE4D4 /* MicrosurveyPromptState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosurveyPromptState.swift; sourceTree = ""; }; 8A1E3BDE28CBA81E003388C4 /* SponsoredContentFilterUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SponsoredContentFilterUtility.swift; sourceTree = ""; }; 8A1E3BE128CBACD7003388C4 /* SponsoredContentFilterUtilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SponsoredContentFilterUtilityTests.swift; sourceTree = ""; }; 8A1E3BE528CBBF44003388C4 /* OpenSearchEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSearchEngine.swift; sourceTree = ""; }; @@ -5813,7 +5819,6 @@ 8A2825342760399B00395E66 /* KeyboardPressesHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardPressesHandlerTests.swift; sourceTree = ""; }; 8A285B07294A5D4C00149B0F /* HomepageHeroImageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomepageHeroImageViewModel.swift; sourceTree = ""; }; 8A28C627291028870078A81A /* CanRemoveQuickActionBookmarkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CanRemoveQuickActionBookmarkTests.swift; sourceTree = ""; }; - 8A28F3FC2BD6B7A400A93410 /* MicrosurveyViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosurveyViewModel.swift; sourceTree = ""; }; 8A2B1A5A28216C4C0061216B /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Configuration/Debug.xcconfig; sourceTree = ""; }; 8A2B1A5B28216C4C0061216B /* Common.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Common.xcconfig; path = Configuration/Common.xcconfig; sourceTree = ""; }; 8A2B1A5C28216C4D0061216B /* Release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Configuration/Release.xcconfig; sourceTree = ""; }; @@ -5934,6 +5939,7 @@ 8A83B7472A264FB7002FF9AC /* LibraryCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryCoordinator.swift; sourceTree = ""; }; 8A83B7492A265044002FF9AC /* SettingsCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCoordinatorTests.swift; sourceTree = ""; }; 8A83B74B2A265061002FF9AC /* LibraryCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryCoordinatorTests.swift; sourceTree = ""; }; + 8A8482EE2BE15FFE00F9007B /* MicrosurveyStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosurveyStateTests.swift; sourceTree = ""; }; 8A8629E1288096C40096DDB1 /* BookmarksFolderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksFolderCell.swift; sourceTree = ""; }; 8A8629E52880B69C0096DDB1 /* BookmarksPanelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksPanelTests.swift; sourceTree = ""; }; 8A86DAD7277298DE00D7BFFF /* ClosedTabsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosedTabsStoreTests.swift; sourceTree = ""; }; @@ -9395,6 +9401,17 @@ path = LottieFiles; sourceTree = ""; }; + 8A1CBB932BE017BE008BE4D4 /* Prompt */ = { + isa = PBXGroup; + children = ( + 8A6A3D462BD0390100BFDB64 /* MicrosurveyPrompt.swift */, + 8A1CBB942BE017D3008BE4D4 /* MicrosurveyPromptAction.swift */, + 8A1CBB962BE0182C008BE4D4 /* MicrosurveyPromptMiddleware.swift */, + 8A1CBB982BE01839008BE4D4 /* MicrosurveyPromptState.swift */, + ); + path = Prompt; + sourceTree = ""; + }; 8A1E3BE028CBAC1F003388C4 /* Utils */ = { isa = PBXGroup; children = ( @@ -9601,8 +9618,7 @@ 8A6A3D452BD038EF00BFDB64 /* Microsurvey */ = { isa = PBXGroup; children = ( - 8A6A3D462BD0390100BFDB64 /* MicrosurveyPrompt.swift */, - 8A28F3FC2BD6B7A400A93410 /* MicrosurveyViewModel.swift */, + 8A1CBB932BE017BE008BE4D4 /* Prompt */, ); path = Microsurvey; sourceTree = ""; @@ -9627,6 +9643,14 @@ path = Launch; sourceTree = ""; }; + 8A8482ED2BE15FEF00F9007B /* Microsurvey */ = { + isa = PBXGroup; + children = ( + 8A8482EE2BE15FFE00F9007B /* MicrosurveyStateTests.swift */, + ); + path = Microsurvey; + sourceTree = ""; + }; 8A93F85C29D36D9F004159D9 /* Coordinators */ = { isa = PBXGroup; children = ( @@ -11821,6 +11845,7 @@ F84B21D61A090F8100AAB793 /* ClientTests */ = { isa = PBXGroup; children = ( + 8A8482ED2BE15FEF00F9007B /* Microsurvey */, 8AC225632B6D3F9600CDA7FD /* Telemetry */, 8A171A6029F82AD90085770E /* Application */, CA7BD564248185B500A0A61B /* BreachAlertsTests.swift */, @@ -13803,7 +13828,6 @@ C8680C5728BFDF7F00BC902A /* WallpaperThumbnailUtility.swift in Sources */, C23889DF2A4EFCE500429673 /* ShareExtensionCoordinator.swift in Sources */, 8AE80BBE2891C21A00BC12EA /* JumpBackInSyncedTab.swift in Sources */, - 8A28F3FD2BD6B7A400A93410 /* MicrosurveyViewModel.swift in Sources */, 8C6F94652A972EB300415FF6 /* FakespotAdjustRatingView.swift in Sources */, 8A3EF7FD2A2FCFAC00796E3A /* AppReviewPromptSetting.swift in Sources */, D3B6923F1B9F9A58004B87A4 /* FindInPageHelper.swift in Sources */, @@ -13877,6 +13901,7 @@ D3BE7B461B054F8600641031 /* UITestAppDelegate.swift in Sources */, C8DC90C72A06759E0008832B /* MarkupAttributionUtility.swift in Sources */, 219A0FD52ACC8506009A6D1A /* InactiveTabsCell.swift in Sources */, + 8A1CBB992BE01839008BE4D4 /* MicrosurveyPromptState.swift in Sources */, 23D57E6E25ED6F2700883FAD /* SearchViewController.swift in Sources */, C82A94F2269F68ED00624AA7 /* LegacyFeatureFlagsManager.swift in Sources */, C8610DAA2A0EBF7100B79FF1 /* OnboardingCardDelegate.swift in Sources */, @@ -14180,6 +14205,7 @@ 216A0D792A40E85A008077BA /* ThemeSettingsState.swift in Sources */, 5A3A2A0D287F742C00B79EAC /* BackgroundSyncUtility.swift in Sources */, 21AFCFEE2AE80B700027E9CE /* TabsCoordinator.swift in Sources */, + 8A1CBB952BE017D3008BE4D4 /* MicrosurveyPromptAction.swift in Sources */, 23ED80FF25C89C9800D0E9D5 /* DefaultBrowserOnboardingViewController.swift in Sources */, 8A3EF80D2A2FD04D00796E3A /* ResetWallpaperOnboardingPage.swift in Sources */, E1FE132F29C0B3CB002A65FF /* NotificationSurfaceManager.swift in Sources */, @@ -14220,6 +14246,7 @@ E660BDD91BB06521009AC090 /* TabsButton.swift in Sources */, 8C92DE932A7128DE0090BD28 /* ProductAdsResponse.swift in Sources */, E1A6AB4828CA833000EBEBDD /* WallpaperBaseViewController.swift in Sources */, + 8A1CBB972BE0182C008BE4D4 /* MicrosurveyPromptMiddleware.swift in Sources */, 4331A9BB27193DF0005E8080 /* ContextualHintViewController.swift in Sources */, 39EF434E260A73950011E22E /* Experiments.swift in Sources */, E15DE7C0293A670700B32667 /* PhotonActionSheetSeparator.swift in Sources */, @@ -14571,6 +14598,7 @@ 8C8D8C822AA2229300490D32 /* FakespotViewModelTests.swift in Sources */, 5A70EF19295E2E1600790249 /* DependencyHelperMock.swift in Sources */, 8A96C4BB28F9E7B300B75884 /* XCTestCaseRootViewController.swift in Sources */, + 8A8482F02BE1602500F9007B /* MicrosurveyStateTests.swift in Sources */, 8ADED7EC27691351009C19E6 /* CalendarExtensionsTests.swift in Sources */, 3B39EDBA1E16E18900EF029F /* CustomSearchEnginesTest.swift in Sources */, C80C11EE28B3C8B80062922A /* WallpaperMetadataTrackerTests.swift in Sources */, diff --git a/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 668ad2a6b231c..0d8d7a515d833 100644 --- a/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,14 +1,5 @@ { "pins" : [ - { - "identity" : "a-star", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Dev1an/A-Star", - "state" : { - "revision" : "036256f9a8d1dda44085a2b92fa58199446a8339", - "version" : "3.0.0-beta-1" - } - }, { "identity" : "dip", "kind" : "remoteSourceControl", @@ -36,33 +27,6 @@ "revision" : "7674c93e79ee5aac17681acace324761325e7346" } }, - { - "identity" : "glean-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/mozilla/glean-swift", - "state" : { - "revision" : "227b9d80062eb6921963fac1899a25fff6babe02", - "version" : "60.0.0" - } - }, - { - "identity" : "ios_sdk", - "kind" : "remoteSourceControl", - "location" : "https://github.com/adjust/ios_sdk.git", - "state" : { - "revision" : "f7a0ad4a9f99fcbfc4c73dcad557ea3c86c5aaf6", - "version" : "4.37.0" - } - }, - { - "identity" : "kif", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kif-framework/KIF.git", - "state" : { - "revision" : "6c3ff27d9449eab614dae63e571596e4982a5205", - "version" : "3.8.9" - } - }, { "identity" : "kingfisher", "kind" : "remoteSourceControl", @@ -72,33 +36,6 @@ "version" : "7.11.0" } }, - { - "identity" : "lottie-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/airbnb/lottie-ios.git", - "state" : { - "revision" : "f522990668c2f9132323a2e68d924c7dcb9130b4", - "version" : "4.4.0" - } - }, - { - "identity" : "mappamundi", - "kind" : "remoteSourceControl", - "location" : "https://github.com/mozilla-mobile/MappaMundi.git", - "state" : { - "branch" : "master", - "revision" : "f56a6e483163a761adc8cd25c337db0ed1eac524" - } - }, - { - "identity" : "rust-components-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/mozilla/rust-components-swift.git", - "state" : { - "revision" : "cadf075e1a4c41c01fffc56ab76a946ac2280779", - "version" : "127.0.20240510050250" - } - }, { "identity" : "sentry-cocoa", "kind" : "remoteSourceControl", @@ -108,42 +45,6 @@ "version" : "8.21.0" } }, - { - "identity" : "snapkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/SnapKit/SnapKit.git", - "state" : { - "revision" : "e74fe2a978d1216c3602b129447c7301573cc2d8", - "version" : "5.7.0" - } - }, - { - "identity" : "swift-asn1", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-asn1.git", - "state" : { - "revision" : "c7e239b5c1492ffc3ebd7fbcc7a92548ce4e78f0", - "version" : "1.1.0" - } - }, - { - "identity" : "swift-certificates", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-certificates.git", - "state" : { - "revision" : "bc566f88842b3b8001717326d935c2d113af5741", - "version" : "1.2.0" - } - }, - { - "identity" : "swift-crypto", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-crypto.git", - "state" : { - "revision" : "f0525da24dc3c6cbb2b6b338b65042bc91cbc4bb", - "version" : "3.3.0" - } - }, { "identity" : "swiftybeaver", "kind" : "remoteSourceControl", diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift index 05acfac52c3e4..adb3c37aa1736 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift @@ -17,6 +17,7 @@ struct BrowserViewControllerState: ScreenState, Equatable { var reloadWebView: Bool var browserViewType: BrowserViewType var navigateToHome: Bool + var microsurveyState: MicrosurveyPromptState init(appState: AppState, uuid: WindowUUID) { guard let bvcState = store.state.screenState( @@ -37,7 +38,8 @@ struct BrowserViewControllerState: ScreenState, Equatable { windowUUID: bvcState.windowUUID, reloadWebView: bvcState.reloadWebView, browserViewType: bvcState.browserViewType, - navigateToHome: bvcState.navigateToHome) + navigateToHome: bvcState.navigateToHome, + microsurveyState: bvcState.microsurveyState) } init(windowUUID: WindowUUID) { @@ -50,7 +52,8 @@ struct BrowserViewControllerState: ScreenState, Equatable { showOverlay: false, windowUUID: windowUUID, browserViewType: .normalHomepage, - navigateToHome: false) + navigateToHome: false, + microsurveyState: MicrosurveyPromptState(windowUUID: windowUUID)) } init( @@ -64,6 +67,7 @@ struct BrowserViewControllerState: ScreenState, Equatable { reloadWebView: Bool = false, browserViewType: BrowserViewType, navigateToHome: Bool = false + microsurveyState: MicrosurveyPromptState ) { self.searchScreenState = searchScreenState self.showDataClearanceFlow = showDataClearanceFlow @@ -75,6 +79,7 @@ struct BrowserViewControllerState: ScreenState, Equatable { self.reloadWebView = reloadWebView self.browserViewType = browserViewType self.navigateToHome = navigateToHome + self.microsurveyState = microsurveyState } static let reducer: Reducer = { state, action in @@ -83,6 +88,8 @@ struct BrowserViewControllerState: ScreenState, Equatable { if let action = action as? FakespotAction { return BrowserViewControllerState.reduceStateForFakeSpotAction(action: action, state: state) + } else if let action = action as? MicrosurveyPromptAction { + return BrowserViewControllerState.reduceStateForMicrosurveyAction(action: action, state: state) } else if let action = action as? PrivateModeAction { return BrowserViewControllerState.reduceStateForPrivateModeAction(action: action, state: state) } else if let action = action as? GeneralBrowserAction { @@ -99,7 +106,8 @@ struct BrowserViewControllerState: ScreenState, Equatable { windowUUID: state.windowUUID, reloadWebView: false, browserViewType: state.browserViewType, - navigateToHome: state.navigateToHome) + navigateToHome: state.navigateToHome, + microsurveyState: MicrosurveyPromptState.reducer(state.microsurveyState, action)) } } @@ -112,7 +120,20 @@ struct BrowserViewControllerState: ScreenState, Equatable { fakespotState: FakespotState.reducer(state.fakespotState, action), windowUUID: state.windowUUID, browserViewType: state.browserViewType, - navigateToHome: state.navigateToHome) + navigateToHome: state.navigateToHome, + microsurveyState: MicrosurveyPromptState.reducer(state.microsurveyState, action)) + } + + static func reduceStateForMicrosurveyAction(action: MicrosurveyPromptAction, + state: BrowserViewControllerState) -> BrowserViewControllerState { + return BrowserViewControllerState( + searchScreenState: state.searchScreenState, + showDataClearanceFlow: state.showDataClearanceFlow, + fakespotState: state.fakespotState, + windowUUID: state.windowUUID, + browserViewType: state.browserViewType, + navigateToHome: state.navigateToHome, + microsurveyState: MicrosurveyPromptState.reducer(state.microsurveyState, action)) } static func reduceStateForPrivateModeAction(action: PrivateModeAction, @@ -132,7 +153,8 @@ struct BrowserViewControllerState: ScreenState, Equatable { windowUUID: state.windowUUID, reloadWebView: true, browserViewType: browserViewType, - navigateToHome: state.navigateToHome) + navigateToHome: state.navigateToHome, + microsurveyState: MicrosurveyPromptState.reducer(state.microsurveyState, action)) default: return state } @@ -151,7 +173,8 @@ struct BrowserViewControllerState: ScreenState, Equatable { toast: toastType, windowUUID: state.windowUUID, browserViewType: state.browserViewType, - navigateToHome: state.navigateToHome) + navigateToHome: state.navigateToHome, + microsurveyState: MicrosurveyPromptState.reducer(state.microsurveyState, action)) case GeneralBrowserActionType.showOverlay: let showOverlay = action.showOverlay ?? false return BrowserViewControllerState( @@ -162,7 +185,8 @@ struct BrowserViewControllerState: ScreenState, Equatable { showOverlay: showOverlay, windowUUID: state.windowUUID, browserViewType: state.browserViewType, - navigateToHome: state.navigateToHome) + navigateToHome: state.navigateToHome, + microsurveyState: MicrosurveyPromptState.reducer(state.microsurveyState, action)) case GeneralBrowserActionType.updateSelectedTab: return BrowserViewControllerState.resolveStateForUpdateSelectedTab(action: action, state: state) case GeneralBrowserActionType.goToHomepage: @@ -175,7 +199,8 @@ struct BrowserViewControllerState: ScreenState, Equatable { toast: state.toast, windowUUID: state.windowUUID, browserViewType: state.browserViewType, - navigateToHome: showHomepage) + navigateToHome: showHomepage, + microsurveyState: MicrosurveyState.reducer(state.microsurveyState, action)) default: return state @@ -194,7 +219,8 @@ struct BrowserViewControllerState: ScreenState, Equatable { showOverlay: state.showOverlay, windowUUID: state.windowUUID, browserViewType: state.browserViewType, - navigateToHome: state.navigateToHome) + navigateToHome: state.navigateToHome, + microsurveyState: MicrosurveyState.reducer(state.microsurveyState, action)) default: return state } @@ -221,6 +247,7 @@ struct BrowserViewControllerState: ScreenState, Equatable { windowUUID: state.windowUUID, reloadWebView: true, browserViewType: browserViewType, - navigateToHome: state.navigateToHome) + navigateToHome: state.navigateToHome, + microsurveyState: MicrosurveyPromptState.reducer(state.microsurveyState, action)) } } diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift index fac190f3633ed..de5ae89b8c140 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift @@ -588,6 +588,18 @@ class BrowserViewController: UIViewController, } updateToolbarActions() + + // Microsurveys + if !state.microsurveyState.showPrompt { + guard microsurvey != nil else { return } + removeMicrosurveyPrompt() + } else if state.microsurveyState.showSurvey { + // TODO: FXIOS-8895: Create Microsurvey Modal View + print("CYN - FXIOS-8895: Create Microsurvey Modal View") + } else if state.microsurveyState.showPrompt { + guard microsurvey == nil else { return } + createMicrosurveyPrompt(with: state.microsurveyState) + } } } @@ -1205,7 +1217,9 @@ class BrowserViewController: UIViewController, removeMicrosurveyPrompt() } - createMicrosurveyPrompt() + store.dispatch( + MicrosurveyPromptAction(windowUUID: windowUUID, actionType: MicrosurveyPromptActionType.showPrompt) + ) } private func updateMicrosurveyConstraints() { @@ -1229,14 +1243,8 @@ class BrowserViewController: UIViewController, updateViewConstraints() } - private func createMicrosurveyPrompt() { - let viewModel = MicrosurveyViewModel(openAction: { - // TODO: FXIOS-8895: Create Micro Survey Modal View - }) { - // TODO: FXIOS-8898: Setup Redux to handle open and dismissing modal - } - - self.microsurvey = MicrosurveyPromptView(viewModel: viewModel) + private func createMicrosurveyPrompt(with state: MicrosurveyPromptState) { + self.microsurvey = MicrosurveyPromptView(state: state, windowUUID: windowUUID) updateMicrosurveyConstraints() } diff --git a/firefox-ios/Client/Frontend/Fakespot/FakespotState.swift b/firefox-ios/Client/Frontend/Fakespot/FakespotState.swift index ad2a4588a1fb4..3960435bd80ec 100644 --- a/firefox-ios/Client/Frontend/Fakespot/FakespotState.swift +++ b/firefox-ios/Client/Frontend/Fakespot/FakespotState.swift @@ -33,17 +33,6 @@ struct FakespotState: ScreenState, Equatable { var isSettingsExpanded: Bool { expandState[currentTabUUID]?.isSettingsExpanded ?? false } var isHighlightsSectionExpanded: Bool { expandState[currentTabUUID]?.isHighlightsSectionExpanded ?? false } - init(_ appState: BrowserViewControllerState) { - self.init( - windowUUID: appState.windowUUID, - isOpen: appState.fakespotState.isOpen, - sidebarOpenForiPadLandscape: appState.fakespotState.sidebarOpenForiPadLandscape, - currentTabUUID: appState.fakespotState.currentTabUUID, - expandState: appState.fakespotState.expandState, - telemetryState: appState.fakespotState.telemetryState - ) - } - init(windowUUID: WindowUUID) { self.init( windowUUID: windowUUID, diff --git a/firefox-ios/Client/Frontend/Microsurvey/MicrosurveyViewModel.swift b/firefox-ios/Client/Frontend/Microsurvey/MicrosurveyViewModel.swift deleted file mode 100644 index 0662914897fd5..0000000000000 --- a/firefox-ios/Client/Frontend/Microsurvey/MicrosurveyViewModel.swift +++ /dev/null @@ -1,18 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/ - -import Foundation -import Shared - -struct MicrosurveyViewModel { - // TODO: FXIOS-8990 - Mobile Messaging Structure - // Title + button text can come from mobile messaging; but has a hardcoded string as fallback - var title = String( - format: .Microsurvey.Prompt.TitleLabel, - AppName.shortName.rawValue - ) - var buttonText: String = .Microsurvey.Prompt.TakeSurveyButton - var openAction: () -> Void - var closeAction: () -> Void -} diff --git a/firefox-ios/Client/Frontend/Microsurvey/MicrosurveyPrompt.swift b/firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPrompt.swift similarity index 86% rename from firefox-ios/Client/Frontend/Microsurvey/MicrosurveyPrompt.swift rename to firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPrompt.swift index e79ea9f1da026..6ca6ab787407f 100644 --- a/firefox-ios/Client/Frontend/Microsurvey/MicrosurveyPrompt.swift +++ b/firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPrompt.swift @@ -5,6 +5,7 @@ import Common import Foundation import ComponentLibrary +import Redux /* |----------------| @@ -15,7 +16,6 @@ import ComponentLibrary */ class MicrosurveyPromptView: UIView, ThemeApplicable { - private var viewModel: MicrosurveyViewModel struct UX { static let headerStackSpacing: CGFloat = 8 static let stackSpacing: CGFloat = 17 @@ -29,6 +29,8 @@ class MicrosurveyPromptView: UIView, ThemeApplicable { ) } + private let windowUUID: WindowUUID + private lazy var logoImage: UIImageView = .build { imageView in imageView.image = UIImage(imageLiteralResourceName: ImageIdentifiers.homeHeaderLogoBall) imageView.contentMode = .scaleAspectFit @@ -66,19 +68,32 @@ class MicrosurveyPromptView: UIView, ThemeApplicable { @objc func closeMicroSurvey() { - viewModel.closeAction() + store.dispatch( + MicrosurveyPromptAction(windowUUID: windowUUID, actionType: MicrosurveyPromptActionType.closePrompt) + ) } @objc func openMicroSurvey() { - viewModel.openAction() + store.dispatch( + MicrosurveyPromptAction(windowUUID: windowUUID, actionType: MicrosurveyPromptActionType.continueToSurvey) + ) } - init(viewModel: MicrosurveyViewModel) { - self.viewModel = viewModel + init(state: MicrosurveyPromptState, windowUUID: WindowUUID) { + self.windowUUID = windowUUID super.init(frame: .zero) + configure(with: state) setupView() - configure() + } + + private func configure(with state: MicrosurveyPromptState) { + titleLabel.text = state.model.title + let roundedButtonViewModel = SecondaryRoundedButtonViewModel( + title: state.model.button, + a11yIdentifier: AccessibilityIdentifiers.Microsurvey.Prompt.takeSurveyButton + ) + surveyButton.configure(viewModel: roundedButtonViewModel) } required init?(coder aDecoder: NSCoder) { @@ -109,15 +124,6 @@ class MicrosurveyPromptView: UIView, ThemeApplicable { ]) } - private func configure() { - titleLabel.text = viewModel.title - let roundedButtonViewModel = SecondaryRoundedButtonViewModel( - title: viewModel.buttonText, - a11yIdentifier: AccessibilityIdentifiers.Microsurvey.Prompt.takeSurveyButton - ) - surveyButton.configure(viewModel: roundedButtonViewModel) - } - func applyTheme(theme: Theme) { backgroundColor = theme.colors.layer1 titleLabel.textColor = theme.colors.textPrimary diff --git a/firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptAction.swift b/firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptAction.swift new file mode 100644 index 0000000000000..c3cdea26a706c --- /dev/null +++ b/firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptAction.swift @@ -0,0 +1,31 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +import Foundation +import Redux + +class MicrosurveyPromptAction: Action { } + +class MicrosurveyPromptMiddlewareAction: Action { + let microsurveyState: MicrosurveyPromptState? + + init(microsurveyState: MicrosurveyPromptState? = nil, + windowUUID: UUID, + actionType: ActionType) { + self.microsurveyState = microsurveyState + super.init(windowUUID: windowUUID, actionType: actionType) + } +} + +enum MicrosurveyPromptActionType: ActionType { + case showPrompt + case closePrompt + case continueToSurvey +} + +enum MicrosurveyPromptMiddlewareActionType: ActionType { + case initialize(MicrosurveyModel) + case dismissPrompt + case openSurvey +} diff --git a/firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptMiddleware.swift b/firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptMiddleware.swift new file mode 100644 index 0000000000000..776b2a3c31bf8 --- /dev/null +++ b/firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptMiddleware.swift @@ -0,0 +1,58 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +import Foundation +import Redux +import Shared + +class MicrosurveyPromptMiddleware { + lazy var microsurveyProvider: Middleware = { state, action in + let windowUUID = action.windowUUID + switch action.actionType { + case MicrosurveyPromptActionType.showPrompt: + self.initializeMicrosurvey(windowUUID: windowUUID) + case MicrosurveyPromptActionType.closePrompt: + self.dismissPrompt(windowUUID: windowUUID) + case MicrosurveyPromptActionType.continueToSurvey: + self.openSurvey(windowUUID: windowUUID) + default: + break + } + } + + private func initializeMicrosurvey(windowUUID: WindowUUID) { + let newAction = MicrosurveyPromptMiddlewareAction( + windowUUID: windowUUID, + actionType: MicrosurveyPromptMiddlewareActionType.initialize(MicrosurveyModel()) + ) + store.dispatch(newAction) + } + + private func dismissPrompt(windowUUID: WindowUUID) { + let newAction = MicrosurveyPromptMiddlewareAction( + windowUUID: windowUUID, + actionType: MicrosurveyPromptMiddlewareActionType.dismissPrompt + ) + store.dispatch(newAction) + } + + private func openSurvey(windowUUID: WindowUUID) { + let newAction = MicrosurveyPromptMiddlewareAction( + windowUUID: windowUUID, + actionType: MicrosurveyPromptMiddlewareActionType.openSurvey + ) + store.dispatch(newAction) + } +} + +struct MicrosurveyModel: Equatable { + // TODO: FXIOS-8990 - Mobile Messaging Structure + // Title + button text can come from mobile messaging; but has a hardcoded string as fallback + let title = String( + format: .Microsurvey.Prompt.TitleLabel, + AppName.shortName.rawValue + ) + let button: String = .Microsurvey.Prompt.TakeSurveyButton + let a11yLabel: String = .Microsurvey.Prompt.CloseButtonAccessibilityLabel +} diff --git a/firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptState.swift b/firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptState.swift new file mode 100644 index 0000000000000..0650bffd76c8c --- /dev/null +++ b/firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptState.swift @@ -0,0 +1,66 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +import Foundation +import Redux +import Shared + +struct MicrosurveyPromptState: StateType, Equatable { + var windowUUID: WindowUUID + var showPrompt: Bool + var showSurvey: Bool + var model: MicrosurveyModel + + init(windowUUID: WindowUUID) { + self.init(windowUUID: windowUUID, + showPrompt: false, + showSurvey: false, + model: MicrosurveyModel()) + } + + init(windowUUID: WindowUUID, + showPrompt: Bool, + showSurvey: Bool, + model: MicrosurveyModel) { + self.windowUUID = windowUUID + self.showPrompt = showPrompt + self.showSurvey = showSurvey + self.model = model + } + + static let reducer: Reducer = { state, action in + // TODO: FXIOS-9068 Need to test this experience with multiwindow + guard let action = action as? MicrosurveyPromptMiddlewareAction else { return state } + switch action.actionType { + case MicrosurveyPromptMiddlewareActionType.initialize(let model): + return MicrosurveyPromptState( + windowUUID: state.windowUUID, + showPrompt: true, + showSurvey: state.showSurvey, + model: model + ) + case MicrosurveyPromptMiddlewareActionType.dismissPrompt: + return MicrosurveyPromptState( + windowUUID: state.windowUUID, + showPrompt: false, + showSurvey: state.showSurvey, + model: state.model + ) + case MicrosurveyPromptMiddlewareActionType.openSurvey: + return MicrosurveyPromptState( + windowUUID: state.windowUUID, + showPrompt: state.showPrompt, + showSurvey: true, + model: state.model + ) + default: + return MicrosurveyPromptState( + windowUUID: state.windowUUID, + showPrompt: false, + showSurvey: false, + model: MicrosurveyModel() + ) + } + } +} diff --git a/firefox-ios/Client/Redux/GlobalState/AppState.swift b/firefox-ios/Client/Redux/GlobalState/AppState.swift index 7bb772d67730d..e9bf69a897670 100644 --- a/firefox-ios/Client/Redux/GlobalState/AppState.swift +++ b/firefox-ios/Client/Redux/GlobalState/AppState.swift @@ -69,5 +69,6 @@ let store = Store(state: AppState(), ThemeManagerMiddleware().themeManagerProvider, TabManagerMiddleware().tabsPanelProvider, RemoteTabsPanelMiddleware().remoteTabsPanelProvider, - ToolbarMiddleware().toolbarProvider + ToolbarMiddleware().toolbarProvider, + MicrosurveyPromptMiddleware().microsurveyProvider ]) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Microsurvey/MicrosurveyStateTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Microsurvey/MicrosurveyStateTests.swift new file mode 100644 index 0000000000000..ad3b000a1a6c5 --- /dev/null +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Microsurvey/MicrosurveyStateTests.swift @@ -0,0 +1,77 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +import Common +import Redux +import Storage +import Shared +import XCTest + +@testable import Client + +final class MicrosurveyStateTests: XCTestCase { + override func setUp() { + super.setUp() + DependencyHelperMock().bootstrapDependencies() + } + + override func tearDown() { + super.tearDown() + DependencyHelperMock().reset() + } + + func testShowPromptAction() { + let initialState = createSubject() + let reducer = microsurveyReducer() + + XCTAssertEqual(initialState.showPrompt, false) + + let action = getAction(for: .initialize(MicrosurveyModel())) + let newState = reducer(initialState, action) + + XCTAssertEqual(newState.showPrompt, true) + } + + func testDismissPromptAction() { + let initialState = MicrosurveyPromptState( + windowUUID: .XCTestDefaultUUID, + showPrompt: true, + showSurvey: false, + model: MicrosurveyModel() + ) + let reducer = microsurveyReducer() + + XCTAssertEqual(initialState.showPrompt, true) + + let action = getAction(for: .dismissPrompt) + let newState = reducer(initialState, action) + + XCTAssertEqual(newState.showPrompt, false) + } + + func testShowSurveyAction() { + let initialState = createSubject() + let reducer = microsurveyReducer() + + XCTAssertEqual(initialState.showSurvey, false) + + let action = getAction(for: .openSurvey) + let newState = reducer(initialState, action) + + XCTAssertEqual(newState.showSurvey, true) + } + + // MARK: - Private + private func createSubject() -> MicrosurveyPromptState { + return MicrosurveyPromptState(windowUUID: .XCTestDefaultUUID) + } + + private func microsurveyReducer() -> Reducer { + return MicrosurveyPromptState.reducer + } + + private func getAction(for actionType: MicrosurveyPromptMiddlewareActionType) -> MicrosurveyPromptMiddlewareAction { + return MicrosurveyPromptMiddlewareAction(windowUUID: .XCTestDefaultUUID, actionType: actionType) + } +} diff --git a/firefox-ios/nimbus-features/microsurveyFeature.yaml b/firefox-ios/nimbus-features/microsurveyFeature.yaml index b955efee9a173..42470c8873d5d 100644 --- a/firefox-ios/nimbus-features/microsurveyFeature.yaml +++ b/firefox-ios/nimbus-features/microsurveyFeature.yaml @@ -16,4 +16,4 @@ features: enabled: false - channel: developer value: - enabled: false + enabled: true