add swiftUI code

This commit is contained in:
zeus
2025-01-22 14:09:10 +08:00
parent 68e7b7347c
commit 8a99853829
2531 changed files with 486215 additions and 0 deletions
@@ -0,0 +1,142 @@
import Foundation
import Libbox
import Library
import SwiftUI
@MainActor
public struct ActiveDashboardView: View {
@Environment(\.scenePhase) var scenePhase
@Environment(\.selection) private var parentSelection
@EnvironmentObject private var environments: ExtensionEnvironments
@EnvironmentObject private var profile: ExtensionProfile
@State private var isLoading = true
@State private var profileList: [ProfilePreview] = []
@State private var selectedProfileID: Int64 = 0
@State private var alert: Alert?
@State private var selection = DashboardPage.overview
@State private var systemProxyAvailable = false
@State private var systemProxyEnabled = false
public init() {}
public var body: some View {
if isLoading {
ProgressView().onAppear {
Task {
await doReload()
}
}
} else {
if ApplicationLibrary.inPreview {
body1
} else {
body1
.onAppear {
Task {
await doReloadSystemProxy()
}
}
.onChangeCompat(of: profile.status) { newStatus in
if newStatus == .connected {
Task {
await doReloadSystemProxy()
}
}
}
}
}
}
private var body1: some View {
VStack {
#if os(iOS) || os(tvOS)
if ApplicationLibrary.inPreview || profile.status.isConnectedStrict {
Picker("Page", selection: $selection) {
ForEach(DashboardPage.allCases) { page in
page.label
}
}
.pickerStyle(.segmented)
#if os(iOS)
.padding([.leading, .trailing])
.navigationBarTitleDisplayMode(.inline)
#endif
TabView(selection: $selection) {
ForEach(DashboardPage.allCases) { page in
page.contentView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled)
.tag(page)
}
}
.tabViewStyle(.page(indexDisplayMode: .always))
} else {
OverviewView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled)
}
#elseif os(macOS)
OverviewView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled)
#endif
}
.onReceive(environments.profileUpdate) { _ in
Task {
await doReload()
}
}
.onReceive(environments.selectedProfileUpdate) { _ in
Task {
selectedProfileID = await SharedPreferences.selectedProfileID.get()
if profile.status.isConnected {
await doReloadSystemProxy()
}
}
}
.alertBinding($alert)
}
private func doReload() async {
defer {
isLoading = false
}
if ApplicationLibrary.inPreview {
profileList = [
ProfilePreview(Profile(id: 0, name: "profile local", type: .local, path: "")),
ProfilePreview(Profile(id: 1, name: "profile remote", type: .remote, path: "", lastUpdated: Date(timeIntervalSince1970: 0))),
]
systemProxyAvailable = true
systemProxyEnabled = true
selectedProfileID = 0
} else {
do {
profileList = try await ProfileManager.list().map { ProfilePreview($0) }
if profileList.isEmpty {
return
}
selectedProfileID = await SharedPreferences.selectedProfileID.get()
if profileList.filter({ profile in
profile.id == selectedProfileID
})
.isEmpty {
selectedProfileID = profileList[0].id
await SharedPreferences.selectedProfileID.set(selectedProfileID)
}
} catch {
alert = Alert(error)
return
}
}
environments.emptyProfiles = profileList.isEmpty
}
private nonisolated func doReloadSystemProxy() async {
do {
let status = try LibboxNewStandaloneCommandClient()!.getSystemProxyStatus()
await MainActor.run {
systemProxyAvailable = status.available
systemProxyEnabled = status.enabled
}
} catch {
await MainActor.run {
alert = Alert(error)
}
}
}
}
@@ -0,0 +1,222 @@
//
// ActiveDashboardViewMACOS.swift
// ApplicationLibrary
//
// Created by Mac on 2024/10/22.
//
import Foundation
import Libbox
import Library
import SwiftUI
@MainActor
public struct ActiveDashboardViewMACOS: View {
@Environment(\.scenePhase) var scenePhase
@Environment(\.selection) private var parentSelection
@EnvironmentObject private var environments: ExtensionEnvironments
@EnvironmentObject private var profile: ExtensionProfile
@State private var isLoading = true
@State private var profileList: [ProfilePreview] = []
@State private var selectedProfileID: Int64 = 0
@State private var alert: Alert?
@State private var selection = DashboardPage.overview
@State private var systemProxyAvailable = false
@State private var systemProxyEnabled = false
public init() {}
public var body: some View {
if isLoading {
ProgressView().onAppear {
Task {
await doReload()
}
}
} else {
if ApplicationLibrary.inPreview {
body1
} else {
body1
.onAppear {
Task {
await doReloadSystemProxy()
}
}
.onChangeCompat(of: profile.status) { newStatus in
if newStatus == .connected {
Task {
await doReloadSystemProxy()
}
}
}
}
}
}
private var body1: some View {
VStack {
#if os(iOS) || os(tvOS)
if ApplicationLibrary.inPreview || profile.status.isConnectedStrict {
/* Picker("Page", selection: $selection) {
ForEach(DashboardPage.allCases) { page in
page.label
}
}
.pickerStyle(.segmented)
#if os(iOS)
.padding([.leading, .trailing])
.navigationBarTitleDisplayMode(.inline)
#endif
TabView(selection: $selection) {
ForEach(DashboardPage.allCases) { page in
page.contentView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled)
.tag(page)
}
}
.tabViewStyle(.page(indexDisplayMode: .always))
*/
OverviewView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled)
} else {
OverviewView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled)
}
#elseif os(macOS)
OverviewView($profileList, $selectedProfileID, $systemProxyAvailable, $systemProxyEnabled)
#endif
}
.onReceive(environments.profileUpdate) { _ in
Task {
await doReload()
}
}
.onReceive(environments.selectedProfileUpdate) { _ in
Task {
selectedProfileID = await SharedPreferences.selectedProfileID.get()
if profile.status.isConnected {
await doReloadSystemProxy()
}
}
}
.alertBinding($alert)
}
private func doReload() async {
defer {
isLoading = false
}
if ApplicationLibrary.inPreview {
profileList = [
ProfilePreview(Profile(id: 0, name: "profile local", type: .local, path: "")),
ProfilePreview(Profile(id: 1, name: "profile remote", type: .remote, path: "", lastUpdated: Date(timeIntervalSince1970: 0))),
]
systemProxyAvailable = true
systemProxyEnabled = true
selectedProfileID = 0
} else {
do {
profileList = try await ProfileManager.list().map { ProfilePreview($0) }
if profileList.isEmpty {
//add profile remote
Task {
if StoreManager.shared.getSuburlData().count > 5 {
await createProfile()
}
}
await environments.reload()
return
}
selectedProfileID = await SharedPreferences.selectedProfileID.get()
if profileList.filter({ profile in
profile.id == selectedProfileID
})
.isEmpty {
selectedProfileID = profileList[0].id
await SharedPreferences.selectedProfileID.set(selectedProfileID)
}
// try await ProfileManager.list().forEach { p in
// await ProfileManager.update(p)
// }
} catch {
alert = Alert(error)
return
}
}
environments.emptyProfiles = profileList.isEmpty
}
private nonisolated func doReloadSystemProxy() async {
do {
let status = try LibboxNewStandaloneCommandClient()!.getSystemProxyStatus()
await MainActor.run {
systemProxyAvailable = status.available
systemProxyEnabled = status.enabled
}
} catch {
await MainActor.run {
alert = Alert(error)
}
}
}
private func createProfile() async {
do {
try await createProfileBackground()
} catch {
print(error)
}
environments.profileUpdate.send()
}
private nonisolated func createProfileBackground() async throws {
let nextProfileID = try await ProfileManager.nextID()
var savePath = ""
let remoteURL: String = StoreManager.shared.getSuburlData()
var lastUpdated: Date? = nil
let remoteContent = try HTTPClient().getString(remoteURL)
var error: NSError?
LibboxCheckConfig(remoteContent, &error)
if let error {
throw error
}
let profileConfigDirectory = FilePath.sharedDirectory.appendingPathComponent("configs", isDirectory: true)
try FileManager.default.createDirectory(at: profileConfigDirectory, withIntermediateDirectories: true)
let profileConfig = profileConfigDirectory.appendingPathComponent("config_\(nextProfileID).json")
try remoteContent.write(to: profileConfig, atomically: true, encoding: .utf8)
savePath = profileConfig.relativePath
lastUpdated = .now
try await ProfileManager.create(Profile(
name: "VPN",
type: ProfileType.remote,
path: savePath,
remoteURL: remoteURL,
autoUpdate: true,
autoUpdateInterval: 60,
lastUpdated: lastUpdated
))
#if os(iOS) || os(tvOS)
try UIProfileUpdateTask.configure()
let list = try await ProfileManager.list()
list.forEach { profileOne in
print("\(profileOne.path) \n \(profileOne.remoteURL ?? "") \n \(profileOne.id ?? 0 )")
}
#else
try await ProfileUpdateTask.configure()
#endif
}
}
@@ -0,0 +1,114 @@
import Libbox
import Library
import SwiftUI
@MainActor
public struct ClashModeView: View {
@Environment(\.scenePhase) private var scenePhase
@StateObject private var commandClient = CommandClient(.clashMode)
@State private var clashMode = ""
@State private var alert: Alert?
public init() {}
public var body: some View {
VStack {
if commandClient.clashModeList.count > 1 {
Picker("", selection: Binding(get: {
clashMode
}, set: { newMode in
clashMode = newMode
Task {
await setClashMode(newMode)
await restartVPNServer()
}
}), content: {
ForEach(commandClient.clashModeList, id: \.self) { it in
/*if it.lowercased() == "rule" {
Text("")
}else if it.lowercased() == "direct" {
Text("")
}else if it.lowercased() == "global" {
Text("")
}else{
}*/
Text(it)
}
})
.pickerStyle(.segmented)
.padding([.top], 8)
}else{
//DEBUG
/*
let newclashModeList = ["","",""]
Picker("", selection: Binding(get: {
clashMode
}, set: { newMode in
}), content: {
ForEach(newclashModeList, id: \.self) { it in
/*if it.lowercased() == "rule" {
Text("")
}else if it.lowercased() == "direct" {
Text("")
}else if it.lowercased() == "global" {
Text("")
}else{
Text(it)
}*/
Text(it)
}
})
.pickerStyle(.segmented)
.padding([.top], 8)
*/
}
}
.onReceive(commandClient.$clashMode) { newMode in
clashMode = newMode
}
.padding([.leading, .trailing])
.onAppear {
commandClient.connect()
}
.onDisappear {
commandClient.disconnect()
}
.onChangeCompat(of: scenePhase) { newValue in
print("\(String(describing: commandClient.status))")
if newValue == .active {
commandClient.connect()
} else {
commandClient.disconnect()
}
}
.alertBinding($alert)
}
private nonisolated func setClashMode(_ newMode: String) async {
do {
try LibboxNewStandaloneCommandClient()!.setClashMode(newMode)
} catch {
await MainActor.run {
alert = Alert(error)
}
}
}
private nonisolated func restartVPNServer() async {
do {
try LibboxNewStandaloneCommandClient()!.serviceReload()
} catch {
await MainActor.run {
alert = Alert(error)
}
}
}
}
@@ -0,0 +1,46 @@
import Foundation
import Library
import SwiftUI
public enum DashboardPage: Int, CaseIterable, Identifiable {
public var id: Self {
self
}
case overview
case groups
}
public extension DashboardPage {
var title: String {
switch self {
case .overview:
return NSLocalizedString("Overview", comment: "")
case .groups:
return NSLocalizedString("Groups", comment: "")
}
}
var label: some View {
switch self {
case .overview:
return Label("Overview", systemImage: "text.and.command.macwindow")
case .groups:
return Label("Groups", systemImage: "rectangle.3.group.fill")
}
}
@MainActor
func contentView(_ profileList: Binding<[ProfilePreview]>, _ selectedProfileID: Binding<Int64>, _ systemProxyAvailable: Binding<Bool>, _ systemProxyEnabled: Binding<Bool>) -> some View {
viewBuilder {
switch self {
case .overview:
OverviewView(profileList, selectedProfileID, systemProxyAvailable, systemProxyEnabled)
case .groups:
GroupListView()
}
}
}
}
@@ -0,0 +1,246 @@
import Libbox
import Library
import SwiftUI
@MainActor
public struct DashboardView: View {
#if os(macOS)
@Environment(\.controlActiveState) private var controlActiveState
@State private var isLoading = true
@State private var systemExtensionInstalled = true
#endif
public init() {}
public var body: some View {
viewBuilder {
#if os(macOS)
HStack{
Button {
withAnimation{
// isUserViewActive.toggle()
}
} label: {
Image(systemName: "circle.grid.cross")
.font(.title2)
.padding(5)
.foregroundColor(.white)
.background(
RoundedRectangle(cornerRadius: 10)
.strokeBorder(.white.opacity(0.25),lineWidth: 1)
)
}
Spacer()
/* Button {
withAnimation{
isSubscriptionActive.toggle()
}
} label: {
Image(systemName: "slider.horizontal.3")
.font(.title2)
.padding(12)
.background(
RoundedRectangle(cornerRadius: 10)
.strokeBorder(.white.opacity(0.25),lineWidth: 1)
)
} */
// Text Bubble
Button {
withAnimation{
// isSubscriptionActive.toggle()
}
} label: {
Text("超值折扣")
.font(.footnote)
.foregroundColor(.white)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 4)
.fill(Color.orange)
)
.overlay(
TriangleShape()
.fill(Color.orange)
.frame(width: 10, height: 10)
.offset(x: 10)
, alignment: .trailing
)
// Panda Image with VIP Tag
HStack(spacing: 10){
Text(verbatim: "")
Image("applogo") // Replace with actual image asset
.resizable()
.frame(width: 30, height: 30)
.cornerRadius(15)
// Text("VIP")
// .font(.caption)
// .fontWeight(.bold)
// .foregroundColor(.yellow)
// .padding(4)
// .background(Color.orange)
// .cornerRadius(4)
}
}
.padding()
Button {
withAnimation{
// isKefuActive.toggle()
}
} label: {
Image(systemName: "person.fill.questionmark")
.font(.title2)
.padding(5)
.foregroundColor(.white)
.background(
RoundedRectangle(cornerRadius: 10)
.strokeBorder(.white.opacity(0.25),lineWidth: 1)
)
Text("客服").foregroundColor(.white)
}
}
if Variant.useSystemExtension {
viewBuilder {
if !systemExtensionInstalled {
FormView {
InstallSystemExtensionButton {
await reload()
}
}
} else {
DashboardView0()
}
}.onAppear {
Task {
await reload()
}
}
} else {
DashboardView0()
}
#else
DashboardView0()
#endif
}
#if os(macOS)
.onChangeCompat(of: controlActiveState) { newValue in
if newValue != .inactive {
if Variant.useSystemExtension {
if !isLoading {
Task {
await reload()
}
}
}
}
}
#endif
}
struct TriangleShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.minX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.closeSubpath()
return path
}
}
#if os(macOS)
private nonisolated func reload() async {
let systemExtensionInstalled = await SystemExtension.isInstalled()
await MainActor.run {
self.systemExtensionInstalled = systemExtensionInstalled
isLoading = false
}
}
#endif
struct DashboardView0: View {
@EnvironmentObject private var environments: ExtensionEnvironments
var body: some View {
if ApplicationLibrary.inPreview {
// ActiveDashboardView()/
ActiveDashboardViewMACOS()
} else if environments.extensionProfileLoading {
ProgressView()
} else if let profile = environments.extensionProfile {
DashboardView1().environmentObject(profile)
} else {
FormView {
InstallProfileButton { error in
//add profile remote
await environments.reload()
}
}
}
}
}
struct DashboardView1: View {
@EnvironmentObject private var environments: ExtensionEnvironments
@EnvironmentObject private var profile: ExtensionProfile
@State private var alert: Alert?
var body: some View {
VStack {
// ActiveDashboardView()
ActiveDashboardViewMACOS()
}
.alertBinding($alert)
.onChangeCompat(of: profile.status) { newValue in
if newValue == .disconnecting || newValue == .connected {
Task {
await checkServiceError()
}
}
}
}
private nonisolated func checkServiceError() async {
var error: NSError?
let message = LibboxReadServiceError(&error)
if error != nil {
return
}
await MainActor.run {
alert = Alert(title: Text("Service Error"), message: Text(message))
}
}
}
}
@@ -0,0 +1,134 @@
//
// DashboardViewMacOS.swift
// ApplicationLibrary
//
// Created by Mac on 2024/10/22.
//
import Libbox
import Library
import SwiftUI
@MainActor
public struct DashboardViewMacOS: View {
#if os(macOS)
@Environment(\.controlActiveState) private var controlActiveState
@State private var isLoading = true
@State private var systemExtensionInstalled = true
#endif
public init() {}
public var body: some View {
viewBuilder {
#if os(macOS)
if Variant.useSystemExtension {
viewBuilder {
if !systemExtensionInstalled {
FormView {
InstallSystemExtensionButton {
await reload()
}
}
} else {
DashboardView0()
}
}.onAppear {
Task {
await reload()
}
}
} else {
DashboardView0()
}
#else
DashboardView0()
#endif
}
#if os(macOS)
.onChangeCompat(of: controlActiveState) { newValue in
if newValue != .inactive {
if Variant.useSystemExtension {
if !isLoading {
Task {
await reload()
}
}
}
}
}
#endif
}
#if os(macOS)
private nonisolated func reload() async {
let systemExtensionInstalled = await SystemExtension.isInstalled()
await MainActor.run {
self.systemExtensionInstalled = systemExtensionInstalled
isLoading = false
}
}
#endif
struct DashboardView0: View {
@EnvironmentObject private var environments: ExtensionEnvironments
var body: some View {
if ApplicationLibrary.inPreview {
ActiveDashboardView()
} else if environments.extensionProfileLoading {
ProgressView()
} else if let profile = environments.extensionProfile {
DashboardView1().environmentObject(profile)
} else {
FormView {
InstallProfileButton {
//add profile remote
await environments.reload()
}
}
}
}
}
struct DashboardView1: View {
@EnvironmentObject private var environments: ExtensionEnvironments
@EnvironmentObject private var profile: ExtensionProfile
@State private var alert: Alert?
var body: some View {
VStack {
ActiveDashboardView()
}
.alertBinding($alert)
.onChangeCompat(of: profile.status) { newValue in
if newValue == .disconnecting || newValue == .connected {
Task {
await checkServiceError()
}
}
}
}
private nonisolated func checkServiceError() async {
var error: NSError?
let message = LibboxReadServiceError(&error)
if error != nil {
return
}
await MainActor.run {
alert = Alert(title: Text("Service Error"), message: Text(message))
}
}
}
}
@@ -0,0 +1,186 @@
import Libbox
import Library
import SwiftUI
public struct ExtensionStatusView: View {
@Environment(\.scenePhase) private var scenePhase
@StateObject private var commandClient = CommandClient(.status)
@State private var columnCount: Int = 4
@State private var alert: Alert?
private let infoFont = Font.system(.caption, design: .monospaced)
public init() {}
public var body: some View {
if columnCount == 1 {
ScrollView {
body0
}
} else {
body0
}
}
public var body0: some View {
viewBuilder {
VStack {
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount), alignment: .leading) {
if ApplicationLibrary.inPreview {
StatusItem("Status") {
StatusLine("Memory", "6.4 MB")
StatusLine("Goroutines", "89")
}
StatusItem("Connections") {
StatusLine("Inbound", "34")
StatusLine("Outbound", "28")
}
StatusItem("Traffic") {
StatusLine("Uplink", "38 B/s")
StatusLine("Downlink", "249 MB/s")
}
StatusItem("TrafficTotal") {
StatusLine("Uplink", "52 MB")
StatusLine("Downlink", "5.6 GB")
}
} else if let message = commandClient.status {
// StatusItem("Status") {
// StatusLine("Memory", LibboxFormatMemoryBytes(message.memory))
// StatusLine("Goroutines", "\(message.goroutines)")
// }
// StatusItem("Connections") {
// StatusLine("Inbound", "\(message.connectionsIn)")
// StatusLine("Outbound", "\(message.connectionsOut)")
// }
if message.trafficAvailable {
// StatusItem("") {
// StatusLine("", "\(LibboxFormatBytes(message.uplink))/s")
// StatusLine("", "\(LibboxFormatBytes(message.downlink))/s")
// }
StatusItem("总流量") {
HStack(spacing: 10, content: {
StatusLine("上传", LibboxFormatBytes(message.uplinkTotal))
Spacer()
StatusLine("下载", LibboxFormatBytes(message.downlinkTotal))
})
}
}
} else {
StatusItem("总流量") {
HStack(spacing: 10, content: {
StatusLine("上传", ".../s")
Spacer()
StatusLine("下载", ".../s")
})
}
// StatusItem("Connections") {
// StatusLine("Inbound", "...")
// StatusLine("Outbound", "...")
// }
}
}.background {
GeometryReader { geometry in
Rectangle()
.fill(.clear)
.frame(height: 1)
.onChangeCompat(of: geometry.size.width) { newValue in
updateColumnCount(newValue)
}
.onAppear {
updateColumnCount(geometry.size.width)
}
}.padding()
}
}
.frame(alignment: .topLeading)
.padding([.top, .leading, .trailing])
}
.onAppear {
commandClient.connect()
}
.onDisappear {
commandClient.disconnect()
}
.onChangeCompat(of: scenePhase) { newValue in
if newValue == .active {
commandClient.connect()
} else {
commandClient.disconnect()
}
}
.alertBinding($alert)
}
private func updateColumnCount(_ width: Double) {
let v = Int(Int(width) / 155)
let new = v <= 1 ? 1 : (v > 4 ? 4 : (v % 2 == 0 ? v : v - 1))
if new != columnCount {
columnCount = new
}
//
print(columnCount)
columnCount = 1
}
private struct StatusItem<T>: View where T: View {
private let title: String
@ViewBuilder private let content: () -> T
init(_ title: String, @ViewBuilder content: @escaping () -> T) {
self.title = title
self.content = content
}
var body: some View {
VStack(alignment: .leading) {
HStack {
Text(title)
.font(.subheadline)
Spacer()
}.padding(.bottom, 8)
content()
}
.frame(minWidth: 125, alignment: .topLeading)
#if os(tvOS)
.padding(EdgeInsets(top: 20, leading: 26, bottom: 20, trailing: 26))
#else
.padding(EdgeInsets(top: 10, leading: 13, bottom: 10, trailing: 13))
#endif
.background(backgroundColor.opacity(0.7))
.cornerRadius(10)
}
private var backgroundColor: Color {
#if os(iOS)
return Color(uiColor: .secondarySystemGroupedBackground)
#elseif os(macOS)
return Color(nsColor: .textBackgroundColor)
#elseif os(tvOS)
return Color(uiColor: .black)
#endif
}
}
private struct StatusLine: View {
private let name: String
private let value: String
init(_ name: String, _ value: String) {
self.name = name
self.value = value
}
var body: some View {
HStack {
Text(name)
.font(.subheadline)
.foregroundColor(.secondary)
Spacer()
Text(value)
.font(.subheadline)
}
}
}
}
@@ -0,0 +1,79 @@
import Library
import SwiftUI
@MainActor
public struct InstallProfileButton: View {
private let callback: (Error?) async -> Void
public init(_ callback: @escaping ((Error?) async -> Void)) {
self.callback = callback
}
public var body: some View {
parsePowerButton()
/* FormButton {
Task {
await installProfile()
}
} label: {
Label("VPN", systemImage: "lock.doc.fill")
.font(.title3)
.padding(15)
.foregroundColor(.red)
.background(
RoundedRectangle(cornerRadius: 10)
.strokeBorder(.white.opacity(0.25),lineWidth: 1)
)
}
.alertBinding($alert)*/
}
@ViewBuilder
func parsePowerButton()->some View{
Button {
Task {
await installProfile()
}
} label: {
ZStack{
ZStack{
//
// LottieView(animationFileName: "1d2a0fe5" , loopMode: .loop)
LottieView(animationFileName: "8dfa14a6" , loopMode: .loop).scaleEffect(0.2)
Text("开启服务").bold().font(.title2).foregroundStyle(Color.white)
}
}
// Max Frame...
.frame(width: 190,height: 190)
}.padding()
}
private func installProfile() async {
print("installProfile...")
do {
try await ExtensionProfile.install()
await callback(nil)
} catch {
print("installProfile: \(error.localizedDescription)")
await callback(error)
// alert = Alert(error)
}
}
}
@@ -0,0 +1,39 @@
#if os(macOS)
import Library
import SwiftUI
@MainActor
public struct InstallSystemExtensionButton: View {
@State private var alert: Alert?
private let callback: () async -> Void
public init(_ callback: @escaping () async -> Void) {
self.callback = callback
}
public var body: some View {
FormButton {
Task {
await installSystemExtension()
}
} label: {
Label("Install System Extension", systemImage: "lock.doc.fill")
}
.alertBinding($alert)
}
private func installSystemExtension() async {
do {
if let result = try await SystemExtension.install() {
if result == .willCompleteAfterReboot {
alert = Alert(errorMessage: "Need Reboot")
}
}
await callback()
} catch {
alert = Alert(error)
}
}
}
#endif
@@ -0,0 +1,296 @@
import Foundation
import Libbox
import Library
import SwiftUI
@MainActor
public struct OverviewView: View {
@Environment(\.selection) private var selection
@EnvironmentObject private var environments: ExtensionEnvironments
@EnvironmentObject private var profile: ExtensionProfile
@Binding private var profileList: [ProfilePreview]
@Binding private var selectedProfileID: Int64
@Binding private var systemProxyAvailable: Bool
@Binding private var systemProxyEnabled: Bool
@State private var alert: Alert?
@State private var reasserting = false
private var selectedProfileIDLocal: Binding<Int64> {
$selectedProfileID.withSetter { newValue in
reasserting = true
Task { [self] in
await switchProfile(newValue)
}
}
}
public init(_ profileList: Binding<[ProfilePreview]>, _ selectedProfileID: Binding<Int64>, _ systemProxyAvailable: Binding<Bool>, _ systemProxyEnabled: Binding<Bool>) {
_profileList = profileList
_selectedProfileID = selectedProfileID
_systemProxyAvailable = systemProxyAvailable
_systemProxyEnabled = systemProxyEnabled
}
public var body: some View {
if profileList.isEmpty
{
VStack {
parsePowerButton()
}.alertBinding($alert)
}else{
VStack {
#if os(iOS) || os(tvOS)
StartStopButton()
#elseif os(macOS)
StartStopButton().buttonStyle(PlainButtonStyle()) //
#endif
if ApplicationLibrary.inPreview || profile.status.isConnected {
ClashModeView()
ExtensionStatusView()
Spacer()
}
}.alertBinding($alert)
.disabled(!ApplicationLibrary.inPreview && (!profile.status.isSwitchable || reasserting))
}
}
@ViewBuilder
func parsePowerButton()->some View{
VStack{
Button {
} label: {
ZStack{
ZStack{
LottieView(animationFileName: "1d2a0fe5" , loopMode: .playOnce).scaleEffect(0.2)
}
}
// Max Frame...
.frame(width: 190,height: 190)
.background(
ZStack{
// Rings....
Circle()
.trim(from: 0.3, to: 0.5)
.stroke(
LinearGradient(colors: [
Color("gray"),
Color("gray")
.opacity(0.5),
Color("gray")
.opacity(0.3),
Color( "gray")
.opacity(0.1),
], startPoint: .leading, endPoint: .trailing),
style: StrokeStyle(lineWidth: 4, lineCap: .round, lineJoin: .round)
)
// Shadows...
.shadow(color: Color( "gray"), radius: 5, x: 1, y: -4)
Circle()
.trim(from: 0.3, to: 0.55)
.stroke(
LinearGradient(colors: [
Color( "gray2"),
Color( "gray2")
.opacity(0.5),
Color("gray2")
.opacity(0.3),
Color( "gray2")
.opacity(0.1),
], startPoint: .leading, endPoint: .trailing),
style: StrokeStyle(lineWidth: 4, lineCap: .round, lineJoin: .round)
)
// Shadows...
.shadow(color: Color( "gray2"), radius: 5, x: 1, y: -4)
.rotationEffect(.init(degrees: 160))
// Main Little Ring...
Circle()
.stroke(
Color("Ring1")
.opacity(0.01),
lineWidth: 11
)
// Toggling Shadow when button is Clicked...
.shadow(color: Color("Ring2").opacity( 0), radius: 5, x: 1, y: -4)
}
)
}.padding().disabled(true)
// .padding(.top,UIScreen.main.bounds.height < 750 ? 30 : 100)
Text("正在初始化...").font(.system(size: 18, weight: .semibold)).foregroundStyle(.white)
}
}
public var bodyOld: some View {
VStack {
if profileList.isEmpty {
VStack{
LottieView(animationFileName: "8c31d47d" , loopMode: .loop)
} .aspectRatio(contentMode: .fill)
.scaleEffect(0.1).frame(width: 190,height: 190).padding()
// VStack{
// LottieView(animationFileName: "51a05581" , loopMode: .loop)
// }.padding()
// Spacer()
//Text("Empty profiles")
//retry download profiles
} else {
#if os(iOS) || os(tvOS)
StartStopButton()
/**
Picker("Profile", selection: selectedProfileIDLocal, content: {
ForEach(profileList, id: \.self) { it in
Text(it.name).tag(it.id)
}
})
.pickerStyle(.segmented)
.padding([.top], 8)
*/
// Section("Profile") {
// Picker(selection: selectedProfileIDLocal) {
// ForEach(profileList, id: \.id) { profile in
// Text(profile.name).tag(profile.id)
// }
// } label: {}
// .pickerStyle(.inline)
// }
#elseif os(macOS)
StartStopButton().buttonStyle(PlainButtonStyle()) //
/*if ApplicationLibrary.inPreview || profile.status.isConnectedStrict, systemProxyAvailable {
Toggle("HTTP Proxy", isOn: $systemProxyEnabled)
.onChangeCompat(of: systemProxyEnabled) { newValue in
Task {
await setSystemProxyEnabled(newValue)
}
}
}
Section("Profile") {
ForEach(profileList, id: \.id) { profile in
Picker(profile.name, selection: selectedProfileIDLocal) {
Text("").tag(profile.id)
}
}
.pickerStyle(.radioGroup)
}*/
#endif
/*
FormView {
#if os(iOS) || os(tvOS)
StartStopButton()
if ApplicationLibrary.inPreview || profile.status.isConnectedStrict, systemProxyAvailable {
Toggle("HTTP Proxy", isOn: $systemProxyEnabled)
.onChangeCompat(of: systemProxyEnabled) { newValue in
Task {
await setSystemProxyEnabled(newValue)
}
}
}
/*
// Profile
Section("Profile") {
Picker(selection: selectedProfileIDLocal) {
ForEach(profileList, id: \.id) { profile in
Text(profile.name).tag(profile.id)
}
} label: {}
.pickerStyle(.inline)
}*/
#elseif os(macOS)
if ApplicationLibrary.inPreview || profile.status.isConnectedStrict, systemProxyAvailable {
Toggle("HTTP Proxy", isOn: $systemProxyEnabled)
.onChangeCompat(of: systemProxyEnabled) { newValue in
Task {
await setSystemProxyEnabled(newValue)
}
}
}
Section("Profile") {
ForEach(profileList, id: \.id) { profile in
Picker(profile.name, selection: selectedProfileIDLocal) {
Text("").tag(profile.id)
}
}
.pickerStyle(.radioGroup)
}
#endif
}
*/
if ApplicationLibrary.inPreview || profile.status.isConnected {
ClashModeView()
ExtensionStatusView()
}
}
}
.alertBinding($alert)
.disabled(!ApplicationLibrary.inPreview && (!profile.status.isSwitchable || reasserting))
}
private func switchProfile(_ newProfileID: Int64) async {
await SharedPreferences.selectedProfileID.set(newProfileID)
environments.selectedProfileUpdate.send()
if profile.status.isConnected {
do {
try await serviceReload()
} catch {
alert = Alert(error)
}
}
reasserting = false
}
private nonisolated func serviceReload() async throws {
try LibboxNewStandaloneCommandClient()!.serviceReload()
}
private nonisolated func setSystemProxyEnabled(_ isEnabled: Bool) async {
do {
try LibboxNewStandaloneCommandClient()!.setSystemProxyEnabled(isEnabled)
await SharedPreferences.systemProxyEnabled.set(isEnabled)
} catch {
await MainActor.run {
alert = Alert(error)
}
}
}
}
@@ -0,0 +1,335 @@
import Library
import Lottie
import NetworkExtension
import SwiftUI
import Libbox
import Library
import SwiftUI
@MainActor
public struct StartStopButton: View {
@EnvironmentObject private var environments: ExtensionEnvironments
public init() {}
public var body: some View {
viewBuilder {
if ApplicationLibrary.inPreview {
#if os(iOS) || os(tvOS)
Toggle(isOn: .constant(true)) {
Text("Enabled")
}
#elseif os(macOS)
Button {} label: {
Label("Stop", systemImage: "stop.fill")
}
#endif
} else if let profile = environments.extensionProfile {
Button0().environmentObject(profile)
} else {
#if os(iOS) || os(tvOS)
EmptyView()
#elseif os(macOS)
Button {} label: {
Label("Start", systemImage: "play.fill")
}
.disabled(true)
#endif
}
}
.disabled(environments.emptyProfiles)
}
private struct Button0: View {
@EnvironmentObject private var environments: ExtensionEnvironments
@EnvironmentObject private var profile: ExtensionProfile
@State private var alert: Alert?
@State var vpnStatus : NEVPNStatus = .invalid
var body: some View {
viewBuilder {
#if os(iOS) || os(tvOS)
/* Toggle(isOn: Binding(get: {
profile.status.isConnected
}, set: { newValue, _ in
Task {
await switchProfile(newValue)
}
})) {
Text("Enabled")
}*/
VStack{
PowerButton()
Label {
if vpnStatus == .connected {
Text("已连接")
}else if vpnStatus == .disconnected {
Text("断开连接")
}else if vpnStatus == .connecting {
Text("连接中...")
}else if vpnStatus == .disconnecting {
Text("断开中...")
}else {
Text("切换中...")
}
} icon: {
Image(systemName: vpnStatus.isConnectedStrict ? "checkmark.shield" : "shield.slash")
}
.font(.system(size: 18, weight: .semibold))
// Spacer()
}
#elseif os(macOS)
VStack{
PowerButton().background(Color.clear)
Label {
if vpnStatus == .connected {
Text("已连接")
}else if vpnStatus == .disconnected {
Text("断开连接")
}else if vpnStatus == .connecting {
Text("连接中...")
}else if vpnStatus == .disconnecting {
Text("断开中...")
}else {
Text("切换中...")
}
} icon: {
Image(systemName: vpnStatus.isConnectedStrict ? "checkmark.shield" : "shield.slash")
}
.font(.system(size: 18, weight: .semibold))
//Spacer()
}
/*Button {
Task {
await switchProfile(!profile.status.isConnectedStrict)
}
} label: {
if !profile.status.isConnected {
Label("Start", systemImage: "play.fill")
} else {
Label("Stop", systemImage: "stop.fill")
}
}
*/
#endif
}
.disabled(!profile.status.isEnabled)
.alertBinding($alert)
.onAppear {
Task{
vpnStatus = profile.status //.isConnected
}
}.onChangeCompat(of: profile.status) { newStatus in
vpnStatus = newStatus
// if newStatus == .connected {
// Task {
// await doReloadSystemProxy()
// }
// }
}
}
@ViewBuilder
func PowerButton()->some View{
Button {
Task {
if self.vpnStatus.isConnected {
await switchProfile(false)
}else{
await switchProfile(true)
}
}
} label: {
ZStack{
ZStack{
#if os(iOS) || os(tvOS)
#endif
if vpnStatus == .connected {
LottieView(animationFileName: "51a05581" , loopMode: .playOnce).scaleEffect(0.5)
}else if (vpnStatus == .connecting || vpnStatus == .disconnecting) {
LottieView(animationFileName: "65ea130a" , loopMode: .loop)
} else{
LottieView(animationFileName: "1d2a0fe5" , loopMode: .playOnce).scaleEffect(0.2)
}
}
}
// Max Frame...
.frame(width: 190,height: 190)
.background(
ZStack{
// Rings....
Circle()
.trim(from: vpnStatus.isConnected ? 0 : 0.3, to: vpnStatus.isConnected ? 1 : 0.5)
.stroke(
LinearGradient(colors: [
Color(vpnStatus.isConnected ? "Ring1" : "gray"),
Color(vpnStatus.isConnected ? "Ring1" : "gray")
.opacity(0.5),
Color(vpnStatus.isConnected ? "Ring1" : "gray")
.opacity(0.3),
Color(vpnStatus.isConnected ? "Ring1" : "gray")
.opacity(0.1),
], startPoint: .leading, endPoint: .trailing),
style: StrokeStyle(lineWidth: 4, lineCap: .round, lineJoin: .round)
)
// Shadows...
.shadow(color: Color(vpnStatus.isConnected ? "Ring1" : "gray"), radius: 5, x: 1, y: -4)
Circle()
.trim(from: vpnStatus.isConnected ? 0 : 0.3, to: vpnStatus.isConnected ? 1 : 0.55)
.stroke(
LinearGradient(colors: [
Color(vpnStatus.isConnected ? "Ring2" : "gray2"),
Color(vpnStatus.isConnected ? "Ring2" : "gray2")
.opacity(0.5),
Color(vpnStatus.isConnected ? "Ring2" : "gray2")
.opacity(0.3),
Color(vpnStatus.isConnected ? "Ring2" : "gray2")
.opacity(0.1),
], startPoint: .leading, endPoint: .trailing),
style: StrokeStyle(lineWidth: 4, lineCap: .round, lineJoin: .round)
)
// Shadows...
.shadow(color: Color(vpnStatus.isConnected ? "Ring2" : "gray2"), radius: 5, x: 1, y: -4)
.rotationEffect(.init(degrees: 160))
// Main Little Ring...
Circle()
.stroke(
Color("Ring1")
.opacity(0.01),
lineWidth: 11
)
// Toggling Shadow when button is Clicked...
.shadow(color: Color("Ring2").opacity(vpnStatus.isConnected ? 0.04 : 0), radius: 5, x: 1, y: -4)
}
)
}.padding()
// .padding(.top,UIScreen.main.bounds.height < 750 ? 30 : 100)
}
private nonisolated func switchProfile(_ isEnabled: Bool) async {
do {
if isEnabled {
try await profile.start()
await environments.logClient.connect()
} else {
try await profile.stop()
}
} catch {
await MainActor.run {
alert = Alert(error)
}
}
}
/*private nonisolated func doReloadSystemProxy() async {
do {
let status = try LibboxNewStandaloneCommandClient()!.getSystemProxyStatus()
await MainActor.run {
}
} catch {
await MainActor.run {
alert = Alert(error)
}
}
}*/
}
}
#if os(iOS) || os(tvOS)
struct LottieView: UIViewRepresentable {
var animationFileName: String
let loopMode: LottieLoopMode
func updateUIView(_ uiView: UIViewType, context: Context) {
}
func makeUIView(context: Context) -> Lottie.LottieAnimationView {
let animationView = LottieAnimationView(name: animationFileName)
animationView.loopMode = loopMode
animationView.play()
animationView.contentMode = .scaleAspectFit
return animationView
}
}
#elseif os(macOS)
struct LottieView: NSViewRepresentable {
var animationFileName: String
let loopMode: LottieLoopMode
func makeNSView(context: Context) -> NSView {
let view = NSView()
let animationView = LottieAnimationView(name: animationFileName)
animationView.loopMode = loopMode
animationView.play()
animationView.contentMode = .scaleAspectFit
// Add the Lottie animation view as a subview of the NSView
animationView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(animationView)
// Set up constraints to fill the parent view
NSLayoutConstraint.activate([
animationView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
animationView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
animationView.topAnchor.constraint(equalTo: view.topAnchor),
animationView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
return view
}
func updateNSView(_ nsView: NSView, context: Context) {
// Optionally implement updates to the NSView
}
}
#endif
@@ -0,0 +1,58 @@
//
// XufeiTixingButton.swift
// ApplicationLibrary
//
// Created by Mac on 2024/10/26.
//
import Foundation
//
import Library
import SwiftUI
@MainActor
public struct XufeiTixingButton: View {
private let callback: () async -> Void
public init(_ callback: @escaping (() async -> Void)) {
self.callback = callback
}
public var body: some View {
parsePowerButton()
}
@ViewBuilder
func parsePowerButton()->some View{
Button {
Task {
await callback()
}
} label: {
ZStack{
ZStack{
//
// LottieView(animationFileName: "1d2a0fe5" , loopMode: .loop)
LottieView(animationFileName: "8dfa14a6" , loopMode: .loop).scaleEffect(0.2)
Text("开启服务").bold().font(.title2).foregroundStyle(Color.white)
//LottieView(animationFileName: "8dfa14a6" , loopMode: .loop).scaleEffect(0.5)
}
}
// Max Frame...
.frame(width: 190,height: 190)
}.padding()
}
}