mirror of
https://github.com/cranci1/Sora.git
synced 2026-03-11 17:45:37 +00:00
more linting ( 0 errors and warnings ), logger types, cleanup
This commit is contained in:
parent
ee11dc81c1
commit
63724c6ef0
36 changed files with 801 additions and 454 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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" : {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(()))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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?) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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([])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -106,11 +106,11 @@ struct VolumeSlider<T: BinaryFloatingPoint>: View {
|
|||
switch p {
|
||||
case muteThreshold:
|
||||
return "speaker.slash.fill"
|
||||
case muteThreshold..<lowThreshold:
|
||||
case muteThreshold ..< lowThreshold:
|
||||
return "speaker.fill"
|
||||
case lowThreshold..<midThreshold:
|
||||
case lowThreshold ..< midThreshold:
|
||||
return "speaker.wave.1.fill"
|
||||
case midThreshold..<highThreshold:
|
||||
case midThreshold ..< highThreshold:
|
||||
return "speaker.wave.2.fill"
|
||||
default:
|
||||
return "speaker.wave.3.fill"
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
var holdGesture: UILongPressGestureRecognizer?
|
||||
|
||||
var isPlaying = true
|
||||
var currentTimeVal: Double = 0.0
|
||||
var duration: Double = 0.0
|
||||
var currentTimeVal = 0.0
|
||||
var duration = 0.0
|
||||
var isVideoLoaded = false
|
||||
|
||||
private var isHoldPauseEnabled: Bool {
|
||||
|
|
@ -68,13 +68,13 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
private var currentMenuButtonTrailing: NSLayoutConstraint!
|
||||
|
||||
var subtitleForegroundColor: UIColor = .white
|
||||
var subtitleBackgroundEnabled: Bool = true
|
||||
var subtitleFontSize: Double = 20.0
|
||||
var subtitleShadowRadius: Double = 1.0
|
||||
var subtitleBackgroundEnabled = true
|
||||
var subtitleFontSize = 20.0
|
||||
var subtitleShadowRadius = 1.0
|
||||
var subtitlesLoader = VTTSubtitlesLoader()
|
||||
var subtitleStackView: UIStackView!
|
||||
var subtitleLabels: [UILabel] = []
|
||||
var subtitlesEnabled: Bool = true {
|
||||
var subtitlesEnabled = true {
|
||||
didSet {
|
||||
subtitleStackView.isHidden = !subtitlesEnabled
|
||||
}
|
||||
|
|
@ -97,7 +97,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
var qualityButton: UIButton!
|
||||
var holdSpeedIndicator: UIButton!
|
||||
|
||||
var isHLSStream: Bool = false
|
||||
var isHLSStream = false
|
||||
var qualities: [(String, String)] = []
|
||||
var currentQualityURL: URL?
|
||||
var baseM3U8URL: URL?
|
||||
|
|
@ -165,10 +165,10 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
private var audioSession = AVAudioSession.sharedInstance()
|
||||
private var hiddenVolumeView = MPVolumeView(frame: .zero)
|
||||
private var systemVolumeSlider: UISlider?
|
||||
private var volumeValue: Double = 0.0
|
||||
private var volumeValue = 0.0
|
||||
private var volumeViewModel = VolumeViewModel()
|
||||
var volumeSliderHostingView: UIView?
|
||||
private var subtitleDelay: Double = 0.0
|
||||
private var subtitleDelay = 0.0
|
||||
|
||||
init(module: ScrapingModule,
|
||||
continueWatchingManager: ContinueWatchingManager,
|
||||
|
|
@ -180,7 +180,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
subtitlesURL: String?,
|
||||
aniListID: Int,
|
||||
episodeImageUrl: String) {
|
||||
|
||||
self.module = module
|
||||
self.continueWatchingManager = continueWatchingManager
|
||||
self.streamURL = urlString
|
||||
|
|
@ -215,6 +214,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
|
@ -255,7 +255,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
self?.fetchSkipTimes(type: "op")
|
||||
self?.fetchSkipTimes(type: "ed")
|
||||
case .failure(let error):
|
||||
Logger.shared.log("Unable to fetch MAL ID: \(error)", type: "Error")
|
||||
Logger.shared.log("Unable to fetch MAL ID: \(error)", type: .error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -272,7 +272,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
do {
|
||||
try audioSession.setActive(true)
|
||||
} catch {
|
||||
Logger.shared.log("Error activating audio session: \(error)", type: "Debug")
|
||||
Logger.shared.log("Error activating audio session: \(error)", type: .debug)
|
||||
}
|
||||
|
||||
volumeViewModel.value = Double(audioSession.outputVolume)
|
||||
|
|
@ -281,7 +281,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
guard let newVol = change.newValue else { return }
|
||||
DispatchQueue.main.async {
|
||||
self?.volumeViewModel.value = Double(newVol)
|
||||
Logger.shared.log("Hardware volume changed, new value: \(newVol)", type: "Debug")
|
||||
Logger.shared.log("Hardware volume changed, new value: \(newVol)", type: .debug)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -329,7 +329,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
guard let marqueeLabel = marqueeLabel else {
|
||||
guard let marqueeLabel else {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -386,12 +386,12 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
@objc
|
||||
private func playerItemDidChange() {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
if self.qualityButton.isHidden && self.isHLSStream {
|
||||
self.qualityButton.isHidden = false
|
||||
self.qualityButton.menu = self.qualitySelectionMenu()
|
||||
guard let self else { return }
|
||||
if qualityButton.isHidden && isHLSStream {
|
||||
qualityButton.isHidden = false
|
||||
qualityButton.menu = qualitySelectionMenu()
|
||||
|
||||
self.updateMenuButtonConstraints()
|
||||
updateMenuButtonConstraints()
|
||||
|
||||
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut) {
|
||||
self.view.layoutIfNeeded()
|
||||
|
|
@ -546,15 +546,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -726,7 +726,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
subtitleStackView.distribution = .fill
|
||||
subtitleStackView.spacing = 2
|
||||
|
||||
if let subtitleStackView = subtitleStackView {
|
||||
if let subtitleStackView {
|
||||
view.addSubview(subtitleStackView)
|
||||
subtitleStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
|
|
@ -749,7 +749,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
subtitleBottomToSafeAreaConstraint?.isActive = true
|
||||
}
|
||||
|
||||
for _ in 0..<2 {
|
||||
for _ in 0 ..< 2 {
|
||||
let label = UILabel()
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 0
|
||||
|
|
@ -866,7 +866,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
holdSpeedIndicator.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .bold)
|
||||
holdSpeedIndicator.setImage(image, for: .normal)
|
||||
|
||||
holdSpeedIndicator.backgroundColor = UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 0.8)
|
||||
holdSpeedIndicator.backgroundColor = UIColor(red: 51 / 255.0, green: 51 / 255.0, blue: 51 / 255.0, alpha: 0.8)
|
||||
holdSpeedIndicator.tintColor = .white
|
||||
holdSpeedIndicator.setTitleColor(.white, for: .normal)
|
||||
holdSpeedIndicator.layer.cornerRadius = 21
|
||||
|
|
@ -975,7 +975,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
|
||||
self.player.pause()
|
||||
} else {
|
||||
|
||||
let target = CMTime(seconds: self.sliderViewModel.sliderValue,
|
||||
preferredTimescale: 600)
|
||||
self.player.seek(
|
||||
|
|
@ -983,15 +982,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1038,7 +1037,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
skipIntroButton.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .bold)
|
||||
skipIntroButton.setImage(introImage, for: .normal)
|
||||
|
||||
skipIntroButton.backgroundColor = UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 0.8)
|
||||
skipIntroButton.backgroundColor = UIColor(red: 51 / 255.0, green: 51 / 255.0, blue: 51 / 255.0, alpha: 0.8)
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
var config = UIButton.Configuration.filled()
|
||||
|
|
@ -1083,7 +1082,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
skipOutroButton.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .bold)
|
||||
skipOutroButton.setImage(outroImage, for: .normal)
|
||||
|
||||
skipOutroButton.backgroundColor = UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 0.8)
|
||||
skipOutroButton.backgroundColor = UIColor(red: 51 / 255.0, green: 51 / 255.0, blue: 51 / 255.0, alpha: 0.8)
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
var config = UIButton.Configuration.filled()
|
||||
|
|
@ -1174,7 +1173,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
menuButton.setImage(image, for: .normal)
|
||||
menuButton.tintColor = .white
|
||||
|
||||
if let subtitlesURL = subtitlesURL, !subtitlesURL.isEmpty {
|
||||
if let subtitlesURL, !subtitlesURL.isEmpty {
|
||||
menuButton.showsMenuAsPrimaryAction = true
|
||||
menuButton.menu = buildOptionsMenu()
|
||||
} else {
|
||||
|
|
@ -1265,7 +1264,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
skip85Button.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .bold)
|
||||
skip85Button.setImage(image, for: .normal)
|
||||
|
||||
skip85Button.backgroundColor = UIColor(red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 0.8)
|
||||
skip85Button.backgroundColor = UIColor(red: 51 / 255.0, green: 51 / 255.0, blue: 51 / 255.0, alpha: 0.8)
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
var config = UIButton.Configuration.filled()
|
||||
|
|
@ -1354,51 +1353,51 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
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,
|
||||
let currentItem = self.player.currentItem,
|
||||
guard let self,
|
||||
let currentItem = player.currentItem,
|
||||
currentItem.duration.seconds.isFinite else { return }
|
||||
|
||||
let currentDuration = currentItem.duration.seconds
|
||||
if currentDuration.isNaN || currentDuration <= 0 { return }
|
||||
|
||||
self.currentTimeVal = time.seconds
|
||||
self.duration = currentDuration
|
||||
self.updateSegments()
|
||||
currentTimeVal = time.seconds
|
||||
duration = currentDuration
|
||||
updateSegments()
|
||||
|
||||
if !self.isSliderEditing {
|
||||
self.sliderViewModel.sliderValue = max(0, min(self.currentTimeVal, self.duration))
|
||||
if !isSliderEditing {
|
||||
sliderViewModel.sliderValue = max(0, min(currentTimeVal, duration))
|
||||
}
|
||||
|
||||
self.updateSkipButtonsVisibility()
|
||||
updateSkipButtonsVisibility()
|
||||
|
||||
UserDefaults.standard.set(self.currentTimeVal, forKey: "lastPlayedTime_\(self.fullUrl)")
|
||||
UserDefaults.standard.set(self.duration, forKey: "totalTime_\(self.fullUrl)")
|
||||
UserDefaults.standard.set(currentTimeVal, forKey: "lastPlayedTime_\(fullUrl)")
|
||||
UserDefaults.standard.set(duration, forKey: "totalTime_\(fullUrl)")
|
||||
|
||||
if subtitlesEnabled {
|
||||
let adjustedTime = self.currentTimeVal - self.subtitleDelay
|
||||
let cues = self.subtitlesLoader.cues.filter { adjustedTime >= $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..<resolutionEndRange.lowerBound])
|
||||
let resolutionPart = String(line[resolutionRange.upperBound ..< resolutionEndRange.lowerBound])
|
||||
if let heightStr = resolutionPart.components(separatedBy: "x").last,
|
||||
let height = Int(heightStr) {
|
||||
|
||||
let nextLine = lines[index + 1].trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let qualityName = getQualityName(for: height)
|
||||
|
||||
var qualityURL = nextLine
|
||||
if !nextLine.hasPrefix("http") && nextLine.contains(".m3u8") {
|
||||
if let baseURL = self.baseM3U8URL {
|
||||
if let baseURL = baseM3U8URL {
|
||||
let baseURLString = baseURL.deletingLastPathComponent().absoluteString
|
||||
qualityURL = URL(string: nextLine, relativeTo: baseURL)?.absoluteString
|
||||
?? baseURLString + "/" + nextLine
|
||||
|
|
@ -1876,7 +1875,9 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
|
||||
if isHLSStream {
|
||||
if qualities.isEmpty {
|
||||
let loadingAction = UIAction(title: "Loading qualities...", attributes: .disabled) { _ in }
|
||||
let loadingAction = UIAction(title: "Loading qualities...", attributes: .disabled) { _ in
|
||||
Logger.shared.log("Loading State Button clicked", type: .info)
|
||||
}
|
||||
menuItems.append(loadingAction)
|
||||
} else {
|
||||
var menuTitle = "Video Quality"
|
||||
|
|
@ -1901,7 +1902,9 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
return UIMenu(title: menuTitle, children: menuItems)
|
||||
}
|
||||
} else {
|
||||
let unavailableAction = UIAction(title: "Quality selection unavailable", attributes: .disabled) { _ in }
|
||||
let unavailableAction = UIAction(title: "Quality selection unavailable", attributes: .disabled) { _ in
|
||||
Logger.shared.log("Disabled State Button clicked", type: .info)
|
||||
}
|
||||
menuItems.append(unavailableAction)
|
||||
}
|
||||
|
||||
|
|
@ -1917,15 +1920,15 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
currentQualityURL = url
|
||||
|
||||
parseM3U8(url: url) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
guard let self else { return }
|
||||
if let last = UserDefaults.standard.string(forKey: "lastSelectedQuality"),
|
||||
self.qualities.contains(where: { $0.1 == last }) {
|
||||
self.switchToQuality(urlString: last)
|
||||
qualities.contains(where: { $0.1 == last }) {
|
||||
switchToQuality(urlString: last)
|
||||
}
|
||||
|
||||
self.qualityButton.isHidden = false
|
||||
self.qualityButton.menu = self.qualitySelectionMenu()
|
||||
self.updateMenuButtonConstraints()
|
||||
qualityButton.isHidden = false
|
||||
qualityButton.menu = qualitySelectionMenu()
|
||||
updateMenuButtonConstraints()
|
||||
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut) {
|
||||
self.view.layoutIfNeeded()
|
||||
}
|
||||
|
|
@ -1942,8 +1945,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
|
||||
if let subURL = subtitlesURL, !subURL.isEmpty {
|
||||
let subtitlesToggleAction = UIAction(title: "Toggle Subtitles") { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.subtitlesEnabled.toggle()
|
||||
guard let self else { return }
|
||||
subtitlesEnabled.toggle()
|
||||
}
|
||||
|
||||
let foregroundActions = [
|
||||
|
|
@ -2062,32 +2065,32 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
|
||||
let delayActions = [
|
||||
UIAction(title: "-0.5s") { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.adjustSubtitleDelay(by: -0.5)
|
||||
guard let self else { return }
|
||||
adjustSubtitleDelay(by: -0.5)
|
||||
},
|
||||
UIAction(title: "-0.2s") { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.adjustSubtitleDelay(by: -0.2)
|
||||
guard let self else { return }
|
||||
adjustSubtitleDelay(by: -0.2)
|
||||
},
|
||||
UIAction(title: "+0.2s") { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.adjustSubtitleDelay(by: 0.2)
|
||||
guard let self else { return }
|
||||
adjustSubtitleDelay(by: 0.2)
|
||||
},
|
||||
UIAction(title: "+0.5s") { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.adjustSubtitleDelay(by: 0.5)
|
||||
guard let self else { return }
|
||||
adjustSubtitleDelay(by: 0.5)
|
||||
},
|
||||
UIAction(title: "Custom...") { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.presentCustomDelayAlert()
|
||||
guard let self else { return }
|
||||
presentCustomDelayAlert()
|
||||
}
|
||||
]
|
||||
|
||||
let resetDelayAction = UIAction(title: "Reset Delay") { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
guard let self else { return }
|
||||
SubtitleSettingsManager.shared.update { settings in settings.subtitleDelay = 0.0 }
|
||||
self.subtitleDelay = 0.0
|
||||
self.loadSubtitleSettings()
|
||||
subtitleDelay = 0.0
|
||||
loadSubtitleSettings()
|
||||
}
|
||||
|
||||
let delayMenu = UIMenu(title: "Subtitle Delay", children: delayActions + [resetDelayAction])
|
||||
|
|
@ -2187,11 +2190,11 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
}
|
||||
|
||||
override var prefersHomeIndicatorAutoHidden: Bool {
|
||||
return true
|
||||
true
|
||||
}
|
||||
|
||||
override var prefersStatusBarHidden: Bool {
|
||||
return true
|
||||
true
|
||||
}
|
||||
|
||||
func setupAudioSession() {
|
||||
|
|
@ -2201,7 +2204,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
try audioSession.setActive(true)
|
||||
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)
|
||||
}
|
||||
|
||||
volumeObserver = audioSession.observe(\.outputVolume, options: [.new]) { [weak self] _, change in
|
||||
|
|
@ -2211,7 +2214,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
}
|
||||
DispatchQueue.main.async {
|
||||
self?.volumeViewModel.value = Double(newVol)
|
||||
Logger.shared.log("Hardware volume changed, new value: \(newVol)", type: "Debug")
|
||||
Logger.shared.log("Hardware volume changed, new value: \(newVol)", type: .debug)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2219,7 +2222,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
private func setupHoldGesture() {
|
||||
holdGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleHoldGesture(_:)))
|
||||
holdGesture?.minimumPressDuration = 0.5
|
||||
if let holdGesture = holdGesture {
|
||||
if let holdGesture {
|
||||
view.addGestureRecognizer(holdGesture)
|
||||
}
|
||||
}
|
||||
|
|
@ -2251,7 +2254,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
}
|
||||
|
||||
private func beginHoldSpeed() {
|
||||
guard let player = player else { return }
|
||||
guard let player else { return }
|
||||
originalRate = player.rate
|
||||
let holdSpeed = UserDefaults.standard.float(forKey: "holdSpeedPlayer")
|
||||
let speed = holdSpeed > 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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Int> = 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..<episodeChunkSize
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
||||
orientationChanged.toggle()
|
||||
}
|
||||
|
||||
if showStreamLoadingView {
|
||||
VStack(spacing: 16) {
|
||||
|
|
@ -474,7 +450,7 @@ struct MediaInfoView: View {
|
|||
.padding(.vertical, 8)
|
||||
.padding(.horizontal, 24)
|
||||
.background(
|
||||
Color(red: 1.0, green: 112/255.0, blue: 94/255.0)
|
||||
Color(red: 1.0, green: 112 / 255.0, blue: 94 / 255.0)
|
||||
)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(8)
|
||||
|
|
@ -501,11 +477,43 @@ struct MediaInfoView: View {
|
|||
}) {
|
||||
HStack {
|
||||
Image(systemName: "chevron.left")
|
||||
.accessibilityLabel("Right Arrow Icon")
|
||||
Text("Search")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.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)", type: .general)
|
||||
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..<episodeChunkSize
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
||||
orientationChanged.toggle()
|
||||
}
|
||||
.onDisappear {
|
||||
activeFetchID = nil
|
||||
isFetchingEpisode = false
|
||||
|
|
@ -630,7 +638,7 @@ struct MediaInfoView: View {
|
|||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.shared.log("Error loading module: \(error)", type: "Error")
|
||||
Logger.shared.log("Error loading module: \(error)", type: .error)
|
||||
isLoading = false
|
||||
isRefetching = false
|
||||
}
|
||||
|
|
@ -653,114 +661,114 @@ struct MediaInfoView: View {
|
|||
if module.metadata.softsub == true {
|
||||
if module.metadata.asyncJS == true {
|
||||
jsController.fetchStreamUrlJS(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.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)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue