From 0aee49583c03bb1fa49f6f6633f21290f22ba3c1 Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Mon, 15 Jan 2024 19:37:51 +0000 Subject: [PATCH 01/14] Update embedded files --- .../AppPrivacyConfigurationDataProvider.swift | 4 +- DuckDuckGo/ContentBlocker/macos-config.json | 80 ++++++------------- 2 files changed, 26 insertions(+), 58 deletions(-) diff --git a/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift b/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift index 2dcb9fe3b5..3d57fe64dd 100644 --- a/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift +++ b/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift @@ -22,8 +22,8 @@ import BrowserServicesKit final class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"fec0af5a7d1e3ce584a6bc6243dd673c\"" - public static let embeddedDataSHA = "59b87e458e17fbd898d1b53d74b06bc0a32913143fa5e6ab91644a980a5dc661" + public static let embeddedDataETag = "\"f554a2ba0d10bb67736bfd05e231bd46\"" + public static let embeddedDataSHA = "eb8ef392f3bf3aec4c8784982808cf623d7fb65f606099a532647ed83347b0a5" } var embeddedDataEtag: String { diff --git a/DuckDuckGo/ContentBlocker/macos-config.json b/DuckDuckGo/ContentBlocker/macos-config.json index df721274d7..1423bf8e15 100644 --- a/DuckDuckGo/ContentBlocker/macos-config.json +++ b/DuckDuckGo/ContentBlocker/macos-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1705078221022, + "version": 1705334213687, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -260,6 +260,9 @@ { "domain": "forbes.com" }, + { + "domain": "tuc.org.uk" + }, { "domain": "earth.google.com" }, @@ -280,7 +283,7 @@ ] }, "state": "enabled", - "hash": "8749a4d02730a39a663091b7ce31ee1a" + "hash": "0e798c688c53c17c989fa3a41b8c9f65" }, "autofill": { "exceptions": [ @@ -4635,7 +4638,7 @@ "hash": "5e792dd491428702bc0104240fbce0ce" }, "sync": { - "state": "internal", + "state": "enabled", "features": { "level0ShowSync": { "state": "enabled" @@ -4651,7 +4654,8 @@ } }, "exceptions": [], - "hash": "4e4382e6a69f7cc99222fd924168e80f" + "minSupportedVersion": "1.70.0", + "hash": "0a35f748d63fc61a777ea5e712728fa3" }, "trackerAllowlist": { "state": "enabled", @@ -4882,39 +4886,9 @@ { "rule": "c.amazon-adsystem.com/aax2/apstag.js", "domains": [ - "4029tv.com", "applesfera.com", - "cnn.com", - "corriere.it", - "eurogamer.net", - "foxweather.com", - "kcci.com", - "kcra.com", - "ketv.com", - "kmbc.com", - "koat.com", - "koco.com", - "ksbw.com", - "mynbc5.com", - "seattletimes.com", "thesurfersview.com", - "wapt.com", - "wbaltv.com", - "wcvb.com", - "wdsu.com", - "wesh.com", - "wgal.com", - "wildrivers.lostcoastoutpost.com", - "wisn.com", - "wlky.com", - "wlwt.com", - "wmtw.com", - "wmur.com", - "wpbf.com", - "wtae.com", - "wvtm13.com", - "wxii12.com", - "wyff4.com" + "wildrivers.lostcoastoutpost.com" ] }, { @@ -5781,12 +5755,6 @@ }, "gemius.pl": { "rules": [ - { - "rule": "gapl.hit.gemius.pl/gplayer.js", - "domains": [ - "tvp.pl" - ] - }, { "rule": "pro.hit.gemius.pl/gstream.js", "domains": [ @@ -6214,16 +6182,6 @@ } ] }, - "htlbid.com": { - "rules": [ - { - "rule": "htlbid.com/v3/dangerousminds.net/htlbid.js", - "domains": [ - "dangerousminds.net" - ] - } - ] - }, "hubspot.com": { "rules": [ { @@ -7325,18 +7283,28 @@ }, "trustpilot.com": { "rules": [ + { + "rule": "widget.trustpilot.com/trustboxes/", + "domains": [ + "" + ] + }, + { + "rule": "widget.trustpilot.com/trustbox-data/", + "domains": [ + "" + ] + }, { "rule": "widget.trustpilot.com/bootstrap/v5/tp.widget.bootstrap.min.js", "domains": [ - "azurestandard.com", - "domesticandgeneral.com", - "www.hotpoint.co.uk" + "" ] }, { "rule": "widget.trustpilot.com/bootstrap/v5/tp.widget.sync.bootstrap.min.js", "domains": [ - "www.hotpoint.co.uk" + "" ] } ] @@ -7627,7 +7595,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "103d48713e9ec9b7e0fe23a1a8af6dd4" + "hash": "64bacd744ef069efd7206f48c684a102" }, "trackingCookies1p": { "settings": { From b12405f869d0bc66ce7f6f8f96227865b3754799 Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Mon, 15 Jan 2024 19:37:51 +0000 Subject: [PATCH 02/14] Set marketing version to 1.71.0 --- Configuration/Version.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 8d5fc78ebb..36dccc9575 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 1.70.0 +MARKETING_VERSION = 1.71.0 From 0812aea65e8e8faa25d8c680a916449de358d860 Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Mon, 15 Jan 2024 19:44:03 +0000 Subject: [PATCH 03/14] Bump version to 1.71.0 (104) --- Configuration/BuildNumber.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index c18660b73d..d7b5ba4c6e 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 103 +CURRENT_PROJECT_VERSION = 104 From f77ec1e3c4420417a6be1a805735419b46a83386 Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Tue, 16 Jan 2024 10:35:24 +0100 Subject: [PATCH 04/14] Don't override drop operation if source view is webView (#2058) Task/Issue URL: https://app.asana.com/0/1177771139624306/1206220522850248/f Description: For drags from webView to webView, apply default WebKit logic --- DuckDuckGo/Tab/View/WebView.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/DuckDuckGo/Tab/View/WebView.swift b/DuckDuckGo/Tab/View/WebView.swift index 0f671ab038..7526c6d911 100644 --- a/DuckDuckGo/Tab/View/WebView.swift +++ b/DuckDuckGo/Tab/View/WebView.swift @@ -176,6 +176,9 @@ final class WebView: WKWebView { // MARK: - NSDraggingDestination override func draggingUpdated(_ draggingInfo: NSDraggingInfo) -> NSDragOperation { + if draggingInfo.draggingSource is WebView { + return super.draggingUpdated(draggingInfo) + } if NSApp.isCommandPressed || NSApp.isOptionPressed { return superview?.draggingUpdated(draggingInfo) ?? .none } @@ -190,6 +193,9 @@ final class WebView: WKWebView { } override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool { + if draggingInfo.draggingSource is WebView { + return super.performDragOperation(draggingInfo) + } if NSApp.isCommandPressed || NSApp.isOptionPressed || super.draggingUpdated(draggingInfo) == .none { return superview?.performDragOperation(draggingInfo) ?? false } From 2a7d10d8d2b2c75cd6497b9d07e53432b4e2b3cb Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 16 Jan 2024 12:00:31 +0100 Subject: [PATCH 05/14] UI adjustments for improved VPN user control (#2043) Task/Issue URL: https://app.asana.com/0/0/1206318102194992/f BSK PR: https://github.com/duckduckgo/BrowserServicesKit/pull/622 iOS PR: https://github.com/duckduckgo/iOS/pull/2317 ## Description This PR brings some adjustments to recent VPN user control improvements. - If you hide the VPN menu app icon when the browser is showing VPN preferences, the VPN preferences "Show VPN in Menu Bar" will be updated dynamically. - The copy shown when right clicking on the VPN icon in the browser's navigation bar was adjusted to include "shortcut" at the end, like for other navigation bar buttons. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- DuckDuckGo/Common/Localizables/UserText.swift | 4 ++-- DuckDuckGo/Localizable.xcstrings | 4 ++-- .../NetworkProtectionNavBarButtonModel.swift | 20 +++++++------------ .../Model/VPNPreferencesModel.swift | 8 ++++++++ .../DataBrokerProtection/Package.swift | 2 +- LocalPackages/LoginItems/Package.swift | 2 +- .../NetworkProtectionMac/Package.swift | 2 +- .../Menu/StatusBarMenu.swift | 8 +++++++- LocalPackages/PixelKit/Package.swift | 2 +- LocalPackages/Subscription/Package.swift | 2 +- LocalPackages/SwiftUIExtensions/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- .../SystemExtensionManager/Package.swift | 2 +- LocalPackages/XPCHelper/Package.swift | 2 +- 16 files changed, 38 insertions(+), 30 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 17d734ee58..d9fe0f80cf 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -13127,7 +13127,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 101.1.0; + version = 101.1.1; }; }; AA06B6B52672AF8100F541C5 /* XCRemoteSwiftPackageReference "Sparkle" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 891b0a4d66..bc91def444 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "851187f38974b87889b21259eb442e95aedffafe", - "version" : "101.1.0" + "revision" : "202dc0540c214e21b89395370177873e090a7633", + "version" : "101.1.1" } }, { diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 222481ea96..4ad1d7cc30 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -828,8 +828,8 @@ struct UserText { static let showDownloadsShortcut = NSLocalizedString("pinning.show-downloads-shortcut", value: "Show Downloads Shortcut", comment: "Menu item for showing the downloads shortcut") static let hideDownloadsShortcut = NSLocalizedString("pinning.hide-downloads-shortcut", value: "Hide Downloads Shortcut", comment: "Menu item for hiding the downloads shortcut") - static let showNetworkProtectionShortcut = NSLocalizedString("pinning.show-netp-shortcut", value: "Show Network Protection", comment: "Menu item for showing the NetP shortcut") - static let hideNetworkProtectionShortcut = NSLocalizedString("pinning.hide-netp-shortcut", value: "Hide Network Protection", comment: "Menu item for hiding the NetP shortcut") + static let showNetworkProtectionShortcut = NSLocalizedString("pinning.show-netp-shortcut", value: "Show VPN Shortcut", comment: "Menu item for showing the NetP shortcut") + static let hideNetworkProtectionShortcut = NSLocalizedString("pinning.hide-netp-shortcut", value: "Hide VPN Shortcut", comment: "Menu item for hiding the NetP shortcut") static let showHomeShortcut = NSLocalizedString("pinning.show-home-shortcut", value: "Show Home Button", comment: "Menu item for showing the Home shortcut") static let hideHomeShortcut = NSLocalizedString("pinning.hide-home-shortcut", value: "Hide Home Button", comment: "Menu item for hiding the Home shortcut") diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index bf5c659264..3edb91af1a 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -6471,7 +6471,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Hide Network Protection" + "value" : "Hide VPN Shortcut" } } } @@ -6531,7 +6531,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Show Network Protection" + "value" : "Show VPN Shortcut" } } } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift index c279463b33..df8396f857 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift @@ -219,19 +219,15 @@ final class NetworkProtectionNavBarButtonModel: NSObject, ObservableObject { } guard !isPinned, - !popoverManager.isShown else { + !popoverManager.isShown, + !isHavingConnectivityIssues else { + + pinNetworkProtectionToNavBarIfNeverPinnedBefore() showButton = true return } - Task { - guard !isHavingConnectivityIssues else { - showButton = true - return - } - - showButton = false - } + showButton = false } // MARK: - Pinning @@ -245,10 +241,8 @@ final class NetworkProtectionNavBarButtonModel: NSObject, ObservableObject { /// if the user hasn't toggled it manually before. /// private func pinNetworkProtectionToNavBarIfNeverPinnedBefore() { - assert(showButton) - - guard !pinningManager.wasManuallyToggled(.networkProtection), - !pinningManager.isPinned(.networkProtection) else { + guard !pinningManager.isPinned(.networkProtection), + !pinningManager.wasManuallyToggled(.networkProtection) else { return } diff --git a/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift b/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift index 09d228cb82..b24924ad17 100644 --- a/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift +++ b/DuckDuckGo/Preferences/Model/VPNPreferencesModel.swift @@ -84,6 +84,7 @@ final class VPNPreferencesModel: ObservableObject { shouldShowLocationItem = featureFlagger.isFeatureOn(.vpnGeoswitching) subscribeToOnboardingStatusChanges(defaults: defaults) + subscribeToShowInMenuBarSettingChanges() subscribeToLocationSettingChanges() } @@ -93,6 +94,13 @@ final class VPNPreferencesModel: ObservableObject { .store(in: &cancellables) } + func subscribeToShowInMenuBarSettingChanges() { + settings.showInMenuBarPublisher + .removeDuplicates() + .assign(to: \.showInMenuBar, onWeaklyHeld: self) + .store(in: &cancellables) + } + func subscribeToLocationSettingChanges() { settings.selectedLocationPublisher .map(VPNLocationPreferenceItemModel.init(selectedLocation:)) diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index 102f8d558e..b7820bcf1f 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), .package(path: "../PixelKit"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper") diff --git a/LocalPackages/LoginItems/Package.swift b/LocalPackages/LoginItems/Package.swift index 13e1781cf0..f581530457 100644 --- a/LocalPackages/LoginItems/Package.swift +++ b/LocalPackages/LoginItems/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), ], targets: [ .target( diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index fc1eb57ca5..8e74c1c78c 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -30,7 +30,7 @@ let package = Package( .library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions") ], diff --git a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift index e76ad3b34b..5fc2984894 100644 --- a/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift +++ b/LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Menu/StatusBarMenu.swift @@ -123,8 +123,14 @@ public final class StatusBarMenu: NSObject { menu.delegate = self menu.items = model.contextMenuItems + // I'm not sure why +8 is needed, but that seems to be the right positioning to make this work well + // across all systems. I'm seeing an issue where the menu looks right for me but not for others testing + // this, and this seems to fix it: + // Ref: https://app.asana.com/0/0/1206318017787812/1206336583680668/f + let yPosition = statusItem.statusBar!.thickness + 8 + menu.popUp(positioning: nil, - at: .zero, + at: NSPoint(x: 0, y: yPosition), in: statusItem.button) } diff --git a/LocalPackages/PixelKit/Package.swift b/LocalPackages/PixelKit/Package.swift index 721c89effc..bb165c3ebb 100644 --- a/LocalPackages/PixelKit/Package.swift +++ b/LocalPackages/PixelKit/Package.swift @@ -20,7 +20,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), ], targets: [ .target( diff --git a/LocalPackages/Subscription/Package.swift b/LocalPackages/Subscription/Package.swift index 6736cfdf0d..451e14bdda 100644 --- a/LocalPackages/Subscription/Package.swift +++ b/LocalPackages/Subscription/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["Subscription"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), ], targets: [ .target( diff --git a/LocalPackages/SwiftUIExtensions/Package.swift b/LocalPackages/SwiftUIExtensions/Package.swift index 421153d210..9c5d3f6c85 100644 --- a/LocalPackages/SwiftUIExtensions/Package.swift +++ b/LocalPackages/SwiftUIExtensions/Package.swift @@ -11,7 +11,7 @@ let package = Package( .library(name: "PreferencesViews", targets: ["PreferencesViews"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index f02b1cdaa7..400a0d9b9f 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -13,7 +13,7 @@ let package = Package( ], dependencies: [ .package(path: "../SwiftUIExtensions"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), ], targets: [ .target( diff --git a/LocalPackages/SystemExtensionManager/Package.swift b/LocalPackages/SystemExtensionManager/Package.swift index 8810e97a38..a5128ceae7 100644 --- a/LocalPackages/SystemExtensionManager/Package.swift +++ b/LocalPackages/SystemExtensionManager/Package.swift @@ -16,7 +16,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. diff --git a/LocalPackages/XPCHelper/Package.swift b/LocalPackages/XPCHelper/Package.swift index de0e27728d..eb1d236844 100644 --- a/LocalPackages/XPCHelper/Package.swift +++ b/LocalPackages/XPCHelper/Package.swift @@ -30,7 +30,7 @@ let package = Package( .library(name: "XPCHelper", targets: ["XPCHelper"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. From faecf1d7a086b2232edae2ffa5237bb3d8bb7e89 Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Tue, 16 Jan 2024 08:59:51 -0600 Subject: [PATCH 06/14] Better detection of AMP pages (#2001) Task/Issue URL: https://app.asana.com/0/1202198075905303/1202190330709645/f Tech Design URL: CC: **Description**: **Steps to test this PR**: 1. See BSK PR --- ###### Internal references: [Pull Request Review Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f) [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) [Pull Request Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- LocalPackages/DataBrokerProtection/Package.swift | 2 +- LocalPackages/LoginItems/Package.swift | 2 +- LocalPackages/NetworkProtectionMac/Package.swift | 2 +- LocalPackages/PixelKit/Package.swift | 2 +- LocalPackages/Subscription/Package.swift | 2 +- LocalPackages/SwiftUIExtensions/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/SystemExtensionManager/Package.swift | 2 +- LocalPackages/XPCHelper/Package.swift | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d9fe0f80cf..c943891ea8 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -13127,7 +13127,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 101.1.1; + version = 101.1.2; }; }; AA06B6B52672AF8100F541C5 /* XCRemoteSwiftPackageReference "Sparkle" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index bc91def444..1fb4046733 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "202dc0540c214e21b89395370177873e090a7633", - "version" : "101.1.1" + "revision" : "44569e233945c099887266c1d7b8e07b25558a02", + "version" : "101.1.2" } }, { diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index b7820bcf1f..2eaa312d2f 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.2"), .package(path: "../PixelKit"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper") diff --git a/LocalPackages/LoginItems/Package.swift b/LocalPackages/LoginItems/Package.swift index f581530457..382343e341 100644 --- a/LocalPackages/LoginItems/Package.swift +++ b/LocalPackages/LoginItems/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.2"), ], targets: [ .target( diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 8e74c1c78c..1a787ff8fc 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -30,7 +30,7 @@ let package = Package( .library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.2"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions") ], diff --git a/LocalPackages/PixelKit/Package.swift b/LocalPackages/PixelKit/Package.swift index bb165c3ebb..6459182c89 100644 --- a/LocalPackages/PixelKit/Package.swift +++ b/LocalPackages/PixelKit/Package.swift @@ -20,7 +20,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.2"), ], targets: [ .target( diff --git a/LocalPackages/Subscription/Package.swift b/LocalPackages/Subscription/Package.swift index 451e14bdda..6754cc90e9 100644 --- a/LocalPackages/Subscription/Package.swift +++ b/LocalPackages/Subscription/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["Subscription"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.2"), ], targets: [ .target( diff --git a/LocalPackages/SwiftUIExtensions/Package.swift b/LocalPackages/SwiftUIExtensions/Package.swift index 9c5d3f6c85..11ecf7a69b 100644 --- a/LocalPackages/SwiftUIExtensions/Package.swift +++ b/LocalPackages/SwiftUIExtensions/Package.swift @@ -11,7 +11,7 @@ let package = Package( .library(name: "PreferencesViews", targets: ["PreferencesViews"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.2"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 400a0d9b9f..a5d0bf2fc2 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -13,7 +13,7 @@ let package = Package( ], dependencies: [ .package(path: "../SwiftUIExtensions"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.2"), ], targets: [ .target( diff --git a/LocalPackages/SystemExtensionManager/Package.swift b/LocalPackages/SystemExtensionManager/Package.swift index a5128ceae7..67e2e68c60 100644 --- a/LocalPackages/SystemExtensionManager/Package.swift +++ b/LocalPackages/SystemExtensionManager/Package.swift @@ -16,7 +16,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.2"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. diff --git a/LocalPackages/XPCHelper/Package.swift b/LocalPackages/XPCHelper/Package.swift index eb1d236844..e58d75b8aa 100644 --- a/LocalPackages/XPCHelper/Package.swift +++ b/LocalPackages/XPCHelper/Package.swift @@ -30,7 +30,7 @@ let package = Package( .library(name: "XPCHelper", targets: ["XPCHelper"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "101.1.2"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. From 2c05719680c9663783428200542291978c081cc2 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Tue, 16 Jan 2024 22:24:22 +0600 Subject: [PATCH 07/14] Add bookmark popover in SwiftUI (#2036) Task/Issue URL: https://app.asana.com/0/276630244458377/1206189080947121/f --- DuckDuckGo.xcodeproj/project.pbxproj | 96 ++++-- DuckDuckGo/Bookmarks/Model/Bookmark.swift | 2 +- .../View/AddBookmarkFolderModalView.swift | 2 +- .../View/AddBookmarkFolderPopoverView.swift | 98 ++++++ .../Bookmarks/View/AddBookmarkPopover.swift | 76 +++++ .../View/AddBookmarkPopoverView.swift | 137 +++++++++ ...okmarkAddFolderPopoverViewController.swift | 100 ------ .../BookmarkAddPopoverViewController.swift | 156 ---------- .../Bookmarks/View/BookmarkFolderPicker.swift | 56 ++++ .../Bookmarks/View/BookmarkPopover.swift | 148 --------- .../Bookmarks/View/Bookmarks.storyboard | 284 +----------------- .../AddBookmarkFolderPopoverViewModel.swift | 69 +++++ .../AddBookmarkPopoverViewModel.swift | 149 +++++++++ .../Common/Extensions/NSMenuExtension.swift | 12 + DuckDuckGo/Common/Localizables/UserText.swift | 2 + .../Common/Utilities/ArrayBuilder.swift | 92 ++++++ .../View/SwiftUI/FocusableTextField.swift | 61 ++++ .../View/SwiftUI/NSPopUpButtonView.swift | 72 ++++- DuckDuckGo/Localizable.xcstrings | 39 ++- DuckDuckGo/Menus/MenuBuilder.swift | 71 ----- .../AddressBarButtonsViewController.swift | 24 +- 21 files changed, 940 insertions(+), 806 deletions(-) create mode 100644 DuckDuckGo/Bookmarks/View/AddBookmarkFolderPopoverView.swift create mode 100644 DuckDuckGo/Bookmarks/View/AddBookmarkPopover.swift create mode 100644 DuckDuckGo/Bookmarks/View/AddBookmarkPopoverView.swift delete mode 100644 DuckDuckGo/Bookmarks/View/BookmarkAddFolderPopoverViewController.swift delete mode 100644 DuckDuckGo/Bookmarks/View/BookmarkAddPopoverViewController.swift create mode 100644 DuckDuckGo/Bookmarks/View/BookmarkFolderPicker.swift delete mode 100644 DuckDuckGo/Bookmarks/View/BookmarkPopover.swift create mode 100644 DuckDuckGo/Bookmarks/ViewModel/AddBookmarkFolderPopoverViewModel.swift create mode 100644 DuckDuckGo/Bookmarks/ViewModel/AddBookmarkPopoverViewModel.swift create mode 100644 DuckDuckGo/Common/Utilities/ArrayBuilder.swift create mode 100644 DuckDuckGo/Common/View/SwiftUI/FocusableTextField.swift delete mode 100644 DuckDuckGo/Menus/MenuBuilder.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c943891ea8..8a4f9cf27c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -359,7 +359,7 @@ 3706FB82293F65D500E42796 /* PasswordManagementNoteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE65472271FCD40008D1D63 /* PasswordManagementNoteItemView.swift */; }; 3706FB83293F65D500E42796 /* NSApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5C8F622591021700748EB7 /* NSApplicationExtension.swift */; }; 3706FB84293F65D500E42796 /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9E9A5525A3AE8400D1959D /* NSWindowExtension.swift */; }; - 3706FB85293F65D500E42796 /* BookmarkPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4C425D6A6E8007F5990 /* BookmarkPopover.swift */; }; + 3706FB85293F65D500E42796 /* AddBookmarkPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4C425D6A6E8007F5990 /* AddBookmarkPopover.swift */; }; 3706FB86293F65D500E42796 /* PreferencesDownloadsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC53EF27E8D1440028713D /* PreferencesDownloadsView.swift */; }; 3706FB87293F65D500E42796 /* ProcessExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C2FB127706E6A00BF2C7D /* ProcessExtension.swift */; }; 3706FB88293F65D500E42796 /* PermissionAuthorizationQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106BA526A7BEC80013B453 /* PermissionAuthorizationQuery.swift */; }; @@ -1568,7 +1568,7 @@ 4B957A802AC7AE700062CA31 /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9E9A5525A3AE8400D1959D /* NSWindowExtension.swift */; }; 4B957A812AC7AE700062CA31 /* KeychainType+ClientDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */; }; 4B957A822AC7AE700062CA31 /* SyncDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 370A34B02AB24E3700C77F7C /* SyncDebugMenu.swift */; }; - 4B957A832AC7AE700062CA31 /* BookmarkPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4C425D6A6E8007F5990 /* BookmarkPopover.swift */; }; + 4B957A832AC7AE700062CA31 /* AddBookmarkPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4C425D6A6E8007F5990 /* AddBookmarkPopover.swift */; }; 4B957A842AC7AE700062CA31 /* PreferencesDownloadsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC53EF27E8D1440028713D /* PreferencesDownloadsView.swift */; }; 4B957A852AC7AE700062CA31 /* QRSharingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F7127D29F6779000594A45 /* QRSharingService.swift */; }; 4B957A862AC7AE700062CA31 /* ProcessExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C2FB127706E6A00BF2C7D /* ProcessExtension.swift */; }; @@ -2211,12 +2211,12 @@ 7BE146072A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */; }; 7BE146082A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */; }; 7BEC182F2AD5D8DC00D30536 /* SystemExtensionManager in Frameworks */ = {isa = PBXBuildFile; productRef = 7BEC182E2AD5D8DC00D30536 /* SystemExtensionManager */; }; - 7BEC20422B0F505F00243D3E /* BookmarkAddPopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BEC20402B0F505F00243D3E /* BookmarkAddPopoverViewController.swift */; }; - 7BEC20432B0F505F00243D3E /* BookmarkAddPopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BEC20402B0F505F00243D3E /* BookmarkAddPopoverViewController.swift */; }; - 7BEC20442B0F505F00243D3E /* BookmarkAddPopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BEC20402B0F505F00243D3E /* BookmarkAddPopoverViewController.swift */; }; - 7BEC20452B0F505F00243D3E /* BookmarkAddFolderPopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BEC20412B0F505F00243D3E /* BookmarkAddFolderPopoverViewController.swift */; }; - 7BEC20462B0F505F00243D3E /* BookmarkAddFolderPopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BEC20412B0F505F00243D3E /* BookmarkAddFolderPopoverViewController.swift */; }; - 7BEC20472B0F505F00243D3E /* BookmarkAddFolderPopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BEC20412B0F505F00243D3E /* BookmarkAddFolderPopoverViewController.swift */; }; + 7BEC20422B0F505F00243D3E /* AddBookmarkPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BEC20402B0F505F00243D3E /* AddBookmarkPopoverView.swift */; }; + 7BEC20432B0F505F00243D3E /* AddBookmarkPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BEC20402B0F505F00243D3E /* AddBookmarkPopoverView.swift */; }; + 7BEC20442B0F505F00243D3E /* AddBookmarkPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BEC20402B0F505F00243D3E /* AddBookmarkPopoverView.swift */; }; + 7BEC20452B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BEC20412B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift */; }; + 7BEC20462B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BEC20412B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift */; }; + 7BEC20472B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BEC20412B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift */; }; 7BEEA5122AD1235B00A9E72B /* NetworkProtectionIPC in Frameworks */ = {isa = PBXBuildFile; productRef = 7BEEA5112AD1235B00A9E72B /* NetworkProtectionIPC */; }; 7BEEA5142AD1236300A9E72B /* NetworkProtectionIPC in Frameworks */ = {isa = PBXBuildFile; productRef = 7BEEA5132AD1236300A9E72B /* NetworkProtectionIPC */; }; 7BEEA5162AD1236E00A9E72B /* NetworkProtectionUI in Frameworks */ = {isa = PBXBuildFile; productRef = 7BEEA5152AD1236E00A9E72B /* NetworkProtectionUI */; }; @@ -2490,7 +2490,7 @@ AAC30A2A268E239100D2D9CD /* CrashReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC30A29268E239100D2D9CD /* CrashReport.swift */; }; AAC30A2C268F1ECD00D2D9CD /* CrashReportSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC30A2B268F1ECD00D2D9CD /* CrashReportSender.swift */; }; AAC30A2E268F1EE300D2D9CD /* CrashReportPromptPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC30A2D268F1EE300D2D9CD /* CrashReportPromptPresenter.swift */; }; - AAC5E4C725D6A6E8007F5990 /* BookmarkPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4C425D6A6E8007F5990 /* BookmarkPopover.swift */; }; + AAC5E4C725D6A6E8007F5990 /* AddBookmarkPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4C425D6A6E8007F5990 /* AddBookmarkPopover.swift */; }; AAC5E4C925D6A6E8007F5990 /* Bookmarks.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AAC5E4C625D6A6E8007F5990 /* Bookmarks.storyboard */; }; AAC5E4D025D6A709007F5990 /* Bookmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4CD25D6A709007F5990 /* Bookmark.swift */; }; AAC5E4D125D6A709007F5990 /* BookmarkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4CE25D6A709007F5990 /* BookmarkManager.swift */; }; @@ -2622,9 +2622,9 @@ B62B48392ADE46FC000DECE5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B48382ADE46FC000DECE5 /* Application.swift */; }; B62B483A2ADE46FC000DECE5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B48382ADE46FC000DECE5 /* Application.swift */; }; B62B483C2ADE46FC000DECE5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B48382ADE46FC000DECE5 /* Application.swift */; }; - B62B483E2ADE48DE000DECE5 /* MenuBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B483D2ADE48DE000DECE5 /* MenuBuilder.swift */; }; - B62B483F2ADE48DE000DECE5 /* MenuBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B483D2ADE48DE000DECE5 /* MenuBuilder.swift */; }; - B62B48412ADE48DE000DECE5 /* MenuBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B483D2ADE48DE000DECE5 /* MenuBuilder.swift */; }; + B62B483E2ADE48DE000DECE5 /* ArrayBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B483D2ADE48DE000DECE5 /* ArrayBuilder.swift */; }; + B62B483F2ADE48DE000DECE5 /* ArrayBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B483D2ADE48DE000DECE5 /* ArrayBuilder.swift */; }; + B62B48412ADE48DE000DECE5 /* ArrayBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B483D2ADE48DE000DECE5 /* ArrayBuilder.swift */; }; B62B48562ADE730D000DECE5 /* FileImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B48552ADE730D000DECE5 /* FileImportView.swift */; }; B62B48572ADE730D000DECE5 /* FileImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B48552ADE730D000DECE5 /* FileImportView.swift */; }; B62B48592ADE730D000DECE5 /* FileImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B62B48552ADE730D000DECE5 /* FileImportView.swift */; }; @@ -2801,6 +2801,15 @@ B696AFFB2AC5924800C93203 /* FileLineError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B696AFFA2AC5924800C93203 /* FileLineError.swift */; }; B696AFFC2AC5924800C93203 /* FileLineError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B696AFFA2AC5924800C93203 /* FileLineError.swift */; }; B698E5042908011E00A746A8 /* AppKitPrivateMethodsAvailabilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B698E5032908011E00A746A8 /* AppKitPrivateMethodsAvailabilityTests.swift */; }; + B69A14F22B4D6FE800B9417D /* AddBookmarkFolderPopoverViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69A14F12B4D6FE800B9417D /* AddBookmarkFolderPopoverViewModel.swift */; }; + B69A14F32B4D6FE800B9417D /* AddBookmarkFolderPopoverViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69A14F12B4D6FE800B9417D /* AddBookmarkFolderPopoverViewModel.swift */; }; + B69A14F42B4D6FE800B9417D /* AddBookmarkFolderPopoverViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69A14F12B4D6FE800B9417D /* AddBookmarkFolderPopoverViewModel.swift */; }; + B69A14F62B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69A14F52B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift */; }; + B69A14F72B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69A14F52B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift */; }; + B69A14F82B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69A14F52B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift */; }; + B69A14FA2B4D705D00B9417D /* BookmarkFolderPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69A14F92B4D705D00B9417D /* BookmarkFolderPicker.swift */; }; + B69A14FB2B4D705D00B9417D /* BookmarkFolderPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69A14F92B4D705D00B9417D /* BookmarkFolderPicker.swift */; }; + B69A14FC2B4D705D00B9417D /* BookmarkFolderPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69A14F92B4D705D00B9417D /* BookmarkFolderPicker.swift */; }; B69B503A2726A12500758A2B /* StatisticsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50342726A11F00758A2B /* StatisticsLoader.swift */; }; B69B503B2726A12500758A2B /* Atb.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50352726A11F00758A2B /* Atb.swift */; }; B69B503C2726A12500758A2B /* StatisticsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50362726A12000758A2B /* StatisticsStore.swift */; }; @@ -2837,6 +2846,9 @@ B6AA64742994B43300D99CD6 /* FutureExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AA64722994B43300D99CD6 /* FutureExtensionTests.swift */; }; B6AAAC2D260330580029438D /* PublishedAfter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AAAC2C260330580029438D /* PublishedAfter.swift */; }; B6AAAC3E26048F690029438D /* RandomAccessCollectionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AAAC3D26048F690029438D /* RandomAccessCollectionExtension.swift */; }; + B6ABC5962B4861D4008343B9 /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */; }; + B6ABC5972B4861D4008343B9 /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */; }; + B6ABC5982B4861D4008343B9 /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */; }; B6AE39F129373AF200C37AA4 /* EmptyAttributionRulesProver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AE39F029373AF200C37AA4 /* EmptyAttributionRulesProver.swift */; }; B6AE39F329374AEC00C37AA4 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = B6AE39F229374AEC00C37AA4 /* OHHTTPStubs */; }; B6AE39F529374AEC00C37AA4 /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B6AE39F429374AEC00C37AA4 /* OHHTTPStubsSwift */; }; @@ -3718,8 +3730,8 @@ 7BD8679A2A9E9E000063B9F7 /* NetworkProtectionFeatureVisibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureVisibility.swift; sourceTree = ""; }; 7BE146062A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDebugMenu.swift; sourceTree = ""; }; 7BEC182D2AD5D89C00D30536 /* SystemExtensionManager */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SystemExtensionManager; sourceTree = ""; }; - 7BEC20402B0F505F00243D3E /* BookmarkAddPopoverViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkAddPopoverViewController.swift; sourceTree = ""; }; - 7BEC20412B0F505F00243D3E /* BookmarkAddFolderPopoverViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkAddFolderPopoverViewController.swift; sourceTree = ""; }; + 7BEC20402B0F505F00243D3E /* AddBookmarkPopoverView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddBookmarkPopoverView.swift; sourceTree = ""; }; + 7BEC20412B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddBookmarkFolderPopoverView.swift; sourceTree = ""; }; 7BF1A9D72AE054D300FCA683 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7BFE95512A9DF1CE0081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift; sourceTree = ""; }; 7BFE95532A9DF2930081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+NetworkProtectionWaitlist.swift"; sourceTree = ""; }; @@ -3970,7 +3982,7 @@ AAC30A29268E239100D2D9CD /* CrashReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReport.swift; sourceTree = ""; }; AAC30A2B268F1ECD00D2D9CD /* CrashReportSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportSender.swift; sourceTree = ""; }; AAC30A2D268F1EE300D2D9CD /* CrashReportPromptPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportPromptPresenter.swift; sourceTree = ""; }; - AAC5E4C425D6A6E8007F5990 /* BookmarkPopover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkPopover.swift; sourceTree = ""; }; + AAC5E4C425D6A6E8007F5990 /* AddBookmarkPopover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddBookmarkPopover.swift; sourceTree = ""; }; AAC5E4C625D6A6E8007F5990 /* Bookmarks.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Bookmarks.storyboard; sourceTree = ""; }; AAC5E4CD25D6A709007F5990 /* Bookmark.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bookmark.swift; sourceTree = ""; }; AAC5E4CE25D6A709007F5990 /* BookmarkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkManager.swift; sourceTree = ""; }; @@ -4062,7 +4074,7 @@ B62A233B29C322BC00D22475 /* NavigationProtectionIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationProtectionIntegrationTests.swift; sourceTree = ""; }; B62A233F29C41D4400D22475 /* HistoryIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryIntegrationTests.swift; sourceTree = ""; }; B62B48382ADE46FC000DECE5 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; - B62B483D2ADE48DE000DECE5 /* MenuBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBuilder.swift; sourceTree = ""; }; + B62B483D2ADE48DE000DECE5 /* ArrayBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayBuilder.swift; sourceTree = ""; }; B62B48552ADE730D000DECE5 /* FileImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileImportView.swift; sourceTree = ""; }; B62EB47B25BAD3BB005745C6 /* WKWebViewPrivateMethodsAvailabilityTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKWebViewPrivateMethodsAvailabilityTests.swift; sourceTree = ""; }; B630793926731F2600DCEE41 /* FileDownloadManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileDownloadManagerTests.swift; sourceTree = ""; }; @@ -4178,6 +4190,9 @@ B693956826F352DB0015B914 /* DownloadsWebViewMock.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DownloadsWebViewMock.m; sourceTree = ""; }; B696AFFA2AC5924800C93203 /* FileLineError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileLineError.swift; sourceTree = ""; }; B698E5032908011E00A746A8 /* AppKitPrivateMethodsAvailabilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppKitPrivateMethodsAvailabilityTests.swift; sourceTree = ""; }; + B69A14F12B4D6FE800B9417D /* AddBookmarkFolderPopoverViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddBookmarkFolderPopoverViewModel.swift; sourceTree = ""; }; + B69A14F52B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddBookmarkPopoverViewModel.swift; sourceTree = ""; }; + B69A14F92B4D705D00B9417D /* BookmarkFolderPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkFolderPicker.swift; sourceTree = ""; }; B69B50342726A11F00758A2B /* StatisticsLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatisticsLoader.swift; sourceTree = ""; }; B69B50352726A11F00758A2B /* Atb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atb.swift; sourceTree = ""; }; B69B50362726A12000758A2B /* StatisticsStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatisticsStore.swift; sourceTree = ""; }; @@ -4211,6 +4226,7 @@ B6AA64722994B43300D99CD6 /* FutureExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FutureExtensionTests.swift; sourceTree = ""; }; B6AAAC2C260330580029438D /* PublishedAfter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishedAfter.swift; sourceTree = ""; }; B6AAAC3D26048F690029438D /* RandomAccessCollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomAccessCollectionExtension.swift; sourceTree = ""; }; + B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusableTextField.swift; sourceTree = ""; }; B6AE39F029373AF200C37AA4 /* EmptyAttributionRulesProver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyAttributionRulesProver.swift; sourceTree = ""; }; B6AE74332609AFCE005B9B1A /* ProgressEstimationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressEstimationTests.swift; sourceTree = ""; }; B6B140872ABDBCC1004F8E85 /* HoverTrackingArea.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoverTrackingArea.swift; sourceTree = ""; }; @@ -5749,6 +5765,7 @@ isa = PBXGroup; children = ( 37534CA62811988E002621E7 /* AdjacentItemEnumerator.swift */, + B62B483D2ADE48DE000DECE5 /* ArrayBuilder.swift */, B6E319372953446000DD3BCF /* Assertions.swift */, 4BB6CE5E26B77ED000EC5860 /* Cryptography.swift */, 4BB88B5A25B7BA50006F6B06 /* Instruments.swift */, @@ -6136,6 +6153,7 @@ B690152B2ACBF4DA00AD0BAB /* MenuPreview.swift */, B6DE57F52B05EA9000CD54B9 /* SheetHostingWindow.swift */, 4B44FEF22B1FEF5A000619D8 /* FocusableTextEditor.swift */, + B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */, B6F9BDE32B45CD1900677B33 /* ModalView.swift */, ); path = SwiftUI; @@ -7097,7 +7115,6 @@ AA7E919628746BCC00AB6B62 /* HistoryMenu.swift */, AA4BBA3A25C58FA200C4FB0F /* MainMenu.swift */, AA6EF9B425081B4C004754E6 /* MainMenuActions.swift */, - B62B483D2ADE48DE000DECE5 /* MenuBuilder.swift */, AAAB9115288EB46B00A057A9 /* VisitMenuItem.swift */, ); path = Menus; @@ -7151,6 +7168,8 @@ AAB549DD25DAB8E90058460B /* ViewModel */ = { isa = PBXGroup; children = ( + B69A14F12B4D6FE800B9417D /* AddBookmarkFolderPopoverViewModel.swift */, + B69A14F52B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift */, AAB549DE25DAB8F80058460B /* BookmarkViewModel.swift */, B6F9BDD72B45B7D900677B33 /* AddBookmarkModalViewModel.swift */, B6F9BDDF2B45C1A800677B33 /* AddBookmarkFolderModalViewModel.swift */, @@ -7224,11 +7243,13 @@ AAC5E4C125D6A6C3007F5990 /* View */ = { isa = PBXGroup; children = ( - 7BEC20412B0F505F00243D3E /* BookmarkAddFolderPopoverViewController.swift */, - 7BEC20402B0F505F00243D3E /* BookmarkAddPopoverViewController.swift */, + 7BEC20412B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift */, + AAC5E4C425D6A6E8007F5990 /* AddBookmarkPopover.swift */, + 7BEC20402B0F505F00243D3E /* AddBookmarkPopoverView.swift */, 4B9292CB2667123700AD2C21 /* AddBookmarkModalView.swift */, 4B9292CA2667123700AD2C21 /* AddBookmarkFolderModalView.swift */, 4B9292CC2667123700AD2C21 /* BookmarkListViewController.swift */, + B69A14F92B4D705D00B9417D /* BookmarkFolderPicker.swift */, 4B9292CD2667123700AD2C21 /* BookmarkManagementDetailViewController.swift */, 4B9292C72667123700AD2C21 /* BookmarkManagementSidebarViewController.swift */, 4B9292C82667123700AD2C21 /* BookmarkManagementSplitViewController.swift */, @@ -7241,7 +7262,6 @@ 4B92928A26670D1700AD2C21 /* BookmarkTableCellView.xib */, 4B92928626670D1600AD2C21 /* OutlineSeparatorViewCell.swift */, AAC5E4C625D6A6E8007F5990 /* Bookmarks.storyboard */, - AAC5E4C425D6A6E8007F5990 /* BookmarkPopover.swift */, 4B0511B3262CAA5A00F6079C /* RoundedSelectionRowView.swift */, ); path = View; @@ -9585,6 +9605,7 @@ 3706FB71293F65D500E42796 /* AddBookmarkModalView.swift in Sources */, 3706FB72293F65D500E42796 /* RecentlyClosedCoordinator.swift in Sources */, 3706FB74293F65D500E42796 /* FaviconHostReference.swift in Sources */, + B69A14F32B4D6FE800B9417D /* AddBookmarkFolderPopoverViewModel.swift in Sources */, 4B67854B2AA8DE76008A5004 /* NetworkProtectionFeatureVisibility.swift in Sources */, C168B9AD2B31DC7F001AFAD9 /* AutofillNeverPromptWebsitesManager.swift in Sources */, 37CBCA9B2A8966E60050218F /* SyncSettingsAdapter.swift in Sources */, @@ -9616,7 +9637,7 @@ 3706FB83293F65D500E42796 /* NSApplicationExtension.swift in Sources */, 37197EA42942441D00394917 /* NewWindowPolicy.swift in Sources */, 3706FB84293F65D500E42796 /* NSWindowExtension.swift in Sources */, - 3706FB85293F65D500E42796 /* BookmarkPopover.swift in Sources */, + 3706FB85293F65D500E42796 /* AddBookmarkPopover.swift in Sources */, 3706FB86293F65D500E42796 /* PreferencesDownloadsView.swift in Sources */, 7BBD45B22A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift in Sources */, 3706FB87293F65D500E42796 /* ProcessExtension.swift in Sources */, @@ -9721,7 +9742,7 @@ 7B430EA22A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift in Sources */, 3706FBD5293F65D500E42796 /* TabCollection+NSSecureCoding.swift in Sources */, 3706FBD6293F65D500E42796 /* Instruments.swift in Sources */, - B62B483F2ADE48DE000DECE5 /* MenuBuilder.swift in Sources */, + B62B483F2ADE48DE000DECE5 /* ArrayBuilder.swift in Sources */, 569277C229DDCBB500B633EF /* HomePageContinueSetUpModel.swift in Sources */, 3706FBD7293F65D500E42796 /* ContentBlockerRulesLists.swift in Sources */, 3706FBD8293F65D500E42796 /* NSViewControllerExtension.swift in Sources */, @@ -9795,6 +9816,7 @@ 3706FC08293F65D500E42796 /* CoreDataBookmarkImporter.swift in Sources */, 3706FC09293F65D500E42796 /* SuggestionViewModel.swift in Sources */, 3706FC0A293F65D500E42796 /* BookmarkManagedObject.swift in Sources */, + B6ABC5972B4861D4008343B9 /* FocusableTextField.swift in Sources */, 3706FC0B293F65D500E42796 /* CSVLoginExporter.swift in Sources */, 4B37EE722B4CFEE400A89A61 /* SurveyURLBuilder.swift in Sources */, 37197EAC294244D600394917 /* FutureExtension.swift in Sources */, @@ -9810,6 +9832,7 @@ 3706FC11293F65D500E42796 /* Publishers.NestedObjectChanges.swift in Sources */, 3706FC12293F65D500E42796 /* MenuItemSelectors.swift in Sources */, 3706FC13293F65D500E42796 /* FaviconView.swift in Sources */, + B69A14F72B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift in Sources */, 3706FC14293F65D500E42796 /* OnboardingFlow.swift in Sources */, EEC8EB3E2982CA3B0065AA39 /* JSAlertViewModel.swift in Sources */, 3706FC16293F65D500E42796 /* PasswordManagementLoginModel.swift in Sources */, @@ -9868,7 +9891,7 @@ B6685E4329A61C470043D2EE /* DownloadsTabExtension.swift in Sources */, 3706FC44293F65D500E42796 /* BookmarkList.swift in Sources */, 3706FC45293F65D500E42796 /* BookmarkTableRowView.swift in Sources */, - 7BEC20462B0F505F00243D3E /* BookmarkAddFolderPopoverViewController.swift in Sources */, + 7BEC20462B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift in Sources */, 3706FC47293F65D500E42796 /* FavoritesView.swift in Sources */, 3706FC48293F65D500E42796 /* HomePage.swift in Sources */, 3706FC49293F65D500E42796 /* RoundedSelectionRowView.swift in Sources */, @@ -9892,7 +9915,7 @@ 3706FC58293F65D500E42796 /* NSSizeExtension.swift in Sources */, 3706FC59293F65D500E42796 /* Fire.swift in Sources */, 3706FC5A293F65D500E42796 /* RandomAccessCollectionExtension.swift in Sources */, - 7BEC20432B0F505F00243D3E /* BookmarkAddPopoverViewController.swift in Sources */, + 7BEC20432B0F505F00243D3E /* AddBookmarkPopoverView.swift in Sources */, 3706FC5B293F65D500E42796 /* NSOutlineViewExtensions.swift in Sources */, 3706FC5C293F65D500E42796 /* AppDelegate.swift in Sources */, 3706FC5D293F65D500E42796 /* ContentOverlayViewController.swift in Sources */, @@ -9915,6 +9938,7 @@ 3706FC69293F65D500E42796 /* UserScripts.swift in Sources */, 3706FC6A293F65D500E42796 /* NSWorkspaceExtension.swift in Sources */, B6C0BB6829AEFF8100AE8E3C /* BookmarkExtension.swift in Sources */, + B69A14FB2B4D705D00B9417D /* BookmarkFolderPicker.swift in Sources */, 4B41EDB22B168B1E001EEDF4 /* VPNFeedbackFormView.swift in Sources */, 3706FC6C293F65D500E42796 /* BookmarkViewModel.swift in Sources */, 3706FC6D293F65D500E42796 /* DaxSpeech.swift in Sources */, @@ -10597,7 +10621,7 @@ 4B9579ED2AC7AE700062CA31 /* AppIconChanger.swift in Sources */, 4B9579EE2AC7AE700062CA31 /* AppMain.swift in Sources */, 4B9579EF2AC7AE700062CA31 /* ProductWaitlistRequest.swift in Sources */, - 7BEC20442B0F505F00243D3E /* BookmarkAddPopoverViewController.swift in Sources */, + 7BEC20442B0F505F00243D3E /* AddBookmarkPopoverView.swift in Sources */, 4B9579F02AC7AE700062CA31 /* Bookmark.xcdatamodeld in Sources */, 4B9579F12AC7AE700062CA31 /* DefaultBrowserPromptView.swift in Sources */, 4B9579F22AC7AE700062CA31 /* WaitlistActivationDateStore.swift in Sources */, @@ -10754,7 +10778,7 @@ 4B957A7A2AC7AE700062CA31 /* FireCoordinator.swift in Sources */, 4B957A7B2AC7AE700062CA31 /* GeolocationProvider.swift in Sources */, 4B957A7C2AC7AE700062CA31 /* NSAlert+ActiveDownloadsTermination.swift in Sources */, - B62B48412ADE48DE000DECE5 /* MenuBuilder.swift in Sources */, + B62B48412ADE48DE000DECE5 /* ArrayBuilder.swift in Sources */, 4B957A7D2AC7AE700062CA31 /* IndexPathExtension.swift in Sources */, 4B957A7E2AC7AE700062CA31 /* PasswordManagementNoteItemView.swift in Sources */, B6B4D1D22B0E0DD000C26286 /* DataImportNoDataView.swift in Sources */, @@ -10763,7 +10787,7 @@ 4B957A812AC7AE700062CA31 /* KeychainType+ClientDefault.swift in Sources */, 4B41EDA52B1543B9001EEDF4 /* VPNPreferencesModel.swift in Sources */, 4B957A822AC7AE700062CA31 /* SyncDebugMenu.swift in Sources */, - 4B957A832AC7AE700062CA31 /* BookmarkPopover.swift in Sources */, + 4B957A832AC7AE700062CA31 /* AddBookmarkPopover.swift in Sources */, 4B957A842AC7AE700062CA31 /* PreferencesDownloadsView.swift in Sources */, 4B957A852AC7AE700062CA31 /* QRSharingService.swift in Sources */, 4B957A862AC7AE700062CA31 /* ProcessExtension.swift in Sources */, @@ -10771,6 +10795,7 @@ 4B957A882AC7AE700062CA31 /* BadgeAnimationView.swift in Sources */, 4B957A892AC7AE700062CA31 /* BrowserTabSelectionDelegate.swift in Sources */, 4B957A8A2AC7AE700062CA31 /* ContinueSetUpView.swift in Sources */, + B69A14F42B4D6FE800B9417D /* AddBookmarkFolderPopoverViewModel.swift in Sources */, 4B957A8B2AC7AE700062CA31 /* PasswordManagementListSection.swift in Sources */, 4B957A8C2AC7AE700062CA31 /* FaviconReferenceCache.swift in Sources */, 4B957A8D2AC7AE700062CA31 /* BookmarkTreeController.swift in Sources */, @@ -10911,6 +10936,7 @@ C168B9AE2B31DC7F001AFAD9 /* AutofillNeverPromptWebsitesManager.swift in Sources */, 4B957B0D2AC7AE700062CA31 /* JSAlertViewModel.swift in Sources */, 4B957B0E2AC7AE700062CA31 /* BookmarksBarCollectionViewItem.swift in Sources */, + B69A14FC2B4D705D00B9417D /* BookmarkFolderPicker.swift in Sources */, 4B957B0F2AC7AE700062CA31 /* FileDownloadError.swift in Sources */, 4B957B102AC7AE700062CA31 /* MoreOrLessView.swift in Sources */, 4B957B112AC7AE700062CA31 /* DateExtension.swift in Sources */, @@ -10958,7 +10984,7 @@ 4B957B362AC7AE700062CA31 /* BurnerHomePageView.swift in Sources */, 4B957B372AC7AE700062CA31 /* UserText+PasswordManager.swift in Sources */, 4B957B382AC7AE700062CA31 /* LoadingProgressView.swift in Sources */, - 7BEC20472B0F505F00243D3E /* BookmarkAddFolderPopoverViewController.swift in Sources */, + 7BEC20472B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift in Sources */, 4B957B392AC7AE700062CA31 /* StatisticsStore.swift in Sources */, EEC4A66B2B2C87D300F7C0AA /* VPNLocationView.swift in Sources */, 4B957B3A2AC7AE700062CA31 /* BWInstallationService.swift in Sources */, @@ -11024,6 +11050,7 @@ 4B957B722AC7AE700062CA31 /* NSSizeExtension.swift in Sources */, 4B957B732AC7AE700062CA31 /* Fire.swift in Sources */, 4B957B742AC7AE700062CA31 /* SyncBookmarksAdapter.swift in Sources */, + B6ABC5982B4861D4008343B9 /* FocusableTextField.swift in Sources */, 4B957B752AC7AE700062CA31 /* RandomAccessCollectionExtension.swift in Sources */, 4B957B762AC7AE700062CA31 /* NSOutlineViewExtensions.swift in Sources */, 4B957B772AC7AE700062CA31 /* AppDelegate.swift in Sources */, @@ -11073,6 +11100,7 @@ 4B68DDFF2ACBA14100FB0973 /* FileLineError.swift in Sources */, 4B957B9D2AC7AE700062CA31 /* NSImageExtensions.swift in Sources */, 4B957B9E2AC7AE700062CA31 /* WaitlistKeychainStorage.swift in Sources */, + B69A14F82B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift in Sources */, 4B957B9F2AC7AE700062CA31 /* PasswordManagementViewController.swift in Sources */, 4B957BA02AC7AE700062CA31 /* ImportedBookmarks.swift in Sources */, 4B957BA12AC7AE700062CA31 /* UserDefaults+NetworkProtectionShared.swift in Sources */, @@ -11257,7 +11285,7 @@ B6A9E48426146AAB0067D1B9 /* PixelParameters.swift in Sources */, AA5FA697275F90C400DCE9C9 /* FaviconImageCache.swift in Sources */, 1430DFF524D0580F00B8978C /* TabBarViewController.swift in Sources */, - B62B483E2ADE48DE000DECE5 /* MenuBuilder.swift in Sources */, + B62B483E2ADE48DE000DECE5 /* ArrayBuilder.swift in Sources */, 4B92929B26670D2A00AD2C21 /* BookmarkOutlineViewDataSource.swift in Sources */, 56D145EB29E6C99B00E3488A /* DataImportStatusProviding.swift in Sources */, 31D5375C291D944100407A95 /* PasswordManagementBitwardenItemView.swift in Sources */, @@ -11286,7 +11314,7 @@ B6676BE12AA986A700525A21 /* AddressBarTextEditor.swift in Sources */, B69B503B2726A12500758A2B /* Atb.swift in Sources */, 37A6A8F12AFCC988008580A3 /* FaviconsFetcherOnboarding.swift in Sources */, - 7BEC20452B0F505F00243D3E /* BookmarkAddFolderPopoverViewController.swift in Sources */, + 7BEC20452B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift in Sources */, B6C0BB6A29AF1C7000AE8E3C /* BrowserTabView.swift in Sources */, B6B1E88026D5DA9B0062C350 /* DownloadsViewController.swift in Sources */, 85AC3AF725D5DBFD00C7D2AA /* DataExtension.swift in Sources */, @@ -11381,6 +11409,7 @@ 1E7E2E9229029F9B00C01B54 /* WebsiteBreakageReporter.swift in Sources */, 37CD54C927F2FDD100F1F7B9 /* PrivacyPreferencesModel.swift in Sources */, B6F1C80B2761C45400334924 /* LocalUnprotectedDomains.swift in Sources */, + B69A14FA2B4D705D00B9417D /* BookmarkFolderPicker.swift in Sources */, 1D36E658298AA3BA00AA485D /* InternalUserDeciderStore.swift in Sources */, B634DBE5293C944700C3C99E /* NewWindowPolicy.swift in Sources */, 31CF3432288B0B1B0087244B /* NavigationBarBadgeAnimator.swift in Sources */, @@ -11397,7 +11426,7 @@ EEC4A66D2B2C894F00F7C0AA /* VPNLocationPreferenceItemModel.swift in Sources */, 3776582F27F82E62009A6B35 /* AutofillPreferences.swift in Sources */, AAD8078727B3F45600CF7703 /* WebsiteBreakage.swift in Sources */, - 7BEC20422B0F505F00243D3E /* BookmarkAddPopoverViewController.swift in Sources */, + 7BEC20422B0F505F00243D3E /* AddBookmarkPopoverView.swift in Sources */, 1D074B272909A433006E4AC3 /* PasswordManagerCoordinator.swift in Sources */, 4BE6547E271FCD4D008D1D63 /* PasswordManagementIdentityModel.swift in Sources */, 85C6A29625CC1FFD00EEB5F1 /* UserDefaultsWrapper.swift in Sources */, @@ -11510,12 +11539,13 @@ AAECA42024EEA4AC00EFA63A /* IndexPathExtension.swift in Sources */, 4B9579212AC687170062CA31 /* HardwareModel.swift in Sources */, B6080BC52B21E78100B418EF /* DataImportErrorView.swift in Sources */, + B69A14F22B4D6FE800B9417D /* AddBookmarkFolderPopoverViewModel.swift in Sources */, 4BE65478271FCD41008D1D63 /* PasswordManagementNoteItemView.swift in Sources */, AA5C8F632591021700748EB7 /* NSApplicationExtension.swift in Sources */, AA9E9A5625A3AE8400D1959D /* NSWindowExtension.swift in Sources */, 7BD3AF5D2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift in Sources */, 370A34B12AB24E3700C77F7C /* SyncDebugMenu.swift in Sources */, - AAC5E4C725D6A6E8007F5990 /* BookmarkPopover.swift in Sources */, + AAC5E4C725D6A6E8007F5990 /* AddBookmarkPopover.swift in Sources */, 37CC53F027E8D1440028713D /* PreferencesDownloadsView.swift in Sources */, B6F7127E29F6779000594A45 /* QRSharingService.swift in Sources */, B68C2FB227706E6A00BF2C7D /* ProcessExtension.swift in Sources */, @@ -11776,6 +11806,7 @@ B693954A26F04BEB0015B914 /* NibLoadable.swift in Sources */, AA3D531527A1ED9300074EC1 /* FeedbackWindow.swift in Sources */, B6685E3F29A606190043D2EE /* WorkspaceProtocol.swift in Sources */, + B6ABC5962B4861D4008343B9 /* FocusableTextField.swift in Sources */, 85F0FF1327CFAB04001C7C6E /* RecentlyVisitedView.swift in Sources */, AA7EB6DF27E7C57D00036718 /* MouseOverAnimationButton.swift in Sources */, AA7412B724D1687000D22FE0 /* TabBarScrollView.swift in Sources */, @@ -11800,6 +11831,7 @@ 3158B1592B0BF76400AF130C /* DataBrokerProtectionFeatureDisabler.swift in Sources */, B655124829A79465009BFE1C /* NavigationActionExtension.swift in Sources */, 85308E25267FC9F2001ABD76 /* NSAlertExtension.swift in Sources */, + B69A14F62B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift in Sources */, 4B59024826B3673600489384 /* ThirdPartyBrowser.swift in Sources */, B60C6F7729B0E286007BFAA8 /* SearchNonexistentDomainNavigationResponder.swift in Sources */, B65E6B9E26D9EC0800095F96 /* CircularProgressView.swift in Sources */, diff --git a/DuckDuckGo/Bookmarks/Model/Bookmark.swift b/DuckDuckGo/Bookmarks/Model/Bookmark.swift index 25df130bcc..99ca656b3f 100644 --- a/DuckDuckGo/Bookmarks/Model/Bookmark.swift +++ b/DuckDuckGo/Bookmarks/Model/Bookmark.swift @@ -19,7 +19,7 @@ import Cocoa import Bookmarks -internal class BaseBookmarkEntity { +internal class BaseBookmarkEntity: Identifiable { static func singleEntity(with uuid: String) -> NSFetchRequest { let request = BookmarkEntity.fetchRequest() diff --git a/DuckDuckGo/Bookmarks/View/AddBookmarkFolderModalView.swift b/DuckDuckGo/Bookmarks/View/AddBookmarkFolderModalView.swift index c225aa0726..31cc06dc04 100644 --- a/DuckDuckGo/Bookmarks/View/AddBookmarkFolderModalView.swift +++ b/DuckDuckGo/Bookmarks/View/AddBookmarkFolderModalView.swift @@ -29,7 +29,7 @@ struct AddBookmarkFolderModalView: ModalView { .fontWeight(.semibold) HStack(spacing: 16) { - Text("Name:", comment: "New bookmark folder dialog folder name field heading") + Text(UserText.newBookmarkDialogBookmarkNameTitle) .frame(height: 22) TextField("", text: $model.folderName) diff --git a/DuckDuckGo/Bookmarks/View/AddBookmarkFolderPopoverView.swift b/DuckDuckGo/Bookmarks/View/AddBookmarkFolderPopoverView.swift new file mode 100644 index 0000000000..fbb892ba9b --- /dev/null +++ b/DuckDuckGo/Bookmarks/View/AddBookmarkFolderPopoverView.swift @@ -0,0 +1,98 @@ +// +// AddBookmarkFolderPopoverView.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 SwiftUI + +struct AddBookmarkFolderPopoverView: ModalView { + + @ObservedObject var model: AddBookmarkFolderPopoverViewModel + + var body: some View { + VStack(alignment: .leading, spacing: 16) { + Text(UserText.newFolder) + .bold() + + VStack(alignment: .leading, spacing: 7) { + Text("Location:", comment: "Add Folder popover: parent folder picker title") + + BookmarkFolderPicker(folders: model.folders, selectedFolder: $model.parent) + .accessibilityIdentifier("bookmark.folder.folder.dropdown") + .disabled(model.isDisabled) + } + + VStack(alignment: .leading, spacing: 7) { + Text(UserText.newFolderDialogFolderNameTitle) + + TextField("", text: $model.folderName) + .focusedOnAppear() + .accessibilityIdentifier("bookmark.folder.name.textfield") + .textFieldStyle(RoundedBorderTextFieldStyle()) + .disabled(model.isDisabled) + } + .padding(.bottom, 16) + + HStack { + Spacer() + + Button(action: { + model.cancel() + }) { + Text(UserText.cancel) + } + .accessibilityIdentifier("bookmark.add.cancel.button") + .disabled(model.isDisabled) + + Button(action: { + model.addFolder() + }) { + Text("Add Folder", comment: "Add Folder popover: Create folder button") + } + .keyboardShortcut(.defaultAction) + .accessibilityIdentifier("bookmark.add.add.folder.button") + .disabled(model.isAddFolderButtonDisabled || model.isDisabled) + } + } + .font(.system(size: 13)) + .padding() + .frame(width: 300, height: 229) + .background(Color(.popoverBackground)) + } +} + +#if DEBUG +#Preview { + let bkman = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [ + BookmarkFolder(id: "1", title: "Folder 1", children: [ + BookmarkFolder(id: "2", title: "Nested Folder", children: [ + ]) + ]), + BookmarkFolder(id: "3", title: "Another Folder", children: [ + BookmarkFolder(id: "4", title: "Nested Folder", children: [ + BookmarkFolder(id: "5", title: "Another Nested Folder", children: [ + ]) + ]) + ]) + ])) + bkman.loadBookmarks() + customAssertionFailure = { _, _, _ in } + + return AddBookmarkFolderPopoverView(model: AddBookmarkFolderPopoverViewModel(bookmarkManager: bkman) { + print("CompletionHandler:", $0?.title ?? "") + }) +} +#endif diff --git a/DuckDuckGo/Bookmarks/View/AddBookmarkPopover.swift b/DuckDuckGo/Bookmarks/View/AddBookmarkPopover.swift new file mode 100644 index 0000000000..288fa34e46 --- /dev/null +++ b/DuckDuckGo/Bookmarks/View/AddBookmarkPopover.swift @@ -0,0 +1,76 @@ +// +// AddBookmarkPopover.swift +// +// Copyright © 2021 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 AppKit +import SwiftUI + +final class AddBookmarkPopover: NSPopover { + + var isNew: Bool = false + var bookmark: Bookmark? { + didSet { + setupBookmarkAddController() + } + } + + private weak var addressBar: NSView? + + /// prefferred bounding box for the popover positioning + override var boundingFrame: NSRect { + guard let addressBar, + let window = addressBar.window else { return .infinite } + var frame = window.convertToScreen(addressBar.convert(addressBar.bounds, to: nil)) + + frame = frame.insetBy(dx: -42, dy: -window.frame.size.height) + + return frame + } + + override init() { + super.init() + + animates = false + behavior = .transient + } + + required init?(coder: NSCoder) { + fatalError("BookmarksPopover: Bad initializer") + } + + private func setupBookmarkAddController() { + guard let bookmark else { return } + contentViewController = NSHostingController(rootView: AddBookmarkPopoverView(model: AddBookmarkPopoverViewModel(bookmark: bookmark)) + .legacyOnDismiss { [weak self] in + self?.performClose(nil) + }) + } + + override func show(relativeTo positioningRect: NSRect, of positioningView: NSView, preferredEdge: NSRectEdge) { + self.addressBar = positioningView.superview + super.show(relativeTo: positioningRect, of: positioningView, preferredEdge: preferredEdge) + } + + override func performClose(_ sender: Any?) { + self.close() + } + + func popoverWillClose() { + bookmark = nil + } + +} diff --git a/DuckDuckGo/Bookmarks/View/AddBookmarkPopoverView.swift b/DuckDuckGo/Bookmarks/View/AddBookmarkPopoverView.swift new file mode 100644 index 0000000000..620f7f9b9e --- /dev/null +++ b/DuckDuckGo/Bookmarks/View/AddBookmarkPopoverView.swift @@ -0,0 +1,137 @@ +// +// AddBookmarkPopoverView.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 SwiftUI +import SwiftUIExtensions + +struct AddBookmarkPopoverView: View { + + @ObservedObject private var model: AddBookmarkPopoverViewModel + @Environment(\.dismiss) private var dismiss + + init(model: AddBookmarkPopoverViewModel) { + self.model = model + } + + var body: some View { + if let addFolderViewModel = model.addFolderViewModel { + AddBookmarkFolderPopoverView(model: addFolderViewModel) + } else { + addBookmarkView + } + } + + @MainActor + private var addBookmarkView: some View { + VStack(alignment: .leading, spacing: 19) { + Text("Bookmark Added", comment: "Bookmark Added popover title") + .fontWeight(.bold) + .padding(.bottom, 4) + + VStack(alignment: .leading, spacing: 10) { + TextField("", text: $model.bookmarkTitle) + .focusedOnAppear() + .accessibilityIdentifier("bookmark.add.name.textfield") + .textFieldStyle(RoundedBorderTextFieldStyle()) + .font(.system(size: 14)) + + HStack { + BookmarkFolderPicker(folders: model.folders, + selectedFolder: $model.selectedFolder) + .accessibilityIdentifier("bookmark.add.folder.dropdown") + + Button { + model.addFolderButtonAction() + } label: { + Image(.addFolder) + } + .accessibilityIdentifier("bookmark.add.new.folder.button") + .buttonStyle(StandardButtonStyle()) + } + } + + Divider() + + Button { + model.favoritesButtonAction() + } label: { + HStack(spacing: 8) { + if model.bookmark.isFavorite { + Image(.favoriteFilled) + Text(UserText.removeFromFavorites) + } else { + Image(.favorite) + Text(UserText.addToFavorites) + } + } + } + .accessibilityIdentifier("bookmark.add.add.to.favorites.button") + .buttonStyle(.borderless) + .foregroundColor(Color.button) + + HStack { + Spacer() + + Button { + model.removeButtonAction(dismiss: dismiss.callAsFunction) + } label: { + Text("Remove", comment: "Remove bookmark button title") + } + .accessibilityIdentifier("bookmark.add.remove.button") + + Button { + model.doneButtonAction(dismiss: dismiss.callAsFunction) + } label: { + Text(UserText.done) + } + .keyboardShortcut(.defaultAction) + .accessibilityIdentifier("bookmark.add.done.button") + } + + } + .font(.system(size: 13)) + .padding(EdgeInsets(top: 19, leading: 19, bottom: 19, trailing: 19)) + .frame(width: 300, height: 229) + .background(Color(.popoverBackground)) + } + +} + +#if DEBUG +#Preview { { + let bkm = Bookmark(id: "n", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: false, parentFolderUUID: "1") + let bkman = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [ + BookmarkFolder(id: "1", title: "Folder 1", children: [ + bkm, + BookmarkFolder(id: "2", title: "Nested Folder", children: [ + ]) + ]), + Bookmark(id: "b2", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: true, parentFolderUUID: "1"), + BookmarkFolder(id: "3", title: "Another Folder", children: [ + BookmarkFolder(id: "4", title: "Nested Folder", children: [ + BookmarkFolder(id: "5", title: "Another Nested Folder", children: [ + ]) + ]) + ]) + ])) + bkman.loadBookmarks() + customAssertionFailure = { _, _, _ in } + + return AddBookmarkPopoverView(model: AddBookmarkPopoverViewModel(bookmark: bkm, bookmarkManager: bkman)) +}() } +#endif diff --git a/DuckDuckGo/Bookmarks/View/BookmarkAddFolderPopoverViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkAddFolderPopoverViewController.swift deleted file mode 100644 index 8c11a81517..0000000000 --- a/DuckDuckGo/Bookmarks/View/BookmarkAddFolderPopoverViewController.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// BookmarkAddFolderPopoverViewController.swift -// -// Copyright © 2023 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 AppKit -import Combine - -final class BookmarkAddFolderPopoverViewController: NSViewController { - - weak var container: BookmarkPopoverContainer? - - @IBOutlet var folderNameTextField: NSTextField! - @IBOutlet var folderPickerPopUpButton: NSPopUpButton! - @IBOutlet var addFolderButton: NSButton! - - var bookmarkManager: BookmarkManager { - guard let container else { - return LocalBookmarkManager.shared - } - return container.bookmarkManager - } - - var bookmark: Bookmark? { - get { - container?.bookmark - } - set { - container?.bookmark = newValue - } - } - - var currentBookmarkFolder: NSMenuItem? { - let selectedFolderMenuItem = folderPickerPopUpButton.menu?.items.first(where: { menuItem in - guard let folder = menuItem.representedObject as? BookmarkFolder else { - return false - } - return folder.id == bookmark?.parentFolderUUID - - }) - return selectedFolderMenuItem - } - - @IBAction private func cancel(_ sender: NSButton) { - container?.showBookmarkAddView() - } - - @IBAction private func save(_ sender: NSButton) { - let name = folderNameTextField.stringValue - let selectedFolder = folderPickerPopUpButton.selectedItem?.representedObject as? BookmarkFolder - guard let currentBookmark = self.bookmark else { return } - - // Create the folder and then move the bookmark to it - bookmarkManager.makeFolder(for: name, parent: selectedFolder, completion: { folder in - self.bookmarkManager.move(objectUUIDs: [currentBookmark.id], - toIndex: 1, - withinParentFolder: .parent(uuid: folder.id), - completion: { _ in }) - self.container?.showBookmarkAddView() - }) - - } - - override func viewDidLoad() { - super.viewDidLoad() - folderNameTextField.delegate = self - addFolderButton.isEnabled = false - refreshFolderPicker() - } - - private func refreshFolderPicker() { - guard let menuItems = container?.getMenuItems() else { - return - } - folderPickerPopUpButton.menu?.items = menuItems - folderPickerPopUpButton.select(currentBookmarkFolder ?? menuItems.first) - } - -} - -extension BookmarkAddFolderPopoverViewController: NSTextFieldDelegate { - - func controlTextDidChange(_ obj: Notification) { - addFolderButton.isEnabled = folderNameTextField.stringValue != "" - } - -} diff --git a/DuckDuckGo/Bookmarks/View/BookmarkAddPopoverViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkAddPopoverViewController.swift deleted file mode 100644 index 53493da240..0000000000 --- a/DuckDuckGo/Bookmarks/View/BookmarkAddPopoverViewController.swift +++ /dev/null @@ -1,156 +0,0 @@ -// -// BookmarkAddPopoverViewController.swift -// -// Copyright © 2021 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 Cocoa -import Combine - -final class BookmarkAddPopoverViewController: NSViewController { - - static let favoriteImage = NSImage(named: "Favorite") - static let favoriteFilledImage = NSImage(named: "FavoriteFilled") - - weak var container: BookmarkPopoverContainer? - - @IBOutlet weak var textField: NSTextField! - @IBOutlet weak var favoriteButton: NSButton! - @IBOutlet weak var folderPickerPopUpButton: NSPopUpButton! - @IBOutlet weak var folderAddButton: NSButton! - - private var folderPickerSelectionCancellable: AnyCancellable? - private var cancellables = Set() - - var bookmarkManager: BookmarkManager { - guard let container else { - return LocalBookmarkManager.shared - } - return container.bookmarkManager - } - - var bookmark: Bookmark? { - get { - container?.bookmark - } - set { - container?.bookmark = newValue - if isViewLoaded { - updateSubviews() - } - } - } - - override func viewDidLoad() { - super.viewDidLoad() - setupPickerSelectionCancellable() - setupListPublisher() - folderAddButton.setButtonType(.onOff) - textField.delegate = self - } - - private func setupListPublisher() { - bookmarkManager.listPublisher.receive(on: DispatchQueue.main).sink { [weak self] _ in - guard let url = self?.bookmark?.url else { return } - self?.bookmark = self?.bookmarkManager.getBookmark(forUrl: url) - self?.refreshFolderPicker() - }.store(in: &cancellables) - } - - private func setupPickerSelectionCancellable() { - folderPickerSelectionCancellable = folderPickerPopUpButton.selectionPublisher.dropFirst().sink { [weak self] index in - guard let self = self, - let bookmark = self.bookmark, - let menuItem = self.folderPickerPopUpButton.item(at: index) else { return } - - let folder = menuItem.representedObject as? BookmarkFolder - self.bookmarkManager.add(bookmark: bookmark, to: folder, completion: { _ in }) - } - } - - override func viewWillAppear() { - super.viewWillAppear() - - updateSubviews() - refreshFolderPicker() - } - - @IBAction func removeButtonAction(_ sender: NSButton) { - guard let bookmark = bookmark else { return } - bookmarkManager.remove(bookmark: bookmark) - container?.popoverShouldClose() - } - - @IBAction func doneButtonAction(_ sender: NSButton) { - container?.popoverShouldClose() - } - - @IBAction func favoritesButtonAction(_ sender: Any) { - guard let bookmark = bookmark else { return } - bookmark.isFavorite = !bookmark.isFavorite - self.bookmark = bookmark - - bookmarkManager.update(bookmark: bookmark) - } - - @IBAction func addFolder(_ sender: Any) { - container?.showFolderAddView() - } - - private func updateSubviews() { - guard let bookmark = bookmark else { - textField.stringValue = "" - favoriteButton.image = Self.favoriteImage - favoriteButton.title = UserText.addToFavorites - return - } - - if textField.stringValue != bookmark.title { - textField.stringValue = bookmark.title - } - - favoriteButton.image = bookmark.isFavorite ? Self.favoriteFilledImage : Self.favoriteImage - favoriteButton.title = " \(bookmark.isFavorite ? UserText.removeFromFavorites : UserText.addToFavorites)" - } - - private func refreshFolderPicker() { - guard let menuItems = container?.getMenuItems() else { - return - } - folderPickerPopUpButton.menu?.items = menuItems - - let selectedFolderMenuItem = menuItems.first(where: { menuItem in - guard let folder = menuItem.representedObject as? BookmarkFolder else { - return false - } - return folder.id == bookmark?.parentFolderUUID - }) - - folderPickerPopUpButton.select(selectedFolderMenuItem ?? menuItems.first) - } - -} - -extension BookmarkAddPopoverViewController: NSTextFieldDelegate { - - func controlTextDidChange(_ obj: Notification) { - guard let bookmark = bookmark else { return } - bookmark.title = textField.stringValue - self.bookmark = bookmark - - bookmarkManager.update(bookmark: bookmark) - } - -} diff --git a/DuckDuckGo/Bookmarks/View/BookmarkFolderPicker.swift b/DuckDuckGo/Bookmarks/View/BookmarkFolderPicker.swift new file mode 100644 index 0000000000..0ff06e71ee --- /dev/null +++ b/DuckDuckGo/Bookmarks/View/BookmarkFolderPicker.swift @@ -0,0 +1,56 @@ +// +// BookmarkFolderPicker.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 AppKit +import SwiftUI + +struct BookmarkFolderPicker: View { + + let folders: [FolderViewModel] + @Binding var selectedFolder: BookmarkFolder? + + var body: some View { + + NSPopUpButtonView(selection: $selectedFolder, viewCreator: NSPopUpButton.init) { + + PopupButtonItem(icon: .folder, title: UserText.bookmarks) + + PopupButtonItem.separator() + + for folder in folders { + PopupButtonItem(icon: .folder, title: folder.title, indentation: folder.level, selectionValue: folder.entity) + } + } + + } + +} + +#Preview { { + let folder1 = BookmarkFolder(id: "3", title: "Another Folder", children: []) + let folder2 = BookmarkFolder(id: "4", title: "Nested Folder", children: []) + let folder3 = BookmarkFolder(id: "5", title: "Another Nested Folder", children: []) + @State var selectedFolder: BookmarkFolder? = folder2 + + return BookmarkFolderPicker(folders: [ + FolderViewModel(entity: folder1, level: 0), + FolderViewModel(entity: folder2, level: 1), + FolderViewModel(entity: folder3, level: 2), + ], selectedFolder: _selectedFolder.projectedValue) + +}() } diff --git a/DuckDuckGo/Bookmarks/View/BookmarkPopover.swift b/DuckDuckGo/Bookmarks/View/BookmarkPopover.swift deleted file mode 100644 index 433dd01dcc..0000000000 --- a/DuckDuckGo/Bookmarks/View/BookmarkPopover.swift +++ /dev/null @@ -1,148 +0,0 @@ -// -// BookmarkPopover.swift -// -// Copyright © 2021 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 Cocoa - -protocol BookmarkPopoverContainer: AnyObject { - var bookmark: Bookmark? { get set } - var bookmarkManager: BookmarkManager { get } - - func getMenuItems() -> [NSMenuItem] - func showBookmarkAddView() - func showFolderAddView() - func popoverWillClose() - func popoverShouldClose() -} - -final class BookmarkPopover: NSPopover { - - var isNew = false - var bookmark: Bookmark? - - private weak var addressBar: NSView? - - private enum Constants { - static let storyboard = NSStoryboard(name: "Bookmarks", bundle: nil) - static let bookmarkAddPopoverID = "BookmarkPopoverViewController" - static let folderAddPopoverID = "BookmarkAddFolderPopoverViewController" - } - - /// prefferred bounding box for the popover positioning - override var boundingFrame: NSRect { - guard let addressBar, - let window = addressBar.window else { return .infinite } - var frame = window.convertToScreen(addressBar.convert(addressBar.bounds, to: nil)) - - frame = frame.insetBy(dx: -36, dy: -window.frame.size.height) - - return frame - } - - override init() { - super.init() - - self.animates = false - self.behavior = .transient - setupBookmarkAddController() - } - - required init?(coder: NSCoder) { - fatalError("BookmarksPopover: Bad initializer") - } - - // swiftlint:disable force_cast - private func setupBookmarkAddController() { - let controller = Constants.storyboard.instantiateController(withIdentifier: Constants.bookmarkAddPopoverID) as! BookmarkAddPopoverViewController - controller.container = self - contentViewController = controller - } - - private func setupFolderAddController() { - let controller = Constants.storyboard.instantiateController(withIdentifier: Constants.folderAddPopoverID) as! BookmarkAddFolderPopoverViewController - controller.container = self - contentViewController = controller - } - // swiftlint:enable force_cast - - override func show(relativeTo positioningRect: NSRect, of positioningView: NSView, preferredEdge: NSRectEdge) { - self.addressBar = positioningView.superview - super.show(relativeTo: positioningRect, of: positioningView, preferredEdge: preferredEdge) - } - - private func createMenuItems(for bookmarkFolders: [BookmarkFolder], level: Int = 0) -> [NSMenuItem] { - let viewModels = bookmarkFolders.map(BookmarkViewModel.init(entity:)) - var menuItems = [NSMenuItem]() - - for viewModel in viewModels { - let menuItem = NSMenuItem(bookmarkViewModel: viewModel) - menuItem.indentationLevel = level - menuItems.append(menuItem) - - if let folder = viewModel.entity as? BookmarkFolder, !folder.children.isEmpty { - let childFolders = folder.children.compactMap { $0 as? BookmarkFolder } - menuItems.append(contentsOf: createMenuItems(for: childFolders, level: level + 1)) - } - } - - return menuItems - } - -} - -extension BookmarkPopover: BookmarkPopoverContainer { - - func getMenuItems() -> [NSMenuItem] { - guard let list = bookmarkManager.list else { - assertionFailure("Tried to refresh bookmark folder picker, but couldn't get bookmark list") - return [] - } - - let rootFolder = NSMenuItem(title: "Bookmarks", action: nil, target: nil, keyEquivalent: "") - rootFolder.image = NSImage(named: "Folder") - - let topLevelFolders = list.topLevelEntities.compactMap { $0 as? BookmarkFolder } - var folderMenuItems = [NSMenuItem]() - - folderMenuItems.append(rootFolder) - folderMenuItems.append(.separator()) - folderMenuItems.append(contentsOf: createMenuItems(for: topLevelFolders)) - - return folderMenuItems - } - - var bookmarkManager: BookmarkManager { - LocalBookmarkManager.shared - } - - func showBookmarkAddView() { - setupBookmarkAddController() - } - - func showFolderAddView() { - setupFolderAddController() - } - - func popoverWillClose() { - setupBookmarkAddController() - } - - func popoverShouldClose() { - close() - } - -} diff --git a/DuckDuckGo/Bookmarks/View/Bookmarks.storyboard b/DuckDuckGo/Bookmarks/View/Bookmarks.storyboard index 7c1b551e18..03d9c83e5e 100644 --- a/DuckDuckGo/Bookmarks/View/Bookmarks.storyboard +++ b/DuckDuckGo/Bookmarks/View/Bookmarks.storyboard @@ -329,6 +329,7 @@ + @@ -360,155 +361,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -777,135 +629,7 @@ DQ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -913,7 +637,6 @@ Gw - @@ -932,8 +655,5 @@ Gw - - - diff --git a/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkFolderPopoverViewModel.swift b/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkFolderPopoverViewModel.swift new file mode 100644 index 0000000000..14011d3383 --- /dev/null +++ b/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkFolderPopoverViewModel.swift @@ -0,0 +1,69 @@ +// +// AddBookmarkFolderPopoverViewModel.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 Foundation + +final class AddBookmarkFolderPopoverViewModel: ObservableObject { + + private let bookmarkManager: BookmarkManager + + let folders: [FolderViewModel] + @Published var parent: BookmarkFolder? + + @Published var folderName: String = "" + + @Published private(set) var isDisabled = false + + private let completionHandler: (BookmarkFolder?) -> Void + + init(bookmark: Bookmark? = nil, + folderName: String = "", + bookmarkManager: BookmarkManager = LocalBookmarkManager.shared, + completionHandler: @escaping (BookmarkFolder?) -> Void = {_ in }) { + + self.folders = .init(bookmarkManager.list) + self.bookmarkManager = bookmarkManager + self.folderName = folderName + self.completionHandler = completionHandler + self.parent = bookmark.flatMap { bookmark in + folders.first(where: { $0.id == bookmark.parentFolderUUID })?.entity + } + } + + func cancel() { + completionHandler(nil) + } + + func addFolder() { + guard !folderName.isEmpty else { + assertionFailure("folderName is empty, button should be disabled") + return + } + + isDisabled = true + bookmarkManager.makeFolder(for: folderName, parent: parent) { [completionHandler] result in + completionHandler(result) + } + } + + var isAddFolderButtonDisabled: Bool { + folderName.isEmpty + } + +} diff --git a/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkPopoverViewModel.swift b/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkPopoverViewModel.swift new file mode 100644 index 0000000000..076ad3346d --- /dev/null +++ b/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkPopoverViewModel.swift @@ -0,0 +1,149 @@ +// +// AddBookmarkPopoverViewModel.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 Foundation + +@MainActor +final class AddBookmarkPopoverViewModel: ObservableObject { + + private let bookmarkManager: BookmarkManager + + @Published private(set) var bookmark: Bookmark + + @Published private(set) var folders: [FolderViewModel] = [] + + @Published var selectedFolder: BookmarkFolder? { + didSet { + if oldValue?.id != selectedFolder?.id { + bookmarkManager.add(bookmark: bookmark, to: selectedFolder) { _ in + // this is an invalid callback fired before bookmarks finish reloading + } + } + } + } + + @Published var addFolderViewModel: AddBookmarkFolderPopoverViewModel? + + private var bookmarkListCancellable: AnyCancellable? + + init(bookmark: Bookmark, + bookmarkManager: BookmarkManager = LocalBookmarkManager.shared) { + self.bookmarkManager = bookmarkManager + self.bookmark = bookmark + self.bookmarkTitle = bookmark.title + + bookmarkListCancellable = bookmarkManager.listPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] list in + self?.folders = .init(list) + self?.reloadBookmark() + } + } + + private func reloadBookmark() { + bookmark = bookmarkManager.getBookmark(for: bookmark.url.url ?? .empty) ?? bookmark + resetSelectedFolder() + } + + private func resetSelectedFolder() { + selectedFolder = folders.first(where: { $0.id == bookmark.parentFolderUUID })?.entity + } + + func removeButtonAction(dismiss: () -> Void) { + bookmarkManager.remove(bookmark: bookmark) + dismiss() + } + + func doneButtonAction(dismiss: () -> Void) { + dismiss() + } + + func favoritesButtonAction() { + bookmark.isFavorite.toggle() + + bookmarkManager.update(bookmark: bookmark) + } + + func addFolderButtonAction() { + addFolderViewModel = .init(bookmark: bookmark, bookmarkManager: bookmarkManager) { [bookmark, bookmarkManager, weak self] newFolder in + if let newFolder { + bookmarkManager.move(objectUUIDs: [bookmark.id], + toIndex: 1, + withinParentFolder: .parent(uuid: newFolder.id), + completion: { [weak self] _ in + self?.reloadBookmark() + }) + } + self?.resetAddFolderState() + } + } + + private func resetAddFolderState() { + addFolderViewModel = nil + } + + @Published var bookmarkTitle: String { + didSet { + bookmark.title = bookmarkTitle + + bookmarkManager.update(bookmark: bookmark) + } + } + +} + +struct FolderViewModel: Identifiable, Equatable { + + let entity: BookmarkFolder + let level: Int + + var id: BookmarkFolder.ID { + entity.id + } + + var title: String { + BookmarkViewModel(entity: entity).menuTitle + } + +} + +extension [FolderViewModel] { + + init(_ list: BookmarkList?) { + guard let topLevelEntities = list?.topLevelEntities else { + assertionFailure("Tried to refresh bookmark folder picker, but couldn't get bookmark list") + self = [] + return + } + self.init(entities: topLevelEntities, level: 0) + } + + private init(entities: [BaseBookmarkEntity], level: Int) { + self = [] + for entity in entities { + guard let folder = entity as? BookmarkFolder else { continue } + let item = FolderViewModel(entity: folder, level: level) + self.append(item) + + let childModels = Self(entities: folder.children, level: level + 1) + self.append(contentsOf: childModels) + } + } + +} diff --git a/DuckDuckGo/Common/Extensions/NSMenuExtension.swift b/DuckDuckGo/Common/Extensions/NSMenuExtension.swift index d05cfbc33b..69043e9a3b 100644 --- a/DuckDuckGo/Common/Extensions/NSMenuExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSMenuExtension.swift @@ -18,6 +18,8 @@ import AppKit +typealias MenuBuilder = ArrayBuilder + extension NSMenu { convenience init(title: String = "", items: [NSMenuItem]) { @@ -25,6 +27,16 @@ extension NSMenu { self.items = items } + convenience init(title string: String = "", @MenuBuilder items: () -> [NSMenuItem]) { + self.init(title: string, items: items()) + } + + @discardableResult + func buildItems(@MenuBuilder items: () -> [NSMenuItem]) -> NSMenu { + self.items = items() + return self + } + func indexOfItem(withIdentifier id: String) -> Int? { return items.enumerated().first(where: { $0.element.identifier?.rawValue == id })?.offset } diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 4ad1d7cc30..bd0060b054 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -458,6 +458,8 @@ struct UserText { static let newFolder = NSLocalizedString("folder.optionsMenu.newFolder", value: "New Folder", comment: "Option for creating a new folder") static let renameFolder = NSLocalizedString("folder.optionsMenu.renameFolder", value: "Rename Folder", comment: "Option for renaming a folder") static let deleteFolder = NSLocalizedString("folder.optionsMenu.deleteFolder", value: "Delete Folder", comment: "Option for deleting a folder") + static let newFolderDialogFolderNameTitle = NSLocalizedString("add.folder.name", value: "Name:", comment: "Add Folder popover: folder name text field title") + static let newBookmarkDialogBookmarkNameTitle = NSLocalizedString("add.bookmark.name", value: "Name:", comment: "New bookmark folder dialog folder name field heading") static let updateBookmark = NSLocalizedString("bookmark.update", value: "Update Bookmark", comment: "Option for updating a bookmark") diff --git a/DuckDuckGo/Common/Utilities/ArrayBuilder.swift b/DuckDuckGo/Common/Utilities/ArrayBuilder.swift new file mode 100644 index 0000000000..e872e4d7bc --- /dev/null +++ b/DuckDuckGo/Common/Utilities/ArrayBuilder.swift @@ -0,0 +1,92 @@ +// +// ArrayBuilder.swift +// +// Copyright © 2023 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 AppKit + +@resultBuilder +struct ArrayBuilder { + + @inlinable + static func buildBlock() -> [Element] { + return [] + } + + @inlinable + static func buildBlock(_ element: Element) -> [Element] { + return [element] + } + + @inlinable + static func buildBlock(_ elements: Element...) -> [Element] { + return elements + } + + @inlinable + static func buildBlock(_ components: [Element]...) -> [Element] { + return components.flatMap { $0 } + } + + @inlinable + static func buildOptional(_ components: [Element]?) -> [Element] { + return components ?? [] + } + + @inlinable + static func buildEither(first component: Element) -> [Element] { + return [component] + } + + @inlinable + static func buildEither(first component: [Element]) -> [Element] { + component + } + + @inlinable + static func buildEither(second component: [Element]) -> [Element] { + component + } + + static func buildLimitedAvailability(_ component: [Element]) -> [Element] { + component + } + + @inlinable + static func buildArray(_ components: [[Element]]) -> [Element] { + components.flatMap { $0 } + } + + @inlinable + static func buildExpression(_ expression: [Element]) -> [Element] { + return expression + } + + @inlinable + static func buildExpression(_ expression: Element) -> [Element] { + return [expression] + } + + @inlinable + static func buildExpression(_ expression: Element?) -> [Element] { + return expression.map { [$0] } ?? [] + } + + static func buildExpression(_ expression: Void) -> [Element] { + return [] + } + +} diff --git a/DuckDuckGo/Common/View/SwiftUI/FocusableTextField.swift b/DuckDuckGo/Common/View/SwiftUI/FocusableTextField.swift new file mode 100644 index 0000000000..776b22e968 --- /dev/null +++ b/DuckDuckGo/Common/View/SwiftUI/FocusableTextField.swift @@ -0,0 +1,61 @@ +// +// FocusableTextField.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 SwiftUI + +extension TextField { + +#if !APPSTORE + @available(macOS, obsoleted: 12.0, message: "This needs to be cleaned up") + @ViewBuilder + func focusedOnAppear() -> some View { + if #available(macOS 12.0, *) { + TextFieldFocusedOnAppear(textField: self) + } else { + self + } + } +#else + @ViewBuilder + func focusedOnAppear() -> some View { + TextFieldFocusedOnAppear(textField: self) + } +#endif + +} + +@available(macOS 12.0, *) +struct TextFieldFocusedOnAppear: View { + + let textField: TextField