Skip to content

Commit

Permalink
Move HTML NTP into a local package (#3633)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/72649045549333/1208909283620583/f

Description:
This change does not add any new feature and it only reorganizes the code adding NewTabPage local package
that contains all HTML NTP code that is independent of the rest of the app. In order to achieve that,
additional local packages were created: WebKitExtensions and Utilities, and a few more extension classes
were moved from the main app target to AppKitExtensions.
  • Loading branch information
ayoy authored Dec 9, 2024
1 parent 8ab609c commit b994bcb
Show file tree
Hide file tree
Showing 68 changed files with 1,464 additions and 977 deletions.
310 changes: 98 additions & 212 deletions DuckDuckGo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,16 @@
ReferencedContainer = "container:LocalPackages/AppKitExtensions">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "NewTabPageTests"
BuildableName = "NewTabPageTests"
BlueprintName = "NewTabPageTests"
ReferencedContainer = "container:LocalPackages/NewTabPage">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
Expand Down
18 changes: 10 additions & 8 deletions DuckDuckGo/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import FeatureFlags
import History
import MetricKit
import Networking
import NewTabPage
import Persistence
import PixelKit
import PixelExperimentKit
Expand Down Expand Up @@ -97,12 +98,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
private(set) lazy var newTabPageActionsManager: NewTabPageActionsManaging = NewTabPageActionsManager(
appearancePreferences: .shared,
activeRemoteMessageModel: activeRemoteMessageModel,
privacyStats: privacyStats,
openURLHandler: { url in
Task { @MainActor in
WindowControllersManager.shared.showTab(with: .contentFromURL(url, source: .appOpenUrl))
}
}
privacyStats: privacyStats
)
let privacyStats: PrivacyStatsCollecting
let activeRemoteMessageModel: ActiveRemoteMessageModel
Expand Down Expand Up @@ -267,13 +263,19 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager
)
)
activeRemoteMessageModel = ActiveRemoteMessageModel(remoteMessagingClient: remoteMessagingClient)
activeRemoteMessageModel = ActiveRemoteMessageModel(remoteMessagingClient: remoteMessagingClient, openURLHandler: { url in
WindowControllersManager.shared.showTab(with: .contentFromURL(url, source: .appOpenUrl))
})
} else {
// As long as remoteMessagingClient is private to App Delegate and activeRemoteMessageModel
// is used only by HomePage RootView as environment object,
// it's safe to not initialize the client for unit tests to avoid side effects.
remoteMessagingClient = nil
activeRemoteMessageModel = ActiveRemoteMessageModel(remoteMessagingStore: nil, remoteMessagingAvailabilityProvider: nil)
activeRemoteMessageModel = ActiveRemoteMessageModel(
remoteMessagingStore: nil,
remoteMessagingAvailabilityProvider: nil,
openURLHandler: { _ in }
)
}

