Files
sing-box-for-apple/ApplicationLibrary/Views/Tools/CrashReportListView.swift
T
2026-04-23 08:12:53 +08:00

250 lines
9.3 KiB
Swift

import Libbox
import Library
import SwiftUI
@MainActor
public struct CrashReportListView: View {
@EnvironmentObject private var environments: ExtensionEnvironments
@State private var isLoading = true
@State private var alert: AlertState?
#if os(tvOS)
@State private var showCrashTrigger = false
@State private var selectedReport: CrashReport?
#endif
public init() {}
private var manager: CrashReportManager {
environments.crashReportManager
}
public var body: some View {
FormView {
if !isLoading {
Section {
if manager.reports.isEmpty {
Text("Empty")
.foregroundStyle(.secondary)
} else {
ForEach(manager.reports) { report in
#if os(tvOS)
Button {
selectedReport = report
} label: {
reportLabel(report)
}
#else
FormNavigationLink {
CrashReportDetailView(report: report)
} label: {
reportLabel(report)
}
#endif
}
}
} header: {
Text("Reports")
} footer: {
Text("You will receive a report when a crash occurs.")
}
}
}
.overlay {
if isLoading {
ProgressView()
}
}
.onAppear {
Task {
await manager.refresh()
isLoading = false
}
}
.navigationTitle("Crash Report")
.alert($alert)
#if os(tvOS)
.navigationDestination(item: $selectedReport) { report in
CrashReportDetailView(report: report)
.toolbar {
ToolbarItemGroup(placement: .topBarLeading) {
BackButton()
}
}
}
#endif
#if os(tvOS)
.navigationDestination(isPresented: $showCrashTrigger) {
CrashTriggerView()
}
.toolbar {
if Variant.inDebug {
ToolbarItem(placement: .confirmationAction) {
Button {
showCrashTrigger = true
} label: {
Image(systemName: "ant.fill")
}
}
}
if !manager.reports.isEmpty {
ToolbarItem(placement: .confirmationAction) {
Button {
Task {
await manager.deleteAll()
}
} label: {
Image(systemName: "trash.fill")
}
.tint(.red)
}
}
}
#else
.toolbar {
if !manager.reports.isEmpty || Variant.inDebug {
Menu {
if Variant.inDebug {
Menu {
Menu("Application") {
Button("Go Crash") {
LibboxTriggerGoPanic()
}
Button("Native Crash") {
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(200)) {
fatalError("debug native crash")
}
}
}
if let profile = environments.extensionProfile {
NetworkExtensionCrashMenu(profile: profile)
}
#if os(macOS)
RootHelperCrashMenu()
#endif
} label: {
Label("Crash Trigger", systemImage: "ant.fill")
}
}
if !manager.reports.isEmpty {
Button(role: .destructive) {
Task {
await manager.deleteAll()
}
} label: {
Label("Delete All", systemImage: "trash.fill")
}
}
} label: {
Label("Others", systemImage: "line.3.horizontal.circle")
}
}
}
#endif
}
private func reportLabel(_ report: CrashReport) -> some View {
ReportLabel(date: report.date, isRead: report.isRead, origin: report.origin)
}
}
#if os(tvOS)
private struct CrashTriggerView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var environments: ExtensionEnvironments
var body: some View {
Form {
Section("Application") {
Button("Go Crash") {
LibboxTriggerGoPanic()
}
Button("Native Crash") {
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(200)) {
fatalError("debug native crash")
}
}
}
if let profile = environments.extensionProfile, profile.status.isConnectedStrict {
Section("NetworkExtension") {
Button("Go Crash") {
try? LibboxNewStandaloneCommandClient()?.triggerGoCrash()
dismiss()
Task {
try? await Task.sleep(nanoseconds: NSEC_PER_SEC)
await environments.crashReportManager.refresh()
}
}
Button("Native Crash") {
try? LibboxNewStandaloneCommandClient()?.triggerNativeCrash()
dismiss()
Task {
try? await Task.sleep(nanoseconds: NSEC_PER_SEC)
await environments.crashReportManager.refresh()
}
}
}
}
}
.navigationTitle("Crash Trigger")
.toolbar {
ToolbarItemGroup(placement: .topBarLeading) {
BackButton()
}
}
}
}
#else
private struct NetworkExtensionCrashMenu: View {
@EnvironmentObject private var environments: ExtensionEnvironments
@ObservedObject var profile: ExtensionProfile
var body: some View {
if profile.status.isConnectedStrict {
Menu("NetworkExtension") {
Button("Go Crash") {
try? LibboxNewStandaloneCommandClient()?.triggerGoCrash()
Task {
try? await Task.sleep(nanoseconds: NSEC_PER_SEC)
await environments.crashReportManager.refresh()
}
}
Button("Native Crash") {
try? LibboxNewStandaloneCommandClient()?.triggerNativeCrash()
Task {
try? await Task.sleep(nanoseconds: NSEC_PER_SEC)
await environments.crashReportManager.refresh()
}
}
}
}
}
}
#endif
#if os(macOS)
private struct RootHelperCrashMenu: View {
@EnvironmentObject private var environments: ExtensionEnvironments
var body: some View {
if Variant.useSystemExtension, HelperServiceManager.rootHelperStatus == .enabled {
Menu("RootHelper") {
Button("Go Crash") {
try? RootHelperClient.shared.triggerGoCrash()
Task {
try? await Task.sleep(nanoseconds: NSEC_PER_SEC)
await environments.crashReportManager.refresh()
}
}
Button("Native Crash") {
try? RootHelperClient.shared.triggerNativeCrash()
Task {
try? await Task.sleep(nanoseconds: NSEC_PER_SEC)
await environments.crashReportManager.refresh()
}
}
}
}
}
}
#endif