Add SwiftLint phase (#262)

This commit is contained in:
Davide De Rosa 2023-03-17 21:55:47 +01:00 committed by GitHub
parent cecf64d871
commit f06f097f27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
227 changed files with 1399 additions and 1389 deletions

View File

@ -1047,6 +1047,7 @@
0E41BDA828671339006346B4 /* Embed Launcher */, 0E41BDA828671339006346B4 /* Embed Launcher */,
0E3152B7223F9EF500F61841 /* Embed Plugins */, 0E3152B7223F9EF500F61841 /* Embed Plugins */,
0EB2B14B2733FB6F007705AB /* Embed Foundation Extensions */, 0EB2B14B2733FB6F007705AB /* Embed Foundation Extensions */,
0E1B5F5D29C5082700FE7D18 /* SwiftLint */,
); );
buildRules = ( buildRules = (
); );
@ -1297,6 +1298,24 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */
0E1B5F5D29C5082700FE7D18 /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = SwiftLint;
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
0EADDC7227F0677F0093E303 /* Copy Core Data codegen */ = { 0EADDC7227F0677F0093E303 /* Copy Core Data codegen */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1; alwaysOutOfDate = 1;

View File

@ -28,8 +28,8 @@ import UIKit
class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject { class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject {
private let mac = MacBundle.shared private let mac = MacBundle.shared
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
mac.configure() mac.configure()
mac.menu.install() mac.menu.install()

View File

@ -44,7 +44,7 @@ extension Constants {
Bundle.main.isTestFlight Bundle.main.isTestFlight
}() }()
} }
enum Plugins { enum Plugins {
static let macBridgeName = "PassepartoutMac.bundle" static let macBridgeName = "PassepartoutMac.bundle"
} }
@ -92,9 +92,9 @@ extension Constants {
static let disableVPN = "DisableVPNIntent" static let disableVPN = "DisableVPNIntent"
static let connectVPN = "ConnectVPNIntent" static let connectVPN = "ConnectVPNIntent"
static let moveToLocation = "MoveToLocationIntent" static let moveToLocation = "MoveToLocationIntent"
static let trustCellularNetwork = "TrustCellularNetworkIntent" static let trustCellularNetwork = "TrustCellularNetworkIntent"
static let trustCurrentNetwork = "TrustCurrentNetworkIntent" static let trustCurrentNetwork = "TrustCurrentNetworkIntent"
@ -109,7 +109,7 @@ extension Constants {
enum Domain { enum Domain {
static let name = "passepartoutvpn.app" static let name = "passepartoutvpn.app"
} }
enum Services { enum Services {
static let version = "v5" static let version = "v5"
@ -120,38 +120,38 @@ extension Constants {
"https://www.facebook.com", "https://www.facebook.com",
"https://www.instagram.com" "https://www.instagram.com"
] ]
static let connectivityURL = URL(string: connectivityStrings.randomElement()!)! static let connectivityURL = URL(string: connectivityStrings.randomElement()!)!
static let connectivityTimeout: TimeInterval = 10.0 static let connectivityTimeout: TimeInterval = 10.0
} }
enum Persistence { enum Persistence {
static let profilesContainerName = "Profiles" static let profilesContainerName = "Profiles"
static let providersContainerName = "Providers" static let providersContainerName = "Providers"
} }
// milliseconds // milliseconds
enum RateLimit { enum RateLimit {
static let providerManager = 10000 static let providerManager = 10000
static let vpnToggle = 500 static let vpnToggle = 500
} }
enum Log { enum Log {
enum App { enum App {
static let url = containerURL(filename: "App.log") static let url = containerURL(filename: "App.log")
static let format = "$DHH:mm:ss.SSS$d $C$L$c $N.$F:$l - $M" static let format = "$DHH:mm:ss.SSS$d $C$L$c $N.$F:$l - $M"
} }
enum Tunnel { enum Tunnel {
static let path = containerPath(filename: "Tunnel.log") static let path = containerPath(filename: "Tunnel.log")
static let format = "$DHH:mm:ss$d - $M" static let format = "$DHH:mm:ss$d - $M"
} }
private static let parentPath = "Library/Caches" private static let parentPath = "Library/Caches"
static let level: SwiftyBeaver.Level = { static let level: SwiftyBeaver.Level = {
@ -160,9 +160,9 @@ extension Constants {
} }
return .init(rawValue: levelNum) ?? .info return .init(rawValue: levelNum) ?? .info
}() }()
static let maxBytes = 100000 static let maxBytes = 100000
static let refreshInterval: TimeInterval = 5.0 static let refreshInterval: TimeInterval = 5.0
private static func containerURL(filename: String) -> URL { private static func containerURL(filename: String) -> URL {
@ -175,30 +175,30 @@ extension Constants {
"\(parentPath)/\(filename)" "\(parentPath)/\(filename)"
} }
} }
enum URLs { enum URLs {
static let readme = Repos.apple.appendingPathComponent("blob/master/README.md") static let readme = Repos.apple.appendingPathComponent("blob/master/README.md")
static let changelog = Repos.apple.appendingPathComponent("blob/master/CHANGELOG.md") static let changelog = Repos.apple.appendingPathComponent("blob/master/CHANGELOG.md")
static let filetypes: [UTType] = [.item] static let filetypes: [UTType] = [.item]
static let website = URL(string: "https://\(Domain.name)")! static let website = URL(string: "https://\(Domain.name)")!
static let faq = website.appendingPathComponent("faq") static let faq = website.appendingPathComponent("faq")
static let disclaimer = website.appendingPathComponent("disclaimer") static let disclaimer = website.appendingPathComponent("disclaimer")
static let privacyPolicy = website.appendingPathComponent("privacy") static let privacyPolicy = website.appendingPathComponent("privacy")
static let donate = website.appendingPathComponent("donate") static let donate = website.appendingPathComponent("donate")
static let subreddit = URL(string: "https://www.reddit.com/r/passepartout")! static let subreddit = URL(string: "https://www.reddit.com/r/passepartout")!
static let twitch = URL(string: "twitch://stream/keeshux")! static let twitch = URL(string: "twitch://stream/keeshux")!
static let twitchFallback = URL(string: "https://twitch.tv/keeshux")! static let twitchFallback = URL(string: "https://twitch.tv/keeshux")!
static let githubSponsors = URL(string: "https://www.github.com/sponsors/passepartoutvpn")! static let githubSponsors = URL(string: "https://www.github.com/sponsors/passepartoutvpn")!
} }
@ -206,31 +206,31 @@ extension Constants {
private static let githubRoot = URL(string: "https://github.com/passepartoutvpn/")! private static let githubRoot = URL(string: "https://github.com/passepartoutvpn/")!
private static let githubRawRoot = URL(string: "https://\(Domain.name)/")! private static let githubRawRoot = URL(string: "https://\(Domain.name)/")!
private static func github(repo: String) -> URL { private static func github(repo: String) -> URL {
githubRoot.appendingPathComponent(repo) githubRoot.appendingPathComponent(repo)
} }
private static func githubRaw(repo: String) -> URL { private static func githubRaw(repo: String) -> URL {
githubRawRoot.appendingPathComponent(repo) githubRawRoot.appendingPathComponent(repo)
} }
static let apple = github(repo: "passepartout-apple") static let apple = github(repo: "passepartout-apple")
static let api = githubRaw(repo: "api") static let api = githubRaw(repo: "api")
} }
// milliseconds // milliseconds
enum Delays { enum Delays {
static let scrolling = 100 static let scrolling = 100
// @available(*, deprecated, message: "file importer stops showing again after closing with swipe down") // @available(*, deprecated, message: "file importer stops showing again after closing with swipe down")
static let xxxPresentFileImporter = 200 static let xxxPresentFileImporter = 200
// @available(*, deprecated, message: "edited shortcut is outdated in delegate") // @available(*, deprecated, message: "edited shortcut is outdated in delegate")
static let xxxReloadEditedShortcut = 200 static let xxxReloadEditedShortcut = 200
} }
enum Rating { enum Rating {
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
static let eventCount = 10 static let eventCount = 10

View File

@ -30,7 +30,7 @@ extension View {
var themeIdiom: UIUserInterfaceIdiom { var themeIdiom: UIUserInterfaceIdiom {
UIDevice.current.userInterfaceIdiom UIDevice.current.userInterfaceIdiom
} }
var themeIsiPadPortrait: Bool { var themeIsiPadPortrait: Bool {
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
false false
@ -39,7 +39,7 @@ extension View {
return device.userInterfaceIdiom == .pad && device.orientation.isPortrait return device.userInterfaceIdiom == .pad && device.orientation.isPortrait
#endif #endif
} }
var themeIsiPadMultitasking: Bool { var themeIsiPadMultitasking: Bool {
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
false false
@ -128,15 +128,15 @@ extension View {
fileprivate var themePrimaryBackgroundColor: Color { fileprivate var themePrimaryBackgroundColor: Color {
Color(Asset.Assets.primaryColor.color) Color(Asset.Assets.primaryColor.color)
} }
fileprivate var themeSecondaryColor: Color { fileprivate var themeSecondaryColor: Color {
.secondary .secondary
} }
fileprivate var themeLightTextColor: Color { fileprivate var themeLightTextColor: Color {
Color(Asset.Assets.lightTextColor.color) Color(Asset.Assets.lightTextColor.color)
} }
fileprivate var themeErrorColor: Color { fileprivate var themeErrorColor: Color {
.red .red
} }
@ -160,11 +160,11 @@ extension View {
var themeAssetsLogoImage: String { var themeAssetsLogoImage: String {
"Logo" "Logo"
} }
var themeCheckmarkImage: String { var themeCheckmarkImage: String {
"checkmark" "checkmark"
} }
var themeShareImage: String { var themeShareImage: String {
"square.and.arrow.up" "square.and.arrow.up"
} }
@ -172,11 +172,11 @@ extension View {
var themeCopyImage: String { var themeCopyImage: String {
"doc.on.doc" "doc.on.doc"
} }
var themeCloseImage: String { var themeCloseImage: String {
"xmark" "xmark"
} }
var themeConceilImage: String { var themeConceilImage: String {
"eye.slash" "eye.slash"
} }
@ -186,11 +186,11 @@ extension View {
} }
// MARK: Organizer // MARK: Organizer
func themeAssetsProviderImage(_ providerName: ProviderName) -> String { func themeAssetsProviderImage(_ providerName: ProviderName) -> String {
"providers/\(providerName)" "providers/\(providerName)"
} }
func themeAssetsCountryImage(_ countryCode: String) -> String { func themeAssetsCountryImage(_ countryCode: String) -> String {
"flags/\(countryCode.lowercased())" "flags/\(countryCode.lowercased())"
} }
@ -198,15 +198,15 @@ extension View {
var themeProviderImage: String { var themeProviderImage: String {
"externaldrive.connected.to.line.below" "externaldrive.connected.to.line.below"
} }
var themeHostFilesImage: String { var themeHostFilesImage: String {
"folder" "folder"
} }
var themeHostTextImage: String { var themeHostTextImage: String {
"text.justify" "text.justify"
} }
var themeSettingsImage: String { var themeSettingsImage: String {
"gearshape" "gearshape"
} }
@ -222,11 +222,11 @@ extension View {
var themeWriteReviewImage: String { var themeWriteReviewImage: String {
"star" "star"
} }
var themeAddMenuImage: String { var themeAddMenuImage: String {
"plus" "plus"
} }
var themeProfileActiveImage: String { var themeProfileActiveImage: String {
"checkmark.circle" "checkmark.circle"
} }
@ -257,19 +257,19 @@ extension View {
"highlighter" "highlighter"
// "character.cursor.ibeam" // "character.cursor.ibeam"
} }
var themeDuplicateImage: String { var themeDuplicateImage: String {
"doc.on.doc" "doc.on.doc"
} }
var themeUninstallImage: String { var themeUninstallImage: String {
"arrow.uturn.down" "arrow.uturn.down"
} }
var themeDeleteImage: String { var themeDeleteImage: String {
"trash" "trash"
} }
var themeVPNProtocolImage: String { var themeVPNProtocolImage: String {
"bolt" "bolt"
// "waveform.path.ecg" // "waveform.path.ecg"
@ -277,36 +277,36 @@ extension View {
// "pc" // "pc"
// "captions.bubble.fill" // "captions.bubble.fill"
} }
var themeEndpointImage: String { var themeEndpointImage: String {
"link" "link"
} }
var themeAccountImage: String { var themeAccountImage: String {
"person" "person"
} }
var themeProviderLocationImage: String { var themeProviderLocationImage: String {
"location" "location"
} }
var themeProviderPresetImage: String { var themeProviderPresetImage: String {
"slider.horizontal.3" "slider.horizontal.3"
} }
var themeNetworkSettingsImage: String { var themeNetworkSettingsImage: String {
// "network" // "network"
"globe" "globe"
} }
var themeOnDemandImage: String { var themeOnDemandImage: String {
"wifi" "wifi"
} }
var themeDiagnosticsImage: String { var themeDiagnosticsImage: String {
"bandage.fill" "bandage.fill"
} }
var themeFAQImage: String { var themeFAQImage: String {
"questionmark.diamond" "questionmark.diamond"
} }
@ -345,11 +345,11 @@ extension View {
func themeSecondaryTextStyle() -> some View { func themeSecondaryTextStyle() -> some View {
foregroundColor(themeSecondaryColor) foregroundColor(themeSecondaryColor)
} }
func themeLightTextStyle() -> some View { func themeLightTextStyle() -> some View {
foregroundColor(themeLightTextColor) foregroundColor(themeLightTextColor)
} }
@available(iOS 15, *) @available(iOS 15, *)
func themePrimaryTintStyle() -> some View { func themePrimaryTintStyle() -> some View {
tint(themePrimaryBackgroundColor) tint(themePrimaryBackgroundColor)
@ -363,7 +363,7 @@ extension View {
lineLimit(1) lineLimit(1)
.truncationMode(.middle) .truncationMode(.middle)
} }
func themeRawTextStyle() -> some View { func themeRawTextStyle() -> some View {
disableAutocorrection(true) disableAutocorrection(true)
.autocapitalization(.none) .autocapitalization(.none)
@ -381,7 +381,7 @@ extension View {
} }
// MARK: Animations // MARK: Animations
extension View { extension View {
func themeAnimation<V: Equatable>(on value: V) -> some View { func themeAnimation<V: Equatable>(on value: V) -> some View {
animation(.default, value: value) animation(.default, value: value)
@ -416,7 +416,7 @@ extension View {
} }
} }
} }
func themeSaveButtonLabel() -> some View { func themeSaveButtonLabel() -> some View {
// themeCheckmarkImage.asSystemImage // themeCheckmarkImage.asSystemImage
Text(L10n.Global.Strings.save) Text(L10n.Global.Strings.save)
@ -451,7 +451,7 @@ extension View {
.foregroundColor(themeSecondaryColor) .foregroundColor(themeSecondaryColor)
} }
} }
@ViewBuilder @ViewBuilder
func themeErrorMessage(_ message: String?) -> some View { func themeErrorMessage(_ message: String?) -> some View {
if let message = message { if let message = message {
@ -480,13 +480,13 @@ extension View {
.keyboardType(.asciiCapable) .keyboardType(.asciiCapable)
.themeRawTextStyle() .themeRawTextStyle()
} }
func themeValidIPAddress(_ ipAddress: String?) -> some View { func themeValidIPAddress(_ ipAddress: String?) -> some View {
themeValidating(ipAddress, validator: Validators.ipAddress) themeValidating(ipAddress, validator: Validators.ipAddress)
.keyboardType(.numbersAndPunctuation) .keyboardType(.numbersAndPunctuation)
.themeRawTextStyle() .themeRawTextStyle()
} }
func themeValidSocketPort() -> some View { func themeValidSocketPort() -> some View {
keyboardType(.numberPad) keyboardType(.numberPad)
} }

View File

@ -30,20 +30,20 @@ import PassepartoutLibrary
@MainActor @MainActor
class AppContext { class AppContext {
let logManager: LogManager let logManager: LogManager
let productManager: ProductManager let productManager: ProductManager
private let reviewer: Reviewer private let reviewer: Reviewer
private var cancellables: Set<AnyCancellable> = [] private var cancellables: Set<AnyCancellable> = []
init(coreContext: CoreContext) { init(coreContext: CoreContext) {
logManager = LogManager(logFile: Constants.Log.App.url) logManager = LogManager(logFile: Constants.Log.App.url)
logManager.logLevel = Constants.Log.level logManager.logLevel = Constants.Log.level
logManager.logFormat = Constants.Log.App.format logManager.logFormat = Constants.Log.App.format
logManager.configureLogging() logManager.configureLogging()
pp_log.info("Logging to: \(logManager.logFile!)") pp_log.info("Logging to: \(logManager.logFile!)")
productManager = ProductManager( productManager = ProductManager(
appType: Constants.InApp.appType, appType: Constants.InApp.appType,
buildProducts: Constants.InApp.buildProducts buildProducts: Constants.InApp.buildProducts
@ -51,12 +51,12 @@ class AppContext {
reviewer = Reviewer() reviewer = Reviewer()
reviewer.eventCountBeforeRating = Constants.Rating.eventCount reviewer.eventCountBeforeRating = Constants.Rating.eventCount
// post // post
configureObjects(coreContext: coreContext) configureObjects(coreContext: coreContext)
} }
private func configureObjects(coreContext: CoreContext) { private func configureObjects(coreContext: CoreContext) {
coreContext.vpnManager.isOnDemandRulesSupported = { coreContext.vpnManager.isOnDemandRulesSupported = {
self.isEligibleForOnDemandRules() self.isEligibleForOnDemandRules()
@ -81,7 +81,7 @@ class AppContext {
} }
}.store(in: &cancellables) }.store(in: &cancellables)
} }
// eligibility: ignore network settings if ineligible // eligibility: ignore network settings if ineligible
private func isEligibleForNetworkSettings() -> Bool { private func isEligibleForNetworkSettings() -> Bool {
guard productManager.isEligible(forFeature: .networkSettings) else { guard productManager.isEligible(forFeature: .networkSettings) else {
@ -90,7 +90,7 @@ class AppContext {
} }
return true return true
} }
// eligibility: reset on-demand rules if no trusted networks // eligibility: reset on-demand rules if no trusted networks
private func isEligibleForOnDemandRules() -> Bool { private func isEligibleForOnDemandRules() -> Bool {
guard productManager.isEligible(forFeature: .trustedNetworks) else { guard productManager.isEligible(forFeature: .trustedNetworks) else {

View File

@ -38,7 +38,7 @@ extension ProviderMetadata: Identifiable, Comparable, Hashable {
public static func <(lhs: Self, rhs: Self) -> Bool { public static func <(lhs: Self, rhs: Self) -> Bool {
lhs.fullName.lowercased() < rhs.fullName.lowercased() lhs.fullName.lowercased() < rhs.fullName.lowercased()
} }
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {
hasher.combine(name) hasher.combine(name)
} }
@ -68,7 +68,7 @@ extension ProviderServer: Comparable {
public static func ==(lhs: Self, rhs: Self) -> Bool { public static func ==(lhs: Self, rhs: Self) -> Bool {
lhs.id == rhs.id lhs.id == rhs.id
} }
// "Default" comes first (nil localizedName) // "Default" comes first (nil localizedName)
public static func <(lhs: Self, rhs: Self) -> Bool { public static func <(lhs: Self, rhs: Self) -> Bool {
guard lhs.localizedName != rhs.localizedName else { guard lhs.localizedName != rhs.localizedName else {
@ -113,7 +113,7 @@ extension ProviderMetadata {
} }
return URL(string: string) return URL(string: string)
} }
var referralURL: URL? { var referralURL: URL? {
guard let string = Constants.URLs.referrals[name] else { guard let string = Constants.URLs.referrals[name] else {
return nil return nil

View File

@ -28,7 +28,7 @@ import PassepartoutLibrary
protocol ProviderProfileAvailability { protocol ProviderProfileAvailability {
var profile: Profile { get } var profile: Profile { get }
var providerManager: ProviderManager { get } var providerManager: ProviderManager { get }
} }

View File

@ -31,12 +31,12 @@ extension VPNProtocolType {
let protos: [Self] = [.openVPN, .wireGuard] let protos: [Self] = [.openVPN, .wireGuard]
return protos.map(\.fileExtension) return protos.map(\.fileExtension)
}() }()
var fileExtension: String { var fileExtension: String {
switch self { switch self {
case .openVPN: case .openVPN:
return "ovpn" return "ovpn"
case .wireGuard: case .wireGuard:
return "conf" return "conf"
} }

View File

@ -27,11 +27,11 @@ import Foundation
struct BuildProducts { struct BuildProducts {
private let productsAtBuild: (Int) -> [LocalProduct] private let productsAtBuild: (Int) -> [LocalProduct]
init(productsAtBuild: @escaping (Int) -> [LocalProduct]) { init(productsAtBuild: @escaping (Int) -> [LocalProduct]) {
self.productsAtBuild = productsAtBuild self.productsAtBuild = productsAtBuild
} }
func products(atBuild build: Int) -> [LocalProduct] { func products(atBuild build: Int) -> [LocalProduct] {
productsAtBuild(build) productsAtBuild(build)
} }

View File

@ -33,13 +33,13 @@ struct LocalProduct: RawRepresentable, Equatable, Hashable {
private static let bundle = "com.algoritmico.\(bundleSubdomain).Passepartout" private static let bundle = "com.algoritmico.\(bundleSubdomain).Passepartout"
private static let donationsBundle = "\(bundle).donations" private static let donationsBundle = "\(bundle).donations"
private static let featuresBundle = "\(bundle).features" private static let featuresBundle = "\(bundle).features"
static let providersBundle = "\(bundle).providers" static let providersBundle = "\(bundle).providers"
// MARK: Donations // MARK: Donations
static let tinyDonation = LocalProduct(donationDescription: "Tiny") static let tinyDonation = LocalProduct(donationDescription: "Tiny")
static let smallDonation = LocalProduct(donationDescription: "Small") static let smallDonation = LocalProduct(donationDescription: "Small")
@ -51,7 +51,7 @@ struct LocalProduct: RawRepresentable, Equatable, Hashable {
static let hugeDonation = LocalProduct(donationDescription: "Huge") static let hugeDonation = LocalProduct(donationDescription: "Huge")
static let maxiDonation = LocalProduct(donationDescription: "Maxi") static let maxiDonation = LocalProduct(donationDescription: "Maxi")
static let allDonations: [LocalProduct] = [ static let allDonations: [LocalProduct] = [
.tinyDonation, .tinyDonation,
.smallDonation, .smallDonation,
@ -66,9 +66,9 @@ struct LocalProduct: RawRepresentable, Equatable, Hashable {
} }
// MARK: Features // MARK: Features
static let allProviders = LocalProduct(featureId: "all_providers") static let allProviders = LocalProduct(featureId: "all_providers")
static let networkSettings = LocalProduct(featureId: "network_settings") static let networkSettings = LocalProduct(featureId: "network_settings")
static let trustedNetworks = LocalProduct(featureId: "trusted_networks") static let trustedNetworks = LocalProduct(featureId: "trusted_networks")
@ -76,7 +76,7 @@ struct LocalProduct: RawRepresentable, Equatable, Hashable {
static let siriShortcuts = LocalProduct(featureId: "siri") static let siriShortcuts = LocalProduct(featureId: "siri")
static let fullVersion_iOS = LocalProduct(featureId: "full_version") static let fullVersion_iOS = LocalProduct(featureId: "full_version")
static let fullVersion_macOS = LocalProduct(featureId: "full_mac_version") static let fullVersion_macOS = LocalProduct(featureId: "full_mac_version")
static let fullVersion = LocalProduct(featureId: "full_multi_version") static let fullVersion = LocalProduct(featureId: "full_multi_version")
@ -100,7 +100,7 @@ struct LocalProduct: RawRepresentable, Equatable, Hashable {
static var all: [LocalProduct] { static var all: [LocalProduct] {
allDonations + allFeatures// + allProviders allDonations + allFeatures// + allProviders
} }
var isDonation: Bool { var isDonation: Bool {
rawValue.hasPrefix(LocalProduct.donationsBundle) rawValue.hasPrefix(LocalProduct.donationsBundle)
} }
@ -112,11 +112,11 @@ struct LocalProduct: RawRepresentable, Equatable, Hashable {
var isProvider: Bool { var isProvider: Bool {
rawValue.hasPrefix(LocalProduct.providersBundle) rawValue.hasPrefix(LocalProduct.providersBundle)
} }
// MARK: RawRepresentable // MARK: RawRepresentable
let rawValue: String let rawValue: String
init?(rawValue: String) { init?(rawValue: String) {
self.rawValue = rawValue self.rawValue = rawValue
} }
@ -140,22 +140,22 @@ private extension ProviderName {
switch self { switch self {
case .mullvad: case .mullvad:
return "Mullvad" return "Mullvad"
case .nordvpn: case .nordvpn:
return "NordVPN" return "NordVPN"
case .pia: case .pia:
return "PIA" return "PIA"
case .protonvpn: case .protonvpn:
return "ProtonVPN" return "ProtonVPN"
case .tunnelbear: case .tunnelbear:
return "TunnelBear" return "TunnelBear"
case .vyprvpn: case .vyprvpn:
return "VyprVPN" return "VyprVPN"
case .windscribe: case .windscribe:
return "Windscribe" return "Windscribe"

View File

@ -31,7 +31,7 @@ import Combine
enum ProductError: Error { enum ProductError: Error {
case uneligible case uneligible
case beta case beta
} }
@ -40,30 +40,30 @@ class ProductManager: NSObject, ObservableObject {
case freemium = 0 case freemium = 0
case beta = 1 case beta = 1
case fullVersion = 2 case fullVersion = 2
} }
let appType: AppType let appType: AppType
let buildProducts: BuildProducts let buildProducts: BuildProducts
let didRefundProducts = PassthroughSubject<Void, Never>() let didRefundProducts = PassthroughSubject<Void, Never>()
@Published private(set) var isRefreshingProducts = false @Published private(set) var isRefreshingProducts = false
@Published private(set) var products: [SKProduct] @Published private(set) var products: [SKProduct]
// //
private let inApp: InApp<LocalProduct> private let inApp: InApp<LocalProduct>
private var purchasedAppBuild: Int? private var purchasedAppBuild: Int?
private var purchasedFeatures: Set<LocalProduct> private var purchasedFeatures: Set<LocalProduct>
private var purchaseDates: [LocalProduct: Date] private var purchaseDates: [LocalProduct: Date]
private var cancelledPurchases: Set<LocalProduct>? { private var cancelledPurchases: Set<LocalProduct>? {
willSet { willSet {
guard cancelledPurchases != nil else { guard cancelledPurchases != nil else {
@ -76,20 +76,20 @@ class ProductManager: NSObject, ObservableObject {
detectRefunds(newCancelledPurchases) detectRefunds(newCancelledPurchases)
} }
} }
private var refreshRequest: SKReceiptRefreshRequest? private var refreshRequest: SKReceiptRefreshRequest?
init(appType: AppType, buildProducts: BuildProducts) { init(appType: AppType, buildProducts: BuildProducts) {
self.appType = appType self.appType = appType
self.buildProducts = buildProducts self.buildProducts = buildProducts
products = [] products = []
inApp = InApp() inApp = InApp()
purchasedAppBuild = nil purchasedAppBuild = nil
purchasedFeatures = [] purchasedFeatures = []
purchaseDates = [:] purchaseDates = [:]
cancelledPurchases = nil cancelledPurchases = nil
super.init() super.init()
reloadReceipt() reloadReceipt()
@ -97,15 +97,15 @@ class ProductManager: NSObject, ObservableObject {
refreshProducts() refreshProducts()
} }
deinit { deinit {
SKPaymentQueue.default().remove(self) SKPaymentQueue.default().remove(self)
} }
func canMakePayments() -> Bool { func canMakePayments() -> Bool {
SKPaymentQueue.canMakePayments() SKPaymentQueue.canMakePayments()
} }
func refreshProducts() { func refreshProducts() {
let ids = LocalProduct.all let ids = LocalProduct.all
guard !ids.isEmpty else { guard !ids.isEmpty else {
@ -130,7 +130,7 @@ class ProductManager: NSObject, ObservableObject {
func product(withIdentifier identifier: LocalProduct) -> SKProduct? { func product(withIdentifier identifier: LocalProduct) -> SKProduct? {
inApp.product(withIdentifier: identifier) inApp.product(withIdentifier: identifier)
} }
func featureProducts(including: [LocalProduct]) -> [SKProduct] { func featureProducts(including: [LocalProduct]) -> [SKProduct] {
inApp.products.filter { inApp.products.filter {
guard let p = LocalProduct(rawValue: $0.productIdentifier) else { guard let p = LocalProduct(rawValue: $0.productIdentifier) else {
@ -145,7 +145,7 @@ class ProductManager: NSObject, ObservableObject {
return true return true
} }
} }
func featureProducts(excluding: [LocalProduct]) -> [SKProduct] { func featureProducts(excluding: [LocalProduct]) -> [SKProduct] {
inApp.products.filter { inApp.products.filter {
guard let p = LocalProduct(rawValue: $0.productIdentifier) else { guard let p = LocalProduct(rawValue: $0.productIdentifier) else {
@ -160,7 +160,7 @@ class ProductManager: NSObject, ObservableObject {
return true return true
} }
} }
func purchase(_ product: SKProduct, completionHandler: @escaping (Result<InAppPurchaseResult, Error>) -> Void) { func purchase(_ product: SKProduct, completionHandler: @escaping (Result<InAppPurchaseResult, Error>) -> Void) {
inApp.purchase(product: product) { result in inApp.purchase(product: product) { result in
if case .success = result { if case .success = result {
@ -171,7 +171,7 @@ class ProductManager: NSObject, ObservableObject {
} }
} }
} }
func restorePurchases(completionHandler: @escaping (Error?) -> Void) { func restorePurchases(completionHandler: @escaping (Error?) -> Void) {
inApp.restorePurchases { (finished, _, error) in inApp.restorePurchases { (finished, _, error) in
guard finished else { guard finished else {
@ -184,7 +184,7 @@ class ProductManager: NSObject, ObservableObject {
} }
// MARK: In-app eligibility // MARK: In-app eligibility
private func isCurrentPlatformVersion() -> Bool { private func isCurrentPlatformVersion() -> Bool {
purchasedFeatures.contains(isMac ? .fullVersion_macOS : .fullVersion_iOS) purchasedFeatures.contains(isMac ? .fullVersion_macOS : .fullVersion_iOS)
} }
@ -222,7 +222,7 @@ class ProductManager: NSObject, ObservableObject {
func isEligibleForFeedback() -> Bool { func isEligibleForFeedback() -> Bool {
appType == .beta || !purchasedFeatures.isEmpty appType == .beta || !purchasedFeatures.isEmpty
} }
func hasPurchased(_ product: LocalProduct) -> Bool { func hasPurchased(_ product: LocalProduct) -> Bool {
purchasedFeatures.contains(product) purchasedFeatures.contains(product)
} }
@ -257,7 +257,7 @@ class ProductManager: NSObject, ObservableObject {
} }
if let iapReceipts = receipt.inAppPurchaseReceipts { if let iapReceipts = receipt.inAppPurchaseReceipts {
purchaseDates.removeAll() purchaseDates.removeAll()
pp_log.debug("In-app receipts:") pp_log.debug("In-app receipts:")
iapReceipts.forEach { iapReceipts.forEach {
guard let pid = $0.productIdentifier, let product = LocalProduct(rawValue: pid) else { guard let pid = $0.productIdentifier, let product = LocalProduct(rawValue: pid) else {
@ -296,7 +296,7 @@ extension ProductManager {
let isEligibleForFullVersion = isFullVersion() let isEligibleForFullVersion = isFullVersion()
let hasCancelledFullVersion: Bool let hasCancelledFullVersion: Bool
let hasCancelledTrustedNetworks: Bool let hasCancelledTrustedNetworks: Bool
if isMac { if isMac {
hasCancelledFullVersion = !isEligibleForFullVersion && ( hasCancelledFullVersion = !isEligibleForFullVersion && (
refunds.contains(.fullVersion) || refunds.contains(.fullVersion_macOS) refunds.contains(.fullVersion) || refunds.contains(.fullVersion_macOS)
@ -308,7 +308,7 @@ extension ProductManager {
) )
hasCancelledTrustedNetworks = !isEligibleForFullVersion && refunds.contains(.trustedNetworks) hasCancelledTrustedNetworks = !isEligibleForFullVersion && refunds.contains(.trustedNetworks)
} }
// review features and potentially revert them if they were used (Siri is handled in AppDelegate) // review features and potentially revert them if they were used (Siri is handled in AppDelegate)
if hasCancelledFullVersion || hasCancelledTrustedNetworks { if hasCancelledFullVersion || hasCancelledTrustedNetworks {
didRefundProducts.send() didRefundProducts.send()

View File

@ -36,10 +36,10 @@ extension IntentDispatcher {
case activeAndConnected(UUID) case activeAndConnected(UUID)
} }
typealias VPNIntentActivity = IntentActivity<VPNManager> typealias VPNIntentActivity = IntentActivity<VPNManager>
static let enableVPN = VPNIntentActivity(name: Constants.Activities.enableVPN) { activity, vpnManager in static let enableVPN = VPNIntentActivity(name: Constants.Activities.enableVPN) { _, vpnManager in
pp_log.info("Enabling VPN...") pp_log.info("Enabling VPN...")
Task { Task {
@ -51,7 +51,7 @@ extension IntentDispatcher {
} }
} }
static let disableVPN = VPNIntentActivity(name: Constants.Activities.disableVPN) { activity, vpnManager in static let disableVPN = VPNIntentActivity(name: Constants.Activities.disableVPN) { _, vpnManager in
pp_log.info("Disabling VPN...") pp_log.info("Disabling VPN...")
Task { Task {
@ -81,7 +81,7 @@ extension IntentDispatcher {
} }
} }
} }
static let moveToLocation = VPNIntentActivity(name: Constants.Activities.moveToLocation) { activity, vpnManager in static let moveToLocation = VPNIntentActivity(name: Constants.Activities.moveToLocation) { activity, vpnManager in
pp_log.info("Moving to VPN location...") pp_log.info("Moving to VPN location...")
@ -110,27 +110,27 @@ extension IntentDispatcher {
} }
} }
} }
static let trustCellularNetwork = VPNIntentActivity(name: Constants.Activities.trustCellularNetwork) { activity, vpnManager in static let trustCellularNetwork = VPNIntentActivity(name: Constants.Activities.trustCellularNetwork) { _, vpnManager in
pp_log.info("Trusting mobile network...") pp_log.info("Trusting mobile network...")
handleCellularNetwork(true, vpnManager) handleCellularNetwork(true, vpnManager)
} }
static let trustCurrentNetwork = VPNIntentActivity(name: Constants.Activities.trustCurrentNetwork) { activity, vpnManager in static let trustCurrentNetwork = VPNIntentActivity(name: Constants.Activities.trustCurrentNetwork) { _, vpnManager in
pp_log.info("Trusting current Wi-Fi...") pp_log.info("Trusting current Wi-Fi...")
handleCurrentNetwork(true, vpnManager) handleCurrentNetwork(true, vpnManager)
} }
static let untrustCellularNetwork = VPNIntentActivity(name: Constants.Activities.untrustCellularNetwork) { activity, vpnManager in static let untrustCellularNetwork = VPNIntentActivity(name: Constants.Activities.untrustCellularNetwork) { _, vpnManager in
pp_log.info("Untrusting mobile network...") pp_log.info("Untrusting mobile network...")
handleCellularNetwork(false, vpnManager) handleCellularNetwork(false, vpnManager)
} }
static let untrustCurrentNetwork = VPNIntentActivity(name: Constants.Activities.untrustCurrentNetwork) { activity, vpnManager in static let untrustCurrentNetwork = VPNIntentActivity(name: Constants.Activities.untrustCurrentNetwork) { _, vpnManager in
pp_log.info("Untrusting current Wi-Fi...") pp_log.info("Untrusting current Wi-Fi...")
handleCurrentNetwork(false, vpnManager) handleCurrentNetwork(false, vpnManager)
} }
private static func handleCellularNetwork(_ trust: Bool, _ vpnManager: VPNManager) { private static func handleCellularNetwork(_ trust: Bool, _ vpnManager: VPNManager) {
Task { Task {
do { do {

View File

@ -30,19 +30,19 @@ import PassepartoutLibrary
class IntentDispatcher { class IntentDispatcher {
private struct Groups { private struct Groups {
static let vpn = "VPN" static let vpn = "VPN"
static let trust = "Trust" static let trust = "Trust"
} }
// MARK: Intents // MARK: Intents
static func intentConnect(header: Profile.Header) -> ConnectVPNIntent { static func intentConnect(header: Profile.Header) -> ConnectVPNIntent {
let intent = ConnectVPNIntent() let intent = ConnectVPNIntent()
intent.profileId = header.id.uuidString intent.profileId = header.id.uuidString
intent.profileName = header.name intent.profileName = header.name
return intent return intent
} }
static func intentMoveTo(header: Profile.Header, providerFullName: String, server: ProviderServer) -> MoveToLocationIntent { static func intentMoveTo(header: Profile.Header, providerFullName: String, server: ProviderServer) -> MoveToLocationIntent {
let intent = MoveToLocationIntent() let intent = MoveToLocationIntent()
intent.profileId = header.id.uuidString intent.profileId = header.id.uuidString
@ -51,33 +51,33 @@ class IntentDispatcher {
intent.serverName = server.localizedLongDescription(withCategory: false) intent.serverName = server.localizedLongDescription(withCategory: false)
return intent return intent
} }
static func intentEnable() -> EnableVPNIntent { static func intentEnable() -> EnableVPNIntent {
EnableVPNIntent() EnableVPNIntent()
} }
static func intentDisable() -> DisableVPNIntent { static func intentDisable() -> DisableVPNIntent {
DisableVPNIntent() DisableVPNIntent()
} }
static func intentTrustWiFi() -> TrustCurrentNetworkIntent { static func intentTrustWiFi() -> TrustCurrentNetworkIntent {
TrustCurrentNetworkIntent() TrustCurrentNetworkIntent()
} }
static func intentUntrustWiFi() -> UntrustCurrentNetworkIntent { static func intentUntrustWiFi() -> UntrustCurrentNetworkIntent {
UntrustCurrentNetworkIntent() UntrustCurrentNetworkIntent()
} }
static func intentTrustCellular() -> TrustCellularNetworkIntent { static func intentTrustCellular() -> TrustCellularNetworkIntent {
TrustCellularNetworkIntent() TrustCellularNetworkIntent()
} }
static func intentUntrustCellular() -> UntrustCellularNetworkIntent { static func intentUntrustCellular() -> UntrustCellularNetworkIntent {
UntrustCellularNetworkIntent() UntrustCellularNetworkIntent()
} }
// MARK: Donations // MARK: Donations
static func donateConnection(with profile: Profile, providerManager: ProviderManager) { static func donateConnection(with profile: Profile, providerManager: ProviderManager) {
let genericIntent: INIntent let genericIntent: INIntent
if let providerName = profile.header.providerName { if let providerName = profile.header.providerName {
@ -93,24 +93,24 @@ class IntentDispatcher {
} else { } else {
genericIntent = intentConnect(header: profile.header) genericIntent = intentConnect(header: profile.header)
} }
let interaction = INInteraction(intent: genericIntent, response: nil) let interaction = INInteraction(intent: genericIntent, response: nil)
interaction.groupIdentifier = profile.id.uuidString interaction.groupIdentifier = profile.id.uuidString
interaction.donateAndLog() interaction.donateAndLog()
} }
static func donateEnableVPN() { static func donateEnableVPN() {
let interaction = INInteraction(intent: intentEnable(), response: nil) let interaction = INInteraction(intent: intentEnable(), response: nil)
interaction.groupIdentifier = Groups.vpn interaction.groupIdentifier = Groups.vpn
interaction.donateAndLog() interaction.donateAndLog()
} }
static func donateDisableVPN() { static func donateDisableVPN() {
let interaction = INInteraction(intent: intentDisable(), response: nil) let interaction = INInteraction(intent: intentDisable(), response: nil)
interaction.groupIdentifier = Groups.vpn interaction.groupIdentifier = Groups.vpn
interaction.donateAndLog() interaction.donateAndLog()
} }
static func donateTrustCurrentNetwork() { static func donateTrustCurrentNetwork() {
let interaction = INInteraction(intent: intentTrustWiFi(), response: nil) let interaction = INInteraction(intent: intentTrustWiFi(), response: nil)
interaction.groupIdentifier = Groups.trust interaction.groupIdentifier = Groups.trust
@ -122,19 +122,19 @@ class IntentDispatcher {
interaction.groupIdentifier = Groups.trust interaction.groupIdentifier = Groups.trust
interaction.donateAndLog() interaction.donateAndLog()
} }
static func donateTrustCellularNetwork() { static func donateTrustCellularNetwork() {
let interaction = INInteraction(intent: intentTrustCellular(), response: nil) let interaction = INInteraction(intent: intentTrustCellular(), response: nil)
interaction.groupIdentifier = Groups.trust interaction.groupIdentifier = Groups.trust
interaction.donateAndLog() interaction.donateAndLog()
} }
static func donateUntrustCellularNetwork() { static func donateUntrustCellularNetwork() {
let interaction = INInteraction(intent: intentUntrustCellular(), response: nil) let interaction = INInteraction(intent: intentUntrustCellular(), response: nil)
interaction.groupIdentifier = Groups.trust interaction.groupIdentifier = Groups.trust
interaction.donateAndLog() interaction.donateAndLog()
} }
static func forgetProfile(withHeader header: Profile.Header) { static func forgetProfile(withHeader header: Profile.Header) {
INInteraction.delete(with: header.id.uuidString) { (error) in INInteraction.delete(with: header.id.uuidString) { (error) in
if let error = error { if let error = error {

View File

@ -32,20 +32,20 @@ import PassepartoutLibrary
@MainActor @MainActor
class IntentsManager: NSObject, ObservableObject { class IntentsManager: NSObject, ObservableObject {
@Published private(set) var isReloadingShortcuts = false @Published private(set) var isReloadingShortcuts = false
@Published private(set) var shortcuts: [UUID: Shortcut] = [:] @Published private(set) var shortcuts: [UUID: Shortcut] = [:]
let shouldDismissIntentView = PassthroughSubject<Void, Never>() let shouldDismissIntentView = PassthroughSubject<Void, Never>()
private var continuation: CheckedContinuation<[INVoiceShortcut], Never>? private var continuation: CheckedContinuation<[INVoiceShortcut], Never>?
override init() { override init() {
super.init() super.init()
Task { Task {
await reloadShortcuts() await reloadShortcuts()
} }
} }
func reloadShortcuts() async { func reloadShortcuts() async {
isReloadingShortcuts = true isReloadingShortcuts = true
do { do {
@ -81,10 +81,10 @@ extension IntentsManager: INUIEditVoiceShortcutViewControllerDelegate {
guard let vs = voiceShortcut else { guard let vs = voiceShortcut else {
return return
} }
shortcuts[vs.identifier] = Shortcut(vs) shortcuts[vs.identifier] = Shortcut(vs)
shouldDismissIntentView.send() shouldDismissIntentView.send()
// XXX: iOS bug, vs.invocationPhrase here is still the old one before edit // XXX: iOS bug, vs.invocationPhrase here is still the old one before edit
// //
// additionally, back from edit view controller does not trigger either onAppear or // additionally, back from edit view controller does not trigger either onAppear or

View File

@ -27,11 +27,11 @@ import Foundation
class MacBundle { class MacBundle {
static let shared = MacBundle() static let shared = MacBundle()
private var bridge: MacBridge! private var bridge: MacBridge!
private lazy var bridgeDelegate = MacBundleDelegate(bundle: self) private lazy var bridgeDelegate = MacBundleDelegate(bundle: self)
@MainActor @MainActor
func configure() { func configure() {
guard let bundleURL = Bundle.main.builtInPlugInsURL?.appendingPathComponent(Constants.Plugins.macBridgeName) else { guard let bundleURL = Bundle.main.builtInPlugInsURL?.appendingPathComponent(Constants.Plugins.macBridgeName) else {
@ -46,11 +46,11 @@ class MacBundle {
bridge = bridgeClass.init() bridge = bridgeClass.init()
bridge.menu.delegate = bridgeDelegate bridge.menu.delegate = bridgeDelegate
} }
var utils: MacUtils { var utils: MacUtils {
bridge.utils bridge.utils
} }
var menu: MacMenu { var menu: MacMenu {
bridge.menu bridge.menu
} }

View File

@ -27,17 +27,17 @@ import Foundation
class MacBundleDelegate: MacMenuDelegate { class MacBundleDelegate: MacMenuDelegate {
private weak var bundle: MacBundle? private weak var bundle: MacBundle?
@MainActor @MainActor
var profileManager: LightProfileManager { var profileManager: LightProfileManager {
DefaultLightProfileManager() DefaultLightProfileManager()
} }
@MainActor @MainActor
var providerManager: LightProviderManager { var providerManager: LightProviderManager {
DefaultLightProviderManager() DefaultLightProviderManager()
} }
@MainActor @MainActor
var vpnManager: LightVPNManager { var vpnManager: LightVPNManager {
DefaultLightVPNManager() DefaultLightVPNManager()
@ -46,7 +46,7 @@ class MacBundleDelegate: MacMenuDelegate {
var utils: LightUtils { var utils: LightUtils {
DefaultLightUtils() DefaultLightUtils()
} }
init(bundle: MacBundle?) { init(bundle: MacBundle?) {
self.bundle = bundle self.bundle = bundle
} }

View File

@ -29,17 +29,17 @@ import Combine
class DefaultLightProfile: LightProfile { class DefaultLightProfile: LightProfile {
let id: UUID let id: UUID
let name: String let name: String
let vpnProtocol: String let vpnProtocol: String
let isActive: Bool let isActive: Bool
let providerName: String? let providerName: String?
let providerServer: LightProviderServer? let providerServer: LightProviderServer?
init(_ header: Profile.Header, vpnProtocol: String, isActive: Bool, providerServer: LightProviderServer?) { init(_ header: Profile.Header, vpnProtocol: String, isActive: Bool, providerServer: LightProviderServer?) {
id = header.id id = header.id
name = header.name name = header.name
@ -52,11 +52,11 @@ class DefaultLightProfile: LightProfile {
class DefaultLightProfileManager: LightProfileManager { class DefaultLightProfileManager: LightProfileManager {
private let profileManager = ProfileManager.shared private let profileManager = ProfileManager.shared
private let providerManager = ProviderManager.shared private let providerManager = ProviderManager.shared
private var subscriptions: Set<AnyCancellable> = [] private var subscriptions: Set<AnyCancellable> = []
weak var delegate: LightProfileManagerDelegate? weak var delegate: LightProfileManagerDelegate?
init() { init() {
@ -66,7 +66,7 @@ class DefaultLightProfileManager: LightProfileManager {
self.delegate?.didUpdateProfiles() self.delegate?.didUpdateProfiles()
}.store(in: &subscriptions) }.store(in: &subscriptions)
} }
var hasProfiles: Bool { var hasProfiles: Bool {
profileManager.hasProfiles profileManager.hasProfiles
} }

View File

@ -29,9 +29,9 @@ import PassepartoutLibrary
class DefaultLightProviderCategory: LightProviderCategory { class DefaultLightProviderCategory: LightProviderCategory {
let name: String let name: String
var locations: [LightProviderLocation] var locations: [LightProviderLocation]
init(_ category: ProviderCategory) { init(_ category: ProviderCategory) {
name = category.name name = category.name
locations = category.locations locations = category.locations
@ -42,13 +42,13 @@ class DefaultLightProviderCategory: LightProviderCategory {
class DefaultLightProviderLocation: LightProviderLocation { class DefaultLightProviderLocation: LightProviderLocation {
let description: String let description: String
let id: String let id: String
let countryCode: String let countryCode: String
let servers: [LightProviderServer] let servers: [LightProviderServer]
init(_ location: ProviderLocation) { init(_ location: ProviderLocation) {
description = location.localizedCountry description = location.localizedCountry
id = location.id id = location.id
@ -61,15 +61,15 @@ class DefaultLightProviderLocation: LightProviderLocation {
class DefaultLightProviderServer: LightProviderServer { class DefaultLightProviderServer: LightProviderServer {
let description: String let description: String
let longDescription: String let longDescription: String
let categoryName: String let categoryName: String
let locationId: String let locationId: String
let serverId: String let serverId: String
init(_ server: ProviderServer) { init(_ server: ProviderServer) {
description = server.localizedShortDescriptionWithDefault description = server.localizedShortDescriptionWithDefault
longDescription = server.localizedLongDescription(withCategory: false) longDescription = server.localizedLongDescription(withCategory: false)
@ -81,11 +81,11 @@ class DefaultLightProviderServer: LightProviderServer {
class DefaultLightProviderManager: LightProviderManager { class DefaultLightProviderManager: LightProviderManager {
private let providerManager = ProviderManager.shared private let providerManager = ProviderManager.shared
private var subscriptions: Set<AnyCancellable> = [] private var subscriptions: Set<AnyCancellable> = []
weak var delegate: LightProviderManagerDelegate? weak var delegate: LightProviderManagerDelegate?
init() { init() {
providerManager.didUpdateProviders providerManager.didUpdateProviders
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
@ -93,7 +93,7 @@ class DefaultLightProviderManager: LightProviderManager {
self.delegate?.didUpdateProviders() self.delegate?.didUpdateProviders()
}.store(in: &subscriptions) }.store(in: &subscriptions)
} }
func categories(_ name: String, vpnProtocol: String) -> [LightProviderCategory] { func categories(_ name: String, vpnProtocol: String) -> [LightProviderCategory] {
guard let vpnProtocolType = VPNProtocolType(rawValue: vpnProtocol) else { guard let vpnProtocolType = VPNProtocolType(rawValue: vpnProtocol) else {
fatalError("Unrecognized VPN protocol: \(vpnProtocol)") fatalError("Unrecognized VPN protocol: \(vpnProtocol)")

View File

@ -28,18 +28,18 @@ import SwiftUI
class DefaultLightUtils: LightUtils { class DefaultLightUtils: LightUtils {
private let app: UIApplication private let app: UIApplication
init() { init() {
app = .shared app = .shared
} }
@AppStorage(AppPreference.launchesOnLogin.key) var launchesOnLogin = false @AppStorage(AppPreference.launchesOnLogin.key) var launchesOnLogin = false
func requestScene() { func requestScene() {
guard app.connectedScenes.isEmpty else { guard app.connectedScenes.isEmpty else {
return return
} }
app.requestSceneSessionActivation(nil, userActivity: nil, options: nil) { error in app.requestSceneSessionActivation(nil, userActivity: nil, options: nil) { _ in
// //
} }
} }

View File

@ -29,17 +29,17 @@ import Combine
class DefaultLightVPNManager: LightVPNManager { class DefaultLightVPNManager: LightVPNManager {
private let vpnManager = VPNManager.shared private let vpnManager = VPNManager.shared
private var subscriptions: Set<AnyCancellable> = [] private var subscriptions: Set<AnyCancellable> = []
var isEnabled: Bool { var isEnabled: Bool {
vpnManager.currentState.isEnabled vpnManager.currentState.isEnabled
} }
var vpnStatus: LightVPNStatus { var vpnStatus: LightVPNStatus {
vpnManager.currentState.vpnStatus.asLightVPNStatus vpnManager.currentState.vpnStatus.asLightVPNStatus
} }
private var delegates: [String: LightVPNManagerDelegate] = [:] private var delegates: [String: LightVPNManagerDelegate] = [:]
init() { init() {
@ -63,7 +63,7 @@ class DefaultLightVPNManager: LightVPNManager {
) )
}.store(in: &subscriptions) }.store(in: &subscriptions)
} }
func connect(with profileId: UUID) { func connect(with profileId: UUID) {
Task { Task {
try? await vpnManager.connect(with: profileId) try? await vpnManager.connect(with: profileId)
@ -75,7 +75,7 @@ class DefaultLightVPNManager: LightVPNManager {
try? await vpnManager.connect(with: profileId, toServer: serverId) try? await vpnManager.connect(with: profileId, toServer: serverId)
} }
} }
func disconnect() { func disconnect() {
Task { Task {
await vpnManager.disable() await vpnManager.disable()
@ -97,11 +97,11 @@ class DefaultLightVPNManager: LightVPNManager {
await vpnManager.reconnect() await vpnManager.reconnect()
} }
} }
func addDelegate(_ delegate: LightVPNManagerDelegate, withIdentifier identifier: String) { func addDelegate(_ delegate: LightVPNManagerDelegate, withIdentifier identifier: String) {
delegates[identifier] = delegate delegates[identifier] = delegate
} }
func removeDelegate(withIdentifier identifier: String) { func removeDelegate(withIdentifier identifier: String) {
delegates.removeValue(forKey: identifier) delegates.removeValue(forKey: identifier)
} }
@ -120,13 +120,13 @@ private extension VPNStatus {
switch self { switch self {
case .connected: case .connected:
return .connected return .connected
case .connecting: case .connecting:
return .connecting return .connecting
case .disconnected: case .disconnected:
return .disconnected return .disconnected
case .disconnecting: case .disconnecting:
return .disconnecting return .disconnecting
} }

View File

@ -33,9 +33,9 @@ struct AddingTextField<Field: View, ActionLabel: View>: View {
let textField: (@escaping () -> Void) -> Field let textField: (@escaping () -> Void) -> Field
let addLabel: () -> ActionLabel let addLabel: () -> ActionLabel
var commitLabel: (() -> ActionLabel)? var commitLabel: (() -> ActionLabel)?
@State private var isAdding = false @State private var isAdding = false
var body: some View { var body: some View {
@ -51,14 +51,14 @@ struct AddingTextField<Field: View, ActionLabel: View>: View {
} }
} }
} }
private func doAdd() { private func doAdd() {
withAnimation { withAnimation {
onAdd?() onAdd?()
isAdding = true isAdding = true
} }
} }
private func doCommit() { private func doCommit() {
withAnimation { withAnimation {
onCommit?() onCommit?()

View File

@ -25,7 +25,7 @@
import SwiftUI import SwiftUI
func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> { func ??<T>(lhs: Binding<T?>, rhs: T) -> Binding<T> {
Binding { Binding {
lhs.wrappedValue ?? rhs lhs.wrappedValue ?? rhs
} set: { } set: {
@ -34,15 +34,15 @@ func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
} }
extension Binding { extension Binding {
func toString() -> Binding<String> where Value == Optional<URL> { func toString() -> Binding<String> where Value == URL? {
.init { .init {
wrappedValue?.absoluteString ?? "" wrappedValue?.absoluteString ?? ""
} set: { } set: {
wrappedValue = URL(string: $0) ?? wrappedValue wrappedValue = URL(string: $0) ?? wrappedValue
} }
} }
func toString<T>() -> Binding<String> where Value == Optional<T>, T: FixedWidthInteger { func toString<T>() -> Binding<String> where Value == T?, T: FixedWidthInteger {
.init { .init {
guard let v = wrappedValue else { guard let v = wrappedValue else {
return "" return ""

View File

@ -29,17 +29,17 @@ struct CopySavingButton<T: Equatable, Label: View>: View {
@Binding var original: T @Binding var original: T
@Binding var copy: T @Binding var copy: T
var mapping: (T) -> T var mapping: (T) -> T
let label: () -> Label let label: () -> Label
var saveAnyway = false var saveAnyway = false
var onSave: (() -> Void)? var onSave: (() -> Void)?
@State private var isLoaded = false @State private var isLoaded = false
var body: some View { var body: some View {
Button(action: saveToOriginal, label: label) Button(action: saveToOriginal, label: label)
.disabled(!canSave) .disabled(!canSave)

View File

@ -27,9 +27,9 @@ import SwiftUI
struct DestructiveButton<Label: View>: View { struct DestructiveButton<Label: View>: View {
let action: () -> Void let action: () -> Void
let label: () -> Label let label: () -> Label
var body: some View { var body: some View {
if #available(iOS 15, *) { if #available(iOS 15, *) {
Button(role: .destructive, action: action, label: label) Button(role: .destructive, action: action, label: label)

View File

@ -38,21 +38,21 @@ struct EditableTextList<Field: View, ActionLabel: View>: View {
onEditingChanged: (Bool) -> Void, onEditingChanged: (Bool) -> Void,
onCommit: () -> Void onCommit: () -> Void
) )
@Binding var elements: [String] @Binding var elements: [String]
var allowsDuplicates = true var allowsDuplicates = true
var mapping: ([IdentifiableString]) -> [IdentifiableString] = { $0 } var mapping: ([IdentifiableString]) -> [IdentifiableString] = { $0 }
var onAdd: ((Binding<String>) -> Void)? var onAdd: ((Binding<String>) -> Void)?
let textField: (FieldCallback) -> Field let textField: (FieldCallback) -> Field
let addLabel: () -> ActionLabel let addLabel: () -> ActionLabel
var commitLabel: (() -> ActionLabel)? var commitLabel: (() -> ActionLabel)?
@State private var isLoaded = false @State private var isLoaded = false
@State private var identifiableElements: [IdentifiableString] = [] @State private var identifiableElements: [IdentifiableString] = []
@ -60,7 +60,7 @@ struct EditableTextList<Field: View, ActionLabel: View>: View {
@State private var editedTextStrings: [UUID: String] = [:] @State private var editedTextStrings: [UUID: String] = [:]
private let addedUUID = UUID() private let addedUUID = UUID()
private var addedText: Binding<String> { private var addedText: Binding<String> {
.init { .init {
editedTextStrings[addedUUID] ?? "" editedTextStrings[addedUUID] ?? ""
@ -68,7 +68,7 @@ struct EditableTextList<Field: View, ActionLabel: View>: View {
editedTextStrings[addedUUID] = $0 editedTextStrings[addedUUID] = $0
} }
} }
var body: some View { var body: some View {
debugChanges() debugChanges()
return Group { return Group {
@ -87,7 +87,7 @@ struct EditableTextList<Field: View, ActionLabel: View>: View {
} }
}.onChange(of: elements, perform: remapElements) }.onChange(of: elements, perform: remapElements)
} }
private func existingRow(_ element: IdentifiableString) -> some View { private func existingRow(_ element: IdentifiableString) -> some View {
let editedText = binding(toEditedElement: element) let editedText = binding(toEditedElement: element)
@ -100,7 +100,7 @@ struct EditableTextList<Field: View, ActionLabel: View>: View {
replaceElement(at: element.id, with: editedText) replaceElement(at: element.id, with: editedText)
})) }))
} }
private var newRow: some View { private var newRow: some View {
AddingTextField( AddingTextField(
onAdd: { onAdd: {
@ -144,7 +144,7 @@ extension EditableTextList {
identifiableElements = newIdentifiableElements identifiableElements = newIdentifiableElements
} }
} }
private func addElement() { private func addElement() {
guard allowsDuplicates || !identifiableElements.contains(where: { guard allowsDuplicates || !identifiableElements.contains(where: {
$0.string == addedText.wrappedValue $0.string == addedText.wrappedValue
@ -155,7 +155,7 @@ extension EditableTextList {
identifiableElements.append(.init(string: addedText.wrappedValue)) identifiableElements.append(.init(string: addedText.wrappedValue))
commit() commit()
} }
private func binding(toEditedElement element: IdentifiableString) -> Binding<String> { private func binding(toEditedElement element: IdentifiableString) -> Binding<String> {
// print(">>> <-> \(element)") // print(">>> <-> \(element)")
.init { .init {
@ -184,14 +184,14 @@ extension EditableTextList {
} }
commit() commit()
} }
private func onDelete(offsets: IndexSet) { private func onDelete(offsets: IndexSet) {
var mapped = mapping(identifiableElements) var mapped = mapping(identifiableElements)
mapped.remove(atOffsets: offsets) mapped.remove(atOffsets: offsets)
identifiableElements = mapped identifiableElements = mapped
commit() commit()
} }
private func onMove(indexSet: IndexSet, to: Int) { private func onMove(indexSet: IndexSet, to: Int) {
var mapped = mapping(identifiableElements) var mapped = mapping(identifiableElements)
mapped.move(fromOffsets: indexSet, toOffset: to) mapped.move(fromOffsets: indexSet, toOffset: to)

View File

@ -27,23 +27,23 @@ import SwiftUI
struct GenericCreditsView: View { struct GenericCreditsView: View {
typealias License = (String, String, URL) typealias License = (String, String, URL)
typealias Notice = (String, String) typealias Notice = (String, String)
var licensesHeader: String? = "Licenses" var licensesHeader: String? = "Licenses"
var noticesHeader: String? = "Notices" var noticesHeader: String? = "Notices"
var translationsHeader: String? = "Translations" var translationsHeader: String? = "Translations"
let licenses: [License] let licenses: [License]
let notices: [Notice] let notices: [Notice]
let translations: [String: String] let translations: [String: String]
@State private var contentForLicense: [String: String] = [:] @State private var contentForLicense: [String: String] = [:]
var body: some View { var body: some View {
List { List {
if !licenses.isEmpty { if !licenses.isEmpty {
@ -57,27 +57,27 @@ struct GenericCreditsView: View {
} }
} }
} }
private var sortedLicenses: [License] { private var sortedLicenses: [License] {
licenses.sorted { licenses.sorted {
$0.0.lowercased() < $1.0.lowercased() $0.0.lowercased() < $1.0.lowercased()
} }
} }
private var sortedNotices: [Notice] { private var sortedNotices: [Notice] {
notices.sorted { notices.sorted {
$0.0.lowercased() < $1.0.lowercased() $0.0.lowercased() < $1.0.lowercased()
} }
} }
private var sortedLanguages: [String] { private var sortedLanguages: [String] {
translations.keys.sorted { translations.keys.sorted {
$0.localizedAsCountryCode < $1.localizedAsCountryCode $0.localizedAsCountryCode < $1.localizedAsCountryCode
} }
} }
private var licensesSection: some View { private var licensesSection: some View {
Section ( Section(
header: licensesHeader.map(Text.init) header: licensesHeader.map(Text.init)
) { ) {
ForEach(sortedLicenses, id: \.0) { license in ForEach(sortedLicenses, id: \.0) { license in
@ -98,7 +98,7 @@ struct GenericCreditsView: View {
} }
private var noticesSection: some View { private var noticesSection: some View {
Section ( Section(
header: noticesHeader.map(Text.init) header: noticesHeader.map(Text.init)
) { ) {
ForEach(sortedNotices, id: \.0) { notice in ForEach(sortedNotices, id: \.0) { notice in
@ -108,7 +108,7 @@ struct GenericCreditsView: View {
} }
private var translationsSection: some View { private var translationsSection: some View {
Section ( Section(
header: translationsHeader.map(Text.init) header: translationsHeader.map(Text.init)
) { ) {
ForEach(sortedLanguages, id: \.self) { code in ForEach(sortedLanguages, id: \.self) { code in
@ -123,7 +123,7 @@ struct GenericCreditsView: View {
} }
} }
} }
private func noticeView(_ content: (String, String)) -> some View { private func noticeView(_ content: (String, String)) -> some View {
VStack { VStack {
Text(content.1) Text(content.1)
@ -137,9 +137,9 @@ struct GenericCreditsView: View {
extension GenericCreditsView { extension GenericCreditsView {
struct LicenseView: View { struct LicenseView: View {
let url: URL let url: URL
@Binding var content: String? @Binding var content: String?
var body: some View { var body: some View {
ZStack { ZStack {
content.map { unwrapped in content.map { unwrapped in
@ -154,7 +154,7 @@ extension GenericCreditsView {
} }
}.onAppear(perform: loadURL) }.onAppear(perform: loadURL)
} }
private func loadURL() { private func loadURL() {
guard content == nil else { guard content == nil else {
return return

View File

@ -27,13 +27,13 @@ import SwiftUI
struct GenericVersionView: View { struct GenericVersionView: View {
let logoName: String let logoName: String
let appName: String let appName: String
let versionString: String let versionString: String
let extraString: String? let extraString: String?
var body: some View { var body: some View {
ScrollView { ScrollView {
Image(logoName) Image(logoName)

View File

@ -27,7 +27,7 @@ import SwiftUI
public struct IntentActivity<UserObject> { public struct IntentActivity<UserObject> {
public let name: String public let name: String
public let handler: (NSUserActivity, UserObject) -> Void public let handler: (NSUserActivity, UserObject) -> Void
} }

View File

@ -29,9 +29,9 @@ import IntentsUI
struct IntentAddView: UIViewControllerRepresentable { struct IntentAddView: UIViewControllerRepresentable {
let shortcut: INShortcut let shortcut: INShortcut
let delegate: INUIAddVoiceShortcutViewControllerDelegate? let delegate: INUIAddVoiceShortcutViewControllerDelegate?
func makeUIViewController(context: UIViewControllerRepresentableContext<IntentAddView>) -> INUIAddVoiceShortcutViewController { func makeUIViewController(context: UIViewControllerRepresentableContext<IntentAddView>) -> INUIAddVoiceShortcutViewController {
let vc = INUIAddVoiceShortcutViewController(shortcut: shortcut) let vc = INUIAddVoiceShortcutViewController(shortcut: shortcut)
vc.delegate = delegate vc.delegate = delegate

View File

@ -29,9 +29,9 @@ import IntentsUI
struct IntentEditView: UIViewControllerRepresentable { struct IntentEditView: UIViewControllerRepresentable {
let shortcut: Shortcut let shortcut: Shortcut
let delegate: INUIEditVoiceShortcutViewControllerDelegate? let delegate: INUIEditVoiceShortcutViewControllerDelegate?
func makeUIViewController(context: UIViewControllerRepresentableContext<IntentEditView>) -> INUIEditVoiceShortcutViewController { func makeUIViewController(context: UIViewControllerRepresentableContext<IntentEditView>) -> INUIEditVoiceShortcutViewController {
let vc = INUIEditVoiceShortcutViewController(voiceShortcut: shortcut.native) let vc = INUIEditVoiceShortcutViewController(voiceShortcut: shortcut.native)
vc.delegate = delegate vc.delegate = delegate

View File

@ -27,7 +27,7 @@ import SwiftUI
struct LongContentView: View { struct LongContentView: View {
@Binding var content: String @Binding var content: String
var body: some View { var body: some View {
TextEditor(text: $content) TextEditor(text: $content)
.font(.system(.body, design: .monospaced)) .font(.system(.body, design: .monospaced))
@ -39,13 +39,13 @@ struct LongContentView: View {
struct LongContentLink<Preview: View>: View { struct LongContentLink<Preview: View>: View {
private let title: String private let title: String
@Binding private var content: String @Binding private var content: String
private let preview: String? private let preview: String?
private let previewLabel: ((String) -> Preview)? private let previewLabel: ((String) -> Preview)?
init( init(
_ title: String, _ title: String,
content: Binding<String>, content: Binding<String>,
@ -57,7 +57,7 @@ struct LongContentLink<Preview: View>: View {
self.preview = preview self.preview = preview
self.previewLabel = previewLabel self.previewLabel = previewLabel
} }
var body: some View { var body: some View {
NavigationLink { NavigationLink {
LongContentView(content: $content) LongContentView(content: $content)

View File

@ -29,32 +29,32 @@ import MessageUI
struct MailComposerView: UIViewControllerRepresentable { struct MailComposerView: UIViewControllerRepresentable {
class Coordinator: NSObject, MFMailComposeViewControllerDelegate { class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
@Binding private var isPresented: Bool @Binding private var isPresented: Bool
init(_ view: MailComposerView) { init(_ view: MailComposerView) {
_isPresented = view._isPresented _isPresented = view._isPresented
} }
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
isPresented = false isPresented = false
} }
} }
typealias Attachment = (data: Data, mimeType: String, fileName: String) typealias Attachment = (data: Data, mimeType: String, fileName: String)
static func canSendMail() -> Bool { static func canSendMail() -> Bool {
MFMailComposeViewController.canSendMail() MFMailComposeViewController.canSendMail()
} }
@Binding var isPresented: Bool @Binding var isPresented: Bool
let toRecipients: [String] let toRecipients: [String]
let subject: String let subject: String
let messageBody: String let messageBody: String
var attachments: [Attachment]? var attachments: [Attachment]?
func makeUIViewController(context: UIViewControllerRepresentableContext<MailComposerView>) -> MFMailComposeViewController { func makeUIViewController(context: UIViewControllerRepresentableContext<MailComposerView>) -> MFMailComposeViewController {
let vc = MFMailComposeViewController() let vc = MFMailComposeViewController()
vc.setToRecipients(toRecipients) vc.setToRecipients(toRecipients)
@ -69,7 +69,7 @@ struct MailComposerView: UIViewControllerRepresentable {
func updateUIViewController(_ uiViewController: MFMailComposeViewController, context: UIViewControllerRepresentableContext<MailComposerView>) { func updateUIViewController(_ uiViewController: MFMailComposeViewController, context: UIViewControllerRepresentableContext<MailComposerView>) {
} }
func makeCoordinator() -> Coordinator { func makeCoordinator() -> Coordinator {
Coordinator(self) Coordinator(self)
} }

View File

@ -27,15 +27,15 @@ import SwiftUI
struct RevealingSecureField<ImageContent: View>: View { struct RevealingSecureField<ImageContent: View>: View {
let title: String let title: String
@Binding private var text: String @Binding private var text: String
private let conceilImage: () -> ImageContent private let conceilImage: () -> ImageContent
private let revealImage: () -> ImageContent private let revealImage: () -> ImageContent
@State private var isRevealed = false @State private var isRevealed = false
init( init(
_ title: String, _ title: String,
text: Binding<String>, text: Binding<String>,
@ -47,7 +47,7 @@ struct RevealingSecureField<ImageContent: View>: View {
self.conceilImage = conceilImage self.conceilImage = conceilImage
self.revealImage = revealImage self.revealImage = revealImage
} }
var body: some View { var body: some View {
HStack { HStack {
if isRevealed { if isRevealed {

View File

@ -29,14 +29,14 @@ import StoreKit
public class Reviewer: ObservableObject { public class Reviewer: ObservableObject {
private struct Keys { private struct Keys {
static let eventCount = "Reviewer.EventCount" static let eventCount = "Reviewer.EventCount"
static let lastVersion = "Reviewer.LastVersion" static let lastVersion = "Reviewer.LastVersion"
} }
private let defaults: UserDefaults private let defaults: UserDefaults
public var eventCountBeforeRating: Int = .max public var eventCountBeforeRating: Int = .max
public init() { public init() {
defaults = .standard defaults = .standard
} }
@ -66,7 +66,7 @@ public class Reviewer: ObservableObject {
count += eventCount count += eventCount
defaults.set(count, forKey: Keys.eventCount) defaults.set(count, forKey: Keys.eventCount)
print("Reviewer: Event reported for version \(currentVersion) (count: \(count), prompt: \(eventCountBeforeRating))") print("Reviewer: Event reported for version \(currentVersion) (count: \(count), prompt: \(eventCountBeforeRating))")
guard count >= eventCountBeforeRating else { guard count >= eventCountBeforeRating else {
return false return false
} }
@ -74,11 +74,11 @@ public class Reviewer: ObservableObject {
defaults.removeObject(forKey: Keys.eventCount) defaults.removeObject(forKey: Keys.eventCount)
defaults.set(currentVersion, forKey: Keys.lastVersion) defaults.set(currentVersion, forKey: Keys.lastVersion)
requestReview() requestReview()
return true return true
} }
// may or may not appear // may or may not appear
private func requestReview() { private func requestReview() {
guard let scene = UIApplication.shared.windows.first(where: { $0.windowScene != nil })?.windowScene else { guard let scene = UIApplication.shared.windows.first(where: { $0.windowScene != nil })?.windowScene else {

View File

@ -28,11 +28,11 @@ import Intents
struct Shortcut: Identifiable, Hashable, Comparable { struct Shortcut: Identifiable, Hashable, Comparable {
let native: INVoiceShortcut let native: INVoiceShortcut
init(_ native: INVoiceShortcut) { init(_ native: INVoiceShortcut) {
self.native = native self.native = native
} }
var id: UUID { var id: UUID {
native.identifier native.identifier
} }
@ -40,7 +40,7 @@ struct Shortcut: Identifiable, Hashable, Comparable {
static func ==(lhs: Self, rhs: Self) -> Bool { static func ==(lhs: Self, rhs: Self) -> Bool {
lhs.phrase == rhs.phrase lhs.phrase == rhs.phrase
} }
static func <(lhs: Self, rhs: Self) -> Bool { static func <(lhs: Self, rhs: Self) -> Bool {
lhs.phrase < rhs.phrase lhs.phrase < rhs.phrase
} }

View File

@ -27,15 +27,15 @@ import SwiftUI
struct StyledPicker<T: Hashable, Label: View, Style: ListStyle>: View { struct StyledPicker<T: Hashable, Label: View, Style: ListStyle>: View {
let title: String let title: String
@Binding var selection: T @Binding var selection: T
let values: [T] let values: [T]
let pickerLabel: (T) -> Label let pickerLabel: (T) -> Label
let selectionLabel: (T) -> Label let selectionLabel: (T) -> Label
let listStyle: () -> Style let listStyle: () -> Style
@State private var isPresented = false @State private var isPresented = false
@ -49,7 +49,7 @@ struct StyledPicker<T: Hashable, Label: View, Style: ListStyle>: View {
} }
} }
} }
private func pickerView() -> some View { private func pickerView() -> some View {
List { List {
Section { Section {

View File

@ -28,13 +28,13 @@ import Foundation
struct Validators { struct Validators {
enum ValidationError: Error { enum ValidationError: Error {
case notSet case notSet
case empty case empty
case ipAddress case ipAddress
case domainName case domainName
case url case url
} }
@ -71,7 +71,7 @@ struct Validators {
throw ValidationError.domainName throw ValidationError.domainName
} }
} }
static func url(_ string: String) throws { static func url(_ string: String) throws {
guard let _ = URL(string: string) else { guard let _ = URL(string: string) else {
throw ValidationError.url throw ValidationError.url

View File

@ -31,7 +31,7 @@ struct AboutView: View {
private let versionString = Constants.Global.appVersionString private let versionString = Constants.Global.appVersionString
private let redditURL = Constants.URLs.subreddit private let redditURL = Constants.URLs.subreddit
private let shareMessage = L10n.Global.Messages.share private let shareMessage = L10n.Global.Messages.share
private let readmeURL = Constants.URLs.readme private let readmeURL = Constants.URLs.readme
@ -55,7 +55,7 @@ struct AboutView: View {
}.themeSecondaryView() }.themeSecondaryView()
.navigationTitle(L10n.About.title) .navigationTitle(L10n.About.title)
} }
private var infoSection: some View { private var infoSection: some View {
Section { Section {
NavigationLink { NavigationLink {
@ -69,7 +69,7 @@ struct AboutView: View {
} }
} }
} }
private var supportSection: some View { private var supportSection: some View {
Section { Section {
Button(L10n.About.Items.JoinCommunity.caption) { Button(L10n.About.Items.JoinCommunity.caption) {

View File

@ -28,19 +28,19 @@ import PassepartoutLibrary
struct AccountView: View { struct AccountView: View {
@ObservedObject private var providerManager: ProviderManager @ObservedObject private var providerManager: ProviderManager
private let providerName: ProviderName? private let providerName: ProviderName?
private let vpnProtocol: VPNProtocolType private let vpnProtocol: VPNProtocolType
@Binding private var account: Profile.Account @Binding private var account: Profile.Account
private let saveAnyway: Bool private let saveAnyway: Bool
private let onSave: (() -> Void)? private let onSave: (() -> Void)?
@State private var liveAccount = Profile.Account() @State private var liveAccount = Profile.Account()
init( init(
providerName: ProviderName?, providerName: ProviderName?,
vpnProtocol: VPNProtocolType, vpnProtocol: VPNProtocolType,
@ -55,7 +55,7 @@ struct AccountView: View {
self.saveAnyway = saveAnyway self.saveAnyway = saveAnyway
self.onSave = onSave self.onSave = onSave
} }
var body: some View { var body: some View {
List { List {
Section { Section {
@ -126,7 +126,7 @@ struct AccountView: View {
) )
} }
} }
private func openGuidanceURL(_ url: URL) { private func openGuidanceURL(_ url: URL) {
URL.openURL(url) URL.openURL(url)
} }
@ -155,10 +155,10 @@ private extension Profile.Account.AuthenticationMethod {
switch self { switch self {
case .persistent: case .persistent:
return L10n.Account.Items.AuthenticationMethod.persistent return L10n.Account.Items.AuthenticationMethod.persistent
case .interactive: case .interactive:
return L10n.Account.Items.AuthenticationMethod.interactive return L10n.Account.Items.AuthenticationMethod.interactive
case .totp: case .totp:
return Unlocalized.Other.totp return Unlocalized.Other.totp
} }

View File

@ -31,15 +31,15 @@ import TunnelKitWireGuard
extension AddHostView { extension AddHostView {
struct NameView: View { struct NameView: View {
@ObservedObject private var profileManager: ProfileManager @ObservedObject private var profileManager: ProfileManager
private let url: URL private let url: URL
private let deletingURLOnSuccess: Bool private let deletingURLOnSuccess: Bool
private let bindings: AddProfileView.Bindings private let bindings: AddProfileView.Bindings
@State private var viewModel = ViewModel() @State private var viewModel = ViewModel()
@State private var isEnteringCredentials = false @State private var isEnteringCredentials = false
private var isComplete: Bool { private var isComplete: Bool {
@ -107,7 +107,7 @@ extension AddHostView {
completeSection completeSection
} }
} }
private var encryptionSection: some View { private var encryptionSection: some View {
Section { Section {
SecureField(L10n.AddProfile.Host.Sections.Encryption.footer, text: $viewModel.encryptionPassphrase) { SecureField(L10n.AddProfile.Host.Sections.Encryption.footer, text: $viewModel.encryptionPassphrase) {
@ -132,7 +132,7 @@ extension AddHostView {
themeErrorMessage(viewModel.errorMessage) themeErrorMessage(viewModel.errorMessage)
} }
} }
private var hiddenAccountLink: some View { private var hiddenAccountLink: some View {
NavigationLink("", isActive: $isEnteringCredentials) { NavigationLink("", isActive: $isEnteringCredentials) {
AddProfileView.AccountWrapperView( AddProfileView.AccountWrapperView(
@ -153,11 +153,11 @@ extension AddHostView {
private func requestResourcePermissions() { private func requestResourcePermissions() {
_ = url.startAccessingSecurityScopedResource() _ = url.startAccessingSecurityScopedResource()
} }
private func dropResourcePermissions() { private func dropResourcePermissions() {
url.stopAccessingSecurityScopedResource() url.stopAccessingSecurityScopedResource()
} }
private func alertOverwriteExistingProfile() -> Alert { private func alertOverwriteExistingProfile() -> Alert {
Alert( Alert(
title: Text(L10n.AddProfile.Shared.title), title: Text(L10n.AddProfile.Shared.title),

View File

@ -31,19 +31,19 @@ import TunnelKitWireGuard
extension AddHostView { extension AddHostView {
struct ViewModel: Equatable { struct ViewModel: Equatable {
private var isNamePreset = false private var isNamePreset = false
var profileName = "" var profileName = ""
private(set) var requiresPassphrase = false private(set) var requiresPassphrase = false
var encryptionPassphrase = "" var encryptionPassphrase = ""
var processedProfile: Profile = .placeholder var processedProfile: Profile = .placeholder
private(set) var errorMessage: String? private(set) var errorMessage: String?
var isAskingOverwrite = false var isAskingOverwrite = false
mutating func presetName(withURL url: URL) { mutating func presetName(withURL url: URL) {
guard !isNamePreset else { guard !isNamePreset else {
return return
@ -96,7 +96,7 @@ extension AddHostView {
setMessage(forParsingError: error) setMessage(forParsingError: error)
} }
} }
@MainActor @MainActor
mutating func addProcessedProfile(to profileManager: ProfileManager) -> Bool { mutating func addProcessedProfile(to profileManager: ProfileManager) -> Bool {
guard !processedProfile.isPlaceholder else { guard !processedProfile.isPlaceholder else {
@ -107,7 +107,7 @@ extension AddHostView {
profileManager.saveProfile(processedProfile, isActive: nil) profileManager.saveProfile(processedProfile, isActive: nil)
return true return true
} }
private mutating func setMessage(forParsingError error: Error) { private mutating func setMessage(forParsingError error: Error) {
errorMessage = error.localizedVPNParsingDescription errorMessage = error.localizedVPNParsingDescription
} }

View File

@ -31,7 +31,7 @@ struct AddProfileMenu: View {
case addProvider case addProvider
case addHost(URL, Bool) case addHost(URL, Bool)
// XXX: alert ids // XXX: alert ids
var id: Int { var id: Int {
switch self { switch self {
@ -41,16 +41,16 @@ struct AddProfileMenu: View {
} }
} }
} }
@Binding private var modalType: ModalType? @Binding private var modalType: ModalType?
@Binding private var isHostFileImporterPresented: Bool @Binding private var isHostFileImporterPresented: Bool
init(modalType: Binding<ModalType?>, isHostFileImporterPresented: Binding<Bool>) { init(modalType: Binding<ModalType?>, isHostFileImporterPresented: Binding<Bool>) {
_modalType = modalType _modalType = modalType
_isHostFileImporterPresented = isHostFileImporterPresented _isHostFileImporterPresented = isHostFileImporterPresented
} }
var body: some View { var body: some View {
Menu { Menu {
Button { Button {
@ -74,7 +74,7 @@ struct AddProfileMenu: View {
themeAddMenuImage.asSystemImage themeAddMenuImage.asSystemImage
}.sheet(item: $modalType, content: presentedModal) }.sheet(item: $modalType, content: presentedModal)
} }
@ViewBuilder @ViewBuilder
private func presentedModal(_ modalType: ModalType) -> some View { private func presentedModal(_ modalType: ModalType) -> some View {
switch modalType { switch modalType {
@ -99,7 +99,7 @@ struct AddProfileMenu: View {
}.themeGlobal() }.themeGlobal()
} }
} }
private var isModalPresented: Binding<Bool> { private var isModalPresented: Binding<Bool> {
.init { .init {
modalType != nil modalType != nil

View File

@ -30,14 +30,14 @@ enum AddProfileView {
struct Bindings { struct Bindings {
@Binding var isPresented: Bool @Binding var isPresented: Bool
} }
struct ProfileNameSection: View { struct ProfileNameSection: View {
@Binding var profileName: String @Binding var profileName: String
let errorMessage: String? let errorMessage: String?
let onCommit: () -> Void let onCommit: () -> Void
var body: some View { var body: some View {
Section { Section {
TextField(L10n.Global.Placeholders.profileName, text: $profileName, onCommit: onCommit) TextField(L10n.Global.Placeholders.profileName, text: $profileName, onCommit: onCommit)
@ -49,12 +49,12 @@ enum AddProfileView {
} }
} }
} }
struct ExistingProfilesSection: View { struct ExistingProfilesSection: View {
let headers: [Profile.Header] let headers: [Profile.Header]
@Binding var profileName: String @Binding var profileName: String
var body: some View { var body: some View {
Section { Section {
ForEach(headers, content: existingProfileButton) ForEach(headers, content: existingProfileButton)
@ -62,7 +62,7 @@ enum AddProfileView {
Text(L10n.AddProfile.Shared.Views.Existing.header) Text(L10n.AddProfile.Shared.Views.Existing.header)
} }
} }
private func existingProfileButton(_ header: Profile.Header) -> some View { private func existingProfileButton(_ header: Profile.Header) -> some View {
Button(header.name) { Button(header.name) {
profileName = header.name profileName = header.name
@ -74,11 +74,11 @@ enum AddProfileView {
@ObservedObject private var profileManager: ProfileManager @ObservedObject private var profileManager: ProfileManager
@Binding private var profile: Profile @Binding private var profile: Profile
private let bindings: AddProfileView.Bindings private let bindings: AddProfileView.Bindings
@State private var account = Profile.Account() @State private var account = Profile.Account()
init( init(
profile: Binding<Profile>, profile: Binding<Profile>,
bindings: AddProfileView.Bindings bindings: AddProfileView.Bindings
@ -87,7 +87,7 @@ enum AddProfileView {
_profile = profile _profile = profile
self.bindings = bindings self.bindings = bindings
} }
var body: some View { var body: some View {
AccountView( AccountView(
providerName: profile.header.providerName, providerName: profile.header.providerName,

View File

@ -29,17 +29,17 @@ import PassepartoutLibrary
extension AddProviderView { extension AddProviderView {
struct NameView: View { struct NameView: View {
@ObservedObject private var profileManager: ProfileManager @ObservedObject private var profileManager: ProfileManager
@Binding private var profile: Profile @Binding private var profile: Profile
private let providerMetadata: ProviderMetadata private let providerMetadata: ProviderMetadata
private let bindings: AddProfileView.Bindings private let bindings: AddProfileView.Bindings
@State private var viewModel = ViewModel() @State private var viewModel = ViewModel()
@State private var isEnteringCredentials = false @State private var isEnteringCredentials = false
init( init(
profile: Binding<Profile>, profile: Binding<Profile>,
providerMetadata: ProviderMetadata, providerMetadata: ProviderMetadata,
@ -80,7 +80,7 @@ extension AddProviderView {
}.alert(isPresented: $viewModel.isAskingOverwrite, content: alertOverwriteExistingProfile) }.alert(isPresented: $viewModel.isAskingOverwrite, content: alertOverwriteExistingProfile)
.navigationTitle(providerMetadata.fullName) .navigationTitle(providerMetadata.fullName)
} }
private var hiddenAccountLink: some View { private var hiddenAccountLink: some View {
NavigationLink("", isActive: $isEnteringCredentials) { NavigationLink("", isActive: $isEnteringCredentials) {
AddProfileView.AccountWrapperView( AddProfileView.AccountWrapperView(

View File

@ -28,26 +28,26 @@ import PassepartoutLibrary
struct AddProviderView: View { struct AddProviderView: View {
@ObservedObject private var providerManager: ProviderManager @ObservedObject private var providerManager: ProviderManager
@ObservedObject private var productManager: ProductManager @ObservedObject private var productManager: ProductManager
private let bindings: AddProfileView.Bindings private let bindings: AddProfileView.Bindings
@StateObject private var viewModel = ViewModel() @StateObject private var viewModel = ViewModel()
init(bindings: AddProfileView.Bindings) { init(bindings: AddProfileView.Bindings) {
providerManager = .shared providerManager = .shared
productManager = .shared productManager = .shared
self.bindings = bindings self.bindings = bindings
} }
private var providers: [ProviderMetadata] { private var providers: [ProviderMetadata] {
providerManager.allProviders() providerManager.allProviders()
.filter { .filter {
$0.supportedVPNProtocols.contains(viewModel.selectedVPNProtocol) $0.supportedVPNProtocols.contains(viewModel.selectedVPNProtocol)
}.sorted() }.sorted()
} }
private var availableVPNProtocols: [VPNProtocolType] { private var availableVPNProtocols: [VPNProtocolType] {
var protos: Set<VPNProtocolType> = [] var protos: Set<VPNProtocolType> = []
providers.forEach { providers.forEach {
@ -57,7 +57,7 @@ struct AddProviderView: View {
} }
return protos.sorted() return protos.sorted()
} }
var body: some View { var body: some View {
ZStack { ZStack {
ForEach(providers, id: \.navigationId, content: hiddenProviderLink) ForEach(providers, id: \.navigationId, content: hiddenProviderLink)
@ -82,7 +82,7 @@ struct AddProviderView: View {
}.navigationTitle(L10n.AddProfile.Shared.title) }.navigationTitle(L10n.AddProfile.Shared.title)
.themeSecondaryView() .themeSecondaryView()
} }
private var mainSection: some View { private var mainSection: some View {
Section { Section {
let protos = availableVPNProtocols let protos = availableVPNProtocols
@ -99,7 +99,7 @@ struct AddProviderView: View {
Text(L10n.AddProfile.Provider.Sections.Vpn.footer) Text(L10n.AddProfile.Provider.Sections.Vpn.footer)
} }
} }
private var providersSection: some View { private var providersSection: some View {
Section { Section {
ForEach(providers, content: providerRow) ForEach(providers, content: providerRow)
@ -107,7 +107,7 @@ struct AddProviderView: View {
themeErrorMessage(viewModel.errorMessage) themeErrorMessage(viewModel.errorMessage)
}.disabled(viewModel.isFetchingAnyProvider) }.disabled(viewModel.isFetchingAnyProvider)
} }
private func providerRow(_ metadata: ProviderMetadata) -> some View { private func providerRow(_ metadata: ProviderMetadata) -> some View {
Button { Button {
presentOrPurchaseProvider(metadata) presentOrPurchaseProvider(metadata)
@ -115,7 +115,7 @@ struct AddProviderView: View {
Label(metadata.fullName, image: themeAssetsProviderImage(metadata.name)) Label(metadata.fullName, image: themeAssetsProviderImage(metadata.name))
}.withTrailingProgress(when: viewModel.isFetchingProvider(metadata.name)) }.withTrailingProgress(when: viewModel.isFetchingProvider(metadata.name))
} }
private func hiddenProviderLink(_ metadata: ProviderMetadata) -> some View { private func hiddenProviderLink(_ metadata: ProviderMetadata) -> some View {
NavigationLink("", tag: metadata, selection: $viewModel.selectedProvider) { NavigationLink("", tag: metadata, selection: $viewModel.selectedProvider) {
NameView( NameView(
@ -132,7 +132,7 @@ struct AddProviderView: View {
}.withTrailingProgress(when: viewModel.isUpdatingIndex) }.withTrailingProgress(when: viewModel.isUpdatingIndex)
.disabled(viewModel.isUpdatingIndex) .disabled(viewModel.isUpdatingIndex)
} }
// eligibility: select or purchase provider // eligibility: select or purchase provider
private func presentOrPurchaseProvider(_ metadata: ProviderMetadata) { private func presentOrPurchaseProvider(_ metadata: ProviderMetadata) {
guard productManager.isEligible(forProvider: metadata.name) else { guard productManager.isEligible(forProvider: metadata.name) else {

View File

@ -30,7 +30,7 @@ extension AddProviderView {
class ViewModel: ObservableObject { class ViewModel: ObservableObject {
enum PendingOperation { enum PendingOperation {
case index case index
case provider(ProviderName) case provider(ProviderName)
} }
@ -54,19 +54,19 @@ extension AddProviderView {
} }
return false return false
} }
@Published var selectedVPNProtocol: VPNProtocolType = .openVPN @Published var selectedVPNProtocol: VPNProtocolType = .openVPN
@Published var selectedProvider: ProviderMetadata? @Published var selectedProvider: ProviderMetadata?
@Published var pendingProfile: Profile = .placeholder @Published var pendingProfile: Profile = .placeholder
@Published private(set) var pendingOperation: PendingOperation? @Published private(set) var pendingOperation: PendingOperation?
@Published var isPaywallPresented = false @Published var isPaywallPresented = false
@Published private(set) var errorMessage: String? @Published private(set) var errorMessage: String?
func selectProvider(_ metadata: ProviderMetadata, _ providerManager: ProviderManager) { func selectProvider(_ metadata: ProviderMetadata, _ providerManager: ProviderManager) {
errorMessage = nil errorMessage = nil
guard let server = providerManager.anyDefaultServer( guard let server = providerManager.anyDefaultServer(
@ -109,7 +109,7 @@ extension AddProviderView {
pendingProfile = Profile(metadata, server: server) pendingProfile = Profile(metadata, server: server)
selectedProvider = metadata selectedProvider = metadata
} }
func updateIndex(_ providerManager: ProviderManager) { func updateIndex(_ providerManager: ProviderManager) {
errorMessage = nil errorMessage = nil
pendingOperation = .index pendingOperation = .index
@ -136,11 +136,11 @@ extension AddProviderView.NameView {
private var isNamePreset = false private var isNamePreset = false
var profileName = "" var profileName = ""
var isAskingOverwrite = false var isAskingOverwrite = false
private(set) var errorMessage: String? private(set) var errorMessage: String?
mutating func presetName(withMetadata metadata: ProviderMetadata) { mutating func presetName(withMetadata metadata: ProviderMetadata) {
guard !isNamePreset else { guard !isNamePreset else {
return return
@ -173,7 +173,7 @@ extension AddProviderView.NameView {
profileManager.saveProfile(finalProfile, isActive: nil) profileManager.saveProfile(finalProfile, isActive: nil)
return finalProfile return finalProfile
} }
private mutating func setMessage(forError error: Error) { private mutating func setMessage(forError error: Error) {
errorMessage = error.localizedDescription errorMessage = error.localizedDescription
} }

View File

@ -28,8 +28,8 @@ import SwiftUI
struct CreditsView: View { struct CreditsView: View {
var body: some View { var body: some View {
GenericCreditsView( GenericCreditsView(
licensesHeader: nil,//L10n.Credits.Sections.Licenses.header, licensesHeader: nil,// L10n.Credits.Sections.Licenses.header,
noticesHeader: nil,//L10n.Credits.Sections.Notices.header, noticesHeader: nil,// L10n.Credits.Sections.Notices.header,
translationsHeader: L10n.Global.Strings.translations, translationsHeader: L10n.Global.Strings.translations,
licenses: Unlocalized.Credits.licenses, licenses: Unlocalized.Credits.licenses,
notices: Unlocalized.Credits.notices, notices: Unlocalized.Credits.notices,

View File

@ -29,23 +29,23 @@ import PassepartoutLibrary
struct DebugLogView: View { struct DebugLogView: View {
private let title: String private let title: String
private let url: URL private let url: URL
private let timer: AnyPublisher<Date, Never> private let timer: AnyPublisher<Date, Never>
@State private var logLines: [String] = [] @State private var logLines: [String] = []
@State private var isSharing = false @State private var isSharing = false
private let maxBytes = UInt64(Constants.Log.maxBytes) private let maxBytes = UInt64(Constants.Log.maxBytes)
private let appName = Constants.Global.appName private let appName = Constants.Global.appName
private let appVersion = Constants.Global.appVersionString private let appVersion = Constants.Global.appVersionString
private let shareFilename = Unlocalized.Issues.Filenames.debugLog private let shareFilename = Unlocalized.Issues.Filenames.debugLog
init(title: String, url: URL, refreshInterval: TimeInterval?) { init(title: String, url: URL, refreshInterval: TimeInterval?) {
self.title = title self.title = title
self.url = url self.url = url
@ -58,7 +58,7 @@ struct DebugLogView: View {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
} }
var body: some View { var body: some View {
ScrollViewReader { scrollProxy in ScrollViewReader { scrollProxy in
ScrollView(showsIndicators: true) { ScrollView(showsIndicators: true) {
@ -96,22 +96,22 @@ struct DebugLogView: View {
Text(logLines[$0]) Text(logLines[$0])
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
} }
}//.padding() }// .padding()
// TODO: layout, a slight padding would be nice, but it glitches on first touch // TODO: layout, a slight padding would be nice, but it glitches on first touch
} }
private func refreshLog(scrollingToLatestWith scrollProxy: ScrollViewProxy?) { private func refreshLog(scrollingToLatestWith scrollProxy: ScrollViewProxy?) {
logLines = url.trailingLines(bytes: maxBytes) logLines = url.trailingLines(bytes: maxBytes)
if let scrollProxy = scrollProxy { if let scrollProxy = scrollProxy {
scrollToLatestUpdate(scrollProxy) scrollToLatestUpdate(scrollProxy)
} }
} }
private func refreshLog(_: Date) { private func refreshLog(_: Date) {
refreshLog(scrollingToLatestWith: nil) refreshLog(scrollingToLatestWith: nil)
} }
} }
extension DebugLogView { extension DebugLogView {
private func shareDebugLog() { private func shareDebugLog() {
guard !logLines.isEmpty else { guard !logLines.isEmpty else {
@ -120,7 +120,7 @@ extension DebugLogView {
} }
isSharing = true isSharing = true
} }
private func sharingActivityView() -> some View { private func sharingActivityView() -> some View {
ActivityView(activityItems: sharingItems) ActivityView(activityItems: sharingItems)
} }

View File

@ -31,7 +31,7 @@ extension DiagnosticsView {
struct OpenVPNView: View { struct OpenVPNView: View {
enum AlertType: Int, Identifiable { enum AlertType: Int, Identifiable {
case emailNotConfigured case emailNotConfigured
var id: Int { var id: Int {
return rawValue return rawValue
} }
@ -40,23 +40,23 @@ extension DiagnosticsView {
@ObservedObject private var providerManager: ProviderManager @ObservedObject private var providerManager: ProviderManager
@ObservedObject private var vpnManager: VPNManager @ObservedObject private var vpnManager: VPNManager
@ObservedObject private var currentVPNState: ObservableVPNState @ObservedObject private var currentVPNState: ObservableVPNState
@ObservedObject private var productManager: ProductManager @ObservedObject private var productManager: ProductManager
private let providerName: ProviderName? private let providerName: ProviderName?
private var isEligibleForFeedback: Bool { private var isEligibleForFeedback: Bool {
productManager.isEligibleForFeedback() productManager.isEligibleForFeedback()
} }
@State private var isReportingIssue = false @State private var isReportingIssue = false
@State private var alertType: AlertType? @State private var alertType: AlertType?
private let vpnProtocol: VPNProtocolType = .openVPN private let vpnProtocol: VPNProtocolType = .openVPN
init(providerName: ProviderName?) { init(providerName: ProviderName?) {
providerManager = .shared providerManager = .shared
vpnManager = .shared vpnManager = .shared
@ -103,7 +103,7 @@ extension DiagnosticsView {
}.disabled(cfg == nil) }.disabled(cfg == nil)
} }
} }
private var debugLogSection: some View { private var debugLogSection: some View {
Section { Section {
DebugLogSection(appLogURL: appLogURL, tunnelLogURL: tunnelLogURL) DebugLogSection(appLogURL: appLogURL, tunnelLogURL: tunnelLogURL)
@ -114,7 +114,7 @@ extension DiagnosticsView {
Text(L10n.Diagnostics.Sections.DebugLog.footer) Text(L10n.Diagnostics.Sections.DebugLog.footer)
} }
} }
private var issueReporterSection: some View { private var issueReporterSection: some View {
Section { Section {
Button(L10n.Diagnostics.Items.ReportIssue.caption, action: presentReportIssue) Button(L10n.Diagnostics.Items.ReportIssue.caption, action: presentReportIssue)

View File

@ -30,9 +30,9 @@ import TunnelKitWireGuard
extension DiagnosticsView { extension DiagnosticsView {
struct WireGuardView: View { struct WireGuardView: View {
@ObservedObject private var vpnManager: VPNManager @ObservedObject private var vpnManager: VPNManager
private let providerName: ProviderName? private let providerName: ProviderName?
init(providerName: ProviderName?) { init(providerName: ProviderName?) {
vpnManager = .shared vpnManager = .shared
self.providerName = providerName self.providerName = providerName

View File

@ -28,7 +28,7 @@ import PassepartoutLibrary
struct DiagnosticsView: View { struct DiagnosticsView: View {
let vpnProtocol: VPNProtocolType let vpnProtocol: VPNProtocolType
let providerName: ProviderName? let providerName: ProviderName?
var body: some View { var body: some View {
@ -38,7 +38,7 @@ struct DiagnosticsView: View {
DiagnosticsView.OpenVPNView( DiagnosticsView.OpenVPNView(
providerName: providerName providerName: providerName
) )
case .wireGuard: case .wireGuard:
DiagnosticsView.WireGuardView( DiagnosticsView.WireGuardView(
providerName: providerName providerName: providerName
@ -51,9 +51,9 @@ struct DiagnosticsView: View {
extension DiagnosticsView { extension DiagnosticsView {
struct DebugLogSection: View { struct DebugLogSection: View {
let appLogURL: URL? let appLogURL: URL?
let tunnelLogURL: URL? let tunnelLogURL: URL?
private let refreshInterval = Constants.Log.refreshInterval private let refreshInterval = Constants.Log.refreshInterval
var body: some View { var body: some View {
@ -68,7 +68,7 @@ extension DiagnosticsView {
refreshInterval: nil refreshInterval: nil
) )
} }
private var tunnelLink: some View { private var tunnelLink: some View {
navigationLink( navigationLink(
withTitle: Unlocalized.VPN.vpn, withTitle: Unlocalized.VPN.vpn,

View File

@ -30,9 +30,9 @@ import PassepartoutLibrary
struct DonateView: View { struct DonateView: View {
enum AlertType: Identifiable { enum AlertType: Identifiable {
case thankYou case thankYou
case purchaseFailed(Error) case purchaseFailed(Error)
// XXX: alert ids // XXX: alert ids
var id: Int { var id: Int {
switch self { switch self {
@ -42,19 +42,19 @@ struct DonateView: View {
} }
} }
} }
@Environment(\.scenePhase) private var scenePhase @Environment(\.scenePhase) private var scenePhase
@ObservedObject private var productManager: ProductManager @ObservedObject private var productManager: ProductManager
@State private var alertType: AlertType? @State private var alertType: AlertType?
@State private var pendingDonationIdentifier: String? @State private var pendingDonationIdentifier: String?
init() { init() {
productManager = .shared productManager = .shared
} }
var body: some View { var body: some View {
List { List {
productsSection productsSection
@ -90,7 +90,7 @@ struct DonateView: View {
) )
} }
} }
private var productsSection: some View { private var productsSection: some View {
Section { Section {
if !productManager.isRefreshingProducts { if !productManager.isRefreshingProducts {
@ -104,7 +104,7 @@ struct DonateView: View {
Text(L10n.Donate.Sections.OneTime.footer) Text(L10n.Donate.Sections.OneTime.footer)
} }
} }
@ViewBuilder @ViewBuilder
private func productRow(_ product: SKProduct) -> some View { private func productRow(_ product: SKProduct) -> some View {
HStack { HStack {
@ -129,7 +129,7 @@ extension DonateView {
pendingDonationIdentifier = product.productIdentifier pendingDonationIdentifier = product.productIdentifier
productManager.purchase(product, completionHandler: handlePurchaseResult) productManager.purchase(product, completionHandler: handlePurchaseResult)
} }
private func handlePurchaseResult(_ result: Result<InAppPurchaseResult, Error>) { private func handlePurchaseResult(_ result: Result<InAppPurchaseResult, Error>) {
switch result { switch result {
case .success(let value): case .success(let value):
@ -138,7 +138,7 @@ extension DonateView {
} else { } else {
// cancelled // cancelled
} }
case .failure(let error): case .failure(let error):
alertType = .purchaseFailed(error) alertType = .purchaseFailed(error)
} }

View File

@ -32,11 +32,11 @@ extension EndpointAdvancedView {
@Binding var builder: OpenVPN.ConfigurationBuilder @Binding var builder: OpenVPN.ConfigurationBuilder
let isReadonly: Bool let isReadonly: Bool
let isServerPushed: Bool let isServerPushed: Bool
private let fallbackConfiguration = OpenVPN.ConfigurationBuilder(withFallbacks: true).build() private let fallbackConfiguration = OpenVPN.ConfigurationBuilder(withFallbacks: true).build()
var body: some View { var body: some View {
List { List {
let cfg = builder.build() let cfg = builder.build()
@ -104,7 +104,7 @@ extension EndpointAdvancedView.OpenVPNView {
Text(Unlocalized.Network.ipv4) Text(Unlocalized.Network.ipv4)
} }
} }
private var ipv6Section: some View { private var ipv6Section: some View {
Section { Section {
if let settings = builder.ipv6 { if let settings = builder.ipv6 {
@ -156,7 +156,7 @@ extension EndpointAdvancedView.OpenVPNView {
} }
} }
} }
private var communicationEditableSection: some View { private var communicationEditableSection: some View {
Section { Section {
themeTextPicker( themeTextPicker(
@ -185,7 +185,7 @@ extension EndpointAdvancedView.OpenVPNView {
Text(L10n.Endpoint.Advanced.Openvpn.Sections.Communication.header) Text(L10n.Endpoint.Advanced.Openvpn.Sections.Communication.header)
} }
} }
private func compressionSection(configuration: OpenVPN.Configuration) -> some View { private func compressionSection(configuration: OpenVPN.Configuration) -> some View {
configuration.compressionSettings.map { settings in configuration.compressionSettings.map { settings in
Section { Section {
@ -202,7 +202,7 @@ extension EndpointAdvancedView.OpenVPNView {
} }
} }
} }
private var compressionEditableSection: some View { private var compressionEditableSection: some View {
Section { Section {
themeTextPicker( themeTextPicker(
@ -334,21 +334,21 @@ extension OpenVPN.Configuration {
} }
return (compressionFraming, compressionAlgorithm) return (compressionFraming, compressionAlgorithm)
} }
var dnsSettings: (servers: [String], domains: [String])? { var dnsSettings: (servers: [String], domains: [String])? {
guard !(dnsServers?.isEmpty ?? true) || !(searchDomains?.isEmpty ?? true) else { guard !(dnsServers?.isEmpty ?? true) || !(searchDomains?.isEmpty ?? true) else {
return nil return nil
} }
return (dnsServers ?? [], searchDomains ?? []) return (dnsServers ?? [], searchDomains ?? [])
} }
var proxySettings: (proxy: Proxy?, pac: URL?, bypass: [String])? { var proxySettings: (proxy: Proxy?, pac: URL?, bypass: [String])? {
guard httpsProxy != nil || httpProxy != nil || proxyAutoConfigurationURL != nil || !(proxyBypassDomains?.isEmpty ?? true) else { guard httpsProxy != nil || httpProxy != nil || proxyAutoConfigurationURL != nil || !(proxyBypassDomains?.isEmpty ?? true) else {
return nil return nil
} }
return (httpsProxy ?? httpProxy, proxyAutoConfigurationURL, proxyBypassDomains ?? []) return (httpsProxy ?? httpProxy, proxyAutoConfigurationURL, proxyBypassDomains ?? [])
} }
var otherSettings: (keepAlive: TimeInterval?, reneg: TimeInterval?, randomizeEndpoint: Bool?, randomizeHostnames: Bool?)? { var otherSettings: (keepAlive: TimeInterval?, reneg: TimeInterval?, randomizeEndpoint: Bool?, randomizeHostnames: Bool?)? {
guard keepAliveInterval != nil || renegotiatesAfter != nil || randomizeEndpoint != nil || randomizeHostnames != nil else { guard keepAliveInterval != nil || renegotiatesAfter != nil || randomizeEndpoint != nil || randomizeHostnames != nil else {
return nil return nil
@ -361,15 +361,15 @@ private extension EndpointAdvancedView.OpenVPNView {
var fallbackCipher: OpenVPN.Cipher { var fallbackCipher: OpenVPN.Cipher {
fallbackConfiguration.cipher! fallbackConfiguration.cipher!
} }
var fallbackDigest: OpenVPN.Digest { var fallbackDigest: OpenVPN.Digest {
fallbackConfiguration.digest! fallbackConfiguration.digest!
} }
var fallbackCompressionFraming: OpenVPN.CompressionFraming { var fallbackCompressionFraming: OpenVPN.CompressionFraming {
fallbackConfiguration.compressionFraming! fallbackConfiguration.compressionFraming!
} }
var fallbackCompressionAlgorithm: OpenVPN.CompressionAlgorithm { var fallbackCompressionAlgorithm: OpenVPN.CompressionAlgorithm {
fallbackConfiguration.compressionAlgorithm! fallbackConfiguration.compressionAlgorithm!
} }

View File

@ -78,7 +78,7 @@ extension EndpointAdvancedView.WireGuardView {
} }
} }
} }
private var mtuSection: some View { private var mtuSection: some View {
builder.mtu.map { mtu in builder.mtu.map { mtu in
Section { Section {

View File

@ -36,21 +36,21 @@ extension EndpointView {
@ObservedObject private var currentProfile: ObservableProfile @ObservedObject private var currentProfile: ObservableProfile
@Binding private var builder: OpenVPN.ConfigurationBuilder @Binding private var builder: OpenVPN.ConfigurationBuilder
@Binding private var customEndpoint: Endpoint? @Binding private var customEndpoint: Endpoint?
private var isConfigurationReadonly: Bool { private var isConfigurationReadonly: Bool {
currentProfile.value.isProvider currentProfile.value.isProvider
} }
@State private var isFirstAppearance = true @State private var isFirstAppearance = true
@State private var isAutomatic = false @State private var isAutomatic = false
@State private var selectedSocketType: SocketType = .udp @State private var selectedSocketType: SocketType = .udp
@State private var selectedPort: UInt16 = 0 @State private var selectedPort: UInt16 = 0
// XXX: do not escape mutating 'self', use constant providerManager // XXX: do not escape mutating 'self', use constant providerManager
init(currentProfile: ObservableProfile) { init(currentProfile: ObservableProfile) {
let providerManager: ProviderManager = .shared let providerManager: ProviderManager = .shared
@ -104,7 +104,7 @@ extension EndpointView {
} }
} }
} }
var body: some View { var body: some View {
ScrollViewReader { scrollProxy in ScrollViewReader { scrollProxy in
List { List {
@ -135,7 +135,7 @@ extension EndpointView.OpenVPNView {
Toggle(L10n.Global.Strings.automatic, isOn: $isAutomatic.themeAnimation()) Toggle(L10n.Global.Strings.automatic, isOn: $isAutomatic.themeAnimation())
} }
} }
private var filtersSection: some View { private var filtersSection: some View {
Section { Section {
themeTextPicker( themeTextPicker(
@ -162,7 +162,7 @@ extension EndpointView.OpenVPNView {
Text(L10n.Global.Strings.addresses) Text(L10n.Global.Strings.addresses)
} }
} }
private var advancedSection: some View { private var advancedSection: some View {
Section { Section {
let caption = L10n.Endpoint.Advanced.title let caption = L10n.Endpoint.Advanced.title
@ -175,7 +175,7 @@ extension EndpointView.OpenVPNView {
} }
} }
} }
private func button(forEndpoint endpoint: Endpoint?) -> some View { private func button(forEndpoint endpoint: Endpoint?) -> some View {
Button { Button {
customEndpoint = endpoint customEndpoint = endpoint
@ -265,7 +265,7 @@ extension EndpointView.OpenVPNView {
}.map(\.proto.port)) }.map(\.proto.port))
return Array(allPorts).sorted() return Array(allPorts).sorted()
} }
private var filteredRemotes: [Endpoint]? { private var filteredRemotes: [Endpoint]? {
builder.remotes?.filter { builder.remotes?.filter {
$0.proto.socketType == selectedSocketType && $0.proto.port == selectedPort $0.proto.socketType == selectedSocketType && $0.proto.port == selectedPort

View File

@ -38,7 +38,7 @@ extension EndpointView {
@Binding private var builder: WireGuard.ConfigurationBuilder @Binding private var builder: WireGuard.ConfigurationBuilder
// var customPeer: Binding<Endpoint?>? = nil // var customPeer: Binding<Endpoint?>? = nil
// XXX: do not escape mutating 'self', use constant providerManager // XXX: do not escape mutating 'self', use constant providerManager
init(currentProfile: ObservableProfile, isReadonly: Bool) { init(currentProfile: ObservableProfile, isReadonly: Bool) {
let providerManager: ProviderManager = .shared let providerManager: ProviderManager = .shared

View File

@ -28,11 +28,11 @@ import PassepartoutLibrary
struct EndpointView: View { struct EndpointView: View {
@ObservedObject private var currentProfile: ObservableProfile @ObservedObject private var currentProfile: ObservableProfile
init(currentProfile: ObservableProfile) { init(currentProfile: ObservableProfile) {
self.currentProfile = currentProfile self.currentProfile = currentProfile
} }
var body: some View { var body: some View {
switch currentProfile.value.currentVPNProtocol { switch currentProfile.value.currentVPNProtocol {
case .openVPN: case .openVPN:

View File

@ -34,7 +34,7 @@ struct NetworkSettingsView: View {
} }
@State private var settings = Profile.NetworkSettings() @State private var settings = Profile.NetworkSettings()
init(currentProfile: ObservableProfile) { init(currentProfile: ObservableProfile) {
self.currentProfile = currentProfile self.currentProfile = currentProfile
} }
@ -64,7 +64,7 @@ struct NetworkSettingsView: View {
) )
} }
} }
// EditButton() // EditButton()
// .disabled(!isAnythingManual) // .disabled(!isAnythingManual)
@ -83,7 +83,7 @@ struct NetworkSettingsView: View {
// } // }
return false return false
} }
private func mapNotEmpty(elements: [IdentifiableString]) -> [IdentifiableString] { private func mapNotEmpty(elements: [IdentifiableString]) -> [IdentifiableString] {
elements elements
.filter { !$0.string.isEmpty } .filter { !$0.string.isEmpty }
@ -110,7 +110,7 @@ extension NetworkSettingsView {
// MARK: DNS // MARK: DNS
extension NetworkSettingsView { extension NetworkSettingsView {
@ViewBuilder @ViewBuilder
private var dnsView: some View { private var dnsView: some View {
Section { Section {
@ -146,7 +146,7 @@ extension NetworkSettingsView {
dnsManualDomains dnsManualDomains
} }
} }
private var dnsManualHTTPSRow: some View { private var dnsManualHTTPSRow: some View {
TextField(Unlocalized.Placeholders.dohURL, text: $settings.dns.dnsHTTPSURL.toString()) TextField(Unlocalized.Placeholders.dohURL, text: $settings.dns.dnsHTTPSURL.toString())
.themeValidURL(settings.dns.dnsHTTPSURL?.absoluteString) .themeValidURL(settings.dns.dnsHTTPSURL?.absoluteString)
@ -177,7 +177,7 @@ extension NetworkSettingsView {
} }
} }
} }
private var dnsManualDomains: some View { private var dnsManualDomains: some View {
Section { Section {
EditableTextList( EditableTextList(
@ -203,7 +203,7 @@ extension NetworkSettingsView {
// MARK: Proxy // MARK: Proxy
extension NetworkSettingsView { extension NetworkSettingsView {
@ViewBuilder @ViewBuilder
private var proxyView: some View { private var proxyView: some View {
Section { Section {
@ -242,7 +242,7 @@ extension NetworkSettingsView {
proxyManualBypassDomains proxyManualBypassDomains
} }
} }
private var proxyManualBypassDomains: some View { private var proxyManualBypassDomains: some View {
Section { Section {
EditableTextList( EditableTextList(
@ -271,7 +271,7 @@ extension NetworkSettingsView {
private var mtuView: some View { private var mtuView: some View {
Section { Section {
Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticMTU.themeAnimation()) Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticMTU.themeAnimation())
if !settings.isAutomaticMTU { if !settings.isAutomaticMTU {
themeTextPicker( themeTextPicker(
L10n.Global.Strings.bytes, L10n.Global.Strings.bytes,

View File

@ -29,9 +29,9 @@ import PassepartoutLibrary
extension OnDemandView { extension OnDemandView {
struct SSIDList: View { struct SSIDList: View {
@Binding var withSSIDs: [String: Bool] @Binding var withSSIDs: [String: Bool]
@StateObject private var reader = SSIDReader() @StateObject private var reader = SSIDReader()
var body: some View { var body: some View {
EditableTextList(elements: allSSIDs, allowsDuplicates: false, mapping: mapElements) { text in EditableTextList(elements: allSSIDs, allowsDuplicates: false, mapping: mapElements) { text in
requestSSID(text) requestSSID(text)
@ -43,13 +43,13 @@ extension OnDemandView {
Text(L10n.Global.Strings.add) Text(L10n.Global.Strings.add)
} }
} }
private func mapElements(elements: [IdentifiableString]) -> [IdentifiableString] { private func mapElements(elements: [IdentifiableString]) -> [IdentifiableString] {
elements elements
.filter { !$0.string.isEmpty } .filter { !$0.string.isEmpty }
.sorted { $0.string.lowercased() < $1.string.lowercased() } .sorted { $0.string.lowercased() < $1.string.lowercased() }
} }
private func ssidRow(callback: EditableTextList.FieldCallback) -> some View { private func ssidRow(callback: EditableTextList.FieldCallback) -> some View {
Group { Group {
if callback.isNewElement { if callback.isNewElement {
@ -61,7 +61,7 @@ extension OnDemandView {
} }
} }
} }
private func ssidField(callback: EditableTextList.FieldCallback) -> some View { private func ssidField(callback: EditableTextList.FieldCallback) -> some View {
TextField( TextField(
Unlocalized.Network.ssid, Unlocalized.Network.ssid,
@ -102,7 +102,7 @@ extension OnDemandView.SSIDList {
// print(">>> withSSIDs (allSSIDs): \(withSSIDs)") // print(">>> withSSIDs (allSSIDs): \(withSSIDs)")
} }
} }
private var onSSIDs: Binding<Set<String>> { private var onSSIDs: Binding<Set<String>> {
.init { .init {
Set(withSSIDs.filter { Set(withSSIDs.filter {
@ -128,7 +128,7 @@ extension OnDemandView.SSIDList {
// print(">>> withSSIDs (onSSIDs): \(withSSIDs)") // print(">>> withSSIDs (onSSIDs): \(withSSIDs)")
} }
} }
private func isSSIDOn(_ ssid: String) -> Binding<Bool> { private func isSSIDOn(_ ssid: String) -> Binding<Bool> {
.init { .init {
withSSIDs[ssid] ?? false withSSIDs[ssid] ?? false

View File

@ -36,7 +36,7 @@ struct OnDemandView: View {
} }
@State private var onDemand = Profile.OnDemand() @State private var onDemand = Profile.OnDemand()
init(currentProfile: ObservableProfile) { init(currentProfile: ObservableProfile) {
productManager = .shared productManager = .shared
self.currentProfile = currentProfile self.currentProfile = currentProfile
@ -72,7 +72,7 @@ extension OnDemandView {
Toggle(L10n.Global.Strings.enabled, isOn: $onDemand.isEnabled.themeAnimation()) Toggle(L10n.Global.Strings.enabled, isOn: $onDemand.isEnabled.themeAnimation())
} }
} }
@ViewBuilder @ViewBuilder
private var mainView: some View { private var mainView: some View {
if Utils.hasCellularData() { if Utils.hasCellularData() {
@ -118,7 +118,7 @@ extension OnDemandView {
IntentDispatcher.donateTrustCellularNetwork() IntentDispatcher.donateTrustCellularNetwork()
IntentDispatcher.donateUntrustCellularNetwork() IntentDispatcher.donateUntrustCellularNetwork()
} }
// eligibility: donate intents if eligible for Siri // eligibility: donate intents if eligible for Siri
private func donateNetworkIntents(_: [String: Bool]) { private func donateNetworkIntents(_: [String: Bool]) {
guard isEligibleForSiri else { guard isEligibleForSiri else {

View File

@ -29,11 +29,11 @@ import PassepartoutLibrary
extension OrganizerView { extension OrganizerView {
struct ProfileRow: View { struct ProfileRow: View {
private let profile: Profile private let profile: Profile
private let isActiveProfile: Bool private let isActiveProfile: Bool
@Binding private var modalType: ModalType? @Binding private var modalType: ModalType?
private var interactiveProfile: Binding<Profile?> { private var interactiveProfile: Binding<Profile?> {
.init { .init {
if case .interactiveAccount(let profile) = modalType { if case .interactiveAccount(let profile) = modalType {
@ -48,13 +48,13 @@ extension OrganizerView {
} }
} }
} }
init(profile: Profile, isActiveProfile: Bool, modalType: Binding<ModalType?>) { init(profile: Profile, isActiveProfile: Bool, modalType: Binding<ModalType?>) {
self.profile = profile self.profile = profile
self.isActiveProfile = isActiveProfile self.isActiveProfile = isActiveProfile
_modalType = modalType _modalType = modalType
} }
var body: some View { var body: some View {
debugChanges() debugChanges()
return HStack { return HStack {
@ -62,7 +62,7 @@ extension OrganizerView {
Text(profile.header.name) Text(profile.header.name)
.font(.headline) .font(.headline)
.themeLongTextStyle() .themeLongTextStyle()
VPNStatusText(isActiveProfile: isActiveProfile) VPNStatusText(isActiveProfile: isActiveProfile)
.font(.subheadline) .font(.subheadline)
.themeSecondaryTextStyle() .themeSecondaryTextStyle()

View File

@ -29,7 +29,7 @@ import PassepartoutLibrary
extension OrganizerView { extension OrganizerView {
struct ProfilesList: View { struct ProfilesList: View {
@ObservedObject private var profileManager: ProfileManager @ObservedObject private var profileManager: ProfileManager
@Binding private var modalType: ModalType? @Binding private var modalType: ModalType?
init(modalType: Binding<ModalType?>) { init(modalType: Binding<ModalType?>) {
@ -49,7 +49,7 @@ extension OrganizerView {
profileManager.currentProfileId = $0.id profileManager.currentProfileId = $0.id
} }
} }
private var mainView: some View { private var mainView: some View {
List { List {
if profileManager.hasProfiles { if profileManager.hasProfiles {
@ -69,7 +69,7 @@ extension OrganizerView {
} }
}.themeAnimation(on: profileManager.headers) }.themeAnimation(on: profileManager.headers)
} }
private var profilesView: some View { private var profilesView: some View {
ForEach(sortedProfiles, content: profileRow(forProfile:)) ForEach(sortedProfiles, content: profileRow(forProfile:))
.onDelete(perform: removeProfiles) .onDelete(perform: removeProfiles)
@ -91,7 +91,7 @@ extension OrganizerView {
ProfileContextMenu(header: profile.header) ProfileContextMenu(header: profile.header)
} }
} }
private func profileLabel(forProfile profile: Profile) -> some View { private func profileLabel(forProfile profile: Profile) -> some View {
ProfileRow( ProfileRow(
profile: profile, profile: profile,
@ -124,7 +124,7 @@ extension OrganizerView {
profileManager.removeProfiles(withIds: toDelete) profileManager.removeProfiles(withIds: toDelete)
} }
} }
private func performMigrationsIfNeeded() { private func performMigrationsIfNeeded() {
Task { @MainActor in Task { @MainActor in
UpgradeManager.shared.doMigrations(profileManager) UpgradeManager.shared.doMigrations(profileManager)
@ -140,13 +140,13 @@ extension OrganizerView {
@ObservedObject private var currentVPNState: ObservableVPNState @ObservedObject private var currentVPNState: ObservableVPNState
let header: Profile.Header let header: Profile.Header
init(header: Profile.Header) { init(header: Profile.Header) {
profileManager = .shared profileManager = .shared
currentVPNState = .shared currentVPNState = .shared
self.header = header self.header = header
} }
var body: some View { var body: some View {
if #available(iOS 16, *), isActiveProfileNotDisconnected { if #available(iOS 16, *), isActiveProfileNotDisconnected {
reconnectButton reconnectButton
@ -154,11 +154,11 @@ extension OrganizerView {
duplicateButton duplicateButton
deleteButton deleteButton
} }
private var isActiveProfileNotDisconnected: Bool { private var isActiveProfileNotDisconnected: Bool {
profileManager.isActiveProfile(header.id) && currentVPNState.vpnStatus != .disconnected profileManager.isActiveProfile(header.id) && currentVPNState.vpnStatus != .disconnected
} }
private var reconnectButton: some View { private var reconnectButton: some View {
ProfileView.ReconnectButton() ProfileView.ReconnectButton()
} }

View File

@ -31,13 +31,13 @@ extension OrganizerView {
@Environment(\.scenePhase) private var scenePhase @Environment(\.scenePhase) private var scenePhase
@ObservedObject private var profileManager: ProfileManager @ObservedObject private var profileManager: ProfileManager
@ObservedObject private var vpnManager: VPNManager @ObservedObject private var vpnManager: VPNManager
@Binding private var alertType: AlertType? @Binding private var alertType: AlertType?
@Binding private var didHandleSubreddit: Bool @Binding private var didHandleSubreddit: Bool
@State private var isFirstLaunch = true @State private var isFirstLaunch = true
init(alertType: Binding<AlertType?>, didHandleSubreddit: Binding<Bool>) { init(alertType: Binding<AlertType?>, didHandleSubreddit: Binding<Bool>) {
@ -46,7 +46,7 @@ extension OrganizerView {
_alertType = alertType _alertType = alertType
_didHandleSubreddit = didHandleSubreddit _didHandleSubreddit = didHandleSubreddit
} }
var body: some View { var body: some View {
// dummy text, EmptyView() does not trigger on*() handlers // dummy text, EmptyView() does not trigger on*() handlers
@ -55,7 +55,7 @@ extension OrganizerView {
.onAppear(perform: onAppear) .onAppear(perform: onAppear)
.onChange(of: scenePhase, perform: onScenePhase) .onChange(of: scenePhase, perform: onScenePhase)
} }
private func onAppear() { private func onAppear() {
guard didHandleSubreddit else { guard didHandleSubreddit else {
alertType = .subscribeReddit alertType = .subscribeReddit
@ -92,7 +92,7 @@ extension OrganizerView {
break break
} }
} }
private func persist() { private func persist() {
profileManager.persist() profileManager.persist()
} }

View File

@ -40,9 +40,9 @@ struct OrganizerView: View {
enum AlertType: Identifiable { enum AlertType: Identifiable {
case subscribeReddit case subscribeReddit
case error(String, String) case error(String, String)
// XXX: alert ids // XXX: alert ids
var id: Int { var id: Int {
switch self { switch self {
@ -62,11 +62,11 @@ struct OrganizerView: View {
@State private var isHostFileImporterPresented = false @State private var isHostFileImporterPresented = false
@AppStorage(AppPreference.didHandleSubreddit.key) private var didHandleSubreddit = false @AppStorage(AppPreference.didHandleSubreddit.key) private var didHandleSubreddit = false
private let hostFileTypes = Constants.URLs.filetypes private let hostFileTypes = Constants.URLs.filetypes
private let redditURL = Constants.URLs.subreddit private let redditURL = Constants.URLs.subreddit
var body: some View { var body: some View {
debugChanges() debugChanges()
return ZStack { return ZStack {
@ -93,13 +93,13 @@ struct OrganizerView: View {
onCompletion: onHostFileImporterResult onCompletion: onHostFileImporterResult
).onOpenURL(perform: onOpenURL) ).onOpenURL(perform: onOpenURL)
.themePrimaryView() .themePrimaryView()
// VPN configuration error publisher (no need to observe VPNManager) // VPN configuration error publisher (no need to observe VPNManager)
.onReceive(VPNManager.shared.configurationError) { .onReceive(VPNManager.shared.configurationError) {
alertType = .error($0.profile.header.name, $0.error.localizedAppDescription) alertType = .error($0.profile.header.name, $0.error.localizedAppDescription)
} }
} }
private var hiddenSceneView: some View { private var hiddenSceneView: some View {
SceneView( SceneView(
alertType: $alertType, alertType: $alertType,
@ -128,7 +128,7 @@ extension OrganizerView {
) )
} }
} }
private func onOpenURL(_ url: URL) { private func onOpenURL(_ url: URL) {
addProfileModalType = .addHost(url, false) addProfileModalType = .addHost(url, false)
} }

View File

@ -37,7 +37,7 @@ extension PaywallView {
var id: Int { var id: Int {
switch self { switch self {
case .purchaseFailed: return 1 case .purchaseFailed: return 1
case .restoreFailed: return 2 case .restoreFailed: return 2
} }
} }
@ -48,19 +48,19 @@ extension PaywallView {
case restoring case restoring
} }
private typealias RowModel = (product: SKProduct, extra: String?) private typealias RowModel = (product: SKProduct, extra: String?)
@Environment(\.scenePhase) private var scenePhase @Environment(\.scenePhase) private var scenePhase
@ObservedObject private var productManager: ProductManager @ObservedObject private var productManager: ProductManager
@Binding private var isPresented: Bool @Binding private var isPresented: Bool
private let feature: LocalProduct? private let feature: LocalProduct?
@State private var alertType: AlertType? @State private var alertType: AlertType?
@State private var purchaseState: PurchaseState? @State private var purchaseState: PurchaseState?
init(isPresented: Binding<Bool>, feature: LocalProduct? = nil) { init(isPresented: Binding<Bool>, feature: LocalProduct? = nil) {
@ -68,7 +68,7 @@ extension PaywallView {
_isPresented = isPresented _isPresented = isPresented
self.feature = feature self.feature = feature
} }
var body: some View { var body: some View {
List { List {
productsSection productsSection
@ -85,7 +85,7 @@ extension PaywallView {
} }
}.themeAnimation(on: productManager.isRefreshingProducts) }.themeAnimation(on: productManager.isRefreshingProducts)
} }
private func presentedAlert(_ alertType: AlertType) -> Alert { private func presentedAlert(_ alertType: AlertType) -> Alert {
switch alertType { switch alertType {
case .purchaseFailed(let product, let error): case .purchaseFailed(let product, let error):
@ -96,7 +96,7 @@ extension PaywallView {
purchaseState = nil purchaseState = nil
} }
) )
case .restoreFailed(let error): case .restoreFailed(let error):
return Alert( return Alert(
title: Text(L10n.Paywall.Items.Restore.title), title: Text(L10n.Paywall.Items.Restore.title),
@ -107,7 +107,7 @@ extension PaywallView {
) )
} }
} }
private var productsSection: some View { private var productsSection: some View {
Section { Section {
if !productManager.isRefreshingProducts { if !productManager.isRefreshingProducts {
@ -134,7 +134,7 @@ extension PaywallView {
purchaseState: purchaseState purchaseState: purchaseState
) )
} }
private var restoreRow: some View { private var restoreRow: some View {
PurchaseRow( PurchaseRow(
title: L10n.Paywall.Items.Restore.title, title: L10n.Paywall.Items.Restore.title,
@ -156,7 +156,7 @@ extension PaywallView.PurchaseView {
switch result { switch result {
case .done: case .done:
isPresented = false isPresented = false
case .cancelled: case .cancelled:
break break
} }
@ -168,7 +168,7 @@ extension PaywallView.PurchaseView {
} }
} }
} }
private func restorePurchases() { private func restorePurchases() {
purchaseState = .restoring purchaseState = .restoring
@ -191,7 +191,7 @@ extension PaywallView.PurchaseView {
} }
return productManager.product(withIdentifier: feature) return productManager.product(withIdentifier: feature)
} }
private var skPlatformVersion: SKProduct? { private var skPlatformVersion: SKProduct? {
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
productManager.product(withIdentifier: .fullVersion_macOS) productManager.product(withIdentifier: .fullVersion_macOS)
@ -258,13 +258,13 @@ private struct PurchaseRow: View {
var product: SKProduct? var product: SKProduct?
let title: String let title: String
let extra: String? let extra: String?
let action: () -> Void let action: () -> Void
let purchaseState: PaywallView.PurchaseView.PurchaseState? let purchaseState: PaywallView.PurchaseView.PurchaseState?
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
actionButton actionButton
@ -276,7 +276,7 @@ private struct PurchaseRow: View {
} }
}.padding([.top, .bottom]) }.padding([.top, .bottom])
} }
@ViewBuilder @ViewBuilder
private var actionButton: some View { private var actionButton: some View {
if let product = product { if let product = product {
@ -285,7 +285,7 @@ private struct PurchaseRow: View {
restoreButton restoreButton
} }
} }
private func purchaseButton(_ product: SKProduct) -> some View { private func purchaseButton(_ product: SKProduct) -> some View {
HStack { HStack {
Button(title, action: action) Button(title, action: action)
@ -300,7 +300,7 @@ private struct PurchaseRow: View {
} }
} }
} }
private var restoreButton: some View { private var restoreButton: some View {
HStack { HStack {
Button(title, action: action) Button(title, action: action)

View File

@ -27,9 +27,9 @@ import SwiftUI
struct PaywallView: View { struct PaywallView: View {
@ObservedObject private var productManager: ProductManager @ObservedObject private var productManager: ProductManager
@Binding private var isPresented: Bool @Binding private var isPresented: Bool
private let feature: LocalProduct? private let feature: LocalProduct?
init<MT>(modalType: Binding<MT?>, feature: LocalProduct? = nil) { init<MT>(modalType: Binding<MT?>, feature: LocalProduct? = nil) {

View File

@ -33,15 +33,15 @@ extension ProfileView {
@ObservedObject private var currentProfile: ObservableProfile @ObservedObject private var currentProfile: ObservableProfile
@Binding private var modalType: ModalType? @Binding private var modalType: ModalType?
private var isEligibleForNetworkSettings: Bool { private var isEligibleForNetworkSettings: Bool {
productManager.isEligible(forFeature: .networkSettings) productManager.isEligible(forFeature: .networkSettings)
} }
private var isEligibleForTrustedNetworks: Bool { private var isEligibleForTrustedNetworks: Bool {
productManager.isEligible(forFeature: .trustedNetworks) productManager.isEligible(forFeature: .trustedNetworks)
} }
init(currentProfile: ObservableProfile, modalType: Binding<ModalType?>) { init(currentProfile: ObservableProfile, modalType: Binding<ModalType?>) {
productManager = .shared productManager = .shared
self.currentProfile = currentProfile self.currentProfile = currentProfile
@ -111,7 +111,7 @@ extension ProfileView {
Text(L10n.Global.Strings.configuration) Text(L10n.Global.Strings.configuration)
} }
} }
private var networkSettingsRow: some View { private var networkSettingsRow: some View {
Label(L10n.NetworkSettings.title, systemImage: themeNetworkSettingsImage) Label(L10n.NetworkSettings.title, systemImage: themeNetworkSettingsImage)
} }

View File

@ -29,7 +29,7 @@ import PassepartoutLibrary
extension ProfileView { extension ProfileView {
struct DiagnosticsSection: View { struct DiagnosticsSection: View {
@ObservedObject var currentProfile: ObservableProfile @ObservedObject var currentProfile: ObservableProfile
var body: some View { var body: some View {
Section { Section {
NavigationLink { NavigationLink {

View File

@ -29,11 +29,11 @@ import PassepartoutLibrary
extension ProfileView { extension ProfileView {
struct ExtraSection: View { struct ExtraSection: View {
@ObservedObject private var currentProfile: ObservableProfile @ObservedObject private var currentProfile: ObservableProfile
init(currentProfile: ObservableProfile) { init(currentProfile: ObservableProfile) {
self.currentProfile = currentProfile self.currentProfile = currentProfile
} }
var body: some View { var body: some View {
if currentProfile.value.isProvider { if currentProfile.value.isProvider {
Section { Section {

View File

@ -30,36 +30,36 @@ extension ProfileView {
struct MainMenu: View { struct MainMenu: View {
enum ActionSheetType: Int, Identifiable { enum ActionSheetType: Int, Identifiable {
case uninstallVPN case uninstallVPN
case deleteProfile case deleteProfile
var id: Int { var id: Int {
rawValue rawValue
} }
} }
@ObservedObject private var profileManager: ProfileManager @ObservedObject private var profileManager: ProfileManager
@ObservedObject private var vpnManager: VPNManager @ObservedObject private var vpnManager: VPNManager
@ObservedObject private var currentVPNState: ObservableVPNState @ObservedObject private var currentVPNState: ObservableVPNState
@ObservedObject private var currentProfile: ObservableProfile @ObservedObject private var currentProfile: ObservableProfile
private var header: Profile.Header { private var header: Profile.Header {
currentProfile.value.header currentProfile.value.header
} }
@Binding private var modalType: ModalType? @Binding private var modalType: ModalType?
@State private var actionSheetType: ActionSheetType? @State private var actionSheetType: ActionSheetType?
private let uninstallVPNTitle = L10n.Global.Strings.uninstall private let uninstallVPNTitle = L10n.Global.Strings.uninstall
private let deleteProfileTitle = L10n.Global.Strings.delete private let deleteProfileTitle = L10n.Global.Strings.delete
private let cancelTitle = L10n.Global.Strings.cancel private let cancelTitle = L10n.Global.Strings.cancel
init(currentProfile: ObservableProfile, modalType: Binding<ModalType?>) { init(currentProfile: ObservableProfile, modalType: Binding<ModalType?>) {
profileManager = .shared profileManager = .shared
vpnManager = .shared vpnManager = .shared
@ -79,7 +79,7 @@ extension ProfileView {
primaryButton: .destructive(Text(uninstallVPNTitle), action: uninstallVPN), primaryButton: .destructive(Text(uninstallVPNTitle), action: uninstallVPN),
secondaryButton: .cancel(Text(cancelTitle)) secondaryButton: .cancel(Text(cancelTitle))
) )
case .deleteProfile: case .deleteProfile:
return Alert( return Alert(
title: Text(deleteProfileTitle), title: Text(deleteProfileTitle),
@ -114,7 +114,7 @@ extension ProfileView {
themeSettingsMenuImage.asSystemImage themeSettingsMenuImage.asSystemImage
} }
} }
private var isActiveProfileNotDisconnected: Bool { private var isActiveProfileNotDisconnected: Bool {
profileManager.isActiveProfile(header.id) && currentVPNState.vpnStatus != .disconnected profileManager.isActiveProfile(header.id) && currentVPNState.vpnStatus != .disconnected
} }
@ -126,7 +126,7 @@ extension ProfileView {
Label(uninstallVPNTitle, systemImage: themeUninstallImage) Label(uninstallVPNTitle, systemImage: themeUninstallImage)
} }
} }
private var deleteProfileButton: some View { private var deleteProfileButton: some View {
DestructiveButton { DestructiveButton {
actionSheetType = .deleteProfile actionSheetType = .deleteProfile
@ -152,11 +152,11 @@ extension ProfileView {
extension ProfileView { extension ProfileView {
struct ReconnectButton: View { struct ReconnectButton: View {
@ObservedObject private var vpnManager: VPNManager @ObservedObject private var vpnManager: VPNManager
init() { init() {
vpnManager = .shared vpnManager = .shared
} }
var body: some View { var body: some View {
Button { Button {
Task { @MainActor in Task { @MainActor in
@ -167,12 +167,12 @@ extension ProfileView {
} }
} }
} }
struct ShortcutsButton: View { struct ShortcutsButton: View {
@ObservedObject private var productManager: ProductManager @ObservedObject private var productManager: ProductManager
@Binding private var modalType: ModalType? @Binding private var modalType: ModalType?
init(modalType: Binding<ModalType?>) { init(modalType: Binding<ModalType?>) {
productManager = .shared productManager = .shared
_modalType = modalType _modalType = modalType
@ -181,7 +181,7 @@ extension ProfileView {
private var isEligibleForSiri: Bool { private var isEligibleForSiri: Bool {
productManager.isEligible(forFeature: .siriShortcuts) productManager.isEligible(forFeature: .siriShortcuts)
} }
var body: some View { var body: some View {
Button { Button {
presentShortcutsOrPaywall() presentShortcutsOrPaywall()
@ -200,14 +200,14 @@ extension ProfileView {
} }
} }
} }
struct RenameButton: View { struct RenameButton: View {
@Binding private var modalType: ModalType? @Binding private var modalType: ModalType?
init(modalType: Binding<ModalType?>) { init(modalType: Binding<ModalType?>) {
_modalType = modalType _modalType = modalType
} }
var body: some View { var body: some View {
Button { Button {
modalType = .rename modalType = .rename
@ -219,17 +219,17 @@ extension ProfileView {
struct DuplicateButton: View { struct DuplicateButton: View {
@ObservedObject private var profileManager: ProfileManager @ObservedObject private var profileManager: ProfileManager
private let header: Profile.Header private let header: Profile.Header
private let setAsCurrent: Bool private let setAsCurrent: Bool
init(header: Profile.Header, setAsCurrent: Bool) { init(header: Profile.Header, setAsCurrent: Bool) {
profileManager = .shared profileManager = .shared
self.header = header self.header = header
self.setAsCurrent = setAsCurrent self.setAsCurrent = setAsCurrent
} }
var body: some View { var body: some View {
Button { Button {
duplicateProfile(withId: header.id) duplicateProfile(withId: header.id)

View File

@ -29,17 +29,17 @@ import PassepartoutLibrary
extension ProfileView { extension ProfileView {
struct ProviderSection: View, ProviderProfileAvailability { struct ProviderSection: View, ProviderProfileAvailability {
@ObservedObject var providerManager: ProviderManager @ObservedObject var providerManager: ProviderManager
@ObservedObject private var currentProfile: ObservableProfile @ObservedObject private var currentProfile: ObservableProfile
var profile: Profile { var profile: Profile {
currentProfile.value currentProfile.value
} }
@State private var isProviderLocationPresented = false @State private var isProviderLocationPresented = false
@State private var isRefreshingInfrastructure = false @State private var isRefreshingInfrastructure = false
init(currentProfile: ObservableProfile) { init(currentProfile: ObservableProfile) {
providerManager = .shared providerManager = .shared
self.currentProfile = currentProfile self.currentProfile = currentProfile
@ -55,7 +55,7 @@ extension ProfileView {
} }
} }
} }
@ViewBuilder @ViewBuilder
private var mainView: some View { private var mainView: some View {
Section { Section {
@ -116,11 +116,11 @@ extension ProfileView {
} }
return themeAssetsCountryImage(code).asAssetImage return themeAssetsCountryImage(code).asAssetImage
} }
private var currentProviderPreset: String? { private var currentProviderPreset: String? {
providerManager.localizedPreset(forProfile: profile) providerManager.localizedPreset(forProfile: profile)
} }
private var lastInfrastructureUpdate: String? { private var lastInfrastructureUpdate: String? {
providerManager.localizedInfrastructureUpdate(forProfile: profile) providerManager.localizedInfrastructureUpdate(forProfile: profile)
} }

View File

@ -29,20 +29,20 @@ import PassepartoutLibrary
extension ProfileView { extension ProfileView {
struct RenameView: View { struct RenameView: View {
@Environment(\.presentationMode) private var presentationMode @Environment(\.presentationMode) private var presentationMode
@ObservedObject private var profileManager: ProfileManager @ObservedObject private var profileManager: ProfileManager
@ObservedObject private var currentProfile: ObservableProfile @ObservedObject private var currentProfile: ObservableProfile
@State private var newName = "" @State private var newName = ""
@State private var isOverwritingExistingProfile = false @State private var isOverwritingExistingProfile = false
init(currentProfile: ObservableProfile) { init(currentProfile: ObservableProfile) {
profileManager = .shared profileManager = .shared
self.currentProfile = currentProfile self.currentProfile = currentProfile
} }
var body: some View { var body: some View {
List { List {
Section { Section {
@ -61,7 +61,7 @@ extension ProfileView {
} }
}.alert(isPresented: $isOverwritingExistingProfile, content: alertOverwriteExistingProfile) }.alert(isPresented: $isOverwritingExistingProfile, content: alertOverwriteExistingProfile)
} }
private func alertOverwriteExistingProfile() -> Alert { private func alertOverwriteExistingProfile() -> Alert {
Alert( Alert(
title: Text(L10n.Profile.Alerts.Rename.title), title: Text(L10n.Profile.Alerts.Rename.title),
@ -72,7 +72,7 @@ extension ProfileView {
secondaryButton: .cancel(Text(L10n.Global.Strings.cancel)) secondaryButton: .cancel(Text(L10n.Global.Strings.cancel))
) )
} }
private func loadCurrentName() { private func loadCurrentName() {
newName = currentProfile.value.header.name newName = currentProfile.value.header.name
} }

View File

@ -33,7 +33,7 @@ extension ProfileView {
private let profile: Profile private let profile: Profile
@Binding private var modalType: ModalType? @Binding private var modalType: ModalType?
private var interactiveProfile: Binding<Profile?> { private var interactiveProfile: Binding<Profile?> {
.init { .init {
modalType == .interactiveAccount ? profile : nil modalType == .interactiveAccount ? profile : nil
@ -41,7 +41,7 @@ extension ProfileView {
modalType = $0 != nil ? .interactiveAccount : nil modalType = $0 != nil ? .interactiveAccount : nil
} }
} }
private var isActiveProfile: Bool { private var isActiveProfile: Bool {
profileManager.isActiveProfile(profile.id) profileManager.isActiveProfile(profile.id)
} }
@ -51,7 +51,7 @@ extension ProfileView {
self.profile = profile self.profile = profile
_modalType = modalType _modalType = modalType
} }
var body: some View { var body: some View {
Section { Section {
toggleView toggleView
@ -63,7 +63,7 @@ extension ProfileView {
.xxxThemeTruncation() .xxxThemeTruncation()
} }
} }
private var toggleView: some View { private var toggleView: some View {
VPNToggle( VPNToggle(
profile: profile, profile: profile,
@ -71,7 +71,7 @@ extension ProfileView {
rateLimit: Constants.RateLimit.vpnToggle rateLimit: Constants.RateLimit.vpnToggle
) )
} }
private var statusView: some View { private var statusView: some View {
HStack { HStack {
Text(L10n.Profile.Items.ConnectionStatus.caption) Text(L10n.Profile.Items.ConnectionStatus.caption)

View File

@ -31,13 +31,13 @@ struct ProfileView: View {
case interactiveAccount case interactiveAccount
case shortcuts case shortcuts
case rename case rename
case paywallShortcuts case paywallShortcuts
case paywallNetworkSettings case paywallNetworkSettings
case paywallTrustedNetworks case paywallTrustedNetworks
var id: Int { var id: Int {
@ -46,17 +46,17 @@ struct ProfileView: View {
} }
@ObservedObject private var currentProfile: ObservableProfile @ObservedObject private var currentProfile: ObservableProfile
private var isLoading: Bool { private var isLoading: Bool {
currentProfile.isLoading currentProfile.isLoading
} }
private var isExisting: Bool { private var isExisting: Bool {
!currentProfile.value.isPlaceholder !currentProfile.value.isPlaceholder
} }
@State private var modalType: ModalType? @State private var modalType: ModalType?
init() { init() {
currentProfile = ProfileManager.shared.currentProfile currentProfile = ProfileManager.shared.currentProfile
} }
@ -83,11 +83,11 @@ struct ProfileView: View {
.navigationTitle(title) .navigationTitle(title)
.themeSecondaryView() .themeSecondaryView()
} }
private var title: String { private var title: String {
currentProfile.name currentProfile.name
} }
private var mainView: some View { private var mainView: some View {
List { List {
if !isLoading { if !isLoading {
@ -107,7 +107,7 @@ struct ProfileView: View {
} }
}.themeAnimation(on: isLoading) }.themeAnimation(on: isLoading)
} }
@ViewBuilder @ViewBuilder
private func presentedModal(_ modalType: ModalType) -> some View { private func presentedModal(_ modalType: ModalType) -> some View {
switch modalType { switch modalType {
@ -115,17 +115,17 @@ struct ProfileView: View {
NavigationView { NavigationView {
InteractiveConnectionView(profile: currentProfile.value) InteractiveConnectionView(profile: currentProfile.value)
}.themeGlobal() }.themeGlobal()
case .shortcuts: case .shortcuts:
NavigationView { NavigationView {
ShortcutsView(target: currentProfile.value) ShortcutsView(target: currentProfile.value)
}.themeGlobal() }.themeGlobal()
case .rename: case .rename:
NavigationView { NavigationView {
RenameView(currentProfile: currentProfile) RenameView(currentProfile: currentProfile)
}.themeGlobal() }.themeGlobal()
case .paywallShortcuts: case .paywallShortcuts:
NavigationView { NavigationView {
PaywallView( PaywallView(

View File

@ -30,13 +30,13 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
@ObservedObject var providerManager: ProviderManager @ObservedObject var providerManager: ProviderManager
@ObservedObject private var currentProfile: ObservableProfile @ObservedObject private var currentProfile: ObservableProfile
var profile: Profile { var profile: Profile {
currentProfile.value currentProfile.value
} }
private let isEditable: Bool private let isEditable: Bool
private var providerName: ProviderName { private var providerName: ProviderName {
guard let name = currentProfile.value.header.providerName else { guard let name = currentProfile.value.header.providerName else {
assertionFailure("Not a provider") assertionFailure("Not a provider")
@ -44,15 +44,15 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
} }
return name return name
} }
private var vpnProtocol: VPNProtocolType { private var vpnProtocol: VPNProtocolType {
currentProfile.value.currentVPNProtocol currentProfile.value.currentVPNProtocol
} }
@Binding private var selectedServer: ProviderServer? @Binding private var selectedServer: ProviderServer?
@Binding private var favoriteLocationIds: Set<String>? @Binding private var favoriteLocationIds: Set<String>?
@AppStorage(AppPreference.isShowingFavorites.key) private var isShowingFavorites = false @AppStorage(AppPreference.isShowingFavorites.key) private var isShowingFavorites = false
private var isShowingEmptyFavorites: Bool { private var isShowingEmptyFavorites: Bool {
@ -61,7 +61,7 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
} }
return favoriteLocationIds?.isEmpty ?? true return favoriteLocationIds?.isEmpty ?? true
} }
// XXX: do not escape mutating 'self', use constant providerManager // XXX: do not escape mutating 'self', use constant providerManager
init(currentProfile: ObservableProfile, isEditable: Bool, isPresented: Binding<Bool>) { init(currentProfile: ObservableProfile, isEditable: Bool, isPresented: Binding<Bool>) {
let providerManager: ProviderManager = .shared let providerManager: ProviderManager = .shared
@ -69,7 +69,7 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
self.providerManager = providerManager self.providerManager = providerManager
self.currentProfile = currentProfile self.currentProfile = currentProfile
self.isEditable = isEditable self.isEditable = isEditable
_selectedServer = .init { _selectedServer = .init {
guard let serverId = currentProfile.value.providerServerId() else { guard let serverId = currentProfile.value.providerServerId() else {
return nil return nil
@ -89,7 +89,7 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
currentProfile.value.setProviderFavoriteLocationIds($0) currentProfile.value.setProviderFavoriteLocationIds($0)
} }
} }
var body: some View { var body: some View {
debugChanges() debugChanges()
return Group { return Group {
@ -110,7 +110,7 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
} }
}.navigationTitle(L10n.Provider.Location.title) }.navigationTitle(L10n.Provider.Location.title)
} }
private var mainView: some View { private var mainView: some View {
// FIXME: layout, restore ScrollViewReader, but content inside it is not re-rendered on isShowingFavorites // FIXME: layout, restore ScrollViewReader, but content inside it is not re-rendered on isShowingFavorites
// ScrollViewReader { scrollProxy in // ScrollViewReader { scrollProxy in
@ -129,7 +129,7 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
private var categoriesView: some View { private var categoriesView: some View {
ForEach(categories, content: categorySection) ForEach(categories, content: categorySection)
} }
private func categorySection(_ category: ProviderCategory) -> some View { private func categorySection(_ category: ProviderCategory) -> some View {
Section { Section {
ForEach(filteredLocations(for: category)) { location in ForEach(filteredLocations(for: category)) { location in
@ -146,7 +146,7 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
!category.name.isEmpty ? Text(category.name) : nil !category.name.isEmpty ? Text(category.name) : nil
} }
} }
@ViewBuilder @ViewBuilder
private func locationRow(_ location: ProviderLocation) -> some View { private func locationRow(_ location: ProviderLocation) -> some View {
if let onlyServer = location.onlyServer { if let onlyServer = location.onlyServer {
@ -155,7 +155,7 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
multipleServersRow(location) multipleServersRow(location)
} }
} }
private func multipleServersRow(_ location: ProviderLocation) -> some View { private func multipleServersRow(_ location: ProviderLocation) -> some View {
NavigationLink(destination: { NavigationLink(destination: {
ServerListView( ServerListView(
@ -180,7 +180,7 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
) )
} }
} }
private var emptyFavoritesSection: some View { private var emptyFavoritesSection: some View {
Section { Section {
} footer: { } footer: {
@ -204,7 +204,7 @@ extension ProviderLocationView {
private func server(withId serverId: String) -> ProviderServer? { private func server(withId serverId: String) -> ProviderServer? {
providerManager.server(withId: serverId) providerManager.server(withId: serverId)
} }
private var categories: [ProviderCategory] { private var categories: [ProviderCategory] {
providerManager.categories(providerName, vpnProtocol: vpnProtocol) providerManager.categories(providerName, vpnProtocol: vpnProtocol)
.filter { .filter {
@ -223,11 +223,11 @@ extension ProviderLocationView {
} }
return locations.sorted() return locations.sorted()
} }
private func isFavoriteLocation(_ location: ProviderLocation) -> Bool { private func isFavoriteLocation(_ location: ProviderLocation) -> Bool {
favoriteLocationIds?.contains(location.id) ?? false favoriteLocationIds?.contains(location.id) ?? false
} }
private func toggleFavoriteLocation(_ location: ProviderLocation) { private func toggleFavoriteLocation(_ location: ProviderLocation) {
if !isFavoriteLocation(location) { if !isFavoriteLocation(location) {
if favoriteLocationIds == nil { if favoriteLocationIds == nil {
@ -248,7 +248,7 @@ extension ProviderLocationView {
let location: ProviderLocation let location: ProviderLocation
let selectedLocationId: String? let selectedLocationId: String?
var body: some View { var body: some View {
HStack { HStack {
themeAssetsCountryImage(location.countryCode).asAssetImage themeAssetsCountryImage(location.countryCode).asAssetImage
@ -269,11 +269,11 @@ extension ProviderLocationView {
struct ServerListView: View { struct ServerListView: View {
@ObservedObject private var providerManager: ProviderManager @ObservedObject private var providerManager: ProviderManager
private let location: ProviderLocation private let location: ProviderLocation
@Binding private var selectedServer: ProviderServer? @Binding private var selectedServer: ProviderServer?
init(location: ProviderLocation, selectedServer: Binding<ProviderServer?>) { init(location: ProviderLocation, selectedServer: Binding<ProviderServer?>) {
providerManager = .shared providerManager = .shared
self.location = location self.location = location

View File

@ -34,13 +34,13 @@ struct ProviderPresetView: View {
@ObservedObject private var currentProfile: ObservableProfile @ObservedObject private var currentProfile: ObservableProfile
private var server: ProviderServer? private var server: ProviderServer?
@Binding private var selectedPreset: ProviderServer.Preset? @Binding private var selectedPreset: ProviderServer.Preset?
// XXX: do not escape mutating 'self', use constant providerManager // XXX: do not escape mutating 'self', use constant providerManager
init(currentProfile: ObservableProfile) { init(currentProfile: ObservableProfile) {
let providerManager: ProviderManager = .shared let providerManager: ProviderManager = .shared
self.providerManager = providerManager self.providerManager = providerManager
self.currentProfile = currentProfile self.currentProfile = currentProfile
@ -61,7 +61,7 @@ struct ProviderPresetView: View {
currentProfile.value.setProviderPreset(preset) currentProfile.value.setProviderPreset(preset)
} }
} }
var body: some View { var body: some View {
List { List {
ForEach(availablePresets, id: \.id, content: presetSection) ForEach(availablePresets, id: \.id, content: presetSection)
@ -77,7 +77,7 @@ struct ProviderPresetView: View {
presetSelectionRow(preset) presetSelectionRow(preset)
} }
NavigationLink(L10n.Endpoint.Advanced.title) { NavigationLink(L10n.Endpoint.Advanced.title) {
// TODO: WireGuard, preset assumes OpenVPN (read current protocol instead) // TODO: WireGuard, preset assumes OpenVPN (read current protocol instead)
preset.openVPNConfiguration.map { preset.openVPNConfiguration.map {
EndpointAdvancedView.OpenVPNView( EndpointAdvancedView.OpenVPNView(
@ -91,7 +91,7 @@ struct ProviderPresetView: View {
Text(preset.name) Text(preset.name)
} }
} }
private func presetSelectionRow(_ preset: ProviderServer.Preset) -> some View { private func presetSelectionRow(_ preset: ProviderServer.Preset) -> some View {
Text(preset.comment) Text(preset.comment)
.withTrailingCheckmark(when: preset.id == selectedPreset?.id) .withTrailingCheckmark(when: preset.id == selectedPreset?.id)

View File

@ -29,15 +29,15 @@ import PassepartoutLibrary
struct ReportIssueView: View { struct ReportIssueView: View {
@Binding private var isPresented: Bool @Binding private var isPresented: Bool
private let toRecipients: [String] private let toRecipients: [String]
private let subject: String private let subject: String
private let messageBody: String private let messageBody: String
private let attachments: [MailComposerView.Attachment] private let attachments: [MailComposerView.Attachment]
init( init(
isPresented: Binding<Bool>, isPresented: Binding<Bool>,
vpnProtocol: VPNProtocolType, vpnProtocol: VPNProtocolType,
@ -46,7 +46,7 @@ struct ReportIssueView: View {
lastUpdate: Date? = nil lastUpdate: Date? = nil
) { ) {
_isPresented = isPresented _isPresented = isPresented
toRecipients = [Unlocalized.Issues.recipient] toRecipients = [Unlocalized.Issues.recipient]
subject = Unlocalized.Issues.subject subject = Unlocalized.Issues.subject
@ -77,7 +77,7 @@ struct ReportIssueView: View {
} }
self.attachments = attachments self.attachments = attachments
} }
var body: some View { var body: some View {
MailComposerView( MailComposerView(
isPresented: $isPresented, isPresented: $isPresented,

View File

@ -30,9 +30,9 @@ struct SettingsView: View {
@ObservedObject private var profileManager: ProfileManager @ObservedObject private var profileManager: ProfileManager
@ObservedObject private var productManager: ProductManager @ObservedObject private var productManager: ProductManager
@Environment(\.presentationMode) private var presentationMode @Environment(\.presentationMode) private var presentationMode
// private var isTestBuild: Bool { // private var isTestBuild: Bool {
// Constants.App.isBeta || Constants.InApp.appType == .beta // Constants.App.isBeta || Constants.InApp.appType == .beta
// } // }
@ -45,7 +45,7 @@ struct SettingsView: View {
profileManager = .shared profileManager = .shared
productManager = .shared productManager = .shared
} }
var body: some View { var body: some View {
List { List {
aboutSection aboutSection
@ -54,7 +54,7 @@ struct SettingsView: View {
}.themeSecondaryView() }.themeSecondaryView()
.navigationTitle(L10n.Settings.title) .navigationTitle(L10n.Settings.title)
} }
private var aboutSection: some View { private var aboutSection: some View {
Section { Section {
NavigationLink { NavigationLink {

View File

@ -30,11 +30,11 @@ import PassepartoutLibrary
extension ShortcutsView { extension ShortcutsView {
struct AddView: View { struct AddView: View {
@ObservedObject private var providerManager: ProviderManager @ObservedObject private var providerManager: ProviderManager
@StateObject private var pendingProfile = ObservableProfile() @StateObject private var pendingProfile = ObservableProfile()
private let target: Profile private let target: Profile
@Binding private var pendingShortcut: INShortcut? @Binding private var pendingShortcut: INShortcut?
@State private var isPresentingProviderLocation = false @State private var isPresentingProviderLocation = false
@ -44,7 +44,7 @@ extension ShortcutsView {
self.target = target self.target = target
_pendingShortcut = pendingShortcut _pendingShortcut = pendingShortcut
} }
var body: some View { var body: some View {
ZStack { ZStack {
hiddenProviderLocationLink hiddenProviderLocationLink
@ -82,7 +82,7 @@ extension ShortcutsView {
} }
} }
} }
private var hiddenProviderLocationLink: some View { private var hiddenProviderLocationLink: some View {
NavigationLink("", isActive: $isPresentingProviderLocation) { NavigationLink("", isActive: $isPresentingProviderLocation) {
ProviderLocationView( ProviderLocationView(
@ -106,7 +106,7 @@ extension ShortcutsView.AddView {
} }
} }
} }
private func addConnect(_ header: Profile.Header) { private func addConnect(_ header: Profile.Header) {
pendingShortcut = INShortcut(intent: IntentDispatcher.intentConnect( pendingShortcut = INShortcut(intent: IntentDispatcher.intentConnect(
header: header header: header

View File

@ -30,35 +30,35 @@ import PassepartoutLibrary
struct ShortcutsView: View { struct ShortcutsView: View {
enum ModalType: Identifiable { enum ModalType: Identifiable {
case edit(shortcut: Shortcut) case edit(shortcut: Shortcut)
case add(shortcut: INShortcut) case add(shortcut: INShortcut)
// XXX: alert ids // XXX: alert ids
var id: Int { var id: Int {
switch self { switch self {
case .edit: return 1 case .edit: return 1
case .add: return 2 case .add: return 2
} }
} }
} }
@StateObject private var intentsManager = IntentsManager() @StateObject private var intentsManager = IntentsManager()
@Environment(\.presentationMode) private var presentationMode @Environment(\.presentationMode) private var presentationMode
private let target: Profile private let target: Profile
@State private var modalType: ModalType? @State private var modalType: ModalType?
@State private var isNavigationPresented = false @State private var isNavigationPresented = false
@State private var pendingShortcut: INShortcut? @State private var pendingShortcut: INShortcut?
init(target: Profile) { init(target: Profile) {
self.target = target self.target = target
} }
var body: some View { var body: some View {
List { List {
if !intentsManager.shortcuts.isEmpty { if !intentsManager.shortcuts.isEmpty {
@ -69,16 +69,16 @@ struct ShortcutsView: View {
themeCloseItem(presentationMode: presentationMode) themeCloseItem(presentationMode: presentationMode)
}.sheet(item: $modalType, content: presentedModal) }.sheet(item: $modalType, content: presentedModal)
.themeAnimation(on: intentsManager.isReloadingShortcuts) .themeAnimation(on: intentsManager.isReloadingShortcuts)
// IntentsUI // IntentsUI
.onReceive(intentsManager.shouldDismissIntentView) { _ in .onReceive(intentsManager.shouldDismissIntentView) { _ in
modalType = nil modalType = nil
} }
.navigationTitle(Unlocalized.Other.siri) .navigationTitle(Unlocalized.Other.siri)
.themeSecondaryView() .themeSecondaryView()
} }
private var shortcutsSection: some View { private var shortcutsSection: some View {
Section { Section {
ForEach(relevantShortcuts, content: rowView) ForEach(relevantShortcuts, content: rowView)
@ -86,13 +86,13 @@ struct ShortcutsView: View {
Text(L10n.Shortcuts.Edit.Sections.All.header) Text(L10n.Shortcuts.Edit.Sections.All.header)
} }
} }
private var relevantShortcuts: [Shortcut] { private var relevantShortcuts: [Shortcut] {
intentsManager.shortcuts.values.filter { intentsManager.shortcuts.values.filter {
$0.isRelevant(to: target) $0.isRelevant(to: target)
}.sorted() }.sorted()
} }
private var addSection: some View { private var addSection: some View {
Section { Section {
NavigationLink(isActive: $isNavigationPresented) { NavigationLink(isActive: $isNavigationPresented) {
@ -116,7 +116,7 @@ struct ShortcutsView: View {
shortcut: shortcut, shortcut: shortcut,
delegate: intentsManager delegate: intentsManager
) )
case .add(let shortcut): case .add(let shortcut):
IntentAddView( IntentAddView(
shortcut: shortcut, shortcut: shortcut,
@ -124,7 +124,7 @@ struct ShortcutsView: View {
) )
} }
} }
private func rowView(forShortcut vs: Shortcut) -> some View { private func rowView(forShortcut vs: Shortcut) -> some View {
Button { Button {
presentEditShortcut(vs) presentEditShortcut(vs)
@ -143,7 +143,7 @@ struct ShortcutsView: View {
presentAddShortcut(pendingShortcut) presentAddShortcut(pendingShortcut)
} }
} }
private func presentEditShortcut(_ shortcut: Shortcut) { private func presentEditShortcut(_ shortcut: Shortcut) {
modalType = .edit(shortcut: shortcut) modalType = .edit(shortcut: shortcut)
} }

View File

@ -28,14 +28,14 @@ import PassepartoutLibrary
struct VPNStatusText: View { struct VPNStatusText: View {
@ObservedObject private var currentVPNState: ObservableVPNState @ObservedObject private var currentVPNState: ObservableVPNState
let isActiveProfile: Bool let isActiveProfile: Bool
init(isActiveProfile: Bool) { init(isActiveProfile: Bool) {
currentVPNState = .shared currentVPNState = .shared
self.isActiveProfile = isActiveProfile self.isActiveProfile = isActiveProfile
} }
var body: some View { var body: some View {
Text(statusText) Text(statusText)
} }

View File

@ -36,11 +36,11 @@ struct VPNToggle: View {
@ObservedObject private var productManager: ProductManager @ObservedObject private var productManager: ProductManager
private let profile: Profile private let profile: Profile
@Binding private var interactiveProfile: Profile? @Binding private var interactiveProfile: Profile?
private let rateLimit: Int private let rateLimit: Int
private var isEnabled: Binding<Bool> { private var isEnabled: Binding<Bool> {
.init { .init {
isActiveProfile && currentVPNState.isEnabled && !shouldPromptForAccount isActiveProfile && currentVPNState.isEnabled && !shouldPromptForAccount
@ -56,11 +56,11 @@ struct VPNToggle: View {
enableVPN() enableVPN()
} }
} }
private var isActiveProfile: Bool { private var isActiveProfile: Bool {
profileManager.isActiveProfile(profile.id) profileManager.isActiveProfile(profile.id)
} }
private var shouldPromptForAccount: Bool { private var shouldPromptForAccount: Bool {
profile.account.authenticationMethod == .interactive && (currentVPNState.vpnStatus == .disconnecting || currentVPNState.vpnStatus == .disconnected) profile.account.authenticationMethod == .interactive && (currentVPNState.vpnStatus == .disconnecting || currentVPNState.vpnStatus == .disconnected)
} }
@ -68,9 +68,9 @@ struct VPNToggle: View {
private var isEligibleForSiri: Bool { private var isEligibleForSiri: Bool {
productManager.isEligible(forFeature: .siriShortcuts) productManager.isEligible(forFeature: .siriShortcuts)
} }
@State private var canToggle = true @State private var canToggle = true
init(profile: Profile, interactiveProfile: Binding<Profile?>, rateLimit: Int) { init(profile: Profile, interactiveProfile: Binding<Profile?>, rateLimit: Int) {
profileManager = .shared profileManager = .shared
vpnManager = .shared vpnManager = .shared
@ -86,7 +86,7 @@ struct VPNToggle: View {
.disabled(!canToggle) .disabled(!canToggle)
.themeAnimation(on: currentVPNState.isEnabled) .themeAnimation(on: currentVPNState.isEnabled)
} }
private func enableVPN() { private func enableVPN() {
Task { @MainActor in Task { @MainActor in
canToggle = false canToggle = false
@ -101,7 +101,7 @@ struct VPNToggle: View {
} }
} }
} }
private func disableVPN() { private func disableVPN() {
Task { @MainActor in Task { @MainActor in
canToggle = false canToggle = false
@ -109,7 +109,7 @@ struct VPNToggle: View {
canToggle = true canToggle = true
} }
} }
private func donateIntents(withProfile profile: Profile) { private func donateIntents(withProfile profile: Profile) {
// eligibility: donate intents if eligible for Siri // eligibility: donate intents if eligible for Siri

View File

@ -52,7 +52,7 @@ extension View {
self self
} }
} }
func withLeadingLabel(_ text: String, color: Color? = nil, image: String) -> some View { func withLeadingLabel(_ text: String, color: Color? = nil, image: String) -> some View {
HStack { HStack {
Label(text, image: image) Label(text, image: image)
@ -80,7 +80,7 @@ extension View {
} }
} }
} }
func withTrailingCheckmark(when condition: Bool) -> some View { func withTrailingCheckmark(when condition: Bool) -> some View {
HStack { HStack {
self self
@ -91,7 +91,7 @@ extension View {
} }
} }
} }
func withTrailingProgress(when condition: Bool) -> some View { func withTrailingProgress(when condition: Bool) -> some View {
HStack { HStack {
self self
@ -145,7 +145,7 @@ private extension View {
} }
private struct HostingWindowFinder: UIViewRepresentable { private struct HostingWindowFinder: UIViewRepresentable {
var callback: (UIWindow?) -> () var callback: (UIWindow?) -> Void
func makeUIView(context: Context) -> UIView { func makeUIView(context: Context) -> UIView {
let view = UIView() let view = UIView()

View File

@ -30,11 +30,11 @@ import PassepartoutLibrary
@MainActor @MainActor
class CoreContext { class CoreContext {
let store: KeyValueStore let store: KeyValueStore
private let profilesPersistence: Persistence private let profilesPersistence: Persistence
private let providersPersistence: Persistence private let providersPersistence: Persistence
var urlsForProfiles: [URL]? { var urlsForProfiles: [URL]? {
profilesPersistence.containerURLs profilesPersistence.containerURLs
} }
@ -44,18 +44,18 @@ class CoreContext {
} }
let upgradeManager: UpgradeManager let upgradeManager: UpgradeManager
let providerManager: ProviderManager let providerManager: ProviderManager
let profileManager: ProfileManager let profileManager: ProfileManager
let vpnManager: VPNManager let vpnManager: VPNManager
private var cancellables: Set<AnyCancellable> = [] private var cancellables: Set<AnyCancellable> = []
init(store: KeyValueStore) { init(store: KeyValueStore) {
self.store = store self.store = store
let persistenceManager = PersistenceManager(store: store) let persistenceManager = PersistenceManager(store: store)
profilesPersistence = persistenceManager.profilesPersistence( profilesPersistence = persistenceManager.profilesPersistence(
withName: Constants.Persistence.profilesContainerName withName: Constants.Persistence.profilesContainerName
@ -106,12 +106,12 @@ class CoreContext {
providerManager: providerManager, providerManager: providerManager,
strategy: strategy strategy: strategy
) )
// post // post
configureObjects() configureObjects()
} }
private func configureObjects() { private func configureObjects() {
providerManager.rateLimitMilliseconds = Constants.RateLimit.providerManager providerManager.rateLimitMilliseconds = Constants.RateLimit.providerManager
vpnManager.tunnelLogPath = Constants.Log.Tunnel.path vpnManager.tunnelLogPath = Constants.Log.Tunnel.path

View File

@ -41,16 +41,16 @@ extension PassepartoutError {
switch self { switch self {
case .missingProfile: case .missingProfile:
return V.missingProfile return V.missingProfile
case .missingAccount: case .missingAccount:
return V.missingAccount return V.missingAccount
case .missingProviderServer: case .missingProviderServer:
return V.missingProviderServer return V.missingProviderServer
case .missingProviderPreset: case .missingProviderPreset:
return V.missingProviderPreset return V.missingProviderPreset
default: default:
return nil return nil
} }
@ -103,7 +103,7 @@ extension Network.Choice {
switch self { switch self {
case .automatic: case .automatic:
return L10n.Global.Strings.automatic return L10n.Global.Strings.automatic
case .manual: case .manual:
return L10n.Global.Strings.manual return L10n.Global.Strings.manual
} }
@ -115,7 +115,7 @@ extension Network.DNSSettings.ConfigurationType {
switch self { switch self {
case .plain: case .plain:
return Unlocalized.DNS.plain return Unlocalized.DNS.plain
case .https: case .https:
return Unlocalized.Network.https return Unlocalized.Network.https
@ -133,10 +133,10 @@ extension Network.ProxySettings.ConfigurationType {
switch self { switch self {
case .manual: case .manual:
return L10n.Global.Strings.manual return L10n.Global.Strings.manual
case .pac: case .pac:
return Unlocalized.Network.proxyAutoConfiguration return Unlocalized.Network.proxyAutoConfiguration
case .disabled: case .disabled:
return L10n.Global.Strings.disabled return L10n.Global.Strings.disabled
} }

View File

@ -43,10 +43,10 @@ extension OpenVPN.CompressionFraming {
switch self { switch self {
case .disabled: case .disabled:
return L10n.Global.Strings.disabled return L10n.Global.Strings.disabled
case .compLZO: case .compLZO:
return Unlocalized.OpenVPN.compLZO return Unlocalized.OpenVPN.compLZO
case .compress, .compressV2: case .compress, .compressV2:
return Unlocalized.OpenVPN.compress return Unlocalized.OpenVPN.compress
} }
@ -59,10 +59,10 @@ extension OpenVPN.CompressionAlgorithm {
switch self { switch self {
case .disabled: case .disabled:
return L10n.Global.Strings.disabled return L10n.Global.Strings.disabled
case .LZO: case .LZO:
return Unlocalized.OpenVPN.lzo return Unlocalized.OpenVPN.lzo
case .other: case .other:
return V.CompressionAlgorithm.Value.other return V.CompressionAlgorithm.Value.other
} }
@ -90,26 +90,26 @@ extension OpenVPN.XORMethod {
switch self { switch self {
case .xormask: case .xormask:
return Unlocalized.OpenVPN.XOR.xormask.rawValue return Unlocalized.OpenVPN.XOR.xormask.rawValue
case .xorptrpos: case .xorptrpos:
return Unlocalized.OpenVPN.XOR.xorptrpos.rawValue return Unlocalized.OpenVPN.XOR.xorptrpos.rawValue
case .reverse: case .reverse:
return Unlocalized.OpenVPN.XOR.reverse.rawValue return Unlocalized.OpenVPN.XOR.reverse.rawValue
case .obfuscate: case .obfuscate:
return Unlocalized.OpenVPN.XOR.obfuscate.rawValue return Unlocalized.OpenVPN.XOR.obfuscate.rawValue
} }
} }
var localizedLongDescription: String { var localizedLongDescription: String {
switch self { switch self {
case .xormask(let mask): case .xormask(let mask):
return "\(localizedDescription) \(mask.toHex())" return "\(localizedDescription) \(mask.toHex())"
case .obfuscate(let mask): case .obfuscate(let mask):
return "\(localizedDescription) \(mask.toHex())" return "\(localizedDescription) \(mask.toHex())"
default: default:
return localizedDescription return localizedDescription
} }
@ -139,7 +139,7 @@ extension Bool {
let V = L10n.Global.Strings.self let V = L10n.Global.Strings.self
return self ? V.enabled : V.disabled return self ? V.enabled : V.disabled
} }
var localizedDescriptionAsRandomizeHostnames: String { var localizedDescriptionAsRandomizeHostnames: String {
let V = L10n.Global.Strings.self let V = L10n.Global.Strings.self
return self ? V.enabled : V.disabled return self ? V.enabled : V.disabled
@ -151,10 +151,10 @@ extension OpenVPN.PullMask {
switch self { switch self {
case .routes: case .routes:
return L10n.Endpoint.Advanced.Openvpn.Items.Route.caption return L10n.Endpoint.Advanced.Openvpn.Items.Route.caption
case .dns: case .dns:
return Unlocalized.Network.dns return Unlocalized.Network.dns
case .proxy: case .proxy:
return L10n.Global.Strings.proxy return L10n.Global.Strings.proxy
} }

View File

@ -33,7 +33,7 @@ extension ProviderManager {
} }
return profile.providerPreset(server)?.localizedDescription return profile.providerPreset(server)?.localizedDescription
} }
func localizedInfrastructureUpdate(forProfile profile: Profile) -> String? { func localizedInfrastructureUpdate(forProfile profile: Profile) -> String? {
guard let providerName = profile.header.providerName else { guard let providerName = profile.header.providerName else {
return nil return nil

View File

@ -35,13 +35,13 @@ extension VPNStatus {
switch self { switch self {
case .connecting: case .connecting:
return L10n.Tunnelkit.Vpn.connecting return L10n.Tunnelkit.Vpn.connecting
case .connected: case .connected:
return L10n.Tunnelkit.Vpn.active return L10n.Tunnelkit.Vpn.active
case .disconnecting: case .disconnecting:
return L10n.Tunnelkit.Vpn.disconnecting return L10n.Tunnelkit.Vpn.disconnecting
case .disconnected: case .disconnected:
return L10n.Tunnelkit.Vpn.inactive return L10n.Tunnelkit.Vpn.inactive
} }
@ -121,37 +121,37 @@ extension Error {
} }
return localizedDescription return localizedDescription
} }
private func ovpnErrorDescription(_ error: OpenVPNProviderError) -> String? { private func ovpnErrorDescription(_ error: OpenVPNProviderError) -> String? {
let V = L10n.Tunnelkit.Errors.Vpn.self let V = L10n.Tunnelkit.Errors.Vpn.self
switch error { switch error {
case .socketActivity, .timeout: case .socketActivity, .timeout:
return V.timeout return V.timeout
case .dnsFailure: case .dnsFailure:
return V.dns return V.dns
case .tlsInitialization, .tlsServerVerification, .tlsHandshake: case .tlsInitialization, .tlsServerVerification, .tlsHandshake:
return V.tls return V.tls
case .authentication: case .authentication:
return V.auth return V.auth
case .encryptionInitialization, .encryptionData: case .encryptionInitialization, .encryptionData:
return V.encryption return V.encryption
case .serverCompression, .lzo: case .serverCompression, .lzo:
return V.compression return V.compression
case .networkChanged: case .networkChanged:
return V.network return V.network
case .routing: case .routing:
return V.routing return V.routing
case .gatewayUnattainable: case .gatewayUnattainable:
return V.gateway return V.gateway
case .serverShutdown: case .serverShutdown:
return V.shutdown return V.shutdown
@ -184,7 +184,7 @@ extension Error {
pp_log.error("Could not parse configuration URL: \(localizedDescription)") pp_log.error("Could not parse configuration URL: \(localizedDescription)")
return L10n.Tunnelkit.Errors.parsing(localizedDescription) return L10n.Tunnelkit.Errors.parsing(localizedDescription)
} }
private func ovpnErrorDescription(_ error: OpenVPN.ConfigurationError) -> String { private func ovpnErrorDescription(_ error: OpenVPN.ConfigurationError) -> String {
let V = L10n.Tunnelkit.Errors.Openvpn.self let V = L10n.Tunnelkit.Errors.Openvpn.self
switch error { switch error {
@ -203,7 +203,7 @@ extension Error {
case .missingConfiguration(let option): case .missingConfiguration(let option):
pp_log.error("Could not parse configuration URL: missing configuration, \(option)") pp_log.error("Could not parse configuration URL: missing configuration, \(option)")
return V.requiredOption(option) return V.requiredOption(option)
case .unsupportedConfiguration(var option): case .unsupportedConfiguration(var option):
if option.contains("external") { if option.contains("external") {
option.append(" (see FAQ)") option.append(" (see FAQ)")

View File

@ -28,24 +28,24 @@ import PassepartoutLibrary
enum Unlocalized { enum Unlocalized {
static let appName = Constants.Global.appName static let appName = Constants.Global.appName
enum Placeholders { enum Placeholders {
static let empty = "" static let empty = ""
static let address = "0.0.0.0" static let address = "0.0.0.0"
static let port = "8080" static let port = "8080"
static let hostname = "example.com" static let hostname = "example.com"
static let dohURL = "https://example.com/dns-query" static let dohURL = "https://example.com/dns-query"
static let dotServerName = hostname static let dotServerName = hostname
static let dnsAddress = address static let dnsAddress = address
static let dnsDomain = hostname static let dnsDomain = hostname
static let pacURL = "https://proxy/auto-conf" static let pacURL = "https://proxy/auto-conf"
static let proxyBypassDomain = hostname static let proxyBypassDomain = hostname
@ -54,24 +54,24 @@ enum Unlocalized {
enum DNS { enum DNS {
static let plain = "Cleartext" static let plain = "Cleartext"
} }
enum Keychain { enum Keychain {
static func passwordLabel(_ profileName: String, vpnProtocol: VPNProtocolType) -> String { static func passwordLabel(_ profileName: String, vpnProtocol: VPNProtocolType) -> String {
"\(Constants.Global.appName): \(profileName) (\(vpnProtocol.description))" "\(Constants.Global.appName): \(profileName) (\(vpnProtocol.description))"
} }
} }
enum Issues { enum Issues {
static let recipient = "issues@\(Constants.Domain.name)" static let recipient = "issues@\(Constants.Domain.name)"
static let subject = "\(appName) - Report issue" static let subject = "\(appName) - Report issue"
static func body(_ description: String, _ metadata: String) -> String { static func body(_ description: String, _ metadata: String) -> String {
"Hi,\n\n\(description)\n\n\(metadata)\n\nRegards" "Hi,\n\n\(description)\n\n\(metadata)\n\nRegards"
} }
static let template = "description of the issue: " static let template = "description of the issue: "
static let maxLogBytes = UInt64(20000) static let maxLogBytes = UInt64(20000)
enum Filenames { enum Filenames {
@ -81,13 +81,13 @@ enum Unlocalized {
let iso = fmt.string(from: Date()) let iso = fmt.string(from: Date())
return "debug-\(iso).txt" return "debug-\(iso).txt"
} }
static let configuration = "profile.ovpn" static let configuration = "profile.ovpn"
// static let configuration = "profile.ovpn.txt" // static let configuration = "profile.ovpn.txt"
static let template = "description of the issue: " static let template = "description of the issue: "
} }
enum MIME { enum MIME {
static let debugLog = "text/plain" static let debugLog = "text/plain"
@ -98,9 +98,9 @@ enum Unlocalized {
enum Social { enum Social {
static let reddit = "Reddit" static let reddit = "Reddit"
private static let twitterHashtags = ["OpenVPN", "WireGuard", "iOS", "macOS"] private static let twitterHashtags = ["OpenVPN", "WireGuard", "iOS", "macOS"]
static func twitterIntent(withMessage message: String) -> URL { static func twitterIntent(withMessage message: String) -> URL {
var text = message var text = message
for ht in twitterHashtags { for ht in twitterHashtags {
@ -121,7 +121,7 @@ enum Unlocalized {
static let recipient = "translate@\(Constants.Domain.name)" static let recipient = "translate@\(Constants.Domain.name)"
static let subject = "\(appName) - Translations" static let subject = "\(appName) - Translations"
static func body(_ description: String) -> String { static func body(_ description: String) -> String {
"Hi,\n\n\(description)\n\nRegards" "Hi,\n\n\(description)\n\nRegards"
} }
@ -148,9 +148,9 @@ enum Unlocalized {
enum Credits { enum Credits {
typealias License = (String, String, URL) typealias License = (String, String, URL)
typealias Notice = (String, String) typealias Notice = (String, String)
static let author = "Davide De Rosa" static let author = "Davide De Rosa"
static let licenses: [License] = [( static let licenses: [License] = [(
@ -178,7 +178,7 @@ enum Unlocalized {
"MIT", "MIT",
URL(string: "https://raw.githubusercontent.com/SwiftyBeaver/SwiftyBeaver/master/LICENSE")! URL(string: "https://raw.githubusercontent.com/SwiftyBeaver/SwiftyBeaver/master/LICENSE")!
)] )]
static let notices: [Notice] = [( static let notices: [Notice] = [(
"Circle Icons", "Circle Icons",
"The logo is taken from the awesome Circle Icons set by Nick Roach." "The logo is taken from the awesome Circle Icons set by Nick Roach."
@ -196,63 +196,63 @@ enum Unlocalized {
enum About { enum About {
static let github = "GitHub" static let github = "GitHub"
static let readme = "README" static let readme = "README"
static let changelog = "CHANGELOG" static let changelog = "CHANGELOG"
static let faq = "FAQ" static let faq = "FAQ"
} }
enum VPN { enum VPN {
static let vpn = "VPN" static let vpn = "VPN"
static let certificateAuthority = "CA" static let certificateAuthority = "CA"
static let xor = "XOR" static let xor = "XOR"
} }
enum OpenVPN { enum OpenVPN {
static let compLZO = "--comp-lzo" static let compLZO = "--comp-lzo"
static let compress = "--compress" static let compress = "--compress"
static let lzo = "LZO" static let lzo = "LZO"
enum XOR: String { enum XOR: String {
case xormask case xormask
case xorptrpos case xorptrpos
case reverse case reverse
case obfuscate case obfuscate
} }
} }
enum Network { enum Network {
static let dns = "DNS" static let dns = "DNS"
static let tls = "TLS" static let tls = "TLS"
static let https = "HTTPS" static let https = "HTTPS"
static let url = "URL" static let url = "URL"
static let mtu = "MTU" static let mtu = "MTU"
static let ipv4 = "IPv4" static let ipv4 = "IPv4"
static let ipv6 = "IPv6" static let ipv6 = "IPv6"
static let ssid = "SSID" static let ssid = "SSID"
static let proxyAutoConfiguration = "PAC" static let proxyAutoConfiguration = "PAC"
} }
enum Other { enum Other {
static let siri = "Siri" static let siri = "Siri"
static let totp = "TOTP" static let totp = "TOTP"
} }
} }

View File

@ -28,8 +28,8 @@ import Foundation
@objc(MacBridge) @objc(MacBridge)
public protocol MacBridge: NSObjectProtocol { public protocol MacBridge: NSObjectProtocol {
init() init()
var utils: MacUtils { get } var utils: MacUtils { get }
var menu: MacMenu { get } var menu: MacMenu { get }
} }

View File

@ -28,7 +28,7 @@ import Foundation
@objc @objc
public protocol MacUtils { public protocol MacUtils {
var isStartedByLauncher: Bool { get } var isStartedByLauncher: Bool { get }
func sendAppToBackground() func sendAppToBackground()
} }

View File

@ -28,13 +28,13 @@ import Foundation
@objc(LightProfile) @objc(LightProfile)
public protocol LightProfile { public protocol LightProfile {
var id: UUID { get } var id: UUID { get }
var name: String { get } var name: String { get }
var vpnProtocol: String { get } var vpnProtocol: String { get }
var isActive: Bool { get } var isActive: Bool { get }
var providerName: String? { get } var providerName: String? { get }
var providerServer: LightProviderServer? { get } var providerServer: LightProviderServer? { get }
@ -50,13 +50,13 @@ extension LightProfile {
@objc @objc
public protocol LightProfileManager { public protocol LightProfileManager {
var hasProfiles: Bool { get } var hasProfiles: Bool { get }
var profiles: [LightProfile] { get } var profiles: [LightProfile] { get }
var activeProfileId: UUID? { get } var activeProfileId: UUID? { get }
var activeProfileName: String? { get } var activeProfileName: String? { get }
var delegate: LightProfileManagerDelegate? { get set } var delegate: LightProfileManagerDelegate? { get set }
} }

View File

@ -39,20 +39,20 @@ public protocol LightProviderLocation {
var id: String { get } var id: String { get }
var countryCode: String { get } var countryCode: String { get }
var servers: [LightProviderServer] { get } var servers: [LightProviderServer] { get }
} }
@objc(LightProviderServer) @objc(LightProviderServer)
public protocol LightProviderServer { public protocol LightProviderServer {
var description: String { get } var description: String { get }
var longDescription: String { get } var longDescription: String { get }
var categoryName: String { get } var categoryName: String { get }
var locationId: String { get } var locationId: String { get }
var serverId: String { get } var serverId: String { get }
} }
@ -60,7 +60,7 @@ public protocol LightProviderServer {
@objc @objc
public protocol LightProviderManager { public protocol LightProviderManager {
var delegate: LightProviderManagerDelegate? { get set } var delegate: LightProviderManagerDelegate? { get set }
func categories(_ name: String, vpnProtocol: String) -> [LightProviderCategory] func categories(_ name: String, vpnProtocol: String) -> [LightProviderCategory]
func downloadIfNeeded(_ name: String, vpnProtocol: String) func downloadIfNeeded(_ name: String, vpnProtocol: String)

View File

@ -28,6 +28,6 @@ import Foundation
@objc @objc
public protocol LightUtils { public protocol LightUtils {
var launchesOnLogin: Bool { get set } var launchesOnLogin: Bool { get set }
func requestScene() func requestScene()
} }

View File

@ -28,11 +28,11 @@ import Foundation
@objc(LightVPNStatus) @objc(LightVPNStatus)
public enum LightVPNStatus: Int { public enum LightVPNStatus: Int {
case connecting case connecting
case connected case connected
case disconnecting case disconnecting
case disconnected case disconnected
} }
@ -42,19 +42,19 @@ public protocol LightVPNManager {
var isEnabled: Bool { get } var isEnabled: Bool { get }
var vpnStatus: LightVPNStatus { get } var vpnStatus: LightVPNStatus { get }
func connect(with profileId: UUID) func connect(with profileId: UUID)
func connect(with profileId: UUID, to serverId: String) func connect(with profileId: UUID, to serverId: String)
func disconnect() func disconnect()
func toggle() func toggle()
func reconnect() func reconnect()
func addDelegate(_ delegate: LightVPNManagerDelegate, withIdentifier identifier: String) func addDelegate(_ delegate: LightVPNManagerDelegate, withIdentifier identifier: String)
func removeDelegate(withIdentifier identifier: String) func removeDelegate(withIdentifier identifier: String)
} }

View File

@ -28,10 +28,10 @@ import Foundation
@objc(MenuBuilder) @objc(MenuBuilder)
public protocol MenuBuilder: NSObjectProtocol { public protocol MenuBuilder: NSObjectProtocol {
weak var delegate: MenuDelegate? { get set } weak var delegate: MenuDelegate? { get set }
init() init()
func sendAppToBackground() func sendAppToBackground()
func buildMenu() func buildMenu()
} }

View File

@ -28,7 +28,7 @@ import AppKit
class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
private let appURL = Constants.Launcher.appURL private let appURL = Constants.Launcher.appURL
private var isAppRunning: Bool { private var isAppRunning: Bool {
NSWorkspace.shared.runningApplications.contains { NSWorkspace.shared.runningApplications.contains {
$0.bundleIdentifier == Constants.Launcher.appId $0.bundleIdentifier == Constants.Launcher.appId
@ -40,12 +40,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
NSApp.terminate(self) NSApp.terminate(self)
return return
} }
let cfg = NSWorkspace.OpenConfiguration() let cfg = NSWorkspace.OpenConfiguration()
cfg.hides = true cfg.hides = true
cfg.activates = false cfg.activates = false
cfg.addsToRecentItems = false cfg.addsToRecentItems = false
NSWorkspace.shared.openApplication(at: appURL, configuration: cfg) { app, error in NSWorkspace.shared.openApplication(at: appURL, configuration: cfg) { _, error in
if let error = error { if let error = error {
NSLog("Unable to launch main app: \(error)") NSLog("Unable to launch main app: \(error)")
return return

View File

@ -30,7 +30,7 @@ extension Constants {
static var bundle: Bundle { static var bundle: Bundle {
Bundle(for: PassepartoutMac.self) Bundle(for: PassepartoutMac.self)
} }
static let appLauncherId: String = bundleConfig("launcher_id", in: bundle) static let appLauncherId: String = bundleConfig("launcher_id", in: bundle)
} }
} }

View File

@ -52,7 +52,7 @@ extension LightVPNStatus {
switch self { switch self {
case .connected, .disconnected: case .connected, .disconnected:
resourceName = "StatusActive" resourceName = "StatusActive"
case .connecting, .disconnecting: case .connecting, .disconnecting:
resourceName = "StatusPending" resourceName = "StatusPending"
} }
@ -61,12 +61,12 @@ extension LightVPNStatus {
} }
return image return image
} }
var imageAlpha: Double { var imageAlpha: Double {
switch self { switch self {
case .disconnected: case .disconnected:
return 0.5 return 0.5
default: default:
return 1.0 return 1.0
} }

View File

@ -27,14 +27,14 @@ import Foundation
class DefaultMacMenu: MacMenu { class DefaultMacMenu: MacMenu {
weak var delegate: MacMenuDelegate? weak var delegate: MacMenuDelegate?
private lazy var menu: PassepartoutMenu = { private lazy var menu: PassepartoutMenu = {
guard let delegate = delegate else { guard let delegate = delegate else {
fatalError("Must set MacMenu.delegate") fatalError("Must set MacMenu.delegate")
} }
return PassepartoutMenu(macMenuDelegate: delegate) return PassepartoutMenu(macMenuDelegate: delegate)
}() }()
func install() { func install() {
menu.install() menu.install()
} }

Some files were not shown because too many files have changed in this diff Show More