diff --git a/firefox-ios/Client.xcodeproj/project.pbxproj b/firefox-ios/Client.xcodeproj/project.pbxproj index a290b959ea9f..4dedf5fc8add 100644 --- a/firefox-ios/Client.xcodeproj/project.pbxproj +++ b/firefox-ios/Client.xcodeproj/project.pbxproj @@ -864,6 +864,8 @@ 8A8BAE122B2107E400D774EB /* GCDWebServers in Frameworks */ = {isa = PBXBuildFile; productRef = 8A8BAE112B2107E400D774EB /* GCDWebServers */; }; 8A8BAE142B21110000D774EB /* GCDWebServers in Frameworks */ = {isa = PBXBuildFile; productRef = 8A8BAE132B21110000D774EB /* GCDWebServers */; }; 8A8BAE162B2119E600D774EB /* InternalURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8BAE152B2119E600D774EB /* InternalURL.swift */; }; + 8A8D277B2CBFFD710076AD3A /* BrowserNavigationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8D277A2CBFFD710076AD3A /* BrowserNavigationType.swift */; }; + 8A8D277D2CC000BE0076AD3A /* NavigationBrowserAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8D277C2CC000BE0076AD3A /* NavigationBrowserAction.swift */; }; 8A8DDEBF276259A900E7B97A /* RatingPromptManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8DDEBE276259A900E7B97A /* RatingPromptManager.swift */; }; 8A93080927BFE88F0052167D /* PhotonActionSheetContainerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A93080827BFE88F0052167D /* PhotonActionSheetContainerCell.swift */; }; 8A93080B27C01AD60052167D /* SingleActionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A93080A27C01AD60052167D /* SingleActionViewModel.swift */; }; @@ -7141,6 +7143,8 @@ 8A880C432C63CFE200B77F23 /* MockLoginViewModelDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLoginViewModelDelegate.swift; sourceTree = ""; }; 8A8917682B57283B008B01EA /* LegacyHomepageHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyHomepageHeaderCell.swift; sourceTree = ""; }; 8A8BAE152B2119E600D774EB /* InternalURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalURL.swift; sourceTree = ""; }; + 8A8D277A2CBFFD710076AD3A /* BrowserNavigationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserNavigationType.swift; sourceTree = ""; }; + 8A8D277C2CC000BE0076AD3A /* NavigationBrowserAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBrowserAction.swift; sourceTree = ""; }; 8A8DDEBE276259A900E7B97A /* RatingPromptManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatingPromptManager.swift; sourceTree = ""; }; 8A93080827BFE88F0052167D /* PhotonActionSheetContainerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotonActionSheetContainerCell.swift; sourceTree = ""; }; 8A93080A27C01AD60052167D /* SingleActionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleActionViewModel.swift; sourceTree = ""; }; @@ -10495,6 +10499,7 @@ children = ( 5A2918CA2B522338002B197E /* GeneralBrowserAction.swift */, 7ADC1D182C27D35B003ED924 /* ActionProviderBuilder.swift */, + 8A8D277C2CC000BE0076AD3A /* NavigationBrowserAction.swift */, ); path = Actions; sourceTree = ""; @@ -10738,6 +10743,7 @@ 81CAE4DA2B1A2C220040C78A /* BrowserViewControllerState.swift */, 81122E202B221AC0003DD9F8 /* SearchScreenState.swift */, 5A1947142B8FA9E0009C7A6C /* BrowserViewType.swift */, + 8A8D277A2CBFFD710076AD3A /* BrowserNavigationType.swift */, ); path = State; sourceTree = ""; @@ -15690,6 +15696,7 @@ 1DFE57FF27BAE3150025DE58 /* HomepageSectionType.swift in Sources */, C2D1A10D2A66C70000205DCC /* BookmarksCoordinator.swift in Sources */, 8C46E1B72B2209F000F56521 /* FakespotAdsEvent.swift in Sources */, + 8A8D277D2CC000BE0076AD3A /* NavigationBrowserAction.swift in Sources */, 396E38F11EE0C8EC00CC180F /* FxAPushMessageHandler.swift in Sources */, 8A76B01629F6EB3900A82607 /* ScreenshotService.swift in Sources */, 8C1953322B85EAB500761B20 /* AutofillHeaderView.swift in Sources */, @@ -15900,6 +15907,7 @@ 43D00493296FC48F00CB0F31 /* CreditCardSettingsEmptyView.swift in Sources */, CEFA977E1FAA6B490016F365 /* SyncContentSettingsViewController.swift in Sources */, C8CD80DC2A1E8C970097C3AE /* OnboardingTelemetryUtility.swift in Sources */, + 8A8D277B2CBFFD710076AD3A /* BrowserNavigationType.swift in Sources */, 96C11E9B2864C2DD00840E7C /* DependencyHelper.swift in Sources */, 8CE1E4382B8C76C80026530B /* LoginListViewModel.swift in Sources */, 0B8BF3722CA2DA4600E9812D /* EditBookmarkViewController.swift in Sources */, diff --git a/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift b/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift index 6617e1c6c641..8c082605bc38 100644 --- a/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift +++ b/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift @@ -22,7 +22,6 @@ class BrowserCoordinator: BaseCoordinator, LibraryCoordinatorDelegate, EnhancedTrackingProtectionCoordinatorDelegate, FakespotCoordinatorDelegate, - HomepageCoordinatorDelegate, ParentCoordinatorDelegate, TabManagerDelegate, TabTrayCoordinatorDelegate, @@ -137,7 +136,6 @@ class BrowserCoordinator: BaseCoordinator, func showHomepage() { let homepageController = self.homepageViewController ?? HomepageViewController(windowUUID: windowUUID) - homepageController.parentCoordinator = self guard browserViewController.embedContent(homepageController) else { logger.log("Unable to embed new homepage", level: .debug, category: .coordinator) return @@ -155,8 +153,11 @@ class BrowserCoordinator: BaseCoordinator, self.privateViewController = privateHomepageController } - // MARK: - PrivateHomepageDelegate + func navigateFromHomePanel(to url: URL, visitType: VisitType, isGoogleTopSite: Bool) { + browserViewController.homePanel(didSelectURL: url, visitType: visitType, isGoogleTopSite: isGoogleTopSite) + } + // MARK: - PrivateHomepageDelegate func homePanelDidRequestToOpenInNewTab(with url: URL, isPrivate: Bool, selectNewTab: Bool) { browserViewController.homePanelDidRequestToOpenInNewTab( url, diff --git a/firefox-ios/Client/Coordinators/Browser/BrowserNavigationHandler.swift b/firefox-ios/Client/Coordinators/Browser/BrowserNavigationHandler.swift index 7e6a2c593778..081f8b780ae9 100644 --- a/firefox-ios/Client/Coordinators/Browser/BrowserNavigationHandler.swift +++ b/firefox-ios/Client/Coordinators/Browser/BrowserNavigationHandler.swift @@ -7,6 +7,7 @@ import Storage import WebKit import struct MozillaAppServices.CreditCard +import enum MozillaAppServices.VisitType protocol BrowserNavigationHandler: AnyObject, QRCodeNavigationHandler { /// Asks to show a settings page, can be a general settings page or a child page @@ -99,6 +100,9 @@ protocol BrowserNavigationHandler: AnyObject, QRCodeNavigationHandler { /// Shows the toolbar's search engine selection bottom sheet (iPhone) or popup (iPad) func showSearchEngineSelection(forSourceView sourceView: UIView) + + /// Navigates from home page to a new link + func navigateFromHomePanel(to url: URL, visitType: VisitType, isGoogleTopSite: Bool) } extension BrowserNavigationHandler { diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/NavigationBrowserAction.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/NavigationBrowserAction.swift new file mode 100644 index 000000000000..308c7b15d431 --- /dev/null +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/NavigationBrowserAction.swift @@ -0,0 +1,25 @@ +// 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 Foundation +import Redux + +/// Actions that are related to navigation from the user perspective +class NavigationBrowserAction: Action { + let url: URL? + init(url: URL? = nil, + windowUUID: WindowUUID, + actionType: ActionType) { + self.url = url + super.init(windowUUID: windowUUID, + actionType: actionType) + } +} + +enum NavigationBrowserActionType: ActionType { + case tapOnCustomizeHomepage + case tapOnCell + case tapOnLink +} diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserNavigationType.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserNavigationType.swift new file mode 100644 index 000000000000..4efb06c323db --- /dev/null +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserNavigationType.swift @@ -0,0 +1,28 @@ +// 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 + +/// View types that the browser coordinator can navigate to +enum BrowserNavigationDestination: Equatable { + // Native views + case customizeHomepage + + // Webpage views + case link +} + +/// This type exists as a field on the BrowserViewControllerState +struct NavigationDestination: Equatable { + let destination: BrowserNavigationDestination + let url: URL? + + init( + _ destination: BrowserNavigationDestination, + url: URL? = nil + ) { + self.destination = destination + self.url = url + } +} diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift index 91775602b8a2..4bd3d0d26565 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift @@ -49,6 +49,7 @@ struct BrowserViewControllerState: ScreenState, Equatable { var buttonTapped: UIButton? var frame: WKFrameInfo? var microsurveyState: MicrosurveyPromptState + var navigationDestination: NavigationDestination? init(appState: AppState, uuid: WindowUUID) { guard let bvcState = store.state.screenState( @@ -72,7 +73,8 @@ struct BrowserViewControllerState: ScreenState, Equatable { displayView: bvcState.displayView, buttonTapped: bvcState.buttonTapped, frame: bvcState.frame, - microsurveyState: bvcState.microsurveyState) + microsurveyState: bvcState.microsurveyState, + navigationDestination: bvcState.navigationDestination) } init(windowUUID: WindowUUID) { @@ -87,7 +89,8 @@ struct BrowserViewControllerState: ScreenState, Equatable { navigateTo: nil, displayView: nil, buttonTapped: nil, - microsurveyState: MicrosurveyPromptState(windowUUID: windowUUID)) + microsurveyState: MicrosurveyPromptState(windowUUID: windowUUID), + navigationDestination: nil) } init( @@ -103,7 +106,8 @@ struct BrowserViewControllerState: ScreenState, Equatable { displayView: DisplayType? = nil, buttonTapped: UIButton? = nil, frame: WKFrameInfo? = nil, - microsurveyState: MicrosurveyPromptState + microsurveyState: MicrosurveyPromptState, + navigationDestination: NavigationDestination? = nil ) { self.searchScreenState = searchScreenState self.showDataClearanceFlow = showDataClearanceFlow @@ -118,6 +122,7 @@ struct BrowserViewControllerState: ScreenState, Equatable { self.buttonTapped = buttonTapped self.frame = frame self.microsurveyState = microsurveyState + self.navigationDestination = navigationDestination } static let reducer: Reducer = { state, action in @@ -130,6 +135,8 @@ struct BrowserViewControllerState: ScreenState, Equatable { return BrowserViewControllerState.reduceStateForMicrosurveyAction(action: action, state: state) } else if let action = action as? GeneralBrowserAction { return BrowserViewControllerState.reduceStateForGeneralBrowserAction(action: action, state: state) + } else if let action = action as? NavigationBrowserAction { + return BrowserViewControllerState.reduceStateForNavigationBrowserAction(action: action, state: state) } else { return BrowserViewControllerState( searchScreenState: state.searchScreenState, @@ -138,7 +145,50 @@ struct BrowserViewControllerState: ScreenState, Equatable { windowUUID: state.windowUUID, reloadWebView: false, browserViewType: state.browserViewType, - microsurveyState: MicrosurveyPromptState.reducer(state.microsurveyState, action)) + microsurveyState: MicrosurveyPromptState.reducer(state.microsurveyState, action), + navigationDestination: nil) + } + } + + // MARK: - Navigation Browser Action + static func reduceStateForNavigationBrowserAction( + action: NavigationBrowserAction, + state: BrowserViewControllerState + ) -> BrowserViewControllerState { + switch action.actionType { + case NavigationBrowserActionType.tapOnCustomizeHomepage: + return BrowserViewControllerState( + searchScreenState: state.searchScreenState, + showDataClearanceFlow: state.showDataClearanceFlow, + fakespotState: FakespotState.reducer(state.fakespotState, action), + windowUUID: state.windowUUID, + browserViewType: state.browserViewType, + microsurveyState: MicrosurveyPromptState.reducer(state.microsurveyState, action), + navigationDestination: NavigationDestination(.customizeHomepage) + ) + + case NavigationBrowserActionType.tapOnCell, + NavigationBrowserActionType.tapOnLink: + return BrowserViewControllerState( + searchScreenState: state.searchScreenState, + showDataClearanceFlow: state.showDataClearanceFlow, + fakespotState: FakespotState.reducer(state.fakespotState, action), + windowUUID: state.windowUUID, + browserViewType: state.browserViewType, + microsurveyState: MicrosurveyPromptState.reducer(state.microsurveyState, action), + navigationDestination: NavigationDestination(.link, url: action.url) + ) + + default: + return BrowserViewControllerState( + searchScreenState: state.searchScreenState, + showDataClearanceFlow: state.showDataClearanceFlow, + fakespotState: FakespotState.reducer(state.fakespotState, action), + windowUUID: state.windowUUID, + browserViewType: state.browserViewType, + microsurveyState: MicrosurveyPromptState.reducer(state.microsurveyState, action), + navigationDestination: nil + ) } } diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift index 2cfa26e55ee6..523558ec8c8e 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift @@ -2064,6 +2064,9 @@ class BrowserViewController: UIViewController, handleNavigationActions(for: state) case _ where state.displayView != nil: handleDisplayActions(for: state) + case _ where state.navigationDestination != nil: + guard let destination = state.navigationDestination else { return } + handleNavigation(to: destination) default: break } } @@ -2079,6 +2082,20 @@ class BrowserViewController: UIViewController, store.dispatch(action) } + private func handleNavigation(to type: NavigationDestination) { + switch type.destination { + case .customizeHomepage: + navigationHandler?.show(settings: .homePage) + case .link: + guard let url = type.url else { + logger.log("url should not be nil when navigating for a link type", level: .warning, category: .coordinator) + return + } + // TODO: FXIOS-10165 - Pass in the proper values based on top sites and other homepage links + navigationHandler?.navigateFromHomePanel(to: url, visitType: .link, isGoogleTopSite: false) + } + } + private func handleDisplayActions(for state: BrowserViewControllerState) { guard let displayState = state.displayView else { return } diff --git a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageDiffableDataSource.swift b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageDiffableDataSource.swift index 2ac6207a47f1..b1bb4fc1fbda 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageDiffableDataSource.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageDiffableDataSource.swift @@ -21,7 +21,7 @@ final class HomepageDiffableDataSource: enum HomeItem: Hashable { case header case pocket(PocketStoryState) - case pocketDiscover(String) + case pocketDiscover case customizeHomepage static var cellTypes: [ReusableCell.Type] { @@ -43,10 +43,7 @@ final class HomepageDiffableDataSource: let stories: [HomeItem] = state.pocketState.pocketData.compactMap { .pocket($0) } snapshot.appendItems(stories, toSection: .pocket) - let discoverItem = state.pocketState.pocketDiscoverTitle - if !discoverItem.isEmpty { - snapshot.appendItems([.pocketDiscover(discoverItem)], toSection: .pocket) - } + snapshot.appendItems([.pocketDiscover], toSection: .pocket) snapshot.appendItems([], toSection: .pocket) snapshot.appendItems([.customizeHomepage], toSection: .customizeHomepage) diff --git a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageViewController.swift b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageViewController.swift index 601bbeda0840..d25d499b0560 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageViewController.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageViewController.swift @@ -6,10 +6,6 @@ import Foundation import Common import Redux -protocol HomepageCoordinatorDelegate: AnyObject { - func showSettings(at destination: Route.SettingsSection) -} - final class HomepageViewController: UIViewController, UICollectionViewDelegate, ContentContainable, @@ -31,9 +27,6 @@ final class HomepageViewController: UIViewController, let windowUUID: WindowUUID var currentWindowUUID: UUID? { return windowUUID } - // MARK: Navigation variables - weak var parentCoordinator: HomepageCoordinatorDelegate? - // MARK: - Private variables private var collectionView: UICollectionView? private var dataSource: HomepageDiffableDataSource? @@ -113,10 +106,6 @@ final class HomepageViewController: UIViewController, func newState(state: HomepageState) { homepageState = state dataSource?.applyInitialSnapshot(state: state) - - if homepageState.navigateTo == .customizeHomepage { - parentCoordinator?.showSettings(at: .homePage) - } } func unsubscribeFromRedux() { @@ -235,7 +224,7 @@ final class HomepageViewController: UIViewController, pocketCell.configure(story: story, theme: currentTheme) return pocketCell - case .pocketDiscover(let title): + case .pocketDiscover: guard let pocketDiscoverCell = collectionView?.dequeueReusableCell( cellType: PocketDiscoverCell.self, for: indexPath @@ -243,7 +232,7 @@ final class HomepageViewController: UIViewController, return UICollectionViewCell() } - pocketDiscoverCell.configure(text: title, theme: currentTheme) + pocketDiscoverCell.configure(text: homepageState.pocketState.pocketDiscoverItem.title, theme: currentTheme) return pocketDiscoverCell case .customizeHomepage: @@ -290,7 +279,7 @@ final class HomepageViewController: UIViewController, for: indexPath) else { return UICollectionReusableView() } footerView.onTapLearnMore = { - // TODO: FXIOS-10164: Navigation for Pocket section + self.navigateToPocketLearnMore() } footerView.applyTheme(theme: currentTheme) return footerView @@ -327,21 +316,50 @@ final class HomepageViewController: UIViewController, private func navigateToHomepageSettings() { store.dispatch( - HomepageAction( - windowUUID: windowUUID, - actionType: HomepageActionType.tappedOnCustomizeHomepage + NavigationBrowserAction( + windowUUID: self.windowUUID, + actionType: NavigationBrowserActionType.tapOnCustomizeHomepage + ) + ) + } + + private func navigateToPocketLearnMore() { + store.dispatch( + NavigationBrowserAction( + url: homepageState.pocketState.footerURL, + windowUUID: self.windowUUID, + actionType: NavigationBrowserActionType.tapOnLink ) ) } // MARK: - UICollectionViewDelegate func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - // TODO: FXIOS-10162 - Dummy trigger to update with proper triggers - - guard let section = HomepageSection(rawValue: indexPath.section) else { + guard let item = dataSource?.itemIdentifier(for: indexPath) else { + self.logger.log( + "Item selected at \(indexPath) but does not navigate anywhere", + level: .debug, + category: .homepage + ) return } - switch section { + switch item { + case .pocket(let story): + store.dispatch( + NavigationBrowserAction( + url: story.url, + windowUUID: self.windowUUID, + actionType: NavigationBrowserActionType.tapOnCell + ) + ) + case .pocketDiscover: + store.dispatch( + NavigationBrowserAction( + url: homepageState.pocketState.pocketDiscoverItem.url, + windowUUID: self.windowUUID, + actionType: NavigationBrowserActionType.tapOnCell + ) + ) default: return } diff --git a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Pocket/PocketState.swift b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Pocket/PocketState.swift index 6bdf64ad0ef6..b44a31174b2b 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Pocket/PocketState.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Pocket/PocketState.swift @@ -5,6 +5,7 @@ import Common import Foundation import Redux +import Shared struct SectionHeaderState: Equatable { var sectionHeaderTitle: String @@ -14,34 +15,41 @@ struct SectionHeaderState: Equatable { var sectionButtonA11yIdentifier: String? } +struct PocketDiscoverState: Equatable { + var title: String + var url: URL? +} + /// State for the pocket section that is used in the homepage struct PocketState: StateType, Equatable { var windowUUID: WindowUUID var pocketData: [PocketStoryState] - var pocketDiscoverTitle: String + let pocketDiscoverItem = PocketDiscoverState( + title: .FirefoxHomepage.Pocket.DiscoverMore, + url: PocketProvider.MoreStoriesURL + ) // TODO: FXIOS-10312 Update color for section header when wallpaper is configured with redux - var sectionHeaderState = SectionHeaderState( + let sectionHeaderState = SectionHeaderState( sectionHeaderTitle: .FirefoxHomepage.Pocket.SectionTitle, sectionTitleA11yIdentifier: AccessibilityIdentifiers.FirefoxHomepage.SectionTitles.pocket, isSectionHeaderButtonHidden: true, sectionHeaderColor: .systemRed) + let footerURL = SupportUtils.URLForPocketLearnMore + init(windowUUID: WindowUUID) { self.init( windowUUID: windowUUID, - pocketData: [], - pocketDiscoverTitle: "" + pocketData: [] ) } private init( windowUUID: WindowUUID, - pocketData: [PocketStoryState], - pocketDiscoverTitle: String + pocketData: [PocketStoryState] ) { self.windowUUID = windowUUID self.pocketData = pocketData - self.pocketDiscoverTitle = pocketDiscoverTitle } static let reducer: Reducer = { state, action in @@ -49,8 +57,7 @@ struct PocketState: StateType, Equatable { else { return PocketState( windowUUID: state.windowUUID, - pocketData: state.pocketData, - pocketDiscoverTitle: state.pocketDiscoverTitle + pocketData: state.pocketData ) } @@ -61,21 +68,18 @@ struct PocketState: StateType, Equatable { else { return PocketState( windowUUID: state.windowUUID, - pocketData: state.pocketData, - pocketDiscoverTitle: state.pocketDiscoverTitle + pocketData: state.pocketData ) } return PocketState( windowUUID: state.windowUUID, - pocketData: stories, - pocketDiscoverTitle: .FirefoxHomepage.Pocket.DiscoverMore + pocketData: stories ) default: return PocketState( windowUUID: state.windowUUID, - pocketData: state.pocketData, - pocketDiscoverTitle: state.pocketDiscoverTitle + pocketData: state.pocketData ) } } diff --git a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Pocket/PocketStoryState.swift b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Pocket/PocketStoryState.swift index 65a06782036e..8a71b0d1bcdb 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Pocket/PocketStoryState.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Pocket/PocketStoryState.swift @@ -13,6 +13,7 @@ class PocketStoryState: Equatable, Hashable { } var title: String { story.title } + var url: URL? { story.url } var imageURL: URL { story.imageURL } var description: String { if let sponsor = story.sponsor { diff --git a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Redux/HomepageAction.swift b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Redux/HomepageAction.swift index b63b86c4a3e0..74948670c5fe 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Redux/HomepageAction.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Redux/HomepageAction.swift @@ -6,8 +6,6 @@ import Common import Redux final class HomepageAction: Action { - var navigationDestination: HomepageState.NavigationDestination? - override init(windowUUID: WindowUUID, actionType: any ActionType) { super.init(windowUUID: windowUUID, actionType: actionType) } @@ -15,5 +13,4 @@ final class HomepageAction: Action { enum HomepageActionType: ActionType { case initialize - case tappedOnCustomizeHomepage } diff --git a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Redux/HomepageState.swift b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Redux/HomepageState.swift index a70e4bc175be..dcff4045ad39 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Redux/HomepageState.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Redux/HomepageState.swift @@ -6,14 +6,9 @@ import Common import Redux struct HomepageState: ScreenState, Equatable { - enum NavigationDestination { - case customizeHomepage - } - var windowUUID: WindowUUID var headerState: HeaderState var pocketState: PocketState - var navigateTo: NavigationDestination? init(appState: AppState, uuid: WindowUUID) { guard let homepageState = store.state.screenState( @@ -28,8 +23,7 @@ struct HomepageState: ScreenState, Equatable { self.init( windowUUID: homepageState.windowUUID, headerState: homepageState.headerState, - pocketState: homepageState.pocketState, - navigateTo: homepageState.navigateTo + pocketState: homepageState.pocketState ) } @@ -44,13 +38,11 @@ struct HomepageState: ScreenState, Equatable { private init( windowUUID: WindowUUID, headerState: HeaderState, - pocketState: PocketState, - navigateTo: NavigationDestination? = nil + pocketState: PocketState ) { self.windowUUID = windowUUID self.headerState = headerState self.pocketState = pocketState - self.navigateTo = navigateTo } static let reducer: Reducer = { state, action in @@ -70,13 +62,6 @@ struct HomepageState: ScreenState, Equatable { headerState: HeaderState.reducer(state.headerState, action), pocketState: PocketState.reducer(state.pocketState, action) ) - case HomepageActionType.tappedOnCustomizeHomepage: - return HomepageState( - windowUUID: state.windowUUID, - headerState: HeaderState.reducer(state.headerState, action), - pocketState: PocketState.reducer(state.pocketState, action), - navigateTo: .customizeHomepage - ) default: return HomepageState( windowUUID: state.windowUUID, diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewControllerStateTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewControllerStateTests.swift index 93345f4a6794..b64c57501eb1 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewControllerStateTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewControllerStateTests.swift @@ -101,6 +101,48 @@ final class BrowserViewControllerStateTests: XCTestCase { XCTAssertEqual(newState.navigateTo, .reload) } + // MARK: - Navigation Browser Action + func test_customizeHomepage_navigationBrowserAction_returnsExpectedState() { + let initialState = createSubject() + let reducer = browserViewControllerReducer() + + XCTAssertNil(initialState.navigationDestination) + + let action = getNavigationBrowserAction(for: .tapOnCustomizeHomepage) + let newState = reducer(initialState, action) + + XCTAssertEqual(newState.navigationDestination?.destination, .customizeHomepage) + XCTAssertEqual(newState.navigationDestination?.url, nil) + } + + func test_tapOnCell_navigationBrowserAction_returnsExpectedState() throws { + let initialState = createSubject() + let reducer = browserViewControllerReducer() + + XCTAssertNil(initialState.navigationDestination) + + let url = try XCTUnwrap(URL(string: "www.example.com")) + let action = getNavigationBrowserAction(for: .tapOnCell, url: url) + let newState = reducer(initialState, action) + + XCTAssertEqual(newState.navigationDestination?.destination, .link) + XCTAssertEqual(newState.navigationDestination?.url?.absoluteString, "www.example.com") + } + + func test_tapOnLink_navigationBrowserAction_returnsExpectedState() throws { + let initialState = createSubject() + let reducer = browserViewControllerReducer() + + XCTAssertNil(initialState.navigationDestination) + + let url = try XCTUnwrap(URL(string: "www.example.com")) + let action = getNavigationBrowserAction(for: .tapOnLink, url: url) + let newState = reducer(initialState, action) + + XCTAssertEqual(newState.navigationDestination?.destination, .link) + XCTAssertEqual(newState.navigationDestination?.url?.absoluteString, "www.example.com") + } + // MARK: - Private private func createSubject() -> BrowserViewControllerState { return BrowserViewControllerState(windowUUID: .XCTestDefaultUUID) @@ -114,6 +156,17 @@ final class BrowserViewControllerStateTests: XCTestCase { return GeneralBrowserAction(windowUUID: .XCTestDefaultUUID, actionType: actionType) } + private func getNavigationBrowserAction( + for actionType: NavigationBrowserActionType, + url: URL? = nil + ) -> NavigationBrowserAction { + return NavigationBrowserAction( + url: url, + windowUUID: .XCTestDefaultUUID, + actionType: actionType + ) + } + private func getPrivateModeAction(isPrivate: Bool, for actionType: PrivateModeActionType) -> PrivateModeAction { return PrivateModeAction(isPrivate: isPrivate, windowUUID: .XCTestDefaultUUID, actionType: actionType) } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Mocks/MockBrowserCoordinator.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Mocks/MockBrowserCoordinator.swift index 175f1d41cb9a..3c24e8ac33a8 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Mocks/MockBrowserCoordinator.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Mocks/MockBrowserCoordinator.swift @@ -9,6 +9,7 @@ import WebKit @testable import Client import struct MozillaAppServices.CreditCard +import enum MozillaAppServices.VisitType class MockBrowserCoordinator: BrowserNavigationHandler, ParentCoordinatorDelegate { var showSettingsCalled = 0 @@ -33,6 +34,7 @@ class MockBrowserCoordinator: BrowserNavigationHandler, ParentCoordinatorDelegat var showMicrosurveyCalled = 0 var showMainMenuCalled = 0 var showPasswordGeneratorCalled = 0 + var navigateFromHomePanelCalled = 0 func show(settings: Client.Route.SettingsSection, onDismiss: (() -> Void)?) { showSettingsCalled += 1 @@ -117,6 +119,10 @@ class MockBrowserCoordinator: BrowserNavigationHandler, ParentCoordinatorDelegat showSearchEngineSelectionCalled += 1 } + func navigateFromHomePanel(to url: URL, visitType: VisitType, isGoogleTopSite: Bool) { + navigateFromHomePanelCalled += 1 + } + func dismissFakespotModal(animated: Bool) { dismissFakespotModalCalled += 1 } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/HomepageDiffableDataSourceTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/HomepageDiffableDataSourceTests.swift index e8148d90d090..5d2a5b1263ec 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/HomepageDiffableDataSourceTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/HomepageDiffableDataSourceTests.swift @@ -40,7 +40,7 @@ final class HomepageDiffableDataSourceTests: XCTestCase { XCTAssertEqual(snapshot.itemIdentifiers(inSection: .header).count, 1) XCTAssertEqual(snapshot.itemIdentifiers(inSection: .topSites).count, 0) - XCTAssertEqual(snapshot.itemIdentifiers(inSection: .pocket).count, 0) + XCTAssertEqual(snapshot.itemIdentifiers(inSection: .pocket).count, 1) XCTAssertEqual(snapshot.itemIdentifiers(inSection: .customizeHomepage).count, 1) } } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/Redux/HomepageStateTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/Redux/HomepageStateTests.swift index 051d9f036988..6e5efb39a552 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/Redux/HomepageStateTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/Redux/HomepageStateTests.swift @@ -35,22 +35,6 @@ final class HomepageStateTests: XCTestCase { XCTAssertTrue(newState.headerState.showPrivateModeToggle) } - func test_tappedOnCustomizeHomepage_returnsExpectedState() { - let initialState = createSubject() - let reducer = homepageReducer() - - let newState = reducer( - initialState, - HomepageAction( - windowUUID: .XCTestDefaultUUID, - actionType: HomepageActionType.tappedOnCustomizeHomepage - ) - ) - - XCTAssertEqual(newState.windowUUID, .XCTestDefaultUUID) - XCTAssertEqual(newState.navigateTo, .customizeHomepage) - } - // MARK: - Private private func createSubject() -> HomepageState { return HomepageState(windowUUID: .XCTestDefaultUUID) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/Redux/PocketStateTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/Redux/PocketStateTests.swift index ba9e10aa96b8..dafc3529d1cd 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/Redux/PocketStateTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/Redux/PocketStateTests.swift @@ -13,7 +13,8 @@ final class PocketStateTests: XCTestCase { XCTAssertEqual(initialState.windowUUID, .XCTestDefaultUUID) XCTAssertEqual(initialState.pocketData, []) - XCTAssertEqual(initialState.pocketDiscoverTitle, "") + XCTAssertEqual(initialState.pocketDiscoverItem.title, .FirefoxHomepage.Pocket.DiscoverMore) + XCTAssertEqual(initialState.pocketDiscoverItem.url, PocketProvider.MoreStoriesURL) } func test_retrievedUpdatedStoriesAction_returnsExpectedState() throws { @@ -42,7 +43,8 @@ final class PocketStateTests: XCTestCase { XCTAssertEqual(newState.windowUUID, .XCTestDefaultUUID) XCTAssertEqual(newState.pocketData.count, 3) XCTAssertEqual(newState.pocketData.compactMap { $0.title }, ["feed1", "feed2", "feed3"]) - XCTAssertEqual(newState.pocketDiscoverTitle, .FirefoxHomepage.Pocket.DiscoverMore) + XCTAssertEqual(initialState.pocketDiscoverItem.title, .FirefoxHomepage.Pocket.DiscoverMore) + XCTAssertEqual(initialState.pocketDiscoverItem.url, PocketProvider.MoreStoriesURL) } // MARK: - Private