From a79a7738f4a471a33600cf5af6cac0c9b34c023f Mon Sep 17 00:00:00 2001 From: cranci1 <100066266+cranci1@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:28:27 +0100 Subject: [PATCH] added loger thatnks to @Seeike Co-Authored-By: Seiike <122684677+Seeike@users.noreply.github.com> --- Sora.xcodeproj/project.pbxproj | 30 ++++++- Sora/MediaPlayer/NormalPlayer.swift | 2 +- Sora/Utils/Loaders/JSController.swift | 69 ++++++++------- Sora/Utils/Logger/Logger.swift | 85 +++++++++++++++++++ .../EpisodeCell/EpisodeCell.swift | 6 +- Sora/Views/MediaInfoView/MediaInfoView.swift | 8 +- Sora/Views/SearchView.swift | 2 +- .../SettingsSubViews/SettingsViewLogger.swift | 50 +++++++++++ .../SettingsSubViews/SettingsViewModule.swift | 0 .../{ => SettingsView}/SettingsView.swift | 8 +- 10 files changed, 212 insertions(+), 48 deletions(-) create mode 100644 Sora/Utils/Logger/Logger.swift create mode 100644 Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift rename Sora/Views/{ => SettingsView}/SettingsSubViews/SettingsViewModule.swift (100%) rename Sora/Views/{ => SettingsView}/SettingsView.swift (94%) diff --git a/Sora.xcodeproj/project.pbxproj b/Sora.xcodeproj/project.pbxproj index 16e106f..9b73dd4 100644 --- a/Sora.xcodeproj/project.pbxproj +++ b/Sora.xcodeproj/project.pbxproj @@ -24,6 +24,8 @@ 133F55BB2D33B55100E08EEA /* LibraryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133F55BA2D33B55100E08EEA /* LibraryManager.swift */; }; 138AA1B82D2D66FD0021F9DF /* EpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */; }; 138AA1B92D2D66FD0021F9DF /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */; }; + 1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */; }; + 1399FAD62D3AB3DB00E97C31 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1399FAD52D3AB3DB00E97C31 /* Logger.swift */; }; 13DC0C462D302C7500D0F966 /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DC0C452D302C7500D0F966 /* VideoPlayer.swift */; }; 13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD12D32D97400C1EBD7 /* CustomPlayer.swift */; }; 13EA2BD62D32D97400C1EBD7 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD32D32D97400C1EBD7 /* Double+Extension.swift */; }; @@ -51,6 +53,8 @@ 133F55BA2D33B55100E08EEA /* LibraryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryManager.swift; sourceTree = ""; }; 138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeCell.swift; sourceTree = ""; }; 138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = ""; }; + 1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewLogger.swift; sourceTree = ""; }; + 1399FAD52D3AB3DB00E97C31 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 13DC0C412D2EC9BA00D0F966 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 13DC0C452D302C7500D0F966 /* VideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = ""; }; 13EA2BD12D32D97400C1EBD7 /* CustomPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomPlayer.swift; sourceTree = ""; }; @@ -116,12 +120,11 @@ 133D7C7B2D2BE2630075467E /* Views */ = { isa = PBXGroup; children = ( + 1399FAD22D3AB34F00E97C31 /* SettingsView */, 133F55B92D33B53E00E08EEA /* LibraryView */, - 133D7C832D2BE2630075467E /* SettingsSubViews */, 133D7C7F2D2BE2630075467E /* MediaInfoView */, 133D7C7D2D2BE2630075467E /* HomeView.swift */, 133D7C7C2D2BE2630075467E /* SearchView.swift */, - 133D7C822D2BE2630075467E /* SettingsView.swift */, ); path = Views; sourceTree = ""; @@ -138,6 +141,7 @@ 133D7C832D2BE2630075467E /* SettingsSubViews */ = { isa = PBXGroup; children = ( + 1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */, 133D7C842D2BE2630075467E /* SettingsViewModule.swift */, ); path = SettingsSubViews; @@ -146,10 +150,11 @@ 133D7C852D2BE2640075467E /* Utils */ = { isa = PBXGroup; children = ( - 13EA2BDA2D32D9FF00C1EBD7 /* Miru */, 133D7C862D2BE2640075467E /* Extensions */, 133D7C882D2BE2640075467E /* Modules */, 133D7C8A2D2BE2640075467E /* Loaders */, + 1399FAD12D3AB33D00E97C31 /* Logger */, + 13EA2BDA2D32D9FF00C1EBD7 /* Miru */, ); path = Utils; sourceTree = ""; @@ -196,6 +201,23 @@ path = EpisodeCell; sourceTree = ""; }; + 1399FAD12D3AB33D00E97C31 /* Logger */ = { + isa = PBXGroup; + children = ( + 1399FAD52D3AB3DB00E97C31 /* Logger.swift */, + ); + path = Logger; + sourceTree = ""; + }; + 1399FAD22D3AB34F00E97C31 /* SettingsView */ = { + isa = PBXGroup; + children = ( + 133D7C832D2BE2630075467E /* SettingsSubViews */, + 133D7C822D2BE2630075467E /* SettingsView.swift */, + ); + path = SettingsView; + sourceTree = ""; + }; 13DC0C442D302C6A00D0F966 /* MediaPlayer */ = { isa = PBXGroup; children = ( @@ -315,6 +337,7 @@ buildActionMask = 2147483647; files = ( 13DC0C462D302C7500D0F966 /* VideoPlayer.swift in Sources */, + 1399FAD62D3AB3DB00E97C31 /* Logger.swift in Sources */, 133D7C902D2BE2640075467E /* SettingsView.swift in Sources */, 13EA2BD72D32D97400C1EBD7 /* MusicProgressSlider.swift in Sources */, 13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */, @@ -334,6 +357,7 @@ 133D7C6E2D2BE2500075467E /* SoraApp.swift in Sources */, 138AA1B92D2D66FD0021F9DF /* CircularProgressBar.swift in Sources */, 13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */, + 1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sora/MediaPlayer/NormalPlayer.swift b/Sora/MediaPlayer/NormalPlayer.swift index 2c92496..1a1e4ea 100644 --- a/Sora/MediaPlayer/NormalPlayer.swift +++ b/Sora/MediaPlayer/NormalPlayer.swift @@ -71,7 +71,7 @@ class NormalPlayer: AVPlayerViewController { try audioSession.overrideOutputAudioPort(.speaker) } catch { - print("Failed to set up AVAudioSession: \(error)") + Logger.shared.log("Failed to set up AVAudioSession: \(error)") } } } diff --git a/Sora/Utils/Loaders/JSController.swift b/Sora/Utils/Loaders/JSController.swift index a66e068..fbac42f 100644 --- a/Sora/Utils/Loaders/JSController.swift +++ b/Sora/Utils/Loaders/JSController.swift @@ -17,32 +17,31 @@ class JSController: ObservableObject { private func setupContext() { let logFunction: @convention(block) (String) -> Void = { message in - print("JavaScript log: \(message)") + Logger.shared.log("JavaScript log: \(message)") } context.setObject(logFunction, forKeyedSubscript: "log" as NSString) let fetchNativeFunction: @convention(block) (String, JSValue, JSValue) -> Void = { urlString, resolve, reject in guard let url = URL(string: urlString) else { - print("Invalid URL") + Logger.shared.log("Invalid URL") reject.call(withArguments: ["Invalid URL"]) return } let task = URLSession.custom.dataTask(with: url) { data, _, error in if let error = error { - print(url) - print("Network error in fetchNativeFunction: \(error.localizedDescription)") + Logger.shared.log("Network error in fetchNativeFunction: \(error.localizedDescription)") reject.call(withArguments: [error.localizedDescription]) return } guard let data = data else { - print("No data in response") + Logger.shared.log("No data in response") reject.call(withArguments: ["No data"]) return } if let text = String(data: data, encoding: .utf8) { resolve.call(withArguments: [text]) } else { - print("Unable to decode data to text") + Logger.shared.log("Unable to decode data to text") reject.call(withArguments: ["Unable to decode data"]) } } @@ -78,13 +77,13 @@ class JSController: ObservableObject { guard let self = self else { return } if let error = error { - print("Network error: \(error)") + Logger.shared.log("Network error: \(error)") DispatchQueue.main.async { completion([]) } return } guard let data = data, let html = String(data: data, encoding: .utf8) else { - print("Failed to decode HTML") + Logger.shared.log("Failed to decode HTML") DispatchQueue.main.async { completion([]) } return } @@ -102,7 +101,7 @@ class JSController: ObservableObject { completion(resultItems) } } else { - print("Failed to parse results") + Logger.shared.log("Failed to parse results") DispatchQueue.main.async { completion([]) } } }.resume() @@ -118,13 +117,13 @@ class JSController: ObservableObject { guard let self = self else { return } if let error = error { - print("Network error: \(error)") + Logger.shared.log("Network error: \(error)") DispatchQueue.main.async { completion([], []) } return } guard let data = data, let html = String(data: data, encoding: .utf8) else { - print("Failed to decode HTML") + Logger.shared.log("Failed to decode HTML") DispatchQueue.main.async { completion([], []) } return } @@ -142,7 +141,7 @@ class JSController: ObservableObject { ) } } else { - print("Failed to parse results") + Logger.shared.log("Failed to parse results") } if let fetchEpisodesFunction = self.context.objectForKeyedSubscript("extractEpisodes"), @@ -170,13 +169,13 @@ class JSController: ObservableObject { guard let self = self else { return } if let error = error { - print("Network error: \(error)") + Logger.shared.log("Network error: \(error)") DispatchQueue.main.async { completion(nil) } return } guard let data = data, let html = String(data: data, encoding: .utf8) else { - print("Failed to decode HTML") + Logger.shared.log("Failed to decode HTML") DispatchQueue.main.async { completion(nil) } return } @@ -187,7 +186,7 @@ class JSController: ObservableObject { completion(streamUrl) } } else { - print("Failed to extract stream URL") + Logger.shared.log("Failed to extract stream URL") DispatchQueue.main.async { completion(nil) } } }.resume() @@ -195,20 +194,20 @@ class JSController: ObservableObject { func fetchJsSearchResults(keyword: String, module: ScrapingModule, completion: @escaping ([SearchItem]) -> Void) { if let exception = context.exception { - print("JavaScript exception: \(exception)") + Logger.shared.log("JavaScript exception: \(exception)") completion([]) return } guard let searchResultsFunction = context.objectForKeyedSubscript("searchResults") else { - print("No JavaScript function searchResults found") + Logger.shared.log("No JavaScript function searchResults found") completion([]) return } let promiseValue = searchResultsFunction.call(withArguments: [keyword]) guard let promise = promiseValue else { - print("searchResults did not return a Promise") + Logger.shared.log("searchResults did not return a Promise") completion([]) return } @@ -231,19 +230,19 @@ class JSController: ObservableObject { } } else { - print("Failed to parse JSON") + Logger.shared.log("Failed to parse JSON") DispatchQueue.main.async { completion([]) } } } catch { - print("JSON parsing error: \(error)") + Logger.shared.log("JSON parsing error: \(error)") DispatchQueue.main.async { completion([]) } } } else { - print("Result is not a string") + Logger.shared.log("Result is not a string") DispatchQueue.main.async { completion([]) } @@ -251,7 +250,7 @@ class JSController: ObservableObject { } let catchBlock: @convention(block) (JSValue) -> Void = { error in - print("Promise rejected: \(String(describing: error.toString()))") + Logger.shared.log("Promise rejected: \(String(describing: error.toString()))") DispatchQueue.main.async { completion([]) } @@ -271,19 +270,19 @@ class JSController: ObservableObject { } if let exception = context.exception { - print("JavaScript exception: \(exception)") + Logger.shared.log("JavaScript exception: \(exception)") completion([], []) return } guard let extractDetailsFunction = context.objectForKeyedSubscript("extractDetails") else { - print("No JavaScript function extractDetails found") + Logger.shared.log("No JavaScript function extractDetails found") completion([], []) return } guard let extractEpisodesFunction = context.objectForKeyedSubscript("extractEpisodes") else { - print("No JavaScript function extractEpisodes found") + Logger.shared.log("No JavaScript function extractEpisodes found") completion([], []) return } @@ -293,7 +292,7 @@ class JSController: ObservableObject { let promiseValueDetails = extractDetailsFunction.call(withArguments: [url.absoluteString]) guard let promiseDetails = promiseValueDetails else { - print("extractDetails did not return a Promise") + Logger.shared.log("extractDetails did not return a Promise") completion([], []) return } @@ -311,19 +310,19 @@ class JSController: ObservableObject { return MediaItem(description: description, aliases: aliases, airdate: airdate) } } else { - print("Failed to parse JSON of extractDetails") + Logger.shared.log("Failed to parse JSON of extractDetails") DispatchQueue.main.async { completion([], []) } } } catch { - print("JSON parsing error of extract details: \(error)") + Logger.shared.log("JSON parsing error of extract details: \(error)") DispatchQueue.main.async { completion([], []) } } } else { - print("Result is not a string of extractDetails") + Logger.shared.log("Result is not a string of extractDetails") DispatchQueue.main.async { completion([], []) } @@ -331,7 +330,7 @@ class JSController: ObservableObject { } let catchBlockDetails: @convention(block) (JSValue) -> Void = { error in - print("Promise rejected of extractDetails: \(String(describing: error.toString()))") + Logger.shared.log("Promise rejected of extractDetails: \(String(describing: error.toString()))") DispatchQueue.main.async { completion([], []) } @@ -346,7 +345,7 @@ class JSController: ObservableObject { let promiseValueEpisodes = extractEpisodesFunction.call(withArguments: [url.absoluteString]) guard let promiseEpisodes = promiseValueEpisodes else { - print("extractEpisodes did not return a Promise") + Logger.shared.log("extractEpisodes did not return a Promise") completion([], []) return } @@ -368,19 +367,19 @@ class JSController: ObservableObject { } } else { - print("Failed to parse JSON of extractEpisodes") + Logger.shared.log("Failed to parse JSON of extractEpisodes") DispatchQueue.main.async { completion([], []) } } } catch { - print("JSON parsing error of extractEpisodes: \(error)") + Logger.shared.log("JSON parsing error of extractEpisodes: \(error)") DispatchQueue.main.async { completion([], []) } } } else { - print("Result is not a string of extractEpisodes") + Logger.shared.log("Result is not a string of extractEpisodes") DispatchQueue.main.async { completion([], []) } @@ -388,7 +387,7 @@ class JSController: ObservableObject { } let catchBlockEpisodes: @convention(block) (JSValue) -> Void = { error in - print("Promise rejected of extractEpisodes: \(String(describing: error.toString()))") + Logger.shared.log("Promise rejected of extractEpisodes: \(String(describing: error.toString()))") DispatchQueue.main.async { completion([], []) } diff --git a/Sora/Utils/Logger/Logger.swift b/Sora/Utils/Logger/Logger.swift new file mode 100644 index 0000000..f2a9a23 --- /dev/null +++ b/Sora/Utils/Logger/Logger.swift @@ -0,0 +1,85 @@ +// +// Logging.swift +// Sora +// +// Created by seiike on 16/01/2025. +// + +import Foundation + +class Logger { + static let shared = Logger() + + enum LogLevel: String { + case info = "INFO" + case warning = "WARNING" + case error = "ERROR" + } + + private var logs: [(level: LogLevel, message: String, timestamp: Date)] = [] + private let logFileURL: URL + + private init() { + let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! + logFileURL = documentDirectory.appendingPathComponent("logs.txt") + loadLogs() + } + + func log(_ message: String, level: LogLevel = .info) { + let entry = (level: level, message: message, timestamp: Date()) + logs.append(entry) + saveLogToFile(entry) + } + + func getLogs() -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + return logs.map { "[\(dateFormatter.string(from: $0.timestamp))] [\($0.level.rawValue)] \($0.message)" } + .joined(separator: "\n---\n") + } + + func clearLogs() { + logs.removeAll() + try? FileManager.default.removeItem(at: logFileURL) + } + + private func loadLogs() { + guard let data = try? Data(contentsOf: logFileURL), + let content = String(data: data, encoding: .utf8) else { return } + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + + content.components(separatedBy: "\n---\n").forEach { line in + let components = line.components(separatedBy: "] [") + guard components.count == 3, + let timestampString = components.first?.dropFirst().trimmingCharacters(in: .whitespaces), + let timestamp = dateFormatter.date(from: timestampString), + let message = components.last?.dropLast() else { return } + + let levelRaw = components[1].trimmingCharacters(in: .whitespaces) + guard let level = LogLevel(rawValue: levelRaw) else { return } + + logs.append((level: level, message: String(message), timestamp: timestamp)) + } + } + + private func saveLogToFile(_ log: (level: LogLevel, message: String, timestamp: Date)) { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + + let logString = "[\(dateFormatter.string(from: log.timestamp))] [\(log.level.rawValue)] \(log.message)\n---\n" + + if let data = logString.data(using: .utf8) { + if FileManager.default.fileExists(atPath: logFileURL.path) { + if let handle = try? FileHandle(forWritingTo: logFileURL) { + handle.seekToEndOfFile() + handle.write(data) + handle.closeFile() + } + } else { + try? data.write(to: logFileURL) + } + } + } +} \ No newline at end of file diff --git a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift index 427f16a..f3acb8e 100644 --- a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift +++ b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift @@ -103,7 +103,7 @@ struct EpisodeCell: View { URLSession.custom.dataTask(with: url) { data, _, error in if let error = error { - print("Failed to fetch episode details: \(error)") + Logger.shared.log("Failed to fetch episode details: \(error)") DispatchQueue.main.async { self.isLoading = false } @@ -133,7 +133,7 @@ struct EpisodeCell: View { let episodeDetails = episodes["\(episodeID + 1)"] as? [String: Any], let title = episodeDetails["title"] as? [String: String], let image = episodeDetails["image"] as? String else { - print("Invalid response format") + Logger.shared.log("Invalid response format") DispatchQueue.main.async { self.isLoading = false } @@ -146,7 +146,7 @@ struct EpisodeCell: View { self.isLoading = false } } catch { - print("Failed to parse JSON: \(error)") + Logger.shared.log("Failed to parse JSON: \(error)") DispatchQueue.main.async { self.isLoading = false } diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index 8402c19..00e3eb4 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -189,7 +189,7 @@ struct MediaInfoView: View { case .success(let id): itemID = id case .failure(let error): - print("Failed to fetch Item ID: \(error)") + Logger.shared.log("Failed to fetch Item ID: \(error)") } } } @@ -224,7 +224,7 @@ struct MediaInfoView: View { } } } catch { - print("Error loading module: \(error)") + Logger.shared.log("Error loading module: \(error)") self.isLoading = false } } @@ -243,7 +243,7 @@ struct MediaInfoView: View { } } } catch { - print("Error loading module: \(error)") + Logger.shared.log("Error loading module: \(error)") self.isLoading = false } } @@ -266,7 +266,7 @@ struct MediaInfoView: View { private func openSafariViewController(with urlString: String) { guard let url = URL(string: urlString) else { - print("Unable to open the webpage") + Logger.shared.log("Unable to open the webpage") return } let safariViewController = SFSafariViewController(url: url) diff --git a/Sora/Views/SearchView.swift b/Sora/Views/SearchView.swift index e17944f..ca823c3 100644 --- a/Sora/Views/SearchView.swift +++ b/Sora/Views/SearchView.swift @@ -168,7 +168,7 @@ struct SearchView: View { } } } catch { - print("Error loading module: \(error)") + Logger.shared.log("Error loading module: \(error)") isSearching = false hasNoResults = true } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift new file mode 100644 index 0000000..aeb83f2 --- /dev/null +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift @@ -0,0 +1,50 @@ +// +// SettingsViewLogger.swift +// Sora +// +// Created by seiike on 16/01/2025. +// + +import SwiftUI + +struct SettingsViewLogger: View { + @State private var logs: String = "" + + var body: some View { + VStack { + ScrollView { + Text(logs) + .font(.footnote) + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + .textSelection(.enabled) + } + .navigationTitle("Logs") + .onAppear { + logs = Logger.shared.getLogs() + } + } + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Menu { + Button(action: { + UIPasteboard.general.string = logs + }) { + Label("Copy to Clipboard", systemImage: "doc.on.doc") + } + Button(role: .destructive, action: { + Logger.shared.clearLogs() + logs = Logger.shared.getLogs() + }) { + Label("Clear Logs", systemImage: "trash") + } + } label: { + Image(systemName: "ellipsis.circle") + .resizable() + .frame(width: 20, height: 20) + } + } + } + } +} diff --git a/Sora/Views/SettingsSubViews/SettingsViewModule.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewModule.swift similarity index 100% rename from Sora/Views/SettingsSubViews/SettingsViewModule.swift rename to Sora/Views/SettingsView/SettingsSubViews/SettingsViewModule.swift diff --git a/Sora/Views/SettingsView.swift b/Sora/Views/SettingsView/SettingsView.swift similarity index 94% rename from Sora/Views/SettingsView.swift rename to Sora/Views/SettingsView/SettingsView.swift index a3b059c..83e976f 100644 --- a/Sora/Views/SettingsView.swift +++ b/Sora/Views/SettingsView/SettingsView.swift @@ -32,6 +32,12 @@ struct SettingsView: View { } } + Section(header: Text("Debug")) { + NavigationLink(destination: SettingsViewLogger()) { + Text("Logs") + } + } + Section(header: Text("Info")) { Button(action: { if let url = URL(string: "https://github.com/cranci1/Sora") { @@ -121,7 +127,7 @@ class Settings: ObservableObject { let colorData = try NSKeyedArchiver.archivedData(withRootObject: uiColor, requiringSecureCoding: false) UserDefaults.standard.set(colorData, forKey: "accentColor") } catch { - print("Failed to save accent color: \(error.localizedDescription)") + Logger.shared.log("Failed to save accent color: \(error.localizedDescription)") } }