mirror of
https://github.com/cranci1/Sora.git
synced 2026-03-11 17:45:37 +00:00
added loger thatnks to @Seeike
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
Co-Authored-By: Seiike <122684677+Seeike@users.noreply.github.com>
This commit is contained in:
parent
ad6f2e3e46
commit
a79a7738f4
10 changed files with 212 additions and 48 deletions
|
|
@ -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 = "<group>"; };
|
||||
138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeCell.swift; sourceTree = "<group>"; };
|
||||
138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = "<group>"; };
|
||||
1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewLogger.swift; sourceTree = "<group>"; };
|
||||
1399FAD52D3AB3DB00E97C31 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
13DC0C412D2EC9BA00D0F966 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
13DC0C452D302C7500D0F966 /* VideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = "<group>"; };
|
||||
13EA2BD12D32D97400C1EBD7 /* CustomPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomPlayer.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -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 = "<group>";
|
||||
|
|
@ -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 = "<group>";
|
||||
|
|
@ -196,6 +201,23 @@
|
|||
path = EpisodeCell;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1399FAD12D3AB33D00E97C31 /* Logger */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1399FAD52D3AB3DB00E97C31 /* Logger.swift */,
|
||||
);
|
||||
path = Logger;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1399FAD22D3AB34F00E97C31 /* SettingsView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
133D7C832D2BE2630075467E /* SettingsSubViews */,
|
||||
133D7C822D2BE2630075467E /* SettingsView.swift */,
|
||||
);
|
||||
path = SettingsView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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([], [])
|
||||
}
|
||||
|
|
|
|||
85
Sora/Utils/Logger/Logger.swift
Normal file
85
Sora/Utils/Logger/Logger.swift
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ struct SearchView: View {
|
|||
}
|
||||
}
|
||||
} catch {
|
||||
print("Error loading module: \(error)")
|
||||
Logger.shared.log("Error loading module: \(error)")
|
||||
isSearching = false
|
||||
hasNoResults = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)")
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue