Add CI/CD configuration and API documentation

This commit is contained in:
2026-07-01 21:40:53 +08:00
commit c590135d68
4168 changed files with 740252 additions and 0 deletions
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,13 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>
</plist>
@@ -0,0 +1,46 @@
import AppIntents
import Library
import WidgetKit
struct ConfigurationIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Configuration"
static var description = IntentDescription("Configuration sing-bix widget.")
}
struct StartServiceIntent: AppIntent {
static var title: LocalizedStringResource = "Start sing-box"
static var description =
IntentDescription("Start sing-box servie")
func perform() async throws -> some IntentResult {
guard let extensionProfile = try await (ExtensionProfile.load()) else {
throw NSError(domain: "NetworkExtension not installed", code: 0)
}
if extensionProfile.status == .connected {
return .result()
} else {
try await extensionProfile.start()
}
return .result()
}
}
struct StopServiceIntent: AppIntent {
static var title: LocalizedStringResource = "Stop sing-box"
static var description =
IntentDescription("Stop sing-box service")
static var parameterSummary: some ParameterSummary {
Summary("Stop sing-box service")
}
func perform() async throws -> some IntentResult {
guard let extensionProfile = try await (ExtensionProfile.load()) else {
return .result()
}
extensionProfile.stop()
return .result()
}
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.sagernet.sfa</string>
</array>
</dict>
</plist>
@@ -0,0 +1,89 @@
import AppIntents
import Libbox
import Library
import SwiftUI
import WidgetKit
struct Provider: AppIntentTimelineProvider {
func placeholder(in _: Context) -> ExtensionStatus {
ExtensionStatus(date: .now, isConnected: false, profileList: [])
}
func snapshot(for _: ConfigurationIntent, in _: Context) async -> ExtensionStatus {
var status = ExtensionStatus(date: .now, isConnected: false, profileList: [])
do {
status.isConnected = try await ExtensionProfile.load()?.status.isStrictConnected ?? false
let profileList = try ProfileManager.list()
let selectedProfileID = SharedPreferences.selectedProfileID
for profile in profileList {
status.profileList.append(ProfileEntry(profile: profile, isSelected: profile.id == selectedProfileID))
}
} catch {}
return status
}
func timeline(for intent: ConfigurationIntent, in context: Context) async -> Timeline<ExtensionStatus> {
await Timeline(entries: [snapshot(for: intent, in: context)], policy: .never)
}
}
struct ExtensionStatus: TimelineEntry {
var date: Date
var isConnected: Bool
var profileList: [ProfileEntry]
}
struct ProfileEntry {
let profile: Profile
let isSelected: Bool
}
struct WidgetView: View {
@Environment(\.widgetFamily) private var family
var status: ExtensionStatus
var body: some View {
VStack {
LabeledContent {
Text(LibboxVersion())
.font(.caption)
} label: {
Text("sing-box")
.font(.headline)
}
VStack {
viewBuilder {
if !status.isConnected {
Button(intent: StartServiceIntent()) {
Image(systemName: "play.fill")
}
} else {
Button(intent: StopServiceIntent()) {
Image(systemName: "stop.fill")
}
}
}
.controlSize(.large)
.invalidatableContent()
}
.frame(maxHeight: .infinity, alignment: .center)
}
.frame(maxHeight: .infinity, alignment: .topLeading)
.containerBackground(.fill.tertiary, for: .widget)
}
}
struct WidgetExtension: Widget {
@State private var extensionProfile: ExtensionProfile?
var body: some WidgetConfiguration {
AppIntentConfiguration(kind: "sing-box", intent: ConfigurationIntent.self, provider: Provider()) { status in
WidgetView(status: status)
}
.supportedFamilies([.systemSmall])
}
}
@@ -0,0 +1,9 @@
import SwiftUI
import WidgetKit
@main
struct WidgetExtensionBundle: WidgetBundle {
var body: some Widget {
WidgetExtension()
}
}