mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-16 20:30:30 +00:00
308 lines
14 KiB
Swift
308 lines
14 KiB
Swift
//
|
||
// ConsoleLogsView.swift
|
||
// StikJIT
|
||
//
|
||
// Created by neoarz on 3/29/25.
|
||
//
|
||
|
||
import SwiftUI
|
||
import UIKit
|
||
|
||
struct ConsoleLogsView: View {
|
||
@Environment(\.dismiss) private var dismiss
|
||
@StateObject private var logManager = LogManager.shared
|
||
@State private var autoScroll = true
|
||
@State private var scrollView: ScrollViewProxy? = nil
|
||
|
||
// Alert handling
|
||
@State private var showingCustomAlert = false
|
||
@State private var alertMessage = ""
|
||
@State private var alertTitle = ""
|
||
@State private var isError = false
|
||
|
||
var body: some View {
|
||
NavigationView {
|
||
ZStack {
|
||
Color.black
|
||
.edgesIgnoringSafeArea(.all)
|
||
|
||
VStack(spacing: 0) {
|
||
// Terminal logs area (made it look somewhat like feathers implementation)
|
||
ScrollViewReader { proxy in
|
||
ScrollView {
|
||
VStack(spacing: 0) {
|
||
// Device Information (all thw way on top)
|
||
ForEach(["Version: \(UIDevice.current.systemVersion)",
|
||
"Name: \(UIDevice.current.name)",
|
||
"Model: \(UIDevice.current.model)",
|
||
"StikJIT Version: App Version: 1.0"], id: \.self) { info in
|
||
Text("[\(timeString())] ℹ️ \(info)")
|
||
.font(.system(size: 11, design: .monospaced))
|
||
.foregroundColor(.white)
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
.padding(.vertical, 2)
|
||
.padding(.horizontal, 4)
|
||
}
|
||
|
||
Spacer()
|
||
|
||
// Log entries
|
||
ForEach(logManager.logs) { logEntry in
|
||
Text(AttributedString(createLogAttributedString(logEntry)))
|
||
.font(.system(size: 11, design: .monospaced))
|
||
.textSelection(.enabled)
|
||
.lineLimit(nil)
|
||
.fixedSize(horizontal: false, vertical: true)
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
.padding(.vertical, 1)
|
||
.padding(.horizontal, 4)
|
||
.id(logEntry.id)
|
||
}
|
||
}
|
||
}
|
||
.onAppear {
|
||
scrollView = proxy
|
||
}
|
||
.onChange(of: logManager.logs.count) {
|
||
if autoScroll, let lastLog = logManager.logs.last {
|
||
proxy.scrollTo(lastLog.id, anchor: .bottom)
|
||
}
|
||
}
|
||
}
|
||
|
||
Spacer()
|
||
|
||
|
||
VStack(spacing: 16) {
|
||
// Error count with red theme
|
||
HStack {
|
||
Text("\(logManager.errorCount) Errors")
|
||
.font(.headline)
|
||
.foregroundColor(.white)
|
||
.padding(.vertical, 12)
|
||
.frame(maxWidth: .infinity)
|
||
.background(Color.red)
|
||
.cornerRadius(10)
|
||
}
|
||
.padding(.horizontal)
|
||
|
||
// Action buttons with dark background
|
||
VStack(spacing: 1) {
|
||
// Export button
|
||
Button(action: {
|
||
// Create logs content with device information
|
||
var logsContent = "=== DEVICE INFORMATION ===\n"
|
||
logsContent += "Version: \(UIDevice.current.systemVersion)\n"
|
||
logsContent += "Name: \(UIDevice.current.name)\n"
|
||
logsContent += "Model: \(UIDevice.current.model)\n"
|
||
logsContent += "StikJIT Version: App Version: 1.0\n\n"
|
||
logsContent += "=== LOG ENTRIES ===\n"
|
||
|
||
// Add all log entries with proper formatting
|
||
logsContent += logManager.logs.map {
|
||
"[\(formatTime(date: $0.timestamp))] [\($0.type.rawValue)] \($0.message)"
|
||
}.joined(separator: "\n")
|
||
|
||
// Save to document directory (accessible in Files app)
|
||
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||
let dateFormatter = DateFormatter()
|
||
dateFormatter.dateFormat = "yyyy-MM-dd_HH-mm-ss"
|
||
let timestamp = dateFormatter.string(from: Date())
|
||
let fileURL = documentsDirectory.appendingPathComponent("StikJIT_Logs_\(timestamp).txt")
|
||
|
||
do {
|
||
// Write the logs to the file
|
||
try logsContent.write(to: fileURL, atomically: true, encoding: .utf8)
|
||
|
||
// Set alert variables and show the alert
|
||
alertTitle = "Logs Exported"
|
||
alertMessage = "Logs have been saved to Files app in StikJIT folder."
|
||
isError = false
|
||
showingCustomAlert = true
|
||
} catch {
|
||
// Set error alert variables and show the alert
|
||
alertTitle = "Export Failed"
|
||
alertMessage = "Failed to save logs: \(error.localizedDescription)"
|
||
isError = true
|
||
showingCustomAlert = true
|
||
}
|
||
}) {
|
||
HStack {
|
||
Text("Export Logs")
|
||
.foregroundColor(.blue)
|
||
Spacer()
|
||
Image(systemName: "square.and.arrow.down")
|
||
.foregroundColor(.gray)
|
||
}
|
||
.padding(.vertical, 14)
|
||
.padding(.horizontal, 20)
|
||
.contentShape(Rectangle())
|
||
}
|
||
.background(Color(red: 0.1, green: 0.1, blue: 0.1))
|
||
|
||
Divider()
|
||
.background(Color(red: 0.15, green: 0.15, blue: 0.15))
|
||
|
||
// Copy button
|
||
Button(action: {
|
||
// Create logs content with device information
|
||
var logsContent = "=== DEVICE INFORMATION ===\n"
|
||
logsContent += "Version: \(UIDevice.current.systemVersion)\n"
|
||
logsContent += "Name: \(UIDevice.current.name)\n"
|
||
logsContent += "Model: \(UIDevice.current.model)\n"
|
||
logsContent += "StikJIT Version: App Version: 1.0\n\n"
|
||
logsContent += "=== LOG ENTRIES ===\n"
|
||
|
||
// Add all log entries with proper formatting
|
||
logsContent += logManager.logs.map {
|
||
"[\(formatTime(date: $0.timestamp))] [\($0.type.rawValue)] \($0.message)"
|
||
}.joined(separator: "\n")
|
||
|
||
// Copy to clipboard
|
||
UIPasteboard.general.string = logsContent
|
||
|
||
// Show success alert using SwiftUI alert
|
||
alertTitle = "Logs Copied"
|
||
alertMessage = "Logs have been copied to clipboard."
|
||
isError = false
|
||
showingCustomAlert = true
|
||
}) {
|
||
HStack {
|
||
Text("Copy Logs")
|
||
.foregroundColor(.blue)
|
||
Spacer()
|
||
Image(systemName: "doc.on.doc")
|
||
.foregroundColor(.gray)
|
||
}
|
||
.padding(.vertical, 14)
|
||
.padding(.horizontal, 20)
|
||
.contentShape(Rectangle())
|
||
}
|
||
.background(Color(red: 0.1, green: 0.1, blue: 0.1))
|
||
}
|
||
.cornerRadius(10)
|
||
.padding(.horizontal)
|
||
.padding(.bottom, 20)
|
||
}
|
||
}
|
||
}
|
||
.navigationBarTitleDisplayMode(.inline)
|
||
.toolbar {
|
||
ToolbarItem(placement: .principal) {
|
||
Text("Console Logs")
|
||
.font(.headline)
|
||
.foregroundColor(.white)
|
||
}
|
||
|
||
ToolbarItem(placement: .navigationBarLeading) {
|
||
Button(action: {
|
||
dismiss()
|
||
}) {
|
||
HStack(spacing: 2) {
|
||
Image(systemName: "chevron.left")
|
||
.font(.system(size: 16, weight: .semibold))
|
||
Text("Settings")
|
||
.fontWeight(.regular)
|
||
}
|
||
.foregroundColor(.blue)
|
||
}
|
||
}
|
||
|
||
ToolbarItem(placement: .navigationBarTrailing) {
|
||
Button(action: {
|
||
logManager.clearLogs()
|
||
}) {
|
||
Text("Clear")
|
||
.foregroundColor(.blue)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.navigationViewStyle(StackNavigationViewStyle())
|
||
.overlay(
|
||
ZStack {
|
||
if showingCustomAlert {
|
||
CustomErrorView(
|
||
title: alertTitle,
|
||
message: alertMessage,
|
||
onDismiss: {
|
||
showingCustomAlert = false
|
||
},
|
||
showButton: true,
|
||
primaryButtonText: "OK",
|
||
messageType: isError ? .error : .success
|
||
)
|
||
}
|
||
}
|
||
)
|
||
}
|
||
|
||
// Creates an NSAttributedString that combines timestamp, type, and message
|
||
// with proper styling for each component
|
||
private func createLogAttributedString(_ logEntry: LogManager.LogEntry) -> NSAttributedString {
|
||
let fullString = NSMutableAttributedString()
|
||
|
||
// Timestamp part
|
||
let timestampString = "[\(formatTime(date: logEntry.timestamp))]"
|
||
let timestampAttr = NSAttributedString(
|
||
string: timestampString,
|
||
attributes: [.foregroundColor: UIColor.gray]
|
||
)
|
||
fullString.append(timestampAttr)
|
||
fullString.append(NSAttributedString(string: " "))
|
||
|
||
// Log type part
|
||
let typeString = "[\(logEntry.type.rawValue)]"
|
||
let typeColor = UIColor(colorForLogType(logEntry.type))
|
||
let typeAttr = NSAttributedString(
|
||
string: typeString,
|
||
attributes: [.foregroundColor: typeColor]
|
||
)
|
||
fullString.append(typeAttr)
|
||
fullString.append(NSAttributedString(string: " "))
|
||
|
||
// Message part
|
||
let messageAttr = NSAttributedString(
|
||
string: logEntry.message,
|
||
attributes: [.foregroundColor: UIColor.white]
|
||
)
|
||
fullString.append(messageAttr)
|
||
|
||
return fullString
|
||
}
|
||
|
||
// Helper to display current time
|
||
private func timeString() -> String {
|
||
let formatter = DateFormatter()
|
||
formatter.dateFormat = "HH:mm:ss"
|
||
return formatter.string(from: Date())
|
||
}
|
||
|
||
// Helper to format Date objects to time strings
|
||
private func formatTime(date: Date) -> String {
|
||
let formatter = DateFormatter()
|
||
formatter.dateFormat = "HH:mm:ss"
|
||
return formatter.string(from: date)
|
||
}
|
||
|
||
// Return color based on log type
|
||
private func colorForLogType(_ type: LogManager.LogEntry.LogType) -> Color {
|
||
switch type {
|
||
case .info:
|
||
return .green
|
||
case .error:
|
||
return .red
|
||
case .debug:
|
||
return .blue
|
||
case .warning:
|
||
return .orange
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
struct ConsoleLogsView_Previews: PreviewProvider {
|
||
static var previews: some View {
|
||
ConsoleLogsView()
|
||
}
|
||
}
|