Add screenshot generator

This commit is contained in:
世界
2026-01-08 16:13:20 +08:00
parent 264c4eed81
commit 6b589cb771
57 changed files with 1931 additions and 109 deletions
@@ -191,6 +191,7 @@ public struct ConnectionListView: View {
return UIMenu(children: [stateMenu, sortMenu, closeAction])
}
}
#elseif os(macOS)
private struct ConnectionMenuView: View {
@Binding var connectionStateFilter: ConnectionStateFilter
@@ -56,7 +56,7 @@ public class ConnectionListViewModel: BaseViewModel {
}
public func connect() {
if ApplicationLibrary.inPreview {
if Variant.screenshotMode {
isLoading = false
return
}
@@ -39,14 +39,14 @@ import SwiftUI
#endif
} else {
content.onAppear {
guard !ApplicationLibrary.inPreview, profile.status.isConnected else {
guard !Variant.screenshotMode, profile.status.isConnected else {
return
}
Task {
await coordinator.reloadSystemProxy()
}
}.onChangeCompat(of: profile.status) { status in
guard !ApplicationLibrary.inPreview, status == .connected else {
guard !Variant.screenshotMode, status == .connected else {
return
}
Task {
@@ -60,7 +60,7 @@ import SwiftUI
Group {
#if os(iOS)
if useLegacyTabView {
if ApplicationLibrary.inPreview || profile.status.isConnectedStrict {
if Variant.screenshotMode || profile.status.isConnectedStrict {
VStack {
pageSelector
pageContent
@@ -126,11 +126,7 @@ import SwiftUI
#endif
#endif
.onAppear {
if ApplicationLibrary.inPreview {
environments.commandClient.connect()
} else {
environments.connect()
}
environments.connect()
}.onChangeCompat(of: scenePhase) { phase in
guard phase == .active else {
return
@@ -19,13 +19,19 @@ public struct ButtonVisibilityState {
return
}
groupsCount = commandClient.groups?.count ?? 0
let actualGroupsCount = commandClient.groups?.count ?? 0
let screenshotFallbackGroupsCount = 2
groupsCount = Variant.screenshotMode && actualGroupsCount == 0
? screenshotFallbackGroupsCount
: actualGroupsCount
connectionsCount = commandClient.connections?.count ?? 0
let isConnected = ApplicationLibrary.inPreview || profile.status.isConnectedStrict
let isConnected = Variant.screenshotMode || profile.status.isConnectedStrict
let hasGroups = Variant.screenshotMode || (commandClient.groups?.isEmpty == false)
showConnectionsButton = isConnected
showGroupsButton = isConnected && (commandClient.groups?.isEmpty == false)
showGroupsButton = isConnected && hasGroups
}
private mutating func reset() {
@@ -11,7 +11,7 @@ public struct ConnectionsCard: View {
DashboardCardView(title: "", isHalfWidth: true) {
VStack(alignment: .leading, spacing: 8) {
DashboardCardHeader(icon: "link.circle.fill", title: "Connections")
if ApplicationLibrary.inPreview {
if Variant.screenshotMode {
DashboardCardLine(String(localized: "Inbound"), "34")
DashboardCardLine(String(localized: "Outbound"), "28")
} else if let message = commandClient.status {
@@ -12,7 +12,7 @@ public struct DownloadTrafficCard: View {
VStack(alignment: .leading, spacing: 8) {
DashboardCardHeader(icon: "arrow.down.circle.fill", title: "Download")
if ApplicationLibrary.inPreview {
if Variant.screenshotMode {
Text("249 MB/s")
.font(.title2)
.fontWeight(.medium)
@@ -11,7 +11,7 @@ public struct StatusCard: View {
DashboardCardView(title: "", isHalfWidth: true) {
VStack(alignment: .leading, spacing: 8) {
DashboardCardHeader(icon: "info.circle.fill", title: "Status")
if ApplicationLibrary.inPreview {
if Variant.screenshotMode {
DashboardCardLine(String(localized: "Memory"), "6.4 MB")
DashboardCardLine(String(localized: "Goroutines"), "89")
} else if let message = commandClient.status {
@@ -12,7 +12,7 @@ public struct UploadTrafficCard: View {
VStack(alignment: .leading, spacing: 8) {
DashboardCardHeader(icon: "arrow.up.circle.fill", title: "Upload")
if ApplicationLibrary.inPreview {
if Variant.screenshotMode {
Text("38 B/s")
.font(.title2)
.fontWeight(.medium)
@@ -28,7 +28,7 @@ public struct ExtensionStatusView: View {
Group {
VStack {
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount), alignment: .leading) {
if ApplicationLibrary.inPreview {
if Variant.screenshotMode {
StatusItem(String(localized: "Status")) {
StatusLine(String(localized: "Memory"), "6.4 MB")
StatusLine(String(localized: "Goroutines"), "89")
@@ -10,16 +10,7 @@ public struct StartStopButton: View {
public var body: some View {
Group {
if ApplicationLibrary.inPreview {
Button {} label: {
#if os(tvOS)
Image(systemName: "stop.fill")
#else
Label("Stop", systemImage: "stop.fill")
#endif
}
.labelStyle(.iconOnly)
} else if let profile = environments.extensionProfile {
if let profile = environments.extensionProfile {
ToggleConnectionButton().environmentObject(profile)
} else {
Button {} label: {
@@ -108,6 +99,7 @@ public struct StartStopButton: View {
.disabled(!profile.status.isEnabled)
.alert($alert)
.onReceive(timer) { _ in
guard !Variant.screenshotMode else { return }
Task { @MainActor in
currentTime = Date()
}
@@ -140,7 +132,12 @@ public struct StartStopButton: View {
private var runtimeDuration: String? {
guard let connectedDate = profile.connectedDate else { return nil }
let interval = currentTime.timeIntervalSince(connectedDate)
let interval: TimeInterval
if Variant.screenshotMode {
interval = 3600
} else {
interval = currentTime.timeIntervalSince(connectedDate)
}
guard interval >= 0 else { return nil }
let hours = Int(interval) / 3600
@@ -77,9 +77,7 @@ public struct DashboardView: View {
@ViewBuilder
private var mainContent: some View {
if ApplicationLibrary.inPreview {
activeDashboardView
} else if environments.extensionProfileLoading {
if environments.extensionProfileLoading {
ProgressView()
} else if let profile = environments.extensionProfile {
activeDashboardView
@@ -48,7 +48,7 @@ public final class DashboardViewModel: BaseViewModel {
defer { isLoading = false }
if ApplicationLibrary.inPreview {
if Variant.screenshotMode {
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))),
@@ -41,7 +41,7 @@ public struct OverviewView: View {
}
}
.alert($coordinator.alert)
.disabled(!ApplicationLibrary.inPreview && (!profile.status.isSwitchable || coordinator.reasserting))
.disabled(!Variant.screenshotMode && (!profile.status.isSwitchable || coordinator.reasserting))
}
@ViewBuilder
@@ -85,9 +85,9 @@ public struct OverviewView: View {
private func shouldShowCard(_ card: DashboardCard) -> Bool {
switch card {
case .status, .connections, .uploadTraffic, .downloadTraffic, .clashMode:
return ApplicationLibrary.inPreview || profile.status.isConnected
return Variant.screenshotMode || profile.status.isConnected
case .httpProxy:
return (ApplicationLibrary.inPreview || profile.status.isConnectedStrict) && systemProxyAvailable
return (Variant.screenshotMode || profile.status.isConnectedStrict) && systemProxyAvailable
case .profile:
return true
}
@@ -14,17 +14,18 @@ public class GroupListViewModel: BaseViewModel {
}
public func connect() {
if ApplicationLibrary.inPreview {
if Variant.screenshotMode {
groups = [
OutboundGroup(tag: "my_group", type: "selector", selected: "server", selectable: true, isExpand: 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),
OutboundGroupItem(tag: "server", type: "Shadowsocks", urlTestTime: .now, urlTestDelay: 10),
OutboundGroupItem(tag: "server2", type: "WireGuard", urlTestTime: .now, urlTestDelay: 20),
OutboundGroupItem(tag: "auto", type: "URLTest", urlTestTime: .now, urlTestDelay: 30),
]),
OutboundGroup(tag: "Auto", type: "urltest", selected: "Tokyo", selectable: true, isExpand: false, items: [
OutboundGroupItem(tag: "Tokyo", type: "Shadowsocks", urlTestTime: .now, urlTestDelay: 10),
OutboundGroupItem(tag: "Singapore", type: "VMess", urlTestTime: .now, urlTestDelay: 20),
OutboundGroupItem(tag: "Hong Kong", type: "Trojan", urlTestTime: .now, urlTestDelay: 15),
]),
OutboundGroup(tag: "group2", type: "urltest", selected: "client", selectable: true, isExpand: false, items:
(0 ..< 234).map { index in
OutboundGroupItem(tag: "client\(index)", type: "Shadowsocks", urlTestTime: .now, urlTestDelay: UInt16(100 + index * 10))
}),
]
isLoading = false
}
+2 -4
View File
@@ -98,7 +98,7 @@ private struct LogViewContent: View {
func updateUIView(_ uiView: UIButton, context _: Context) {
uiView.menu = createMenu()
if #available(iOS 26.0, *) {
if #available(iOS 17.0, *) {
uiView.tintColor = colorScheme == .dark ? .white : .black
}
}
@@ -225,7 +225,7 @@ private struct LogContentInnerView: View {
var body: some View {
Group {
if ApplicationLibrary.inPreview {
if Variant.screenshotMode {
previewContent
} else if dataModel.isEmpty {
emptyContent
@@ -240,8 +240,6 @@ private struct LogContentInnerView: View {
private var previewContent: some View {
let 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)",
@@ -17,6 +17,25 @@ public enum NavigationPage: Int, CaseIterable, Identifiable {
}
public extension NavigationPage {
init?(snapshotValue: String) {
switch snapshotValue.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
case "dashboard":
self = .dashboard
case "logs":
self = .logs
case "settings":
self = .settings
#if os(macOS)
case "groups":
self = .groups
case "connections":
self = .connections
#endif
default:
return nil
}
}
#if os(macOS)
static var macosDefaultPages: [NavigationPage] {
[.logs, .settings]
@@ -98,7 +98,7 @@ public struct CoreView: View {
}
private nonisolated func loadSettings() async {
if ApplicationLibrary.inPreview {
if Variant.screenshotMode {
await MainActor.run {
version = "<redacted>"
dataSize = LibboxFormatBytes(1000 * 1000 * 10)
@@ -12,7 +12,7 @@ final class SettingViewModel: BaseViewModel {
nonisolated func checkTaiwanFlagAvailability() async {
let available: Bool
if ApplicationLibrary.inPreview {
if Variant.screenshotMode {
available = true
} else {
available = !DeviceCensorship.isChinaDevice()