From f766bd61a58e75b65559b2a3509effdea46ab5f8 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Mon, 15 Jan 2024 20:19:40 +0100 Subject: [PATCH 1/6] VPN Geoswitching - look and feel tweaks + Pixels (#2045) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/0/1206317271028889/f **Description**: Second PR for the bulk of the VPN Geoswitching feature. - Makes some tweaks to the styling (font sizes, images, colours) - adds the correct separators - fixes the click area of the country items. - preloads the ViewModel so the countries are already loaded when the sheet is shown - adds the pixels listed in the [macOS Geoswitching Pixels subtask](https://app.asana.com/0/0/1206112407389115/f). **Steps to test this PR**: 1. Make sure you’re an internal user 2. Navigate to Settings -> VPN 3. Check this screen and the VPN Location that the Location item links to match [the designs](https://www.figma.com/file/d7d8ZGkWD0W5ExhBI0tESv/O-N---VPN-Settings-Templates?type=design&node-id=1342-7329&mode=design&t=Wfh4vgagnGqTtVWo-0) 4. Check that switching countries and/or cities updates the VPN location 5. Check that everything looks and feels good. 6. Check that the appropriate pixels are being sent — ###### 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) --- .../Location-16-Solid.imageset/Contents.json | 15 + .../Location-16-Solid.pdf | Bin 0 -> 2495 bytes .../NetworkProtectionPixelEvent.swift | 14 + .../EventMapping+NetworkProtectionError.swift | 24 +- .../VPNLocationPreferenceItem.swift | 17 +- .../VPNLocationPreferenceItemModel.swift | 5 +- .../VPNLocation/VPNLocationView.swift | 263 ++++++++++-------- .../VPNLocation/VPNLocationViewModel.swift | 66 +++-- DuckDuckGo/Statistics/PixelEvent.swift | 12 + DuckDuckGo/Statistics/PixelParameters.swift | 4 + 10 files changed, 279 insertions(+), 141 deletions(-) create mode 100644 DuckDuckGo/Assets.xcassets/Images/Location-16-Solid.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Images/Location-16-Solid.imageset/Location-16-Solid.pdf diff --git a/DuckDuckGo/Assets.xcassets/Images/Location-16-Solid.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/Location-16-Solid.imageset/Contents.json new file mode 100644 index 0000000000..1a37f7eb94 --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/Location-16-Solid.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Location-16-Solid.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/Location-16-Solid.imageset/Location-16-Solid.pdf b/DuckDuckGo/Assets.xcassets/Images/Location-16-Solid.imageset/Location-16-Solid.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ddcabe468b93c60f767774dd5febb091afc88f18 GIT binary patch literal 2495 zcmc&#OK;Rj5We@X=u0FxVE4Oi2}L4Lt#%P%W)Db+gX1*Gurqenc2wVzqhj-UsLfHYsll~Q|+wP7l zC@u!*vZ){Lw+!NYe->?e!zKJRMb+LHO{mJRJLkv8gRAAkbr*wP)XiqV7NM;lnliL8 z#Q9@)x$KHA^lvi$lm!=w6ZG)oRbX%ee* zrv={-YMoWi5}K(@d&LP8ne))JiS%BA21qIsMre|Zu-YRgSqrPAj+#zuXP{{kFSHRo zmJ6jNVMZF~qO~vEF-|1ChppfaoiiPGugLa8^Rd z5|l|JG}&cj&_`$+>)6wEskde%RA%uRo9OgHn3Q>606Fz(JK6Ofmf5 ztncrN=UtEK&c<8Pu#u*l{GYqH18@juV}H0EriLwsX6+gJnTel*+Ozs_Z<52lO2$p0 zF#xuf>YJ*nyOw{XU4v)1(p7OA2H5 z11v&YJQ392fWD|dS2swN(+w7U%=ol^fUVrizujeAVt#MAq8qkT#QEl@MOUor+g;no zCVXaCO_KGUL;tz6R$C5ycqjlpyc(h0U^qU396DzQlE&;q4(#k|`F+D|Un`@5zhkI` zdzx2VxT99r6svV;7}5L38OVLn)|>E>zgXS8>?uwcbq#)rzF4nxx%w7}3zVttil&R~ ND482}baeji;ya&5Aff;O literal 0 HcmV?d00001 diff --git a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift index 1840f30df1..3f0b9b0633 100644 --- a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift +++ b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift @@ -54,6 +54,8 @@ enum NetworkProtectionPixelEvent: PixelKitEvent { case networkProtectionClientInvalidInviteCode case networkProtectionClientFailedToRedeemInviteCode(error: Error?) case networkProtectionClientFailedToParseRedeemResponse(error: Error) + case networkProtectionClientFailedToFetchLocations(error: Error?) + case networkProtectionClientFailedToParseLocationsResponse(error: Error?) case networkProtectionClientInvalidAuthToken case networkProtectionServerListStoreFailedToEncodeServerList @@ -158,6 +160,12 @@ enum NetworkProtectionPixelEvent: PixelKitEvent { case .networkProtectionClientFailedToParseRedeemResponse: return "m_mac_netp_backend_api_error_parsing_redeem_response_failed" + case .networkProtectionClientFailedToFetchLocations: + return "m_mac_netp_backend_api_error_failed_to_fetch_location_list" + + case .networkProtectionClientFailedToParseLocationsResponse: + return "m_mac_netp_backend_api_error_parsing_location_list_response_failed" + case .networkProtectionClientInvalidAuthToken: return "m_mac_netp_backend_api_error_invalid_auth_token" @@ -251,6 +259,12 @@ enum NetworkProtectionPixelEvent: PixelKitEvent { case .networkProtectionClientFailedToRedeemInviteCode(error: let error): return error?.pixelParameters + case .networkProtectionClientFailedToFetchLocations(error: let error): + return error?.pixelParameters + + case .networkProtectionClientFailedToParseLocationsResponse(error: let error): + return error?.pixelParameters + case .networkProtectionUnhandledError(let function, let line, let error): var parameters = error.pixelParameters parameters[PixelKit.Parameters.function] = function diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift index e9201fa310..b0b7edd33b 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/EventMapping+NetworkProtectionError.swift @@ -26,28 +26,45 @@ import PixelKit extension EventMapping where Event == NetworkProtectionError { static var networkProtectionAppDebugEvents: EventMapping = .init { event, _, _, _ in let domainEvent: NetworkProtectionPixelEvent + let frequency: PixelKit.Frequency switch event { case .failedToEncodeRedeemRequest: domainEvent = .networkProtectionClientFailedToEncodeRedeemRequest + frequency = .standard case .invalidInviteCode: domainEvent = .networkProtectionClientInvalidInviteCode + frequency = .standard case .failedToRedeemInviteCode(let error): domainEvent = .networkProtectionClientFailedToRedeemInviteCode(error: error) + frequency = .standard case .failedToParseRedeemResponse(let error): domainEvent = .networkProtectionClientFailedToParseRedeemResponse(error: error) + frequency = .standard case .invalidAuthToken: domainEvent = .networkProtectionClientInvalidAuthToken + frequency = .standard case .failedToCastKeychainValueToData(field: let field): domainEvent = .networkProtectionKeychainErrorFailedToCastKeychainValueToData(field: field) + frequency = .standard case .keychainReadError(field: let field, status: let status): domainEvent = .networkProtectionKeychainReadError(field: field, status: status) + frequency = .standard case .keychainWriteError(field: let field, status: let status): domainEvent = .networkProtectionKeychainWriteError(field: field, status: status) + frequency = .standard case .keychainDeleteError(status: let status): domainEvent = .networkProtectionKeychainDeleteError(status: status) + frequency = .standard case .noAuthTokenFound: domainEvent = .networkProtectionNoAuthTokenFoundError + frequency = .standard + case .failedToFetchLocationList(let error): + domainEvent = .networkProtectionClientFailedToFetchLocations(error: error) + frequency = .dailyAndContinuous + case .failedToParseLocationListResponse(let error): + domainEvent = .networkProtectionClientFailedToParseLocationsResponse(error: error) + frequency = .dailyAndContinuous case .noServerRegistrationInfo, .couldNotSelectClosestServer, .couldNotGetPeerPublicKey, @@ -70,14 +87,13 @@ extension EventMapping where Event == NetworkProtectionError { .wireGuardDnsResolution, .wireGuardSetNetworkSettings, .startWireGuardBackend, - .failedToRetrieveAuthToken, - .failedToFetchLocationList, - .failedToParseLocationListResponse: + .failedToRetrieveAuthToken: domainEvent = .networkProtectionUnhandledError(function: #function, line: #line, error: event) + frequency = .standard return case .unhandledError(function: let function, line: let line, error: let error): domainEvent = .networkProtectionUnhandledError(function: function, line: line, error: error) - + frequency = .standard return } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationPreferenceItem.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationPreferenceItem.swift index 0b6cfe5069..4279088dce 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationPreferenceItem.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationPreferenceItem.swift @@ -22,7 +22,7 @@ import Foundation import SwiftUI struct VPNLocationPreferenceItem: View { - let model: VPNLocationPreferenceItemModel + @ObservedObject var model: VPNLocationPreferenceItemModel @State private var isShowingLocationSheet: Bool = false var body: some View { @@ -30,14 +30,13 @@ struct VPNLocationPreferenceItem: View { HStack(spacing: 10) { switch model.icon { case .defaultIcon: - Image(systemName: "location.fill") - .resizable() - .frame(width: 18, height: 18) + Image("Location-16-Solid") + .foregroundColor(Color("BlackWhite100").opacity(0.9)) case .emoji(let string): - Text(string).font(.system(size: 20)) + Text(string).font(.system(size: 16)) } - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 2) { Text(model.title) .font(.system(size: 13)) .foregroundColor(.primary) @@ -52,13 +51,15 @@ struct VPNLocationPreferenceItem: View { isShowingLocationSheet = true } .sheet(isPresented: $isShowingLocationSheet) { - VPNLocationView(isPresented: $isShowingLocationSheet) + VPNLocationView(model: model.locationsViewModel, isPresented: $isShowingLocationSheet) } } } .frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .topLeading) - .padding(10) + .frame(height: 52) + .padding(.horizontal, 10) .background(Color("BlackWhite1")) + .animation(.default) .roundedBorder() } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationPreferenceItemModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationPreferenceItemModel.swift index dc0ea89271..dc8bda6885 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationPreferenceItemModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationPreferenceItemModel.swift @@ -21,7 +21,7 @@ import Foundation import NetworkProtection -struct VPNLocationPreferenceItemModel { +final class VPNLocationPreferenceItemModel: ObservableObject { enum LocationIcon { case defaultIcon case emoji(String) @@ -31,6 +31,9 @@ struct VPNLocationPreferenceItemModel { let subtitle: String? let icon: LocationIcon + // This is preloaded so the user doesn't have to wait for the list to load on presentation + let locationsViewModel = VPNLocationViewModel() + init(selectedLocation: VPNSettings.SelectedLocation) { switch selectedLocation { case .nearest: diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift index 27cc2a8ea2..76f86ee12b 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift @@ -23,106 +23,121 @@ import SwiftUI import SwiftUIExtensions struct VPNLocationView: View { - @StateObject var model = VPNLocationViewModel() + @StateObject var model: VPNLocationViewModel @Binding var isPresented: Bool var body: some View { - VStack(alignment: .leading) { - Text(UserText.vpnLocationListTitle) - .font(.system(size: 17, weight: .bold)) - .foregroundColor(.primary) - VStack(alignment: .leading, spacing: 16) { - nearest(isSelected: model.isNearestSelected) - countries() + VStack(spacing: 0) { + ScrollView { + VStack(alignment: .leading, spacing: 16) { + Text(UserText.vpnLocationListTitle) + .font(.system(size: 17, weight: .bold)) + .foregroundColor(.primary) + VStack(alignment: .leading, spacing: 20) { + nearestSection + countriesSection + } + .animation(.default, value: model.state.isLoading) + .padding(0) + } + .padding(.horizontal, 56) + .padding(.top, 32) + .padding(.bottom, 20) } - .padding(0) - } - .padding(.horizontal, 56) - .padding(.top, 32) - .padding(.bottom, 20) - .frame(minWidth: 624, maxWidth: .infinity, minHeight: 514, maxHeight: 514, alignment: .top) - Spacer() - Group { VPNLocationViewButtons( onDone: { model.onSubmit() isPresented = false }, onCancel: { isPresented = false - }) - .navigationTitle(UserText.vpnFeedbackFormTitle) + } + ) .onAppear { Task { await model.onViewAppeared() } } } - .background(Color.secondary.opacity(0.1)) + .frame(minWidth: 624, maxWidth: .infinity, minHeight: 514, maxHeight: 514, alignment: .top) } @ViewBuilder - private func nearest(isSelected: Bool) -> some View { - PreferencePaneSection(verticalPadding: 12) { + private var nearestSection: some View { + PreferencePaneSection(verticalPadding: 0) { Text(UserText.vpnLocationRecommendedSectionTitle) .font(.system(size: 15)) .foregroundColor(.primary) - ChecklistItem( - isSelected: isSelected, - action: { - Task { - await model.onNearestItemSelection() - } - }, label: { - Image(systemName: "location.fill") - .resizable() - .frame(width: 18, height: 18) - VStack(alignment: .leading, spacing: 4) { - Text(UserText.vpnLocationNearestAvailable) - .foregroundColor(.primary) - Text(UserText.vpnLocationNearestAvailableSubtitle) - .font(.system(size: 11)) - .foregroundColor(.secondary) - } - } - ) - .frame(idealWidth: .infinity, maxWidth: .infinity) - .padding(10) - .background(Color("BlackWhite1")) - .roundedBorder() + nearestItem } } @ViewBuilder - private func countries() -> some View { - switch model.state { - case .loading: - EmptyView() - .listRowBackground(Color.clear) - case .loaded(let countryItems): - PreferencePaneSection(verticalPadding: 12) { - Text(UserText.vpnLocationCustomSectionTitle) - .font(.system(size: 15)) - .foregroundColor(.primary) - LazyVStack(alignment: .leading) { - ForEach(countryItems) { item in - CountryItem( - itemModel: item, - action: { - Task { - await model.onCountryItemSelection(id: item.id) - } - }, cityPickerAction: { selection in - Task { - await model.onCountryItemSelection(id: item.id, cityId: selection) - } - }) - .padding(10) - } + private var nearestItem: some View { + ChecklistItem( + isSelected: model.isNearestSelected, + action: { + Task { + await model.onNearestItemSelection() + } + }, label: { + Image("Location-16-Solid") + .padding(4) + .foregroundColor(Color("BlackWhite100").opacity(0.9)) + VStack(alignment: .leading, spacing: 2) { + Text(UserText.vpnLocationNearestAvailable) + .font(.system(size: 13)) + .foregroundColor(.primary) + Text(UserText.vpnLocationNearestAvailableSubtitle) + .font(.system(size: 11)) + .foregroundColor(.secondary) } - .roundedBorder() + } + ) + .roundedBorder() + } + + @ViewBuilder + private var countriesSection: some View { + PreferencePaneSection(verticalPadding: 0) { + Text(UserText.vpnLocationCustomSectionTitle) + .font(.system(size: 15)) + .foregroundColor(.primary) + switch model.state { + case .loading: + EmptyView() + case .loaded(let countryItems): + countriesList(countries: countryItems) } } } + + private func countriesList(countries: [VPNCountryItemModel]) -> some View { + VStack(spacing: 0) { + ForEach(countries) { country in + if !country.isFirstItem { + Rectangle() + .fill(Color("BlackWhite10")) + .frame(height: 1) + .padding(.init(top: 0, leading: 10, bottom: 0, trailing: 10)) + } + + CountryItem( + itemModel: country, + action: { + Task { + await model.onCountryItemSelection(id: country.id) + } + }, + cityPickerAction: { selection in + Task { + await model.onCountryItemSelection(id: country.id, cityId: selection) + } + } + ) + } + } + .roundedBorder() + } } private struct CountryItem: View { @@ -150,30 +165,51 @@ private struct CountryItem: View { action: action, label: { Text(itemModel.emoji) - VStack(alignment: .leading, spacing: 4) { - Text(itemModel.title) - .foregroundColor(.primary) - if let subtitle = itemModel.subtitle { - Text(subtitle) - .foregroundColor(.secondary) - } - } + .font(.system(size: 16)) + .padding(4) + labels if itemModel.shouldShowPicker { Spacer() - Picker("", selection: selectedCityItemBinding) { - Text(itemModel.nearestCityPickerItem.name) - .tag(itemModel.nearestCityPickerItem) - Divider() - ForEach(itemModel.cityPickerItems) { cityItem in - Text(cityItem.name) - .tag(cityItem) - } - } - .pickerStyle(.menu) - .frame(width: 90) + picker } } ) + .frame(idealWidth: .infinity, maxWidth: .infinity) + .background(Color("BlackWhite1")) + } + + @ViewBuilder + private var labels: some View { + VStack(alignment: .leading, spacing: 2) { + Text(itemModel.title) + .font(.system(size: 13)) + .foregroundColor(.primary) + .background(Color.clear) + if let subtitle = itemModel.subtitle { + Text(subtitle) + .font(.system(size: 11)) + .foregroundColor(.secondary) + .background(Color.clear) + } + } + .background(Color.clear) + } + + @ViewBuilder + private var picker: some View { + Picker("", selection: selectedCityItemBinding) { + Text(itemModel.nearestCityPickerItem.name) + .tag(itemModel.nearestCityPickerItem) + Divider() + ForEach(itemModel.cityPickerItems) { cityItem in + Text(cityItem.name) + .tag(cityItem) + } + } + .foregroundColor(.accentColor) + .pickerStyle(.menu) + .frame(width: 90) + .background(Color.clear) } } @@ -183,17 +219,20 @@ private struct ChecklistItem: View where Content: View { @ViewBuilder let label: () -> Content var body: some View { - HStack(spacing: 12) { - Image(systemName: "checkmark") - .foregroundColor(Color.accentColor) - .if(!isSelected) { - $0.hidden() - } - label() + HStack(alignment: .center, spacing: 10) { + HStack { + Image(systemName: "checkmark") + .foregroundColor(.accentColor) + .if(!isSelected) { + $0.hidden() + } + label() + } + .padding(10) } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) + .frame(height: 52) .contentShape(Rectangle()) - .background(Color("BlackWhite1")) .onTapGesture { action() } @@ -205,18 +244,24 @@ private struct VPNLocationViewButtons: View { let onCancel: () -> Void var body: some View { - HStack { - Spacer() - button(text: UserText.vpnLocationCancelButtonTitle, action: onCancel) - .keyboardShortcut(.cancelAction) - .buttonStyle(DismissActionButtonStyle()) - - button(text: UserText.vpnLocationSubmitButtonTitle, action: onDone) - .keyboardShortcut(.defaultAction) - .buttonStyle(DefaultActionButtonStyle(enabled: true)) + VStack(spacing: 0) { + Rectangle() + .fill(Color("BlackWhite10")) + .frame(height: 1) + HStack { + Spacer() + button(text: UserText.vpnLocationCancelButtonTitle, action: onCancel) + .keyboardShortcut(.cancelAction) + .buttonStyle(DismissActionButtonStyle()) + + button(text: UserText.vpnLocationSubmitButtonTitle, action: onDone) + .keyboardShortcut(.defaultAction) + .buttonStyle(DefaultActionButtonStyle(enabled: true)) + } + .padding(.vertical, 16) + .padding(.horizontal, 20) + .background(Color("BlackWhite1")) } - .padding(.vertical, 16) - .padding(.horizontal, 20) } @ViewBuilder diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift index 09a3af829c..074337cb59 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift @@ -51,21 +51,27 @@ final class VPNLocationViewModel: ObservableObject { init(locationListRepository: NetworkProtectionLocationListRepository, settings: VPNSettings) { self.locationListRepository = locationListRepository self.settings = settings - state = .loading selectedLocation = settings.selectedLocation self.isNearestSelected = selectedLocation == .nearest + state = .loading + Task { + await reloadList() + } } func onViewAppeared() async { + Pixel.fire(.networkProtectionGeoswitchingOpened) await reloadList() } func onNearestItemSelection() async { + DailyPixel.fire(pixel: .networkProtectionGeoswitchingSetNearest, frequency: .dailyAndCount, includeAppVersionParameter: true) selectedLocation = .nearest await reloadList() } func onCountryItemSelection(id: String, cityId: String? = nil) async { + DailyPixel.fire(pixel: .networkProtectionGeoswitchingSetCustom, frequency: .dailyAndCount, includeAppVersionParameter: true) let location = NetworkProtectionSelectedLocation(country: id, city: cityId) selectedLocation = .location(location) await reloadList() @@ -77,44 +83,50 @@ final class VPNLocationViewModel: ObservableObject { @MainActor private func reloadList() async { - guard let list = try? await locationListRepository.fetchLocationList() else { return } + guard let locations = try? await locationListRepository.fetchLocationList().sortedByName() else { return } + if locations.isEmpty { + DailyPixel.fire(pixel: .networkProtectionGeoswitchingNoLocations, frequency: .dailyAndCount, includeAppVersionParameter: true) + } let isNearestSelected = selectedLocation == .nearest + self.isNearestSelected = isNearestSelected + var countryItems = [VPNCountryItemModel]() - let countryItems = list.map { currentLocation in + for i in 0.. Self { + sorted(by: { lhs, rhs in + lhs.country.localizedLocationFromCountryCode < rhs.country.localizedLocationFromCountryCode + }) + } +} + +private extension String { + var localizedLocationFromCountryCode: String { + Locale.current.localizedString(forRegionCode: self) ?? "" + } +} + #endif diff --git a/DuckDuckGo/Statistics/PixelEvent.swift b/DuckDuckGo/Statistics/PixelEvent.swift index 3e5cfe1d09..8d1312b603 100644 --- a/DuckDuckGo/Statistics/PixelEvent.swift +++ b/DuckDuckGo/Statistics/PixelEvent.swift @@ -178,6 +178,10 @@ extension Pixel { case networkProtectionRemoteMessageDismissed(messageID: String) case networkProtectionRemoteMessageOpened(messageID: String) case networkProtectionEnabledOnSearch + case networkProtectionGeoswitchingOpened + case networkProtectionGeoswitchingSetNearest + case networkProtectionGeoswitchingSetCustom + case networkProtectionGeoswitchingNoLocations // Sync case syncSignupDirect @@ -566,6 +570,14 @@ extension Pixel.Event { case .dailyPixel(let pixel, isFirst: let isFirst): return pixel.name + (isFirst ? "_d" : "_c") + case .networkProtectionGeoswitchingOpened: + return "m_mac_netp_imp_geoswitching_c" + case .networkProtectionGeoswitchingSetNearest: + return "m_mac_netp_ev_geoswitching_set_nearest" + case .networkProtectionGeoswitchingSetCustom: + return "m_mac_netp_ev_geoswitching_set_custom" + case .networkProtectionGeoswitchingNoLocations: + return "m_mac_netp_ev_geoswitching_no_locations" } } } diff --git a/DuckDuckGo/Statistics/PixelParameters.swift b/DuckDuckGo/Statistics/PixelParameters.swift index 25ed00fb87..4aa86c7cca 100644 --- a/DuckDuckGo/Statistics/PixelParameters.swift +++ b/DuckDuckGo/Statistics/PixelParameters.swift @@ -144,6 +144,10 @@ extension Pixel.Event { .networkProtectionRemoteMessageDismissed, .networkProtectionRemoteMessageOpened, .networkProtectionEnabledOnSearch, + .networkProtectionGeoswitchingOpened, + .networkProtectionGeoswitchingSetNearest, + .networkProtectionGeoswitchingSetCustom, + .networkProtectionGeoswitchingNoLocations, .syncSignupDirect, .syncSignupConnect, .syncLogin, From 0aee49583c03bb1fa49f6f6633f21290f22ba3c1 Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Mon, 15 Jan 2024 19:37:51 +0000 Subject: [PATCH 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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.