diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.swift b/DuckDuckGo/DuckPlayer/DuckPlayer.swift index df7e5ecf29..4192367a0d 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayer.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.swift @@ -81,15 +81,20 @@ public enum DuckPlayerReferrer { protocol DuckPlayerProtocol { var settings: DuckPlayerSettingsProtocol { get } + var hostView: UIViewController? { get } init(settings: DuckPlayerSettingsProtocol) func setUserValues(params: Any, message: WKScriptMessage) -> Encodable? func getUserValues(params: Any, message: WKScriptMessage) -> Encodable? func openVideoInDuckPlayer(url: URL, webView: WKWebView) + func openDuckPlayerSettings(params: Any, message: WKScriptMessage) async -> Encodable? + func openDuckPlayerInfo(params: Any, message: WKScriptMessage) async -> Encodable? func initialSetupPlayer(params: Any, message: WKScriptMessage) async -> Encodable? func initialSetupOverlay(params: Any, message: WKScriptMessage) async -> Encodable? + + func setHostViewController(_ vc: UIViewController) } final class DuckPlayer: DuckPlayerProtocol { @@ -98,11 +103,18 @@ final class DuckPlayer: DuckPlayerProtocol { static let commonName = "Duck Player" private(set) var settings: DuckPlayerSettingsProtocol + private(set) var hostView: UIViewController? init(settings: DuckPlayerSettingsProtocol = DuckPlayerSettings()) { self.settings = settings } + // Sets a presenting VC, so DuckPlayer can present the + // info sheet directly + public func setHostViewController(_ vc: UIViewController) { + hostView = vc + } + // MARK: - Common Message Handlers public func setUserValues(params: Any, message: WKScriptMessage) -> Encodable? { @@ -135,6 +147,26 @@ final class DuckPlayer: DuckPlayerProtocol { let webView = message.webView return await self.encodedPlayerSettings(with: webView) } + + public func openDuckPlayerSettings(params: Any, message: WKScriptMessage) async -> Encodable? { + NotificationCenter.default.post( + name: .settingsDeepLinkNotification, + object: SettingsViewModel.SettingsDeepLinkSection.duckPlayer, + userInfo: nil + ) + return nil + } + + @MainActor + public func presentDuckPlayerInfo() { + guard let hostView else { return } + DuckPlayerModalPresenter().presentDuckPlayerFeatureModal(on: hostView) + } + + public func openDuckPlayerInfo(params: Any, message: WKScriptMessage) async -> Encodable? { + await presentDuckPlayerInfo() + return nil + } private func encodeUserValues() -> UserValues { UserValues( diff --git a/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift b/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift index af283c3615..e85f0ee19b 100644 --- a/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift +++ b/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift @@ -35,6 +35,8 @@ final class YoutubePlayerUserScript: NSObject, Subfeature { static let setUserValues = "setUserValues" static let getUserValues = "getUserValues" static let initialSetup = "initialSetup" + static let openSettings = "openSettings" + static let openInfo = "openInfo" } init(duckPlayer: DuckPlayerProtocol) { @@ -73,6 +75,10 @@ final class YoutubePlayerUserScript: NSObject, Subfeature { return duckPlayer.setUserValues case Handlers.initialSetup: return duckPlayer.initialSetupPlayer + case Handlers.openSettings: + return duckPlayer.openDuckPlayerSettings + case Handlers.openInfo: + return duckPlayer.openDuckPlayerInfo default: assertionFailure("YoutubePlayerUserScript: Failed to parse User Script message: \(methodName)") return nil diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 4399ff1653..51675e1d04 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -113,6 +113,7 @@ class MainViewController: UIViewController { private var favoritesDisplayModeCancellable: AnyCancellable? private var emailCancellables = Set() private var urlInterceptorCancellables = Set() + private var settingsDeepLinkcancellables = Set() #if NETWORK_PROTECTION private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults @@ -261,6 +262,7 @@ class MainViewController: UIViewController { addLaunchTabNotificationObserver() subscribeToEmailProtectionStatusNotifications() subscribeToURLInterceptorNotifications() + subscribeToSettingsDeeplinkNotifications() #if NETWORK_PROTECTION subscribeToNetworkProtectionEvents() @@ -1349,6 +1351,23 @@ class MainViewController: UIViewController { } .store(in: &urlInterceptorCancellables) } + + private func subscribeToSettingsDeeplinkNotifications() { + NotificationCenter.default.publisher(for: .settingsDeepLinkNotification) + .receive(on: DispatchQueue.main) + .sink { [weak self] notification in + switch notification.object as? SettingsViewModel.SettingsDeepLinkSection { + + case .duckPlayer: + let deepLinkTarget: SettingsViewModel.SettingsDeepLinkSection + deepLinkTarget = .duckPlayer + self?.launchSettings(deepLinkTarget: deepLinkTarget) + default: + return + } + } + .store(in: &settingsDeepLinkcancellables) + } #if NETWORK_PROTECTION private func subscribeToNetworkProtectionEvents() { diff --git a/DuckDuckGo/SettingsRootView.swift b/DuckDuckGo/SettingsRootView.swift index 32d4599313..415ce3d3bd 100644 --- a/DuckDuckGo/SettingsRootView.swift +++ b/DuckDuckGo/SettingsRootView.swift @@ -117,6 +117,8 @@ struct SettingsRootView: View { SubscriptionContainerViewFactory.makeSubscribeFlow(origin: origin, navigationCoordinator: subscriptionNavigationCoordinator, subscriptionManager: AppDependencyProvider.shared.subscriptionManager) + case .duckPlayer: + SettingsDuckPlayerView().environmentObject(viewModel) default: EmptyView() } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index f437dd7d4f..a31df34ba0 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -634,6 +634,7 @@ extension SettingsViewModel { case dbp case itr case subscriptionFlow(origin: String? = nil) + case duckPlayer // Add other cases as needed var id: String { @@ -642,6 +643,7 @@ extension SettingsViewModel { case .dbp: return "dbp" case .itr: return "itr" case .subscriptionFlow: return "subscriptionFlow" + case .duckPlayer: return "duckPlayer" // Ensure all cases are covered } } @@ -791,3 +793,8 @@ extension SettingsViewModel { } } + +// Deeplink notification handling +extension NSNotification.Name { + static let settingsDeepLinkNotification: NSNotification.Name = Notification.Name(rawValue: "com.duckduckgo.notification.settingsDeepLink") +} diff --git a/DuckDuckGoTests/DuckPlayerMocks.swift b/DuckDuckGoTests/DuckPlayerMocks.swift index ed84be6f1d..09d95354a8 100644 --- a/DuckDuckGoTests/DuckPlayerMocks.swift +++ b/DuckDuckGoTests/DuckPlayerMocks.swift @@ -110,6 +110,18 @@ final class MockDuckPlayerSettings: DuckPlayerSettingsProtocol { } final class MockDuckPlayer: DuckPlayerProtocol { + var hostView: UIViewController? + + func openDuckPlayerSettings(params: Any, message: WKScriptMessage) async -> (any Encodable)? { + nil + } + + func openDuckPlayerInfo(params: Any, message: WKScriptMessage) async -> (any Encodable)? { + nil + } + + func setHostViewController(_ vc: UIViewController) {} + func initialSetupPlayer(params: Any, message: WKScriptMessage) async -> (any Encodable)? { nil }