Refactor AppUI for TV target (#775)
Split AppUI into AppUI and AppUIMain to allow for a new, simplified AppUITV target tailored for the Apple TV. As a PoC, present a view with a list of the shared profiles.
This commit is contained in:
parent
8536aee755
commit
944d6f8c28
|
@ -23,12 +23,14 @@
|
|||
0EC797422B9378E000C093B7 /* Shared+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC797402B9378E000C093B7 /* Shared+App.swift */; };
|
||||
0EC797432B9378E000C093B7 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC797412B9378E000C093B7 /* Shared.swift */; };
|
||||
0EC797442B93790600C093B7 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC797412B9378E000C093B7 /* Shared.swift */; };
|
||||
0EC9C0232CA5BD0B00C52954 /* AppUI in Frameworks */ = {isa = PBXBuildFile; productRef = 0EC9C0222CA5BD0B00C52954 /* AppUI */; };
|
||||
0ED61CF82CD0418C008FE259 /* AppDelegate+macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED61CF72CD0418C008FE259 /* AppDelegate+macOS.swift */; };
|
||||
0ED61CFA2CD04192008FE259 /* AppDelegate+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED61CF92CD04192008FE259 /* AppDelegate+iOS.swift */; };
|
||||
0ED61CF82CD0418C008FE259 /* App+macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED61CF72CD0418C008FE259 /* App+macOS.swift */; };
|
||||
0ED61CFA2CD04192008FE259 /* App+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED61CF92CD04192008FE259 /* App+iOS.swift */; };
|
||||
0EDE56EA2CABE40D0082D21C /* Intents.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0EDE56E62CABE40D0082D21C /* Intents.plist */; };
|
||||
0EDE56FA2CABE42E0082D21C /* PassepartoutIntents.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 0EDE56F02CABE42E0082D21C /* PassepartoutIntents.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
0EDE57002CABE4B50082D21C /* IntentsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE56E72CABE40D0082D21C /* IntentsExtension.swift */; };
|
||||
0EE8D7DD2CD1107E00F6600C /* AppUIMain in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, macos, ); productRef = 0EE8D7DC2CD1107E00F6600C /* AppUIMain */; };
|
||||
0EE8D7DF2CD1108900F6600C /* AppUITV in Frameworks */ = {isa = PBXBuildFile; platformFilters = (tvos, ); productRef = 0EE8D7DE2CD1108900F6600C /* AppUITV */; };
|
||||
0EE8D7E12CD112C200F6600C /* App+tvOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE8D7E02CD112C200F6600C /* App+tvOS.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -115,12 +117,13 @@
|
|||
0EC797402B9378E000C093B7 /* Shared+App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Shared+App.swift"; sourceTree = "<group>"; };
|
||||
0EC797412B9378E000C093B7 /* Shared.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = "<group>"; };
|
||||
0ED1EFDA2C33059600CBD9BD /* App.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = App.plist; sourceTree = "<group>"; };
|
||||
0ED61CF72CD0418C008FE259 /* AppDelegate+macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+macOS.swift"; sourceTree = "<group>"; };
|
||||
0ED61CF92CD04192008FE259 /* AppDelegate+iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+iOS.swift"; sourceTree = "<group>"; };
|
||||
0ED61CF72CD0418C008FE259 /* App+macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+macOS.swift"; sourceTree = "<group>"; };
|
||||
0ED61CF92CD04192008FE259 /* App+iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+iOS.swift"; sourceTree = "<group>"; };
|
||||
0EDE56E52CABE40D0082D21C /* Intents.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Intents.entitlements; sourceTree = "<group>"; };
|
||||
0EDE56E62CABE40D0082D21C /* Intents.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Intents.plist; sourceTree = "<group>"; };
|
||||
0EDE56E72CABE40D0082D21C /* IntentsExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntentsExtension.swift; sourceTree = "<group>"; };
|
||||
0EDE56F02CABE42E0082D21C /* PassepartoutIntents.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.extensionkit-extension"; includeInIndex = 0; path = PassepartoutIntents.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0EE8D7E02CD112C200F6600C /* App+tvOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+tvOS.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -144,7 +147,8 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0EC9C0232CA5BD0B00C52954 /* AppUI in Frameworks */,
|
||||
0EE8D7DF2CD1108900F6600C /* AppUITV in Frameworks */,
|
||||
0EE8D7DD2CD1107E00F6600C /* AppUIMain in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -214,8 +218,7 @@
|
|||
0E7E3D5A2B9345FD002BBDB4 /* App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0ED61CF52CD0416A008FE259 /* iOS */,
|
||||
0ED61CF62CD04174008FE259 /* macOS */,
|
||||
0ED61CF62CD04174008FE259 /* Platforms */,
|
||||
0ED1EFDA2C33059600CBD9BD /* App.plist */,
|
||||
0E7E3D5B2B9345FD002BBDB4 /* App.entitlements */,
|
||||
0E7C3CCC2C9AF44600B72E69 /* AppDelegate.swift */,
|
||||
|
@ -247,20 +250,14 @@
|
|||
path = Tunnel;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0ED61CF52CD0416A008FE259 /* iOS */ = {
|
||||
0ED61CF62CD04174008FE259 /* Platforms */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0ED61CF92CD04192008FE259 /* AppDelegate+iOS.swift */,
|
||||
0ED61CF92CD04192008FE259 /* App+iOS.swift */,
|
||||
0ED61CF72CD0418C008FE259 /* App+macOS.swift */,
|
||||
0EE8D7E02CD112C200F6600C /* App+tvOS.swift */,
|
||||
);
|
||||
path = iOS;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0ED61CF62CD04174008FE259 /* macOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0ED61CF72CD0418C008FE259 /* AppDelegate+macOS.swift */,
|
||||
);
|
||||
path = macOS;
|
||||
path = Platforms;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0EDE56E82CABE40D0082D21C /* Intents */ = {
|
||||
|
@ -298,7 +295,8 @@
|
|||
);
|
||||
name = Passepartout;
|
||||
packageProductDependencies = (
|
||||
0EC9C0222CA5BD0B00C52954 /* AppUI */,
|
||||
0EE8D7DC2CD1107E00F6600C /* AppUIMain */,
|
||||
0EE8D7DE2CD1108900F6600C /* AppUITV */,
|
||||
);
|
||||
productName = PassepartoutKit;
|
||||
productReference = 0E06D18F2B87629100176E1D /* Passepartout.app */;
|
||||
|
@ -466,11 +464,12 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0ED61CF82CD0418C008FE259 /* AppDelegate+macOS.swift in Sources */,
|
||||
0ED61CF82CD0418C008FE259 /* App+macOS.swift in Sources */,
|
||||
0E7C3CCD2C9AF44600B72E69 /* AppDelegate.swift in Sources */,
|
||||
0ED61CFA2CD04192008FE259 /* AppDelegate+iOS.swift in Sources */,
|
||||
0ED61CFA2CD04192008FE259 /* App+iOS.swift in Sources */,
|
||||
0E7E3D6B2B9345FD002BBDB4 /* PassepartoutApp.swift in Sources */,
|
||||
0EC797422B9378E000C093B7 /* Shared+App.swift in Sources */,
|
||||
0EE8D7E12CD112C200F6600C /* App+tvOS.swift in Sources */,
|
||||
0EC797432B9378E000C093B7 /* Shared.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -621,7 +620,7 @@
|
|||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
|
@ -697,7 +696,7 @@
|
|||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
|
@ -1001,9 +1000,13 @@
|
|||
isa = XCSwiftPackageProductDependency;
|
||||
productName = TunnelLibrary;
|
||||
};
|
||||
0EC9C0222CA5BD0B00C52954 /* AppUI */ = {
|
||||
0EE8D7DC2CD1107E00F6600C /* AppUIMain */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = AppUI;
|
||||
productName = AppUIMain;
|
||||
};
|
||||
0EE8D7DE2CD1108900F6600C /* AppUITV */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = AppUITV;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
|
|
|
@ -23,7 +23,12 @@
|
|||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import AppUI
|
||||
#if os(iOS) || os(macOS)
|
||||
import AppUIMain
|
||||
#elseif os(tvOS)
|
||||
import AppUITV
|
||||
#endif
|
||||
|
||||
import CommonLibrary
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
@ -31,56 +36,34 @@ import SwiftUI
|
|||
@main
|
||||
struct PassepartoutApp: App {
|
||||
|
||||
#if os(iOS)
|
||||
#if os(iOS) || os(tvOS)
|
||||
|
||||
@UIApplicationDelegateAdaptor
|
||||
private var appDelegate: AppDelegate
|
||||
#else
|
||||
|
||||
#elseif os(macOS)
|
||||
|
||||
@NSApplicationDelegateAdaptor
|
||||
private var appDelegate: AppDelegate
|
||||
|
||||
#endif
|
||||
|
||||
@Environment(\.scenePhase)
|
||||
private var scenePhase
|
||||
|
||||
private var context: AppContext {
|
||||
@StateObject
|
||||
var theme = Theme()
|
||||
}
|
||||
|
||||
extension PassepartoutApp {
|
||||
var appName: String {
|
||||
BundleConfiguration.mainDisplayName
|
||||
}
|
||||
|
||||
var context: AppContext {
|
||||
appDelegate.context
|
||||
}
|
||||
|
||||
private let appName = BundleConfiguration.mainDisplayName
|
||||
|
||||
@StateObject
|
||||
private var theme = Theme()
|
||||
|
||||
#if os(iOS)
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
contentView()
|
||||
.onOpenURL { url in
|
||||
ImporterPipe.shared.send([url])
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
var body: some Scene {
|
||||
Window(appName, id: appName, content: contentView)
|
||||
.defaultSize(width: 600, height: 400)
|
||||
|
||||
Settings {
|
||||
SettingsView(profileManager: context.profileManager)
|
||||
.frame(minWidth: 300, minHeight: 200)
|
||||
}
|
||||
MenuBarExtra {
|
||||
AppMenu()
|
||||
.withEnvironment(from: context, theme: theme)
|
||||
} label: {
|
||||
AppMenuImage(connectionObserver: context.connectionObserver)
|
||||
.environmentObject(theme)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private extension PassepartoutApp {
|
||||
func contentView() -> some View {
|
||||
AppCoordinator(
|
||||
profileManager: context.profileManager,
|
||||
|
@ -103,7 +86,6 @@ private extension PassepartoutApp {
|
|||
break
|
||||
}
|
||||
}
|
||||
.themeLockScreen()
|
||||
.withEnvironment(from: context, theme: theme)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// App+iOS.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 10/28/24.
|
||||
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||
//
|
||||
// https://github.com/passepartoutvpn
|
||||
//
|
||||
// This file is part of Passepartout.
|
||||
//
|
||||
// Passepartout is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Passepartout is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
import AppUIMain
|
||||
import SwiftUI
|
||||
|
||||
extension AppDelegate: UIApplicationDelegate {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||
configure()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension PassepartoutApp {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
contentView()
|
||||
.onOpenURL { url in
|
||||
ImporterPipe.shared.send([url])
|
||||
}
|
||||
.themeLockScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// AppDelegate+macOS.swift
|
||||
// App+macOS.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 10/28/24.
|
||||
|
@ -25,9 +25,9 @@
|
|||
|
||||
#if os(macOS)
|
||||
|
||||
import AppKit
|
||||
import AppUI
|
||||
import AppUIMain
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
extension AppDelegate: NSApplicationDelegate {
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
|
@ -82,4 +82,28 @@ private extension AppDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
extension PassepartoutApp {
|
||||
|
||||
@SceneBuilder
|
||||
var body: some Scene {
|
||||
Window(appName, id: appName) {
|
||||
contentView()
|
||||
.themeLockScreen()
|
||||
}
|
||||
.defaultSize(width: 600, height: 400)
|
||||
|
||||
Settings {
|
||||
SettingsView(profileManager: context.profileManager)
|
||||
.frame(minWidth: 300, minHeight: 200)
|
||||
}
|
||||
MenuBarExtra {
|
||||
AppMenu()
|
||||
.withEnvironment(from: context, theme: theme)
|
||||
} label: {
|
||||
AppMenuImage(connectionObserver: context.connectionObserver)
|
||||
.environmentObject(theme)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,8 +1,8 @@
|
|||
//
|
||||
// AppDelegate+iOS.swift
|
||||
// App+tvOS.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 10/28/24.
|
||||
// Created by Davide De Rosa on 10/29/24.
|
||||
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||
//
|
||||
// https://github.com/passepartoutvpn
|
||||
|
@ -23,10 +23,10 @@
|
|||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#if os(iOS)
|
||||
#if os(tvOS)
|
||||
|
||||
import AppUI
|
||||
import UIKit
|
||||
import AppUITV
|
||||
import SwiftUI
|
||||
|
||||
extension AppDelegate: UIApplicationDelegate {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||
|
@ -35,4 +35,10 @@ extension AppDelegate: UIApplicationDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
extension PassepartoutApp {
|
||||
var body: some Scene {
|
||||
WindowGroup(content: contentView)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "dtfoundation",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Cocoanetics/DTFoundation.git",
|
||||
"state" : {
|
||||
"revision" : "a61be65dd7d5b2cde3acabd13bf320b71f2907a5",
|
||||
"version" : "1.7.19"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "generic-json-swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zoul/generic-json-swift",
|
||||
"state" : {
|
||||
"revision" : "0a06575f4038b504e78ac330913d920f1630f510",
|
||||
"version" : "2.0.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "kvitto",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Cocoanetics/Kvitto",
|
||||
"state" : {
|
||||
"revision" : "88888674d772ddcf19671159ed0022cb0bc37be2",
|
||||
"version" : "1.0.6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "openssl-apple",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/passepartoutvpn/openssl-apple",
|
||||
"state" : {
|
||||
"revision" : "0edc07c7a0e4ec2ca0f448dd68314241ccc925b3",
|
||||
"version" : "3.2.107"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "passepartoutkit-source",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
|
||||
"state" : {
|
||||
"revision" : "31aff403169c7cebe91a07fb8d225ab844a9a9ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "passepartoutkit-source-openvpn-openssl",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl",
|
||||
"state" : {
|
||||
"revision" : "3e687d2348e8e1cbc214e260df73890d6420b4ec",
|
||||
"version" : "0.9.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "passepartoutkit-source-wireguard-go",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source-wireguard-go",
|
||||
"state" : {
|
||||
"revision" : "8d142c806fb7dc4a2cd754d38d99da0d6398b811",
|
||||
"version" : "0.9.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "wg-go-apple",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/passepartoutvpn/wg-go-apple",
|
||||
"state" : {
|
||||
"revision" : "860e82efaf261da37483a5f51555be83e5a79ad3",
|
||||
"version" : "0.0.20240714"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "wireguard-apple",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/passepartoutvpn/wireguard-apple",
|
||||
"state" : {
|
||||
"revision" : "a896f784bc5ed94f29d97e376be5cfa08d4a5d44",
|
||||
"version" : "1.1.1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
|
@ -21,6 +21,14 @@ let package = Package(
|
|||
name: "AppUI",
|
||||
targets: ["AppUI"]
|
||||
),
|
||||
.library(
|
||||
name: "AppUIMain",
|
||||
targets: ["AppUIMain"]
|
||||
),
|
||||
.library(
|
||||
name: "AppUITV",
|
||||
targets: ["AppUITV"]
|
||||
),
|
||||
.library(
|
||||
name: "TunnelLibrary",
|
||||
targets: ["CommonLibrary"]
|
||||
|
@ -78,13 +86,26 @@ let package = Package(
|
|||
"AppDataProviders",
|
||||
"AppLibrary",
|
||||
"Kvitto",
|
||||
"LegacyV2",
|
||||
"UtilsLibrary"
|
||||
],
|
||||
resources: [
|
||||
.process("Resources")
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "AppUIMain",
|
||||
dependencies: [
|
||||
"AppUI",
|
||||
"LegacyV2"
|
||||
],
|
||||
resources: [
|
||||
.process("Resources")
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "AppUITV",
|
||||
dependencies: ["AppUI"]
|
||||
),
|
||||
.target(
|
||||
name: "CommonLibrary",
|
||||
dependencies: [
|
||||
|
@ -114,6 +135,10 @@ let package = Package(
|
|||
name: "AppLibraryTests",
|
||||
dependencies: ["AppLibrary"]
|
||||
),
|
||||
.testTarget(
|
||||
name: "AppUIMainTests",
|
||||
dependencies: ["AppUIMain"]
|
||||
),
|
||||
.testTarget(
|
||||
name: "AppUITests",
|
||||
dependencies: ["AppUI"]
|
||||
|
|
|
@ -24,9 +24,12 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
import PassepartoutKit
|
||||
|
||||
public protocol AppUIConfiguring {
|
||||
static func configure(with context: AppContext)
|
||||
}
|
||||
|
||||
public enum AppUI {
|
||||
public static func configure(with context: AppContext) {
|
||||
assertMissingModuleImplementations()
|
||||
|
@ -36,29 +39,13 @@ public enum AppUI {
|
|||
}
|
||||
}
|
||||
|
||||
private extension AppUI {
|
||||
static func assertMissingModuleImplementations() {
|
||||
let providerModuleTypes: Set<ModuleType> = [
|
||||
.openVPN
|
||||
]
|
||||
extension AppUI {
|
||||
public static func assertMissingModuleImplementations() {
|
||||
ModuleType.allCases.forEach { moduleType in
|
||||
let builder = moduleType.newModule()
|
||||
guard builder is ModuleTypeProviding else {
|
||||
fatalError("\(moduleType): is not ModuleTypeProviding")
|
||||
}
|
||||
guard builder is any ModuleViewProviding else {
|
||||
fatalError("\(moduleType): is not ModuleViewProviding")
|
||||
}
|
||||
if providerModuleTypes.contains(moduleType) {
|
||||
do {
|
||||
let module = try builder.tryBuild()
|
||||
guard module is any ProviderEntityViewProviding else {
|
||||
fatalError("\(moduleType): is not ProviderEntityViewProviding")
|
||||
}
|
||||
} catch {
|
||||
fatalError("\(moduleType): empty module is not buildable")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import PassepartoutKit
|
|||
|
||||
@MainActor
|
||||
public final class ConnectionObserver: ObservableObject {
|
||||
let tunnel: Tunnel
|
||||
public let tunnel: Tunnel
|
||||
|
||||
private let environment: TunnelEnvironment
|
||||
|
||||
|
|
|
@ -30,21 +30,21 @@ import Foundation
|
|||
import PassepartoutKit
|
||||
|
||||
@MainActor
|
||||
final class ProfileEditor: ObservableObject {
|
||||
public final class ProfileEditor: ObservableObject {
|
||||
|
||||
@Published
|
||||
private var editableProfile: EditableProfile
|
||||
|
||||
@Published
|
||||
var isShared: Bool
|
||||
public var isShared: Bool
|
||||
|
||||
private(set) var removedModules: [UUID: any ModuleBuilder]
|
||||
|
||||
convenience init() {
|
||||
public convenience init() {
|
||||
self.init(modules: [])
|
||||
}
|
||||
|
||||
init(modules: [any ModuleBuilder]) {
|
||||
public init(modules: [any ModuleBuilder]) {
|
||||
editableProfile = EditableProfile(
|
||||
modules: modules,
|
||||
activeModulesIds: Set(modules.map(\.id))
|
||||
|
@ -53,13 +53,13 @@ final class ProfileEditor: ObservableObject {
|
|||
removedModules = [:]
|
||||
}
|
||||
|
||||
init(profile: Profile) {
|
||||
public init(profile: Profile) {
|
||||
editableProfile = profile.editable()
|
||||
isShared = false
|
||||
removedModules = [:]
|
||||
}
|
||||
|
||||
func editProfile(_ profile: Profile, isShared: Bool) {
|
||||
public func editProfile(_ profile: Profile, isShared: Bool) {
|
||||
editableProfile = profile.editable()
|
||||
self.isShared = isShared
|
||||
removedModules = [:]
|
||||
|
@ -69,7 +69,7 @@ final class ProfileEditor: ObservableObject {
|
|||
// MARK: - Types
|
||||
|
||||
extension ProfileEditor {
|
||||
var moduleTypes: [ModuleType] {
|
||||
public var moduleTypes: [ModuleType] {
|
||||
editableProfile.modules
|
||||
.compactMap {
|
||||
$0 as? ModuleTypeProviding
|
||||
|
@ -77,7 +77,7 @@ extension ProfileEditor {
|
|||
.map(\.moduleType)
|
||||
}
|
||||
|
||||
var availableModuleTypes: [ModuleType] {
|
||||
public var availableModuleTypes: [ModuleType] {
|
||||
ModuleType
|
||||
.allCases
|
||||
.filter {
|
||||
|
@ -97,7 +97,7 @@ extension ProfileEditor {
|
|||
// MARK: - Editing
|
||||
|
||||
extension ProfileEditor {
|
||||
var profile: EditableProfile {
|
||||
public var profile: EditableProfile {
|
||||
get {
|
||||
editableProfile
|
||||
}
|
||||
|
@ -108,21 +108,21 @@ extension ProfileEditor {
|
|||
}
|
||||
|
||||
extension ProfileEditor {
|
||||
var modules: [any ModuleBuilder] {
|
||||
public var modules: [any ModuleBuilder] {
|
||||
editableProfile.modules
|
||||
}
|
||||
|
||||
func module(withId moduleId: UUID) -> (any ModuleBuilder)? {
|
||||
public func module(withId moduleId: UUID) -> (any ModuleBuilder)? {
|
||||
editableProfile.modules.first {
|
||||
$0.id == moduleId
|
||||
} ?? removedModules[moduleId]
|
||||
}
|
||||
|
||||
func isActiveModule(withId moduleId: UUID) -> Bool {
|
||||
public func isActiveModule(withId moduleId: UUID) -> Bool {
|
||||
editableProfile.isActiveModule(withId: moduleId)
|
||||
}
|
||||
|
||||
func toggleModule(withId moduleId: UUID) {
|
||||
public func toggleModule(withId moduleId: UUID) {
|
||||
guard let existingModule = module(withId: moduleId) else {
|
||||
return
|
||||
}
|
||||
|
@ -133,11 +133,11 @@ extension ProfileEditor {
|
|||
}
|
||||
}
|
||||
|
||||
func moveModules(from offsets: IndexSet, to newOffset: Int) {
|
||||
public func moveModules(from offsets: IndexSet, to newOffset: Int) {
|
||||
editableProfile.modules.move(fromOffsets: offsets, toOffset: newOffset)
|
||||
}
|
||||
|
||||
func removeModules(at offsets: IndexSet) {
|
||||
public func removeModules(at offsets: IndexSet) {
|
||||
offsets.forEach {
|
||||
let module = editableProfile.modules[$0]
|
||||
removedModules[module.id] = module
|
||||
|
@ -145,7 +145,7 @@ extension ProfileEditor {
|
|||
}
|
||||
}
|
||||
|
||||
func removeModule(withId moduleId: UUID) {
|
||||
public func removeModule(withId moduleId: UUID) {
|
||||
guard let index = editableProfile.modules.firstIndex(where: { $0.id == moduleId }) else {
|
||||
return
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ extension ProfileEditor {
|
|||
editableProfile.modules.remove(at: index)
|
||||
}
|
||||
|
||||
func saveModule(_ module: any ModuleBuilder, activating: Bool) {
|
||||
public func saveModule(_ module: any ModuleBuilder, activating: Bool) {
|
||||
if let index = editableProfile.modules.firstIndex(where: { $0.id == module.id }) {
|
||||
editableProfile.modules[index] = module
|
||||
} else {
|
||||
|
@ -175,7 +175,7 @@ private extension ProfileEditor {
|
|||
// MARK: - Building
|
||||
|
||||
extension ProfileEditor {
|
||||
func build() throws -> Profile {
|
||||
public func build() throws -> Profile {
|
||||
let builder = try editableProfile.builder()
|
||||
let profile = try builder.tryBuild()
|
||||
|
||||
|
@ -190,7 +190,7 @@ extension ProfileEditor {
|
|||
// MARK: - Saving
|
||||
|
||||
extension ProfileEditor {
|
||||
func save(to profileManager: ProfileManager) async throws {
|
||||
public func save(to profileManager: ProfileManager) async throws {
|
||||
do {
|
||||
let newProfile = try build()
|
||||
try await profileManager.save(newProfile, isShared: isShared)
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
import Foundation
|
||||
import PassepartoutKit
|
||||
|
||||
enum AppError {
|
||||
public enum AppError {
|
||||
case emptyProfileName
|
||||
|
||||
case malformedModule(any ModuleBuilder, error: Error)
|
||||
|
@ -35,7 +35,7 @@ enum AppError {
|
|||
|
||||
case generic(PassepartoutError)
|
||||
|
||||
init(_ error: Error) {
|
||||
public init(_ error: Error) {
|
||||
if let spError = error as? AppError {
|
||||
self = spError
|
||||
} else {
|
||||
|
|
|
@ -26,18 +26,18 @@
|
|||
import Foundation
|
||||
import PassepartoutKit
|
||||
|
||||
struct EditableProfile: MutableProfileType {
|
||||
var id = UUID()
|
||||
public struct EditableProfile: MutableProfileType {
|
||||
public var id = UUID()
|
||||
|
||||
var name: String = ""
|
||||
public var name: String = ""
|
||||
|
||||
var modules: [any ModuleBuilder] = []
|
||||
public var modules: [any ModuleBuilder] = []
|
||||
|
||||
var activeModulesIds: Set<UUID> = []
|
||||
public var activeModulesIds: Set<UUID> = []
|
||||
|
||||
var modulesMetadata: [UUID: ModuleMetadata]?
|
||||
public var modulesMetadata: [UUID: ModuleMetadata]?
|
||||
|
||||
func builder() throws -> Profile.Builder {
|
||||
public func builder() throws -> Profile.Builder {
|
||||
var builder = Profile.Builder(id: id)
|
||||
builder.modules = try modules.compactMap {
|
||||
do {
|
||||
|
@ -68,7 +68,7 @@ struct EditableProfile: MutableProfileType {
|
|||
}
|
||||
|
||||
extension Profile {
|
||||
func editable() -> EditableProfile {
|
||||
public func editable() -> EditableProfile {
|
||||
EditableProfile(
|
||||
id: id,
|
||||
name: name,
|
||||
|
@ -78,7 +78,7 @@ extension Profile {
|
|||
)
|
||||
}
|
||||
|
||||
var modulesBuilders: [any ModuleBuilder] {
|
||||
public var modulesBuilders: [any ModuleBuilder] {
|
||||
modules.compactMap {
|
||||
guard let buildableModule = $0 as? any BuildableType else {
|
||||
return nil
|
||||
|
|
|
@ -27,7 +27,7 @@ import Foundation
|
|||
import PassepartoutKit
|
||||
|
||||
extension ModuleType {
|
||||
func newModule() -> any ModuleBuilder {
|
||||
public func newModule() -> any ModuleBuilder {
|
||||
switch self {
|
||||
case .openVPN:
|
||||
return OpenVPNModule.Builder()
|
||||
|
|
|
@ -27,7 +27,7 @@ import Foundation
|
|||
import PassepartoutKit
|
||||
import PassepartoutWireGuardGo
|
||||
|
||||
enum ModuleType: String, CaseIterable {
|
||||
public enum ModuleType: String, CaseIterable {
|
||||
case openVPN
|
||||
|
||||
case wireGuard
|
||||
|
@ -42,7 +42,7 @@ enum ModuleType: String, CaseIterable {
|
|||
}
|
||||
|
||||
extension ModuleType: Identifiable {
|
||||
var id: String {
|
||||
public var id: String {
|
||||
rawValue
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,8 +26,13 @@
|
|||
import Foundation
|
||||
import PassepartoutKit
|
||||
|
||||
struct TunnelInstallation {
|
||||
let header: ProfileHeader
|
||||
public struct TunnelInstallation {
|
||||
public let header: ProfileHeader
|
||||
|
||||
let onDemand: Bool
|
||||
public let onDemand: Bool
|
||||
|
||||
public init(header: ProfileHeader, onDemand: Bool) {
|
||||
self.header = header
|
||||
self.onDemand = onDemand
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,17 +29,17 @@ import PassepartoutKit
|
|||
|
||||
@MainActor
|
||||
extension Tunnel {
|
||||
func install(_ profile: Profile, processor: ProfileProcessor) async throws {
|
||||
public func install(_ profile: Profile, processor: ProfileProcessor) async throws {
|
||||
let newProfile = try processor.processed(profile)
|
||||
try await install(newProfile, connect: false, title: processor.title)
|
||||
}
|
||||
|
||||
func connect(with profile: Profile, processor: ProfileProcessor) async throws {
|
||||
public func connect(with profile: Profile, processor: ProfileProcessor) async throws {
|
||||
let newProfile = try processor.processed(profile)
|
||||
try await install(newProfile, connect: true, title: processor.title)
|
||||
}
|
||||
|
||||
func currentLog(parameters: Constants.Log) async -> [String] {
|
||||
public func currentLog(parameters: Constants.Log) async -> [String] {
|
||||
let output = try? await sendMessage(.localLog(
|
||||
sinceLast: parameters.sinceLast,
|
||||
maxLevel: parameters.maxLevel
|
|
@ -43,7 +43,7 @@ public final class IAPManager: ObservableObject {
|
|||
|
||||
private var purchasedAppBuild: Int?
|
||||
|
||||
private(set) var purchasedProducts: Set<AppProduct>
|
||||
public private(set) var purchasedProducts: Set<AppProduct>
|
||||
|
||||
private var eligibleFeatures: Set<AppFeature>
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import PassepartoutKit
|
|||
import UtilsLibrary
|
||||
|
||||
extension AppError: LocalizedError {
|
||||
var errorDescription: String? {
|
||||
public var errorDescription: String? {
|
||||
let V = Strings.Errors.App.self
|
||||
switch self {
|
||||
case .emptyProfileName:
|
||||
|
|
|
@ -29,13 +29,13 @@ import PassepartoutKit
|
|||
extension ModuleBuilder {
|
||||
|
||||
@MainActor
|
||||
func description(inEditor editor: ProfileEditor) -> String {
|
||||
public func description(inEditor editor: ProfileEditor) -> String {
|
||||
editor.profile.displayName(forModuleWithId: id) ?? typeDescription
|
||||
}
|
||||
}
|
||||
|
||||
extension ModuleBuilder {
|
||||
var typeDescription: String {
|
||||
public var typeDescription: String {
|
||||
guard let providing = self as? ModuleTypeProviding else {
|
||||
return String(describing: self)
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import SwiftUI
|
|||
import UtilsLibrary
|
||||
|
||||
extension ErrorHandler {
|
||||
static func `default`() -> ErrorHandler {
|
||||
public static func `default`() -> ErrorHandler {
|
||||
ErrorHandler(
|
||||
defaultTitle: Strings.Unlocalized.appName,
|
||||
dismissTitle: Strings.Global.ok,
|
||||
|
|
|
@ -64,7 +64,7 @@ extension Date: StyledLocalizableEntity {
|
|||
}
|
||||
|
||||
extension UUID {
|
||||
var flatString: String {
|
||||
public var flatString: String {
|
||||
let str = uuidString.replacingOccurrences(of: "-", with: "")
|
||||
assert(str.count == 32)
|
||||
return str
|
||||
|
|
|
@ -27,9 +27,9 @@ import Foundation
|
|||
import PassepartoutKit
|
||||
|
||||
extension Strings {
|
||||
enum Unlocalized {
|
||||
enum OpenVPN {
|
||||
enum XOR: String {
|
||||
public enum Unlocalized {
|
||||
public enum OpenVPN {
|
||||
public enum XOR: String {
|
||||
case xormask
|
||||
|
||||
case xorptrpos
|
||||
|
@ -39,23 +39,23 @@ extension Strings {
|
|||
case obfuscate
|
||||
}
|
||||
|
||||
static let compLZO = "--comp-lzo"
|
||||
public static let compLZO = "--comp-lzo"
|
||||
|
||||
static let compress = "--compress"
|
||||
public static let compress = "--compress"
|
||||
|
||||
static let lzo = "LZO"
|
||||
public static let lzo = "LZO"
|
||||
}
|
||||
|
||||
enum Placeholders {
|
||||
static let hostname = "example.com"
|
||||
public enum Placeholders {
|
||||
public static let hostname = "example.com"
|
||||
|
||||
static let dohURL = "https://1.2.3.4/some-query"
|
||||
public static let dohURL = "https://1.2.3.4/some-query"
|
||||
|
||||
static let dotHostname = "dns-hostname.com"
|
||||
public static let dotHostname = "dns-hostname.com"
|
||||
|
||||
static let ipV4DNS = "1.1.1.1"
|
||||
public static let ipV4DNS = "1.1.1.1"
|
||||
|
||||
static func ipDestination(forFamily family: Address.Family) -> String {
|
||||
public static func ipDestination(forFamily family: Address.Family) -> String {
|
||||
switch family {
|
||||
case .v4:
|
||||
return "192.168.15.0/24"
|
||||
|
@ -65,7 +65,7 @@ extension Strings {
|
|||
}
|
||||
}
|
||||
|
||||
static func ipGateway(forFamily family: Address.Family) -> String {
|
||||
public static func ipGateway(forFamily family: Address.Family) -> String {
|
||||
switch family {
|
||||
case .v4:
|
||||
return "192.168.15.1"
|
||||
|
@ -75,67 +75,67 @@ extension Strings {
|
|||
}
|
||||
}
|
||||
|
||||
static let mtu = "1500"
|
||||
public static let mtu = "1500"
|
||||
|
||||
static let proxyIPv4Address = "192.168.1.1"
|
||||
public static let proxyIPv4Address = "192.168.1.1"
|
||||
|
||||
static let proxyPort = "1080"
|
||||
public static let proxyPort = "1080"
|
||||
|
||||
static let pacURL = "http://proxy.com/pac.url"
|
||||
public static let pacURL = "http://proxy.com/pac.url"
|
||||
}
|
||||
|
||||
enum Issues {
|
||||
static let subject = "\(appName) - Report issue"
|
||||
public enum Issues {
|
||||
public static let subject = "\(appName) - Report issue"
|
||||
|
||||
static let attachmentMimeType = "text/plain"
|
||||
public static let attachmentMimeType = "text/plain"
|
||||
|
||||
static let appLogFilename = "app.log"
|
||||
public static let appLogFilename = "app.log"
|
||||
|
||||
static let tunnelLogFilename = "tunnel.log"
|
||||
public static let tunnelLogFilename = "tunnel.log"
|
||||
}
|
||||
|
||||
static let appName = "Passepartout"
|
||||
public static let appName = "Passepartout"
|
||||
|
||||
static let ca = "CA"
|
||||
public static let ca = "CA"
|
||||
|
||||
static let dns = "DNS"
|
||||
public static let dns = "DNS"
|
||||
|
||||
static let faq = "FAQ"
|
||||
public static let faq = "FAQ"
|
||||
|
||||
static let http = "HTTP"
|
||||
public static let http = "HTTP"
|
||||
|
||||
static let https = "HTTPS"
|
||||
public static let https = "HTTPS"
|
||||
|
||||
static let httpProxy = "HTTP Proxy"
|
||||
public static let httpProxy = "HTTP Proxy"
|
||||
|
||||
static let iCloud = "iCloud"
|
||||
public static let iCloud = "iCloud"
|
||||
|
||||
static let ip = "IP"
|
||||
public static let ip = "IP"
|
||||
|
||||
static let ipv4 = "IPv4"
|
||||
public static let ipv4 = "IPv4"
|
||||
|
||||
static let ipv6 = "IPv6"
|
||||
public static let ipv6 = "IPv6"
|
||||
|
||||
static let mtu = "MTU"
|
||||
public static let mtu = "MTU"
|
||||
|
||||
static let openVPN = "OpenVPN"
|
||||
public static let openVPN = "OpenVPN"
|
||||
|
||||
static let otp = "OTP"
|
||||
public static let otp = "OTP"
|
||||
|
||||
static let pac = "PAC"
|
||||
public static let pac = "PAC"
|
||||
|
||||
static let proxy = "Proxy"
|
||||
public static let proxy = "Proxy"
|
||||
|
||||
static let tls = "TLS"
|
||||
public static let tls = "TLS"
|
||||
|
||||
static let url = "URL"
|
||||
public static let url = "URL"
|
||||
|
||||
static let uuid = "UUID"
|
||||
public static let uuid = "UUID"
|
||||
|
||||
static let wifi = "Wi-Fi"
|
||||
public static let wifi = "Wi-Fi"
|
||||
|
||||
static let wireGuard = "WireGuard"
|
||||
public static let wireGuard = "WireGuard"
|
||||
|
||||
static let xor = "XOR"
|
||||
public static let xor = "XOR"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ extension AppContext {
|
|||
profileManager: {
|
||||
let profiles: [Profile] = (0..<20)
|
||||
.reduce(into: []) { list, _ in
|
||||
list.append(.newProfile())
|
||||
list.append(.newMockProfile())
|
||||
}
|
||||
return ProfileManager(profiles: profiles)
|
||||
}(),
|
||||
|
@ -109,7 +109,7 @@ extension ProviderManager {
|
|||
// MARK: - Profile
|
||||
|
||||
extension Profile {
|
||||
static let mock: Profile = {
|
||||
public static let mock: Profile = {
|
||||
var profile = Profile.Builder()
|
||||
profile.name = "Mock profile"
|
||||
|
||||
|
@ -144,7 +144,7 @@ extension Profile {
|
|||
}
|
||||
}()
|
||||
|
||||
static func newProfile() -> Profile {
|
||||
public static func newMockProfile() -> Profile {
|
||||
do {
|
||||
var copy = mock.builder(withNewId: true)
|
||||
copy.name = String(copy.id.uuidString.prefix(8))
|
||||
|
|
|
@ -26,13 +26,13 @@
|
|||
import Foundation
|
||||
import PassepartoutKit
|
||||
|
||||
protocol TunnelContextProviding {
|
||||
public protocol TunnelContextProviding {
|
||||
var connectionObserver: ConnectionObserver { get }
|
||||
}
|
||||
|
||||
@MainActor
|
||||
extension TunnelContextProviding {
|
||||
var tunnelConnectionStatus: TunnelStatus {
|
||||
public var tunnelConnectionStatus: TunnelStatus {
|
||||
var status = connectionObserver.tunnel.status
|
||||
if status == .active, let connectionStatus = connectionObserver.connectionStatus {
|
||||
if connectionStatus == .connected {
|
|
@ -27,7 +27,7 @@ import AppLibrary
|
|||
import Foundation
|
||||
import PassepartoutKit
|
||||
|
||||
protocol TunnelInstallationProviding {
|
||||
public protocol TunnelInstallationProviding {
|
||||
var profileManager: ProfileManager { get }
|
||||
|
||||
var tunnel: Tunnel { get }
|
||||
|
@ -35,7 +35,7 @@ protocol TunnelInstallationProviding {
|
|||
|
||||
@MainActor
|
||||
extension TunnelInstallationProviding {
|
||||
var installation: TunnelInstallation? {
|
||||
public var installation: TunnelInstallation? {
|
||||
guard let currentProfile = tunnel.currentProfile else {
|
||||
return nil
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ extension TunnelInstallationProviding {
|
|||
return TunnelInstallation(header: header, onDemand: currentProfile.onDemand)
|
||||
}
|
||||
|
||||
var currentProfile: Profile? {
|
||||
public var currentProfile: Profile? {
|
||||
guard let id = tunnel.currentProfile?.id else {
|
||||
return nil
|
||||
}
|
|
@ -84,20 +84,20 @@ extension ThemeSectionWithHeaderFooterModifier {
|
|||
// MARK: - Views
|
||||
|
||||
extension ThemeTappableText {
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
commonView
|
||||
.foregroundStyle(.primary)
|
||||
}
|
||||
}
|
||||
|
||||
extension ThemeTextField {
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
commonView
|
||||
}
|
||||
}
|
||||
|
||||
extension ThemeSecureField {
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
commonView
|
||||
}
|
||||
}
|
||||
|
@ -109,13 +109,13 @@ extension ThemeRemovableItemRow {
|
|||
}
|
||||
|
||||
extension ThemeEditableListSection.RemoveLabel {
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
extension ThemeEditableListSection.EditLabel {
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
|
@ -91,7 +91,7 @@ extension ThemeSectionWithHeaderFooterModifier {
|
|||
// MARK: - Views
|
||||
|
||||
extension ThemeTappableText {
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
commonView
|
||||
.buttonStyle(.plain)
|
||||
.cursor(.hand)
|
||||
|
@ -99,14 +99,14 @@ extension ThemeTappableText {
|
|||
}
|
||||
|
||||
extension ThemeTextField {
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
commonView
|
||||
.labelsHidden()
|
||||
}
|
||||
}
|
||||
|
||||
extension ThemeSecureField {
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
commonView
|
||||
.labelsHidden()
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ extension ThemeRemovableItemRow {
|
|||
}
|
||||
|
||||
extension ThemeEditableListSection.RemoveLabel {
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
Button(action: action) {
|
||||
ThemeImage(.editableSectionRemove)
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ extension ThemeEditableListSection.RemoveLabel {
|
|||
}
|
||||
|
||||
extension ThemeEditableListSection.EditLabel {
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
ThemeImage(.editableSectionEdit)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// Theme+tvOS.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 10/29/24.
|
||||
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||
//
|
||||
// https://github.com/passepartoutvpn
|
||||
//
|
||||
// This file is part of Passepartout.
|
||||
//
|
||||
// Passepartout is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Passepartout is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#if os(tvOS)
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension Theme {
|
||||
public convenience init() {
|
||||
self.init(dummy: Void())
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -24,12 +24,16 @@
|
|||
//
|
||||
|
||||
import CommonLibrary
|
||||
#if canImport(LocalAuthentication)
|
||||
import LocalAuthentication
|
||||
#endif
|
||||
import SwiftUI
|
||||
import UtilsLibrary
|
||||
|
||||
// MARK: - Modifiers
|
||||
|
||||
#if !os(tvOS)
|
||||
|
||||
struct ThemeWindowModifier: ViewModifier {
|
||||
let size: CGSize
|
||||
}
|
||||
|
@ -381,6 +385,8 @@ struct ThemeTipModifier: ViewModifier {
|
|||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// MARK: - Views
|
||||
|
||||
public enum ThemeAnimationCategory: CaseIterable {
|
||||
|
@ -395,23 +401,23 @@ public enum ThemeAnimationCategory: CaseIterable {
|
|||
case providers
|
||||
}
|
||||
|
||||
struct ThemeImage: View {
|
||||
public struct ThemeImage: View {
|
||||
|
||||
@EnvironmentObject
|
||||
private var theme: Theme
|
||||
|
||||
private let name: Theme.ImageName
|
||||
|
||||
init(_ name: Theme.ImageName) {
|
||||
public init(_ name: Theme.ImageName) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
Image(systemName: theme.systemImageName(name))
|
||||
}
|
||||
}
|
||||
|
||||
struct ThemeImageLabel: View {
|
||||
public struct ThemeImageLabel: View {
|
||||
|
||||
@EnvironmentObject
|
||||
private var theme: Theme
|
||||
|
@ -420,12 +426,12 @@ struct ThemeImageLabel: View {
|
|||
|
||||
private let name: Theme.ImageName
|
||||
|
||||
init(_ title: String, _ name: Theme.ImageName) {
|
||||
public init(_ title: String, _ name: Theme.ImageName) {
|
||||
self.title = title
|
||||
self.name = name
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
Label {
|
||||
Text(title)
|
||||
} icon: {
|
||||
|
@ -434,34 +440,80 @@ struct ThemeImageLabel: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct ThemeMenuImage: View {
|
||||
public struct ThemeCountryFlag: View {
|
||||
private let code: String?
|
||||
|
||||
private let placeholderTip: String?
|
||||
|
||||
private let countryTip: ((String) -> String?)?
|
||||
|
||||
public init(code: String?, placeholderTip: String? = nil, countryTip: ((String) -> String?)? = nil) {
|
||||
self.code = code
|
||||
self.placeholderTip = placeholderTip
|
||||
self.countryTip = countryTip
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Group {
|
||||
if let code {
|
||||
let image = Image("flags/\(code.lowercased())")
|
||||
.resizable()
|
||||
|
||||
if let tip = countryTip?(code) {
|
||||
image
|
||||
.help(tip)
|
||||
} else {
|
||||
image
|
||||
}
|
||||
} else {
|
||||
let image = Image(systemName: "globe")
|
||||
if let placeholderTip {
|
||||
image
|
||||
.help(placeholderTip)
|
||||
} else {
|
||||
image
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: 20, height: 15)
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
|
||||
public struct ThemeMenuImage: View {
|
||||
|
||||
@EnvironmentObject
|
||||
private var theme: Theme
|
||||
|
||||
private let name: Theme.MenuImageName
|
||||
|
||||
init(_ name: Theme.MenuImageName) {
|
||||
public init(_ name: Theme.MenuImageName) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
Image(theme.menuImageName(name))
|
||||
}
|
||||
}
|
||||
|
||||
struct ThemeDisclosableMenu<Content, Label>: View where Content: View, Label: View {
|
||||
public struct ThemeDisclosableMenu<Content, Label>: View where Content: View, Label: View {
|
||||
|
||||
@ViewBuilder
|
||||
let content: () -> Content
|
||||
private let content: () -> Content
|
||||
|
||||
@ViewBuilder
|
||||
let label: Label
|
||||
private let label: () -> Label
|
||||
|
||||
var body: some View {
|
||||
public init(content: @escaping () -> Content, label: @escaping () -> Label) {
|
||||
self.content = content
|
||||
self.label = label
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Menu(content: content) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
label
|
||||
label()
|
||||
ThemeImage(.disclose)
|
||||
}
|
||||
.contentShape(.rect)
|
||||
|
@ -473,20 +525,32 @@ struct ThemeDisclosableMenu<Content, Label>: View where Content: View, Label: Vi
|
|||
}
|
||||
}
|
||||
|
||||
struct ThemeCopiableText<Value, ValueView>: View where Value: CustomStringConvertible, ValueView: View {
|
||||
public struct ThemeCopiableText<Value, ValueView>: View where Value: CustomStringConvertible, ValueView: View {
|
||||
|
||||
@EnvironmentObject
|
||||
private var theme: Theme
|
||||
|
||||
var title: String?
|
||||
private let title: String?
|
||||
|
||||
let value: Value
|
||||
private let value: Value
|
||||
|
||||
var isMultiLine = true
|
||||
private let isMultiLine: Bool
|
||||
|
||||
let valueView: (Value) -> ValueView
|
||||
private let valueView: (Value) -> ValueView
|
||||
|
||||
var body: some View {
|
||||
public init(
|
||||
title: String? = nil,
|
||||
value: Value,
|
||||
isMultiLine: Bool = true,
|
||||
valueView: @escaping (Value) -> ValueView
|
||||
) {
|
||||
self.title = title
|
||||
self.value = value
|
||||
self.isMultiLine = isMultiLine
|
||||
self.valueView = valueView
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
HStack {
|
||||
if let title {
|
||||
Text(title)
|
||||
|
@ -509,10 +573,15 @@ struct ThemeCopiableText<Value, ValueView>: View where Value: CustomStringConver
|
|||
}
|
||||
}
|
||||
|
||||
struct ThemeTappableText: View {
|
||||
let title: String
|
||||
public struct ThemeTappableText: View {
|
||||
private let title: String
|
||||
|
||||
let action: () -> Void
|
||||
private let action: () -> Void
|
||||
|
||||
public init(title: String, action: @escaping () -> Void) {
|
||||
self.title = title
|
||||
self.action = action
|
||||
}
|
||||
|
||||
var commonView: some View {
|
||||
Button(action: action) {
|
||||
|
@ -522,15 +591,15 @@ struct ThemeTappableText: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct ThemeTextField: View {
|
||||
let title: String?
|
||||
public struct ThemeTextField: View {
|
||||
private let title: String?
|
||||
|
||||
@Binding
|
||||
var text: String
|
||||
private var text: String
|
||||
|
||||
let placeholder: String
|
||||
private let placeholder: String
|
||||
|
||||
init(_ title: String, text: Binding<String>, placeholder: String) {
|
||||
public init(_ title: String, text: Binding<String>, placeholder: String) {
|
||||
self.title = title
|
||||
_text = text
|
||||
self.placeholder = placeholder
|
||||
|
@ -554,13 +623,19 @@ struct ThemeTextField: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct ThemeSecureField: View {
|
||||
let title: String?
|
||||
public struct ThemeSecureField: View {
|
||||
private let title: String?
|
||||
|
||||
@Binding
|
||||
var text: String
|
||||
private var text: String
|
||||
|
||||
let placeholder: String
|
||||
private let placeholder: String
|
||||
|
||||
public init(title: String?, text: Binding<String>, placeholder: String) {
|
||||
self.title = title
|
||||
_text = text
|
||||
self.placeholder = placeholder
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var commonView: some View {
|
||||
|
@ -586,15 +661,25 @@ struct ThemeSecureField: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct ThemeRemovableItemRow<ItemView>: View where ItemView: View {
|
||||
let isEditing: Bool
|
||||
public struct ThemeRemovableItemRow<ItemView>: View where ItemView: View {
|
||||
private let isEditing: Bool
|
||||
|
||||
@ViewBuilder
|
||||
let itemView: () -> ItemView
|
||||
private let itemView: () -> ItemView
|
||||
|
||||
let removeAction: () -> Void
|
||||
|
||||
var body: some View {
|
||||
public init(
|
||||
isEditing: Bool,
|
||||
@ViewBuilder itemView: @escaping () -> ItemView,
|
||||
removeAction: @escaping () -> Void
|
||||
) {
|
||||
self.isEditing = isEditing
|
||||
self.itemView = itemView
|
||||
self.removeAction = removeAction
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
RemovableItemRow(
|
||||
isEditing: isEditing,
|
||||
itemView: itemView,
|
||||
|
@ -603,11 +688,17 @@ struct ThemeRemovableItemRow<ItemView>: View where ItemView: View {
|
|||
}
|
||||
}
|
||||
|
||||
enum ThemeEditableListSection {
|
||||
struct RemoveLabel: View {
|
||||
public enum ThemeEditableListSection {
|
||||
public struct RemoveLabel: View {
|
||||
let action: () -> Void
|
||||
|
||||
public init(action: @escaping () -> Void) {
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
|
||||
struct EditLabel: View {
|
||||
public struct EditLabel: View {
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -28,68 +28,62 @@ import UtilsLibrary
|
|||
|
||||
@MainActor
|
||||
public final class Theme: ObservableObject {
|
||||
public internal(set) var rootModalSize: CGSize?
|
||||
|
||||
// @Published
|
||||
// private var palette: Palette
|
||||
//
|
||||
// public init(palette: Palette) {
|
||||
// self.palette = palette
|
||||
// }
|
||||
public internal(set) var secondaryModalSize: CGSize?
|
||||
|
||||
var rootModalSize: CGSize?
|
||||
public internal(set) var popoverSize: CGSize?
|
||||
|
||||
var secondaryModalSize: CGSize?
|
||||
public internal(set) var relevantWeight: Font.Weight = .semibold
|
||||
|
||||
var popoverSize: CGSize?
|
||||
public internal(set) var titleColor: Color = .primary
|
||||
|
||||
var relevantWeight: Font.Weight = .semibold
|
||||
public internal(set) var valueColor: Color = .secondary
|
||||
|
||||
var titleColor: Color = .primary
|
||||
public internal(set) var gridHeaderStyle: Font = .headline
|
||||
|
||||
var valueColor: Color = .secondary
|
||||
public internal(set) var gridRadius: CGFloat = 12.0
|
||||
|
||||
var gridHeaderStyle: Font = .headline
|
||||
public internal(set) var gridHeaderBottom: CGFloat = 8.0
|
||||
|
||||
var gridRadius: CGFloat = 12.0
|
||||
public internal(set) var gridCellColor: HierarchicalShapeStyle = .quinary
|
||||
|
||||
var gridHeaderBottom: CGFloat = 8.0
|
||||
public internal(set) var gridCellActiveColor: HierarchicalShapeStyle = .quaternary
|
||||
|
||||
var gridCellColor: HierarchicalShapeStyle = .quinary
|
||||
public internal(set) var emptyMessageFont: Font = .title
|
||||
|
||||
var gridCellActiveColor: HierarchicalShapeStyle = .quaternary
|
||||
public internal(set) var emptyMessageColor: Color = .secondary
|
||||
|
||||
var emptyMessageFont: Font = .title
|
||||
public internal(set) var primaryColor = Color(red: 0.318, green: 0.365, blue: 0.443)
|
||||
|
||||
var emptyMessageColor: Color = .secondary
|
||||
public internal(set) var activeColor = Color(red: .zero, green: Double(0xAA) / 255.0, blue: .zero)
|
||||
|
||||
var primaryColor = Color(red: 0.318, green: 0.365, blue: 0.443)
|
||||
public internal(set) var inactiveColor: Color = .gray
|
||||
|
||||
var activeColor = Color(red: .zero, green: Double(0xAA) / 255.0, blue: .zero)
|
||||
public internal(set) var pendingColor: Color = .orange
|
||||
|
||||
var inactiveColor: Color = .gray
|
||||
|
||||
var pendingColor: Color = .orange
|
||||
|
||||
var errorColor: Color = .red
|
||||
public internal(set) var errorColor: Color = .red
|
||||
|
||||
private var animation: Animation = .spring
|
||||
|
||||
var animationCategories: Set<ThemeAnimationCategory> = Set(ThemeAnimationCategory.allCases)
|
||||
public internal(set) var animationCategories: Set<ThemeAnimationCategory> = Set(ThemeAnimationCategory.allCases)
|
||||
|
||||
var logoImage = "Logo"
|
||||
public internal(set) var logoImage = "Logo"
|
||||
|
||||
var systemImageName: (ImageName) -> String = Theme.ImageName.defaultSystemName
|
||||
public internal(set) var systemImageName: (ImageName) -> String = Theme.ImageName.defaultSystemName
|
||||
|
||||
var menuImageName: (MenuImageName) -> String = Theme.MenuImageName.defaultImageName
|
||||
public internal(set) var menuImageName: (MenuImageName) -> String = Theme.MenuImageName.defaultImageName
|
||||
|
||||
init(dummy: Void) {
|
||||
}
|
||||
|
||||
func animation(for category: ThemeAnimationCategory) -> Animation? {
|
||||
public func animation(for category: ThemeAnimationCategory) -> Animation? {
|
||||
animationCategories.contains(category) ? animation : nil
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
|
||||
// MARK: - Modifiers
|
||||
|
||||
extension View {
|
||||
|
@ -214,39 +208,6 @@ extension View {
|
|||
}
|
||||
}
|
||||
|
||||
struct ThemeCountryFlag: View {
|
||||
let code: String?
|
||||
|
||||
var placeholderTip: String?
|
||||
|
||||
var countryTip: ((String) -> String?)?
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if let code {
|
||||
let image = Image("flags/\(code.lowercased())")
|
||||
.resizable()
|
||||
|
||||
if let tip = countryTip?(code) {
|
||||
image
|
||||
.help(tip)
|
||||
} else {
|
||||
image
|
||||
}
|
||||
} else {
|
||||
let image = Image(systemName: "globe")
|
||||
if let placeholderTip {
|
||||
image
|
||||
.help(placeholderTip)
|
||||
} else {
|
||||
image
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: 20, height: 15)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Views
|
||||
|
||||
extension Theme {
|
||||
|
@ -268,3 +229,5 @@ extension Theme {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -25,6 +25,6 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
protocol ThemeProviding {
|
||||
public protocol ThemeProviding {
|
||||
var theme: Theme { get }
|
||||
}
|
|
@ -27,7 +27,7 @@ import PassepartoutKit
|
|||
import SwiftUI
|
||||
|
||||
extension ProfileEditor {
|
||||
func binding(forNameOf moduleId: UUID) -> Binding<String> {
|
||||
public func binding(forNameOf moduleId: UUID) -> Binding<String> {
|
||||
Binding { [weak self] in
|
||||
self?.profile.name(forModuleWithId: moduleId) ?? ""
|
||||
} set: { [weak self] in
|
||||
|
@ -35,7 +35,7 @@ extension ProfileEditor {
|
|||
}
|
||||
}
|
||||
|
||||
func binding(forProviderOf moduleId: UUID) -> Binding<ProviderID?> {
|
||||
public func binding(forProviderOf moduleId: UUID) -> Binding<ProviderID?> {
|
||||
Binding { [weak self] in
|
||||
self?.profile.providerId(forModuleWithId: moduleId)
|
||||
} set: { [weak self] in
|
||||
|
@ -43,7 +43,7 @@ extension ProfileEditor {
|
|||
}
|
||||
}
|
||||
|
||||
func binding<E>(forProviderEntityOf moduleId: UUID) -> Binding<E?> where E: ProviderEntity & Codable {
|
||||
public func binding<E>(forProviderEntityOf moduleId: UUID) -> Binding<E?> where E: ProviderEntity & Codable {
|
||||
Binding { [weak self] in
|
||||
try? self?.profile.providerEntity(E.self, forModuleWithId: moduleId)
|
||||
} set: { [weak self] in
|
||||
|
@ -51,7 +51,7 @@ extension ProfileEditor {
|
|||
}
|
||||
}
|
||||
|
||||
subscript<T>(module: T) -> Binding<T> where T: ModuleBuilder {
|
||||
public subscript<T>(module: T) -> Binding<T> where T: ModuleBuilder {
|
||||
Binding { [weak self] in
|
||||
guard let foundModule = self?.module(withId: module.id) else {
|
||||
fatalError("Module not found in editor: \(module.id)")
|
|
@ -0,0 +1,58 @@
|
|||
//
|
||||
// AppUIMain.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 10/29/24.
|
||||
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||
//
|
||||
// https://github.com/passepartoutvpn
|
||||
//
|
||||
// This file is part of Passepartout.
|
||||
//
|
||||
// Passepartout is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Passepartout is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
@_exported import AppUI
|
||||
import Foundation
|
||||
|
||||
public enum AppUIMain: AppUIConfiguring {
|
||||
public static func configure(with context: AppContext) {
|
||||
assertMissingModuleImplementations()
|
||||
AppUI.configure(with: context)
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppUIMain {
|
||||
static func assertMissingModuleImplementations() {
|
||||
let providerModuleTypes: Set<ModuleType> = [
|
||||
.openVPN
|
||||
]
|
||||
ModuleType.allCases.forEach { moduleType in
|
||||
let builder = moduleType.newModule()
|
||||
guard builder is any ModuleViewProviding else {
|
||||
fatalError("\(moduleType): is not ModuleViewProviding")
|
||||
}
|
||||
if providerModuleTypes.contains(moduleType) {
|
||||
do {
|
||||
let module = try builder.tryBuild()
|
||||
guard module is any ProviderEntityViewProviding else {
|
||||
fatalError("\(moduleType): is not ProviderEntityViewProviding")
|
||||
}
|
||||
} catch {
|
||||
fatalError("\(moduleType): empty module is not buildable")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,15 +24,19 @@
|
|||
//
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
import CommonLibrary
|
||||
import Foundation
|
||||
import PassepartoutKit
|
||||
import UIKit
|
||||
|
||||
#else
|
||||
|
||||
import AppKit
|
||||
import CommonLibrary
|
||||
import Foundation
|
||||
import PassepartoutKit
|
||||
|
||||
#endif
|
||||
|
||||
struct Issue: Identifiable {
|
|
@ -1,8 +1,8 @@
|
|||
//
|
||||
// ProviderEntityViewProviding.swift
|
||||
// ProviderEntityViewProviding+Extensions.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 10/16/24.
|
||||
// Created by Davide De Rosa on 10/29/24.
|
||||
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||
//
|
||||
// https://github.com/passepartoutvpn
|
||||
|
@ -26,16 +26,6 @@
|
|||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
protocol ProviderEntityViewProviding {
|
||||
associatedtype EntityContent: View
|
||||
|
||||
@MainActor
|
||||
func providerEntityView(
|
||||
with provider: ModuleMetadata.Provider,
|
||||
onSelect: @escaping (any ProviderEntity & Encodable) async throws -> Void
|
||||
) -> EntityContent
|
||||
}
|
||||
|
||||
extension ProviderEntityViewProviding where Self: ProviderCompatibleModule, EntityType.Configuration: ProviderConfigurationIdentifiable & Codable {
|
||||
func vpnProviderEntityView(
|
||||
with provider: ModuleMetadata.Provider,
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// ProviderEntityViewProviding.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 10/16/24.
|
||||
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||
//
|
||||
// https://github.com/passepartoutvpn
|
||||
//
|
||||
// This file is part of Passepartout.
|
||||
//
|
||||
// Passepartout is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Passepartout is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
protocol ProviderEntityViewProviding {
|
||||
associatedtype EntityContent: View
|
||||
|
||||
@MainActor
|
||||
func providerEntityView(
|
||||
with provider: ModuleMetadata.Provider,
|
||||
onSelect: @escaping (any ProviderEntity & Encodable) async throws -> Void
|
||||
) -> EntityContent
|
||||
}
|
|
@ -39,11 +39,11 @@ public struct AppCoordinator: View {
|
|||
@AppStorage(AppPreference.profilesLayout.key)
|
||||
private var layout: ProfilesLayout = .list
|
||||
|
||||
let profileManager: ProfileManager
|
||||
private let profileManager: ProfileManager
|
||||
|
||||
let tunnel: Tunnel
|
||||
private let tunnel: Tunnel
|
||||
|
||||
let registry: Registry
|
||||
private let registry: Registry
|
||||
|
||||
@StateObject
|
||||
private var profileEditor = ProfileEditor()
|
|
@ -75,7 +75,7 @@ private extension AppMenu {
|
|||
}
|
||||
|
||||
var profilesList: some View {
|
||||
ForEach(profileManager.headers, id: \.self, content: profileToggle)
|
||||
ForEach(profileManager.headers, id: \.id, content: profileToggle)
|
||||
}
|
||||
|
||||
func profileToggle(for header: ProfileHeader) -> some View {
|
|
@ -31,7 +31,7 @@ import SwiftUI
|
|||
public struct AppMenuImage: View, TunnelContextProviding {
|
||||
|
||||
@ObservedObject
|
||||
var connectionObserver: ConnectionObserver
|
||||
public var connectionObserver: ConnectionObserver
|
||||
|
||||
public init(connectionObserver: ConnectionObserver) {
|
||||
self.connectionObserver = connectionObserver
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue