Skip to content

Commit

Permalink
BSK changes for NetP iOS Geoswitching (#2141)
Browse files Browse the repository at this point in the history
  • Loading branch information
graeme authored Nov 14, 2023
1 parent 9c4124e commit e88ee7f
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 7 deletions.
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

0 comments on commit e88ee7f

Please sign in to comment.