From 23ae861f878121b5c9257d10070cf3ca0897f31b Mon Sep 17 00:00:00 2001 From: neoarz Date: Fri, 11 Apr 2025 10:59:53 -0400 Subject: [PATCH 1/6] show idevice only --- StikJIT/Utilities/LogManager.swift | 12 ++ StikJIT/Views/ConsoleLogsView.swift | 178 +++++++++++++++++++++++++++- 2 files changed, 185 insertions(+), 5 deletions(-) diff --git a/StikJIT/Utilities/LogManager.swift b/StikJIT/Utilities/LogManager.swift index 6b72983..91b2096 100644 --- a/StikJIT/Utilities/LogManager.swift +++ b/StikJIT/Utilities/LogManager.swift @@ -88,4 +88,16 @@ class LogManager: ObservableObject { self.errorCount = 0 } } + + func removeOldestLogs(count: Int) { + DispatchQueue.main.async { + // Remove the oldest logs and update error count + let removedLogs = self.logs.prefix(count) + self.logs.removeFirst(count) + + // Update error count by counting removed error logs + let removedErrorCount = removedLogs.filter { $0.type == .error }.count + self.errorCount = max(0, self.errorCount - removedErrorCount) + } + } } diff --git a/StikJIT/Views/ConsoleLogsView.swift b/StikJIT/Views/ConsoleLogsView.swift index 8e37aa6..9191ad2 100644 --- a/StikJIT/Views/ConsoleLogsView.swift +++ b/StikJIT/Views/ConsoleLogsView.swift @@ -23,6 +23,14 @@ struct ConsoleLogsView: View { @State private var alertTitle = "" @State private var isError = false + // Timer to check for log updates + @State private var logCheckTimer: Timer? = nil + + // Track if the view is active (visible) + @State private var isViewActive = false + @State private var lastProcessedLineCount = 0 // Track last processed line count + @State private var isLoadingLogs = false // Track loading state + private var accentColor: Color { if customAccentColorHex.isEmpty { return .blue @@ -74,6 +82,16 @@ struct ConsoleLogsView: View { } .onAppear { scrollView = proxy + isViewActive = true + // Load logs asynchronously + Task { + await loadIdeviceLogsAsync() + } + startLogCheckTimer() + } + .onDisappear { + isViewActive = false + stopLogCheckTimer() } .onChange(of: logManager.logs.count) { if autoScroll, let lastLog = logManager.logs.last { @@ -224,11 +242,23 @@ struct ConsoleLogsView: View { } ToolbarItem(placement: .navigationBarTrailing) { - Button(action: { - logManager.clearLogs() - }) { - Text("Clear") - .foregroundColor(accentColor) + HStack { + Button(action: { + // Load the logs again + Task { + await loadIdeviceLogsAsync() + } + }) { + Image(systemName: "arrow.clockwise") + .foregroundColor(accentColor) + } + + Button(action: { + logManager.clearLogs() + }) { + Text("Clear") + .foregroundColor(accentColor) + } } } } @@ -313,6 +343,144 @@ struct ConsoleLogsView: View { return .orange } } + + // Function to load idevice logs from file asynchronously + private func loadIdeviceLogsAsync() async { + guard !isLoadingLogs else { return } + isLoadingLogs = true + + // Get the path to the idevice log file + let logPath = URL.documentsDirectory.appendingPathComponent("idevice_log.txt").path + + // Check if the file exists + guard FileManager.default.fileExists(atPath: logPath) else { + await MainActor.run { + logManager.addInfoLog("No idevice logs found (Restart the app to continue reading)") + isLoadingLogs = false + } + return + } + + do { + // Read the file content + let logContent = try String(contentsOfFile: logPath, encoding: .utf8) + let lines = logContent.components(separatedBy: .newlines) + + // Only take the last 300 lines + let maxLines = 300 + let startIndex = max(0, lines.count - maxLines) + let recentLines = Array(lines[startIndex.. lastProcessedLineCount { + let newLines = Array(lines[lastProcessedLineCount.. maxLines { + let excessCount = logManager.logs.count - maxLines + logManager.removeOldestLogs(count: excessCount) + } + } + } + } catch { + await MainActor.run { + logManager.addErrorLog("Failed to read new logs: \(error.localizedDescription)") + } + } + + isLoadingLogs = false + } + + // Function to stop the timer + private func stopLogCheckTimer() { + logCheckTimer?.invalidate() + logCheckTimer = nil + } } struct ConsoleLogsView_Previews: PreviewProvider { From 255f545fcb0d6cbf9c9a2eb28fe12baa4f3c5980 Mon Sep 17 00:00:00 2001 From: neoarz Date: Fri, 11 Apr 2025 11:53:09 -0400 Subject: [PATCH 2/6] update device info at top --- StikJIT/Views/ConsoleLogsView.swift | 91 ++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 26 deletions(-) diff --git a/StikJIT/Views/ConsoleLogsView.swift b/StikJIT/Views/ConsoleLogsView.swift index 9191ad2..695d6aa 100644 --- a/StikJIT/Views/ConsoleLogsView.swift +++ b/StikJIT/Views/ConsoleLogsView.swift @@ -30,6 +30,7 @@ struct ConsoleLogsView: View { @State private var isViewActive = false @State private var lastProcessedLineCount = 0 // Track last processed line count @State private var isLoadingLogs = false // Track loading state + @State private var isAtBottom = true // Track if user is at bottom of logs private var accentColor: Color { if customAccentColorHex.isEmpty { @@ -52,19 +53,31 @@ struct ConsoleLogsView: View { ScrollView { VStack(spacing: 0) { // Device Information - 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)") + VStack(alignment: .leading, spacing: 4) { + Text("=== DEVICE INFORMATION ===") .font(.system(size: 11, design: .monospaced)) .foregroundColor(colorScheme == .dark ? .white : .black) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.vertical, 2) - .padding(.horizontal, 4) + .padding(.vertical, 4) + + Text("iOS Version: \(UIDevice.current.systemVersion)") + .font(.system(size: 11, design: .monospaced)) + .foregroundColor(colorScheme == .dark ? .white : .black) + + Text("Device: \(UIDevice.current.name)") + .font(.system(size: 11, design: .monospaced)) + .foregroundColor(colorScheme == .dark ? .white : .black) + + Text("Model: \(UIDevice.current.model)") + .font(.system(size: 11, design: .monospaced)) + .foregroundColor(colorScheme == .dark ? .white : .black) + + Text("=== LOG ENTRIES ===") + .font(.system(size: 11, design: .monospaced)) + .foregroundColor(colorScheme == .dark ? .white : .black) + .padding(.vertical, 4) } - - Spacer() + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 4) // Log entries ForEach(logManager.logs) { logEntry in @@ -79,6 +92,29 @@ struct ConsoleLogsView: View { .id(logEntry.id) } } + .background( + GeometryReader { geometry in + Color.clear.preference( + key: ScrollOffsetPreferenceKey.self, + value: geometry.frame(in: .named("scroll")).maxY + ) + } + ) + } + .coordinateSpace(name: "scroll") + .onPreferenceChange(ScrollOffsetPreferenceKey.self) { offset in + let threshold: CGFloat = 50 + let scrollViewHeight = UIScreen.main.bounds.height + isAtBottom = offset < (scrollViewHeight + threshold) + } + .onChange(of: logManager.logs.count) { _ in + if isAtBottom { + withAnimation { + if let lastLog = logManager.logs.last { + proxy.scrollTo(lastLog.id, anchor: .bottom) + } + } + } } .onAppear { scrollView = proxy @@ -93,11 +129,6 @@ struct ConsoleLogsView: View { isViewActive = false stopLogCheckTimer() } - .onChange(of: logManager.logs.count) { - if autoScroll, let lastLog = logManager.logs.last { - proxy.scrollTo(lastLog.id, anchor: .bottom) - } - } } Spacer() @@ -366,8 +397,8 @@ struct ConsoleLogsView: View { let logContent = try String(contentsOfFile: logPath, encoding: .utf8) let lines = logContent.components(separatedBy: .newlines) - // Only take the last 300 lines - let maxLines = 300 + // Only take the last 500 lines + let maxLines = 500 let startIndex = max(0, lines.count - maxLines) let recentLines = Array(lines[startIndex.. maxLines { let excessCount = logManager.logs.count - maxLines logManager.removeOldestLogs(count: excessCount) @@ -487,4 +519,11 @@ struct ConsoleLogsView_Previews: PreviewProvider { static var previews: some View { ConsoleLogsView() } +} + +struct ScrollOffsetPreferenceKey: PreferenceKey { + static var defaultValue: CGFloat = 0 + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value = nextValue() + } } From 08ea80e4d21a078632b16361a17b51e2c05a784c Mon Sep 17 00:00:00 2001 From: neoarz Date: Fri, 11 Apr 2025 11:57:01 -0400 Subject: [PATCH 3/6] fix scrolling --- StikJIT/Views/ConsoleLogsView.swift | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/StikJIT/Views/ConsoleLogsView.swift b/StikJIT/Views/ConsoleLogsView.swift index 695d6aa..3150419 100644 --- a/StikJIT/Views/ConsoleLogsView.swift +++ b/StikJIT/Views/ConsoleLogsView.swift @@ -96,21 +96,22 @@ struct ConsoleLogsView: View { GeometryReader { geometry in Color.clear.preference( key: ScrollOffsetPreferenceKey.self, - value: geometry.frame(in: .named("scroll")).maxY + value: geometry.frame(in: .named("scroll")).minY ) } ) } .coordinateSpace(name: "scroll") .onPreferenceChange(ScrollOffsetPreferenceKey.self) { offset in - let threshold: CGFloat = 50 - let scrollViewHeight = UIScreen.main.bounds.height - isAtBottom = offset < (scrollViewHeight + threshold) + // Consider user is at bottom if they're within 20 points of the bottom + // This gives a small buffer for slight scroll movements + isAtBottom = offset > -20 } - .onChange(of: logManager.logs.count) { _ in + .onChange(of: logManager.logs) { newLogs in + // Only auto-scroll if we're already at the bottom if isAtBottom { withAnimation { - if let lastLog = logManager.logs.last { + if let lastLog = newLogs.last { proxy.scrollTo(lastLog.id, anchor: .bottom) } } From 16707c01dbc7fcea71b8c3fa1dfeb0ec88348595 Mon Sep 17 00:00:00 2001 From: neoarz Date: Fri, 11 Apr 2025 11:59:06 -0400 Subject: [PATCH 4/6] rip --- StikJIT/Views/ConsoleLogsView.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/StikJIT/Views/ConsoleLogsView.swift b/StikJIT/Views/ConsoleLogsView.swift index 3150419..19c9cf4 100644 --- a/StikJIT/Views/ConsoleLogsView.swift +++ b/StikJIT/Views/ConsoleLogsView.swift @@ -107,11 +107,10 @@ struct ConsoleLogsView: View { // This gives a small buffer for slight scroll movements isAtBottom = offset > -20 } - .onChange(of: logManager.logs) { newLogs in - // Only auto-scroll if we're already at the bottom + .onChange(of: logManager.logs.count) { _ in if isAtBottom { withAnimation { - if let lastLog = newLogs.last { + if let lastLog = logManager.logs.last { proxy.scrollTo(lastLog.id, anchor: .bottom) } } From b090ced2dfdf54eaf4969e1ec7b532199d44937a Mon Sep 17 00:00:00 2001 From: neoarz Date: Fri, 11 Apr 2025 12:20:29 -0400 Subject: [PATCH 5/6] idevicelogs --- StikJIT/Views/ConsoleLogsView.swift | 57 ++++++++++------------------- 1 file changed, 20 insertions(+), 37 deletions(-) diff --git a/StikJIT/Views/ConsoleLogsView.swift b/StikJIT/Views/ConsoleLogsView.swift index 19c9cf4..3fd2e28 100644 --- a/StikJIT/Views/ConsoleLogsView.swift +++ b/StikJIT/Views/ConsoleLogsView.swift @@ -150,44 +150,27 @@ struct ConsoleLogsView: View { // Action buttons with theme-appropriate 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) + ShareLink( + item: { + // Get the path to the idevice log file + let logPath = URL.documentsDirectory.appendingPathComponent("idevice_log.txt") - // 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 - } - }) { + // Check if the file exists + guard FileManager.default.fileExists(atPath: logPath.path) else { + alertTitle = "Export Failed" + alertMessage = "No idevice logs found" + isError = true + showingCustomAlert = true + return URL.documentsDirectory.appendingPathComponent("empty.txt") + } + + return logPath + }(), + preview: SharePreview( + "StikJIT Logs", + image: Image(systemName: "doc.text") + ) + ) { HStack { Text("Export Logs") .foregroundColor(accentColor) From f8b511386ef8727cbca2d6121c1993c0bab4784c Mon Sep 17 00:00:00 2001 From: neoarz Date: Fri, 11 Apr 2025 12:23:28 -0400 Subject: [PATCH 6/6] rename --- StikJIT/Views/ConsoleLogsView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StikJIT/Views/ConsoleLogsView.swift b/StikJIT/Views/ConsoleLogsView.swift index 3fd2e28..8df8885 100644 --- a/StikJIT/Views/ConsoleLogsView.swift +++ b/StikJIT/Views/ConsoleLogsView.swift @@ -167,7 +167,7 @@ struct ConsoleLogsView: View { return logPath }(), preview: SharePreview( - "StikJIT Logs", + "idevice_log.txt", image: Image(systemName: "doc.text") ) ) {