From 63724c6ef07b7c5683352349d4d6c18925e42490 Mon Sep 17 00:00:00 2001 From: Dominic Drees Date: Fri, 2 May 2025 01:43:08 +0200 Subject: [PATCH] more linting ( 0 errors and warnings ), logger types, cleanup --- .swiftlint.yml | 3 + Localizable.xcstrings | 366 +++++++++++++++++- Sora/SoraApp.swift | 10 +- .../AniList/Auth/Anilist-Login.swift | 6 +- .../AniList/Auth/Anilist-Token.swift | 10 +- .../Mutations/AniListPushUpdates.swift | 2 +- .../Trakt/Auth/Trakt-Login.swift | 6 +- .../Trakt/Auth/Trakt-Token.swift | 4 +- .../Trakt/Mutations/TraktPushUpdates.swift | 2 +- Sora/Utils/Analytics/Analytics.swift | 18 +- .../ContinueWatching/DownloadManager.swift | 4 +- Sora/Utils/Drops/DropManager.swift | 2 +- .../JavaScriptCore+Extensions.swift | 36 +- .../Utils/JSLoader/JSController-Details.swift | 38 +- .../Utils/JSLoader/JSController-Explore.swift | 2 +- Sora/Utils/JSLoader/JSController-Search.swift | 26 +- .../Utils/JSLoader/JSController-Streams.swift | 70 ++-- Sora/Utils/JSLoader/JSController.swift | 2 +- Sora/Utils/Logger/Logger.swift | 15 +- .../Components/VolumeSlider.swift | 6 +- .../CustomPlayer/CustomPlayer.swift | 253 ++++++------ Sora/Utils/MediaPlayer/NormalPlayer.swift | 2 +- Sora/Utils/MediaPlayer/VideoPlayer.swift | 53 +-- .../Modules/ModuleAdditionSettingsView.swift | 2 +- Sora/Utils/Modules/ModuleManager.swift | 30 +- Sora/Utils/ProfileStore/ProfileStore.swift | 2 +- .../iCloudSyncManager/iCloudSyncManager.swift | 14 +- Sora/Views/ExploreView/ExploreView.swift | 6 +- Sora/Views/LibraryView/LibraryManager.swift | 8 +- .../EpisodeCell/EpisodeCell.swift | 4 +- Sora/Views/MediaInfoView/MediaInfoView.swift | 217 ++++++----- Sora/Views/SearchView/SearchView.swift | 6 +- .../SettingsViewAlternateAppIconPicker.swift | 2 +- .../SettingsSubViews/SettingsViewData.swift | 10 +- .../SettingsViewGeneral.swift | 8 +- .../SettingsViewTrackers.swift | 10 +- 36 files changed, 801 insertions(+), 454 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index 97d506d..f93f443 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -64,12 +64,15 @@ disabled_rules: - discouraged_none_name - attributes - prefer_key_path + - block_based_kvo + - for_where # should be fixed sometimes: - implicitly_unwrapped_optional - cyclomatic_complexity - unused_parameter - fatal_error - force_cast + - object_literal large_tuple: 3 number_separator: diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 5cbd1c2..0318843 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -100,7 +100,20 @@ } }, "+ Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plus Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plus Icon" + } + } + } }, "+10s" : { "localizations" : { @@ -199,7 +212,20 @@ } }, "Alternative App Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alternatives App Icon Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alternative App Icon" + } + } + } }, "AniList" : { "localizations" : { @@ -218,7 +244,20 @@ } }, "AniList Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "AniList Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "AniList Icon" + } + } + } }, "AniList.co" : { "localizations" : { @@ -444,6 +483,22 @@ } } }, + "Bookmark Icon" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lesezeichen Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bookmark Icon" + } + } + } + }, "Bookmark items for an easier access later." : { "localizations" : { "de" : { @@ -492,6 +547,22 @@ } } }, + "Calendar Icon" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kalendar Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Calendar Icon" + } + } + } + }, "Cancel" : { "localizations" : { "de" : { @@ -525,7 +596,20 @@ } }, "Checkmark Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ausgewählt Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Checkmark Icon" + } + } + } }, "Clear Cache" : { "localizations" : { @@ -544,7 +628,20 @@ } }, "Clear Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Löschen Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Clear Icon" + } + } + } }, "Clear Logs" : { "localizations" : { @@ -595,7 +692,20 @@ } }, "Configure Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konfigurieren Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configure Icon" + } + } + } }, "Confirm Erase App Data" : { "localizations" : { @@ -934,7 +1044,20 @@ } }, "Episode Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Episoden Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Episode Icon" + } + } + } }, "Episodes" : { "localizations" : { @@ -1017,7 +1140,20 @@ } }, "Error Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fehler Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error Icon" + } + } + } }, "Error: %@" : { "localizations" : { @@ -1053,7 +1189,20 @@ } }, "Expand Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aufklappen Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Expand Icon" + } + } + } }, "Explore" : { "localizations" : { @@ -1088,7 +1237,20 @@ } }, "External App Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Externe App Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "External App Icon" + } + } + } }, "Failed to parse response" : { "localizations" : { @@ -1695,10 +1857,36 @@ } }, "Magazine Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buch Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Magazine Icon" + } + } + } }, "Magnifying Glass Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suchen Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Magnifying Glass Icon" + } + } + } }, "Main" : { "localizations" : { @@ -1845,7 +2033,20 @@ } }, "More Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mehr Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "More Icon" + } + } + } }, "Name" : { "localizations" : { @@ -1867,7 +2068,7 @@ "localizations" : { "de" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "Neuer Nutzer" } }, @@ -2120,7 +2321,20 @@ } }, "Play Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abspielen Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Play Icon" + } + } + } }, "Play Offline Content" : { "localizations" : { @@ -2219,7 +2433,20 @@ } }, "Questionmark Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fragezeichen Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Questionmark Icon" + } + } + } }, "Recently watched content will appear here." : { "localizations" : { @@ -2397,6 +2624,22 @@ } } }, + "Right Arrow Icon" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pfeil nach Rechts Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Right Arrow Icon" + } + } + } + }, "Running Sora %@\nby cranci1" : { "localizations" : { "de" : { @@ -2430,7 +2673,20 @@ } }, "Safari Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Safari Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Safari Icon" + } + } + } }, "Scan to Visit" : { "localizations" : { @@ -2465,7 +2721,20 @@ } }, "Search Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suchen Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search Icon" + } + } + } }, "Search View" : { "localizations" : { @@ -2708,7 +2977,20 @@ } }, "Speaker Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lautsprecher Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Speaker Icon" + } + } + } }, "Speed Settings" : { "localizations" : { @@ -2727,7 +3009,20 @@ } }, "Star Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stern Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Star Icon" + } + } + } }, "Starting authentication..." : { "localizations" : { @@ -2906,7 +3201,20 @@ } }, "Trakt Icon" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trakt Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trakt Icon" + } + } + } }, "Trakt.tv" : { "localizations" : { @@ -3004,6 +3312,22 @@ } } }, + "Warning Icon" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Warnung Icon" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Warning Icon" + } + } + } + }, "You are not logged in" : { "localizations" : { "de" : { diff --git a/Sora/SoraApp.swift b/Sora/SoraApp.swift index cf17674..b3f2f6d 100644 --- a/Sora/SoraApp.swift +++ b/Sora/SoraApp.swift @@ -20,7 +20,7 @@ struct SoraApp: App { if isAuthenticated { Logger.shared.log("Trakt authentication is valid") } else { - Logger.shared.log("Trakt authentication required", type: "Error") + Logger.shared.log("Trakt authentication required", type: .error) } } } @@ -112,7 +112,7 @@ struct SoraApp: App { } else { Logger.shared.log( "Failed to present module addition view: No window scene found", - type: "Error" + type: .error ) } @@ -134,7 +134,7 @@ struct SoraApp: App { if success { Logger.shared.log("AniList token exchange successful") } else { - Logger.shared.log("AniList token exchange failed", type: "Error") + Logger.shared.log("AniList token exchange failed", type: .error) } } @@ -143,12 +143,12 @@ struct SoraApp: App { if success { Logger.shared.log("Trakt token exchange successful") } else { - Logger.shared.log("Trakt token exchange failed", type: "Error") + Logger.shared.log("Trakt token exchange failed", type: .error) } } default: - Logger.shared.log("Unknown authentication service", type: "Error") + Logger.shared.log("Unknown authentication service", type: .error) } } } diff --git a/Sora/Tracking Services/AniList/Auth/Anilist-Login.swift b/Sora/Tracking Services/AniList/Auth/Anilist-Login.swift index cb5160d..76a0935 100644 --- a/Sora/Tracking Services/AniList/Auth/Anilist-Login.swift +++ b/Sora/Tracking Services/AniList/Auth/Anilist-Login.swift @@ -22,13 +22,13 @@ class AniListLogin { if UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url, options: [:]) { success in if success { - Logger.shared.log("Safari opened successfully", type: "Debug") + Logger.shared.log("Safari opened successfully", type: .debug) } else { - Logger.shared.log("Failed to open Safari", type: "Error") + Logger.shared.log("Failed to open Safari", type: .error) } } } else { - Logger.shared.log("Cannot open URL", type: "Error") + Logger.shared.log("Cannot open URL", type: .error) } } } diff --git a/Sora/Tracking Services/AniList/Auth/Anilist-Token.swift b/Sora/Tracking Services/AniList/Auth/Anilist-Token.swift index a3e9d47..ca93535 100644 --- a/Sora/Tracking Services/AniList/Auth/Anilist-Token.swift +++ b/Sora/Tracking Services/AniList/Auth/Anilist-Token.swift @@ -45,7 +45,7 @@ class AniListToken { Logger.shared.log("Exchanging authorization code for access token...") guard let url = URL(string: tokenEndpoint) else { - Logger.shared.log("Invalid token endpoint URL", type: "Error") + Logger.shared.log("Invalid token endpoint URL", type: .error) DispatchQueue.main.async { NotificationCenter.default.post(name: authFailureNotification, object: nil, userInfo: ["error": "Invalid token endpoint URL"]) completion(false) @@ -63,14 +63,14 @@ class AniListToken { let task = URLSession.shared.dataTask(with: request) { data, _, error in DispatchQueue.main.async { if let error { - Logger.shared.log("Error: \(error.localizedDescription)", type: "Error") + Logger.shared.log("Error: \(error.localizedDescription)", type: .error) NotificationCenter.default.post(name: authFailureNotification, object: nil, userInfo: ["error": error.localizedDescription]) completion(false) return } guard let data else { - Logger.shared.log("No data received", type: "Error") + Logger.shared.log("No data received", type: .error) NotificationCenter.default.post(name: authFailureNotification, object: nil, userInfo: ["error": "No data received"]) completion(false) return @@ -88,13 +88,13 @@ class AniListToken { completion(success) } else { let errorMessage = (json["error"] as? String) ?? "Unexpected response" - Logger.shared.log("Authentication error: \(errorMessage)", type: "Error") + Logger.shared.log("Authentication error: \(errorMessage)", type: .error) NotificationCenter.default.post(name: authFailureNotification, object: nil, userInfo: ["error": errorMessage]) completion(false) } } } catch { - Logger.shared.log("Failed to parse JSON: \(error.localizedDescription)", type: "Error") + Logger.shared.log("Failed to parse JSON: \(error.localizedDescription)", type: .error) NotificationCenter.default.post(name: authFailureNotification, object: nil, userInfo: ["error": "Failed to parse response: \(error.localizedDescription)"]) completion(false) } diff --git a/Sora/Tracking Services/AniList/Mutations/AniListPushUpdates.swift b/Sora/Tracking Services/AniList/Mutations/AniListPushUpdates.swift index 563b8c7..340f1d9 100644 --- a/Sora/Tracking Services/AniList/Mutations/AniListPushUpdates.swift +++ b/Sora/Tracking Services/AniList/Mutations/AniListPushUpdates.swift @@ -91,7 +91,7 @@ class AniListMutation { if let data { do { _ = try JSONSerialization.jsonObject(with: data, options: []) - Logger.shared.log("Successfully updated anime progress", type: "Debug") + Logger.shared.log("Successfully updated anime progress", type: .debug) completion(.success(())) } catch { completion(.failure(error)) diff --git a/Sora/Tracking Services/Trakt/Auth/Trakt-Login.swift b/Sora/Tracking Services/Trakt/Auth/Trakt-Login.swift index ab94923..878f622 100644 --- a/Sora/Tracking Services/Trakt/Auth/Trakt-Login.swift +++ b/Sora/Tracking Services/Trakt/Auth/Trakt-Login.swift @@ -22,13 +22,13 @@ class TraktLogin { if UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url, options: [:]) { success in if success { - Logger.shared.log("Safari opened successfully", type: "Debug") + Logger.shared.log("Safari opened successfully", type: .debug) } else { - Logger.shared.log("Failed to open Safari", type: "Error") + Logger.shared.log("Failed to open Safari", type: .error) } } } else { - Logger.shared.log("Cannot open URL", type: "Error") + Logger.shared.log("Cannot open URL", type: .error) } } } diff --git a/Sora/Tracking Services/Trakt/Auth/Trakt-Token.swift b/Sora/Tracking Services/Trakt/Auth/Trakt-Token.swift index dc24d6e..eb28d71 100644 --- a/Sora/Tracking Services/Trakt/Auth/Trakt-Token.swift +++ b/Sora/Tracking Services/Trakt/Auth/Trakt-Token.swift @@ -43,7 +43,7 @@ class TraktToken { static func exchangeAuthorizationCodeForToken(code: String, completion: @escaping (Bool) -> Void) { guard let url = URL(string: tokenEndpoint) else { - Logger.shared.log("Invalid token endpoint URL", type: "Error") + Logger.shared.log("Invalid token endpoint URL", type: .error) handleFailure(error: "Invalid token endpoint URL", completion: completion) return } @@ -139,7 +139,7 @@ class TraktToken { } private static func handleFailure(error: String, completion: @escaping (Bool) -> Void) { - Logger.shared.log(error, type: "Error") + Logger.shared.log(error, type: .error) NotificationCenter.default.post(name: authFailureNotification, object: nil, userInfo: ["error": error]) completion(false) } diff --git a/Sora/Tracking Services/Trakt/Mutations/TraktPushUpdates.swift b/Sora/Tracking Services/Trakt/Mutations/TraktPushUpdates.swift index dbf96e7..54bb648 100644 --- a/Sora/Tracking Services/Trakt/Mutations/TraktPushUpdates.swift +++ b/Sora/Tracking Services/Trakt/Mutations/TraktPushUpdates.swift @@ -122,7 +122,7 @@ class TraktMutation { return } - Logger.shared.log("Successfully updated watch status on Trakt", type: "Debug") + Logger.shared.log("Successfully updated watch status on Trakt", type: .debug) completion(.success(())) } diff --git a/Sora/Utils/Analytics/Analytics.swift b/Sora/Utils/Analytics/Analytics.swift index 32b3ad3..2b89243 100644 --- a/Sora/Utils/Analytics/Analytics.swift +++ b/Sora/Utils/Analytics/Analytics.swift @@ -23,7 +23,7 @@ class AnalyticsManager { private let moduleManager = ModuleManager() private init() { - print("[Info] Analytics initializer called") + Logger.shared.log("Analytics initializer called", type: .info) } // MARK: - Send Analytics Data @@ -38,12 +38,12 @@ class AnalyticsManager { let analyticsEnabled = UserDefaults.standard.bool(forKey: "analyticsEnabled") guard analyticsEnabled else { - Logger.shared.log("Analytics is disabled, skipping event: \(event)", type: "Debug") + Logger.shared.log("Analytics is disabled, skipping event: \(event)", type: .debug) return } guard let selectedModule = getSelectedModule() else { - Logger.shared.log("No selected module found", type: "Debug") + Logger.shared.log("No selected module found", type: .debug) return } @@ -76,30 +76,30 @@ class AnalyticsManager { do { request.httpBody = try JSONSerialization.data(withJSONObject: data, options: []) } catch { - Logger.shared.log("Failed to encode JSON: \(error.localizedDescription)", type: "Debug") + Logger.shared.log("Failed to encode JSON: \(error.localizedDescription)", type: .debug) return } URLSession.shared.dataTask(with: request) { data, _, error in if let error { - Logger.shared.log("Request failed: \(error.localizedDescription)", type: "Debug") + Logger.shared.log("Request failed: \(error.localizedDescription)", type: .debug) return } guard let data else { - Logger.shared.log("No data received from server", type: "Debug") + Logger.shared.log("No data received from server", type: .debug) return } do { let decodedResponse = try JSONDecoder().decode(AnalyticsResponse.self, from: data) if decodedResponse.status == "success" { - Logger.shared.log("Analytics saved: \(decodedResponse.event ?? "unknown event") at \(decodedResponse.timestamp ?? "unknown time")", type: "Debug") + Logger.shared.log("Analytics saved: \(decodedResponse.event ?? "unknown event") at \(decodedResponse.timestamp ?? "unknown time")", type: .debug) } else { - Logger.shared.log("Server error: \(decodedResponse.message)", type: "Debug") + Logger.shared.log("Server error: \(decodedResponse.message)", type: .debug) } } catch { - Logger.shared.log("Failed to decode response: \(error.localizedDescription)", type: "Debug") + Logger.shared.log("Failed to decode response: \(error.localizedDescription)", type: .debug) } }.resume() } diff --git a/Sora/Utils/ContinueWatching/DownloadManager.swift b/Sora/Utils/ContinueWatching/DownloadManager.swift index 611c511..a8aa582 100644 --- a/Sora/Utils/ContinueWatching/DownloadManager.swift +++ b/Sora/Utils/ContinueWatching/DownloadManager.swift @@ -58,7 +58,7 @@ class DownloadManager: NSObject, ObservableObject { localPlaybackURL = localURL } } catch { - print("Error loading local content: \(error)") + Logger.shared.log("Error loading local content: \(error)", type: .error) } } } @@ -75,7 +75,7 @@ extension DownloadManager: AVAssetDownloadDelegate { func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { guard let error else { return } - print("Download error: \(error.localizedDescription)") + Logger.shared.log("Download error: \(error.localizedDescription)", type: .error) activeDownloadTasks.removeValue(forKey: task) } diff --git a/Sora/Utils/Drops/DropManager.swift b/Sora/Utils/Drops/DropManager.swift index 9754c01..8280707 100644 --- a/Sora/Utils/Drops/DropManager.swift +++ b/Sora/Utils/Drops/DropManager.swift @@ -12,7 +12,7 @@ class DropManager { static let shared = DropManager() private init() { - print("[Info] Drops initialized") + Logger.shared.log("Drops initialized", type: .info) } func showDrop(title: String, subtitle: String, duration: TimeInterval, icon: UIImage?) { diff --git a/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift b/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift index b6a5c5d..b68cbfa 100644 --- a/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift +++ b/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift @@ -12,19 +12,19 @@ extension JSContext { let consoleObject = JSValue(newObjectIn: self) let consoleLogFunction: @convention(block) (String) -> Void = { message in - Logger.shared.log(message, type: "Debug") + Logger.shared.log(message, type: .debug) } consoleObject?.setObject(consoleLogFunction, forKeyedSubscript: "log" as NSString) let consoleErrorFunction: @convention(block) (String) -> Void = { message in - Logger.shared.log(message, type: "Error") + Logger.shared.log(message, type: .error) } consoleObject?.setObject(consoleErrorFunction, forKeyedSubscript: "error" as NSString) self.setObject(consoleObject, forKeyedSubscript: "console" as NSString) let logFunction: @convention(block) (String) -> Void = { message in - Logger.shared.log("JavaScript log: \(message)", type: "Debug") + Logger.shared.log("JavaScript log: \(message)", type: .debug) } self.setObject(logFunction, forKeyedSubscript: "log" as NSString) } @@ -32,7 +32,7 @@ extension JSContext { func setupNativeFetch() { let fetchNativeFunction: @convention(block) (String, [String: String]?, JSValue, JSValue) -> Void = { urlString, headers, resolve, reject in guard let url = URL(string: urlString) else { - Logger.shared.log("Invalid URL", type: "Error") + Logger.shared.log("Invalid URL", type: .error) reject.call(withArguments: ["Invalid URL"]) return } @@ -44,19 +44,19 @@ extension JSContext { } let task = URLSession.custom.dataTask(with: request) { data, _, error in if let error { - Logger.shared.log("Network error in fetchNativeFunction: \(error.localizedDescription)", type: "Error") + Logger.shared.log("Network error in fetchNativeFunction: \(error.localizedDescription)", type: .error) reject.call(withArguments: [error.localizedDescription]) return } guard let data else { - Logger.shared.log("No data in response", type: "Error") + Logger.shared.log("No data in response", type: .error) reject.call(withArguments: ["No data"]) return } if let text = String(data: data, encoding: .utf8) { resolve.call(withArguments: [text]) } else { - Logger.shared.log("Unable to decode data to text", type: "Error") + Logger.shared.log("Unable to decode data to text", type: .error) reject.call(withArguments: ["Unable to decode data"]) } } @@ -77,7 +77,7 @@ extension JSContext { func setupFetchV2() { let fetchV2NativeFunction: @convention(block) (String, [String: String]?, String?, String?, ObjCBool, JSValue, JSValue) -> Void = { urlString, headers, method, body, redirect, resolve, reject in guard let url = URL(string: urlString) else { - Logger.shared.log("Invalid URL", type: "Error") + Logger.shared.log("Invalid URL", type: .error) reject.call(withArguments: ["Invalid URL"]) return } @@ -86,10 +86,10 @@ extension JSContext { var request = URLRequest(url: url) request.httpMethod = httpMethod - Logger.shared.log("FetchV2 Request: URL=\(url), Method=\(httpMethod), Body=\(body ?? "nil")", type: "Debug") + Logger.shared.log("FetchV2 Request: URL=\(url), Method=\(httpMethod), Body=\(body ?? "nil")", type: .debug) if httpMethod == "GET", let body, !body.isEmpty, body != "null", body != "undefined" { - Logger.shared.log("GET request must not have a body", type: "Error") + Logger.shared.log("GET request must not have a body", type: .error) reject.call(withArguments: ["GET request must not have a body"]) return } @@ -103,16 +103,16 @@ extension JSContext { request.setValue(value, forHTTPHeaderField: key) } } - Logger.shared.log("Redirect value is \(redirect.boolValue)", type: "Error") + Logger.shared.log("Redirect value is \(redirect.boolValue)", type: .error) let task = URLSession.fetchData(allowRedirects: redirect.boolValue).downloadTask(with: request) { tempFileURL, response, error in if let error { - Logger.shared.log("Network error in fetchV2NativeFunction: \(error.localizedDescription)", type: "Error") + Logger.shared.log("Network error in fetchV2NativeFunction: \(error.localizedDescription)", type: .error) reject.call(withArguments: [error.localizedDescription]) return } guard let tempFileURL else { - Logger.shared.log("No data in response", type: "Error") + Logger.shared.log("No data in response", type: .error) reject.call(withArguments: ["No data"]) return } @@ -127,7 +127,7 @@ extension JSContext { let data = try Data(contentsOf: tempFileURL) if data.count > 10_000_000 { - Logger.shared.log("Response exceeds maximum size", type: "Error") + Logger.shared.log("Response exceeds maximum size", type: .error) reject.call(withArguments: ["Response exceeds maximum size"]) return } @@ -137,11 +137,11 @@ extension JSContext { resolve.call(withArguments: [responseDict]) } else { // rather than reject -> resolve with empty body as user can utilise reponse headers. - Logger.shared.log("Unable to decode data to text", type: "Error") + Logger.shared.log("Unable to decode data to text", type: .error) resolve.call(withArguments: [responseDict]) } } catch { - Logger.shared.log("Error reading downloaded file: \(error.localizedDescription)", type: "Error") + Logger.shared.log("Error reading downloaded file: \(error.localizedDescription)", type: .error) reject.call(withArguments: ["Error reading downloaded file"]) } } @@ -190,7 +190,7 @@ extension JSContext { func setupBase64Functions() { let btoaFunction: @convention(block) (String) -> String? = { data in guard let data = data.data(using: .utf8) else { - Logger.shared.log("btoa: Failed to encode input as UTF-8", type: "Error") + Logger.shared.log("btoa: Failed to encode input as UTF-8", type: .error) return nil } return data.base64EncodedString() @@ -198,7 +198,7 @@ extension JSContext { let atobFunction: @convention(block) (String) -> String? = { base64String in guard let data = Data(base64Encoded: base64String) else { - Logger.shared.log("atob: Invalid base64 input", type: "Error") + Logger.shared.log("atob: Invalid base64 input", type: .error) return nil } diff --git a/Sora/Utils/JSLoader/JSController-Details.swift b/Sora/Utils/JSLoader/JSController-Details.swift index 831f9e6..0fcbeb2 100644 --- a/Sora/Utils/JSLoader/JSController-Details.swift +++ b/Sora/Utils/JSLoader/JSController-Details.swift @@ -18,13 +18,13 @@ extension JSController { guard let self else { return } if let error { - Logger.shared.log("Network error: \(error)", type: "Error") + Logger.shared.log("Network error: \(error)", type: .error) DispatchQueue.main.async { completion([], []) } return } guard let data, let html = String(data: data, encoding: .utf8) else { - Logger.shared.log("Failed to decode HTML", type: "Error") + Logger.shared.log("Failed to decode HTML", type: .error) DispatchQueue.main.async { completion([], []) } return } @@ -32,7 +32,7 @@ extension JSController { var resultItems: [MediaItem] = [] var episodeLinks: [EpisodeLink] = [] - Logger.shared.log(html, type: "HTMLStrings") + Logger.shared.log(html, type: .html) if let parseFunction = self.context.objectForKeyedSubscript("extractDetails"), let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] { resultItems = results.map { item in @@ -43,7 +43,7 @@ extension JSController { ) } } else { - Logger.shared.log("Failed to parse results", type: "Error") + Logger.shared.log("Failed to parse results", type: .error) } if let fetchEpisodesFunction = self.context.objectForKeyedSubscript("extractEpisodes"), @@ -68,19 +68,19 @@ extension JSController { } if let exception = context.exception { - Logger.shared.log("JavaScript exception: \(exception)", type: "Error") + Logger.shared.log("JavaScript exception: \(exception)", type: .error) completion([], []) return } guard let extractDetailsFunction = context.objectForKeyedSubscript("extractDetails") else { - Logger.shared.log("No JavaScript function extractDetails found", type: "Error") + Logger.shared.log("No JavaScript function extractDetails found", type: .error) completion([], []) return } guard let extractEpisodesFunction = context.objectForKeyedSubscript("extractEpisodes") else { - Logger.shared.log("No JavaScript function extractEpisodes found", type: "Error") + Logger.shared.log("No JavaScript function extractEpisodes found", type: .error) completion([], []) return } @@ -93,13 +93,13 @@ extension JSController { dispatchGroup.enter() let promiseValueDetails = extractDetailsFunction.call(withArguments: [url.absoluteString]) guard let promiseDetails = promiseValueDetails else { - Logger.shared.log("extractDetails did not return a Promise", type: "Error") + Logger.shared.log("extractDetails did not return a Promise", type: .error) completion([], []) return } let thenBlockDetails: @convention(block) (JSValue) -> Void = { result in - Logger.shared.log(result.toString(), type: "Debug") + Logger.shared.log(result.toString(), type: .debug) if let jsonOfDetails = result.toString(), let dataDetails = jsonOfDetails.data(using: .utf8) { do { @@ -112,19 +112,19 @@ extension JSController { ) } } else { - Logger.shared.log("Failed to parse JSON of extractDetails", type: "Error") + Logger.shared.log("Failed to parse JSON of extractDetails", type: .error) } } catch { - Logger.shared.log("JSON parsing error of extract details: \(error)", type: "Error") + Logger.shared.log("JSON parsing error of extract details: \(error)", type: .error) } } else { - Logger.shared.log("Result is not a string of extractDetails", type: "Error") + Logger.shared.log("Result is not a string of extractDetails", type: .error) } dispatchGroup.leave() } let catchBlockDetails: @convention(block) (JSValue) -> Void = { error in - Logger.shared.log("Promise rejected of extractDetails: \(String(describing: error.toString()))", type: "Error") + Logger.shared.log("Promise rejected of extractDetails: \(String(describing: error.toString()))", type: .error) dispatchGroup.leave() } @@ -137,13 +137,13 @@ extension JSController { dispatchGroup.enter() let promiseValueEpisodes = extractEpisodesFunction.call(withArguments: [url.absoluteString]) guard let promiseEpisodes = promiseValueEpisodes else { - Logger.shared.log("extractEpisodes did not return a Promise", type: "Error") + Logger.shared.log("extractEpisodes did not return a Promise", type: .error) completion([], []) return } let thenBlockEpisodes: @convention(block) (JSValue) -> Void = { result in - Logger.shared.log(result.toString(), type: "Debug") + Logger.shared.log(result.toString(), type: .debug) if let jsonOfEpisodes = result.toString(), let dataEpisodes = jsonOfEpisodes.data(using: .utf8) { do { @@ -155,19 +155,19 @@ extension JSController { ) } } else { - Logger.shared.log("Failed to parse JSON of extractEpisodes", type: "Error") + Logger.shared.log("Failed to parse JSON of extractEpisodes", type: .error) } } catch { - Logger.shared.log("JSON parsing error of extractEpisodes: \(error)", type: "Error") + Logger.shared.log("JSON parsing error of extractEpisodes: \(error)", type: .error) } } else { - Logger.shared.log("Result is not a string of extractEpisodes", type: "Error") + Logger.shared.log("Result is not a string of extractEpisodes", type: .error) } dispatchGroup.leave() } let catchBlockEpisodes: @convention(block) (JSValue) -> Void = { error in - Logger.shared.log("Promise rejected of extractEpisodes: \(String(describing: error.toString()))", type: "Error") + Logger.shared.log("Promise rejected of extractEpisodes: \(String(describing: error.toString()))", type: .error) dispatchGroup.leave() } diff --git a/Sora/Utils/JSLoader/JSController-Explore.swift b/Sora/Utils/JSLoader/JSController-Explore.swift index b96d956..5e5a060 100644 --- a/Sora/Utils/JSLoader/JSController-Explore.swift +++ b/Sora/Utils/JSLoader/JSController-Explore.swift @@ -85,7 +85,7 @@ extension JSController { guard let title = item["title"] as? String, let imageUrl = item["image"] as? String, let href = item["href"] as? String else { - Logger.shared.log("Missing or invalid data in search result item: \(item)", type: "Error") + Logger.shared.log("Missing or invalid data in search result item: \(item)", type: .error) return nil } return SearchItem(title: title, imageUrl: imageUrl, href: href) diff --git a/Sora/Utils/JSLoader/JSController-Search.swift b/Sora/Utils/JSLoader/JSController-Search.swift index c3192bc..a1038a1 100644 --- a/Sora/Utils/JSLoader/JSController-Search.swift +++ b/Sora/Utils/JSLoader/JSController-Search.swift @@ -20,18 +20,18 @@ extension JSController { guard let self else { return } if let error { - Logger.shared.log("Network error: \(error)", type: "Error") + Logger.shared.log("Network error: \(error)", type: .error) DispatchQueue.main.async { completion([]) } return } guard let data, let html = String(data: data, encoding: .utf8) else { - Logger.shared.log("Failed to decode HTML", type: "Error") + Logger.shared.log("Failed to decode HTML", type: .error) DispatchQueue.main.async { completion([]) } return } - Logger.shared.log(html, type: "HTMLStrings") + Logger.shared.log(html, type: .html) if let parseFunction = self.context.objectForKeyedSubscript("searchResults"), let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] { let resultItems = results.map { item in @@ -45,7 +45,7 @@ extension JSController { completion(resultItems) } } else { - Logger.shared.log("Failed to parse results", type: "Error") + Logger.shared.log("Failed to parse results", type: .error) DispatchQueue.main.async { completion([]) } } }.resume() @@ -53,26 +53,26 @@ extension JSController { func fetchJsSearchResults(keyword: String, module: ScrapingModule, completion: @escaping ([SearchItem]) -> Void) { if let exception = context.exception { - Logger.shared.log("JavaScript exception: \(exception)", type: "Error") + Logger.shared.log("JavaScript exception: \(exception)", type: .error) completion([]) return } guard let searchResultsFunction = context.objectForKeyedSubscript("searchResults") else { - Logger.shared.log("No JavaScript function searchResults found", type: "Error") + Logger.shared.log("No JavaScript function searchResults found", type: .error) completion([]) return } let promiseValue = searchResultsFunction.call(withArguments: [keyword]) guard let promise = promiseValue else { - Logger.shared.log("searchResults did not return a Promise", type: "Error") + Logger.shared.log("searchResults did not return a Promise", type: .error) completion([]) return } let thenBlock: @convention(block) (JSValue) -> Void = { result in - Logger.shared.log(result.toString(), type: "HTMLStrings") + Logger.shared.log(result.toString(), type: .html) if let jsonString = result.toString(), let data = jsonString.data(using: .utf8) { do { @@ -81,7 +81,7 @@ extension JSController { guard let title = item["title"] as? String, let imageUrl = item["image"] as? String, let href = item["href"] as? String else { - Logger.shared.log("Missing or invalid data in search result item: \(item)", type: "Error") + Logger.shared.log("Missing or invalid data in search result item: \(item)", type: .error) return nil } return SearchItem(title: title, imageUrl: imageUrl, href: href) @@ -91,19 +91,19 @@ extension JSController { completion(resultItems) } } else { - Logger.shared.log("Failed to parse JSON", type: "Error") + Logger.shared.log("Failed to parse JSON", type: .error) DispatchQueue.main.async { completion([]) } } } catch { - Logger.shared.log("JSON parsing error: \(error)", type: "Error") + Logger.shared.log("JSON parsing error: \(error)", type: .error) DispatchQueue.main.async { completion([]) } } } else { - Logger.shared.log("Result is not a string", type: "Error") + Logger.shared.log("Result is not a string", type: .error) DispatchQueue.main.async { completion([]) } @@ -111,7 +111,7 @@ extension JSController { } let catchBlock: @convention(block) (JSValue) -> Void = { error in - Logger.shared.log("Promise rejected: \(String(describing: error.toString()))", type: "Error") + Logger.shared.log("Promise rejected: \(String(describing: error.toString()))", type: .error) DispatchQueue.main.async { completion([]) } diff --git a/Sora/Utils/JSLoader/JSController-Streams.swift b/Sora/Utils/JSLoader/JSController-Streams.swift index 85182a0..21caebb 100644 --- a/Sora/Utils/JSLoader/JSController-Streams.swift +++ b/Sora/Utils/JSLoader/JSController-Streams.swift @@ -18,18 +18,18 @@ extension JSController { guard let self else { return } if let error { - Logger.shared.log("Network error: \(error)", type: "Error") + Logger.shared.log("Network error: \(error)", type: .error) DispatchQueue.main.async { completion((nil, nil)) } return } guard let data, let html = String(data: data, encoding: .utf8) else { - Logger.shared.log("Failed to decode HTML", type: "Error") + Logger.shared.log("Failed to decode HTML", type: .error) DispatchQueue.main.async { completion((nil, nil)) } return } - Logger.shared.log(html, type: "HTMLStrings") + Logger.shared.log(html, type: .html) if let parseFunction = self.context.objectForKeyedSubscript("extractStreamUrl"), let resultString = parseFunction.call(withArguments: [html]).toString() { if let data = resultString.data(using: .utf8) { @@ -40,21 +40,21 @@ extension JSController { if let streamsArray = json["streams"] as? [String] { streamUrls = streamsArray - Logger.shared.log("Found \(streamsArray.count) streams", type: "Stream") + Logger.shared.log("Found \(streamsArray.count) streams", type: .debug) } else if let streamUrl = json["stream"] as? String { streamUrls = [streamUrl] - Logger.shared.log("Found single stream", type: "Stream") + Logger.shared.log("Found single stream", type: .debug) } if let subsArray = json["subtitles"] as? [String] { subtitleUrls = subsArray - Logger.shared.log("Found \(subsArray.count) subtitle tracks", type: "Stream") + Logger.shared.log("Found \(subsArray.count) subtitle tracks", type: .debug) } else if let subtitleUrl = json["subtitles"] as? String { subtitleUrls = [subtitleUrl] - Logger.shared.log("Found single subtitle track", type: "Stream") + Logger.shared.log("Found single subtitle track", type: .debug) } - Logger.shared.log("Starting stream with \(streamUrls?.count ?? 0) sources and \(subtitleUrls?.count ?? 0) subtitles", type: "Stream") + Logger.shared.log("Starting stream with \(streamUrls?.count ?? 0) sources and \(subtitleUrls?.count ?? 0) subtitles", type: .debug) DispatchQueue.main.async { completion((streamUrls, subtitleUrls)) } @@ -62,17 +62,17 @@ extension JSController { } if let streamsArray = try? JSONSerialization.jsonObject(with: data, options: []) as? [String] { - Logger.shared.log("Starting multi-stream with \(streamsArray.count) sources", type: "Stream") + Logger.shared.log("Starting multi-stream with \(streamsArray.count) sources", type: .debug) DispatchQueue.main.async { completion((streamsArray, nil)) } return } } } - Logger.shared.log("Starting stream from: \(resultString)", type: "Stream") + Logger.shared.log("Starting stream from: \(resultString)", type: .debug) DispatchQueue.main.async { completion(([resultString], nil)) } } else { - Logger.shared.log("Failed to extract stream URL", type: "Error") + Logger.shared.log("Failed to extract stream URL", type: .error) DispatchQueue.main.async { completion((nil, nil)) } } }.resume() @@ -80,20 +80,20 @@ extension JSController { func fetchStreamUrlJS(episodeUrl: String, softsub: Bool = false, module: ScrapingModule, completion: @escaping ((streams: [String]?, subtitles: [String]?)) -> Void) { if let exception = context.exception { - Logger.shared.log("JavaScript exception: \(exception)", type: "Error") + Logger.shared.log("JavaScript exception: \(exception)", type: .error) completion((nil, nil)) return } guard let extractStreamUrlFunction = context.objectForKeyedSubscript("extractStreamUrl") else { - Logger.shared.log("No JavaScript function extractStreamUrl found", type: "Error") + Logger.shared.log("No JavaScript function extractStreamUrl found", type: .error) completion((nil, nil)) return } let promiseValue = extractStreamUrlFunction.call(withArguments: [episodeUrl]) guard let promise = promiseValue else { - Logger.shared.log("extractStreamUrl did not return a Promise", type: "Error") + Logger.shared.log("extractStreamUrl did not return a Promise", type: .error) completion((nil, nil)) return } @@ -110,21 +110,21 @@ extension JSController { if let streamsArray = json["streams"] as? [String] { streamUrls = streamsArray - Logger.shared.log("Found \(streamsArray.count) streams", type: "Stream") + Logger.shared.log("Found \(streamsArray.count) streams", type: .debug) } else if let streamUrl = json["stream"] as? String { streamUrls = [streamUrl] - Logger.shared.log("Found single stream", type: "Stream") + Logger.shared.log("Found single stream", type: .debug) } if let subsArray = json["subtitles"] as? [String] { subtitleUrls = subsArray - Logger.shared.log("Found \(subsArray.count) subtitle tracks", type: "Stream") + Logger.shared.log("Found \(subsArray.count) subtitle tracks", type: .debug) } else if let subtitleUrl = json["subtitles"] as? String { subtitleUrls = [subtitleUrl] - Logger.shared.log("Found single subtitle track", type: "Stream") + Logger.shared.log("Found single subtitle track", type: .debug) } - Logger.shared.log("Starting stream with \(streamUrls?.count ?? 0) sources and \(subtitleUrls?.count ?? 0) subtitles", type: "Stream") + Logger.shared.log("Starting stream with \(streamUrls?.count ?? 0) sources and \(subtitleUrls?.count ?? 0) subtitles", type: .debug) DispatchQueue.main.async { completion((streamUrls, subtitleUrls)) } @@ -132,7 +132,7 @@ extension JSController { } if let streamsArray = try? JSONSerialization.jsonObject(with: data, options: []) as? [String] { - Logger.shared.log("Starting multi-stream with \(streamsArray.count) sources", type: "Stream") + Logger.shared.log("Starting multi-stream with \(streamsArray.count) sources", type: .debug) DispatchQueue.main.async { completion((streamsArray, nil)) } return } @@ -140,14 +140,14 @@ extension JSController { } let streamUrl = result.toString() - Logger.shared.log("Starting stream from: \(streamUrl ?? "nil")", type: "Stream") + Logger.shared.log("Starting stream from: \(streamUrl ?? "nil")", type: .debug) DispatchQueue.main.async { completion((streamUrl != nil ? [streamUrl!] : nil, nil)) } } let catchBlock: @convention(block) (JSValue) -> Void = { error in - Logger.shared.log("Promise rejected: \(String(describing: error.toString()))", type: "Error") + Logger.shared.log("Promise rejected: \(String(describing: error.toString()))", type: .error) DispatchQueue.main.async { completion((nil, nil)) } @@ -166,33 +166,33 @@ extension JSController { guard let self else { return } if let error { - Logger.shared.log("URLSession error: \(error.localizedDescription)", type: "Error") + Logger.shared.log("URLSession error: \(error.localizedDescription)", type: .error) DispatchQueue.main.async { completion((nil, nil)) } return } guard let data, let htmlString = String(data: data, encoding: .utf8) else { - Logger.shared.log("Failed to fetch HTML data", type: "Error") + Logger.shared.log("Failed to fetch HTML data", type: .error) DispatchQueue.main.async { completion((nil, nil)) } return } DispatchQueue.main.async { if let exception = self.context.exception { - Logger.shared.log("JavaScript exception: \(exception)", type: "Error") + Logger.shared.log("JavaScript exception: \(exception)", type: .error) completion((nil, nil)) return } guard let extractStreamUrlFunction = self.context.objectForKeyedSubscript("extractStreamUrl") else { - Logger.shared.log("No JavaScript function extractStreamUrl found", type: "Error") + Logger.shared.log("No JavaScript function extractStreamUrl found", type: .error) completion((nil, nil)) return } let promiseValue = extractStreamUrlFunction.call(withArguments: [htmlString]) guard let promise = promiseValue else { - Logger.shared.log("extractStreamUrl did not return a Promise", type: "Error") + Logger.shared.log("extractStreamUrl did not return a Promise", type: .error) completion((nil, nil)) return } @@ -209,21 +209,21 @@ extension JSController { if let streamsArray = json["streams"] as? [String] { streamUrls = streamsArray - Logger.shared.log("Found \(streamsArray.count) streams", type: "Stream") + Logger.shared.log("Found \(streamsArray.count) streams", type: .debug) } else if let streamUrl = json["stream"] as? String { streamUrls = [streamUrl] - Logger.shared.log("Found single stream", type: "Stream") + Logger.shared.log("Found single stream", type: .debug) } if let subsArray = json["subtitles"] as? [String] { subtitleUrls = subsArray - Logger.shared.log("Found \(subsArray.count) subtitle tracks", type: "Stream") + Logger.shared.log("Found \(subsArray.count) subtitle tracks", type: .debug) } else if let subtitleUrl = json["subtitles"] as? String { subtitleUrls = [subtitleUrl] - Logger.shared.log("Found single subtitle track", type: "Stream") + Logger.shared.log("Found single subtitle track", type: .debug) } - Logger.shared.log("Starting stream with \(streamUrls?.count ?? 0) sources and \(subtitleUrls?.count ?? 0) subtitles", type: "Stream") + Logger.shared.log("Starting stream with \(streamUrls?.count ?? 0) sources and \(subtitleUrls?.count ?? 0) subtitles", type: .debug) DispatchQueue.main.async { completion((streamUrls, subtitleUrls)) } @@ -231,7 +231,7 @@ extension JSController { } if let streamsArray = try? JSONSerialization.jsonObject(with: data, options: []) as? [String] { - Logger.shared.log("Starting multi-stream with \(streamsArray.count) sources", type: "Stream") + Logger.shared.log("Starting multi-stream with \(streamsArray.count) sources", type: .debug) DispatchQueue.main.async { completion((streamsArray, nil)) } return } @@ -239,14 +239,14 @@ extension JSController { } let streamUrl = result.toString() - Logger.shared.log("Starting stream from: \(streamUrl ?? "nil")", type: "Stream") + Logger.shared.log("Starting stream from: \(streamUrl ?? "nil")", type: .debug) DispatchQueue.main.async { completion((streamUrl != nil ? [streamUrl!] : nil, nil)) } } let catchBlock: @convention(block) (JSValue) -> Void = { error in - Logger.shared.log("Promise rejected: \(String(describing: error.toString()))", type: "Error") + Logger.shared.log("Promise rejected: \(String(describing: error.toString()))", type: .error) DispatchQueue.main.async { completion((nil, nil)) } diff --git a/Sora/Utils/JSLoader/JSController.swift b/Sora/Utils/JSLoader/JSController.swift index 68994bf..fe545c1 100644 --- a/Sora/Utils/JSLoader/JSController.swift +++ b/Sora/Utils/JSLoader/JSController.swift @@ -24,7 +24,7 @@ class JSController: ObservableObject { setupContext() context.evaluateScript(script) if let exception = context.exception { - Logger.shared.log("Error loading script: \(exception)", type: "Error") + Logger.shared.log("Error loading script: \(exception)", type: .error) } } } diff --git a/Sora/Utils/Logger/Logger.swift b/Sora/Utils/Logger/Logger.swift index 369bc03..cbf189b 100644 --- a/Sora/Utils/Logger/Logger.swift +++ b/Sora/Utils/Logger/Logger.swift @@ -25,10 +25,19 @@ class Logger { logFileURL = documentDirectory.appendingPathComponent("logs.txt") } - func log(_ message: String, type: String = "General") { - guard logFilterViewModel.isFilterEnabled(for: type) else { return } + enum LogLevel: String { + case info = "Info" + case warning = "Warning" + case error = "Error" + case general = "General" + case debug = "Debug" + case html = "HTML" + } - let entry = LogEntry(message: message, type: type, timestamp: Date()) + func log(_ message: String, type: LogLevel = .general) { + guard logFilterViewModel.isFilterEnabled(for: type.rawValue) else { return } + + let entry = LogEntry(message: message, type: type.rawValue, timestamp: Date()) logs.append(entry) saveLogToFile(entry) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/Components/VolumeSlider.swift b/Sora/Utils/MediaPlayer/CustomPlayer/Components/VolumeSlider.swift index a2aaf83..2722891 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/Components/VolumeSlider.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/Components/VolumeSlider.swift @@ -106,11 +106,11 @@ struct VolumeSlider: View { switch p { case muteThreshold: return "speaker.slash.fill" - case muteThreshold..= $0.startTime && adjustedTime <= $0.endTime } + let adjustedTime = currentTimeVal - subtitleDelay + let cues = subtitlesLoader.cues.filter { adjustedTime >= $0.startTime && adjustedTime <= $0.endTime } if cues.isEmpty { - self.subtitleLabels[0].text = cues[0].text.strippedHTML - self.subtitleLabels[0].isHidden = false + subtitleLabels[0].text = cues[0].text.strippedHTML + subtitleLabels[0].isHidden = false } else { - self.subtitleLabels[0].text = "" - self.subtitleLabels[0].isHidden = !self.subtitlesEnabled + subtitleLabels[0].text = "" + subtitleLabels[0].isHidden = !subtitlesEnabled } if cues.count > 1 { - self.subtitleLabels[1].text = cues[1].text.strippedHTML - self.subtitleLabels[1].isHidden = false + subtitleLabels[1].text = cues[1].text.strippedHTML + subtitleLabels[1].isHidden = false } else { - self.subtitleLabels[1].text = "" - self.subtitleLabels[1].isHidden = true + subtitleLabels[1].text = "" + subtitleLabels[1].isHidden = true } } else { - self.subtitleLabels[0].text = "" - self.subtitleLabels[0].isHidden = true - self.subtitleLabels[1].text = "" - self.subtitleLabels[1].isHidden = true + subtitleLabels[0].text = "" + subtitleLabels[0].isHidden = true + subtitleLabels[1].text = "" + subtitleLabels[1].isHidden = true } - let segmentsColor = self.getSegmentsColor() + let segmentsColor = getSegmentsColor() DispatchQueue.main.async { if let currentItem = self.player.currentItem, currentItem.duration.seconds > 0 { @@ -1457,15 +1456,15 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele toleranceBefore: .zero, toleranceAfter: .zero ) { [weak self] _ in - guard let self = self else { return } + guard let self else { return } - let final = self.player.currentTime().seconds - self.sliderViewModel.sliderValue = final - self.currentTimeVal = final - self.isSliderEditing = false + let final = player.currentTime().seconds + sliderViewModel.sliderValue = final + currentTimeVal = final + isSliderEditing = false - if self.wasPlayingBeforeSeek { - self.player.playImmediately(atRate: self.originalRate) + if wasPlayingBeforeSeek { + player.playImmediately(atRate: originalRate) } } } @@ -1497,8 +1496,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele func startUpdateTimer() { updateTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in - guard let self = self else { return } - self.currentTimeVal = self.player.currentTime().seconds + guard let self else { return } + currentTimeVal = player.currentTime().seconds } } @@ -1525,7 +1524,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele dimButton.alpha = 1.0 dimButtonTimer?.invalidate() dimButtonTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { [weak self] _ in - guard let self = self else { return } + guard let self else { return } UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseInOut]) { self.dimButton.alpha = 0 } @@ -1589,7 +1588,10 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele let finalSkip = skipValue > 0 ? skipValue : 10 currentTimeVal = min(currentTimeVal + finalSkip, duration) player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600)) { [weak self] _ in - guard self != nil else { return } } + guard self != nil else { + return + } + } animateButtonRotation(forwardButton) } @@ -1699,33 +1701,32 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele private func tryAniListUpdate() { let aniListMutation = AniListMutation() aniListMutation.updateAnimeProgress(animeId: self.aniListID, episodeNumber: self.episodeNumber) { [weak self] result in - guard let self = self else { return } + guard let self else { return } switch result { case .success: - self.aniListUpdatedSuccessfully = true - Logger.shared.log("Successfully updated AniList progress for episode \(self.episodeNumber)", type: "General") + aniListUpdatedSuccessfully = true + Logger.shared.log("Successfully updated AniList progress for episode \(episodeNumber)", type: .general) case .failure(let error): let errorString = error.localizedDescription.lowercased() - Logger.shared.log("AniList progress update failed: \(errorString)", type: "Error") + Logger.shared.log("AniList progress update failed: \(errorString)", type: .error) if errorString.contains("access token not found") { - Logger.shared.log("AniList update will NOT retry due to missing token.", type: "Error") - self.aniListUpdateImpossible = true - + Logger.shared.log("AniList update will NOT retry due to missing token.", type: .error) + aniListUpdateImpossible = true } else { - if self.aniListRetryCount < self.aniListMaxRetries { - self.aniListRetryCount += 1 + if aniListRetryCount < aniListMaxRetries { + aniListRetryCount += 1 let delaySeconds = 5.0 - Logger.shared.log("AniList update will retry in \(delaySeconds)s (attempt \(self.aniListRetryCount)).", type: "Debug") + Logger.shared.log("AniList update will retry in \(delaySeconds)s (attempt \(aniListRetryCount)).", type: .debug) DispatchQueue.main.asyncAfter(deadline: .now() + delaySeconds) { self.tryAniListUpdate() } } else { - Logger.shared.log("AniList update reached max retries. No more attempts.", type: "Error") + Logger.shared.log("AniList update reached max retries. No more attempts.", type: .error) } } } @@ -1763,7 +1764,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele forHTTPHeaderField: "User-Agent") URLSession.shared.dataTask(with: request) { [weak self] data, _, _ in - guard let self = self, + guard let self, let data, let content = String(data: data, encoding: .utf8) else { Logger.shared.log("Failed to load m3u8 file") @@ -1782,8 +1783,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele func getQualityName(for height: Int) -> String { switch height { case 1080...: return "\(height)p (FHD)" - case 720..<1080: return "\(height)p (HD)" - case 480..<720: return "\(height)p (SD)" + case 720 ..< 1080: return "\(height)p (HD)" + case 480 ..< 720: return "\(height)p (SD)" default: return "\(height)p" } } @@ -1793,17 +1794,15 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele if let resolutionRange = line.range(of: "RESOLUTION="), let resolutionEndRange = line[resolutionRange.upperBound...].range(of: ",") ?? line[resolutionRange.upperBound...].range(of: "\n") { - - let resolutionPart = String(line[resolutionRange.upperBound.. 0 ? holdSpeed : 2.0 @@ -2282,7 +2285,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele guard self != nil else { return } if player.timeControlStatus == .paused, let reason = player.reasonForWaitingToPlay { - Logger.shared.log("Paused reason: \(reason)", type: "Error") + Logger.shared.log("Paused reason: \(reason)", type: .error) if reason == .toMinimizeStalls { player.play() } diff --git a/Sora/Utils/MediaPlayer/NormalPlayer.swift b/Sora/Utils/MediaPlayer/NormalPlayer.swift index 3258aff..2588e70 100644 --- a/Sora/Utils/MediaPlayer/NormalPlayer.swift +++ b/Sora/Utils/MediaPlayer/NormalPlayer.swift @@ -56,7 +56,7 @@ class NormalPlayer: AVPlayerViewController { try audioSession.overrideOutputAudioPort(.speaker) } catch { - Logger.shared.log("Didn't set up AVAudioSession: \(error)", type: "Debug") + Logger.shared.log("Didn't set up AVAudioSession: \(error)", type: .debug) } } } diff --git a/Sora/Utils/MediaPlayer/VideoPlayer.swift b/Sora/Utils/MediaPlayer/VideoPlayer.swift index 4053e89..bf7346e 100644 --- a/Sora/Utils/MediaPlayer/VideoPlayer.swift +++ b/Sora/Utils/MediaPlayer/VideoPlayer.swift @@ -5,8 +5,8 @@ // Created by Francesco on 09/01/25. // -import UIKit import AVKit +import UIKit class VideoPlayerViewController: UIViewController { let module: ScrapingModule @@ -16,13 +16,13 @@ class VideoPlayerViewController: UIViewController { var playerViewController: NormalPlayer? var timeObserverToken: Any? var streamUrl: String? - var fullUrl: String = "" - var subtitles: String = "" - var aniListID: Int = 0 + var fullUrl = "" + var subtitles = "" + var aniListID = 0 - var episodeNumber: Int = 0 - var episodeImageUrl: String = "" - var mediaTitle: String = "" + var episodeNumber = 0 + var episodeImageUrl = "" + var mediaTitle = "" init(module: ScrapingModule, continueWatchingManager: ContinueWatchingManager) { self.module = module @@ -30,6 +30,7 @@ class VideoPlayerViewController: UIViewController { super.init(nibName: nil, bundle: nil) } + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -37,7 +38,7 @@ class VideoPlayerViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - guard let streamUrl = streamUrl, let url = URL(string: streamUrl) else { + guard let streamUrl, let url = URL(string: streamUrl) else { return } @@ -54,7 +55,7 @@ class VideoPlayerViewController: UIViewController { playerViewController = NormalPlayer() playerViewController?.player = player - if let playerViewController = playerViewController { + if let playerViewController { addChild(playerViewController) playerViewController.view.frame = view.bounds playerViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] @@ -86,7 +87,7 @@ class VideoPlayerViewController: UIViewController { UserDefaults.standard.set(playbackSpeed, forKey: "lastPlaybackSpeed") } player?.pause() - if let timeObserverToken = timeObserverToken { + if let timeObserverToken { player?.removeTimeObserver(timeObserverToken) self.timeObserverToken = nil } @@ -104,7 +105,7 @@ class VideoPlayerViewController: UIViewController { let interval = CMTime(seconds: 1.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC)) timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in - guard let self = self, + guard let self, let currentItem = player.currentItem, currentItem.duration.seconds.isFinite else { return @@ -116,34 +117,34 @@ class VideoPlayerViewController: UIViewController { UserDefaults.standard.set(currentTime, forKey: "lastPlayedTime_\(fullURL)") UserDefaults.standard.set(duration, forKey: "totalTime_\(fullURL)") - if let streamUrl = self.streamUrl { + if let streamUrl { let progress = min(max(currentTime / duration, 0), 1.0) let item = ContinueWatchingItem( id: UUID(), - imageUrl: self.episodeImageUrl, - episodeNumber: self.episodeNumber, - mediaTitle: self.mediaTitle, + imageUrl: episodeImageUrl, + episodeNumber: episodeNumber, + mediaTitle: mediaTitle, progress: progress, streamUrl: streamUrl, - fullUrl: self.fullUrl, - subtitles: self.subtitles, - aniListID: self.aniListID, - module: self.module + fullUrl: fullUrl, + subtitles: subtitles, + aniListID: aniListID, + module: module ) continueWatchingManager.save(item: item) } let remainingPercentage = (duration - currentTime) / duration - if remainingPercentage < 0.1 && self.aniListID != 0 { + if remainingPercentage < 0.1 && aniListID != 0 { let aniListMutation = AniListMutation() - aniListMutation.updateAnimeProgress(animeId: self.aniListID, episodeNumber: self.episodeNumber) { result in + aniListMutation.updateAnimeProgress(animeId: aniListID, episodeNumber: episodeNumber) { result in switch result { case .success: - Logger.shared.log("Successfully updated AniList progress for episode \(self.episodeNumber)", type: "General") + Logger.shared.log("Successfully updated AniList progress for episode \(self.episodeNumber)", type: .general) case .failure(let error): - Logger.shared.log("Failed to update AniList progress: \(error.localizedDescription)", type: "Error") + Logger.shared.log("Failed to update AniList progress: \(error.localizedDescription)", type: .error) } } } @@ -159,16 +160,16 @@ class VideoPlayerViewController: UIViewController { } override var prefersHomeIndicatorAutoHidden: Bool { - return true + true } override var prefersStatusBarHidden: Bool { - return true + true } deinit { player?.pause() - if let timeObserverToken = timeObserverToken { + if let timeObserverToken { player?.removeTimeObserver(timeObserverToken) } } diff --git a/Sora/Utils/Modules/ModuleAdditionSettingsView.swift b/Sora/Utils/Modules/ModuleAdditionSettingsView.swift index d726d9f..79ccd04 100644 --- a/Sora/Utils/Modules/ModuleAdditionSettingsView.swift +++ b/Sora/Utils/Modules/ModuleAdditionSettingsView.swift @@ -151,7 +151,7 @@ struct ModuleAdditionSettingsView: View { await MainActor.run { errorMessage = "Invalid URL" isLoading = false - Logger.shared.log("Failed to open add module ui with url: \(moduleUrl)", type: "Error") + Logger.shared.log("Failed to open add module ui with url: \(moduleUrl)", type: .error) } return } diff --git a/Sora/Utils/Modules/ModuleManager.swift b/Sora/Utils/Modules/ModuleManager.swift index 65b97f2..e00dd45 100644 --- a/Sora/Utils/Modules/ModuleManager.swift +++ b/Sora/Utils/Modules/ModuleManager.swift @@ -18,9 +18,9 @@ class ModuleManager: ObservableObject { if !FileManager.default.fileExists(atPath: url.path) { do { try "[]".write(to: url, atomically: true, encoding: .utf8) - Logger.shared.log("Created empty modules file", type: "Info") + Logger.shared.log("Created empty modules file", type: .info) } catch { - Logger.shared.log("Failed to create modules file: \(error.localizedDescription)", type: "Error") + Logger.shared.log("Failed to create modules file: \(error.localizedDescription)", type: .error) } } loadModules() @@ -38,7 +38,7 @@ class ModuleManager: ObservableObject { let url = getModulesFilePath() guard FileManager.default.fileExists(atPath: url.path) else { - Logger.shared.log("No modules file found after sync", type: "Error") + Logger.shared.log("No modules file found after sync", type: .error) modules = [] return } @@ -53,7 +53,7 @@ class ModuleManager: ObservableObject { } Logger.shared.log("Reloaded modules after iCloud sync") } catch { - Logger.shared.log("Error handling modules sync: \(error.localizedDescription)", type: "Error") + Logger.shared.log("Error handling modules sync: \(error.localizedDescription)", type: .error) modules = [] } } @@ -71,12 +71,12 @@ class ModuleManager: ObservableObject { let url = getModulesFilePath() guard FileManager.default.fileExists(atPath: url.path) else { - Logger.shared.log("Modules file does not exist, creating empty one", type: "Info") + Logger.shared.log("Modules file does not exist, creating empty one", type: .info) do { try "[]".write(to: url, atomically: true, encoding: .utf8) modules = [] } catch { - Logger.shared.log("Failed to create modules file: \(error.localizedDescription)", type: "Error") + Logger.shared.log("Failed to create modules file: \(error.localizedDescription)", type: .error) modules = [] } return @@ -92,18 +92,18 @@ class ModuleManager: ObservableObject { await checkJSModuleFiles() } } catch { - Logger.shared.log("Failed to decode modules: \(error.localizedDescription)", type: "Error") + Logger.shared.log("Failed to decode modules: \(error.localizedDescription)", type: .error) try "[]".write(to: url, atomically: true, encoding: .utf8) modules = [] } } catch { - Logger.shared.log("Failed to load modules file: \(error.localizedDescription)", type: "Error") + Logger.shared.log("Failed to load modules file: \(error.localizedDescription)", type: .error) modules = [] } } func checkJSModuleFiles() async { - Logger.shared.log("Checking JS module files...", type: "Info") + Logger.shared.log("Checking JS module files...", type: .info) var missingCount = 0 for module in modules { @@ -112,30 +112,30 @@ class ModuleManager: ObservableObject { missingCount += 1 do { guard let scriptUrl = URL(string: module.metadata.scriptUrl) else { - Logger.shared.log("Invalid script URL for module: \(module.metadata.sourceName)", type: "Error") + Logger.shared.log("Invalid script URL for module: \(module.metadata.sourceName)", type: .error) continue } - Logger.shared.log("Downloading missing JS file for: \(module.metadata.sourceName)", type: "Info") + Logger.shared.log("Downloading missing JS file for: \(module.metadata.sourceName)", type: .info) let (scriptData, _) = try await URLSession.custom.data(from: scriptUrl) guard let jsContent = String(data: scriptData, encoding: .utf8) else { - Logger.shared.log("Invalid script encoding for module: \(module.metadata.sourceName)", type: "Error") + Logger.shared.log("Invalid script encoding for module: \(module.metadata.sourceName)", type: .error) continue } try jsContent.write(to: localUrl, atomically: true, encoding: .utf8) Logger.shared.log("Successfully downloaded JS file for module: \(module.metadata.sourceName)") } catch { - Logger.shared.log("Failed to download JS file for module: \(module.metadata.sourceName) - \(error.localizedDescription)", type: "Error") + Logger.shared.log("Failed to download JS file for module: \(module.metadata.sourceName) - \(error.localizedDescription)", type: .error) } } } if missingCount > 0 { - Logger.shared.log("Downloaded \(missingCount) missing module JS files", type: "Info") + Logger.shared.log("Downloaded \(missingCount) missing module JS files", type: .info) } else { - Logger.shared.log("All module JS files are present", type: "Info") + Logger.shared.log("All module JS files are present", type: .info) } } diff --git a/Sora/Utils/ProfileStore/ProfileStore.swift b/Sora/Utils/ProfileStore/ProfileStore.swift index 35b0294..eb44025 100644 --- a/Sora/Utils/ProfileStore/ProfileStore.swift +++ b/Sora/Utils/ProfileStore/ProfileStore.swift @@ -42,7 +42,7 @@ class ProfileStore: ObservableObject { fatalError("This can only fail if suiteName == app bundle id ...") } - Logger.shared.log("loaded UserDefaults suite for '\(currentProfile.name)'", type: "Profile") + Logger.shared.log("loaded UserDefaults suite for '\(currentProfile.name)'", type: .info) return suite } diff --git a/Sora/Utils/iCloudSyncManager/iCloudSyncManager.swift b/Sora/Utils/iCloudSyncManager/iCloudSyncManager.swift index be76d08..2e01b1e 100644 --- a/Sora/Utils/iCloudSyncManager/iCloudSyncManager.swift +++ b/Sora/Utils/iCloudSyncManager/iCloudSyncManager.swift @@ -168,14 +168,14 @@ class ICloudSyncManager { try FileManager.default.copyItem(at: localModulesURL, to: iCloudModulesURL) } } catch { - Logger.shared.log("iCloud modules sync error: \(error)", type: "Error") + Logger.shared.log("iCloud modules sync error: \(error)", type: .error) } } } func syncModulesFromiCloud() { guard let iCloudURL = self.ubiquityContainerURL else { - Logger.shared.log("iCloud container not available", type: "Error") + Logger.shared.log("iCloud container not available", type: .error) return } @@ -184,13 +184,13 @@ class ICloudSyncManager { do { if !FileManager.default.fileExists(atPath: iCloudModulesURL.path) { - Logger.shared.log("No modules file found in iCloud", type: "Info") + Logger.shared.log("No modules file found in iCloud", type: .info) if FileManager.default.fileExists(atPath: localModulesURL.path) { - Logger.shared.log("Copying local modules file to iCloud", type: "Info") + Logger.shared.log("Copying local modules file to iCloud", type: .info) try FileManager.default.copyItem(at: localModulesURL, to: iCloudModulesURL) } else { - Logger.shared.log("Creating new empty modules file in iCloud", type: "Info") + Logger.shared.log("Creating new empty modules file in iCloud", type: .info) let emptyModules: [ScrapingModule] = [] let emptyData = try JSONEncoder().encode(emptyModules) try emptyData.write(to: iCloudModulesURL) @@ -214,7 +214,7 @@ class ICloudSyncManager { } if shouldCopy { - Logger.shared.log("Syncing modules from iCloud", type: "Info") + Logger.shared.log("Syncing modules from iCloud", type: .info) if FileManager.default.fileExists(atPath: localModulesURL.path) { try FileManager.default.removeItem(at: localModulesURL) } @@ -225,7 +225,7 @@ class ICloudSyncManager { } } } catch { - Logger.shared.log("iCloud modules sync error: \(error)", type: "Error") + Logger.shared.log("iCloud modules sync error: \(error)", type: .error) } } diff --git a/Sora/Views/ExploreView/ExploreView.swift b/Sora/Views/ExploreView/ExploreView.swift index 311c458..5969390 100644 --- a/Sora/Views/ExploreView/ExploreView.swift +++ b/Sora/Views/ExploreView/ExploreView.swift @@ -199,7 +199,7 @@ struct ExploreView: View { Menu { if getModuleLanguageGroups().isEmpty { Button("No modules available") { - print("[Error] No Modules Button clicked") + Logger.shared.log("No Modules Button clicked", type: .error) } .disabled(true) @@ -262,7 +262,7 @@ struct ExploreView: View { } private func fetchData() { - Logger.shared.log("Fetching Explore Data", type: "General") + Logger.shared.log("Fetching Explore Data", type: .general) guard let module = selectedModule else { exploreItems = [] hasNoResults = false @@ -292,7 +292,7 @@ struct ExploreView: View { } } } catch { - Logger.shared.log("Error loading module: \(error)", type: "Error") + Logger.shared.log("Error loading module: \(error)", type: .error) isLoading = false hasNoResults = true } diff --git a/Sora/Views/LibraryView/LibraryManager.swift b/Sora/Views/LibraryView/LibraryManager.swift index 35c61f7..bc99d58 100644 --- a/Sora/Views/LibraryView/LibraryManager.swift +++ b/Sora/Views/LibraryView/LibraryManager.swift @@ -52,14 +52,14 @@ class LibraryManager: ObservableObject { if let index = bookmarks.firstIndex(where: { $0.id == item.id }) { bookmarks.remove(at: index) - Logger.shared.log("Removed series \(item.id) from bookmarks.", type: "Debug") + Logger.shared.log("Removed series \(item.id) from bookmarks.", type: .debug) saveBookmarks() } } private func loadBookmarks() { guard let data = userDefaultsSuite.data(forKey: bookmarksKey) else { - Logger.shared.log("No bookmarks data found in UserDefaults.", type: "Debug") + Logger.shared.log("No bookmarks data found in UserDefaults.", type: .debug) bookmarks = [] return } @@ -67,7 +67,7 @@ class LibraryManager: ObservableObject { do { bookmarks = try JSONDecoder().decode([LibraryItem].self, from: data) } catch { - Logger.shared.log("Failed to decode bookmarks: \(error.localizedDescription)", type: "Error") + Logger.shared.log("Failed to decode bookmarks: \(error.localizedDescription)", type: .error) bookmarks = [] } } @@ -77,7 +77,7 @@ class LibraryManager: ObservableObject { let encoded = try JSONEncoder().encode(bookmarks) userDefaultsSuite.set(encoded, forKey: bookmarksKey) } catch { - Logger.shared.log("Failed to save bookmarks: \(error)", type: "Error") + Logger.shared.log("Failed to save bookmarks: \(error)", type: .error) } } diff --git a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift index 08e355c..30bdfa4 100644 --- a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift +++ b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift @@ -160,7 +160,7 @@ struct EpisodeCell: View { URLSession.custom.dataTask(with: url) { data, _, error in if let error { - Logger.shared.log("Failed to fetch anime episode details: \(error)", type: "Error") + Logger.shared.log("Failed to fetch anime episode details: \(error)", type: .error) DispatchQueue.main.async { isLoading = false } return } @@ -177,7 +177,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 { - Logger.shared.log("Invalid anime response format", type: "Error") + Logger.shared.log("Invalid anime response format", type: .error) DispatchQueue.main.async { isLoading = false } return } diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index d7df916..8fdb362 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -22,28 +22,28 @@ struct MediaInfoView: View { let href: String let module: ScrapingModule - @State var aliases: String = "" - @State var synopsis: String = "" - @State var airdate: String = "" - @State var episodeLinks: [EpisodeLink] = [] - @State var itemID: Int? - @State var tmdbID: Int? + @State private var aliases = "" + @State private var synopsis = "" + @State private var airdate = "" + @State private var episodeLinks: [EpisodeLink] = [] + @State private var itemID: Int? + @State private var tmdbID: Int? - @State var isLoading: Bool = true - @State var showFullSynopsis: Bool = false - @State var hasFetched: Bool = false - @State var isRefetching: Bool = true - @State var isFetchingEpisode: Bool = false + @State private var isLoading = true + @State private var showFullSynopsis = false + @State private var hasFetched = false + @State private var isRefetching = true + @State private var isFetchingEpisode = false - @State private var refreshTrigger: Bool = false - @State private var buttonRefreshTrigger: Bool = false + @State private var refreshTrigger = false + @State private var buttonRefreshTrigger = false - @State private var selectedEpisodeNumber: Int = 0 - @State private var selectedEpisodeImage: String = "" - @State private var selectedSeason: Int = 0 + @State private var selectedEpisodeNumber = 0 + @State private var selectedEpisodeImage = "" + @State private var selectedSeason = 0 - @AppStorage("externalPlayer") private var externalPlayer: String = "Default" - @AppStorage("episodeChunkSize") private var episodeChunkSize: Int = 100 + @AppStorage("externalPlayer") private var externalPlayer = "Default" + @AppStorage("episodeChunkSize") private var episodeChunkSize = 100 @StateObject private var jsController = JSController() @EnvironmentObject var moduleManager: ModuleManager @@ -53,16 +53,16 @@ struct MediaInfoView: View { @State private var selectedRange: Range = 0..<100 @State private var showSettingsMenu = false @State private var customAniListID: Int? - @State private var showStreamLoadingView: Bool = false - @State private var currentStreamTitle: String = "" + @State private var showStreamLoadingView = false + @State private var currentStreamTitle = "" @State private var activeFetchID: UUID? @Environment(\.dismiss) private var dismiss - @State private var orientationChanged: Bool = false + @State private var orientationChanged = false private var isGroupedBySeasons: Bool { - return groupedEpisodes().count > 1 + groupedEpisodes().count > 1 } var body: some View { @@ -112,6 +112,7 @@ struct MediaInfoView: View { .resizable() .frame(width: 15, height: 15) .foregroundColor(.secondary) + .accessibilityLabel("Calendar Icon") Text(airdate) .font(.system(size: 12)) @@ -134,6 +135,7 @@ struct MediaInfoView: View { .resizable() .frame(width: 20, height: 20) .foregroundColor(.primary) + .accessibilityLabel("Safari Icon") } .padding(4) .background(Capsule().fill(Color.accentColor.opacity(0.4))) @@ -146,7 +148,7 @@ struct MediaInfoView: View { Label("Set Custom AniList ID", systemImage: "number") } - if let _ = customAniListID { + if customAniListID != nil { Button(action: { customAniListID = nil itemID = nil @@ -176,7 +178,7 @@ struct MediaInfoView: View { Divider() Button(action: { - Logger.shared.log("Debug Info:\nTitle: \(title)\nHref: \(href)\nModule: \(module.metadata.sourceName)\nAniList ID: \(itemID ?? -1)\nCustom ID: \(customAniListID ?? -1)", type: "Debug") + Logger.shared.log("Debug Info:\nTitle: \(title)\nHref: \(href)\nModule: \(module.metadata.sourceName)\nAniList ID: \(itemID ?? -1)\nCustom ID: \(customAniListID ?? -1)", type: .debug) DropManager.shared.showDrop(title: "Debug Info Logged", subtitle: "", duration: 1.0, icon: UIImage(systemName: "terminal")) }) { Label("Log Debug Info", systemImage: "terminal") @@ -186,6 +188,7 @@ struct MediaInfoView: View { .resizable() .frame(width: 20, height: 20) .foregroundColor(.primary) + .accessibilityLabel("More Icon") } } } @@ -221,6 +224,7 @@ struct MediaInfoView: View { HStack { Image(systemName: "play.fill") .foregroundColor(.primary) + .accessibilityLabel("Play Icon") Text(startWatchingText) .font(.headline) .foregroundColor(.primary) @@ -246,6 +250,7 @@ struct MediaInfoView: View { .resizable() .frame(width: 20, height: 27) .foregroundColor(Color.accentColor) + .accessibilityLabel("Bookmark Icon") } } @@ -287,6 +292,7 @@ struct MediaInfoView: View { } } } + if isGroupedBySeasons { let seasons = groupedEpisodes() if !seasons.isEmpty, selectedSeason < seasons.count { @@ -318,8 +324,8 @@ struct MediaInfoView: View { for ep2 in seasons[selectedSeason] where ep2.number < ep.number { let href = ep2.href - updates["lastPlayedTime_\(href)"] = 99999999.0 - updates["totalTime_\(href)"] = 99999999.0 + updates["lastPlayedTime_\(href)"] = 99_999_999.0 + updates["totalTime_\(href)"] = 99_999_999.0 } for (key, value) in updates { @@ -329,7 +335,7 @@ struct MediaInfoView: View { userDefaults.synchronize() refreshTrigger.toggle() - Logger.shared.log("Marked episodes watched within season \(selectedSeason + 1) of \"\(title)\".", type: "General") + Logger.shared.log("Marked episodes watched within season \(selectedSeason + 1) of \"\(title)\".", type: .general) } ) .id(refreshTrigger) @@ -379,7 +385,7 @@ struct MediaInfoView: View { } refreshTrigger.toggle() - Logger.shared.log("Marked \(ep.number - 1) episodes watched within series \"\(title)\".", type: "General") + Logger.shared.log("Marked \(ep.number - 1) episodes watched within series \"\(title)\".", type: .general) } ) .id(refreshTrigger) @@ -401,6 +407,7 @@ struct MediaInfoView: View { Image(systemName: "exclamationmark.triangle") .font(.largeTitle) .foregroundColor(.secondary) + .accessibilityLabel("Warning Icon") HStack(spacing: 2) { Text("No episodes Found:") .foregroundColor(.secondary) @@ -425,37 +432,6 @@ struct MediaInfoView: View { } } } - .onAppear { - buttonRefreshTrigger.toggle() - - if !hasFetched { - DropManager.shared.showDrop(title: "Fetching Data", subtitle: "Please wait while fetching.", duration: 0.5, icon: UIImage(systemName: "arrow.triangle.2.circlepath")) - fetchDetails() - - if let savedID = UserDefaults.standard.object(forKey: "custom_anilist_id_\(href)") as? Int { - customAniListID = savedID - itemID = savedID - Logger.shared.log("Using custom AniList ID: \(savedID)", type: "Debug") - } else { - fetchItemID(byTitle: cleanTitle(title)) { result in - switch result { - case .success(let id): - itemID = id - case .failure(let error): - Logger.shared.log("Failed to fetch AniList ID: \(error)") - AnalyticsManager.shared.sendEvent(event: "error", additionalData: ["error": error, "message": "Failed to fetch AniList ID"]) - } - } - } - - hasFetched = true - AnalyticsManager.shared.sendEvent(event: "search", additionalData: ["title": title]) - } - selectedRange = 0.. 1 { - self.showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) + showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) } else { - self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) + playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) } } else { - self.handleStreamFailure(error: nil) + handleStreamFailure(error: nil) } DispatchQueue.main.async { - self.isFetchingEpisode = false + isFetchingEpisode = false } } } else if module.metadata.streamAsyncJS == true { jsController.fetchStreamUrlJSSecond(episodeUrl: href, softsub: true, module: module) { result in - guard self.activeFetchID == fetchID else { return } + guard activeFetchID == fetchID else { return } if let streams = result.streams, !streams.isEmpty { if streams.count > 1 { - self.showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) + showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) } else { - self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) + playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) } } else { - self.handleStreamFailure(error: nil) + handleStreamFailure(error: nil) } DispatchQueue.main.async { - self.isFetchingEpisode = false + isFetchingEpisode = false } } } else { jsController.fetchStreamUrl(episodeUrl: href, softsub: true, module: module) { result in - guard self.activeFetchID == fetchID else { return } + guard activeFetchID == fetchID else { return } if let streams = result.streams, !streams.isEmpty { if streams.count > 1 { - self.showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) + showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) } else { - self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) + playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) } } else { - self.handleStreamFailure(error: nil) + handleStreamFailure(error: nil) } DispatchQueue.main.async { - self.isFetchingEpisode = false + isFetchingEpisode = false } } } } else { if module.metadata.asyncJS == true { jsController.fetchStreamUrlJS(episodeUrl: href, module: module) { result in - guard self.activeFetchID == fetchID else { return } + guard activeFetchID == fetchID else { return } if let streams = result.streams, !streams.isEmpty { if streams.count > 1 { - self.showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) + showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) } else { - self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) + playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) } } else { - self.handleStreamFailure(error: nil) + handleStreamFailure(error: nil) } DispatchQueue.main.async { - self.isFetchingEpisode = false + isFetchingEpisode = false } } } else if module.metadata.streamAsyncJS == true { jsController.fetchStreamUrlJSSecond(episodeUrl: href, module: module) { result in - guard self.activeFetchID == fetchID else { return } + guard activeFetchID == fetchID else { return } if let streams = result.streams, !streams.isEmpty { if streams.count > 1 { - self.showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) + showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) } else { - self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) + playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) } } else { - self.handleStreamFailure(error: nil) + handleStreamFailure(error: nil) } DispatchQueue.main.async { - self.isFetchingEpisode = false + isFetchingEpisode = false } } } else { jsController.fetchStreamUrl(episodeUrl: href, module: module) { result in - guard self.activeFetchID == fetchID else { return } + guard activeFetchID == fetchID else { return } if let streams = result.streams, !streams.isEmpty { if streams.count > 1 { - self.showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) + showStreamSelectionAlert(streams: streams, fullURL: href, subtitles: result.subtitles?.first) } else { - self.playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) + playStream(url: streams[0], fullURL: href, subtitles: result.subtitles?.first) } } else { - self.handleStreamFailure(error: nil) + handleStreamFailure(error: nil) } DispatchQueue.main.async { - self.isFetchingEpisode = false + isFetchingEpisode = false } } } } } catch { - self.handleStreamFailure(error: error) + handleStreamFailure(error: error) DispatchQueue.main.async { - self.isFetchingEpisode = false + isFetchingEpisode = false } } } @@ -771,7 +779,7 @@ struct MediaInfoView: View { self.isFetchingEpisode = false self.showStreamLoadingView = false if let error { - Logger.shared.log("Error loading module: \(error)", type: "Error") + Logger.shared.log("Error loading module: \(error)", type: .error) AnalyticsManager.shared.sendEvent(event: "error", additionalData: ["error": error, "message": "Failed to fetch stream"]) } DropManager.shared.showDrop(title: "Stream not Found", subtitle: "", duration: 0.5, icon: UIImage(systemName: "xmark")) @@ -810,7 +818,7 @@ struct MediaInfoView: View { } alert.addAction(UIAlertAction(title: title, style: .default) { _ in - self.playStream(url: streamUrl, fullURL: fullURL, subtitles: subtitles) + playStream(url: streamUrl, fullURL: fullURL, subtitles: subtitles) }) streamIndex += 1 @@ -821,7 +829,6 @@ struct MediaInfoView: View { if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let window = windowScene.windows.first, let rootVC = window.rootViewController { - if UIDevice.current.userInterfaceIdiom == .pad { if let popover = alert.popoverPresentationController { popover.sourceView = window @@ -839,7 +846,7 @@ struct MediaInfoView: View { } DispatchQueue.main.async { - self.isFetchingEpisode = false + isFetchingEpisode = false } } } @@ -885,12 +892,12 @@ struct MediaInfoView: View { break } - if let scheme = scheme, let url = URL(string: scheme), UIApplication.shared.canOpenURL(url) { + if let scheme, let url = URL(string: scheme), UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url, options: [:], completionHandler: nil) - Logger.shared.log("Opening external app with scheme: \(url)", type: "General") + Logger.shared.log("Opening external app with scheme: \(url)", type: .general) } else { guard let url = URL(string: url) else { - Logger.shared.log("Invalid stream URL: \(url)", type: "Error") + Logger.shared.log("Invalid stream URL: \(url)", type: .error) DropManager.shared.showDrop(title: "Error", subtitle: "Invalid stream URL", duration: 2.0, icon: UIImage(systemName: "xmark.circle")) return } @@ -916,7 +923,7 @@ struct MediaInfoView: View { let rootVC = windowScene.windows.first?.rootViewController { FindTopViewController.findViewController(rootVC).present(customMediaPlayer, animated: true, completion: nil) } else { - Logger.shared.log("Failed to find root view controller", type: "Error") + Logger.shared.log("Failed to find root view controller", type: .error) DropManager.shared.showDrop(title: "Error", subtitle: "Failed to present player", duration: 2.0, icon: UIImage(systemName: "xmark.circle")) } } @@ -926,7 +933,7 @@ struct MediaInfoView: View { private func selectNextEpisode() { guard let currentIndex = episodeLinks.firstIndex(where: { $0.number == selectedEpisodeNumber }), currentIndex + 1 < episodeLinks.count else { - Logger.shared.log("No more episodes to play", type: "Info") + Logger.shared.log("No more episodes to play", type: .info) return } @@ -938,7 +945,7 @@ struct MediaInfoView: View { private func openSafariViewController(with urlString: String) { guard let url = URL(string: urlString), UIApplication.shared.canOpenURL(url) else { - Logger.shared.log("Unable to open the webpage", type: "Error") + Logger.shared.log("Unable to open the webpage", type: .error) return } let safariViewController = SFSafariViewController(url: url) @@ -949,7 +956,7 @@ struct MediaInfoView: View { } private func cleanTitle(_ title: String?) -> String { - guard let title = title else { return "Unknown" } + guard let title else { return "Unknown" } let cleaned = title.replacingOccurrences( of: "\\s*\\([^\\)]*\\)", @@ -1026,7 +1033,7 @@ struct MediaInfoView: View { customAniListID = id itemID = id UserDefaults.standard.set(id, forKey: "custom_anilist_id_\(href)") - Logger.shared.log("Set custom AniList ID: \(id)", type: "General") + Logger.shared.log("Set custom AniList ID: \(id)", type: .general) } }) diff --git a/Sora/Views/SearchView/SearchView.swift b/Sora/Views/SearchView/SearchView.swift index 0b02f1a..3f537ee 100644 --- a/Sora/Views/SearchView/SearchView.swift +++ b/Sora/Views/SearchView/SearchView.swift @@ -218,7 +218,7 @@ struct SearchView: View { Menu { if getModuleLanguageGroups().isEmpty { Button("No modules available") { - print("[Error] No Modules Button clicked") + Logger.shared.log("No Modules Button clicked", type: .error) } .disabled(true) @@ -290,7 +290,7 @@ struct SearchView: View { } private func performSearch() { - Logger.shared.log("Searching for: \(searchText)", type: "General") + Logger.shared.log("Searching for: \(searchText)", type: .general) guard !searchText.isEmpty, let module = selectedModule else { searchItems = [] hasNoResults = false @@ -320,7 +320,7 @@ struct SearchView: View { } } } catch { - Logger.shared.log("Error loading module: \(error)", type: "Error") + Logger.shared.log("Error loading module: \(error)", type: .error) isSearching = false hasNoResults = true } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewAlternateAppIconPicker.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewAlternateAppIconPicker.swift index 06c98af..83d5a8f 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewAlternateAppIconPicker.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewAlternateAppIconPicker.swift @@ -63,7 +63,7 @@ struct SettingsViewAlternateAppIconPicker: View { UIApplication.shared.setAlternateIconName(iconName == "Default" ? nil : "AppIcon_\(iconName)", completionHandler: { error in isPresented = false if let error { - print("Failed to set alternate icon: \(error.localizedDescription)") + Logger.shared.log("Failed to set alternate icon: \(error.localizedDescription)", type: .error) } }) } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift index 68dbeb5..7a25c19 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift @@ -61,7 +61,7 @@ struct SettingsViewData: View { if let domain = Bundle.main.bundleIdentifier { UserDefaults.standard.removePersistentDomain(forName: domain) UserDefaults.standard.synchronize() - Logger.shared.log("Cleared app data!", type: "General") + Logger.shared.log("Cleared app data!", type: .general) exit(0) } } @@ -75,10 +75,10 @@ struct SettingsViewData: View { for filePath in filePaths { try FileManager.default.removeItem(at: filePath) } - Logger.shared.log("Cache cleared successfully!", type: "General") + Logger.shared.log("Cache cleared successfully!", type: .general) } } catch { - Logger.shared.log("Failed to clear cache.", type: "Error") + Logger.shared.log("Failed to clear cache.", type: .error) } } @@ -90,10 +90,10 @@ struct SettingsViewData: View { for fileURL in fileURLs { try fileManager.removeItem(at: fileURL) } - Logger.shared.log("All files in documents folder removed", type: "General") + Logger.shared.log("All files in documents folder removed", type: .general) exit(0) } catch { - Logger.shared.log("Error removing files in documents folder: \(error)", type: "Error") + Logger.shared.log("Error removing files in documents folder: \(error)", type: .error) } } } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift index 64f11a7..666f1cb 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift @@ -121,12 +121,12 @@ struct SettingsViewGeneral: View { HStack { if UIDevice.current.userInterfaceIdiom == .pad { Picker("Portrait Columns", selection: $mediaColumnsPortrait) { - ForEach(1..<6) { i in Text("\(i)").tag(i) } + ForEach(1 ..< 6) { i in Text("\(i)").tag(i) } } .pickerStyle(MenuPickerStyle()) } else { Picker("Portrait Columns", selection: $mediaColumnsPortrait) { - ForEach(1..<5) { i in Text("\(i)").tag(i) } + ForEach(1 ..< 5) { i in Text("\(i)").tag(i) } } .pickerStyle(MenuPickerStyle()) } @@ -134,12 +134,12 @@ struct SettingsViewGeneral: View { HStack { if UIDevice.current.userInterfaceIdiom == .pad { Picker("Landscape Columns", selection: $mediaColumnsLandscape) { - ForEach(2..<9) { i in Text("\(i)").tag(i) } + ForEach(2 ..< 9) { i in Text("\(i)").tag(i) } } .pickerStyle(MenuPickerStyle()) } else { Picker("Landscape Columns", selection: $mediaColumnsLandscape) { - ForEach(2..<6) { i in Text("\(i)").tag(i) } + ForEach(2 ..< 6) { i in Text("\(i)").tag(i) } } .pickerStyle(MenuPickerStyle()) } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackers.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackers.swift index 260e6ab..0689329 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackers.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackers.swift @@ -309,7 +309,7 @@ struct SettingsViewTrackers: View { request.httpBody = try JSONSerialization.data(withJSONObject: body, options: []) } catch { anilistStatus = "Failed to serialize request" - Logger.shared.log("Failed to serialize request", type: "Error") + Logger.shared.log("Failed to serialize request", type: .error) isAnilistLoading = false return } @@ -319,12 +319,12 @@ struct SettingsViewTrackers: View { isAnilistLoading = false if let error { anilistStatus = "Error: \(error.localizedDescription)" - Logger.shared.log("Error: \(error.localizedDescription)", type: "Error") + Logger.shared.log("Error: \(error.localizedDescription)", type: .error) return } guard let data else { anilistStatus = "No data received" - Logger.shared.log("No data received", type: "Error") + Logger.shared.log("No data received", type: .error) return } do { @@ -339,11 +339,11 @@ struct SettingsViewTrackers: View { anilistStatus = "Logged in as \(name)" } else { anilistStatus = "Unexpected response format!" - Logger.shared.log("Unexpected response format!", type: "Error") + Logger.shared.log("Unexpected response format!", type: .error) } } catch { anilistStatus = "Failed to parse response: \(error.localizedDescription)" - Logger.shared.log("Failed to parse response: \(error.localizedDescription)", type: "Error") + Logger.shared.log("Failed to parse response: \(error.localizedDescription)", type: .error) } } }.resume()