90 lines
2.7 KiB
Swift
90 lines
2.7 KiB
Swift
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])
|
|
}
|
|
}
|