1// CrashLogView.swift
2// SophaxChat
3//
4// Displays the on-device diagnostic log. Users can share the file when filing a bug report.
5// No message content or peer identifiers are ever stored in the log.
6
7import SwiftUI
8import SophaxChatCore
9
10struct CrashLogView: View {
11 @Environment(\.dismiss) private var dismiss
12
13 @State private var logText = ""
14 @State private var showClearAlert = false
15 @State private var exportURL: URL? = nil
16
17 var body: some View {
18 NavigationStack {
19 Group {
20 if logText.isEmpty || logText == "(no log entries)" {
21 ContentUnavailableView(
22 "No Log Entries",
23 systemImage: "checkmark.seal",
24 description: Text("The diagnostic log is empty.")
25 )
26 } else {
27 ScrollView {
28 Text(logText)
29 .font(.system(.caption2, design: .monospaced))
30 .foregroundStyle(.primary)
31 .frame(maxWidth: .infinity, alignment: .leading)
32 .padding()
33 .textSelection(.enabled)
34 }
35 }
36 }
37 .navigationTitle("Diagnostic Log")
38 .navigationBarTitleDisplayMode(.inline)
39 .toolbar {
40 ToolbarItem(placement: .confirmationAction) {
41 Button("Done") { dismiss() }
42 }
43 ToolbarItemGroup(placement: .bottomBar) {
44 if let url = exportURL {
45 ShareLink(item: url, subject: Text("SophaxChat Diagnostic Log")) {
46 Label("Share Log", systemImage: "square.and.arrow.up")
47 }
48 .disabled(logText.isEmpty || logText == "(no log entries)")
49 }
50 Spacer()
51 Button(role: .destructive) {
52 showClearAlert = true
53 } label: {
54 Label("Clear", systemImage: "trash")
55 .foregroundStyle(.red)
56 }
57 .disabled(logText.isEmpty || logText == "(no log entries)")
58 }
59 }
60 .alert("Clear Diagnostic Log?", isPresented: $showClearAlert) {
61 Button("Clear", role: .destructive) {
62 CrashLogManager.shared.clear()
63 logText = "(no log entries)"
64 exportURL = nil
65 }
66 Button("Cancel", role: .cancel) {}
67 } message: {
68 Text("This cannot be undone.")
69 }
70 }
71 .onAppear { reload() }
72 }
73
74 private func reload() {
75 logText = CrashLogManager.shared.exportableText()
76 // Write to a temp file for ShareLink
77 guard logText != "(no log entries)", !logText.isEmpty else { return }
78 let tmp = FileManager.default.temporaryDirectory
79 .appendingPathComponent("sophaxchat-crash-log.txt")
80 try? logText.write(to: tmp, atomically: true, encoding: .utf8)
81 exportURL = tmp
82 }
83}