diff --git a/firefox-ios/Client.xcodeproj/project.pbxproj b/firefox-ios/Client.xcodeproj/project.pbxproj index 692da4ce04c13..417284b911951 100644 --- a/firefox-ios/Client.xcodeproj/project.pbxproj +++ b/firefox-ios/Client.xcodeproj/project.pbxproj @@ -713,6 +713,8 @@ 8A7A26E829D4C0FE00EA76F1 /* IntroScreenManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A26E629D4C0D800EA76F1 /* IntroScreenManagerTests.swift */; }; 8A7A26EA29D4C3C800EA76F1 /* LaunchType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A26E929D4C3C800EA76F1 /* LaunchType.swift */; }; 8A7A93EE2810ADF2005E7E1B /* ContileProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A93ED2810ADF2005E7E1B /* ContileProviderTests.swift */; }; + 8A7AE4442BAB510B0072DAEC /* LibraryPanelCoordinatorDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7AE4432BAB510B0072DAEC /* LibraryPanelCoordinatorDelegate.swift */; }; + 8A7AE4472BAC78230072DAEC /* MockLibraryNavigationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7AE4452BAC76B00072DAEC /* MockLibraryNavigationHandler.swift */; }; 8A7D1AC52BA3542600162F4B /* splashScreen.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A7D1AC42BA3542600162F4B /* splashScreen.json */; }; 8A832A9029DC96C50025D5DD /* LaunchScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A832A8F29DC96C50025D5DD /* LaunchScreenView.swift */; }; 8A832A9229DC99790025D5DD /* LaunchScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A832A9129DC99790025D5DD /* LaunchScreenViewModel.swift */; }; @@ -924,7 +926,6 @@ 96D95016270238500079D39D /* Throttler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96D95015270238500079D39D /* Throttler.swift */; }; 96EA9454293655BF00123345 /* AppSession+Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96EA9453293655BF00123345 /* AppSession+Enums.swift */; }; 96EB6C3827D821B800A9D159 /* HistoryPanelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96EB6C3727D821B800A9D159 /* HistoryPanelViewModel.swift */; }; - 96EB6C3C27D82AEA00A9D159 /* HistoryPanel+ContextMenuExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96EB6C3B27D82AEA00A9D159 /* HistoryPanel+ContextMenuExtensions.swift */; }; 96EB6C3E27D9266500A9D159 /* HistoryActionables.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96EB6C3D27D9266500A9D159 /* HistoryActionables.swift */; }; 96EB6C4027DBEE9800A9D159 /* SearchGroupedItemsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96EB6C3F27DBEE9800A9D159 /* SearchGroupedItemsViewController.swift */; }; 96EB6C4327DC205D00A9D159 /* SearchGroupedItemsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96EB6C4227DC205D00A9D159 /* SearchGroupedItemsViewModel.swift */; }; @@ -5848,6 +5849,8 @@ 8A7A26E629D4C0D800EA76F1 /* IntroScreenManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroScreenManagerTests.swift; sourceTree = ""; }; 8A7A26E929D4C3C800EA76F1 /* LaunchType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LaunchType.swift; sourceTree = ""; }; 8A7A93ED2810ADF2005E7E1B /* ContileProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContileProviderTests.swift; sourceTree = ""; }; + 8A7AE4432BAB510B0072DAEC /* LibraryPanelCoordinatorDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryPanelCoordinatorDelegate.swift; sourceTree = ""; }; + 8A7AE4452BAC76B00072DAEC /* MockLibraryNavigationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLibraryNavigationHandler.swift; sourceTree = ""; }; 8A7D1AC42BA3542600162F4B /* splashScreen.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = splashScreen.json; sourceTree = ""; }; 8A832A8F29DC96C50025D5DD /* LaunchScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchScreenView.swift; sourceTree = ""; }; 8A832A9129DC99790025D5DD /* LaunchScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchScreenViewModel.swift; sourceTree = ""; }; @@ -6169,7 +6172,6 @@ 96D95015270238500079D39D /* Throttler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Throttler.swift; sourceTree = ""; }; 96EA9453293655BF00123345 /* AppSession+Enums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppSession+Enums.swift"; sourceTree = ""; }; 96EB6C3727D821B800A9D159 /* HistoryPanelViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryPanelViewModel.swift; sourceTree = ""; }; - 96EB6C3B27D82AEA00A9D159 /* HistoryPanel+ContextMenuExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HistoryPanel+ContextMenuExtensions.swift"; sourceTree = ""; }; 96EB6C3D27D9266500A9D159 /* HistoryActionables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryActionables.swift; sourceTree = ""; }; 96EB6C3F27DBEE9800A9D159 /* SearchGroupedItemsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchGroupedItemsViewController.swift; sourceTree = ""; }; 96EB6C4227DC205D00A9D159 /* SearchGroupedItemsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchGroupedItemsViewModel.swift; sourceTree = ""; }; @@ -9085,6 +9087,7 @@ 5AE371832A4DD6F50092A760 /* PasswordManagerListViewControllerSpy.swift */, C23889E42A50329200429673 /* MockParentCoordinatorDelegate.swift */, C2446B302A856D13000C527D /* MockLibraryCoordinatorDelegate.swift */, + 8A7AE4452BAC76B00072DAEC /* MockLibraryNavigationHandler.swift */, C2D80BEC2AAF3C6B00CDF7A9 /* MockBrowserCoordinator.swift */, C29B64822AD69C3E00F3244B /* MockQRCodeParentCoordinator.swift */, 21FA8FB12AE856EB0013B815 /* MockTabTrayCoordinatorDelegate.swift */, @@ -10014,7 +10017,6 @@ 59A6825233896FC846499289 /* HistoryPanel.swift */, 211F00AB27F4D918001D9189 /* HistoryPanel+Search.swift */, 96EB6C3727D821B800A9D159 /* HistoryPanelViewModel.swift */, - 96EB6C3B27D82AEA00A9D159 /* HistoryPanel+ContextMenuExtensions.swift */, 96EB6C3D27D9266500A9D159 /* HistoryActionables.swift */, ); path = HistoryPanel; @@ -10113,6 +10115,7 @@ children = ( 8A83B7472A264FB7002FF9AC /* LibraryCoordinator.swift */, C2D1A10C2A66C70000205DCC /* BookmarksCoordinator.swift */, + 8A7AE4432BAB510B0072DAEC /* LibraryPanelCoordinatorDelegate.swift */, C2506C922A6A863600F2B76E /* HistoryCoordinator.swift */, C2A72A662A76938C002ACCE2 /* DownloadsCoordinator.swift */, C2A72A682A769460002ACCE2 /* ReadingListCoordinator.swift */, @@ -13609,6 +13612,7 @@ 396E38F11EE0C8EC00CC180F /* FxAPushMessageHandler.swift in Sources */, 8A76B01629F6EB3900A82607 /* ScreenshotService.swift in Sources */, 8C1953322B85EAB500761B20 /* AutofillHeaderView.swift in Sources */, + 8A7AE4442BAB510B0072DAEC /* LibraryPanelCoordinatorDelegate.swift in Sources */, 8C19532E2B85E7AE00761B20 /* SelfSizingHostingController.swift in Sources */, E4CD9F6D1A77DD2800318571 /* ReaderModeStyleViewController.swift in Sources */, E13E9AB52AAB0FB5001A0E9D /* FakespotViewModel.swift in Sources */, @@ -14322,7 +14326,6 @@ EBA3B2D22268F57E00728BDB /* BadgeWithBackdrop.swift in Sources */, 8AB5958828413F6C0090F4AE /* RecentlySavedCell.swift in Sources */, 8A83B7482A264FB7002FF9AC /* LibraryCoordinator.swift in Sources */, - 96EB6C3C27D82AEA00A9D159 /* HistoryPanel+ContextMenuExtensions.swift in Sources */, E1CD81C2290C62A600124B27 /* HostingTableViewCell.swift in Sources */, 8AA020EF2B9A37E500771DE0 /* NimbusSplashScreenFeatureLayer.swift in Sources */, 43175DB826B87D2C00C41C31 /* AdsTelemetryHelper.swift in Sources */, @@ -14381,6 +14384,7 @@ 8A7653C228A2E57D00924ABF /* PocketDataAdaptorTests.swift in Sources */, C8CD80D42A1E268C0097C3AE /* MockGleanPlumbEvaluationUtility.swift in Sources */, E1AEC176286E0CF500062E29 /* JumpBackInViewModelTests.swift in Sources */, + 8A7AE4472BAC78230072DAEC /* MockLibraryNavigationHandler.swift in Sources */, 8A7A26E829D4C0FE00EA76F1 /* IntroScreenManagerTests.swift in Sources */, E1AEC17A286E0CF500062E29 /* WebViewNavigationHandlerTests.swift in Sources */, D3D488591ABB54CD00A93597 /* FileAccessorTests.swift in Sources */, diff --git a/firefox-ios/Client/Coordinators/Library/BookmarksCoordinator.swift b/firefox-ios/Client/Coordinators/Library/BookmarksCoordinator.swift index fd3f37734dfe5..4c6ec69fa0344 100644 --- a/firefox-ios/Client/Coordinators/Library/BookmarksCoordinator.swift +++ b/firefox-ios/Client/Coordinators/Library/BookmarksCoordinator.swift @@ -5,7 +5,7 @@ import Foundation import Storage -protocol BookmarksCoordinatorDelegate: AnyObject { +protocol BookmarksCoordinatorDelegate: AnyObject, LibraryPanelCoordinatorDelegate { func start(from folder: FxBookmarkNode) /// Shows the bookmark detail to modify a bookmark folder @@ -38,6 +38,7 @@ class BookmarksCoordinator: BaseCoordinator, BookmarksCoordinatorDelegate { private let profile: Profile private weak var parentCoordinator: LibraryCoordinatorDelegate? + private weak var navigationHandler: LibraryNavigationHandler? private let windowUUID: WindowUUID // MARK: - Initializers @@ -46,11 +47,13 @@ class BookmarksCoordinator: BaseCoordinator, BookmarksCoordinatorDelegate { router: Router, profile: Profile, windowUUID: WindowUUID, - parentCoordinator: LibraryCoordinatorDelegate? + parentCoordinator: LibraryCoordinatorDelegate?, + navigationHandler: LibraryNavigationHandler? ) { self.profile = profile self.windowUUID = windowUUID self.parentCoordinator = parentCoordinator + self.navigationHandler = navigationHandler super.init(router: router) } @@ -88,4 +91,8 @@ class BookmarksCoordinator: BaseCoordinator, BookmarksCoordinatorDelegate { } router.push(detailController) } + + func shareLibraryItem(url: URL, sourceView: UIView) { + navigationHandler?.shareLibraryItem(url: url, sourceView: sourceView) + } } diff --git a/firefox-ios/Client/Coordinators/Library/HistoryCoordinator.swift b/firefox-ios/Client/Coordinators/Library/HistoryCoordinator.swift index c800142bcc031..1144db040b982 100644 --- a/firefox-ios/Client/Coordinators/Library/HistoryCoordinator.swift +++ b/firefox-ios/Client/Coordinators/Library/HistoryCoordinator.swift @@ -7,7 +7,7 @@ import Common import Shared import Storage -protocol HistoryCoordinatorDelegate: AnyObject { +protocol HistoryCoordinatorDelegate: AnyObject, LibraryPanelCoordinatorDelegate { func showRecentlyClosedTab() /// Shows table view controller with searched sites grouped. @@ -21,6 +21,7 @@ class HistoryCoordinator: BaseCoordinator, HistoryCoordinatorDelegate { private let windowUUID: WindowUUID private let notificationCenter: NotificationProtocol private weak var parentCoordinator: LibraryCoordinatorDelegate? + private weak var navigationHandler: LibraryNavigationHandler? // MARK: - Initializers @@ -29,12 +30,14 @@ class HistoryCoordinator: BaseCoordinator, HistoryCoordinatorDelegate { windowUUID: WindowUUID, router: Router, notificationCenter: NotificationProtocol = NotificationCenter.default, - parentCoordinator: LibraryCoordinatorDelegate? + parentCoordinator: LibraryCoordinatorDelegate?, + navigationHandler: LibraryNavigationHandler? ) { self.profile = profile self.windowUUID = windowUUID self.parentCoordinator = parentCoordinator self.notificationCenter = notificationCenter + self.navigationHandler = navigationHandler super.init(router: router) self.notificationCenter.addObserver( self, @@ -68,6 +71,10 @@ class HistoryCoordinator: BaseCoordinator, HistoryCoordinatorDelegate { router.push(asGroupListVC) } + func shareLibraryItem(url: URL, sourceView: UIView) { + navigationHandler?.shareLibraryItem(url: url, sourceView: sourceView) + } + deinit { notificationCenter.removeObserver(self) } diff --git a/firefox-ios/Client/Coordinators/Library/LibraryCoordinator.swift b/firefox-ios/Client/Coordinators/Library/LibraryCoordinator.swift index 7b36a20a56611..5c78b4abf384e 100644 --- a/firefox-ios/Client/Coordinators/Library/LibraryCoordinator.swift +++ b/firefox-ios/Client/Coordinators/Library/LibraryCoordinator.swift @@ -13,9 +13,10 @@ protocol LibraryCoordinatorDelegate: AnyObject, LibraryPanelDelegate, RecentlyCl protocol LibraryNavigationHandler: AnyObject { func start(panelType: LibraryPanelType, navigationController: UINavigationController) + func shareLibraryItem(url: URL, sourceView: UIView) } -class LibraryCoordinator: BaseCoordinator, LibraryPanelDelegate, LibraryNavigationHandler { +class LibraryCoordinator: BaseCoordinator, LibraryPanelDelegate, LibraryNavigationHandler, ParentCoordinatorDelegate { private let profile: Profile private let tabManager: TabManager private var libraryViewController: LibraryViewController! @@ -75,6 +76,12 @@ class LibraryCoordinator: BaseCoordinator, LibraryPanelDelegate, LibraryNavigati } } + func shareLibraryItem(url: URL, sourceView: UIView) { + guard !childCoordinators.contains(where: { $0 is ShareExtensionCoordinator }) else { return } + let coordinator = makeShareExtensionCoordinator() + coordinator.start(url: url, sourceView: sourceView) + } + private func makeBookmarksCoordinator(navigationController: UINavigationController) { guard !childCoordinators.contains(where: { $0 is BookmarksCoordinator }) else { return } let router = DefaultRouter(navigationController: navigationController) @@ -82,7 +89,8 @@ class LibraryCoordinator: BaseCoordinator, LibraryPanelDelegate, LibraryNavigati router: router, profile: profile, windowUUID: windowUUID, - parentCoordinator: parentCoordinator + parentCoordinator: parentCoordinator, + navigationHandler: self ) add(child: bookmarksCoordinator) (navigationController.topViewController as? BookmarksPanel)?.bookmarkCoordinatorDelegate = bookmarksCoordinator @@ -95,7 +103,8 @@ class LibraryCoordinator: BaseCoordinator, LibraryPanelDelegate, LibraryNavigati profile: profile, windowUUID: windowUUID, router: router, - parentCoordinator: parentCoordinator + parentCoordinator: parentCoordinator, + navigationHandler: self ) add(child: historyCoordinator) (navigationController.topViewController as? HistoryPanel)?.historyCoordinatorDelegate = historyCoordinator @@ -119,12 +128,31 @@ class LibraryCoordinator: BaseCoordinator, LibraryPanelDelegate, LibraryNavigati let router = DefaultRouter(navigationController: navigationController) let readingListCoordinator = ReadingListCoordinator( parentCoordinator: parentCoordinator, + navigationHandler: self, router: router ) add(child: readingListCoordinator) (navigationController.topViewController as? ReadingListPanel)?.navigationHandler = readingListCoordinator } + // MARK: - ParentCoordinatorDelegate + + func didFinish(from childCoordinator: any Coordinator) { + remove(child: childCoordinator) + } + + private func makeShareExtensionCoordinator() -> ShareExtensionCoordinator { + let coordinator = ShareExtensionCoordinator( + alertContainer: UIView(), + router: router, + profile: profile, + parentCoordinator: self, + tabManager: tabManager + ) + add(child: coordinator) + return coordinator + } + // MARK: - LibraryPanelDelegate func libraryPanelDidRequestToOpenInNewTab(_ url: URL, isPrivate: Bool) { diff --git a/firefox-ios/Client/Coordinators/Library/LibraryPanelCoordinatorDelegate.swift b/firefox-ios/Client/Coordinators/Library/LibraryPanelCoordinatorDelegate.swift new file mode 100644 index 0000000000000..13ba88ee5beca --- /dev/null +++ b/firefox-ios/Client/Coordinators/Library/LibraryPanelCoordinatorDelegate.swift @@ -0,0 +1,10 @@ +// 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 + +/// Share navigation across the library panels +protocol LibraryPanelCoordinatorDelegate: AnyObject { + func shareLibraryItem(url: URL, sourceView: UIView) +} diff --git a/firefox-ios/Client/Coordinators/Library/ReadingListCoordinator.swift b/firefox-ios/Client/Coordinators/Library/ReadingListCoordinator.swift index e7ae28d555197..86df77eb27eb4 100644 --- a/firefox-ios/Client/Coordinators/Library/ReadingListCoordinator.swift +++ b/firefox-ios/Client/Coordinators/Library/ReadingListCoordinator.swift @@ -5,7 +5,7 @@ import Foundation import Storage -protocol ReadingListNavigationHandler: AnyObject { +protocol ReadingListNavigationHandler: AnyObject, LibraryPanelCoordinatorDelegate { func openUrl(_ url: URL, visitType: VisitType) } @@ -13,14 +13,17 @@ class ReadingListCoordinator: BaseCoordinator, ReadingListNavigationHandler { // MARK: - Properties private weak var parentCoordinator: LibraryCoordinatorDelegate? + private weak var navigationHandler: LibraryNavigationHandler? // MARK: - Initializers init( parentCoordinator: LibraryCoordinatorDelegate?, + navigationHandler: LibraryNavigationHandler?, router: Router ) { self.parentCoordinator = parentCoordinator + self.navigationHandler = navigationHandler super.init(router: router) } @@ -29,4 +32,8 @@ class ReadingListCoordinator: BaseCoordinator, ReadingListNavigationHandler { func openUrl(_ url: URL, visitType: VisitType) { parentCoordinator?.libraryPanel(didSelectURL: url, visitType: visitType) } + + func shareLibraryItem(url: URL, sourceView: UIView) { + navigationHandler?.shareLibraryItem(url: url, sourceView: sourceView) + } } diff --git a/firefox-ios/Client/Frontend/Library/Bookmarks/BookmarksPanel.swift b/firefox-ios/Client/Frontend/Library/Bookmarks/BookmarksPanel.swift index f5c3b7201f223..aaadf8999ab95 100644 --- a/firefox-ios/Client/Frontend/Library/Bookmarks/BookmarksPanel.swift +++ b/firefox-ios/Client/Frontend/Library/Bookmarks/BookmarksPanel.swift @@ -542,6 +542,9 @@ extension BookmarksPanel: LibraryPanelContextMenu { }).items actions.append(removeAction) + let cell = tableView.cellForRow(at: indexPath) + actions.append(getShareAction(site: site, sourceView: cell ?? self.view, delegate: bookmarkCoordinatorDelegate)) + return actions } } diff --git a/firefox-ios/Client/Frontend/Library/HistoryPanel/HistoryPanel+ContextMenuExtensions.swift b/firefox-ios/Client/Frontend/Library/HistoryPanel/HistoryPanel+ContextMenuExtensions.swift deleted file mode 100644 index bf3da61e08359..0000000000000 --- a/firefox-ios/Client/Frontend/Library/HistoryPanel/HistoryPanel+ContextMenuExtensions.swift +++ /dev/null @@ -1,46 +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 Common -import UIKit -import Storage - -extension HistoryPanel: LibraryPanelContextMenu { - func presentContextMenu( - for site: Site, - with indexPath: IndexPath, - completionHandler: @escaping () -> PhotonActionSheet? - ) { - guard let contextMenu = completionHandler() else { return } - - present(contextMenu, animated: true, completion: nil) - } - - func getSiteDetails(for indexPath: IndexPath) -> Site? { - return siteAt(indexPath: indexPath) - } - - func getContextMenuActions(for site: Site, with indexPath: IndexPath) -> [PhotonRowActions]? { - guard var actions = getDefaultContextMenuActions( - for: site, - libraryPanelDelegate: libraryPanelDelegate - ) else { return nil } - - let removeAction = SingleActionViewModel(title: .DeleteFromHistoryContextMenuTitle, - iconString: StandardImageIdentifiers.Large.delete, - tapHandler: { _ in - self.removeHistoryItem(at: indexPath) - }) - - let pinTopSite = SingleActionViewModel(title: .AddToShortcutsActionTitle, - iconString: StandardImageIdentifiers.Large.pin, - tapHandler: { _ in - self.pinToTopSites(site) - }) - - actions.append(PhotonRowActions(pinTopSite)) - actions.append(PhotonRowActions(removeAction)) - return actions - } -} diff --git a/firefox-ios/Client/Frontend/Library/HistoryPanel/HistoryPanel.swift b/firefox-ios/Client/Frontend/Library/HistoryPanel/HistoryPanel.swift index cd4e67a39d7d4..fce429a99157c 100644 --- a/firefox-ios/Client/Frontend/Library/HistoryPanel/HistoryPanel.swift +++ b/firefox-ios/Client/Frontend/Library/HistoryPanel/HistoryPanel.swift @@ -18,6 +18,7 @@ private class FetchInProgressError: MaybeErrorType { @objcMembers class HistoryPanel: UIViewController, LibraryPanel, + LibraryPanelContextMenu, Themeable { struct UX { static let WelcomeScreenItemWidth = 170 @@ -629,6 +630,48 @@ class HistoryPanel: UIViewController, object: .selectedHistoryItem, value: .historyPanelNonGroupItem) } + + // MARK: - LibraryPanelContextMenu + + func presentContextMenu( + for site: Site, + with indexPath: IndexPath, + completionHandler: @escaping () -> PhotonActionSheet? + ) { + guard let contextMenu = completionHandler() else { return } + + present(contextMenu, animated: true, completion: nil) + } + + func getSiteDetails(for indexPath: IndexPath) -> Site? { + return siteAt(indexPath: indexPath) + } + + func getContextMenuActions(for site: Site, with indexPath: IndexPath) -> [PhotonRowActions]? { + guard var actions = getDefaultContextMenuActions( + for: site, + libraryPanelDelegate: libraryPanelDelegate + ) else { return nil } + + let removeAction = SingleActionViewModel(title: .DeleteFromHistoryContextMenuTitle, + iconString: StandardImageIdentifiers.Large.delete, + tapHandler: { _ in + self.removeHistoryItem(at: indexPath) + }) + + let pinTopSite = SingleActionViewModel(title: .AddToShortcutsActionTitle, + iconString: StandardImageIdentifiers.Large.pin, + tapHandler: { _ in + self.pinToTopSites(site) + }) + + actions.append(PhotonRowActions(pinTopSite)) + actions.append(PhotonRowActions(removeAction)) + + let cell = tableView.cellForRow(at: indexPath) + actions.append(getShareAction(site: site, sourceView: cell ?? self.view, delegate: historyCoordinatorDelegate)) + return actions + } } // MARK: - UITableViewDelegate related helpers diff --git a/firefox-ios/Client/Frontend/Library/LibraryPanelContextMenu.swift b/firefox-ios/Client/Frontend/Library/LibraryPanelContextMenu.swift index 49332b944d892..9fdb5cdaf9d16 100644 --- a/firefox-ios/Client/Frontend/Library/LibraryPanelContextMenu.swift +++ b/firefox-ios/Client/Frontend/Library/LibraryPanelContextMenu.swift @@ -9,6 +9,7 @@ import Shared protocol LibraryPanelContextMenu { func getSiteDetails(for indexPath: IndexPath) -> Site? func getContextMenuActions(for site: Site, with indexPath: IndexPath) -> [PhotonRowActions]? + func getShareAction(site: Site, sourceView: UIView, delegate: LibraryPanelCoordinatorDelegate?) -> PhotonRowActions func presentContextMenu(for indexPath: IndexPath) func presentContextMenu( for site: Site, @@ -104,4 +105,13 @@ extension LibraryPanelContextMenu { return [openInNewTabAction, openInNewPrivateTabAction] } + + func getShareAction(site: Site, sourceView: UIView, delegate: LibraryPanelCoordinatorDelegate?) -> PhotonRowActions { + return SingleActionViewModel( + title: .ShareContextMenuTitle, + iconString: StandardImageIdentifiers.Large.shareApple) { _ in + guard let siteURL = URL(string: site.url, invalidCharacters: false) else { return } + delegate?.shareLibraryItem(url: siteURL, sourceView: sourceView) + }.items + } } diff --git a/firefox-ios/Client/Frontend/Library/ReaderPanel.swift b/firefox-ios/Client/Frontend/Library/ReaderPanel.swift index 06da690ba629f..543c90165b80f 100644 --- a/firefox-ios/Client/Frontend/Library/ReaderPanel.swift +++ b/firefox-ios/Client/Frontend/Library/ReaderPanel.swift @@ -600,6 +600,9 @@ extension ReadingListPanel: LibraryPanelContextMenu { }).items actions.append(removeAction) + + let cell = tableView.cellForRow(at: indexPath) + actions.append(getShareAction(site: site, sourceView: cell ?? self.view, delegate: navigationHandler)) return actions } } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/BookmarksCoordinatorTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/BookmarksCoordinatorTests.swift index a08ecbdc56891..afaf510602f15 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/BookmarksCoordinatorTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/BookmarksCoordinatorTests.swift @@ -10,6 +10,7 @@ final class BookmarksCoordinatorTests: XCTestCase { private var router: MockRouter! private var profile: MockProfile! private var parentCoordinator: MockLibraryCoordinatorDelegate! + private var navigationHandler: MockLibraryNavigationHandler! override func setUp() { super.setUp() @@ -17,6 +18,7 @@ final class BookmarksCoordinatorTests: XCTestCase { router = MockRouter(navigationController: UINavigationController()) profile = MockProfile() parentCoordinator = MockLibraryCoordinatorDelegate() + navigationHandler = MockLibraryNavigationHandler() } override func tearDown() { @@ -25,6 +27,7 @@ final class BookmarksCoordinatorTests: XCTestCase { router = nil profile = nil parentCoordinator = nil + navigationHandler = nil } func testStart() { @@ -65,12 +68,26 @@ final class BookmarksCoordinatorTests: XCTestCase { XCTAssertEqual(router.pushCalled, 1) } + func testShowShareExtension_callsNavigationHandlerShareFunction() { + let subject = createSubject() + + subject.shareLibraryItem( + url: URL( + string: "https://www.google.com" + )!, + sourceView: UIView() + ) + + XCTAssertEqual(navigationHandler.didShareLibraryItemCalled, 1) + } + private func createSubject() -> BookmarksCoordinator { let subject = BookmarksCoordinator( router: router, profile: profile, windowUUID: .XCTestDefaultUUID, - parentCoordinator: parentCoordinator + parentCoordinator: parentCoordinator, + navigationHandler: navigationHandler ) trackForMemoryLeaks(subject) return subject diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/HistoryCoordinatorTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/HistoryCoordinatorTests.swift index e56660bbd5c80..a66744a7fb35b 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/HistoryCoordinatorTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/HistoryCoordinatorTests.swift @@ -11,6 +11,7 @@ final class HistoryCoordinatorTests: XCTestCase { private var profile: MockProfile! private var parentCoordinator: MockLibraryCoordinatorDelegate! private var notificationCenter: MockNotificationCenter! + private var navigationHandler: MockLibraryNavigationHandler! override func setUp() { super.setUp() @@ -19,6 +20,7 @@ final class HistoryCoordinatorTests: XCTestCase { profile = MockProfile() notificationCenter = MockNotificationCenter() parentCoordinator = MockLibraryCoordinatorDelegate() + navigationHandler = MockLibraryNavigationHandler() } override func tearDown() { @@ -28,6 +30,7 @@ final class HistoryCoordinatorTests: XCTestCase { profile = nil parentCoordinator = nil notificationCenter = nil + navigationHandler = nil } func testShowRecentlyClosedTabs() { @@ -57,13 +60,27 @@ final class HistoryCoordinatorTests: XCTestCase { XCTAssertEqual(notificationCenter.postCallCount, 1) } + func testShowShareExtension_callsNavigationHandlerShareFunction() { + let subject = createSubject() + + subject.shareLibraryItem( + url: URL( + string: "https://www.google.com" + )!, + sourceView: UIView() + ) + + XCTAssertEqual(navigationHandler.didShareLibraryItemCalled, 1) + } + private func createSubject() -> HistoryCoordinator { let subject = HistoryCoordinator( profile: profile, windowUUID: .XCTestDefaultUUID, router: router, notificationCenter: notificationCenter, - parentCoordinator: parentCoordinator + parentCoordinator: parentCoordinator, + navigationHandler: navigationHandler ) trackForMemoryLeaks(subject) return subject diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/LibraryCoordinatorTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/LibraryCoordinatorTests.swift index c50f55ba8d760..f7a9474376165 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/LibraryCoordinatorTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/LibraryCoordinatorTests.swift @@ -96,6 +96,22 @@ final class LibraryCoordinatorTests: XCTestCase { XCTAssertEqual(delegate.didFinishSettingsCalled, 1) } + func testShowShareExtension_addsShareExtensionCoordinator() { + let subject = createSubject() + + subject.shareLibraryItem( + url: URL( + string: "https://www.google.com" + )!, + sourceView: UIView() + ) + + XCTAssertEqual(subject.childCoordinators.count, 1) + XCTAssertTrue(subject.childCoordinators.first is ShareExtensionCoordinator) + XCTAssertEqual(mockRouter.presentCalled, 1) + XCTAssertTrue(mockRouter.presentedViewController is UIActivityViewController) + } + // MARK: - Helper func createSubject() -> LibraryCoordinator { let subject = LibraryCoordinator(router: mockRouter, tabManager: MockTabManager()) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/ReadingListCoordinatorTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/ReadingListCoordinatorTests.swift index 55116353eb7e4..f4aae5fe74799 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/ReadingListCoordinatorTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Library/ReadingListCoordinatorTests.swift @@ -9,17 +9,20 @@ import Storage final class ReadingListCoordinatorTests: XCTestCase { var router: MockRouter! var parentCoordinator: MockLibraryCoordinatorDelegate! + private var navigationHandler: MockLibraryNavigationHandler! override func setUp() { super.setUp() router = MockRouter(navigationController: UINavigationController()) parentCoordinator = MockLibraryCoordinatorDelegate() + navigationHandler = MockLibraryNavigationHandler() } override func tearDown() { super.tearDown() router = nil parentCoordinator = nil + navigationHandler = nil } func testOpenUrl() { @@ -35,9 +38,23 @@ final class ReadingListCoordinatorTests: XCTestCase { XCTAssertEqual(parentCoordinator.lastOpenedURL, urlToOpen) } + func testShowShareExtension_callsNavigationHandlerShareFunction() { + let subject = createSubject() + + subject.shareLibraryItem( + url: URL( + string: "https://www.google.com" + )!, + sourceView: UIView() + ) + + XCTAssertEqual(navigationHandler.didShareLibraryItemCalled, 1) + } + private func createSubject() -> ReadingListCoordinator { let subject = ReadingListCoordinator( parentCoordinator: parentCoordinator, + navigationHandler: navigationHandler, router: router ) trackForMemoryLeaks(subject) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Mocks/MockLibraryNavigationHandler.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Mocks/MockLibraryNavigationHandler.swift new file mode 100644 index 0000000000000..2d6289173a3ad --- /dev/null +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Mocks/MockLibraryNavigationHandler.swift @@ -0,0 +1,20 @@ +// 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 + +@testable import Client + +class MockLibraryNavigationHandler: LibraryNavigationHandler { + var didStartCalled = 0 + var didShareLibraryItemCalled = 0 + + func start(panelType: LibraryPanelType, navigationController: UINavigationController) { + didStartCalled += 1 + } + + func shareLibraryItem(url: URL, sourceView: UIView) { + didShareLibraryItemCalled += 1 + } +}