diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0965772e2b..38fb77252b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -12942,7 +12942,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 96.0.1; + version = 97.0.0; }; }; AA06B6B52672AF8100F541C5 /* XCRemoteSwiftPackageReference "Sparkle" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e9e774e2b4..24e8e97f71 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" : "308abf4ebf170dc73d9f1a8a1730ed3170bed2d5", - "version" : "96.0.1" + "revision" : "d671accf1bf7097c4e7f5cd55cd1c6dfa005cf92", + "version" : "97.0.0" } }, { diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 92e373af52..bf83796dcf 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -69,6 +69,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel private(set) var syncDataProviders: SyncDataProviders! private(set) var syncService: DDGSyncing? private var isSyncInProgressCancellable: AnyCancellable? + private var syncFeatureFlagsCancellable: AnyCancellable? private var screenLockedCancellable: AnyCancellable? private var emailCancellables = Set() let bookmarksManager = LocalBookmarkManager.shared @@ -339,7 +340,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel let environment = defaultEnvironment #endif let syncDataProviders = SyncDataProviders(bookmarksDatabase: BookmarkDatabase.shared.db) - let syncService = DDGSync(dataProvidersSource: syncDataProviders, errorEvents: SyncErrorHandler(), log: OSLog.sync, environment: environment) + let syncService = DDGSync( + dataProvidersSource: syncDataProviders, + errorEvents: SyncErrorHandler(), + privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, + log: OSLog.sync, + environment: environment + ) syncService.initializeIfNeeded() syncDataProviders.setUpDatabaseCleaners(syncService: syncService) diff --git a/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift b/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift index fb7b34b26e..f0e8b44787 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 = "\"ca66d409eb00e5c19f3a0abae449dd1a\"" - public static let embeddedDataSHA = "42f9d3064372bc85ac8ee37afe883ed4741d6a3cfcb9ce927c2f732c3f694140" + public static let embeddedDataETag = "\"8e38db3f042f4a2a5c9f7ca8d33f17c9\"" + public static let embeddedDataSHA = "a9fc8ee927a37d1b5545377ff29de798f3ec3b8a757430c3af72d13a3844c258" } var embeddedDataEtag: String { diff --git a/DuckDuckGo/ContentBlocker/macos-config.json b/DuckDuckGo/ContentBlocker/macos-config.json index 94914fa75a..6ec6c5ecf4 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": 1702579565498, + "version": 1703003631137, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -245,6 +245,9 @@ { "domain": "metro.co.uk" }, + { + "domain": "youtube.com" + }, { "domain": "earth.google.com" }, @@ -264,7 +267,7 @@ ] }, "state": "enabled", - "hash": "9ab9e1acdb6a8617c77109acc1e3943c" + "hash": "a1060783f1bf56f5cc661b994c9c9e56" }, "autofill": { "exceptions": [ @@ -1030,12 +1033,16 @@ { "domain": "flysas.com", "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1347" + }, + { + "domain": "facebook.com", + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1641" } ] }, "exceptions": [], "state": "enabled", - "hash": "d757f6509e9a9a20140c755ed0f21ea2" + "hash": "6ed03febdd780fd845cc1f7d6ce74ecf" }, "dbp": { "state": "enabled", @@ -2014,6 +2021,23 @@ } ] }, + { + "domain": "dexerto.com", + "rules": [ + { + "selector": "#leaderboard-top-adhesion", + "type": "closest-empty" + }, + { + "selector": "[data-cy='Ad']", + "type": "closest-empty" + }, + { + "selector": "[data-cy='VidazooPlayer']", + "type": "closest-empty" + } + ] + }, { "domain": "dpreview.com", "rules": [ @@ -3678,7 +3702,7 @@ ] }, "state": "enabled", - "hash": "c34713afaf78e090b587a923f132ed56" + "hash": "8551081ffe228a4bef49deba3fe37b19" }, "exceptionHandler": { "exceptions": [ @@ -3933,10 +3957,13 @@ }, { "domain": "airbnb.com.br" + }, + { + "domain": "zoom.us" } ], "state": "enabled", - "hash": "75e673ce365430652fc7cc896ed49a5f" + "hash": "82ebfddf791575ada285a49b7fdc71ca" }, "fingerprintingScreenSize": { "settings": { @@ -4460,6 +4487,11 @@ "state": "disabled", "hash": "5e792dd491428702bc0104240fbce0ce" }, + "sync": { + "exceptions": [], + "state": "disabled", + "hash": "728493ef7a1488e4781656d3f9db84aa" + }, "trackerAllowlist": { "state": "enabled", "settings": { @@ -4750,8 +4782,7 @@ { "rule": "analytics.analytics-egain.com/onetag/", "domains": [ - "landsend.com", - "support.norton.com" + "" ] } ] @@ -5323,6 +5354,7 @@ "asics.com", "brooklinen.com", "carters.com", + "otterbox.com", "seatosummit.com" ] } @@ -5382,6 +5414,16 @@ } ] }, + "egain.cloud": { + "rules": [ + { + "rule": "egain.cloud/", + "domains": [ + "" + ] + } + ] + }, "ensighten.com": { "rules": [ { @@ -5509,8 +5551,7 @@ { "rule": "app.five9.com", "domains": [ - "gmsdnv.com", - "machiassavings.bank" + "" ] } ] @@ -5750,6 +5791,12 @@ "rawstory.com", "usatoday.com" ] + }, + { + "rule": "storage.googleapis.com/code.snapengage.com/", + "domains": [ + "" + ] } ] }, @@ -5977,15 +6024,9 @@ "gorgias.chat": { "rules": [ { - "rule": "config.gorgias.chat", + "rule": "gorgias.chat", "domains": [ - "help.athleticbrewing.com" - ] - }, - { - "rule": "assets.gorgias.chat", - "domains": [ - "help.athleticbrewing.com" + "" ] } ] @@ -7509,7 +7550,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "5cecb6d28193f468b28b9afdaca04da1" + "hash": "b6996e16fe1785f6d75cd6302da1f48e" }, "trackingCookies1p": { "settings": { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift index dec1e1cf46..a7dc7255d6 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift @@ -61,7 +61,7 @@ struct VPNLocationView: View { @ViewBuilder private func nearest(isSelected: Bool) -> some View { - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { Text(UserText.vpnLocationRecommendedSectionTitle) .font(.system(size: 15)) .foregroundColor(.primary) @@ -98,7 +98,7 @@ struct VPNLocationView: View { EmptyView() .listRowBackground(Color.clear) case .loaded(let countryItems): - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { Text(UserText.vpnLocationCustomSectionTitle) .font(.system(size: 15)) .foregroundColor(.primary) diff --git a/DuckDuckGo/Preferences/Model/PreferencesSection.swift b/DuckDuckGo/Preferences/Model/PreferencesSection.swift index 2c71c401a7..ad061c7ed0 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSection.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSection.swift @@ -24,7 +24,7 @@ struct PreferencesSection: Hashable, Identifiable { let panes: [PreferencePaneIdentifier] @MainActor - static func defaultSections(includingDuckPlayer: Bool, includingVPN: Bool) -> [PreferencesSection] { + static func defaultSections(includingDuckPlayer: Bool, includingSync: Bool, includingVPN: Bool) -> [PreferencesSection] { let regularPanes: [PreferencePaneIdentifier] = { #if SUBSCRIPTION var panes: [PreferencePaneIdentifier] = [.privacy, .subscription, .general, .appearance, .autofill, .downloads] @@ -37,7 +37,7 @@ struct PreferencesSection: Hashable, Identifiable { #else var panes: [PreferencePaneIdentifier] = [.general, .appearance, .privacy, .autofill, .downloads] - if NSApp.delegateTyped.internalUserDecider.isInternalUser { + if includingSync { panes.insert(.sync, at: 1) } #endif diff --git a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift index 363378641a..11e81dd11c 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift @@ -16,9 +16,10 @@ // limitations under the License. // -import SwiftUI import BrowserServicesKit import Combine +import DDGSync +import SwiftUI final class PreferencesSidebarModel: ObservableObject { @@ -33,7 +34,8 @@ final class PreferencesSidebarModel: ObservableObject { init( loadSections: @escaping () -> [PreferencesSection], tabSwitcherTabs: [Tab.TabContent], - privacyConfigurationManager: PrivacyConfigurationManaging + privacyConfigurationManager: PrivacyConfigurationManaging, + syncService: DDGSyncing ) { self.loadSections = loadSections self.tabSwitcherTabs = tabSwitcherTabs @@ -41,12 +43,18 @@ final class PreferencesSidebarModel: ObservableObject { resetTabSelectionIfNeeded() refreshSections() - privacyConfigurationManager.updatesPublisher + let duckPlayerFeatureFlagDidChange = privacyConfigurationManager.updatesPublisher .map { [weak privacyConfigurationManager] in privacyConfigurationManager?.privacyConfig.isEnabled(featureKey: .duckPlayer) == true } .removeDuplicates() .asVoid() + + let syncFeatureFlagsDidChange = syncService.featureFlagsPublisher.map { $0.contains(.userInterface) } + .removeDuplicates() + .asVoid() + + Publishers.Merge(duckPlayerFeatureFlagDidChange, syncFeatureFlagsDidChange) .receive(on: DispatchQueue.main) .sink { [weak self] in self?.refreshSections() @@ -62,6 +70,7 @@ final class PreferencesSidebarModel: ObservableObject { convenience init( tabSwitcherTabs: [Tab.TabContent] = Tab.TabContent.displayableTabTypes, privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, + syncService: DDGSyncing, includeDuckPlayer: Bool ) { let loadSections = { @@ -71,12 +80,17 @@ final class PreferencesSidebarModel: ObservableObject { let includingVPN = false #endif - return PreferencesSection.defaultSections(includingDuckPlayer: includeDuckPlayer, includingVPN: includingVPN) + return PreferencesSection.defaultSections( + includingDuckPlayer: includeDuckPlayer, + includingSync: syncService.featureFlags.contains(.userInterface), + includingVPN: includingVPN + ) } self.init(loadSections: loadSections, tabSwitcherTabs: tabSwitcherTabs, - privacyConfigurationManager: privacyConfigurationManager) + privacyConfigurationManager: privacyConfigurationManager, + syncService: syncService) } // MARK: - Setup diff --git a/DuckDuckGo/Preferences/Model/SyncPreferences.swift b/DuckDuckGo/Preferences/Model/SyncPreferences.swift index 60933937e5..e1f62308ba 100644 --- a/DuckDuckGo/Preferences/Model/SyncPreferences.swift +++ b/DuckDuckGo/Preferences/Model/SyncPreferences.swift @@ -89,14 +89,38 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { private var isScreenLocked: Bool = false private var recoveryKey: SyncCode.RecoveryKey? + @Published var syncFeatureFlags: SyncFeatureFlags { + didSet { + updateSyncFeatureFlags(syncFeatureFlags) + } + } + + @Published var isDataSyncingAvailable: Bool = true + @Published var isConnectingDevicesAvailable: Bool = true + @Published var isAccountCreationAvailable: Bool = true + @Published var isAccountRecoveryAvailable: Bool = true + + private func updateSyncFeatureFlags(_ syncFeatureFlags: SyncFeatureFlags) { + isDataSyncingAvailable = syncFeatureFlags.contains(.dataSyncing) + isConnectingDevicesAvailable = syncFeatureFlags.contains(.connectFlows) + isAccountCreationAvailable = syncFeatureFlags.contains(.accountCreation) + isAccountRecoveryAvailable = syncFeatureFlags.contains(.accountRecovery) + } + var recoveryCode: String? { syncService.account?.recoveryCode } - init(syncService: DDGSyncing, syncBookmarksAdapter: SyncBookmarksAdapter, appearancePreferences: AppearancePreferences = .shared, managementDialogModel: ManagementDialogModel = ManagementDialogModel()) { + init( + syncService: DDGSyncing, + syncBookmarksAdapter: SyncBookmarksAdapter, + appearancePreferences: AppearancePreferences = .shared, + managementDialogModel: ManagementDialogModel = ManagementDialogModel() + ) { self.syncService = syncService self.syncBookmarksAdapter = syncBookmarksAdapter self.appearancePreferences = appearancePreferences + self.syncFeatureFlags = syncService.featureFlags self.isFaviconsFetchingEnabled = syncBookmarksAdapter.isFaviconsFetchingEnabled self.isUnifiedFavoritesEnabled = appearancePreferences.favoritesDisplayMode.isDisplayUnified @@ -106,11 +130,19 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { self.managementDialogModel = managementDialogModel self.managementDialogModel.delegate = self + updateSyncFeatureFlags(self.syncFeatureFlags) setUpObservables() setUpSyncOptionsObservables(apperancePreferences: appearancePreferences) } private func setUpObservables() { + syncService.featureFlagsPublisher + .dropFirst() + .removeDuplicates() + .receive(on: DispatchQueue.main) + .assign(to: \.syncFeatureFlags, onWeaklyHeld: self) + .store(in: &cancellables) + syncService.authStatePublisher .removeDuplicates() .asVoid() diff --git a/DuckDuckGo/Preferences/View/PreferencesViewController.swift b/DuckDuckGo/Preferences/View/PreferencesViewController.swift index 0289ac6afc..0a9e3823d6 100644 --- a/DuckDuckGo/Preferences/View/PreferencesViewController.swift +++ b/DuckDuckGo/Preferences/View/PreferencesViewController.swift @@ -19,6 +19,7 @@ import AppKit import SwiftUI import Combine +import DDGSync #if NETWORK_PROTECTION import NetworkProtection @@ -28,12 +29,21 @@ final class PreferencesViewController: NSViewController { weak var delegate: BrowserTabSelectionDelegate? - let model = PreferencesSidebarModel(includeDuckPlayer: DuckPlayer.shared.isAvailable) + let model: PreferencesSidebarModel private var selectedTabIndexCancellable: AnyCancellable? private var selectedPreferencePaneCancellable: AnyCancellable? private var bitwardenManager: BWManagement = BWManager.shared + init(syncService: DDGSyncing, duckPlayer: DuckPlayer = DuckPlayer.shared) { + model = PreferencesSidebarModel(syncService: syncService, includeDuckPlayer: duckPlayer.isAvailable) + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func loadView() { view = NSView() } diff --git a/DuckDuckGo/Sync/FaviconsFetcherOnboarding.swift b/DuckDuckGo/Sync/FaviconsFetcherOnboarding.swift index 2f28741993..3cd482ced8 100644 --- a/DuckDuckGo/Sync/FaviconsFetcherOnboarding.swift +++ b/DuckDuckGo/Sync/FaviconsFetcherOnboarding.swift @@ -68,7 +68,8 @@ final class FaviconsFetcherOnboarding { } private var shouldPresentOnboarding: Bool { - !didPresentFaviconsFetchingOnboarding + syncService.featureFlags.contains(.userInterface) + && !didPresentFaviconsFetchingOnboarding && !syncBookmarksAdapter.isFaviconsFetchingEnabled && syncBookmarksAdapter.isEligibleForFaviconsFetcherOnboarding } diff --git a/DuckDuckGo/Tab/View/BrowserTabViewController.swift b/DuckDuckGo/Tab/View/BrowserTabViewController.swift index 872b4fb27e..1192e49e25 100644 --- a/DuckDuckGo/Tab/View/BrowserTabViewController.swift +++ b/DuckDuckGo/Tab/View/BrowserTabViewController.swift @@ -546,7 +546,10 @@ final class BrowserTabViewController: NSViewController { var preferencesViewController: PreferencesViewController? private func preferencesViewControllerCreatingIfNeeded() -> PreferencesViewController { return preferencesViewController ?? { - let preferencesViewController = PreferencesViewController() + guard let syncService = NSApp.delegateTyped.syncService else { + fatalError("Sync service is nil") + } + let preferencesViewController = PreferencesViewController(syncService: syncService) preferencesViewController.delegate = self self.preferencesViewController = preferencesViewController return preferencesViewController diff --git a/LocalPackages/Account/Package.swift b/LocalPackages/Account/Package.swift index 9062d1b12f..b32006c664 100644 --- a/LocalPackages/Account/Package.swift +++ b/LocalPackages/Account/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["Account"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), .package(path: "../Purchase") ], targets: [ diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index 046edbc6a8..ec47443f0f 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: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), .package(path: "../PixelKit"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper") diff --git a/LocalPackages/LoginItems/Package.swift b/LocalPackages/LoginItems/Package.swift index c98d6bb942..54a86db801 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: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ .target( diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 337ecff4d0..e7fb30c89e 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: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions") ], diff --git a/LocalPackages/PixelKit/Package.swift b/LocalPackages/PixelKit/Package.swift index a9cbf81607..aaaa2d9e09 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: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ .target( diff --git a/LocalPackages/Purchase/Package.swift b/LocalPackages/Purchase/Package.swift index b2a61f6b19..6ebc16624c 100644 --- a/LocalPackages/Purchase/Package.swift +++ b/LocalPackages/Purchase/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ .target( diff --git a/LocalPackages/Subscription/Package.swift b/LocalPackages/Subscription/Package.swift index e95d8aba1d..e9b73b884a 100644 --- a/LocalPackages/Subscription/Package.swift +++ b/LocalPackages/Subscription/Package.swift @@ -15,7 +15,7 @@ let package = Package( .package(path: "../Account"), .package(path: "../Purchase"), .package(path: "../SwiftUIExtensions"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SwiftUIExtensions/Package.swift b/LocalPackages/SwiftUIExtensions/Package.swift index 1574244e15..cc43577d0b 100644 --- a/LocalPackages/SwiftUIExtensions/Package.swift +++ b/LocalPackages/SwiftUIExtensions/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/PreferencePaneSection.swift b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/PreferencePaneSection.swift index 928b489ddc..0fadaa2d19 100644 --- a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/PreferencePaneSection.swift +++ b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/PreferencePaneSection.swift @@ -24,9 +24,9 @@ public struct PreferencePaneSection: View where Content: View { public let verticalPadding: CGFloat @ViewBuilder public let content: () -> Content - public init(spacing: CGFloat = 12, vericalPadding: CGFloat = 20, @ViewBuilder content: @escaping () -> Content) { + public init(spacing: CGFloat = 12, verticalPadding: CGFloat = 20, @ViewBuilder content: @escaping () -> Content) { self.spacing = spacing - self.verticalPadding = vericalPadding + self.verticalPadding = verticalPadding self.content = content } diff --git a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/TextButton.swift b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/TextButton.swift index 329cb46b85..1e73626837 100644 --- a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/TextButton.swift +++ b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/TextButton.swift @@ -21,16 +21,19 @@ import SwiftUI public struct TextButton: View { public let title: String + public let fontWeight: Font.Weight public let action: () -> Void - public init(_ title: String, action: @escaping () -> Void) { + public init(_ title: String, weight: Font.Weight = .regular, action: @escaping () -> Void) { self.title = title + self.fontWeight = weight self.action = action } public var body: some View { Button(action: action) { Text(title) + .fontWeight(fontWeight) .foregroundColor(Color("LinkBlueColor")) } .buttonStyle(.plain) diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 1f60d74fb1..49c6f30303 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: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift index 89cb583ab2..4e1d1de021 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift @@ -20,6 +20,11 @@ import Foundation public protocol ManagementViewModel: ObservableObject { + var isDataSyncingAvailable: Bool { get } + var isConnectingDevicesAvailable: Bool { get } + var isAccountCreationAvailable: Bool { get } + var isAccountRecoveryAvailable: Bool { get } + var isSyncEnabled: Bool { get } var isCreatingAccount: Bool { get } var shouldShowErrorMessage: Bool { get set } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift index 2f17ab011d..3878a7d693 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift @@ -25,6 +25,7 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { var body: some View { // Errors VStack(alignment: .leading, spacing: 16) { + syncUnavailableView() if model.isSyncBookmarksPaused { syncPaused(for: .bookmarks) } @@ -34,14 +35,14 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { } // Sync Enabled - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { SyncStatusView() .environmentObject(model) .frame(width: 513, alignment: .topLeading) } // Synced Devices - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { Text(UserText.syncedDevices) .font(Const.Fonts.preferencePaneSectionHeader) .padding(.horizontal, 16) @@ -51,7 +52,7 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { } // Options - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { Text(UserText.optionsSectionTitle) .font(Const.Fonts.preferencePaneSectionHeader) .padding(.horizontal, 16) @@ -94,7 +95,7 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { } // Recovery - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { VStack(alignment: .leading, spacing: 6) { Text(UserText.recovery) .font(Const.Fonts.preferencePaneSectionHeader) @@ -112,7 +113,7 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { } // Turn Off and Delate Data - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { Button(UserText.turnOffAndDeleteServerData) { model.presentDeleteAccount() } @@ -138,28 +139,23 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { return UserText.credentialsLimitExceededAction } } - PreferencePaneSection(vericalPadding: 16) { - HStack(alignment: .top, spacing: 8) { - Text("⚠️") - VStack(alignment: .leading, spacing: 8) { - Text(UserText.syncLimitExceededTitle) - .bold() - Text(description) - Button(actionTitle) { - switch itemType { - case .bookmarks: - model.manageBookmarks() - case .credentials: - model.manageLogins() - } - } - .padding(.top, 8) - } + SyncWarningMessage(title: UserText.syncLimitExceededTitle, message: description, buttonTitle: actionTitle) { + switch itemType { + case .bookmarks: + model.manageBookmarks() + case .credentials: + model.manageLogins() } - .padding(.horizontal, 16) } - .frame(width: 512, alignment: .leading) - .background(RoundedRectangle(cornerRadius: 8).foregroundColor(Color("AlertBubbleBackground"))) + } + + @ViewBuilder + fileprivate func syncUnavailableView() -> some View { + if model.isDataSyncingAvailable { + EmptyView() + } else { + SyncWarningMessage(title: UserText.serviceUnavailable, message: UserText.warningSyncDisabled) + } } enum LimitedItemType { diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift index 471fb6a546..1ea3bf81a7 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift @@ -22,34 +22,10 @@ import SwiftUIExtensions struct SyncSetupView: View where ViewModel: ManagementViewModel { @EnvironmentObject var model: ViewModel - fileprivate func syncWithAnotherDeviceView() -> some View { - return VStack(alignment: .center, spacing: 16) { - Image("Sync-Pair-96") - VStack(alignment: .center, spacing: 8) { - SyncUIViews.TextHeader(text: UserText.beginSyncTitle) - SyncUIViews.TextDetailSecondary(text: UserText.beginSyncDescription) - } - .padding(.bottom, 16) - ZStack { - RoundedRectangle(cornerRadius: 8) - .fill(Color("LinkBlueColor")) - .frame(width: 220, height: 32) - Text(UserText.beginSyncButton) - .foregroundColor(.white) - .bold() - } - .onTapGesture { - model.syncWithAnotherDevicePressed() - } - } - .frame(width: 512, height: 254) - .roundedBorder() - .padding(.top, 20) - } - var body: some View { VStack(alignment: .leading, spacing: 24) { VStack(spacing: 8) { + syncUnavailableView() syncWithAnotherDeviceView() SyncUIViews.TextDetailSecondary(text: UserText.beginSyncFooter) .padding(.bottom, 24) @@ -59,16 +35,67 @@ struct SyncSetupView: View where ViewModel: ManagementViewModel { VStack(alignment: .leading, spacing: 12) { SyncUIViews.TextHeader2(text: UserText.otherOptionsSectionTitle) VStack(alignment: .leading, spacing: 8) { - SyncUIViews.TextLink(text: UserText.syncThisDeviceLink) - .onTapGesture { - model.syncWithServerPressed() - } - SyncUIViews.TextLink(text: UserText.recoverDataLink) - .onTapGesture { - model.recoverDataPressed() - } + TextButton(UserText.syncThisDeviceLink, weight: .semibold, action: model.syncWithServerPressed) + .disabled(!model.isAccountCreationAvailable) + TextButton(UserText.recoverDataLink, weight: .semibold, action: model.recoverDataPressed) + .disabled(!model.isAccountRecoveryAvailable) } } } } + + fileprivate func syncWithAnotherDeviceView() -> some View { + VStack(alignment: .center, spacing: 16) { + Image("Sync-Pair-96") + VStack(alignment: .center, spacing: 8) { + SyncUIViews.TextHeader(text: UserText.beginSyncTitle) + SyncUIViews.TextDetailSecondary(text: UserText.beginSyncDescription) + } + .padding(.bottom, 16) + Button(UserText.beginSyncButton, action: model.syncWithAnotherDevicePressed) + .buttonStyle(SyncWithAnotherDeviceButtonStyle(enabled: model.isConnectingDevicesAvailable)) + .disabled(!model.isConnectingDevicesAvailable) + } + .frame(width: 512, height: 254) + .roundedBorder() + .padding(.top, 20) + } + + @ViewBuilder + fileprivate func syncUnavailableView() -> some View { + if !model.isDataSyncingAvailable || !model.isConnectingDevicesAvailable { + SyncWarningMessage(title: UserText.serviceUnavailable, message: UserText.warningSyncDisabled) + .padding(.top, 16) + } else if !model.isAccountCreationAvailable { + SyncWarningMessage(title: UserText.serviceUnavailable, message: UserText.warningAccountCreationDisabled) + .padding(.top, 16) + } else { + EmptyView() + } + } +} + +private struct SyncWithAnotherDeviceButtonStyle: ButtonStyle { + + public let enabled: Bool + + public init(enabled: Bool) { + self.enabled = enabled + } + + public func makeBody(configuration: Self.Configuration) -> some View { + + let enabledBackgroundColor = configuration.isPressed ? Color(NSColor.controlAccentColor).opacity(0.5) : Color(NSColor.controlAccentColor) + let disabledBackgroundColor = Color.gray.opacity(0.1) + let labelColor = enabled ? Color.white : Color.primary.opacity(0.3) + + configuration.label + .lineLimit(1) + .font(.body.bold()) + .frame(width: 220, height: 32) + .background(enabled ? enabledBackgroundColor : disabledBackgroundColor) + .foregroundColor(labelColor) + .cornerRadius(8) + + } } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncWarningMessage.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncWarningMessage.swift new file mode 100644 index 0000000000..cf9a80ed0b --- /dev/null +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncWarningMessage.swift @@ -0,0 +1,53 @@ +// +// SyncWarningMessage.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 SwiftUI +import SwiftUIExtensions + +struct SyncWarningMessage: View { + let title: String + let message: String + let buttonTitle: String? + let buttonAction: (() -> Void)? + + init(title: String, message: String, buttonTitle: String? = nil, buttonAction: (() -> Void)? = nil) { + self.title = title + self.message = message + self.buttonTitle = buttonTitle + self.buttonAction = buttonAction + } + + var body: some View { + PreferencePaneSection(verticalPadding: 16) { + HStack(alignment: .top, spacing: 8) { + Text("⚠️") + VStack(alignment: .leading, spacing: 8) { + Text(title).bold() + Text(message) + if let buttonTitle, let buttonAction { + Button(buttonTitle, action: buttonAction) + .padding(.top, 8) + } + } + } + .padding(.horizontal, 16) + } + .frame(width: 512, alignment: .leading) + .background(RoundedRectangle(cornerRadius: 8).foregroundColor(Color("AlertBubbleBackground"))) + } +} diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncedDevicesView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncedDevicesView.swift index c026ad51ce..766f8596ca 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncedDevicesView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncedDevicesView.swift @@ -27,7 +27,7 @@ struct SyncedDevicesView: View where ViewModel: ManagementViewModel { let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect() var body: some View { - VStack { + VStack(alignment: .leading) { SyncedDevicesList(devices: model.devices, presentDeviceDetails: model.presentDeviceDetails, presentRemoveDevice: model.presentRemoveDevice) @@ -41,15 +41,14 @@ struct SyncedDevicesView: View where ViewModel: ManagementViewModel { .onDisappear { isVisible = false } - SyncPreferencesRow { - } centerContent: { - Button { - model.syncWithAnotherDevicePressed() - } label: { - Text("Sync with Another Device") - .frame(height: 24) - } + Button { + model.syncWithAnotherDevicePressed() + } label: { + Text(UserText.beginSyncButton) } + .disabled(!model.isConnectingDevicesAvailable) + .padding(.horizontal, 10) + .padding(.bottom, 8) } .roundedBorder() } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift index 928aa1123f..2b7d2a1059 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift @@ -157,4 +157,9 @@ enum UserText { static let fetchFaviconsOnboardingTitle = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-title", value: "Download Missing Icons?", comment: "Title for fetch favicons onboarding dialog") static let fetchFaviconsOnboardingMessage = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-message", value: "Do you want this device to automatically download icons for any new bookmarks synced from your other devices? This will expose the download to your network any time a bookmark is synced.", comment: "Text for fetch favicons onboarding dialog") static let keepFaviconsUpdated = NSLocalizedString("prefrences.sync.keep-favicons-updated", value: "Keep Bookmarks Icons Updated", comment: "Title of the confirmation button for favicons fetching") + + // Sync Feature Flags + static let serviceUnavailable = NSLocalizedString("sync.warning.service-unavailable", value: "Service Unavailable", comment: "Title of the warning message") + static let warningSyncDisabled = NSLocalizedString("sync.warning.sync-disabled", value: "We apologize, but the service is currently unavailable. Please try again later.", comment: "Sync unavailable warning message") + static let warningAccountCreationDisabled = NSLocalizedString("sync.warning.account-creation-disabled", value: "We apologize, but new account creation is currently unavailable for this service. Please try again later.", comment: "Sync unavailable warning message") } diff --git a/LocalPackages/SystemExtensionManager/Package.swift b/LocalPackages/SystemExtensionManager/Package.swift index 63be2bf115..e27bd650a8 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: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], 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 a272ed0be1..1424527204 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: "96.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. diff --git a/UnitTests/Preferences/PreferencesSidebarModelTests.swift b/UnitTests/Preferences/PreferencesSidebarModelTests.swift index f601cb90f4..1d6666ae91 100644 --- a/UnitTests/Preferences/PreferencesSidebarModelTests.swift +++ b/UnitTests/Preferences/PreferencesSidebarModelTests.swift @@ -31,7 +31,12 @@ final class PreferencesSidebarModelTests: XCTestCase { } private func PreferencesSidebarModel(loadSections: [PreferencesSection]? = nil, tabSwitcherTabs: [Tab.TabContent] = Tab.TabContent.displayableTabTypes) -> DuckDuckGo_Privacy_Browser.PreferencesSidebarModel { - return DuckDuckGo_Privacy_Browser.PreferencesSidebarModel(loadSections: { loadSections ?? PreferencesSection.defaultSections(includingDuckPlayer: false, includingVPN: false) }, tabSwitcherTabs: tabSwitcherTabs, privacyConfigurationManager: MockPrivacyConfigurationManager()) + return DuckDuckGo_Privacy_Browser.PreferencesSidebarModel( + loadSections: { loadSections ?? PreferencesSection.defaultSections(includingDuckPlayer: false, includingSync: false, includingVPN: false) }, + tabSwitcherTabs: tabSwitcherTabs, + privacyConfigurationManager: MockPrivacyConfigurationManager(), + syncService: MockDDGSyncing(authState: .inactive, isSyncInProgress: false) + ) } func testWhenInitializedThenFirstPaneInFirstSectionIsSelected() throws { diff --git a/UnitTests/Sync/SyncPreferencesTests.swift b/UnitTests/Sync/SyncPreferencesTests.swift index f3a9a4f8fb..5317d03c02 100644 --- a/UnitTests/Sync/SyncPreferencesTests.swift +++ b/UnitTests/Sync/SyncPreferencesTests.swift @@ -143,6 +143,12 @@ class MockDDGSyncing: DDGSyncing { var dataProvidersSource: DataProvidersSource? + @Published var featureFlags: SyncFeatureFlags = .all + + var featureFlagsPublisher: AnyPublisher { + $featureFlags.eraseToAnyPublisher() + } + @Published var authState: SyncAuthState = .inactive var authStatePublisher: AnyPublisher { @@ -161,7 +167,7 @@ class MockDDGSyncing: DDGSyncing { $isSyncInProgress.eraseToAnyPublisher() } - init(dataProvidersSource: DataProvidersSource? = nil, authState: SyncAuthState, account: SyncAccount? = nil, scheduler: Scheduling, isSyncInProgress: Bool) { + init(dataProvidersSource: DataProvidersSource? = nil, authState: SyncAuthState, account: SyncAccount? = nil, scheduler: Scheduling = CapturingScheduler(), isSyncInProgress: Bool) { self.dataProvidersSource = dataProvidersSource self.authState = authState self.account = account