Add preview support

This commit is contained in:
世界
2023-07-20 13:15:13 +08:00
parent fdbf50d875
commit 251f441a25
14 changed files with 135 additions and 59 deletions
@@ -2,4 +2,5 @@ import Foundation
public class ApplicationLibrary { public class ApplicationLibrary {
public static let bundle = Bundle(for: ApplicationLibrary.self) public static let bundle = Bundle(for: ApplicationLibrary.self)
public static let inPreview = false
} }
@@ -36,7 +36,7 @@ public struct ActiveDashboardView: View {
} else { } else {
VStack { VStack {
#if os(iOS) #if os(iOS)
if profile.status.isConnected { if ApplicationLibrary.inPreview || profile.status.isConnected {
ExtensionStatusView() ExtensionStatusView()
.listStyle(.automatic) .listStyle(.automatic)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
@@ -53,7 +53,7 @@ public struct ActiveDashboardView: View {
} }
} }
#else #else
if profile.status.isConnected { if ApplicationLibrary.inPreview || profile.status.isConnected {
ExtensionStatusView() ExtensionStatusView()
} }
FormView { FormView {
@@ -74,7 +74,7 @@ public struct ActiveDashboardView: View {
await switchProfile(selectedProfileID!) await switchProfile(selectedProfileID!)
} }
} }
.disabled(!profile.status.isSwitchable || reasserting) .disabled(!ApplicationLibrary.inPreview && (!profile.status.isSwitchable || reasserting))
} }
} }
} }
@@ -115,24 +115,32 @@ public struct ActiveDashboardView: View {
defer { defer {
isLoading = false isLoading = false
} }
do { if ApplicationLibrary.inPreview {
profileList = try ProfileManager.list() profileList = [
} catch { Profile(id: 0, name: "profile local", type: .local, path: ""),
errorMessage = error.localizedDescription Profile(id: 1, name: "profile remote", type: .remote, path: "", lastUpdated: Date(timeIntervalSince1970: 0)),
errorPresented = true ]
return selectedProfileID = 0
} } else {
if profileList.isEmpty { do {
return profileList = try ProfileManager.list()
} } catch {
errorMessage = error.localizedDescription
errorPresented = true
return
}
if profileList.isEmpty {
return
}
selectedProfileID = SharedPreferences.selectedProfileID selectedProfileID = SharedPreferences.selectedProfileID
if profileList.filter({ profile in if profileList.filter({ profile in
profile.id == selectedProfileID profile.id == selectedProfileID
}) })
.isEmpty { .isEmpty {
selectedProfileID = profileList[0].id! selectedProfileID = profileList[0].id!
SharedPreferences.selectedProfileID = selectedProfileID SharedPreferences.selectedProfileID = selectedProfileID
}
} }
} }
@@ -7,7 +7,9 @@ public struct DashboardView: View {
public var body: some View { public var body: some View {
viewBuilder { viewBuilder {
if let profile = extensionProfile.wrappedValue { if ApplicationLibrary.inPreview {
ActiveDashboardView()
} else if let profile = extensionProfile.wrappedValue {
ActiveDashboardView().environmentObject(profile) ActiveDashboardView().environmentObject(profile)
} else { } else {
FormView { FormView {
@@ -18,7 +18,16 @@ public struct ExtensionStatusView: View {
viewBuilder { viewBuilder {
VStack { VStack {
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount), alignment: .leading) { LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount), alignment: .leading) {
if let message { if ApplicationLibrary.inPreview {
StatusItem("Memory", "6.4 MiB")
StatusItem("Goroutines", "89")
StatusItem("Inbound Connections", "34")
StatusItem("Outbound Connections", "28")
StatusItem("Uplink", "38 B/s")
StatusItem("Downlink", "249 MiB/s")
StatusItem("Uplink Total", "52 MiB")
StatusItem("Downlink Total", "5.6 GiB")
} else if let message {
StatusItem("Memory", LibboxFormatBytes(message.memory)) StatusItem("Memory", LibboxFormatBytes(message.memory))
StatusItem("Goroutines", "\(message.goroutines)") StatusItem("Goroutines", "\(message.goroutines)")
if message.trafficAvailable { if message.trafficAvailable {
@@ -9,7 +9,18 @@ public struct StartStopButton: View {
public var body: some View { public var body: some View {
viewBuilder { viewBuilder {
if let profile = extensionProfile.wrappedValue { if ApplicationLibrary.inPreview {
#if os(iOS)
Toggle(isOn: .constant(true)) {
Text("Enabled")
}
#elseif os(macOS)
Button(action: {}, label: {
Label("Stop", systemImage: "stop.fill")
})
#endif
} else if let profile = extensionProfile.wrappedValue {
Button0(profile) Button0(profile)
} else { } else {
#if os(iOS) #if os(iOS)
@@ -17,6 +28,7 @@ public struct StartStopButton: View {
Text("Enabled") Text("Enabled")
} }
#elseif os(macOS) #elseif os(macOS)
Button(action: {}, label: { Button(action: {}, label: {
Label("Start", systemImage: "play.fill") Label("Start", systemImage: "play.fill")
}) })
@@ -43,9 +43,24 @@ public struct GroupListView: View {
} }
private func doReload() { private func doReload() {
connectTask?.cancel() if ApplicationLibrary.inPreview {
connectTask = Task.detached { groups = [
await connect() OutboundGroup(tag: "my_group", type: "selector", selected: "server", selectable: true, items: [
OutboundGroupItem(tag: "server", type: "Shadowsocks", urlTestTime: .now, urlTestDelay: 12),
OutboundGroupItem(tag: "server2", type: "WireGuard", urlTestTime: .now, urlTestDelay: 34),
OutboundGroupItem(tag: "auto", type: "URLTest", urlTestTime: .now, urlTestDelay: 100),
]),
OutboundGroup(tag: "group2", type: "urltest", selected: "client", selectable: false, items:
(0 ..< 234).map { index in
OutboundGroupItem(tag: "client\(index)", type: "Shadowsocks", urlTestTime: .now, urlTestDelay: UInt16(100 + index * 10))
}),
]
isLoading = false
} else {
connectTask?.cancel()
connectTask = Task.detached {
await connect()
}
} }
} }
+20 -8
View File
@@ -27,14 +27,26 @@ public class LogClient: ObservableObject {
} }
public func reconnect() { public func reconnect() {
if isConnected { if ApplicationLibrary.inPreview {
return logList = [
} "(packet-tunnel) log server started",
if let connectTask { "INFO[0000] router: loaded geoip database: 250 codes",
connectTask.cancel() "INFO[0000] router: loaded geosite database: 1400 codes",
} "INFO[0000] router: updated default interface en0, index 11",
connectTask = Task.detached { "inbound/tun[0]: started at utun3",
await self.connect() "sing-box started (1.666s)",
]
isConnected = true
} else {
if isConnected {
return
}
if let connectTask {
connectTask.cancel()
}
connectTask = Task.detached {
await self.connect()
}
} }
} }
+8 -4
View File
@@ -61,11 +61,15 @@ public struct LogView: View {
} }
private func connectLog() { private func connectLog() {
guard let profile = extensionProfile.wrappedValue else { if ApplicationLibrary.inPreview {
return
}
if profile.status.isConnected, !logClient.isConnected {
logClient.reconnect() logClient.reconnect()
} else {
guard let profile = extensionProfile.wrappedValue else {
return
}
if profile.status.isConnected, !logClient.isConnected {
logClient.reconnect()
}
} }
} }
} }
@@ -70,15 +70,12 @@ public extension NavigationPage {
} }
func visible(_ profile: ExtensionProfile?) -> Bool { func visible(_ profile: ExtensionProfile?) -> Bool {
if ApplicationLibrary.inPreview {
return true
}
switch self { switch self {
case .groups: case .groups:
return profile?.status.isConnectedStrict == true return profile?.status.isConnectedStrict == true
case .profiles, .settings:
#if os(iOS)
return profile?.status.isConnected != true
#else
fallthrough
#endif
default: default:
return true return true
} }
@@ -165,6 +165,7 @@ public struct NewProfileView: View {
var savePath = "" var savePath = ""
var remoteURL: String? = nil var remoteURL: String? = nil
var lastUpdated: Date? = nil
if profileType == .local { if profileType == .local {
let profileConfigDirectory = FilePath.sharedDirectory.appendingPathComponent("configs", isDirectory: true) let profileConfigDirectory = FilePath.sharedDirectory.appendingPathComponent("configs", isDirectory: true)
@@ -217,6 +218,7 @@ public struct NewProfileView: View {
try remoteContent.write(to: profileConfig, atomically: true, encoding: .utf8) try remoteContent.write(to: profileConfig, atomically: true, encoding: .utf8)
savePath = profileConfig.relativePath savePath = profileConfig.relativePath
remoteURL = remotePath remoteURL = remotePath
lastUpdated = .now
} }
try ProfileManager.create(Profile(name: profileName, type: profileType, path: savePath, remoteURL: remoteURL)) try ProfileManager.create(Profile(name: profileName, type: profileType, path: savePath, remoteURL: remoteURL))
} }
@@ -169,12 +169,19 @@ public struct ProfileView: View {
defer { defer {
isLoading = false isLoading = false
} }
do { if ApplicationLibrary.inPreview {
profileList = try ProfileManager.list() profileList = [
} catch { Profile(id: 0, name: "profile local", type: .local, path: ""),
errorMessage = error.localizedDescription Profile(id: 1, name: "profile remote", type: .remote, path: "", lastUpdated: Date(timeIntervalSince1970: 0)),
errorPresented = true ]
return } else {
do {
profileList = try ProfileManager.list()
} catch {
errorMessage = error.localizedDescription
errorPresented = true
return
}
} }
} }
@@ -148,10 +148,16 @@ public struct SettingView: View {
#endif #endif
disableMemoryLimit = SharedPreferences.disableMemoryLimit disableMemoryLimit = SharedPreferences.disableMemoryLimit
version = LibboxVersion() version = LibboxVersion()
dataSize = "Loading..." if ApplicationLibrary.inPreview {
isLoading = false dataSize = LibboxFormatBytes(1024 * 1024 * 10)
dataSize = (try? FilePath.workingDirectory.formattedSize()) ?? "Unknown" taiwanFlagAvailable = true
taiwanFlagAvailable = !DeviceCensorship.isChinaDevice() isLoading = false
} else {
dataSize = "Loading..."
taiwanFlagAvailable = !DeviceCensorship.isChinaDevice()
isLoading = false
dataSize = (try? FilePath.workingDirectory.formattedSize()) ?? "Unknown"
}
} }
private func clearWorkingDirectory() { private func clearWorkingDirectory() {
+2 -5
View File
@@ -15,19 +15,16 @@ public class Profile: Record, Identifiable, ObservableObject {
@Published public var autoUpdate: Bool @Published public var autoUpdate: Bool
public var lastUpdated: Date? public var lastUpdated: Date?
public init(id: Int64? = nil, name: String, order: UInt32 = 0, type: ProfileType, path: String, remoteURL: String? = nil) { public init(id: Int64? = nil, name: String, order: UInt32 = 0, type: ProfileType, path: String, remoteURL: String? = nil, lastUpdated: Date? = nil) {
self.id = id self.id = id
self.name = name self.name = name
self.order = order self.order = order
self.type = type self.type = type
self.path = path self.path = path
self.remoteURL = remoteURL self.remoteURL = remoteURL
self.lastUpdated = lastUpdated
autoUpdate = false autoUpdate = false
lastUpdated = nil
if type == .remote {
lastUpdated = Date()
}
super.init() super.init()
} }
+4
View File
@@ -54,12 +54,16 @@ struct MainView: View {
.environment(\.selection, $selection) .environment(\.selection, $selection)
.environment(\.extensionProfile, $extensionProfile) .environment(\.extensionProfile, $extensionProfile)
.environment(\.logClient, $logClient) .environment(\.logClient, $logClient)
.preferredColorScheme(.dark)
} }
private func loadProfile() async { private func loadProfile() async {
defer { defer {
profileLoading = false profileLoading = false
} }
if ApplicationLibrary.inPreview {
return
}
if let newProfile = try? await ExtensionProfile.load() { if let newProfile = try? await ExtensionProfile.load() {
if extensionProfile == nil || extensionProfile?.status == .invalid { if extensionProfile == nil || extensionProfile?.status == .invalid {
newProfile.register() newProfile.register()