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 */; };
|
0EC797422B9378E000C093B7 /* Shared+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC797402B9378E000C093B7 /* Shared+App.swift */; };
|
||||||
0EC797432B9378E000C093B7 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC797412B9378E000C093B7 /* Shared.swift */; };
|
0EC797432B9378E000C093B7 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC797412B9378E000C093B7 /* Shared.swift */; };
|
||||||
0EC797442B93790600C093B7 /* 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 /* App+macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED61CF72CD0418C008FE259 /* App+macOS.swift */; };
|
||||||
0ED61CF82CD0418C008FE259 /* AppDelegate+macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED61CF72CD0418C008FE259 /* AppDelegate+macOS.swift */; };
|
0ED61CFA2CD04192008FE259 /* App+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED61CF92CD04192008FE259 /* App+iOS.swift */; };
|
||||||
0ED61CFA2CD04192008FE259 /* AppDelegate+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED61CF92CD04192008FE259 /* AppDelegate+iOS.swift */; };
|
|
||||||
0EDE56EA2CABE40D0082D21C /* Intents.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0EDE56E62CABE40D0082D21C /* Intents.plist */; };
|
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, ); }; };
|
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 */; };
|
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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy 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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
0ED61CF72CD0418C008FE259 /* App+macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+macOS.swift"; sourceTree = "<group>"; };
|
||||||
0ED61CF92CD04192008FE259 /* AppDelegate+iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+iOS.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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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 */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
@ -144,7 +147,8 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
0EC9C0232CA5BD0B00C52954 /* AppUI in Frameworks */,
|
0EE8D7DF2CD1108900F6600C /* AppUITV in Frameworks */,
|
||||||
|
0EE8D7DD2CD1107E00F6600C /* AppUIMain in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -214,8 +218,7 @@
|
||||||
0E7E3D5A2B9345FD002BBDB4 /* App */ = {
|
0E7E3D5A2B9345FD002BBDB4 /* App */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
0ED61CF52CD0416A008FE259 /* iOS */,
|
0ED61CF62CD04174008FE259 /* Platforms */,
|
||||||
0ED61CF62CD04174008FE259 /* macOS */,
|
|
||||||
0ED1EFDA2C33059600CBD9BD /* App.plist */,
|
0ED1EFDA2C33059600CBD9BD /* App.plist */,
|
||||||
0E7E3D5B2B9345FD002BBDB4 /* App.entitlements */,
|
0E7E3D5B2B9345FD002BBDB4 /* App.entitlements */,
|
||||||
0E7C3CCC2C9AF44600B72E69 /* AppDelegate.swift */,
|
0E7C3CCC2C9AF44600B72E69 /* AppDelegate.swift */,
|
||||||
|
@ -247,20 +250,14 @@
|
||||||
path = Tunnel;
|
path = Tunnel;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
0ED61CF52CD0416A008FE259 /* iOS */ = {
|
0ED61CF62CD04174008FE259 /* Platforms */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
0ED61CF92CD04192008FE259 /* AppDelegate+iOS.swift */,
|
0ED61CF92CD04192008FE259 /* App+iOS.swift */,
|
||||||
|
0ED61CF72CD0418C008FE259 /* App+macOS.swift */,
|
||||||
|
0EE8D7E02CD112C200F6600C /* App+tvOS.swift */,
|
||||||
);
|
);
|
||||||
path = iOS;
|
path = Platforms;
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
0ED61CF62CD04174008FE259 /* macOS */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
0ED61CF72CD0418C008FE259 /* AppDelegate+macOS.swift */,
|
|
||||||
);
|
|
||||||
path = macOS;
|
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
0EDE56E82CABE40D0082D21C /* Intents */ = {
|
0EDE56E82CABE40D0082D21C /* Intents */ = {
|
||||||
|
@ -298,7 +295,8 @@
|
||||||
);
|
);
|
||||||
name = Passepartout;
|
name = Passepartout;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
0EC9C0222CA5BD0B00C52954 /* AppUI */,
|
0EE8D7DC2CD1107E00F6600C /* AppUIMain */,
|
||||||
|
0EE8D7DE2CD1108900F6600C /* AppUITV */,
|
||||||
);
|
);
|
||||||
productName = PassepartoutKit;
|
productName = PassepartoutKit;
|
||||||
productReference = 0E06D18F2B87629100176E1D /* Passepartout.app */;
|
productReference = 0E06D18F2B87629100176E1D /* Passepartout.app */;
|
||||||
|
@ -466,11 +464,12 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
0ED61CF82CD0418C008FE259 /* AppDelegate+macOS.swift in Sources */,
|
0ED61CF82CD0418C008FE259 /* App+macOS.swift in Sources */,
|
||||||
0E7C3CCD2C9AF44600B72E69 /* AppDelegate.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 */,
|
0E7E3D6B2B9345FD002BBDB4 /* PassepartoutApp.swift in Sources */,
|
||||||
0EC797422B9378E000C093B7 /* Shared+App.swift in Sources */,
|
0EC797422B9378E000C093B7 /* Shared+App.swift in Sources */,
|
||||||
|
0EE8D7E12CD112C200F6600C /* App+tvOS.swift in Sources */,
|
||||||
0EC797432B9378E000C093B7 /* Shared.swift in Sources */,
|
0EC797432B9378E000C093B7 /* Shared.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -621,7 +620,7 @@
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_STRICT_CONCURRENCY = complete;
|
SWIFT_STRICT_CONCURRENCY = complete;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||||
TVOS_DEPLOYMENT_TARGET = 17.0;
|
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
};
|
};
|
||||||
|
@ -697,7 +696,7 @@
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
SWIFT_STRICT_CONCURRENCY = complete;
|
SWIFT_STRICT_CONCURRENCY = complete;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2,3";
|
||||||
TVOS_DEPLOYMENT_TARGET = 17.0;
|
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
};
|
};
|
||||||
|
@ -1001,9 +1000,13 @@
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = TunnelLibrary;
|
productName = TunnelLibrary;
|
||||||
};
|
};
|
||||||
0EC9C0222CA5BD0B00C52954 /* AppUI */ = {
|
0EE8D7DC2CD1107E00F6600C /* AppUIMain */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = AppUI;
|
productName = AppUIMain;
|
||||||
|
};
|
||||||
|
0EE8D7DE2CD1108900F6600C /* AppUITV */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = AppUITV;
|
||||||
};
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,7 +23,12 @@
|
||||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
// 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 CommonLibrary
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
@ -31,56 +36,34 @@ import SwiftUI
|
||||||
@main
|
@main
|
||||||
struct PassepartoutApp: App {
|
struct PassepartoutApp: App {
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS) || os(tvOS)
|
||||||
|
|
||||||
@UIApplicationDelegateAdaptor
|
@UIApplicationDelegateAdaptor
|
||||||
private var appDelegate: AppDelegate
|
private var appDelegate: AppDelegate
|
||||||
#else
|
|
||||||
|
#elseif os(macOS)
|
||||||
|
|
||||||
@NSApplicationDelegateAdaptor
|
@NSApplicationDelegateAdaptor
|
||||||
private var appDelegate: AppDelegate
|
private var appDelegate: AppDelegate
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@Environment(\.scenePhase)
|
@Environment(\.scenePhase)
|
||||||
private var scenePhase
|
private var scenePhase
|
||||||
|
|
||||||
private var context: AppContext {
|
@StateObject
|
||||||
|
var theme = Theme()
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PassepartoutApp {
|
||||||
|
var appName: String {
|
||||||
|
BundleConfiguration.mainDisplayName
|
||||||
|
}
|
||||||
|
|
||||||
|
var context: AppContext {
|
||||||
appDelegate.context
|
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 {
|
func contentView() -> some View {
|
||||||
AppCoordinator(
|
AppCoordinator(
|
||||||
profileManager: context.profileManager,
|
profileManager: context.profileManager,
|
||||||
|
@ -103,7 +86,6 @@ private extension PassepartoutApp {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.themeLockScreen()
|
|
||||||
.withEnvironment(from: context, theme: theme)
|
.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
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 10/28/24.
|
// Created by Davide De Rosa on 10/28/24.
|
||||||
|
@ -25,9 +25,9 @@
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
|
|
||||||
import AppKit
|
import AppUIMain
|
||||||
import AppUI
|
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
extension AppDelegate: NSApplicationDelegate {
|
extension AppDelegate: NSApplicationDelegate {
|
||||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
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
|
#endif
|
|
@ -1,8 +1,8 @@
|
||||||
//
|
//
|
||||||
// AppDelegate+iOS.swift
|
// App+tvOS.swift
|
||||||
// Passepartout
|
// 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.
|
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||||
//
|
//
|
||||||
// https://github.com/passepartoutvpn
|
// https://github.com/passepartoutvpn
|
||||||
|
@ -23,10 +23,10 @@
|
||||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(tvOS)
|
||||||
|
|
||||||
import AppUI
|
import AppUITV
|
||||||
import UIKit
|
import SwiftUI
|
||||||
|
|
||||||
extension AppDelegate: UIApplicationDelegate {
|
extension AppDelegate: UIApplicationDelegate {
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
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
|
#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",
|
name: "AppUI",
|
||||||
targets: ["AppUI"]
|
targets: ["AppUI"]
|
||||||
),
|
),
|
||||||
|
.library(
|
||||||
|
name: "AppUIMain",
|
||||||
|
targets: ["AppUIMain"]
|
||||||
|
),
|
||||||
|
.library(
|
||||||
|
name: "AppUITV",
|
||||||
|
targets: ["AppUITV"]
|
||||||
|
),
|
||||||
.library(
|
.library(
|
||||||
name: "TunnelLibrary",
|
name: "TunnelLibrary",
|
||||||
targets: ["CommonLibrary"]
|
targets: ["CommonLibrary"]
|
||||||
|
@ -78,13 +86,26 @@ let package = Package(
|
||||||
"AppDataProviders",
|
"AppDataProviders",
|
||||||
"AppLibrary",
|
"AppLibrary",
|
||||||
"Kvitto",
|
"Kvitto",
|
||||||
"LegacyV2",
|
|
||||||
"UtilsLibrary"
|
"UtilsLibrary"
|
||||||
],
|
],
|
||||||
resources: [
|
resources: [
|
||||||
.process("Resources")
|
.process("Resources")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
.target(
|
||||||
|
name: "AppUIMain",
|
||||||
|
dependencies: [
|
||||||
|
"AppUI",
|
||||||
|
"LegacyV2"
|
||||||
|
],
|
||||||
|
resources: [
|
||||||
|
.process("Resources")
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.target(
|
||||||
|
name: "AppUITV",
|
||||||
|
dependencies: ["AppUI"]
|
||||||
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "CommonLibrary",
|
name: "CommonLibrary",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
@ -114,6 +135,10 @@ let package = Package(
|
||||||
name: "AppLibraryTests",
|
name: "AppLibraryTests",
|
||||||
dependencies: ["AppLibrary"]
|
dependencies: ["AppLibrary"]
|
||||||
),
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "AppUIMainTests",
|
||||||
|
dependencies: ["AppUIMain"]
|
||||||
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "AppUITests",
|
name: "AppUITests",
|
||||||
dependencies: ["AppUI"]
|
dependencies: ["AppUI"]
|
||||||
|
|
|
@ -24,9 +24,12 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import NetworkExtension
|
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
|
public protocol AppUIConfiguring {
|
||||||
|
static func configure(with context: AppContext)
|
||||||
|
}
|
||||||
|
|
||||||
public enum AppUI {
|
public enum AppUI {
|
||||||
public static func configure(with context: AppContext) {
|
public static func configure(with context: AppContext) {
|
||||||
assertMissingModuleImplementations()
|
assertMissingModuleImplementations()
|
||||||
|
@ -36,29 +39,13 @@ public enum AppUI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension AppUI {
|
extension AppUI {
|
||||||
static func assertMissingModuleImplementations() {
|
public static func assertMissingModuleImplementations() {
|
||||||
let providerModuleTypes: Set<ModuleType> = [
|
|
||||||
.openVPN
|
|
||||||
]
|
|
||||||
ModuleType.allCases.forEach { moduleType in
|
ModuleType.allCases.forEach { moduleType in
|
||||||
let builder = moduleType.newModule()
|
let builder = moduleType.newModule()
|
||||||
guard builder is ModuleTypeProviding else {
|
guard builder is ModuleTypeProviding else {
|
||||||
fatalError("\(moduleType): is not ModuleTypeProviding")
|
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
|
@MainActor
|
||||||
public final class ConnectionObserver: ObservableObject {
|
public final class ConnectionObserver: ObservableObject {
|
||||||
let tunnel: Tunnel
|
public let tunnel: Tunnel
|
||||||
|
|
||||||
private let environment: TunnelEnvironment
|
private let environment: TunnelEnvironment
|
||||||
|
|
||||||
|
|
|
@ -30,21 +30,21 @@ import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
final class ProfileEditor: ObservableObject {
|
public final class ProfileEditor: ObservableObject {
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
private var editableProfile: EditableProfile
|
private var editableProfile: EditableProfile
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var isShared: Bool
|
public var isShared: Bool
|
||||||
|
|
||||||
private(set) var removedModules: [UUID: any ModuleBuilder]
|
private(set) var removedModules: [UUID: any ModuleBuilder]
|
||||||
|
|
||||||
convenience init() {
|
public convenience init() {
|
||||||
self.init(modules: [])
|
self.init(modules: [])
|
||||||
}
|
}
|
||||||
|
|
||||||
init(modules: [any ModuleBuilder]) {
|
public init(modules: [any ModuleBuilder]) {
|
||||||
editableProfile = EditableProfile(
|
editableProfile = EditableProfile(
|
||||||
modules: modules,
|
modules: modules,
|
||||||
activeModulesIds: Set(modules.map(\.id))
|
activeModulesIds: Set(modules.map(\.id))
|
||||||
|
@ -53,13 +53,13 @@ final class ProfileEditor: ObservableObject {
|
||||||
removedModules = [:]
|
removedModules = [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
init(profile: Profile) {
|
public init(profile: Profile) {
|
||||||
editableProfile = profile.editable()
|
editableProfile = profile.editable()
|
||||||
isShared = false
|
isShared = false
|
||||||
removedModules = [:]
|
removedModules = [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func editProfile(_ profile: Profile, isShared: Bool) {
|
public func editProfile(_ profile: Profile, isShared: Bool) {
|
||||||
editableProfile = profile.editable()
|
editableProfile = profile.editable()
|
||||||
self.isShared = isShared
|
self.isShared = isShared
|
||||||
removedModules = [:]
|
removedModules = [:]
|
||||||
|
@ -69,7 +69,7 @@ final class ProfileEditor: ObservableObject {
|
||||||
// MARK: - Types
|
// MARK: - Types
|
||||||
|
|
||||||
extension ProfileEditor {
|
extension ProfileEditor {
|
||||||
var moduleTypes: [ModuleType] {
|
public var moduleTypes: [ModuleType] {
|
||||||
editableProfile.modules
|
editableProfile.modules
|
||||||
.compactMap {
|
.compactMap {
|
||||||
$0 as? ModuleTypeProviding
|
$0 as? ModuleTypeProviding
|
||||||
|
@ -77,7 +77,7 @@ extension ProfileEditor {
|
||||||
.map(\.moduleType)
|
.map(\.moduleType)
|
||||||
}
|
}
|
||||||
|
|
||||||
var availableModuleTypes: [ModuleType] {
|
public var availableModuleTypes: [ModuleType] {
|
||||||
ModuleType
|
ModuleType
|
||||||
.allCases
|
.allCases
|
||||||
.filter {
|
.filter {
|
||||||
|
@ -97,7 +97,7 @@ extension ProfileEditor {
|
||||||
// MARK: - Editing
|
// MARK: - Editing
|
||||||
|
|
||||||
extension ProfileEditor {
|
extension ProfileEditor {
|
||||||
var profile: EditableProfile {
|
public var profile: EditableProfile {
|
||||||
get {
|
get {
|
||||||
editableProfile
|
editableProfile
|
||||||
}
|
}
|
||||||
|
@ -108,21 +108,21 @@ extension ProfileEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProfileEditor {
|
extension ProfileEditor {
|
||||||
var modules: [any ModuleBuilder] {
|
public var modules: [any ModuleBuilder] {
|
||||||
editableProfile.modules
|
editableProfile.modules
|
||||||
}
|
}
|
||||||
|
|
||||||
func module(withId moduleId: UUID) -> (any ModuleBuilder)? {
|
public func module(withId moduleId: UUID) -> (any ModuleBuilder)? {
|
||||||
editableProfile.modules.first {
|
editableProfile.modules.first {
|
||||||
$0.id == moduleId
|
$0.id == moduleId
|
||||||
} ?? removedModules[moduleId]
|
} ?? removedModules[moduleId]
|
||||||
}
|
}
|
||||||
|
|
||||||
func isActiveModule(withId moduleId: UUID) -> Bool {
|
public func isActiveModule(withId moduleId: UUID) -> Bool {
|
||||||
editableProfile.isActiveModule(withId: moduleId)
|
editableProfile.isActiveModule(withId: moduleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toggleModule(withId moduleId: UUID) {
|
public func toggleModule(withId moduleId: UUID) {
|
||||||
guard let existingModule = module(withId: moduleId) else {
|
guard let existingModule = module(withId: moduleId) else {
|
||||||
return
|
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)
|
editableProfile.modules.move(fromOffsets: offsets, toOffset: newOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeModules(at offsets: IndexSet) {
|
public func removeModules(at offsets: IndexSet) {
|
||||||
offsets.forEach {
|
offsets.forEach {
|
||||||
let module = editableProfile.modules[$0]
|
let module = editableProfile.modules[$0]
|
||||||
removedModules[module.id] = module
|
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 {
|
guard let index = editableProfile.modules.firstIndex(where: { $0.id == moduleId }) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ extension ProfileEditor {
|
||||||
editableProfile.modules.remove(at: index)
|
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 }) {
|
if let index = editableProfile.modules.firstIndex(where: { $0.id == module.id }) {
|
||||||
editableProfile.modules[index] = module
|
editableProfile.modules[index] = module
|
||||||
} else {
|
} else {
|
||||||
|
@ -175,7 +175,7 @@ private extension ProfileEditor {
|
||||||
// MARK: - Building
|
// MARK: - Building
|
||||||
|
|
||||||
extension ProfileEditor {
|
extension ProfileEditor {
|
||||||
func build() throws -> Profile {
|
public func build() throws -> Profile {
|
||||||
let builder = try editableProfile.builder()
|
let builder = try editableProfile.builder()
|
||||||
let profile = try builder.tryBuild()
|
let profile = try builder.tryBuild()
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ extension ProfileEditor {
|
||||||
// MARK: - Saving
|
// MARK: - Saving
|
||||||
|
|
||||||
extension ProfileEditor {
|
extension ProfileEditor {
|
||||||
func save(to profileManager: ProfileManager) async throws {
|
public func save(to profileManager: ProfileManager) async throws {
|
||||||
do {
|
do {
|
||||||
let newProfile = try build()
|
let newProfile = try build()
|
||||||
try await profileManager.save(newProfile, isShared: isShared)
|
try await profileManager.save(newProfile, isShared: isShared)
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
enum AppError {
|
public enum AppError {
|
||||||
case emptyProfileName
|
case emptyProfileName
|
||||||
|
|
||||||
case malformedModule(any ModuleBuilder, error: Error)
|
case malformedModule(any ModuleBuilder, error: Error)
|
||||||
|
@ -35,7 +35,7 @@ enum AppError {
|
||||||
|
|
||||||
case generic(PassepartoutError)
|
case generic(PassepartoutError)
|
||||||
|
|
||||||
init(_ error: Error) {
|
public init(_ error: Error) {
|
||||||
if let spError = error as? AppError {
|
if let spError = error as? AppError {
|
||||||
self = spError
|
self = spError
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -26,18 +26,18 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
struct EditableProfile: MutableProfileType {
|
public struct EditableProfile: MutableProfileType {
|
||||||
var id = UUID()
|
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)
|
var builder = Profile.Builder(id: id)
|
||||||
builder.modules = try modules.compactMap {
|
builder.modules = try modules.compactMap {
|
||||||
do {
|
do {
|
||||||
|
@ -68,7 +68,7 @@ struct EditableProfile: MutableProfileType {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Profile {
|
extension Profile {
|
||||||
func editable() -> EditableProfile {
|
public func editable() -> EditableProfile {
|
||||||
EditableProfile(
|
EditableProfile(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
|
@ -78,7 +78,7 @@ extension Profile {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var modulesBuilders: [any ModuleBuilder] {
|
public var modulesBuilders: [any ModuleBuilder] {
|
||||||
modules.compactMap {
|
modules.compactMap {
|
||||||
guard let buildableModule = $0 as? any BuildableType else {
|
guard let buildableModule = $0 as? any BuildableType else {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -27,7 +27,7 @@ import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
extension ModuleType {
|
extension ModuleType {
|
||||||
func newModule() -> any ModuleBuilder {
|
public func newModule() -> any ModuleBuilder {
|
||||||
switch self {
|
switch self {
|
||||||
case .openVPN:
|
case .openVPN:
|
||||||
return OpenVPNModule.Builder()
|
return OpenVPNModule.Builder()
|
||||||
|
|
|
@ -27,7 +27,7 @@ import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import PassepartoutWireGuardGo
|
import PassepartoutWireGuardGo
|
||||||
|
|
||||||
enum ModuleType: String, CaseIterable {
|
public enum ModuleType: String, CaseIterable {
|
||||||
case openVPN
|
case openVPN
|
||||||
|
|
||||||
case wireGuard
|
case wireGuard
|
||||||
|
@ -42,7 +42,7 @@ enum ModuleType: String, CaseIterable {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ModuleType: Identifiable {
|
extension ModuleType: Identifiable {
|
||||||
var id: String {
|
public var id: String {
|
||||||
rawValue
|
rawValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,13 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
struct TunnelInstallation {
|
public struct TunnelInstallation {
|
||||||
let header: ProfileHeader
|
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
|
@MainActor
|
||||||
extension Tunnel {
|
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)
|
let newProfile = try processor.processed(profile)
|
||||||
try await install(newProfile, connect: false, title: processor.title)
|
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)
|
let newProfile = try processor.processed(profile)
|
||||||
try await install(newProfile, connect: true, title: processor.title)
|
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(
|
let output = try? await sendMessage(.localLog(
|
||||||
sinceLast: parameters.sinceLast,
|
sinceLast: parameters.sinceLast,
|
||||||
maxLevel: parameters.maxLevel
|
maxLevel: parameters.maxLevel
|
|
@ -43,7 +43,7 @@ public final class IAPManager: ObservableObject {
|
||||||
|
|
||||||
private var purchasedAppBuild: Int?
|
private var purchasedAppBuild: Int?
|
||||||
|
|
||||||
private(set) var purchasedProducts: Set<AppProduct>
|
public private(set) var purchasedProducts: Set<AppProduct>
|
||||||
|
|
||||||
private var eligibleFeatures: Set<AppFeature>
|
private var eligibleFeatures: Set<AppFeature>
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ import PassepartoutKit
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
extension AppError: LocalizedError {
|
extension AppError: LocalizedError {
|
||||||
var errorDescription: String? {
|
public var errorDescription: String? {
|
||||||
let V = Strings.Errors.App.self
|
let V = Strings.Errors.App.self
|
||||||
switch self {
|
switch self {
|
||||||
case .emptyProfileName:
|
case .emptyProfileName:
|
||||||
|
|
|
@ -29,13 +29,13 @@ import PassepartoutKit
|
||||||
extension ModuleBuilder {
|
extension ModuleBuilder {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func description(inEditor editor: ProfileEditor) -> String {
|
public func description(inEditor editor: ProfileEditor) -> String {
|
||||||
editor.profile.displayName(forModuleWithId: id) ?? typeDescription
|
editor.profile.displayName(forModuleWithId: id) ?? typeDescription
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ModuleBuilder {
|
extension ModuleBuilder {
|
||||||
var typeDescription: String {
|
public var typeDescription: String {
|
||||||
guard let providing = self as? ModuleTypeProviding else {
|
guard let providing = self as? ModuleTypeProviding else {
|
||||||
return String(describing: self)
|
return String(describing: self)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ import SwiftUI
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
extension ErrorHandler {
|
extension ErrorHandler {
|
||||||
static func `default`() -> ErrorHandler {
|
public static func `default`() -> ErrorHandler {
|
||||||
ErrorHandler(
|
ErrorHandler(
|
||||||
defaultTitle: Strings.Unlocalized.appName,
|
defaultTitle: Strings.Unlocalized.appName,
|
||||||
dismissTitle: Strings.Global.ok,
|
dismissTitle: Strings.Global.ok,
|
||||||
|
|
|
@ -64,7 +64,7 @@ extension Date: StyledLocalizableEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UUID {
|
extension UUID {
|
||||||
var flatString: String {
|
public var flatString: String {
|
||||||
let str = uuidString.replacingOccurrences(of: "-", with: "")
|
let str = uuidString.replacingOccurrences(of: "-", with: "")
|
||||||
assert(str.count == 32)
|
assert(str.count == 32)
|
||||||
return str
|
return str
|
||||||
|
|
|
@ -27,9 +27,9 @@ import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
extension Strings {
|
extension Strings {
|
||||||
enum Unlocalized {
|
public enum Unlocalized {
|
||||||
enum OpenVPN {
|
public enum OpenVPN {
|
||||||
enum XOR: String {
|
public enum XOR: String {
|
||||||
case xormask
|
case xormask
|
||||||
|
|
||||||
case xorptrpos
|
case xorptrpos
|
||||||
|
@ -39,23 +39,23 @@ extension Strings {
|
||||||
case obfuscate
|
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 {
|
public enum Placeholders {
|
||||||
static let hostname = "example.com"
|
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 {
|
switch family {
|
||||||
case .v4:
|
case .v4:
|
||||||
return "192.168.15.0/24"
|
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 {
|
switch family {
|
||||||
case .v4:
|
case .v4:
|
||||||
return "192.168.15.1"
|
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 {
|
public enum Issues {
|
||||||
static let subject = "\(appName) - Report issue"
|
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: {
|
profileManager: {
|
||||||
let profiles: [Profile] = (0..<20)
|
let profiles: [Profile] = (0..<20)
|
||||||
.reduce(into: []) { list, _ in
|
.reduce(into: []) { list, _ in
|
||||||
list.append(.newProfile())
|
list.append(.newMockProfile())
|
||||||
}
|
}
|
||||||
return ProfileManager(profiles: profiles)
|
return ProfileManager(profiles: profiles)
|
||||||
}(),
|
}(),
|
||||||
|
@ -109,7 +109,7 @@ extension ProviderManager {
|
||||||
// MARK: - Profile
|
// MARK: - Profile
|
||||||
|
|
||||||
extension Profile {
|
extension Profile {
|
||||||
static let mock: Profile = {
|
public static let mock: Profile = {
|
||||||
var profile = Profile.Builder()
|
var profile = Profile.Builder()
|
||||||
profile.name = "Mock profile"
|
profile.name = "Mock profile"
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ extension Profile {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
static func newProfile() -> Profile {
|
public static func newMockProfile() -> Profile {
|
||||||
do {
|
do {
|
||||||
var copy = mock.builder(withNewId: true)
|
var copy = mock.builder(withNewId: true)
|
||||||
copy.name = String(copy.id.uuidString.prefix(8))
|
copy.name = String(copy.id.uuidString.prefix(8))
|
||||||
|
|
|
@ -26,13 +26,13 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
protocol TunnelContextProviding {
|
public protocol TunnelContextProviding {
|
||||||
var connectionObserver: ConnectionObserver { get }
|
var connectionObserver: ConnectionObserver { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
extension TunnelContextProviding {
|
extension TunnelContextProviding {
|
||||||
var tunnelConnectionStatus: TunnelStatus {
|
public var tunnelConnectionStatus: TunnelStatus {
|
||||||
var status = connectionObserver.tunnel.status
|
var status = connectionObserver.tunnel.status
|
||||||
if status == .active, let connectionStatus = connectionObserver.connectionStatus {
|
if status == .active, let connectionStatus = connectionObserver.connectionStatus {
|
||||||
if connectionStatus == .connected {
|
if connectionStatus == .connected {
|
|
@ -27,7 +27,7 @@ import AppLibrary
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
protocol TunnelInstallationProviding {
|
public protocol TunnelInstallationProviding {
|
||||||
var profileManager: ProfileManager { get }
|
var profileManager: ProfileManager { get }
|
||||||
|
|
||||||
var tunnel: Tunnel { get }
|
var tunnel: Tunnel { get }
|
||||||
|
@ -35,7 +35,7 @@ protocol TunnelInstallationProviding {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
extension TunnelInstallationProviding {
|
extension TunnelInstallationProviding {
|
||||||
var installation: TunnelInstallation? {
|
public var installation: TunnelInstallation? {
|
||||||
guard let currentProfile = tunnel.currentProfile else {
|
guard let currentProfile = tunnel.currentProfile else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ extension TunnelInstallationProviding {
|
||||||
return TunnelInstallation(header: header, onDemand: currentProfile.onDemand)
|
return TunnelInstallation(header: header, onDemand: currentProfile.onDemand)
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentProfile: Profile? {
|
public var currentProfile: Profile? {
|
||||||
guard let id = tunnel.currentProfile?.id else {
|
guard let id = tunnel.currentProfile?.id else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
|
@ -84,20 +84,20 @@ extension ThemeSectionWithHeaderFooterModifier {
|
||||||
// MARK: - Views
|
// MARK: - Views
|
||||||
|
|
||||||
extension ThemeTappableText {
|
extension ThemeTappableText {
|
||||||
var body: some View {
|
public var body: some View {
|
||||||
commonView
|
commonView
|
||||||
.foregroundStyle(.primary)
|
.foregroundStyle(.primary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ThemeTextField {
|
extension ThemeTextField {
|
||||||
var body: some View {
|
public var body: some View {
|
||||||
commonView
|
commonView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ThemeSecureField {
|
extension ThemeSecureField {
|
||||||
var body: some View {
|
public var body: some View {
|
||||||
commonView
|
commonView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,13 +109,13 @@ extension ThemeRemovableItemRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ThemeEditableListSection.RemoveLabel {
|
extension ThemeEditableListSection.RemoveLabel {
|
||||||
var body: some View {
|
public var body: some View {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ThemeEditableListSection.EditLabel {
|
extension ThemeEditableListSection.EditLabel {
|
||||||
var body: some View {
|
public var body: some View {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -91,7 +91,7 @@ extension ThemeSectionWithHeaderFooterModifier {
|
||||||
// MARK: - Views
|
// MARK: - Views
|
||||||
|
|
||||||
extension ThemeTappableText {
|
extension ThemeTappableText {
|
||||||
var body: some View {
|
public var body: some View {
|
||||||
commonView
|
commonView
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.cursor(.hand)
|
.cursor(.hand)
|
||||||
|
@ -99,14 +99,14 @@ extension ThemeTappableText {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ThemeTextField {
|
extension ThemeTextField {
|
||||||
var body: some View {
|
public var body: some View {
|
||||||
commonView
|
commonView
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ThemeSecureField {
|
extension ThemeSecureField {
|
||||||
var body: some View {
|
public var body: some View {
|
||||||
commonView
|
commonView
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ extension ThemeRemovableItemRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ThemeEditableListSection.RemoveLabel {
|
extension ThemeEditableListSection.RemoveLabel {
|
||||||
var body: some View {
|
public var body: some View {
|
||||||
Button(action: action) {
|
Button(action: action) {
|
||||||
ThemeImage(.editableSectionRemove)
|
ThemeImage(.editableSectionRemove)
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ extension ThemeEditableListSection.RemoveLabel {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ThemeEditableListSection.EditLabel {
|
extension ThemeEditableListSection.EditLabel {
|
||||||
var body: some View {
|
public var body: some View {
|
||||||
ThemeImage(.editableSectionEdit)
|
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
|
import CommonLibrary
|
||||||
|
#if canImport(LocalAuthentication)
|
||||||
import LocalAuthentication
|
import LocalAuthentication
|
||||||
|
#endif
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
// MARK: - Modifiers
|
// MARK: - Modifiers
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
|
|
||||||
struct ThemeWindowModifier: ViewModifier {
|
struct ThemeWindowModifier: ViewModifier {
|
||||||
let size: CGSize
|
let size: CGSize
|
||||||
}
|
}
|
||||||
|
@ -381,6 +385,8 @@ struct ThemeTipModifier: ViewModifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
// MARK: - Views
|
// MARK: - Views
|
||||||
|
|
||||||
public enum ThemeAnimationCategory: CaseIterable {
|
public enum ThemeAnimationCategory: CaseIterable {
|
||||||
|
@ -395,23 +401,23 @@ public enum ThemeAnimationCategory: CaseIterable {
|
||||||
case providers
|
case providers
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ThemeImage: View {
|
public struct ThemeImage: View {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var theme: Theme
|
private var theme: Theme
|
||||||
|
|
||||||
private let name: Theme.ImageName
|
private let name: Theme.ImageName
|
||||||
|
|
||||||
init(_ name: Theme.ImageName) {
|
public init(_ name: Theme.ImageName) {
|
||||||
self.name = name
|
self.name = name
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
public var body: some View {
|
||||||
Image(systemName: theme.systemImageName(name))
|
Image(systemName: theme.systemImageName(name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ThemeImageLabel: View {
|
public struct ThemeImageLabel: View {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var theme: Theme
|
private var theme: Theme
|
||||||
|
@ -420,12 +426,12 @@ struct ThemeImageLabel: View {
|
||||||
|
|
||||||
private let name: Theme.ImageName
|
private let name: Theme.ImageName
|
||||||
|
|
||||||
init(_ title: String, _ name: Theme.ImageName) {
|
public init(_ title: String, _ name: Theme.ImageName) {
|
||||||
self.title = title
|
self.title = title
|
||||||
self.name = name
|
self.name = name
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
public var body: some View {
|
||||||
Label {
|
Label {
|
||||||
Text(title)
|
Text(title)
|
||||||
} icon: {
|
} 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
|
@EnvironmentObject
|
||||||
private var theme: Theme
|
private var theme: Theme
|
||||||
|
|
||||||
private let name: Theme.MenuImageName
|
private let name: Theme.MenuImageName
|
||||||
|
|
||||||
init(_ name: Theme.MenuImageName) {
|
public init(_ name: Theme.MenuImageName) {
|
||||||
self.name = name
|
self.name = name
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
public var body: some View {
|
||||||
Image(theme.menuImageName(name))
|
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
|
@ViewBuilder
|
||||||
let content: () -> Content
|
private let content: () -> Content
|
||||||
|
|
||||||
@ViewBuilder
|
@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) {
|
Menu(content: content) {
|
||||||
HStack(alignment: .firstTextBaseline) {
|
HStack(alignment: .firstTextBaseline) {
|
||||||
label
|
label()
|
||||||
ThemeImage(.disclose)
|
ThemeImage(.disclose)
|
||||||
}
|
}
|
||||||
.contentShape(.rect)
|
.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
|
@EnvironmentObject
|
||||||
private var theme: Theme
|
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 {
|
HStack {
|
||||||
if let title {
|
if let title {
|
||||||
Text(title)
|
Text(title)
|
||||||
|
@ -509,10 +573,15 @@ struct ThemeCopiableText<Value, ValueView>: View where Value: CustomStringConver
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ThemeTappableText: View {
|
public struct ThemeTappableText: View {
|
||||||
let title: String
|
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 {
|
var commonView: some View {
|
||||||
Button(action: action) {
|
Button(action: action) {
|
||||||
|
@ -522,15 +591,15 @@ struct ThemeTappableText: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ThemeTextField: View {
|
public struct ThemeTextField: View {
|
||||||
let title: String?
|
private let title: String?
|
||||||
|
|
||||||
@Binding
|
@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
|
self.title = title
|
||||||
_text = text
|
_text = text
|
||||||
self.placeholder = placeholder
|
self.placeholder = placeholder
|
||||||
|
@ -554,13 +623,19 @@ struct ThemeTextField: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ThemeSecureField: View {
|
public struct ThemeSecureField: View {
|
||||||
let title: String?
|
private let title: String?
|
||||||
|
|
||||||
@Binding
|
@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
|
@ViewBuilder
|
||||||
var commonView: some View {
|
var commonView: some View {
|
||||||
|
@ -586,15 +661,25 @@ struct ThemeSecureField: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ThemeRemovableItemRow<ItemView>: View where ItemView: View {
|
public struct ThemeRemovableItemRow<ItemView>: View where ItemView: View {
|
||||||
let isEditing: Bool
|
private let isEditing: Bool
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
let itemView: () -> ItemView
|
private let itemView: () -> ItemView
|
||||||
|
|
||||||
let removeAction: () -> Void
|
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(
|
RemovableItemRow(
|
||||||
isEditing: isEditing,
|
isEditing: isEditing,
|
||||||
itemView: itemView,
|
itemView: itemView,
|
||||||
|
@ -603,11 +688,17 @@ struct ThemeRemovableItemRow<ItemView>: View where ItemView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ThemeEditableListSection {
|
public enum ThemeEditableListSection {
|
||||||
struct RemoveLabel: View {
|
public struct RemoveLabel: View {
|
||||||
let action: () -> Void
|
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
|
@MainActor
|
||||||
public final class Theme: ObservableObject {
|
public final class Theme: ObservableObject {
|
||||||
|
public internal(set) var rootModalSize: CGSize?
|
||||||
|
|
||||||
// @Published
|
public internal(set) var secondaryModalSize: CGSize?
|
||||||
// private var palette: Palette
|
|
||||||
//
|
|
||||||
// public init(palette: Palette) {
|
|
||||||
// self.palette = palette
|
|
||||||
// }
|
|
||||||
|
|
||||||
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
|
public internal(set) var errorColor: Color = .red
|
||||||
|
|
||||||
var pendingColor: Color = .orange
|
|
||||||
|
|
||||||
var errorColor: Color = .red
|
|
||||||
|
|
||||||
private var animation: Animation = .spring
|
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) {
|
init(dummy: Void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func animation(for category: ThemeAnimationCategory) -> Animation? {
|
public func animation(for category: ThemeAnimationCategory) -> Animation? {
|
||||||
animationCategories.contains(category) ? animation : nil
|
animationCategories.contains(category) ? animation : nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
|
|
||||||
// MARK: - Modifiers
|
// MARK: - Modifiers
|
||||||
|
|
||||||
extension View {
|
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
|
// MARK: - Views
|
||||||
|
|
||||||
extension Theme {
|
extension Theme {
|
||||||
|
@ -268,3 +229,5 @@ extension Theme {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -25,6 +25,6 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
protocol ThemeProviding {
|
public protocol ThemeProviding {
|
||||||
var theme: Theme { get }
|
var theme: Theme { get }
|
||||||
}
|
}
|
|
@ -27,7 +27,7 @@ import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension ProfileEditor {
|
extension ProfileEditor {
|
||||||
func binding(forNameOf moduleId: UUID) -> Binding<String> {
|
public func binding(forNameOf moduleId: UUID) -> Binding<String> {
|
||||||
Binding { [weak self] in
|
Binding { [weak self] in
|
||||||
self?.profile.name(forModuleWithId: moduleId) ?? ""
|
self?.profile.name(forModuleWithId: moduleId) ?? ""
|
||||||
} set: { [weak self] in
|
} 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
|
Binding { [weak self] in
|
||||||
self?.profile.providerId(forModuleWithId: moduleId)
|
self?.profile.providerId(forModuleWithId: moduleId)
|
||||||
} set: { [weak self] in
|
} 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
|
Binding { [weak self] in
|
||||||
try? self?.profile.providerEntity(E.self, forModuleWithId: moduleId)
|
try? self?.profile.providerEntity(E.self, forModuleWithId: moduleId)
|
||||||
} set: { [weak self] in
|
} 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
|
Binding { [weak self] in
|
||||||
guard let foundModule = self?.module(withId: module.id) else {
|
guard let foundModule = self?.module(withId: module.id) else {
|
||||||
fatalError("Module not found in editor: \(module.id)")
|
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)
|
#if os(iOS)
|
||||||
|
|
||||||
import CommonLibrary
|
import CommonLibrary
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
import AppKit
|
import AppKit
|
||||||
import CommonLibrary
|
import CommonLibrary
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct Issue: Identifiable {
|
struct Issue: Identifiable {
|
|
@ -1,8 +1,8 @@
|
||||||
//
|
//
|
||||||
// ProviderEntityViewProviding.swift
|
// ProviderEntityViewProviding+Extensions.swift
|
||||||
// Passepartout
|
// 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.
|
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||||
//
|
//
|
||||||
// https://github.com/passepartoutvpn
|
// https://github.com/passepartoutvpn
|
||||||
|
@ -26,16 +26,6 @@
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
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 {
|
extension ProviderEntityViewProviding where Self: ProviderCompatibleModule, EntityType.Configuration: ProviderConfigurationIdentifiable & Codable {
|
||||||
func vpnProviderEntityView(
|
func vpnProviderEntityView(
|
||||||
with provider: ModuleMetadata.Provider,
|
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)
|
@AppStorage(AppPreference.profilesLayout.key)
|
||||||
private var layout: ProfilesLayout = .list
|
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
|
@StateObject
|
||||||
private var profileEditor = ProfileEditor()
|
private var profileEditor = ProfileEditor()
|
|
@ -75,7 +75,7 @@ private extension AppMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
var profilesList: some View {
|
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 {
|
func profileToggle(for header: ProfileHeader) -> some View {
|
|
@ -31,7 +31,7 @@ import SwiftUI
|
||||||
public struct AppMenuImage: View, TunnelContextProviding {
|
public struct AppMenuImage: View, TunnelContextProviding {
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var connectionObserver: ConnectionObserver
|
public var connectionObserver: ConnectionObserver
|
||||||
|
|
||||||
public init(connectionObserver: ConnectionObserver) {
|
public init(connectionObserver: ConnectionObserver) {
|
||||||
self.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