Decouple WidgetExtension from Library
This commit is contained in:
@@ -1,12 +1,11 @@
|
|||||||
import AppIntents
|
import AppIntents
|
||||||
import Library
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import WidgetKit
|
import WidgetKit
|
||||||
|
|
||||||
struct ServiceToggleControl: ControlWidget {
|
struct ServiceToggleControl: ControlWidget {
|
||||||
var body: some ControlWidgetConfiguration {
|
var body: some ControlWidgetConfiguration {
|
||||||
StaticControlConfiguration(
|
StaticControlConfiguration(
|
||||||
kind: ExtensionProfile.controlKind,
|
kind: WidgetAppConfiguration.widgetControlKind,
|
||||||
provider: Provider()
|
provider: Provider()
|
||||||
) { value in
|
) { value in
|
||||||
ControlWidgetToggle(
|
ControlWidgetToggle(
|
||||||
@@ -15,7 +14,7 @@ struct ServiceToggleControl: ControlWidget {
|
|||||||
action: ToggleServiceControlIntent()
|
action: ToggleServiceControlIntent()
|
||||||
) { isOn in
|
) { isOn in
|
||||||
Label(isOn ? "Running" : "Stopped", systemImage: "shippingbox.fill")
|
Label(isOn ? "Running" : "Stopped", systemImage: "shippingbox.fill")
|
||||||
// .controlWidgetActionHint(isOn ? "Stop" : "Start")
|
.controlWidgetActionHint(isOn ? "Stop" : "Start")
|
||||||
}
|
}
|
||||||
.tint(.init(red: CGFloat(Double(69) / 255), green: CGFloat(Double(90) / 255), blue: CGFloat(Double(100) / 255)))
|
.tint(.init(red: CGFloat(Double(69) / 255), green: CGFloat(Double(90) / 255), blue: CGFloat(Double(100) / 255)))
|
||||||
}
|
}
|
||||||
@@ -31,10 +30,7 @@ extension ServiceToggleControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func currentValue() async throws -> Bool {
|
func currentValue() async throws -> Bool {
|
||||||
guard let extensionProfile = try await (ExtensionProfile.load()) else {
|
try await WidgetTunnelControl.currentIsStarted()
|
||||||
return false
|
|
||||||
}
|
|
||||||
return await extensionProfile.status.isStarted
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,14 +42,7 @@ struct ToggleServiceControlIntent: SetValueIntent {
|
|||||||
var value: Bool
|
var value: Bool
|
||||||
|
|
||||||
func perform() async throws -> some IntentResult {
|
func perform() async throws -> some IntentResult {
|
||||||
guard let extensionProfile = try await (ExtensionProfile.load()) else {
|
try await WidgetTunnelControl.setStarted(value)
|
||||||
return .result()
|
|
||||||
}
|
|
||||||
if value {
|
|
||||||
try await extensionProfile.start()
|
|
||||||
} else {
|
|
||||||
try await extensionProfile.stop()
|
|
||||||
}
|
|
||||||
return .result()
|
return .result()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import Foundation
|
||||||
|
import NetworkExtension
|
||||||
|
|
||||||
|
enum WidgetAppConfiguration {
|
||||||
|
static let packageName: String = {
|
||||||
|
guard let value = Bundle.main.object(forInfoDictionaryKey: "BasePackageIdentifier") as? String else {
|
||||||
|
fatalError("Missing BasePackageIdentifier in Info.plist")
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}()
|
||||||
|
|
||||||
|
static let appGroupID: String = {
|
||||||
|
guard let value = Bundle.main.object(forInfoDictionaryKey: "AppGroupIdentifier") as? String else {
|
||||||
|
fatalError("Missing AppGroupIdentifier in Info.plist")
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}()
|
||||||
|
|
||||||
|
static var widgetControlKind: String { "\(packageName).widget.ServiceToggle" }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NEVPNStatus {
|
||||||
|
var isStarted: Bool {
|
||||||
|
switch self {
|
||||||
|
case .connecting, .connected, .reasserting:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WidgetTunnelControl {
|
||||||
|
static func currentIsStarted() async throws -> Bool {
|
||||||
|
guard let manager = try await loadManager() else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return manager.connection.status.isStarted
|
||||||
|
}
|
||||||
|
|
||||||
|
static func setStarted(_ started: Bool) async throws {
|
||||||
|
guard let manager = try await loadManager() else {
|
||||||
|
NSLog("[WidgetTunnelControl] No tunnel configuration found")
|
||||||
|
throw NSError(domain: "WidgetTunnelControl", code: 1, userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Tunnel configuration not found",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
if started {
|
||||||
|
if manager.isEnabled == false {
|
||||||
|
manager.isEnabled = true
|
||||||
|
try await manager.saveToPreferences()
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
try manager.connection.startVPNTunnel()
|
||||||
|
} catch {
|
||||||
|
NSLog("[WidgetTunnelControl] startVPNTunnel failed: \(error.localizedDescription)")
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
manager.connection.stopVPNTunnel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func loadManager() async throws -> NETunnelProviderManager? {
|
||||||
|
let managers = try await NETunnelProviderManager.loadAllFromPreferences()
|
||||||
|
return managers.first
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
3AE395F42C21A5CA00647718 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AE395F32C21A5CA00647718 /* WidgetKit.framework */; };
|
3AE395F42C21A5CA00647718 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AE395F32C21A5CA00647718 /* WidgetKit.framework */; };
|
||||||
3AE395F62C21A5CA00647718 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AE395F52C21A5CA00647718 /* SwiftUI.framework */; };
|
3AE395F62C21A5CA00647718 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AE395F52C21A5CA00647718 /* SwiftUI.framework */; };
|
||||||
3AE396032C21A5CC00647718 /* WidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3AE395F22C21A5CA00647718 /* WidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
3AE396032C21A5CC00647718 /* WidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3AE395F22C21A5CA00647718 /* WidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
3AE396072C21A60C00647718 /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
|
||||||
3AE4D0BD2A6E2DDC009FEA9E /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
3AE4D0BD2A6E2DDC009FEA9E /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
||||||
3AE4D0BE2A6E2DDC009FEA9E /* Library.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
3AE4D0BE2A6E2DDC009FEA9E /* Library.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
3AE595822F08C3D400C13426 /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
3AE595822F08C3D400C13426 /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; };
|
||||||
@@ -192,13 +191,6 @@
|
|||||||
remoteGlobalIDString = 3AE395F12C21A5CA00647718;
|
remoteGlobalIDString = 3AE395F12C21A5CA00647718;
|
||||||
remoteInfo = WidgetExtensionExtension;
|
remoteInfo = WidgetExtensionExtension;
|
||||||
};
|
};
|
||||||
3AE396092C21A60C00647718 /* PBXContainerItemProxy */ = {
|
|
||||||
isa = PBXContainerItemProxy;
|
|
||||||
containerPortal = 3AEC20BD2A45991900A63465 /* Project object */;
|
|
||||||
proxyType = 1;
|
|
||||||
remoteGlobalIDString = 3AEC211C2A459B4700A63465;
|
|
||||||
remoteInfo = Library;
|
|
||||||
};
|
|
||||||
3AE4D0BA2A6E2C55009FEA9E /* PBXContainerItemProxy */ = {
|
3AE4D0BA2A6E2C55009FEA9E /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 3AEC20BD2A45991900A63465 /* Project object */;
|
containerPortal = 3AEC20BD2A45991900A63465 /* Project object */;
|
||||||
@@ -707,7 +699,6 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
3AE396072C21A60C00647718 /* Library.framework in Frameworks */,
|
|
||||||
3AE395F62C21A5CA00647718 /* SwiftUI.framework in Frameworks */,
|
3AE395F62C21A5CA00647718 /* SwiftUI.framework in Frameworks */,
|
||||||
3AE395F42C21A5CA00647718 /* WidgetKit.framework in Frameworks */,
|
3AE395F42C21A5CA00647718 /* WidgetKit.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
@@ -1098,7 +1089,6 @@
|
|||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
3AE3960A2C21A60C00647718 /* PBXTargetDependency */,
|
|
||||||
);
|
);
|
||||||
fileSystemSynchronizedGroups = (
|
fileSystemSynchronizedGroups = (
|
||||||
3ADDCFD52E8B72BC009ACE1D /* WidgetExtension */,
|
3ADDCFD52E8B72BC009ACE1D /* WidgetExtension */,
|
||||||
@@ -1722,11 +1712,6 @@
|
|||||||
target = 3AE395F12C21A5CA00647718 /* WidgetExtension */;
|
target = 3AE395F12C21A5CA00647718 /* WidgetExtension */;
|
||||||
targetProxy = 3AE396012C21A5CC00647718 /* PBXContainerItemProxy */;
|
targetProxy = 3AE396012C21A5CC00647718 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
3AE3960A2C21A60C00647718 /* PBXTargetDependency */ = {
|
|
||||||
isa = PBXTargetDependency;
|
|
||||||
target = 3AEC211C2A459B4700A63465 /* Library */;
|
|
||||||
targetProxy = 3AE396092C21A60C00647718 /* PBXContainerItemProxy */;
|
|
||||||
};
|
|
||||||
3AE4D0BB2A6E2C55009FEA9E /* PBXTargetDependency */ = {
|
3AE4D0BB2A6E2C55009FEA9E /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = 3AEC211C2A459B4700A63465 /* Library */;
|
target = 3AEC211C2A459B4700A63465 /* Library */;
|
||||||
|
|||||||
Reference in New Issue
Block a user