diff --git a/firefox-ios/Client.xcodeproj/project.pbxproj b/firefox-ios/Client.xcodeproj/project.pbxproj index 1f61630c288d..da451937aed4 100644 --- a/firefox-ios/Client.xcodeproj/project.pbxproj +++ b/firefox-ios/Client.xcodeproj/project.pbxproj @@ -725,6 +725,7 @@ 8A093D7F2A4B3E7D0099ABA5 /* GeneralSettingsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A093D7E2A4B3E7D0099ABA5 /* GeneralSettingsDelegate.swift */; }; 8A093D812A4B58330099ABA5 /* MockSettingsFlowDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A093D802A4B58330099ABA5 /* MockSettingsFlowDelegate.swift */; }; 8A093D832A4B68940099ABA5 /* PrivacySettingsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A093D822A4B68940099ABA5 /* PrivacySettingsDelegate.swift */; }; + 8A09BCC32D22F1BE00CFDF60 /* ContextMenuCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A09BCC22D22F1BE00CFDF60 /* ContextMenuCoordinatorTests.swift */; }; 8A0A1BA02B2200FD00E8706F /* PrivateHomepageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A1B9F2B2200FD00E8706F /* PrivateHomepageViewController.swift */; }; 8A0A1BA32B22030100E8706F /* PrivateMessageCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A1BA22B22030100E8706F /* PrivateMessageCardCell.swift */; }; 8A0D32842A61E1CC007D976D /* StatusBarOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0D32832A61E1CC007D976D /* StatusBarOverlay.swift */; }; @@ -906,6 +907,7 @@ 8A83B74A2A265044002FF9AC /* SettingsCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A83B7492A265044002FF9AC /* SettingsCoordinatorTests.swift */; }; 8A83B74C2A265061002FF9AC /* LibraryCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A83B74B2A265061002FF9AC /* LibraryCoordinatorTests.swift */; }; 8A8482F02BE1602500F9007B /* MicrosurveyPromptStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8482EE2BE15FFE00F9007B /* MicrosurveyPromptStateTests.swift */; }; + 8A855C032D1EE9EF00C1A2F3 /* ContextMenuCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A855C022D1EE9EF00C1A2F3 /* ContextMenuCoordinator.swift */; }; 8A8629E2288096C40096DDB1 /* BookmarksFolderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8629E1288096C40096DDB1 /* BookmarksFolderCell.swift */; }; 8A8629E72880B7330096DDB1 /* LegacyBookmarksPanelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8629E52880B69C0096DDB1 /* LegacyBookmarksPanelTests.swift */; }; 8A86DAD8277298DE00D7BFFF /* ClosedTabsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A86DAD7277298DE00D7BFFF /* ClosedTabsStoreTests.swift */; }; @@ -7517,6 +7519,7 @@ 8A093D7E2A4B3E7D0099ABA5 /* GeneralSettingsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsDelegate.swift; sourceTree = ""; }; 8A093D802A4B58330099ABA5 /* MockSettingsFlowDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSettingsFlowDelegate.swift; sourceTree = ""; }; 8A093D822A4B68940099ABA5 /* PrivacySettingsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettingsDelegate.swift; sourceTree = ""; }; + 8A09BCC22D22F1BE00CFDF60 /* ContextMenuCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuCoordinatorTests.swift; sourceTree = ""; }; 8A0A1B9F2B2200FD00E8706F /* PrivateHomepageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateHomepageViewController.swift; sourceTree = ""; }; 8A0A1BA22B22030100E8706F /* PrivateMessageCardCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateMessageCardCell.swift; sourceTree = ""; }; 8A0D32832A61E1CC007D976D /* StatusBarOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarOverlay.swift; sourceTree = ""; }; @@ -7705,6 +7708,7 @@ 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 /* MicrosurveyPromptStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosurveyPromptStateTests.swift; sourceTree = ""; }; + 8A855C022D1EE9EF00C1A2F3 /* ContextMenuCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuCoordinator.swift; sourceTree = ""; }; 8A8629E1288096C40096DDB1 /* BookmarksFolderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksFolderCell.swift; sourceTree = ""; }; 8A8629E52880B69C0096DDB1 /* LegacyBookmarksPanelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyBookmarksPanelTests.swift; sourceTree = ""; }; 8A86DAD7277298DE00D7BFFF /* ClosedTabsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosedTabsStoreTests.swift; sourceTree = ""; }; @@ -11387,8 +11391,8 @@ 8A00BD852CAB3FC200680AF9 /* Homepage Rebuild */ = { isa = PBXGroup; children = ( + 8A09BCC12D22F1A500CFDF60 /* ContextMenu */, 8AD3AFC42D143F0D00CFC887 /* TopSitesDimensionImplementationTests.swift */, - 8A4B66412D1DC962008C5B64 /* ContextMenuConfigurationTests.swift */, 8A87B4352CC1A8FD003A9239 /* Mock */, 8A87B4302CC1A3BA003A9239 /* PocketManagerTests.swift */, 8A552AC22CB43A7000564C98 /* Redux */, @@ -11410,6 +11414,15 @@ path = Cell; sourceTree = ""; }; + 8A09BCC12D22F1A500CFDF60 /* ContextMenu */ = { + isa = PBXGroup; + children = ( + 8A4B66412D1DC962008C5B64 /* ContextMenuConfigurationTests.swift */, + 8A09BCC22D22F1BE00CFDF60 /* ContextMenuCoordinatorTests.swift */, + ); + path = ContextMenu; + sourceTree = ""; + }; 8A0A1BA12B22010200E8706F /* PrivateHome */ = { isa = PBXGroup; children = ( @@ -11754,6 +11767,7 @@ children = ( 8AC0B1BF2D1D9356004237FD /* ContextMenuConfiguration.swift */, 8A62B15C2CED408B0045F46E /* ContextMenuState.swift */, + 8A855C022D1EE9EF00C1A2F3 /* ContextMenuCoordinator.swift */, ); path = ContextMenu; sourceTree = ""; @@ -16292,6 +16306,7 @@ 8AD40FCF27BADC6B00672675 /* URLTextField.swift in Sources */, 31ADB5DA1E58CEC300E87909 /* ClipboardBarDisplayHandler.swift in Sources */, 8AD1980F27BEB3F100D64B0E /* PhotonActionSheetViewModel.swift in Sources */, + 8A855C032D1EE9EF00C1A2F3 /* ContextMenuCoordinator.swift in Sources */, EB9A179D20E69A7F00B12184 /* LegacyTheme.swift in Sources */, E17BE4C42A94BA6900C5124E /* FakespotHighlightGroupView.swift in Sources */, 8A2DAD4B2CC02AA00067ECD0 /* LabelButtonHeaderView.swift in Sources */, @@ -17384,6 +17399,7 @@ 8CFD56892AAF06D3003157A6 /* ShoppingProductTests.swift in Sources */, 8ACA8F7629198D6400D3075D /* ThrottlerTests.swift in Sources */, 217AEF76284666D4004EED37 /* IntroViewModelTests.swift in Sources */, + 8A09BCC32D22F1BE00CFDF60 /* ContextMenuCoordinatorTests.swift in Sources */, 0B7CF8872CAC1C4E00DC02F8 /* DefaultFolderHierarchyFetcherTests.swift in Sources */, E16C76812ABDC0DB00172DB5 /* FakespotHighlightsCardViewModelTests.swift in Sources */, 1D8487B62AD6038100F7527C /* RemoteTabPanelStateTests.swift in Sources */, diff --git a/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift b/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift index 9eced868602f..6676178e3c22 100644 --- a/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift +++ b/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift @@ -141,12 +141,14 @@ class BrowserCoordinator: BaseCoordinator, func showHomepage( overlayManager: OverlayModeManager, isZeroSearch: Bool, - statusBarScrollDelegate: StatusBarScrollDelegate + statusBarScrollDelegate: StatusBarScrollDelegate, + toastContainer: UIView ) { let homepageController = self.homepageViewController ?? HomepageViewController( windowUUID: windowUUID, overlayManager: overlayManager, - statusBarScrollDelegate: statusBarScrollDelegate + statusBarScrollDelegate: statusBarScrollDelegate, + toastContainer: toastContainer ) guard browserViewController.embedContent(homepageController) else { logger.log("Unable to embed new homepage", level: .debug, category: .coordinator) @@ -171,13 +173,10 @@ class BrowserCoordinator: BaseCoordinator, } func showContextMenu(for configuration: ContextMenuConfiguration) { - let state = ContextMenuState(configuration: configuration) - let viewModel = PhotonActionSheetViewModel(actions: state.actions, - site: state.site, - modalStyle: .overFullScreen) - let sheet = PhotonActionSheet(viewModel: viewModel, windowUUID: windowUUID) - sheet.modalTransitionStyle = .crossDissolve - present(sheet) + let coordinator = ContextMenuCoordinator(configuration: configuration, router: router, windowUUID: windowUUID) + coordinator.parentCoordinator = self + add(child: coordinator) + coordinator.start() } func showEditBookmark(parentFolder: FxBookmarkNode, bookmark: FxBookmarkNode) { @@ -202,11 +201,7 @@ class BrowserCoordinator: BaseCoordinator, // MARK: - PrivateHomepageDelegate func homePanelDidRequestToOpenInNewTab(with url: URL, isPrivate: Bool, selectNewTab: Bool) { - browserViewController.homePanelDidRequestToOpenInNewTab( - url, - isPrivate: isPrivate, - selectNewTab: selectNewTab - ) + openInNewTab(url: url, isPrivate: isPrivate, selectNewTab: selectNewTab) } func switchMode() { @@ -668,7 +663,13 @@ class BrowserCoordinator: BaseCoordinator, } // MARK: - BrowserNavigationHandler - + func openInNewTab(url: URL, isPrivate: Bool, selectNewTab: Bool) { + browserViewController.homePanelDidRequestToOpenInNewTab( + url, + isPrivate: isPrivate, + selectNewTab: selectNewTab + ) + } func showShareSheet( shareType: ShareType, shareMessage: ShareMessage?, diff --git a/firefox-ios/Client/Coordinators/Browser/BrowserDelegate.swift b/firefox-ios/Client/Coordinators/Browser/BrowserDelegate.swift index 2f3a716769d8..abd296640a87 100644 --- a/firefox-ios/Client/Coordinators/Browser/BrowserDelegate.swift +++ b/firefox-ios/Client/Coordinators/Browser/BrowserDelegate.swift @@ -27,7 +27,8 @@ protocol BrowserDelegate: AnyObject { func showHomepage( overlayManager: OverlayModeManager, isZeroSearch: Bool, - statusBarScrollDelegate: StatusBarScrollDelegate + statusBarScrollDelegate: StatusBarScrollDelegate, + toastContainer: UIView ) /// Show the private homepage to the user as part of felt privacy diff --git a/firefox-ios/Client/Coordinators/Browser/BrowserNavigationHandler.swift b/firefox-ios/Client/Coordinators/Browser/BrowserNavigationHandler.swift index f6a400460dc5..d2580599e4cb 100644 --- a/firefox-ios/Client/Coordinators/Browser/BrowserNavigationHandler.swift +++ b/firefox-ios/Client/Coordinators/Browser/BrowserNavigationHandler.swift @@ -110,6 +110,8 @@ protocol BrowserNavigationHandler: AnyObject, QRCodeNavigationHandler { /// Navigates to the edit bookmark view func showEditBookmark(parentFolder: FxBookmarkNode, bookmark: FxBookmarkNode) + + func openInNewTab(url: URL, isPrivate: Bool, selectNewTab: 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 index 6f342b6c4dd6..1e8e81a8652b 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/NavigationBrowserAction.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/NavigationBrowserAction.swift @@ -20,9 +20,17 @@ class NavigationBrowserAction: Action { } enum NavigationBrowserActionType: ActionType { + // Native views + case tapOnTrackingProtection + case tapOnShareSheet case tapOnCustomizeHomepage - case tapOnCell + case tapOnSettingsSection + + // link related case tapOnLink - case tapOnTrackingProtection + case tapOnOpenInNewTab + + // cell related + case tapOnCell case longPressOnCell } diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserNavigationType.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserNavigationType.swift index ac575a086d77..3c11889a17a9 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserNavigationType.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserNavigationType.swift @@ -8,28 +8,47 @@ import Foundation enum BrowserNavigationDestination: Equatable { // Native views case contextMenu - case customizeHomepage + case settings(Route.SettingsSection) case trackingProtectionSettings // Webpage views case link + case newTab + + // System views + case shareSheet(ShareSheetConfiguration) +} + +struct ShareSheetConfiguration: Equatable { + let shareType: ShareType + let shareMessage: ShareMessage? + let sourceView: UIView + let sourceRect: CGRect? + let toastContainer: UIView + let popoverArrowDirection: UIPopoverArrowDirection } /// This type exists as a field on the BrowserViewControllerState struct NavigationDestination: Equatable { let destination: BrowserNavigationDestination let url: URL? + let isPrivate: Bool? + let selectNewTab: Bool? let isGoogleTopSite: Bool? let contextMenuConfiguration: ContextMenuConfiguration? init( _ destination: BrowserNavigationDestination, url: URL? = nil, + isPrivate: Bool? = nil, + selectNewTab: Bool? = nil, isGoogleTopSite: Bool? = nil, contextMenuConfiguration: ContextMenuConfiguration? = nil ) { self.destination = destination self.url = url + self.isPrivate = isPrivate + self.selectNewTab = selectNewTab self.isGoogleTopSite = isGoogleTopSite self.contextMenuConfiguration = contextMenuConfiguration } diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift index 4bb1cf2835f4..8141cc9359bb 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift @@ -160,7 +160,10 @@ struct BrowserViewControllerState: ScreenState, Equatable { NavigationBrowserActionType.tapOnTrackingProtection, NavigationBrowserActionType.tapOnCell, NavigationBrowserActionType.tapOnLink, - NavigationBrowserActionType.longPressOnCell: + NavigationBrowserActionType.longPressOnCell, + NavigationBrowserActionType.tapOnOpenInNewTab, + NavigationBrowserActionType.tapOnSettingsSection, + NavigationBrowserActionType.tapOnShareSheet: return BrowserViewControllerState( searchScreenState: state.searchScreenState, showDataClearanceFlow: state.showDataClearanceFlow, @@ -170,7 +173,6 @@ struct BrowserViewControllerState: ScreenState, Equatable { microsurveyState: MicrosurveyPromptState.reducer(state.microsurveyState, action), navigationDestination: action.navigationDestination ) - default: return defaultState(from: state, action: action) } diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift index 6d4b16e51a82..de4062360939 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift @@ -1378,7 +1378,8 @@ class BrowserViewController: UIViewController, browserDelegate?.showHomepage( overlayManager: overlayManager, isZeroSearch: inline, - statusBarScrollDelegate: statusBarOverlay + statusBarScrollDelegate: statusBarOverlay, + toastContainer: contentContainer ) } else { browserDelegate?.showLegacyHomepage( @@ -2114,8 +2115,8 @@ class BrowserViewController: UIViewController, navigationHandler?.showContextMenu(for: configuration) case .trackingProtectionSettings: navigationHandler?.show(settings: .contentBlocker) - case .customizeHomepage: - navigationHandler?.show(settings: .homePage) + case .settings(let section): + navigationHandler?.show(settings: section) 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) @@ -2126,6 +2127,21 @@ class BrowserViewController: UIViewController, visitType: .link, isGoogleTopSite: type.isGoogleTopSite ?? false ) + case .newTab: + guard let url = type.url, let isPrivate = type.isPrivate, let selectNewTab = type.selectNewTab else { + logger.log("all params need to be set to properly create a new tab", level: .warning, category: .coordinator) + return + } + navigationHandler?.openInNewTab(url: url, isPrivate: isPrivate, selectNewTab: selectNewTab) + case .shareSheet(let config): + navigationHandler?.showShareSheet( + shareType: config.shareType, + shareMessage: config.shareMessage, + sourceView: config.sourceView, + sourceRect: config.sourceRect, + toastContainer: config.toastContainer, + popoverArrowDirection: config.popoverArrowDirection + ) } } diff --git a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/ContextMenu/ContextMenuConfiguration.swift b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/ContextMenu/ContextMenuConfiguration.swift index d1178bd80746..d4b41aecbb74 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/ContextMenu/ContextMenuConfiguration.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/ContextMenu/ContextMenuConfiguration.swift @@ -6,6 +6,7 @@ import Storage struct ContextMenuConfiguration: Equatable { var homepageSection: HomepageSection var sourceView: UIView? + var toastContainer: UIView var site: Site? { switch item { @@ -27,10 +28,12 @@ struct ContextMenuConfiguration: Equatable { init( homepageSection: HomepageSection, item: HomepageItem? = nil, - sourceView: UIView? = nil + sourceView: UIView? = nil, + toastContainer: UIView ) { self.homepageSection = homepageSection self.item = item self.sourceView = sourceView + self.toastContainer = toastContainer } } diff --git a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/ContextMenu/ContextMenuCoordinator.swift b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/ContextMenu/ContextMenuCoordinator.swift new file mode 100644 index 000000000000..30d86e724225 --- /dev/null +++ b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/ContextMenu/ContextMenuCoordinator.swift @@ -0,0 +1,44 @@ +// 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 Common + +protocol ContextMenuCoordinatorDelegate: AnyObject { + func dismissFlow() +} + +final class ContextMenuCoordinator: BaseCoordinator, ContextMenuCoordinatorDelegate { + weak var parentCoordinator: ParentCoordinatorDelegate? + private let windowUUID: WindowUUID + private let configuration: ContextMenuConfiguration + + init( + configuration: ContextMenuConfiguration, + router: Router, + windowUUID: WindowUUID + ) { + self.configuration = configuration + self.windowUUID = windowUUID + super.init(router: router) + } + + func start() { + let state = ContextMenuState(configuration: configuration, windowUUID: windowUUID) + let viewModel = PhotonActionSheetViewModel( + actions: state.actions, + site: state.site, + modalStyle: .overFullScreen + ) + let sheet = PhotonActionSheet(viewModel: viewModel, windowUUID: windowUUID) + sheet.coordinator = self + sheet.modalTransitionStyle = .crossDissolve + router.present(sheet) + } + + func dismissFlow() { + router.dismiss(animated: true, completion: nil) + parentCoordinator?.didFinish(from: self) + } +} diff --git a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/ContextMenu/ContextMenuState.swift b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/ContextMenu/ContextMenuState.swift index cff77adfae05..2739e1343cc8 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/ContextMenu/ContextMenuState.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/ContextMenu/ContextMenuState.swift @@ -4,6 +4,7 @@ import Common import Foundation +import Shared import Storage /// State to populate actions for the `PhotonActionSheet` view @@ -15,7 +16,16 @@ struct ContextMenuState { var site: Site? var actions: [[PhotonRowActions]] = [[]] - init(configuration: ContextMenuConfiguration) { + private let configuration: ContextMenuConfiguration + private let windowUUID: WindowUUID + private let logger: Logger + weak var coordinatorDelegate: ContextMenuCoordinator? + + init(configuration: ContextMenuConfiguration, windowUUID: WindowUUID, logger: Logger = DefaultLogger.shared) { + self.configuration = configuration + self.windowUUID = windowUUID + self.logger = logger + guard let site = configuration.site else { return } self.site = site @@ -33,37 +43,40 @@ struct ContextMenuState { private func getTopSitesActions(site: Site) -> [PhotonRowActions] { let topSiteActions: [PhotonRowActions] if site is PinnedSite { - topSiteActions = getPinnedTileActions() + topSiteActions = getPinnedTileActions(site: site) } else if site as? SponsoredTile != nil { - topSiteActions = getSponsoredTileActions() + topSiteActions = getSponsoredTileActions(site: site) } else { - topSiteActions = getOtherTopSitesActions() + topSiteActions = getOtherTopSitesActions(site: site) } return topSiteActions } - private func getPinnedTileActions() -> [PhotonRowActions] { + private func getPinnedTileActions(site: Site) -> [PhotonRowActions] { + guard let siteURL = site.url.asURL else { return [] } return [getRemovePinTopSiteAction(), - getOpenInNewTabAction(), - getOpenInNewPrivateTabAction(), + getOpenInNewTabAction(siteURL: siteURL), + getOpenInNewPrivateTabAction(siteURL: siteURL), getRemoveTopSiteAction(), - getShareAction()] + getShareAction(siteURL: site.url)] } - private func getSponsoredTileActions() -> [PhotonRowActions] { - return [getOpenInNewTabAction(), - getOpenInNewPrivateTabAction(), + private func getSponsoredTileActions(site: Site) -> [PhotonRowActions] { + guard let siteURL = site.url.asURL else { return [] } + return [getOpenInNewTabAction(siteURL: siteURL), + getOpenInNewPrivateTabAction(siteURL: siteURL), getSettingsAction(), getSponsoredContentAction(), - getShareAction()] + getShareAction(siteURL: site.url)] } - private func getOtherTopSitesActions() -> [PhotonRowActions] { + private func getOtherTopSitesActions(site: Site) -> [PhotonRowActions] { + guard let siteURL = site.url.asURL else { return [] } return [getPinTopSiteAction(), - getOpenInNewTabAction(), - getOpenInNewPrivateTabAction(), + getOpenInNewTabAction(siteURL: siteURL), + getOpenInNewPrivateTabAction(siteURL: siteURL), getRemoveTopSiteAction(), - getShareAction()] + getShareAction(siteURL: site.url)] } /// This action removes the tile out of the top sites. @@ -73,7 +86,7 @@ struct ContextMenuState { iconString: StandardImageIdentifiers.Large.cross, allowIconScaling: true, tapHandler: { _ in - // TODO: FXIOS-10613 - Add proper actions + // TODO: FXIOS-10614 - Add proper actions }).items } @@ -82,7 +95,7 @@ struct ContextMenuState { iconString: StandardImageIdentifiers.Large.pin, allowIconScaling: true, tapHandler: { _ in - // TODO: FXIOS-10613 - Add proper actions + // TODO: FXIOS-10614 - Add proper actions }).items } @@ -93,7 +106,7 @@ struct ContextMenuState { iconString: StandardImageIdentifiers.Large.pinSlash, allowIconScaling: true, tapHandler: { _ in - // TODO: FXIOS-10613 - Add proper actions + // TODO: FXIOS-10614 - Add proper actions }).items } @@ -102,7 +115,8 @@ struct ContextMenuState { iconString: StandardImageIdentifiers.Large.settings, allowIconScaling: true, tapHandler: { _ in - // TODO: FXIOS-10613 - Add proper actions + dispatchSettingsAction(section: .topSites) + // TODO: FXIOS-10171 - Add telemetry }).items } @@ -111,38 +125,50 @@ struct ContextMenuState { iconString: StandardImageIdentifiers.Large.helpCircle, allowIconScaling: true, tapHandler: { _ in - // TODO: FXIOS-10613 - Add proper actions + guard let url = SupportUtils.URLForTopic("sponsor-privacy") else { + self.logger.log( + "Unable to retrieve URL for sponsor-privacy, return early", + level: .warning, + category: .homepage + ) + return + } + dispatchOpenNewTabAction(siteURL: url, isPrivate: false, selectNewTab: true) + // TODO: FXIOS-10171 - Add telemetry }).items } // MARK: - Pocket item's context menu actions private func getPocketActions(site: Site) -> [PhotonRowActions] { - let openInNewTabAction = getOpenInNewTabAction() - let openInNewPrivateTabAction = getOpenInNewPrivateTabAction() - let shareAction = getShareAction() + guard let siteURL = site.url.asURL else { return [] } + let openInNewTabAction = getOpenInNewTabAction(siteURL: siteURL) + let openInNewPrivateTabAction = getOpenInNewPrivateTabAction(siteURL: siteURL) + let shareAction = getShareAction(siteURL: site.url) let bookmarkAction = getBookmarkAction(site: site) return [openInNewTabAction, openInNewPrivateTabAction, bookmarkAction, shareAction] } // MARK: - Default actions - private func getOpenInNewTabAction() -> PhotonRowActions { + private func getOpenInNewTabAction(siteURL: URL) -> PhotonRowActions { return SingleActionViewModel( title: .OpenInNewTabContextMenuTitle, iconString: StandardImageIdentifiers.Large.plus, allowIconScaling: true ) { _ in - // TODO: FXIOS-10613 - Add proper actions + dispatchOpenNewTabAction(siteURL: siteURL, isPrivate: false) + // TODO: FXIOS-10171 - Add telemetry }.items } - private func getOpenInNewPrivateTabAction() -> PhotonRowActions { + private func getOpenInNewPrivateTabAction(siteURL: URL) -> PhotonRowActions { return SingleActionViewModel( title: .OpenInNewPrivateTabContextMenuTitle, iconString: StandardImageIdentifiers.Large.privateMode, allowIconScaling: true ) { _ in - // TODO: FXIOS-10613 - Add proper actions + dispatchOpenNewTabAction(siteURL: siteURL, isPrivate: true) + // TODO: FXIOS-10171 - Add telemetry }.items } @@ -151,7 +177,7 @@ struct ContextMenuState { if site.bookmarked ?? false { bookmarkAction = getRemoveBookmarkAction() } else { - bookmarkAction = getAddBookmarkAction() + bookmarkAction = getAddBookmarkAction(site: site) } return bookmarkAction.items } @@ -161,25 +187,79 @@ struct ContextMenuState { iconString: StandardImageIdentifiers.Large.bookmarkSlash, allowIconScaling: true, tapHandler: { _ in - // TODO: FXIOS-10613 - Add proper actions + // TODO: FXIOS-10975 - Add proper actions }) } - private func getAddBookmarkAction() -> SingleActionViewModel { + private func getAddBookmarkAction(site: Site) -> SingleActionViewModel { return SingleActionViewModel(title: .BookmarkContextMenuTitle, iconString: StandardImageIdentifiers.Large.bookmark, allowIconScaling: true, tapHandler: { _ in - // TODO: FXIOS-10613 - Add proper actions + // TODO: FXIOS-10975 - Add proper actions }) } - private func getShareAction() -> PhotonRowActions { - return SingleActionViewModel(title: .ShareContextMenuTitle, - iconString: StandardImageIdentifiers.Large.share, - allowIconScaling: true, - tapHandler: { _ in - // TODO: FXIOS-10613 - Add proper actions - }).items + private func getShareAction(siteURL: String) -> PhotonRowActions { + return SingleActionViewModel( + title: .ShareContextMenuTitle, + iconString: StandardImageIdentifiers.Large.share, + allowIconScaling: true, + tapHandler: { _ in + guard let url = URL(string: siteURL, invalidCharacters: false) else { + self.logger.log( + "Unable to retrieve URL for \(siteURL), return early", + level: .warning, + category: .homepage + ) + return + } + let shareSheetConfiguration = ShareSheetConfiguration( + shareType: .site(url: url), + shareMessage: nil, + sourceView: configuration.sourceView ?? UIView(), + sourceRect: nil, + toastContainer: configuration.toastContainer, + popoverArrowDirection: [.up, .down, .left] + ) + + dispatchShareSheetAction(shareSheetConfiguration: shareSheetConfiguration) + }).items + } + + // MARK: Dispatch Actions + private func dispatchSettingsAction(section: Route.SettingsSection) { + store.dispatch( + NavigationBrowserAction( + navigationDestination: NavigationDestination(.settings(section)), + windowUUID: windowUUID, + actionType: NavigationBrowserActionType.tapOnSettingsSection + ) + ) + } + + private func dispatchOpenNewTabAction(siteURL: URL, isPrivate: Bool, selectNewTab: Bool = false) { + store.dispatch( + NavigationBrowserAction( + navigationDestination: NavigationDestination( + .newTab, + url: siteURL, + isPrivate: isPrivate, + selectNewTab: selectNewTab + ), + windowUUID: windowUUID, + actionType: NavigationBrowserActionType.tapOnOpenInNewTab + ) + ) + } + + private func dispatchShareSheetAction(shareSheetConfiguration: ShareSheetConfiguration) { + store.dispatch( + NavigationBrowserAction( + navigationDestination: NavigationDestination(.shareSheet(shareSheetConfiguration)), + windowUUID: windowUUID, + actionType: NavigationBrowserActionType.tapOnShareSheet + ) + ) } } diff --git a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageViewController.swift b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageViewController.swift index ed471648ebfd..27afe99ac6ea 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageViewController.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageViewController.swift @@ -43,8 +43,7 @@ final class HomepageViewController: UIViewController, private var dataSource: HomepageDiffableDataSource? // TODO: FXIOS-10541 will handle scrolling for wallpaper and other scroll issues private lazy var wallpaperView: WallpaperBackgroundView = .build { _ in } - private var overlayManager: OverlayModeManager - private var logger: Logger + private var homepageState: HomepageState private var lastContentOffsetY: CGFloat = 0 @@ -52,11 +51,17 @@ final class HomepageViewController: UIViewController, themeManager.getCurrentTheme(for: windowUUID) } + // MARK: - Private constants + private let overlayManager: OverlayModeManager + private let logger: Logger + private let toastContainer: UIView + // MARK: - Initializers init(windowUUID: WindowUUID, themeManager: ThemeManager = AppContainer.shared.resolve(), overlayManager: OverlayModeManager, statusBarScrollDelegate: StatusBarScrollDelegate? = nil, + toastContainer: UIView, notificationCenter: NotificationProtocol = NotificationCenter.default, logger: Logger = DefaultLogger.shared ) { @@ -65,6 +70,7 @@ final class HomepageViewController: UIViewController, self.notificationCenter = notificationCenter self.overlayManager = overlayManager self.statusBarScrollDelegate = statusBarScrollDelegate + self.toastContainer = toastContainer self.logger = logger homepageState = HomepageState(windowUUID: windowUUID) super.init(nibName: nil, bundle: nil) @@ -490,7 +496,7 @@ final class HomepageViewController: UIViewController, private func navigateToHomepageSettings() { store.dispatch( NavigationBrowserAction( - navigationDestination: NavigationDestination(.customizeHomepage), + navigationDestination: NavigationDestination(.settings(.homePage)), windowUUID: self.windowUUID, actionType: NavigationBrowserActionType.tapOnCustomizeHomepage ) @@ -508,7 +514,12 @@ final class HomepageViewController: UIViewController, } private func navigateToContextMenu(for section: HomepageSection, and item: HomepageItem, sourceView: UIView? = nil) { - let configuration = ContextMenuConfiguration(homepageSection: section, item: item, sourceView: sourceView) + let configuration = ContextMenuConfiguration( + homepageSection: section, + item: item, + sourceView: sourceView, + toastContainer: toastContainer + ) store.dispatch( NavigationBrowserAction( navigationDestination: NavigationDestination(.contextMenu, contextMenuConfiguration: configuration), diff --git a/firefox-ios/Client/Frontend/Widgets/PhotonActionSheet/PhotonActionSheet.swift b/firefox-ios/Client/Frontend/Widgets/PhotonActionSheet/PhotonActionSheet.swift index a083db05de2e..a4ab741bf1c2 100644 --- a/firefox-ios/Client/Frontend/Widgets/PhotonActionSheet/PhotonActionSheet.swift +++ b/firefox-ios/Client/Frontend/Widgets/PhotonActionSheet/PhotonActionSheet.swift @@ -48,6 +48,8 @@ class PhotonActionSheet: UIViewController, Themeable { let windowUUID: WindowUUID var currentWindowUUID: UUID? { windowUUID } + weak var coordinator: ContextMenuCoordinatorDelegate? + private lazy var closeButton: UIButton = .build { button in button.setTitle(.CloseButtonTitle, for: .normal) button.layer.cornerRadius = UX.cornerRadius @@ -324,7 +326,7 @@ class PhotonActionSheet: UIViewController, Themeable { @objc private func dismiss(_ gestureRecognizer: UIGestureRecognizer?) { - dismissVC() + dismissSheet() } override func touchesBegan(_ touches: Set, with event: UIEvent?) { @@ -332,7 +334,7 @@ class PhotonActionSheet: UIViewController, Themeable { // Need to handle click outside view for non-popover sheet styles guard let touch = touches.first else { return } if !tableView.frame.contains(touch.location(in: view)) { - dismissVC() + dismissSheet() } } @@ -490,11 +492,16 @@ extension PhotonActionSheet: UITableViewDataSource, UITableViewDelegate { (header as? ThemeApplicable)?.applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID)) return header } + + private func dismissSheet(withCompletion completion: (() -> Void)? = nil) { + dismissVC(withCompletion: completion) + coordinator?.dismissFlow() + } } // MARK: - PhotonActionSheetViewDelegate extension PhotonActionSheet: PhotonActionSheetContainerCellDelegate { func didClick(item: SingleActionViewModel?, animationCompletion: @escaping () -> Void) { - dismissVC(withCompletion: animationCompletion) + dismissSheet(withCompletion: animationCompletion) } } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewControllerStateTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewControllerStateTests.swift index 2739289ca890..aa9efb66e953 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewControllerStateTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewControllerStateTests.swift @@ -88,16 +88,16 @@ final class BrowserViewControllerStateTests: XCTestCase { } // MARK: - Navigation Browser Action - func test_customizeHomepage_navigationBrowserAction_returnsExpectedState() { + func test_tapOnCustomizeHomepage_navigationBrowserAction_returnsExpectedState() { let initialState = createSubject() let reducer = browserViewControllerReducer() XCTAssertNil(initialState.navigationDestination) - let action = getNavigationBrowserAction(for: .tapOnCustomizeHomepage, destination: .customizeHomepage) + let action = getNavigationBrowserAction(for: .tapOnCustomizeHomepage, destination: .settings(.homePage)) let newState = reducer(initialState, action) - XCTAssertEqual(newState.navigationDestination?.destination, .customizeHomepage) + XCTAssertEqual(newState.navigationDestination?.destination, .settings(.homePage)) XCTAssertEqual(newState.navigationDestination?.url, nil) } @@ -129,6 +129,100 @@ final class BrowserViewControllerStateTests: XCTestCase { XCTAssertEqual(newState.navigationDestination?.url?.absoluteString, "www.example.com") } + func test_tapOnShareSheet_navigationBrowserAction_returnsExpectedState() throws { + let initialState = createSubject() + let reducer = browserViewControllerReducer() + + XCTAssertNil(initialState.navigationDestination) + + let url = try XCTUnwrap(URL(string: "www.example.com")) + let shareSheetConfiguration = ShareSheetConfiguration( + shareType: .site(url: url), + shareMessage: nil, + sourceView: UIView(), + sourceRect: nil, + toastContainer: UIView(), + popoverArrowDirection: [.up] + ) + let action = getNavigationBrowserAction( + for: .tapOnShareSheet, + destination: .shareSheet(shareSheetConfiguration) + ) + let newState = reducer(initialState, action) + + XCTAssertEqual(newState.navigationDestination?.destination, .shareSheet(shareSheetConfiguration)) + XCTAssertEqual(newState.navigationDestination?.url, nil) + } + + func test_longPressOnCell_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: .longPressOnCell, destination: .link, url: url) + let newState = reducer(initialState, action) + + XCTAssertEqual(newState.navigationDestination?.destination, .link) + XCTAssertEqual(newState.navigationDestination?.url?.absoluteString, "www.example.com") + } + + func test_tapOnOpenInNewTab_navigationBrowserAction_returnsExpectedState() throws { + let initialState = createSubject() + let reducer = browserViewControllerReducer() + + XCTAssertNil(initialState.navigationDestination) + + let url = try XCTUnwrap(URL(string: "www.example.com")) + let action = NavigationBrowserAction( + navigationDestination: NavigationDestination(.newTab, url: url, isPrivate: false, selectNewTab: false), + windowUUID: .XCTestDefaultUUID, + actionType: NavigationBrowserActionType.tapOnOpenInNewTab + ) + let newState = reducer(initialState, action) + + let navigationDestination = try XCTUnwrap(newState.navigationDestination) + XCTAssertEqual(navigationDestination.destination, .newTab) + XCTAssertEqual(navigationDestination.url?.absoluteString, "www.example.com") + XCTAssertFalse(navigationDestination.isPrivate ?? true) + XCTAssertFalse(navigationDestination.selectNewTab ?? true) + } + + func test_tapOnOpenInNewTab_forPrivateTab_navigationBrowserAction_returnsExpectedState() throws { + let initialState = createSubject() + let reducer = browserViewControllerReducer() + + XCTAssertNil(initialState.navigationDestination) + + let url = try XCTUnwrap(URL(string: "www.example.com")) + let action = NavigationBrowserAction( + navigationDestination: NavigationDestination(.newTab, url: url, isPrivate: true, selectNewTab: true), + windowUUID: .XCTestDefaultUUID, + actionType: NavigationBrowserActionType.tapOnOpenInNewTab + ) + let newState = reducer(initialState, action) + + let navigationDestination = try XCTUnwrap(newState.navigationDestination) + XCTAssertEqual(navigationDestination.destination, .newTab) + XCTAssertEqual(navigationDestination.url?.absoluteString, "www.example.com") + XCTAssertTrue(navigationDestination.isPrivate ?? false) + XCTAssertTrue(navigationDestination.selectNewTab ?? false) + } + + func test_tapOnSettingsSection_navigationBrowserAction_returnsExpectedState() { + let initialState = createSubject() + let reducer = browserViewControllerReducer() + + XCTAssertNil(initialState.navigationDestination) + + let action = getNavigationBrowserAction(for: .tapOnSettingsSection, destination: .settings(.topSites)) + let newState = reducer(initialState, action) + + XCTAssertEqual(newState.navigationDestination?.destination, .settings(.topSites)) + XCTAssertEqual(newState.navigationDestination?.url, nil) + } + // MARK: - Private private func createSubject() -> BrowserViewControllerState { return BrowserViewControllerState(windowUUID: .XCTestDefaultUUID) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/BrowserCoordinatorTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/BrowserCoordinatorTests.swift index 7a95b85ac807..4bedeef689de 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/BrowserCoordinatorTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/BrowserCoordinatorTests.swift @@ -144,7 +144,8 @@ final class BrowserCoordinatorTests: XCTestCase, FeatureFlaggable { subject.showHomepage( overlayManager: overlayModeManager, isZeroSearch: false, - statusBarScrollDelegate: scrollDelegate + statusBarScrollDelegate: scrollDelegate, + toastContainer: UIView() ) XCTAssertNotNil(subject.homepageViewController) @@ -157,7 +158,8 @@ final class BrowserCoordinatorTests: XCTestCase, FeatureFlaggable { subject.showHomepage( overlayManager: overlayModeManager, isZeroSearch: false, - statusBarScrollDelegate: scrollDelegate + statusBarScrollDelegate: scrollDelegate, + toastContainer: UIView() ) let firstHomepage = subject.homepageViewController XCTAssertNotNil(subject.homepageViewController) @@ -165,7 +167,8 @@ final class BrowserCoordinatorTests: XCTestCase, FeatureFlaggable { subject.showHomepage( overlayManager: overlayModeManager, isZeroSearch: false, - statusBarScrollDelegate: scrollDelegate + statusBarScrollDelegate: scrollDelegate, + toastContainer: UIView() ) let secondHomepage = subject.homepageViewController XCTAssertEqual(firstHomepage, secondHomepage) @@ -433,6 +436,17 @@ final class BrowserCoordinatorTests: XCTestCase, FeatureFlaggable { XCTAssertTrue(mockRouter.presentedViewController is BottomSheetViewController) } + func testShowContextMenu_addsContextMenuCoordinator() { + let subject = createSubject() + let config = ContextMenuConfiguration(homepageSection: .customizeHomepage, toastContainer: UIView()) + subject.showContextMenu(for: config) + + XCTAssertEqual(subject.childCoordinators.count, 1) + XCTAssertTrue(subject.childCoordinators.first is ContextMenuCoordinator) + XCTAssertEqual(mockRouter.presentCalled, 1) + XCTAssertTrue(mockRouter.presentedViewController is PhotonActionSheet) + } + // MARK: - ParentCoordinatorDelegate func testRemoveChildCoordinator_whenDidFinishCalled() { 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 182fef05c180..d3577a48bc08 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 @@ -37,6 +37,7 @@ class MockBrowserCoordinator: BrowserNavigationHandler, ParentCoordinatorDelegat var navigateFromHomePanelCalled = 0 var showContextMenuCalled = 0 var showEditBookmarkCalled = 0 + var openInNewTabCalled = 0 func show(settings: Client.Route.SettingsSection, onDismiss: (() -> Void)?) { showSettingsCalled += 1 @@ -155,4 +156,8 @@ class MockBrowserCoordinator: BrowserNavigationHandler, ParentCoordinatorDelegat func showEditBookmark(parentFolder: FxBookmarkNode, bookmark: FxBookmarkNode) { showEditBookmarkCalled += 1 } + + func openInNewTab(url: URL, isPrivate: Bool, selectNewTab: Bool) { + openInNewTabCalled += 1 + } } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/ContentContainerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/ContentContainerTests.swift index d834e79dd3e0..edbaf007d1d3 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/ContentContainerTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/ContentContainerTests.swift @@ -47,14 +47,22 @@ final class ContentContainerTests: XCTestCase { func testCanAddNewHomepage() { let subject = ContentContainer(frame: .zero) - let homepage = HomepageViewController(windowUUID: .XCTestDefaultUUID, overlayManager: overlayModeManager) + let homepage = HomepageViewController( + windowUUID: .XCTestDefaultUUID, + overlayManager: overlayModeManager, + toastContainer: UIView() + ) XCTAssertTrue(subject.canAdd(content: homepage)) } func testCanAddNewHomepageOnceOnly() { let subject = ContentContainer(frame: .zero) - let homepage = HomepageViewController(windowUUID: .XCTestDefaultUUID, overlayManager: overlayModeManager) + let homepage = HomepageViewController( + windowUUID: .XCTestDefaultUUID, + overlayManager: overlayModeManager, + toastContainer: UIView() + ) subject.add(content: homepage) XCTAssertFalse(subject.canAdd(content: homepage)) @@ -127,7 +135,11 @@ final class ContentContainerTests: XCTestCase { func testHasNewHomepage_returnsTrueWhenAdded() { let subject = ContentContainer(frame: .zero) - let homepage = HomepageViewController(windowUUID: .XCTestDefaultUUID, overlayManager: overlayModeManager) + let homepage = HomepageViewController( + windowUUID: .XCTestDefaultUUID, + overlayManager: overlayModeManager, + toastContainer: UIView() + ) subject.add(content: homepage) XCTAssertTrue(subject.hasHomepage) @@ -188,7 +200,11 @@ final class ContentContainerTests: XCTestCase { func testContentView_hasContentNewHomepage_viewIsNotNil() { let subject = ContentContainer(frame: .zero) - let homepage = HomepageViewController(windowUUID: .XCTestDefaultUUID, overlayManager: overlayModeManager) + let homepage = HomepageViewController( + windowUUID: .XCTestDefaultUUID, + overlayManager: overlayModeManager, + toastContainer: UIView() + ) subject.add(content: homepage) XCTAssertNotNil(subject.contentView) } @@ -217,7 +233,11 @@ final class ContentContainerTests: XCTestCase { func test_update_hasNewHomepage_returnsTrue() { let subject = ContentContainer(frame: .zero) - let homepage = HomepageViewController(windowUUID: .XCTestDefaultUUID, overlayManager: overlayModeManager) + let homepage = HomepageViewController( + windowUUID: .XCTestDefaultUUID, + overlayManager: overlayModeManager, + toastContainer: UIView() + ) subject.update(content: homepage) XCTAssertTrue(subject.hasHomepage) XCTAssertFalse(subject.hasLegacyHomepage) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/ContextMenuConfigurationTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/ContextMenu/ContextMenuConfigurationTests.swift similarity index 91% rename from firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/ContextMenuConfigurationTests.swift rename to firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/ContextMenu/ContextMenuConfigurationTests.swift index 3c41fd8e39b2..7d0e16122290 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/ContextMenuConfigurationTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/ContextMenu/ContextMenuConfigurationTests.swift @@ -31,7 +31,8 @@ final class ContextMenuConfigurationTests: XCTestCase { ) let subject = ContextMenuConfiguration( homepageSection: .pocket(nil), - item: pocketItem + item: pocketItem, + toastContainer: UIView() ) XCTAssertEqual(subject.site?.tileURL.absoluteString, "file:///www.example.com/1234") XCTAssertEqual(subject.site?.title, "Site 0") @@ -46,7 +47,8 @@ final class ContextMenuConfigurationTests: XCTestCase { ) let subject = ContextMenuConfiguration( homepageSection: .pocket(nil), - item: pocketItem + item: pocketItem, + toastContainer: UIView() ) XCTAssertEqual(subject.site?.tileURL.absoluteString, "file:///www.example.com/1234") XCTAssertEqual(subject.site?.title, "Discover Site 0") @@ -60,7 +62,8 @@ final class ContextMenuConfigurationTests: XCTestCase { ) let subject = ContextMenuConfiguration( homepageSection: .topSites, - item: topSiteItem + item: topSiteItem, + toastContainer: UIView() ) XCTAssertEqual(subject.site?.tileURL.absoluteString, "www.example.com/1234") XCTAssertEqual(subject.site?.title, "Site 0") @@ -69,7 +72,8 @@ final class ContextMenuConfigurationTests: XCTestCase { func tests_initialState_forNoItem_returnsExpectedState() { let subject = ContextMenuConfiguration( homepageSection: .topSites, - item: nil + item: nil, + toastContainer: UIView() ) XCTAssertNil(subject.site) } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/ContextMenu/ContextMenuCoordinatorTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/ContextMenu/ContextMenuCoordinatorTests.swift new file mode 100644 index 000000000000..1699accdcb3f --- /dev/null +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/ContextMenu/ContextMenuCoordinatorTests.swift @@ -0,0 +1,63 @@ +// 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 XCTest + +@testable import Client + +final class ContextMenuCoordinatorTests: XCTestCase { + private var mockRouter: MockRouter! + + override func setUp() { + super.setUp() + DependencyHelperMock().bootstrapDependencies() + mockRouter = MockRouter(navigationController: MockNavigationController()) + } + + override func tearDown() { + mockRouter = nil + DependencyHelperMock().reset() + super.tearDown() + } + + func test_initialState() { + _ = createSubject() + + XCTAssertFalse(mockRouter.presentedViewController is PhotonActionSheet) + XCTAssertEqual(mockRouter.presentCalled, 0) + } + + func test_start_presentsContextMenuController() throws { + let subject = createSubject() + + subject.start() + + XCTAssertTrue(mockRouter.presentedViewController is PhotonActionSheet) + XCTAssertEqual(mockRouter.presentCalled, 1) + } + + func test_dismissFlow_callsRouterDismiss() throws { + let subject = createSubject() + + subject.start() + subject.dismissFlow() + + XCTAssertEqual(mockRouter.dismissCalled, 1) + } + + private func createSubject(file: StaticString = #file, + line: UInt = #line) -> ContextMenuCoordinator { + let configuration = ContextMenuConfiguration(homepageSection: .header, toastContainer: UIView()) + + let subject = ContextMenuCoordinator( + configuration: configuration, + router: mockRouter, + windowUUID: .XCTestDefaultUUID + ) + + trackForMemoryLeaks(subject, file: file, line: line) + return subject + } +} diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/HomepageViewControllerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/HomepageViewControllerTests.swift index 1145a455409a..87441a7ce431 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/HomepageViewControllerTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/HomepageViewControllerTests.swift @@ -155,6 +155,7 @@ final class HomepageViewControllerTests: XCTestCase, StoreTestUtility { themeManager: themeManager, overlayManager: mockOverlayManager, statusBarScrollDelegate: statusBarScrollDelegate, + toastContainer: UIView(), notificationCenter: notificationCenter ) trackForMemoryLeaks(homepageViewController)