Add preview support
This commit is contained in:
@@ -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,6 +115,13 @@ public struct ActiveDashboardView: View {
|
|||||||
defer {
|
defer {
|
||||||
isLoading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
|
if ApplicationLibrary.inPreview {
|
||||||
|
profileList = [
|
||||||
|
Profile(id: 0, name: "profile local", type: .local, path: ""),
|
||||||
|
Profile(id: 1, name: "profile remote", type: .remote, path: "", lastUpdated: Date(timeIntervalSince1970: 0)),
|
||||||
|
]
|
||||||
|
selectedProfileID = 0
|
||||||
|
} else {
|
||||||
do {
|
do {
|
||||||
profileList = try ProfileManager.list()
|
profileList = try ProfileManager.list()
|
||||||
} catch {
|
} catch {
|
||||||
@@ -135,6 +142,7 @@ public struct ActiveDashboardView: View {
|
|||||||
SharedPreferences.selectedProfileID = selectedProfileID
|
SharedPreferences.selectedProfileID = selectedProfileID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func switchProfile(_ newProfileID: Int64) {
|
private func switchProfile(_ newProfileID: Int64) {
|
||||||
SharedPreferences.selectedProfileID = newProfileID
|
SharedPreferences.selectedProfileID = newProfileID
|
||||||
|
|||||||
@@ -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,11 +43,26 @@ public struct GroupListView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func doReload() {
|
private func doReload() {
|
||||||
|
if ApplicationLibrary.inPreview {
|
||||||
|
groups = [
|
||||||
|
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?.cancel()
|
||||||
connectTask = Task.detached {
|
connectTask = Task.detached {
|
||||||
await connect()
|
await connect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func connect() async {
|
private func connect() async {
|
||||||
let clientOptions = LibboxCommandClientOptions()
|
let clientOptions = LibboxCommandClientOptions()
|
||||||
|
|||||||
@@ -27,6 +27,17 @@ public class LogClient: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func reconnect() {
|
public func reconnect() {
|
||||||
|
if ApplicationLibrary.inPreview {
|
||||||
|
logList = [
|
||||||
|
"(packet-tunnel) log server started",
|
||||||
|
"INFO[0000] router: loaded geoip database: 250 codes",
|
||||||
|
"INFO[0000] router: loaded geosite database: 1400 codes",
|
||||||
|
"INFO[0000] router: updated default interface en0, index 11",
|
||||||
|
"inbound/tun[0]: started at utun3",
|
||||||
|
"sing-box started (1.666s)",
|
||||||
|
]
|
||||||
|
isConnected = true
|
||||||
|
} else {
|
||||||
if isConnected {
|
if isConnected {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -37,6 +48,7 @@ public class LogClient: ObservableObject {
|
|||||||
await self.connect()
|
await self.connect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func connect() async {
|
private func connect() async {
|
||||||
let clientOptions = LibboxCommandClientOptions()
|
let clientOptions = LibboxCommandClientOptions()
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ public struct LogView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func connectLog() {
|
private func connectLog() {
|
||||||
|
if ApplicationLibrary.inPreview {
|
||||||
|
logClient.reconnect()
|
||||||
|
} else {
|
||||||
guard let profile = extensionProfile.wrappedValue else {
|
guard let profile = extensionProfile.wrappedValue else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -69,4 +72,5 @@ public struct LogView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,6 +169,12 @@ public struct ProfileView: View {
|
|||||||
defer {
|
defer {
|
||||||
isLoading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
|
if ApplicationLibrary.inPreview {
|
||||||
|
profileList = [
|
||||||
|
Profile(id: 0, name: "profile local", type: .local, path: ""),
|
||||||
|
Profile(id: 1, name: "profile remote", type: .remote, path: "", lastUpdated: Date(timeIntervalSince1970: 0)),
|
||||||
|
]
|
||||||
|
} else {
|
||||||
do {
|
do {
|
||||||
profileList = try ProfileManager.list()
|
profileList = try ProfileManager.list()
|
||||||
} catch {
|
} catch {
|
||||||
@@ -177,6 +183,7 @@ public struct ProfileView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func updateProfile(_ profile: Profile) {
|
private func updateProfile(_ profile: Profile) {
|
||||||
do {
|
do {
|
||||||
|
|||||||
@@ -148,10 +148,16 @@ public struct SettingView: View {
|
|||||||
#endif
|
#endif
|
||||||
disableMemoryLimit = SharedPreferences.disableMemoryLimit
|
disableMemoryLimit = SharedPreferences.disableMemoryLimit
|
||||||
version = LibboxVersion()
|
version = LibboxVersion()
|
||||||
|
if ApplicationLibrary.inPreview {
|
||||||
|
dataSize = LibboxFormatBytes(1024 * 1024 * 10)
|
||||||
|
taiwanFlagAvailable = true
|
||||||
|
isLoading = false
|
||||||
|
} else {
|
||||||
dataSize = "Loading..."
|
dataSize = "Loading..."
|
||||||
|
taiwanFlagAvailable = !DeviceCensorship.isChinaDevice()
|
||||||
isLoading = false
|
isLoading = false
|
||||||
dataSize = (try? FilePath.workingDirectory.formattedSize()) ?? "Unknown"
|
dataSize = (try? FilePath.workingDirectory.formattedSize()) ?? "Unknown"
|
||||||
taiwanFlagAvailable = !DeviceCensorship.isChinaDevice()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func clearWorkingDirectory() {
|
private func clearWorkingDirectory() {
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user