featureFlagger = DefaultFeatureFlagger(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
//

import AppKit
import Utilities

typealias NSAttributedStringBuilder = ArrayBuilder<NSAttributedString>
extension NSAttributedString {
Expand Down
86 changes: 0 additions & 86 deletions DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,6 @@ extension NSMenuItem {
return item
}

convenience init(title string: String, action selector: Selector? = nil, target: AnyObject? = nil, keyEquivalent: NSEvent.KeyEquivalent = [], representedObject: Any? = nil, state: NSControl.StateValue = .off, items: [NSMenuItem]? = nil) {
self.init(title: string, action: selector, keyEquivalent: keyEquivalent.charCode)
if !keyEquivalent.modifierMask.isEmpty {
self.keyEquivalentModifierMask = keyEquivalent.modifierMask
}
self.target = target
self.representedObject = representedObject
self.state = state

if let items {
self.submenu = NSMenu(title: title, items: items)
}
}

convenience init(title string: String, action selector: Selector? = nil, target: AnyObject? = nil, keyEquivalent: NSEvent.KeyEquivalent = [], representedObject: Any? = nil, state: NSControl.StateValue = .off, @MenuBuilder items: () -> [NSMenuItem]) {
self.init(title: string, action: selector, target: target, keyEquivalent: keyEquivalent, representedObject: representedObject, state: state, items: items())
}

convenience init(action selector: Selector?) {
self.init()
self.action = selector
}

convenience init(bookmarkViewModel: BookmarkViewModel) {
self.init()

Expand All @@ -65,67 +42,4 @@ extension NSMenuItem {
representedObject = bookmarkViewModels
action = #selector(MainViewController.openAllInTabs(_:))
}

convenience init(title: String) {
self.init(title: title, action: nil, keyEquivalent: "")
}

var topMenu: NSMenu? {
var menuItem = self
while let parent = menuItem.parent {
menuItem = parent
}

return menuItem.menu
}

func removeFromParent() {
parent?.submenu?.removeItem(self)
}

@discardableResult
func alternate() -> NSMenuItem {
self.isAlternate = true
return self
}

@discardableResult
func hidden() -> NSMenuItem {
self.isHidden = true
if !keyEquivalent.isEmpty {
self.allowsKeyEquivalentWhenHidden = true
}
return self
}

@discardableResult
func submenu(_ submenu: NSMenu) -> NSMenuItem {
self.submenu = submenu
return self
}

@discardableResult
func withImage(_ image: NSImage?) -> NSMenuItem {
self.image = image
return self
}

@discardableResult
func targetting(_ target: AnyObject) -> NSMenuItem {
self.target = target
return self
}

@discardableResult
func withSubmenu(_ submenu: NSMenu) -> NSMenuItem {
self.submenu = submenu
return self
}

@discardableResult
func withModifierMask(_ mask: NSEvent.ModifierFlags) -> NSMenuItem {
self.keyEquivalentModifierMask = mask
return self
}

}
5 changes: 0 additions & 5 deletions DuckDuckGo/Common/Extensions/URLExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,6 @@ extension URL {
// base url for Error Page Alternate HTML loaded into Web View
static let error = URL(string: "duck://error")!

static func duckFavicon(for faviconURL: URL) -> URL? {
let encodedURL = faviconURL.absoluteString.percentEncoded(withAllowedCharacters: .urlPathAllowed)
return URL(string: "duck://favicon/\(encodedURL)")
}

static let dataBrokerProtection = URL(string: "duck://personal-information-removal")!

#if !SANDBOX_TEST_TOOL
Expand Down
1 change: 1 addition & 0 deletions DuckDuckGo/Common/View/SwiftUI/NSPopUpButtonView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import AppKit
import SwiftUI
import Utilities

struct PopupButtonItem<SelectionValue: Equatable>: Equatable {

Expand Down
67 changes: 67 additions & 0 deletions DuckDuckGo/NewTabPage/ActiveRemoteMessageModel+NewTabPage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// ActiveRemoteMessageModel+NewTabPage.swift
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Combine
import NewTabPage
import RemoteMessaging

extension ActiveRemoteMessageModel: NewTabPageActiveRemoteMessageProviding {
var remoteMessagePublisher: AnyPublisher<RemoteMessageModel?, Never> {
$remoteMessage.dropFirst().eraseToAnyPublisher()
}

func isMessageSupported(_ message: RemoteMessageModel) -> Bool {
return message.content?.isSupported == true
}

func handleAction(_ action: RemoteAction?, andDismissUsing button: RemoteMessageButton) async {
if let action {
await handleAction(action)
}
await dismissRemoteMessage(with: .init(button))
}

private func handleAction(_ remoteAction: RemoteAction) async {
switch remoteAction {
case .url(let value), .share(let value, _), .survey(let value):
if let url = URL.makeURL(from: value) {
await openURLHandler(url)
}
case .appStore:
await openURLHandler(.appStore)
default:
break
}
}
}

extension RemoteMessageViewModel.ButtonAction {

init(_ button: RemoteMessageButton) {
switch button {
case .close:
self = .close
case .action:
self = .action
case .primaryAction:
self = .primaryAction
case .secondaryAction:
self = .secondaryAction
}
}
}
48 changes: 48 additions & 0 deletions DuckDuckGo/NewTabPage/AppearancePreferences+NewTabPage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// AppearancePreferences+NewTabPage.swift
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Combine
import NewTabPage

extension AppearancePreferences: NewTabPageSectionsVisibilityProviding {
var isFavoritesVisible: Bool {
get {
isFavoriteVisible
}
set {
isFavoriteVisible = newValue
}
}

var isPrivacyStatsVisible: Bool {
get {
isRecentActivityVisible
}
set {
isRecentActivityVisible = newValue
}
}

var isFavoritesVisiblePublisher: AnyPublisher<Bool, Never> {
$isFavoriteVisible.dropFirst().removeDuplicates().eraseToAnyPublisher()
}

var isPrivacyStatsVisiblePublisher: AnyPublisher<Bool, Never> {
$isRecentActivityVisible.dropFirst().removeDuplicates().eraseToAnyPublisher()
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// NewTabPageNextStepsCardsProviding.swift
// ContinueSetUpModel+NewTabPage.swift
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
Expand All @@ -18,24 +18,9 @@

import Common
import Combine
import UserScript
import NewTabPage
import PixelKit

protocol NewTabPageNextStepsCardsProviding: AnyObject {
var isViewExpanded: Bool { get set }
var isViewExpandedPublisher: AnyPublisher<Bool, Never> { get }

var cards: [NewTabPageNextStepsCardsClient.CardID] { get }
var cardsPublisher: AnyPublisher<[NewTabPageNextStepsCardsClient.CardID], Never> { get }

@MainActor
func handleAction(for card: NewTabPageNextStepsCardsClient.CardID)

@MainActor
func dismiss(_ card: NewTabPageNextStepsCardsClient.CardID)

func willDisplayCards(_ cards: [NewTabPageNextStepsCardsClient.CardID])
}
import UserScript

extension HomePage.Models.ContinueSetUpModel: NewTabPageNextStepsCardsProviding {
var isViewExpanded: Bool {
Expand Down
Loading

0 comments on commit b994bcb

Please sign in to comment.