Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BSK changes for NetP iOS Geoswitching #2141

Merged
merged 17 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Core/PixelEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ extension Pixel {
case networkProtectionClientFailedToEncodeRegisterKeyRequest
case networkProtectionClientFailedToFetchRegisteredServers
case networkProtectionClientFailedToParseRegisteredServersResponse
case networkProtectionClientFailedToFetchLocations
case networkProtectionClientFailedToParseLocationsResponse
case networkProtectionClientFailedToEncodeRedeemRequest
case networkProtectionClientInvalidInviteCode
case networkProtectionClientFailedToRedeemInviteCode
Expand Down Expand Up @@ -810,6 +812,9 @@ extension Pixel.Event {
case .networkProtectionClientFailedToFetchRegisteredServers: return "m_netp_backend_api_error_failed_to_fetch_registered_servers"
case .networkProtectionClientFailedToParseRegisteredServersResponse:
return "m_netp_backend_api_error_parsing_device_registration_response_failed"
case .networkProtectionClientFailedToFetchLocations: return "m_netp_backend_api_error_failed_to_fetch_locations"
case .networkProtectionClientFailedToParseLocationsResponse:
return "m_netp_backend_api_error_parsing_locations_response_failed"
case .networkProtectionClientFailedToEncodeRedeemRequest: return "m_netp_backend_api_error_encoding_redeem_request_body_failed"
case .networkProtectionClientInvalidInviteCode: return "m_netp_backend_api_error_invalid_invite_code"
case .networkProtectionClientFailedToRedeemInviteCode: return "m_netp_backend_api_error_failed_to_redeem_invite_code"
Expand Down
20 changes: 18 additions & 2 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,8 @@
EE0153EB2A6FF970002A8B26 /* NetworkProtectionRootViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153EA2A6FF970002A8B26 /* NetworkProtectionRootViewModelTests.swift */; };
EE0153ED2A6FF9E6002A8B26 /* NetworkProtectionRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153EC2A6FF9E6002A8B26 /* NetworkProtectionRootView.swift */; };
EE0153EF2A70021E002A8B26 /* NetworkProtectionInviteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153EE2A70021E002A8B26 /* NetworkProtectionInviteView.swift */; };
EE01EB402AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE01EB3F2AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift */; };
EE01EB432AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE01EB422AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift */; };
EE276BEA2A77F823009167B6 /* NetworkProtectionRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE276BE92A77F823009167B6 /* NetworkProtectionRootViewController.swift */; };
EE3766DE2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */; };
EE3B226B29DE0F110082298A /* MockInternalUserStoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3B226A29DE0F110082298A /* MockInternalUserStoring.swift */; };
Expand Down Expand Up @@ -2361,6 +2363,8 @@
EE0153EA2A6FF970002A8B26 /* NetworkProtectionRootViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRootViewModelTests.swift; sourceTree = "<group>"; };
EE0153EC2A6FF9E6002A8B26 /* NetworkProtectionRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRootView.swift; sourceTree = "<group>"; };
EE0153EE2A70021E002A8B26 /* NetworkProtectionInviteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionInviteView.swift; sourceTree = "<group>"; };
EE01EB3F2AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNSettingsViewModel.swift; sourceTree = "<group>"; };
EE01EB422AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNLocationView.swift; sourceTree = "<group>"; };
EE276BE92A77F823009167B6 /* NetworkProtectionRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRootViewController.swift; sourceTree = "<group>"; };
EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionUNNotificationPresenter.swift; sourceTree = "<group>"; };
EE3B226A29DE0F110082298A /* MockInternalUserStoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockInternalUserStoring.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4441,6 +4445,14 @@
name = Root;
sourceTree = "<group>";
};
EE01EB412AFC1DE10096AAC9 /* PreferredLocation */ = {
isa = PBXGroup;
children = (
EE01EB422AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift */,
);
name = PreferredLocation;
sourceTree = "<group>";
};
EE3766DC2AC5940A00AAB575 /* NetworkProtection */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4499,6 +4511,7 @@
isa = PBXGroup;
children = (
EE9D68D02AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift */,
EE01EB3F2AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift */,
);
name = VPNSettings;
sourceTree = "<group>";
Expand All @@ -4515,6 +4528,7 @@
EECD94B22A28B8580085C66E /* NetworkProtection */ = {
isa = PBXGroup;
children = (
EE01EB412AFC1DE10096AAC9 /* PreferredLocation */,
EE9D68D62AE1527F00B55EF4 /* VPNNotifications */,
EE9D68CF2AE00CE000B55EF4 /* VPNSettings */,
EE458D122ABB651500FC651A /* Debug */,
Expand Down Expand Up @@ -6444,6 +6458,7 @@
F1D796F01E7B07610019D451 /* BookmarksViewControllerCells.swift in Sources */,
85058369219F424500ED4EDB /* UIColorExtension.swift in Sources */,
85058368219C49E000ED4EDB /* HomeViewSectionRenderers.swift in Sources */,
EE01EB432AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift in Sources */,
F456B3B525810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift in Sources */,
9820EAF522613CD30089094D /* WebProgressWorker.swift in Sources */,
B6CB93E5286445AB0090FEB4 /* Base64DownloadSession.swift in Sources */,
Expand Down Expand Up @@ -6514,6 +6529,7 @@
31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */,
1E4FAA6627D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift in Sources */,
83004E862193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift in Sources */,
EE01EB402AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift in Sources */,
EE72CA852A862D000043B5B3 /* NetworkProtectionDebugViewController.swift in Sources */,
C18ED43A2AB6F77600BF3805 /* AutofillSettingsEnableFooterView.swift in Sources */,
CB84C7BD29A3EF530088A5B8 /* AppConfigurationURLProvider.swift in Sources */,
Expand Down Expand Up @@ -8087,7 +8103,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID)";
PRODUCT_NAME = "$(TARGET_NAME)";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Development - App";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.duckduckgo.mobile.ios";
SWIFT_VERSION = 5.0;
};
name = Debug;
Expand Down Expand Up @@ -9115,7 +9131,7 @@
repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit";
requirement = {
kind = exactVersion;
version = 83.0.0;
version = 84.0.0;
};
};
C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit",
"state": {
"branch": null,
"revision": "f7e20cd37bbc0d25ae3c3f25ef52d319366613e7",
"version": "83.0.0"
"revision": "9c2c7f39679a1f4441fec95fda86f4c089724e2e",
"version": "84.0.0"
}
},
{
Expand Down
7 changes: 6 additions & 1 deletion DuckDuckGo/EventMapping+NetworkProtectionError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ extension EventMapping where Event == NetworkProtectionError {
var params: [String: String] = [:]

switch event {
case .failedToFetchLocationList(let error):
pixelEvent = .networkProtectionClientFailedToFetchLocations
pixelError = error
case .failedToParseLocationListResponse(let error):
pixelEvent = .networkProtectionClientFailedToParseLocationsResponse
pixelError = error
case .failedToEncodeRedeemRequest:
pixelEvent = .networkProtectionClientFailedToEncodeRedeemRequest
case .invalidInviteCode:
Expand Down Expand Up @@ -89,7 +95,6 @@ extension EventMapping where Event == NetworkProtectionError {
// Should never be sent from from the app
case .unhandledError(function: let function, line: let line, error: let error):
pixelEvent = .networkProtectionUnhandledError

}

DailyPixel.fireDailyAndCount(pixel: pixelEvent, error: pixelError, withAdditionalParameters: params)
Expand Down
29 changes: 28 additions & 1 deletion DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ extension NetworkProtectionKeychainTokenStore {

extension NetworkProtectionCodeRedemptionCoordinator {
convenience init() {
self.init(tokenStore: NetworkProtectionKeychainTokenStore(), errorEvents: .networkProtectionAppDebugEvents)
let tunnelSettings = TunnelSettings(defaults: .networkProtectionGroupDefaults)
self.init(
environment: tunnelSettings.selectedEnvironment,
tokenStore: NetworkProtectionKeychainTokenStore(),
errorEvents: .networkProtectionAppDebugEvents
)
}
}

Expand All @@ -68,4 +73,26 @@ extension NetworkProtectionVPNNotificationsViewModel {
}
}

extension NetworkProtectionVPNSettingsViewModel {
convenience init() {
self.init(
tunnelSettings: TunnelSettings(defaults: .networkProtectionGroupDefaults)
)
}
}

extension NetworkProtectionVPNLocationViewModel {
convenience init() {
let tunnelSettings = TunnelSettings(defaults: .networkProtectionGroupDefaults)
let locationListRepository = NetworkProtectionLocationListCompositeRepository(
environment: tunnelSettings.selectedEnvironment,
tokenStore: NetworkProtectionKeychainTokenStore()
)
self.init(
locationListRepository: locationListRepository,
tunnelSettings: TunnelSettings(defaults: .networkProtectionGroupDefaults)
)
}
}

#endif
100 changes: 100 additions & 0 deletions DuckDuckGo/NetworkProtectionVPNLocationView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//
// NetworkProtectionVPNLocationView.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.
//

#if NETWORK_PROTECTION

import Foundation
import SwiftUI

@available(iOS 15, *)
struct NetworkProtectionVPNLocationView: View {
@StateObject var model = NetworkProtectionVPNLocationViewModel()

var body: some View {
List {
Text("⚠️ FEATURE IS WORK IN PROGRESS ⚠️")
Section {
Button(action: model.onNearestItemSelection) {
Text(UserText.netPPreferredLocationNearest)
}
}
Section {
ForEach(model.countryItems) { item in
Button(action: {
model.onCountryItemSelection(countryID: item.countryID)
}, label: {
Text(item.localizedName)
})
}
}
}
.animation(.default, value: model.countryItems.isEmpty)
.applyInsetGroupedListStyle()
.navigationTitle("VPN Location").onAppear {
Task {
await model.onViewAppeared()
}
}
}
}

import NetworkProtection

final class NetworkProtectionVPNLocationViewModel: ObservableObject {
private let locationListRepository: NetworkProtectionLocationListRepository
private let tunnelSettings: TunnelSettings
@Published public var countryItems: [NetworkProtectionVPNCountryItemModel] = []

init(locationListRepository: NetworkProtectionLocationListRepository, tunnelSettings: TunnelSettings) {
self.locationListRepository = locationListRepository
self.tunnelSettings = tunnelSettings
}

@MainActor
func onViewAppeared() async {
guard let list = try? await locationListRepository.fetchLocationList() else { return }
self.countryItems = list.map(NetworkProtectionVPNCountryItemModel.init(netPLocation:))
}

func onNearestItemSelection() {
tunnelSettings.selectedLocation = .nearest
}

func onCountryItemSelection(countryID: String) {
let location = NetworkProtectionSelectedLocation(country: countryID)
tunnelSettings.selectedLocation = .location(location)
}
}

struct NetworkProtectionVPNCountryItemModel: Identifiable {
let countryID: String
let localizedName: String
let cities: [String]
var id: String {
"\(countryID) - \(cities.count) cities"
}

init(netPLocation: NetworkProtectionLocation) {
self.countryID = netPLocation.country
self.localizedName = Locale.current.localizedString(forRegionCode: countryID) ?? countryID
self.cities = netPLocation.cities.map(\.name)
}
}

#endif
9 changes: 9 additions & 0 deletions DuckDuckGo/NetworkProtectionVPNSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,21 @@
#if NETWORK_PROTECTION

import SwiftUI
import DesignResourcesKit

@available(iOS 15, *)
struct NetworkProtectionVPNSettingsView: View {
@StateObject var viewModel = NetworkProtectionVPNSettingsViewModel()

var body: some View {
List {
NavigationLink(destination: NetworkProtectionVPNLocationView()) {
HStack {
Text(UserText.netPPreferredLocationSettingTitle).daxBodyRegular().foregroundColor(.textPrimary)
Spacer()
Text(viewModel.preferredLocation).daxBodyRegular().foregroundColor(.textSecondary)
}
}
toggleSection(
text: UserText.netPAlwaysOnSettingTitle,
footerText: UserText.netPAlwaysOnSettingFooter
Expand Down
50 changes: 50 additions & 0 deletions DuckDuckGo/NetworkProtectionVPNSettingsViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// NetworkProtectionVPNSettingsViewModel.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.
//

#if NETWORK_PROTECTION

import Foundation
import NetworkProtection
import Combine

final class NetworkProtectionVPNSettingsViewModel: ObservableObject {
private let tunnelSettings: TunnelSettings
private var cancellable: AnyCancellable?

@Published public var preferredLocation: String = UserText.netPPreferredLocationNearest

init(tunnelSettings: TunnelSettings) {
self.tunnelSettings = tunnelSettings
cancellable = tunnelSettings.selectedLocationPublisher.map { selectedLocation in
guard let selectedLocation = selectedLocation.location else {
return UserText.netPPreferredLocationNearest
}
guard let city = selectedLocation.city else {
return Self.localizedString(forRegionCode: selectedLocation.country)
}
return "\(city), \(Self.localizedString(forRegionCode: selectedLocation.country))"
}.assign(to: \.preferredLocation, onWeaklyHeld: self)
}

private static func localizedString(forRegionCode: String) -> String {
Locale.current.localizedString(forRegionCode: forRegionCode) ?? forRegionCode.capitalized
}
}

#endif
6 changes: 6 additions & 0 deletions DuckDuckGo/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,12 @@ In addition to the details entered into this form, your app issue report will co
static let netPStatusViewShareFeedback = NSLocalizedString("network.protection.status.menu.share.feedback", value: "Share Feedback", comment: "The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text")
static let netPStatusViewErrorConnectionFailedTitle = NSLocalizedString("network.protection.status.view.error.connection.failed.title", value: "Failed to Connect.", comment: "Generic connection failed error title shown in NetworkProtection's status view.")
static let netPStatusViewErrorConnectionFailedMessage = NSLocalizedString("network.protection.status.view.error.connection.failed.message", value: "Please try again later.", comment: "Generic connection failed error message shown in NetworkProtection's status view.")
static let netPPreferredLocationSettingTitle = NSLocalizedString("network.protection.vpn.preferred.location.title", value: "Preferred Location", comment: "Title for the Preferred Location VPN Settings item.")
static let netPPreferredLocationNearest = NSLocalizedString("network.protection.vpn.preferred.location.nearest", value: "Nearest Available", comment: "Label for the Preferred Location VPN Settings item when the nearest available location is selected.")
static let netPVPNLocationRecommendedSectionTitle = NSLocalizedString("network.protection.vpn.location.recommended.section.title", value: "Recommended", comment: "Title for the VPN Location screen's Recommended section.")
static let netPVPNLocationAllCountriesSectionTitle = NSLocalizedString("network.protection.vpn.location.all.countries.section.title", value: "All Countries", comment: "Title for the VPN Location screen's All Countries section.")
static let netPVPNLocationNearestAvailableItemTitle = NSLocalizedString("network.protection.vpn.location.nearest.available.item.title", value: "Nearest Available", comment: "Title for the VPN Location screen's Nearest Available selection item.")
static let netPVPNLocationRecommendedSectionFooter = NSLocalizedString("network.protection.vpn.location.recommended.section.footer", value: "Automatically connect to the nearest server we can find", comment: "Footer describing the VPN Location screen's Recommended section which just has Nearest Available.")
static let netPAlwaysOnSettingTitle = NSLocalizedString("network.protection.vpn.always.on.setting.title", value: "Always On", comment: "Title for the Always on VPN setting item.")
static let netPAlwaysOnSettingFooter = NSLocalizedString("network.protection.vpn.always.on.setting.footer", value: "Automatically restore a VPN connection after interruption.", comment: "Footer text for the Always on VPN setting item.")
static let netPSecureDNSSettingTitle = NSLocalizedString("network.protection.vpn.secure.dns.setting.title", value: "Secure DNS", comment: "Title for the Always on VPN setting item.")
Expand Down
Loading