mirror of
https://github.com/cranci1/Sora.git
synced 2026-05-10 03:50:47 +00:00
Merge branch 'dev' into macos
This commit is contained in:
commit
16a52217e1
11 changed files with 261 additions and 182 deletions
|
|
@ -2,8 +2,6 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Sora may requires access to your device's camera.</string>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
|
|
|
|||
|
|
@ -11,13 +11,11 @@ extension JSContext {
|
|||
func setupConsoleLogging() {
|
||||
let consoleObject = JSValue(newObjectIn: self)
|
||||
|
||||
// Set up console.log
|
||||
let consoleLogFunction: @convention(block) (String) -> Void = { message in
|
||||
Logger.shared.log(message, type: "Debug")
|
||||
}
|
||||
consoleObject?.setObject(consoleLogFunction, forKeyedSubscript: "log" as NSString)
|
||||
|
||||
// Set up console.error
|
||||
let consoleErrorFunction: @convention(block) (String) -> Void = { message in
|
||||
Logger.shared.log(message, type: "Error")
|
||||
}
|
||||
|
|
@ -25,7 +23,6 @@ extension JSContext {
|
|||
|
||||
self.setObject(consoleObject, forKeyedSubscript: "console" as NSString)
|
||||
|
||||
// Global log function
|
||||
let logFunction: @convention(block) (String) -> Void = { message in
|
||||
Logger.shared.log("JavaScript log: \(message)", type: "Debug")
|
||||
}
|
||||
|
|
@ -45,7 +42,7 @@ extension JSContext {
|
|||
request.setValue(value, forHTTPHeaderField: key)
|
||||
}
|
||||
}
|
||||
let task = URLSession.cloudflareCustom.dataTask(with: request) { data, _, error in
|
||||
let task = URLSession.custom.dataTask(with: request) { data, _, error in
|
||||
if let error = error {
|
||||
Logger.shared.log("Network error in fetchNativeFunction: \(error.localizedDescription)", type: "Error")
|
||||
reject.call(withArguments: [error.localizedDescription])
|
||||
|
|
@ -78,70 +75,127 @@ extension JSContext {
|
|||
}
|
||||
|
||||
func setupFetchV2() {
|
||||
let fetchV2NativeFunction: @convention(block) (String, [String: String]?, JSValue, JSValue) -> Void = { urlString, headers, resolve, reject in
|
||||
let fetchV2NativeFunction: @convention(block) (String, [String: String]?, String?, String?, JSValue, JSValue) -> Void = { urlString, headers, method, body, resolve, reject in
|
||||
guard let url = URL(string: urlString) else {
|
||||
Logger.shared.log("Invalid URL", type: "Error")
|
||||
reject.call(withArguments: ["Invalid URL"])
|
||||
return
|
||||
}
|
||||
|
||||
let httpMethod = method ?? "GET"
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = httpMethod
|
||||
|
||||
Logger.shared.log("FetchV2 Request: URL=\(url), Method=\(httpMethod), Body=\(body ?? "nil")", type: "Debug")
|
||||
|
||||
if httpMethod == "GET", let body = body, !body.isEmpty, body != "null", body != "undefined" {
|
||||
Logger.shared.log("GET request must not have a body", type: "Error")
|
||||
reject.call(withArguments: ["GET request must not have a body"])
|
||||
return
|
||||
}
|
||||
|
||||
if httpMethod != "GET", let body = body, !body.isEmpty, body != "null", body != "undefined" {
|
||||
request.httpBody = body.data(using: .utf8)
|
||||
}
|
||||
|
||||
|
||||
if let headers = headers {
|
||||
for (key, value) in headers {
|
||||
request.setValue(value, forHTTPHeaderField: key)
|
||||
}
|
||||
}
|
||||
let task = URLSession.cloudflareCustom.dataTask(with: request) { data, response, error in
|
||||
|
||||
let task = URLSession.custom.downloadTask(with: request) { tempFileURL, response, error in
|
||||
if let error = error {
|
||||
Logger.shared.log("Network error in fetchV2NativeFunction: \(error.localizedDescription)", type: "Error")
|
||||
reject.call(withArguments: [error.localizedDescription])
|
||||
return
|
||||
}
|
||||
guard let data = data else {
|
||||
|
||||
guard let tempFileURL = tempFileURL else {
|
||||
Logger.shared.log("No data in response", type: "Error")
|
||||
reject.call(withArguments: ["No data"])
|
||||
return
|
||||
}
|
||||
|
||||
// Just pass the raw data string and let JavaScript handle it
|
||||
if let text = String(data: data, encoding: .utf8) {
|
||||
resolve.call(withArguments: [text])
|
||||
} else {
|
||||
Logger.shared.log("Unable to decode data to text", type: "Error")
|
||||
reject.call(withArguments: ["Unable to decode data"])
|
||||
do {
|
||||
let data = try Data(contentsOf: tempFileURL)
|
||||
|
||||
if data.count > 10_000_000 {
|
||||
Logger.shared.log("Response exceeds maximum size", type: "Error")
|
||||
reject.call(withArguments: ["Response exceeds maximum size"])
|
||||
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")
|
||||
reject.call(withArguments: ["Unable to decode data"])
|
||||
}
|
||||
|
||||
} catch {
|
||||
Logger.shared.log("Error reading downloaded file: \(error.localizedDescription)", type: "Error")
|
||||
reject.call(withArguments: ["Error reading downloaded file"])
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
|
||||
self.setObject(fetchV2NativeFunction, forKeyedSubscript: "fetchV2Native" as NSString)
|
||||
|
||||
// Simpler fetchv2 implementation with text() and json() methods
|
||||
let fetchv2Definition = """
|
||||
function fetchv2(url, headers) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
fetchV2Native(url, headers, function(rawText) {
|
||||
const responseObj = {
|
||||
_data: rawText,
|
||||
text: function() {
|
||||
return Promise.resolve(this._data);
|
||||
},
|
||||
json: function() {
|
||||
try {
|
||||
return Promise.resolve(JSON.parse(this._data));
|
||||
} catch (e) {
|
||||
return Promise.reject("JSON parse error: " + e.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
resolve(responseObj);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
"""
|
||||
function fetchv2(url, headers = {}, method = "GET", body = null) {
|
||||
if (method === "GET") {
|
||||
return new Promise(function(resolve, reject) {
|
||||
fetchV2Native(url, headers, method, null, function(rawText) { // Pass `null` explicitly
|
||||
const responseObj = {
|
||||
_data: rawText,
|
||||
text: function() {
|
||||
return Promise.resolve(this._data);
|
||||
},
|
||||
json: function() {
|
||||
try {
|
||||
return Promise.resolve(JSON.parse(this._data));
|
||||
} catch (e) {
|
||||
return Promise.reject("JSON parse error: " + e.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
resolve(responseObj);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure body is properly serialized
|
||||
const processedBody = body ? JSON.stringify(body) : null;
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
fetchV2Native(url, headers, method, processedBody, function(rawText) {
|
||||
const responseObj = {
|
||||
_data: rawText,
|
||||
text: function() {
|
||||
return Promise.resolve(this._data);
|
||||
},
|
||||
json: function() {
|
||||
try {
|
||||
return Promise.resolve(JSON.parse(this._data));
|
||||
} catch (e) {
|
||||
return Promise.reject("JSON parse error: " + e.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
resolve(responseObj);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
"""
|
||||
self.evaluateScript(fetchv2Definition)
|
||||
}
|
||||
|
||||
func setupBase64Functions() {
|
||||
// btoa function: converts binary string to base64-encoded ASCII string
|
||||
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")
|
||||
|
|
@ -150,7 +204,6 @@ extension JSContext {
|
|||
return data.base64EncodedString()
|
||||
}
|
||||
|
||||
// atob function: decodes base64-encoded ASCII string to binary string
|
||||
let atobFunction: @convention(block) (String) -> String? = { base64String in
|
||||
guard let data = Data(base64Encoded: base64String) else {
|
||||
Logger.shared.log("atob: Invalid base64 input", type: "Error")
|
||||
|
|
@ -160,12 +213,10 @@ extension JSContext {
|
|||
return String(data: data, encoding: .utf8)
|
||||
}
|
||||
|
||||
// Add the functions to the JavaScript context
|
||||
self.setObject(btoaFunction, forKeyedSubscript: "btoa" as NSString)
|
||||
self.setObject(atobFunction, forKeyedSubscript: "atob" as NSString)
|
||||
}
|
||||
|
||||
// Helper method to set up all JavaScript functionality
|
||||
func setupJavaScriptEnvironment() {
|
||||
setupConsoleLogging()
|
||||
setupNativeFetch()
|
||||
|
|
|
|||
|
|
@ -6,60 +6,41 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
extension URLSession {
|
||||
static let userAgents = [
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.2365.92",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.2277.128",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_3_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14.3; rv:123.0) Gecko/20100101 Firefox/123.0",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14.2; rv:122.0) Gecko/20100101 Firefox/122.0",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0",
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:122.0) Gecko/20100101 Firefox/122.0",
|
||||
"Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.105 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.105 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
|
||||
"Mozilla/5.0 (iPad; CPU OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
|
||||
"Mozilla/5.0 (Android 14; Mobile; rv:123.0) Gecko/123.0 Firefox/123.0",
|
||||
"Mozilla/5.0 (Android 13; Mobile; rv:122.0) Gecko/122.0 Firefox/122.0"
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.2569.45",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.2478.89",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 15_1_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/19.1 Safari/605.1.15",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 15_0_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/19.0 Safari/605.1.15",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 15.1; rv:128.0) Gecko/20100101 Firefox/128.0",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 15.0; rv:127.0) Gecko/20100101 Firefox/127.0",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0",
|
||||
"Mozilla/5.0 (Linux; Android 15; SM-S928B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6782.112 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 15; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6782.112 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/19.3 Mobile/15E148 Safari/604.1",
|
||||
"Mozilla/5.0 (iPad; CPU OS 18_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/19.3 Mobile/15E148 Safari/604.1",
|
||||
"Mozilla/5.0 (Android 15; Mobile; rv:128.0) Gecko/128.0 Firefox/128.0",
|
||||
"Mozilla/5.0 (Android 15; Mobile; rv:127.0) Gecko/127.0 Firefox/127.0"
|
||||
]
|
||||
|
||||
static let randomUserAgent: String = {
|
||||
static var randomUserAgent: String = {
|
||||
userAgents.randomElement() ?? userAgents[0]
|
||||
}()
|
||||
|
||||
static let custom: URLSession = {
|
||||
let configuration = URLSessionConfiguration.default
|
||||
configuration.httpAdditionalHeaders = [
|
||||
"User-Agent": randomUserAgent
|
||||
]
|
||||
return URLSession(configuration: configuration)
|
||||
}()
|
||||
|
||||
static let cloudflareCustom: URLSession = {
|
||||
let configuration = URLSessionConfiguration.default
|
||||
configuration.httpAdditionalHeaders = [
|
||||
"User-Agent": randomUserAgent
|
||||
]
|
||||
|
||||
let dnsSettings: [AnyHashable: Any] = [
|
||||
"DNSSettings": [
|
||||
"ServerAddresses": ["1.1.1.1", "1.0.0.1"]
|
||||
]
|
||||
]
|
||||
|
||||
configuration.connectionProxyDictionary = dnsSettings
|
||||
configuration.httpAdditionalHeaders = ["User-Agent": randomUserAgent]
|
||||
return URLSession(configuration: configuration)
|
||||
}()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class JSController: ObservableObject {
|
|||
return
|
||||
}
|
||||
|
||||
URLSession.cloudflareCustom.dataTask(with: url) { [weak self] data, _, error in
|
||||
URLSession.custom.dataTask(with: url) { [weak self] data, _, error in
|
||||
guard let self = self else { return }
|
||||
|
||||
if let error = error {
|
||||
|
|
@ -77,7 +77,7 @@ class JSController: ObservableObject {
|
|||
return
|
||||
}
|
||||
|
||||
URLSession.cloudflareCustom.dataTask(with: url) { [weak self] data, _, error in
|
||||
URLSession.custom.dataTask(with: url) { [weak self] data, _, error in
|
||||
guard let self = self else { return }
|
||||
|
||||
if let error = error {
|
||||
|
|
@ -130,7 +130,7 @@ class JSController: ObservableObject {
|
|||
return
|
||||
}
|
||||
|
||||
URLSession.cloudflareCustom.dataTask(with: url) { [weak self] data, _, error in
|
||||
URLSession.custom.dataTask(with: url) { [weak self] data, _, error in
|
||||
guard let self = self else { return }
|
||||
|
||||
if let error = error {
|
||||
|
|
@ -430,7 +430,7 @@ class JSController: ObservableObject {
|
|||
|
||||
func fetchStreamUrlJSSecond(episodeUrl: String, softsub: Bool = false, completion: @escaping ((stream: String?, subtitles: String?)) -> Void) {
|
||||
let url = URL(string: episodeUrl)!
|
||||
let task = URLSession.cloudflareCustom.dataTask(with: url) { data, response, error in
|
||||
let task = URLSession.custom.dataTask(with: url) { data, response, error in
|
||||
if let error = error {
|
||||
Logger.shared.log("URLSession error: \(error.localizedDescription)", type: "Error")
|
||||
DispatchQueue.main.async { completion((nil, nil)) }
|
||||
|
|
|
|||
|
|
@ -709,20 +709,16 @@ class CustomMediaPlayerViewController: UIViewController {
|
|||
)
|
||||
}
|
||||
|
||||
// Watch Next Button Logic:
|
||||
let hideNext = UserDefaults.standard.bool(forKey: "hideNextButton")
|
||||
let isNearEnd = (self.duration - self.currentTimeVal) <= (self.duration * 0.10)
|
||||
&& self.currentTimeVal != self.duration
|
||||
&& self.showWatchNextButton
|
||||
&& self.duration != 0
|
||||
|
||||
if isNearEnd {
|
||||
// First appearance: show the button in its normal position.
|
||||
if !self.isWatchNextVisible {
|
||||
self.isWatchNextVisible = true
|
||||
self.watchNextButtonAppearedAt = self.currentTimeVal
|
||||
|
||||
// Choose constraints based on current controls visibility.
|
||||
if self.isControlsVisible {
|
||||
NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints)
|
||||
NSLayoutConstraint.activate(self.watchNextButtonControlsConstraints)
|
||||
|
|
@ -730,7 +726,6 @@ class CustomMediaPlayerViewController: UIViewController {
|
|||
NSLayoutConstraint.deactivate(self.watchNextButtonControlsConstraints)
|
||||
NSLayoutConstraint.activate(self.watchNextButtonNormalConstraints)
|
||||
}
|
||||
// Soft fade-in.
|
||||
self.watchNextButton.isHidden = false
|
||||
self.watchNextButton.alpha = 0.0
|
||||
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: {
|
||||
|
|
@ -738,23 +733,19 @@ class CustomMediaPlayerViewController: UIViewController {
|
|||
}, completion: nil)
|
||||
}
|
||||
|
||||
// When 5 seconds have elapsed from when the button first appeared:
|
||||
if let appearedAt = self.watchNextButtonAppearedAt,
|
||||
(self.currentTimeVal - appearedAt) >= 5,
|
||||
!self.isWatchNextRepositioned {
|
||||
// Fade out the button first (even if controls are visible).
|
||||
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: {
|
||||
self.watchNextButton.alpha = 0.0
|
||||
}, completion: { _ in
|
||||
self.watchNextButton.isHidden = true
|
||||
// Then lock it to the controls-attached constraints.
|
||||
NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints)
|
||||
NSLayoutConstraint.activate(self.watchNextButtonControlsConstraints)
|
||||
self.isWatchNextRepositioned = true
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Not near end: reset the watch-next button state.
|
||||
self.watchNextButtonAppearedAt = nil
|
||||
self.isWatchNextVisible = false
|
||||
self.isWatchNextRepositioned = false
|
||||
|
|
@ -771,7 +762,6 @@ class CustomMediaPlayerViewController: UIViewController {
|
|||
|
||||
func repositionWatchNextButton() {
|
||||
self.isWatchNextRepositioned = true
|
||||
// Update constraints so the button is now attached next to the playback controls.
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints)
|
||||
NSLayoutConstraint.activate(self.watchNextButtonControlsConstraints)
|
||||
|
|
@ -799,7 +789,6 @@ class CustomMediaPlayerViewController: UIViewController {
|
|||
self.skip85Button.alpha = self.isControlsVisible ? 0.8 : 0
|
||||
|
||||
if self.isControlsVisible {
|
||||
// Always use the controls-attached constraints.
|
||||
NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints)
|
||||
NSLayoutConstraint.activate(self.watchNextButtonControlsConstraints)
|
||||
if self.isWatchNextRepositioned || self.isWatchNextVisible {
|
||||
|
|
@ -809,7 +798,6 @@ class CustomMediaPlayerViewController: UIViewController {
|
|||
})
|
||||
}
|
||||
} else {
|
||||
// When controls are hidden:
|
||||
if !self.isWatchNextRepositioned && self.isWatchNextVisible {
|
||||
NSLayoutConstraint.deactivate(self.watchNextButtonControlsConstraints)
|
||||
NSLayoutConstraint.activate(self.watchNextButtonNormalConstraints)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class VTTSubtitlesLoader: ObservableObject {
|
|||
|
||||
let format = determineSubtitleFormat(from: url)
|
||||
|
||||
URLSession.cloudflareCustom.dataTask(with: url) { data, _, error in
|
||||
URLSession.shared.dataTask(with: url) { data, _, error in
|
||||
guard let data = data,
|
||||
let content = String(data: data, encoding: .utf8),
|
||||
error == nil else { return }
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ struct MediaInfoView: View {
|
|||
UserDefaults.standard.set(99999999.0, forKey: "totalTime_\(href)")
|
||||
}
|
||||
refreshTrigger.toggle()
|
||||
Logger.shared.log("Marked \(ep.number - 1) episodes watched within anime \"\(title)\".", type: "General")
|
||||
Logger.shared.log("Marked \(ep.number - 1) episodes watched within series \"\(title)\".", type: "General")
|
||||
}
|
||||
)
|
||||
.id(refreshTrigger)
|
||||
|
|
@ -626,9 +626,15 @@ struct MediaInfoView: View {
|
|||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||||
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")
|
||||
DropManager.shared.showDrop(title: "Error", subtitle: "Invalid stream URL", duration: 2.0, icon: UIImage(systemName: "xmark.circle"))
|
||||
return
|
||||
}
|
||||
|
||||
let customMediaPlayer = CustomMediaPlayerViewController(
|
||||
module: module,
|
||||
urlString: url,
|
||||
urlString: url.absoluteString,
|
||||
fullUrl: fullURL,
|
||||
title: title,
|
||||
episodeNumber: selectedEpisodeNumber,
|
||||
|
|
@ -644,6 +650,9 @@ struct MediaInfoView: View {
|
|||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
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")
|
||||
DropManager.shared.showDrop(title: "Error", subtitle: "Failed to present player", duration: 2.0, icon: UIImage(systemName: "xmark.circle"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,20 +165,24 @@ struct SearchView: View {
|
|||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Menu {
|
||||
ForEach(moduleManager.modules, id: \.id) { module in
|
||||
Button {
|
||||
selectedModuleId = module.id.uuidString
|
||||
} label: {
|
||||
HStack {
|
||||
KFImage(URL(string: module.metadata.iconUrl))
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 20, height: 20)
|
||||
.cornerRadius(4)
|
||||
Text(module.metadata.sourceName)
|
||||
if module.id.uuidString == selectedModuleId {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundColor(.accentColor)
|
||||
ForEach(getModuleLanguageGroups(), id: \.self) { language in
|
||||
Menu(language) {
|
||||
ForEach(getModulesForLanguage(language), id: \.id) { module in
|
||||
Button {
|
||||
selectedModuleId = module.id.uuidString
|
||||
} label: {
|
||||
HStack {
|
||||
KFImage(URL(string: module.metadata.iconUrl))
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 20, height: 20)
|
||||
.cornerRadius(4)
|
||||
Text(module.metadata.sourceName)
|
||||
if module.id.uuidString == selectedModuleId {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -269,6 +273,41 @@ struct SearchView: View {
|
|||
return verticalSizeClass == .compact ? mediaColumnsLandscape : mediaColumnsPortrait
|
||||
}
|
||||
}
|
||||
|
||||
private func cleanLanguageName(_ language: String?) -> String {
|
||||
guard let language = language else { return "Unknown" }
|
||||
|
||||
let cleaned = language.replacingOccurrences(
|
||||
of: "\\s*\\([^\\)]*\\)",
|
||||
with: "",
|
||||
options: .regularExpression
|
||||
).trimmingCharacters(in: .whitespaces)
|
||||
|
||||
return cleaned.isEmpty ? "Unknown" : cleaned
|
||||
}
|
||||
|
||||
private func getModulesByLanguage() -> [String: [ScrapingModule]] {
|
||||
var result = [String: [ScrapingModule]]()
|
||||
|
||||
for module in moduleManager.modules {
|
||||
let language = cleanLanguageName(module.metadata.language)
|
||||
if result[language] == nil {
|
||||
result[language] = [module]
|
||||
} else {
|
||||
result[language]?.append(module)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private func getModuleLanguageGroups() -> [String] {
|
||||
return getModulesByLanguage().keys.sorted()
|
||||
}
|
||||
|
||||
private func getModulesForLanguage(_ language: String) -> [ScrapingModule] {
|
||||
return getModulesByLanguage()[language] ?? []
|
||||
}
|
||||
}
|
||||
|
||||
struct SearchBar: View {
|
||||
|
|
|
|||
|
|
@ -14,9 +14,13 @@ struct SettingsViewGeneral: View {
|
|||
@AppStorage("analyticsEnabled") private var analyticsEnabled: Bool = false
|
||||
@AppStorage("multiThreads") private var multiThreadsEnabled: Bool = false
|
||||
@AppStorage("metadataProviders") private var metadataProviders: String = "AniList"
|
||||
@AppStorage("CustomDNSProvider") private var customDNSProvider: String = "Cloudflare"
|
||||
@AppStorage("customPrimaryDNS") private var customPrimaryDNS: String = ""
|
||||
@AppStorage("customSecondaryDNS") private var customSecondaryDNS: String = ""
|
||||
@AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2
|
||||
@AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4
|
||||
|
||||
private let customDNSProviderList = ["Cloudflare", "Google", "OpenDNS", "Quad9", "AdGuard", "CleanBrowsing", "ControlD", "Custom"]
|
||||
private let metadataProvidersList = ["AniList"]
|
||||
@EnvironmentObject var settings: Settings
|
||||
|
||||
|
|
@ -24,7 +28,7 @@ struct SettingsViewGeneral: View {
|
|||
Form {
|
||||
Section(header: Text("Interface")) {
|
||||
ColorPicker("Accent Color", selection: $settings.accentColor)
|
||||
HStack() {
|
||||
HStack {
|
||||
Text("Appearance")
|
||||
Picker("Appearance", selection: $settings.selectedAppearance) {
|
||||
Text("System").tag(Appearance.system)
|
||||
|
|
@ -40,18 +44,10 @@ struct SettingsViewGeneral: View {
|
|||
Text("Episodes Range")
|
||||
Spacer()
|
||||
Menu {
|
||||
Button(action: { episodeChunkSize = 25 }) {
|
||||
Text("25")
|
||||
}
|
||||
Button(action: { episodeChunkSize = 50 }) {
|
||||
Text("50")
|
||||
}
|
||||
Button(action: { episodeChunkSize = 75 }) {
|
||||
Text("75")
|
||||
}
|
||||
Button(action: { episodeChunkSize = 100 }) {
|
||||
Text("100")
|
||||
}
|
||||
Button(action: { episodeChunkSize = 25 }) { Text("25") }
|
||||
Button(action: { episodeChunkSize = 50 }) { Text("50") }
|
||||
Button(action: { episodeChunkSize = 75 }) { Text("75") }
|
||||
Button(action: { episodeChunkSize = 100 }) { Text("100") }
|
||||
} label: {
|
||||
Text("\(episodeChunkSize)")
|
||||
}
|
||||
|
|
@ -63,36 +59,24 @@ struct SettingsViewGeneral: View {
|
|||
Spacer()
|
||||
Menu(metadataProviders) {
|
||||
ForEach(metadataProvidersList, id: \.self) { provider in
|
||||
Button(action: {
|
||||
metadataProviders = provider
|
||||
}) {
|
||||
Button(action: { metadataProviders = provider }) {
|
||||
Text(provider)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//Section(header: Text("Downloads"), footer: Text("Note that the modules will be replaced only if there is a different version string inside the JSON file.")) {
|
||||
// Toggle("Multi Threads conversion", isOn: $multiThreadsEnabled)
|
||||
// .tint(.accentColor)
|
||||
//}
|
||||
|
||||
Section(header: Text("Media Grid Layout"), footer: Text("Adjust the number of media items per row in portrait and landscape modes.")) {
|
||||
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())
|
||||
}
|
||||
|
|
@ -100,16 +84,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())
|
||||
}
|
||||
|
|
@ -121,7 +101,7 @@ struct SettingsViewGeneral: View {
|
|||
.tint(.accentColor)
|
||||
}
|
||||
|
||||
Section(header: Text("Analytics"), footer: Text("Allow Sora to collect anonymous data to improve the app. No personal information is collected. This can be disabled at any time.\n\n Information collected: \n- App version\n- Device model\n- Module Name/Version\n- Error Messages\n- Title of Watched Content")) {
|
||||
Section(header: Text("Advanced"), footer: Text("Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time.")) {
|
||||
Toggle("Enable Analytics", isOn: $analyticsEnabled)
|
||||
.tint(.accentColor)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ struct SettingsView: View {
|
|||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
Section(header: Text("Main Settings")) {
|
||||
Section(header: Text("Main")) {
|
||||
NavigationLink(destination: SettingsViewGeneral()) {
|
||||
Text("General Settings")
|
||||
Text("General Preferences")
|
||||
}
|
||||
NavigationLink(destination: SettingsViewPlayer()) {
|
||||
Text("Media Player")
|
||||
|
|
@ -21,9 +21,9 @@ struct SettingsView: View {
|
|||
NavigationLink(destination: SettingsViewModule()) {
|
||||
Text("Modules")
|
||||
}
|
||||
NavigationLink(destination: SettingsViewTrackers()) {
|
||||
Text("Trackers")
|
||||
}
|
||||
//NavigationLink(destination: SettingsViewTrackers()) {
|
||||
// Text("Trackers")
|
||||
//}
|
||||
}
|
||||
|
||||
Section(header: Text("Info")) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
130217CC2D81C55E0011EFF5 /* DownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130217CB2D81C55E0011EFF5 /* DownloadView.swift */; };
|
||||
130C6BFA2D53AB1F00DC1432 /* SettingsViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */; };
|
||||
13103E892D58A39A000F0673 /* AniListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13103E882D58A39A000F0673 /* AniListItem.swift */; };
|
||||
13103E8B2D58E028000F0673 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13103E8A2D58E028000F0673 /* View.swift */; };
|
||||
|
|
@ -14,6 +15,9 @@
|
|||
131845F92D47C62D00CA7A54 /* SettingsViewGeneral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131845F82D47C62D00CA7A54 /* SettingsViewGeneral.swift */; };
|
||||
1327FBA72D758CEA00FC6689 /* Analytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1327FBA62D758CEA00FC6689 /* Analytics.swift */; };
|
||||
1327FBA92D758DEA00FC6689 /* UIDevice+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1327FBA82D758DEA00FC6689 /* UIDevice+Model.swift */; };
|
||||
132E351D2D959DDB0007800E /* Drops in Frameworks */ = {isa = PBXBuildFile; productRef = 132E351C2D959DDB0007800E /* Drops */; };
|
||||
132E35202D959E1D0007800E /* FFmpeg-iOS-Lame in Frameworks */ = {isa = PBXBuildFile; productRef = 132E351F2D959E1D0007800E /* FFmpeg-iOS-Lame */; };
|
||||
132E35232D959E410007800E /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 132E35222D959E410007800E /* Kingfisher */; };
|
||||
1334FF4D2D786C93007E289F /* TMDB-Seasonal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334FF4C2D786C93007E289F /* TMDB-Seasonal.swift */; };
|
||||
1334FF4F2D786C9E007E289F /* TMDB-Trending.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334FF4E2D786C9E007E289F /* TMDB-Trending.swift */; };
|
||||
1334FF522D7871B7007E289F /* TMDBItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334FF512D7871B7007E289F /* TMDBItem.swift */; };
|
||||
|
|
@ -30,10 +34,8 @@
|
|||
133D7C922D2BE2640075467E /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C872D2BE2640075467E /* URLSession.swift */; };
|
||||
133D7C932D2BE2640075467E /* Modules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C892D2BE2640075467E /* Modules.swift */; };
|
||||
133D7C942D2BE2640075467E /* JSController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C8B2D2BE2640075467E /* JSController.swift */; };
|
||||
133D7C972D2BE2AF0075467E /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 133D7C962D2BE2AF0075467E /* Kingfisher */; };
|
||||
133F55BB2D33B55100E08EEA /* LibraryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133F55BA2D33B55100E08EEA /* LibraryManager.swift */; };
|
||||
1359ED142D76F49900C13034 /* finTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1359ED132D76F49900C13034 /* finTopView.swift */; };
|
||||
1359ED1A2D76FA7D00C13034 /* Drops in Frameworks */ = {isa = PBXBuildFile; productRef = 1359ED192D76FA7D00C13034 /* Drops */; };
|
||||
135CCBE22D4D1138008B9C0E /* SettingsViewPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */; };
|
||||
138AA1B82D2D66FD0021F9DF /* EpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */; };
|
||||
138AA1B92D2D66FD0021F9DF /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */; };
|
||||
|
|
@ -52,6 +54,7 @@
|
|||
13DB46902D900A38008CBC03 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB468F2D900A38008CBC03 /* URL.swift */; };
|
||||
13DB46922D900BCE008CBC03 /* SettingsViewTrackers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB46912D900BCE008CBC03 /* SettingsViewTrackers.swift */; };
|
||||
13DB7CC32D7D99C0004371D3 /* SubtitleSettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB7CC22D7D99C0004371D3 /* SubtitleSettingsManager.swift */; };
|
||||
13DB7CEC2D7DED5D004371D3 /* DownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB7CEB2D7DED5D004371D3 /* DownloadManager.swift */; };
|
||||
13DC0C462D302C7500D0F966 /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DC0C452D302C7500D0F966 /* VideoPlayer.swift */; };
|
||||
13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD12D32D97400C1EBD7 /* CustomPlayer.swift */; };
|
||||
13EA2BD62D32D97400C1EBD7 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD32D32D97400C1EBD7 /* Double+Extension.swift */; };
|
||||
|
|
@ -62,6 +65,7 @@
|
|||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
130217CB2D81C55E0011EFF5 /* DownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadView.swift; sourceTree = "<group>"; };
|
||||
130C6BF82D53A4C200DC1432 /* Sora.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Sora.entitlements; sourceTree = "<group>"; };
|
||||
130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewData.swift; sourceTree = "<group>"; };
|
||||
13103E882D58A39A000F0673 /* AniListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AniListItem.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -107,6 +111,7 @@
|
|||
13DB468F2D900A38008CBC03 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
|
||||
13DB46912D900BCE008CBC03 /* SettingsViewTrackers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewTrackers.swift; sourceTree = "<group>"; };
|
||||
13DB7CC22D7D99C0004371D3 /* SubtitleSettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleSettingsManager.swift; sourceTree = "<group>"; };
|
||||
13DB7CEB2D7DED5D004371D3 /* DownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadManager.swift; sourceTree = "<group>"; };
|
||||
13DC0C412D2EC9BA00D0F966 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
13DC0C452D302C7500D0F966 /* VideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = "<group>"; };
|
||||
13EA2BD12D32D97400C1EBD7 /* CustomPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomPlayer.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -122,8 +127,9 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1359ED1A2D76FA7D00C13034 /* Drops in Frameworks */,
|
||||
133D7C972D2BE2AF0075467E /* Kingfisher in Frameworks */,
|
||||
132E35232D959E410007800E /* Kingfisher in Frameworks */,
|
||||
132E35202D959E1D0007800E /* FFmpeg-iOS-Lame in Frameworks */,
|
||||
132E351D2D959DDB0007800E /* Drops in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -247,6 +253,7 @@
|
|||
1399FAD22D3AB34F00E97C31 /* SettingsView */,
|
||||
133F55B92D33B53E00E08EEA /* LibraryView */,
|
||||
133D7C7C2D2BE2630075467E /* SearchView.swift */,
|
||||
130217CB2D81C55E0011EFF5 /* DownloadView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -277,6 +284,7 @@
|
|||
133D7C852D2BE2640075467E /* Utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13DB7CEA2D7DED50004371D3 /* DownloadManager */,
|
||||
13C0E5E82D5F85DD00E7F619 /* ContinueWatching */,
|
||||
13103E8C2D58E037000F0673 /* SkeletonCells */,
|
||||
13DC0C442D302C6A00D0F966 /* MediaPlayer */,
|
||||
|
|
@ -392,6 +400,14 @@
|
|||
path = Auth;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
13DB7CEA2D7DED50004371D3 /* DownloadManager */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13DB7CEB2D7DED5D004371D3 /* DownloadManager.swift */,
|
||||
);
|
||||
path = DownloadManager;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
13DC0C442D302C6A00D0F966 /* MediaPlayer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -438,8 +454,9 @@
|
|||
);
|
||||
name = Sulfur;
|
||||
packageProductDependencies = (
|
||||
133D7C962D2BE2AF0075467E /* Kingfisher */,
|
||||
1359ED192D76FA7D00C13034 /* Drops */,
|
||||
132E351C2D959DDB0007800E /* Drops */,
|
||||
132E351F2D959E1D0007800E /* FFmpeg-iOS-Lame */,
|
||||
132E35222D959E410007800E /* Kingfisher */,
|
||||
);
|
||||
productName = Sora;
|
||||
productReference = 133D7C6A2D2BE2500075467E /* Sulfur.app */;
|
||||
|
|
@ -469,8 +486,9 @@
|
|||
);
|
||||
mainGroup = 133D7C612D2BE2500075467E;
|
||||
packageReferences = (
|
||||
133D7C952D2BE2AF0075467E /* XCRemoteSwiftPackageReference "Kingfisher" */,
|
||||
1359ED182D76FA7D00C13034 /* XCRemoteSwiftPackageReference "Drops" */,
|
||||
132E351B2D959DDB0007800E /* XCRemoteSwiftPackageReference "Drops" */,
|
||||
132E351E2D959E1D0007800E /* XCRemoteSwiftPackageReference "FFmpeg-iOS-Lame" */,
|
||||
132E35212D959E410007800E /* XCRemoteSwiftPackageReference "Kingfisher" */,
|
||||
);
|
||||
productRefGroup = 133D7C6B2D2BE2500075467E /* Products */;
|
||||
projectDirPath = "";
|
||||
|
|
@ -516,6 +534,7 @@
|
|||
133D7C932D2BE2640075467E /* Modules.swift in Sources */,
|
||||
13DB7CC32D7D99C0004371D3 /* SubtitleSettingsManager.swift in Sources */,
|
||||
133D7C702D2BE2500075467E /* ContentView.swift in Sources */,
|
||||
13DB7CEC2D7DED5D004371D3 /* DownloadManager.swift in Sources */,
|
||||
1334FF522D7871B7007E289F /* TMDBItem.swift in Sources */,
|
||||
13D99CF72D4E73C300250A86 /* ModuleAdditionSettingsView.swift in Sources */,
|
||||
13C0E5EC2D5F85F800E7F619 /* ContinueWatchingItem.swift in Sources */,
|
||||
|
|
@ -535,6 +554,7 @@
|
|||
133D7C942D2BE2640075467E /* JSController.swift in Sources */,
|
||||
133D7C922D2BE2640075467E /* URLSession.swift in Sources */,
|
||||
133D7C912D2BE2640075467E /* SettingsViewModule.swift in Sources */,
|
||||
130217CC2D81C55E0011EFF5 /* DownloadView.swift in Sources */,
|
||||
133F55BB2D33B55100E08EEA /* LibraryManager.swift in Sources */,
|
||||
133D7C8E2D2BE2640075467E /* LibraryView.swift in Sources */,
|
||||
133D7C6E2D2BE2500075467E /* SoraApp.swift in Sources */,
|
||||
|
|
@ -778,15 +798,7 @@
|
|||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
133D7C952D2BE2AF0075467E /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/onevcat/Kingfisher.git";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = 7.9.1;
|
||||
};
|
||||
};
|
||||
1359ED182D76FA7D00C13034 /* XCRemoteSwiftPackageReference "Drops" */ = {
|
||||
132E351B2D959DDB0007800E /* XCRemoteSwiftPackageReference "Drops" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/omaralbeik/Drops.git";
|
||||
requirement = {
|
||||
|
|
@ -794,19 +806,40 @@
|
|||
kind = branch;
|
||||
};
|
||||
};
|
||||
132E351E2D959E1D0007800E /* XCRemoteSwiftPackageReference "FFmpeg-iOS-Lame" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/kewlbear/FFmpeg-iOS-Lame";
|
||||
requirement = {
|
||||
branch = main;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
132E35212D959E410007800E /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/onevcat/Kingfisher.git";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = 7.9.1;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
133D7C962D2BE2AF0075467E /* Kingfisher */ = {
|
||||
132E351C2D959DDB0007800E /* Drops */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 133D7C952D2BE2AF0075467E /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
||||
productName = Kingfisher;
|
||||
};
|
||||
1359ED192D76FA7D00C13034 /* Drops */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 1359ED182D76FA7D00C13034 /* XCRemoteSwiftPackageReference "Drops" */;
|
||||
package = 132E351B2D959DDB0007800E /* XCRemoteSwiftPackageReference "Drops" */;
|
||||
productName = Drops;
|
||||
};
|
||||
132E351F2D959E1D0007800E /* FFmpeg-iOS-Lame */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 132E351E2D959E1D0007800E /* XCRemoteSwiftPackageReference "FFmpeg-iOS-Lame" */;
|
||||
productName = "FFmpeg-iOS-Lame";
|
||||
};
|
||||
132E35222D959E410007800E /* Kingfisher */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 132E35212D959E410007800E /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
||||
productName = Kingfisher;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 133D7C622D2BE2500075467E /* Project object */;
|
||||
|
|
|
|||
Loading…
Reference in a new issue