diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index ff311a2fa2..88beeca5eb 100644 --- a/Core/AppPrivacyConfigurationDataProvider.swift +++ b/Core/AppPrivacyConfigurationDataProvider.swift @@ -23,8 +23,8 @@ import BrowserServicesKit final public class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"f4e8436ab9977e1a8a9d6ee600fc353e\"" - public static let embeddedDataSHA = "86b3a7bece52da74f7d267c2b522ac929d363a384cacc013f2b2d057ee1e386c" + public static let embeddedDataETag = "\"388dd0526e94f80473728c0bfbb48b39\"" + public static let embeddedDataSHA = "f7b9ae8860ff84f33e602b40d0938776d2d9327115b4ddfe09fc0fa09b5e1ff1" } public var embeddedDataEtag: String { diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index dfe79f7d48..06e6589641 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -39,8 +39,10 @@ public enum FeatureFlag: String { extension FeatureFlag: FeatureFlagSourceProviding { public var source: FeatureFlagSource { switch self { - case .debugMenu, .sync, .appTrackingProtection: + case .debugMenu, .appTrackingProtection: return .internalOnly + case .sync: + return .remoteReleasable(.subfeature(SyncSubfeature.level0ShowSync)) case .networkProtection: return .remoteReleasable(.feature(.networkProtection)) case .networkProtectionWaitlistAccess: diff --git a/Core/ios-config.json b/Core/ios-config.json index 7f2a82608c..2afc6d62c5 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1702917767277, + "version": 1703026028516, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -2956,11 +2956,14 @@ ] }, { - "domain": "orange.fr", + "domain": "oceanofcompressed.xyz", "rules": [ { - "selector": ".tag-rm", - "type": "hide-empty" + "type": "disable-default" + }, + { + "selector": "#sticky-ads", + "type": "hide" } ] }, @@ -2977,6 +2980,15 @@ } ] }, + { + "domain": "orange.fr", + "rules": [ + { + "selector": ".tag-rm", + "type": "hide-empty" + } + ] + }, { "domain": "ouest-france.fr", "rules": [ @@ -3788,7 +3800,7 @@ ] }, "state": "enabled", - "hash": "81d363bc5cd7d76de1aa11d5b0f8e27d" + "hash": "182ef21a9dcfd3a160468f851c4b1789" }, "exceptionHandler": { "exceptions": [ @@ -4502,6 +4514,25 @@ "state": "disabled", "hash": "5e792dd491428702bc0104240fbce0ce" }, + "sync": { + "exceptions": [], + "state": "internal", + "features": { + "level0ShowSync": { + "state": "enabled" + }, + "level1AllowDataSyncing": { + "state": "enabled" + }, + "level2AllowSetupFlows": { + "state": "enabled" + }, + "level3AllowCreateAccount": { + "state": "enabled" + } + }, + "hash": "92673fe625ae2b888a4b0bfa9a974ce4" + }, "trackerAllowlist": { "state": "enabled", "settings": { @@ -4764,6 +4795,12 @@ "wxii12.com", "wyff4.com" ] + }, + { + "rule": "z-na.amazon-adsystem.com/widgets/onejs", + "domains": [ + "oceanofcompressed.xyz" + ] } ] }, @@ -5522,8 +5559,7 @@ { "rule": "app.five9.com", "domains": [ - "gmsdnv.com", - "machiassavings.bank" + "" ] } ] @@ -6058,7 +6094,13 @@ { "rule": "api.hubspot.com/livechat-public/v1/message/public", "domains": [ - "pippintitle.com" + "" + ] + }, + { + "rule": "js.hubspot.com/web-interactives-embed.js", + "domains": [ + "" ] }, { @@ -6367,6 +6409,16 @@ } ] }, + "media.net": { + "rules": [ + { + "rule": "contextual.media.net/dmedianet.js", + "domains": [ + "oceanofcompressed.xyz" + ] + } + ] + }, "mediavine.com": { "rules": [ { @@ -6825,7 +6877,8 @@ { "rule": "secure.quantserve.com/quant.js", "domains": [ - "aternos.org" + "aternos.org", + "oceanofcompressed.xyz" ] } ] @@ -7510,7 +7563,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "163c9ec4fc3bdb9dbfc75e70839a31d7" + "hash": "c1968268cb8a82bf532443edd17d9499" }, "trackingCookies1p": { "settings": { diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 67b2d9fd47..03f1bb39f3 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9221,7 +9221,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 96.0.0; + version = 97.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ec9031e07b..5eb1e13d46 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" : "e5c9e31b19b3cf78e2b704a60882ba24b9bc680d", - "version" : "96.0.0" + "revision" : "d671accf1bf7097c4e7f5cd55cd1c6dfa005cf92", + "version" : "97.0.0" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 4aed576445..cabfab4df5 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -283,7 +283,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { favoritesDisplayModeStorage: FavoritesDisplayModeStorage() ) - let syncService = DDGSync(dataProvidersSource: syncDataProviders, errorEvents: SyncErrorHandler(), log: .syncLog, environment: environment) + let syncService = DDGSync( + dataProvidersSource: syncDataProviders, + errorEvents: SyncErrorHandler(), + privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, + log: .syncLog, + environment: environment + ) syncService.initializeIfNeeded() self.syncService = syncService diff --git a/DuckDuckGo/FaviconsFetcherOnboarding.swift b/DuckDuckGo/FaviconsFetcherOnboarding.swift index ec1418f2a7..1f87dd7c82 100644 --- a/DuckDuckGo/FaviconsFetcherOnboarding.swift +++ b/DuckDuckGo/FaviconsFetcherOnboarding.swift @@ -60,7 +60,8 @@ final class FaviconsFetcherOnboarding { } private var shouldPresentOnboarding: Bool { - !didPresentFaviconsFetchingOnboarding + syncService.featureFlags.contains(.userInterface) + && !didPresentFaviconsFetchingOnboarding && !syncBookmarksAdapter.isFaviconsFetchingEnabled && syncBookmarksAdapter.isEligibleForFaviconsFetcherOnboarding } diff --git a/DuckDuckGo/SettingsViewController.swift b/DuckDuckGo/SettingsViewController.swift index 40ddfbdf3e..81b82048a0 100644 --- a/DuckDuckGo/SettingsViewController.swift +++ b/DuckDuckGo/SettingsViewController.swift @@ -113,7 +113,7 @@ class SettingsViewController: UITableViewController { } private var shouldShowSyncCell: Bool { - return featureFlagger.isFeatureOn(.sync) + return syncService.featureFlags.contains(.userInterface) || internalUserDecider.isInternalUser } private var shouldShowTextSizeCell: Bool { diff --git a/DuckDuckGo/SyncSettingsViewController.swift b/DuckDuckGo/SyncSettingsViewController.swift index 84d4847ca8..4a72c25aba 100644 --- a/DuckDuckGo/SyncSettingsViewController.swift +++ b/DuckDuckGo/SyncSettingsViewController.swift @@ -69,6 +69,7 @@ class SyncSettingsViewController: UIHostingController { setUpFaviconsFetcherSwitch(viewModel) setUpFavoritesDisplayModeSwitch(viewModel, appSettings) setUpSyncPaused(viewModel, appSettings) + setUpSyncFeatureFlags(viewModel) refreshForState(syncService.authState) syncService.authStatePublisher @@ -87,6 +88,19 @@ class SyncSettingsViewController: UIHostingController { fatalError("init(coder:) has not been implemented") } + private func setUpSyncFeatureFlags(_ viewModel: SyncSettingsViewModel) { + syncService.featureFlagsPublisher.prepend(syncService.featureFlags) + .removeDuplicates() + .receive(on: DispatchQueue.main) + .sink { featureFlags in + viewModel.isDataSyncingAvailable = featureFlags.contains(.dataSyncing) + viewModel.isConnectingDevicesAvailable = featureFlags.contains(.connectFlows) + viewModel.isAccountCreationAvailable = featureFlags.contains(.accountCreation) + viewModel.isAccountRecoveryAvailable = featureFlags.contains(.accountRecovery) + } + .store(in: &cancellables) + } + private func setUpFaviconsFetcherSwitch(_ viewModel: SyncSettingsViewModel) { viewModel.isFaviconsFetchingEnabled = syncBookmarksAdapter.isFaviconsFetchingEnabled diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index dabaf45967..4b7473b46b 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 935d250501..c42250307d 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -32,7 +32,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/SyncSettingsViewModel.swift b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/SyncSettingsViewModel.swift index c4d11a852f..6934aaae01 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/SyncSettingsViewModel.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/SyncSettingsViewModel.swift @@ -84,6 +84,11 @@ public class SyncSettingsViewModel: ObservableObject { @Published var isBusy = false @Published var recoveryCode = "" + @Published public var isDataSyncingAvailable: Bool = true + @Published public var isConnectingDevicesAvailable: Bool = true + @Published public var isAccountCreationAvailable: Bool = true + @Published public var isAccountRecoveryAvailable: Bool = true + public weak var delegate: SyncManagementViewModelDelegate? private(set) var isOnDevEnvironment: Bool private(set) var switchToProdEnvironment: () -> Void = {} diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/Internal/UserText.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/Internal/UserText.swift index f9691a5ff9..591a324fdd 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/Internal/UserText.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/Internal/UserText.swift @@ -43,7 +43,7 @@ struct UserText { static let turnSyncOffSectionHeader = NSLocalizedString("turn.sync.off.section.header", value: "Sync Enabled", comment: "Turn Sync Off - Section Header") static let turnSyncOffSectionFooter = NSLocalizedString("turn.sync.off.section.footer", value: "Bookmarks and passwords are currently synced across your devices.", comment: "Turn Sync Off - Section Footer") // Sync Paused Errors - static let syncLimitExceededTitle = NSLocalizedString("sync.limit.exceeded.title", value: "⚠️ Sync Paused", comment: "Sync Paused Errors - Title") + static let syncLimitExceededTitle = NSLocalizedString("sync.limit.exceeded.title", value: "Sync Paused", comment: "Sync Paused Errors - Title") static let bookmarksLimitExceededDescription = NSLocalizedString("bookmarks.limit.exceeded.description", value: "Bookmark limit exceeded. Delete some to resume syncing.", comment: "Sync Paused Errors - Bookmarks Limit Exceeded Description") static let credentialsLimitExceededDescription = NSLocalizedString("credentials.limit.exceeded.description", value: "Logins limit exceeded. Delete some to resume syncing.", comment: "Sync Paused Errors - Credentials Limit Exceeded Description") static let bookmarksLimitExceededAction = NSLocalizedString("bookmarks.limit.exceeded.action", value: "Manage Bookmarks", comment: "Sync Paused Errors - Bookmarks Limit Exceeded Action") @@ -159,6 +159,10 @@ struct UserText { static let fetchFaviconsOnboardingMessage = NSLocalizedString("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: "Fetch Favicons Onboarding - Message") static let fetchFaviconsOnboardingButtonTitle = NSLocalizedString("fetch.favicons.onboarding.button.title", value: "Keep Bookmarks Icons Updated", comment: "Fetch Favicons Onboarding - Button Title") + // 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") // swiftlint:enable line_length } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsView.swift index 02e6e171e7..d796e39d20 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsView.swift @@ -17,8 +17,9 @@ // limitations under the License. // -import SwiftUI import DesignResourcesKit +import DuckUI +import SwiftUI public struct SyncSettingsView: View { @@ -46,6 +47,8 @@ public struct SyncSettingsView: View { if model.isSyncEnabled { + syncUnavailableViewWhileLoggedIn() + turnOffSync() // Sync Paused Errors @@ -66,6 +69,8 @@ public struct SyncSettingsView: View { } else { + syncUnavailableViewWhileLoggedOut() + syncWithAnotherDeviceView() otherOptions() @@ -89,9 +94,21 @@ public struct SyncSettingsView: View { @State var selectedDevice: SyncSettingsViewModel.Device? } -// Sync Set up Views +// MARK: - Sync Set up Views + extension SyncSettingsView { + @ViewBuilder + fileprivate func syncUnavailableViewWhileLoggedOut() -> some View { + if !model.isDataSyncingAvailable || !model.isConnectingDevicesAvailable { + SyncWarningMessageView(title: UserText.serviceUnavailable, message: UserText.warningSyncDisabled) + } else if !model.isAccountCreationAvailable { + SyncWarningMessageView(title: UserText.serviceUnavailable, message: UserText.warningAccountCreationDisabled) + } else { + EmptyView() + } + } + @ViewBuilder func syncWithAnotherDeviceView() -> some View { Section { @@ -105,20 +122,11 @@ extension SyncSettingsView { .daxBodyRegular() .multilineTextAlignment(.center) .foregroundColor(Color(designSystemColor: .textPrimary)) - Button(action: { - model.scanQRCode() - }, label: { - Text(UserText.syncWithAnotherDeviceButton) - .daxButton() - .foregroundColor(.white) - .frame(maxWidth: 310) - .frame(height: 50) - .background( - RoundedRectangle(cornerRadius: 8) - .fill(Color(designSystemColor: .accent)) - ) - }) - .padding(.vertical, 16) + Button(UserText.syncWithAnotherDeviceButton, action: model.scanQRCode) + .buttonStyle(PrimaryButtonStyle(disabled: !model.isAccountCreationAvailable)) + .frame(maxWidth: 310) + .disabled(!model.isAccountCreationAvailable) + .padding(.vertical, 16) } Spacer() } @@ -138,37 +146,46 @@ extension SyncSettingsView { @ViewBuilder func otherOptions() -> some View { Section { - Text(UserText.syncAndBackUpThisDeviceLink) - .daxBodyRegular() - .foregroundColor(Color(designSystemColor: .accent)) - .onTapGesture { - isSyncWithSetUpSheetVisible = true - } - .sheet(isPresented: $isSyncWithSetUpSheetVisible, content: { - SyncWithServerView(model: model, onCancel: { - isSyncWithSetUpSheetVisible = false - }) + + Button(UserText.syncAndBackUpThisDeviceLink) { + isSyncWithSetUpSheetVisible = true + } + .sheet(isPresented: $isSyncWithSetUpSheetVisible, content: { + SyncWithServerView(model: model, onCancel: { + isSyncWithSetUpSheetVisible = false }) - Text(UserText.recoverSyncedDataLink) - .daxBodyRegular() - .foregroundColor(Color(designSystemColor: .accent)) - .onTapGesture { - isRecoverSyncedDataSheetVisible = true - } - .sheet(isPresented: $isRecoverSyncedDataSheetVisible, content: { - RecoverSyncedDataView(model: model, onCancel: { - isRecoverSyncedDataSheetVisible = false - }) + }) + .disabled(!model.isAccountCreationAvailable) + + Button(UserText.recoverSyncedDataLink) { + isRecoverSyncedDataSheetVisible = true + } + .sheet(isPresented: $isRecoverSyncedDataSheetVisible, content: { + RecoverSyncedDataView(model: model, onCancel: { + isRecoverSyncedDataSheetVisible = false }) + }) + .disabled(!model.isAccountRecoveryAvailable) + } header: { Text(UserText.otherOptionsSectionHeader) } } } +// MARK: - Sync Enabled Views -// Sync Enabled Views extension SyncSettingsView { + + @ViewBuilder + fileprivate func syncUnavailableViewWhileLoggedIn() -> some View { + if model.isDataSyncingAvailable { + EmptyView() + } else { + SyncWarningMessageView(title: UserText.serviceUnavailable, message: UserText.warningSyncDisabled) + } + } + @ViewBuilder func deleteAllData() -> some View { Section { @@ -219,12 +236,9 @@ extension SyncSettingsView { .padding() } devicesList() - Button(action: { - model.scanQRCode() - }, label: { - Text(UserText.syncedDevicesSyncWithAnotherDeviceLabel) - .padding(.leading, 32) - }) + Button(UserText.syncedDevicesSyncWithAnotherDeviceLabel, action: model.scanQRCode) + .padding(.leading, 32) + .disabled(!model.isConnectingDevicesAvailable) } header: { Text(UserText.syncedDevicesSectionHeader) } @@ -330,20 +344,12 @@ extension SyncSettingsView { } } - Section { - VStack(alignment: .leading, spacing: 4) { - Text(UserText.syncLimitExceededTitle) - .daxBodyBold() - Text(explanation) - .daxBodyRegular() - } - Button(buttonTitle) { - switch itemType { - case .bookmarks: - model.manageBookmarks() - case .credentials: - model.manageLogins() - } + SyncWarningMessageView(title: UserText.syncLimitExceededTitle, message: explanation, buttonTitle: buttonTitle) { + switch itemType { + case .bookmarks: + model.manageBookmarks() + case .credentials: + model.manageLogins() } } } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncWarningMessageView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncWarningMessageView.swift new file mode 100644 index 0000000000..3d0ecd4d02 --- /dev/null +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncWarningMessageView.swift @@ -0,0 +1,46 @@ +// +// SyncWarningMessageView.swift +// DuckDuckGo +// +// 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 + +struct SyncWarningMessageView: 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 { + Section { + VStack(alignment: .leading, spacing: 4) { + Text("⚠️ " + title).daxBodyBold() + Text(message).daxBodyRegular() + } + if let buttonTitle, let buttonAction { + Button(buttonTitle, action: buttonAction) + } + } + } +} diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index 3db6f90251..8675542410 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "96.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "97.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [