more linting

This commit is contained in:
Dominic Drees 2025-05-01 23:59:45 +02:00
parent a4fbcbc112
commit ee11dc81c1
58 changed files with 563 additions and 422 deletions

View file

@ -47,32 +47,35 @@ disabled_rules:
# newly added:
- multiple_closures_with_trailing_closure
- closure_body_length
- type_body_length
- file_name
- file_length
- line_length
- nesting
- legacy_objc_type
- function_body_length
# Configurations
attributes:
always_on_line_above:
- "@ConfigurationElement"
- "@OptionGroup"
- "@RuleConfigurationDescriptionBuilder"
identifier_name:
excluded:
- id
- trailing_comma
- identifier_name
- discarded_notification_center_observer
- extension_access_modifier
- explicit_init
- superfluous_else
- discouraged_optional_boolean
- discouraged_none_name
- attributes
- prefer_key_path
# should be fixed sometimes:
- implicitly_unwrapped_optional
- cyclomatic_complexity
- unused_parameter
- fatal_error
- force_cast
large_tuple: 3
number_separator:
minimum_length: 5
redundant_type_annotation:
consider_default_literal_types_redundant: true
single_test_class: *unit_test_configuration
trailing_comma:
mandatory_comma: true
type_body_length: 400
unneeded_override:
affect_initializers: true
unused_import:

View file

@ -98,6 +98,9 @@
}
}
}
},
"+ Icon" : {
},
"+10s" : {
"localizations" : {
@ -194,6 +197,9 @@
}
}
}
},
"Alternative App Icon" : {
},
"AniList" : {
"localizations" : {
@ -210,6 +216,9 @@
}
}
}
},
"AniList Icon" : {
},
"AniList.co" : {
"localizations" : {
@ -514,6 +523,9 @@
}
}
}
},
"Checkmark Icon" : {
},
"Clear Cache" : {
"localizations" : {
@ -530,6 +542,9 @@
}
}
}
},
"Clear Icon" : {
},
"Clear Logs" : {
"localizations" : {
@ -578,6 +593,9 @@
}
}
}
},
"Configure Icon" : {
},
"Confirm Erase App Data" : {
"localizations" : {
@ -914,6 +932,9 @@
}
}
}
},
"Episode Icon" : {
},
"Episodes" : {
"localizations" : {
@ -994,6 +1015,9 @@
}
}
}
},
"Error Icon" : {
},
"Error: %@" : {
"localizations" : {
@ -1027,6 +1051,9 @@
}
}
}
},
"Expand Icon" : {
},
"Explore" : {
"localizations" : {
@ -1059,6 +1086,9 @@
}
}
}
},
"External App Icon" : {
},
"Failed to parse response" : {
"localizations" : {
@ -1663,6 +1693,12 @@
}
}
}
},
"Magazine Icon" : {
},
"Magnifying Glass Icon" : {
},
"Main" : {
"localizations" : {
@ -1807,6 +1843,9 @@
}
}
}
},
"More Icon" : {
},
"Name" : {
"localizations" : {
@ -2079,6 +2118,9 @@
}
}
}
},
"Play Icon" : {
},
"Play Offline Content" : {
"localizations" : {
@ -2175,6 +2217,9 @@
}
}
}
},
"Questionmark Icon" : {
},
"Recently watched content will appear here." : {
"localizations" : {
@ -2383,6 +2428,9 @@
}
}
}
},
"Safari Icon" : {
},
"Scan to Visit" : {
"localizations" : {
@ -2415,6 +2463,9 @@
}
}
}
},
"Search Icon" : {
},
"Search View" : {
"localizations" : {
@ -2655,6 +2706,9 @@
}
}
}
},
"Speaker Icon" : {
},
"Speed Settings" : {
"localizations" : {
@ -2671,6 +2725,9 @@
}
}
}
},
"Star Icon" : {
},
"Starting authentication..." : {
"localizations" : {
@ -2847,6 +2904,9 @@
}
}
}
},
"Trakt Icon" : {
},
"Trakt.tv" : {
"localizations" : {

View file

@ -36,11 +36,11 @@ struct SoraApp: App {
.accentColor(settings.accentColor)
.onAppear {
// pass initial profile value to other manager
let suite = self.profileStore.getUserDefaultsSuite()
self.libraryManager.userDefaultsSuite = suite
self.continueWatchingManager.userDefaultsSuite = suite
let suite = profileStore.getUserDefaultsSuite()
libraryManager.userDefaultsSuite = suite
continueWatchingManager.userDefaultsSuite = suite
_ = iCloudSyncManager.shared
_ = ICloudSyncManager.shared
settings.updateAppearance()
Task {
@ -58,7 +58,7 @@ struct SoraApp: App {
}
.onChange(of: profileStore.currentProfile) { _ in
// pass changed suite value to other manager
let suite = self.profileStore.getUserDefaultsSuite()
let suite = profileStore.getUserDefaultsSuite()
libraryManager.updateProfileSuite(suite)
continueWatchingManager.updateProfileSuite(suite)
}
@ -71,7 +71,6 @@ struct SoraApp: App {
case "default_page":
if let comps = URLComponents(url: url, resolvingAgainstBaseURL: true),
let libraryURL = comps.queryItems?.first(where: { $0.name == "url" })?.value {
UserDefaults.standard.set(libraryURL, forKey: "lastCommunityURL")
UserDefaults.standard.set(true, forKey: "didReceiveDefaultPageLink")

View file

@ -5,8 +5,8 @@
// Created by Francesco on 08/08/24.
//
import UIKit
import Security
import UIKit
class AniListToken {
static let clientID = "19551"
@ -62,14 +62,14 @@ class AniListToken {
let task = URLSession.shared.dataTask(with: request) { data, _, error in
DispatchQueue.main.async {
if let error = error {
if let 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 = data else {
guard let data else {
Logger.shared.log("No data received", type: "Error")
NotificationCenter.default.post(name: authFailureNotification, object: nil, userInfo: ["error": "No data received"])
completion(false)

View file

@ -5,8 +5,8 @@
// Created by Francesco on 07/04/25.
//
import UIKit
import Security
import UIKit
class AniListMutation {
let apiURL = URL(string: "https://graphql.anilist.co")!
@ -77,7 +77,7 @@ class AniListMutation {
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
if let error {
completion(.failure(error))
return
}
@ -88,7 +88,7 @@ class AniListMutation {
return
}
if let data = data {
if let data {
do {
_ = try JSONSerialization.jsonObject(with: data, options: [])
Logger.shared.log("Successfully updated anime progress", type: "Debug")
@ -118,8 +118,13 @@ class AniListMutation {
"variables": variables
]
guard let jsonData = try? JSONSerialization.data(withJSONObject: requestBody, options: []) else {
completion(.failure(NSError(domain: "", code: -1,
userInfo: [NSLocalizedDescriptionKey: "Failed to serialize GraphQL request"])))
completion(
.failure(
NSError(domain: "", code: -1, userInfo: [
NSLocalizedDescriptionKey: "Failed to serialize GraphQL request"
])
)
)
return
}
@ -129,14 +134,19 @@ class AniListMutation {
request.httpBody = jsonData
URLSession.shared.dataTask(with: request) { data, _, error in
if let e = error {
return completion(.failure(e))
if let error {
return completion(.failure(error))
}
guard let data = data,
guard let data,
let json = try? JSONDecoder().decode(AniListMediaResponse.self, from: data),
let mal = json.data.Media?.idMal else {
return completion(.failure(NSError(domain: "", code: -1,
userInfo: [NSLocalizedDescriptionKey: "Failed to decode AniList response or idMal missing"])))
return completion(
.failure(
NSError(domain: "", code: -1, userInfo: [
NSLocalizedDescriptionKey: "Failed to decode AniList response or idMal missing"
])
)
)
}
completion(.success(mal))
}.resume()
@ -145,8 +155,10 @@ class AniListMutation {
private struct AniListMediaResponse: Decodable {
struct DataField: Decodable {
struct Media: Decodable { let idMal: Int? }
let Media: Media?
}
let data: DataField
}
}

View file

@ -5,8 +5,8 @@
// Created by Francesco on 13/04/25.
//
import UIKit
import Security
import UIKit
class TraktToken {
static let clientID = "6ec81bf19deb80fdfa25652eef101576ca6aaa0dc016d36079b2de413d71c369"
@ -101,12 +101,12 @@ class TraktToken {
let task = URLSession.shared.dataTask(with: request) { data, _, error in
DispatchQueue.main.async {
if let error = error {
if let error {
handleFailure(error: error.localizedDescription, completion: completion)
return
}
guard let data = data else {
guard let data else {
handleFailure(error: "No data received", completion: completion)
return
}
@ -115,7 +115,6 @@ class TraktToken {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
if let accessToken = json["access_token"] as? String,
let refreshToken = json["refresh_token"] as? String {
let accessSuccess = saveToKeychain(key: accessTokenKey, data: accessToken)
let refreshSuccess = saveToKeychain(key: refreshTokenKey, data: refreshToken)

View file

@ -5,8 +5,8 @@
// Created by Francesco on 13/04/25.
//
import UIKit
import Security
import UIKit
class TraktMutation {
let apiURL = URL(string: "https://api.trakt.tv")!
@ -110,7 +110,7 @@ class TraktMutation {
}
let task = URLSession.shared.dataTask(with: request) { _, response, error in
if let error = error {
if let error {
completion(.failure(error))
return
}

View file

@ -18,16 +18,16 @@ struct AnalyticsResponse: Codable {
// MARK: - Analytics Manager
class AnalyticsManager {
static let shared = AnalyticsManager()
private let analyticsURL = URL(string: "http://151.106.3.14:47474/analytics")!
private let moduleManager = ModuleManager()
private init() {}
private init() {
print("[Info] Analytics initializer called")
}
// MARK: - Send Analytics Data
func sendEvent(event: String, additionalData: [String: Any] = [:]) {
let defaults = UserDefaults.standard
// Ensure the key is set with a default value if missing
@ -80,13 +80,13 @@ class AnalyticsManager {
return
}
URLSession.shared.dataTask(with: request) { (data, _, error) in
if let error = error {
URLSession.shared.dataTask(with: request) { data, _, error in
if let error {
Logger.shared.log("Request failed: \(error.localizedDescription)", type: "Debug")
return
}
guard let data = data else {
guard let data else {
Logger.shared.log("No data received from server", type: "Debug")
return
}
@ -106,12 +106,12 @@ class AnalyticsManager {
// MARK: - Get App Version
private func getAppVersion() -> String {
return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown_version"
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown_version"
}
// MARK: - Get Device Model
private func getDeviceModel() -> String {
return UIDevice.modelName
UIDevice.modelName
}
// MARK: - Get Selected Module

View file

@ -16,12 +16,13 @@ class ContinueWatchingManager: ObservableObject {
NotificationCenter.default.addObserver(self, selector: #selector(handleiCloudSync), name: .iCloudSyncDidComplete, object: nil)
}
public func updateProfileSuite(_ newSuite: UserDefaults) {
func updateProfileSuite(_ newSuite: UserDefaults) {
userDefaultsSuite = newSuite
loadItems()
}
@objc private func handleiCloudSync() {
@objc
private func handleiCloudSync() {
NotificationCenter.default.post(name: .ContinueWatchingDidUpdate, object: nil)
}

View file

@ -5,23 +5,23 @@
// Created by Francesco on 29/04/25.
//
import SwiftUI
import AVKit
import AVFoundation
import AVKit
import SwiftUI
class DownloadManager: NSObject, ObservableObject {
@Published var activeDownloads: [(URL, Double)] = []
@Published var localPlaybackURL: URL?
private var assetDownloadURLSession: AVAssetDownloadURLSession!
private var activeDownloadTasks: [URLSessionTask: URL] = [:]
override init() {
super.init()
initializeDownloadSession()
loadLocalContent()
}
private func initializeDownloadSession() {
let configuration = URLSessionConfiguration.background(withIdentifier: "hls-downloader")
assetDownloadURLSession = AVAssetDownloadURLSession(
@ -30,7 +30,7 @@ class DownloadManager: NSObject, ObservableObject {
delegateQueue: .main
)
}
func downloadAsset(from url: URL) {
let asset = AVURLAsset(url: url)
let task = assetDownloadURLSession.makeAssetDownloadTask(
@ -39,21 +39,21 @@ class DownloadManager: NSObject, ObservableObject {
assetArtworkData: nil,
options: nil
)
task?.resume()
activeDownloadTasks[task!] = url
}
private func loadLocalContent() {
guard let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
do {
let contents = try FileManager.default.contentsOfDirectory(
at: documents,
includingPropertiesForKeys: nil,
options: .skipsHiddenFiles
)
if let localURL = contents.first(where: { $0.pathExtension == "movpkg" }) {
localPlaybackURL = localURL
}
@ -64,28 +64,33 @@ class DownloadManager: NSObject, ObservableObject {
}
extension DownloadManager: AVAssetDownloadDelegate {
func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
func urlSession(
_ session: URLSession,
assetDownloadTask: AVAssetDownloadTask,
didFinishDownloadingTo location: URL
) {
activeDownloadTasks.removeValue(forKey: assetDownloadTask)
localPlaybackURL = location
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let error = error else { return }
guard let error else { return }
print("Download error: \(error.localizedDescription)")
activeDownloadTasks.removeValue(forKey: task)
}
func urlSession(_ session: URLSession,
assetDownloadTask: AVAssetDownloadTask,
didLoad timeRange: CMTimeRange,
totalTimeRangesLoaded loadedTimeRanges: [NSValue],
timeRangeExpectedToLoad: CMTimeRange) {
func urlSession(
_ session: URLSession,
assetDownloadTask: AVAssetDownloadTask,
didLoad timeRange: CMTimeRange,
totalTimeRangesLoaded loadedTimeRanges: [NSValue],
timeRangeExpectedToLoad: CMTimeRange
) {
guard let url = activeDownloadTasks[assetDownloadTask] else { return }
let progress = loadedTimeRanges
.map { $0.timeRangeValue.duration.seconds / timeRangeExpectedToLoad.duration.seconds }
.reduce(0, +)
if let index = activeDownloads.firstIndex(where: { $0.0 == url }) {
activeDownloads[index].1 = progress
} else {

View file

@ -11,7 +11,9 @@ import UIKit
class DropManager {
static let shared = DropManager()
private init() {}
private init() {
print("[Info] Drops initialized")
}
func showDrop(title: String, subtitle: String, duration: TimeInterval, icon: UIImage?) {
let position: Drop.Position = .top

View file

@ -8,7 +8,6 @@
import SwiftUI
extension Color {
/// Intitialize SwiftUI Color via HEX String
///
/// - Parameters:

View file

@ -37,18 +37,18 @@ extension JSContext {
return
}
var request = URLRequest(url: url)
if let headers = headers {
if let headers {
for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}
}
let task = URLSession.custom.dataTask(with: request) { data, _, error in
if let error = error {
if let error {
Logger.shared.log("Network error in fetchNativeFunction: \(error.localizedDescription)", type: "Error")
reject.call(withArguments: [error.localizedDescription])
return
}
guard let data = data else {
guard let data else {
Logger.shared.log("No data in response", type: "Error")
reject.call(withArguments: ["No data"])
return
@ -88,30 +88,30 @@ extension JSContext {
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" {
if httpMethod == "GET", let 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" {
if httpMethod != "GET", let body, !body.isEmpty, body != "null", body != "undefined" {
request.httpBody = body.data(using: .utf8)
}
if let headers = headers {
if let headers {
for (key, value) in headers {
request.setValue(value, forHTTPHeaderField: key)
}
}
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 = error {
if let error {
Logger.shared.log("Network error in fetchV2NativeFunction: \(error.localizedDescription)", type: "Error")
reject.call(withArguments: [error.localizedDescription])
return
}
guard let tempFileURL = tempFileURL else {
guard let tempFileURL else {
Logger.shared.log("No data in response", type: "Error")
reject.call(withArguments: ["No data"])
return
@ -133,7 +133,6 @@ extension JSContext {
}
if let text = String(data: data, encoding: .utf8) {
responseDict["body"] = text
resolve.call(withArguments: [responseDict])
} else {
@ -141,7 +140,6 @@ extension JSContext {
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")
reject.call(withArguments: ["Error reading downloaded file"])

View file

@ -19,6 +19,6 @@ extension String {
}
var trimmed: String {
return self.trimmingCharacters(in: .whitespacesAndNewlines)
self.trimmingCharacters(in: .whitespacesAndNewlines)
}
}

View file

@ -8,14 +8,14 @@
import UIKit
public extension UIDevice {
static let modelName: String = {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
let identifier = machineMirror.children.reduce(into: "") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return }
identifier += String(UnicodeScalar(UInt8(value)))
}
func mapToDevice(identifier: String) -> String { // swiftlint:disable:this cyclomatic_complexity

View file

@ -19,7 +19,6 @@ extension UIApplication {
}
extension Decodable where Self: UIColor {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let components = try container.decode([CGFloat].self)
@ -33,9 +32,8 @@ extension Encodable where Self: UIColor {
(r, g, b, a) = (0, 0, 0, 0)
var container = encoder.singleValueContainer()
self.getRed(&r, green: &g, blue: &b, alpha: &a)
try container.encode([r,g,b,a])
try container.encode([r, g, b, a])
}
}
extension UIColor: Codable { }

View file

@ -9,9 +9,11 @@ import Foundation
// URL DELEGATE CLASS FOR FETCH API
class FetchDelegate: NSObject, URLSessionTaskDelegate {
private let allowRedirects: Bool
init(allowRedirects: Bool) {
self.allowRedirects = allowRedirects
}
// This handles the redirection and prevents it.
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
if allowRedirects {
@ -19,10 +21,9 @@ class FetchDelegate: NSObject, URLSessionTaskDelegate {
} else {
completionHandler(nil) // Block Redirect
}
}
}
extension URLSession {
static let userAgents = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",

View file

@ -37,4 +37,3 @@ struct HideToolbarModifier: ViewModifier {
}
}
}

View file

@ -7,7 +7,7 @@
import UIKit
class findTopViewController {
class FindTopViewController {
static func findViewController(_ viewController: UIViewController) -> UIViewController {
if let presented = viewController.presentedViewController {
return findViewController(presented)

View file

@ -8,7 +8,6 @@
import JavaScriptCore
extension JSController {
func fetchDetails(url: String, completion: @escaping ([MediaItem], [EpisodeLink]) -> Void) {
guard let url = URL(string: url) else {
completion([], [])
@ -16,15 +15,15 @@ extension JSController {
}
URLSession.custom.dataTask(with: url) { [weak self] data, _, error in
guard let self = self else { return }
guard let self else { return }
if let error = error {
if let error {
Logger.shared.log("Network error: \(error)", type: "Error")
DispatchQueue.main.async { completion([], []) }
return
}
guard let data = data, let html = String(data: data, encoding: .utf8) else {
guard let data, let html = String(data: data, encoding: .utf8) else {
Logger.shared.log("Failed to decode HTML", type: "Error")
DispatchQueue.main.async { completion([], []) }
return

View file

@ -9,7 +9,6 @@ import JavaScriptCore
// TODO: implement and test
extension JSController {
func fetchExploreResults(module: ScrapingModule, completion: @escaping ([ExploreItem]) -> Void) {
/*let searchUrl = module.metadata.searchBaseUrl.replacingOccurrences(of: "%s", with: keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")
@ -21,13 +20,13 @@ extension JSController {
URLSession.custom.dataTask(with: url) { [weak self] data, _, error in
guard let self = self else { return }
if let error = error {
if let error {
Logger.shared.log("Network error: \(error)",type: "Error")
DispatchQueue.main.async { completion([]) }
return
}
guard let data = data, let html = String(data: data, encoding: .utf8) else {
guard let data, let html = String(data: data, encoding: .utf8) else {
Logger.shared.log("Failed to decode HTML",type: "Error")
DispatchQueue.main.async { completion([]) }
return

View file

@ -8,7 +8,6 @@
import JavaScriptCore
extension JSController {
func fetchSearchResults(keyword: String, module: ScrapingModule, completion: @escaping ([SearchItem]) -> Void) {
let searchUrl = module.metadata.searchBaseUrl.replacingOccurrences(of: "%s", with: keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")
@ -18,15 +17,15 @@ extension JSController {
}
URLSession.custom.dataTask(with: url) { [weak self] data, _, error in
guard let self = self else { return }
guard let self else { return }
if let error = error {
if let error {
Logger.shared.log("Network error: \(error)", type: "Error")
DispatchQueue.main.async { completion([]) }
return
}
guard let data = data, let html = String(data: data, encoding: .utf8) else {
guard let data, let html = String(data: data, encoding: .utf8) else {
Logger.shared.log("Failed to decode HTML", type: "Error")
DispatchQueue.main.async { completion([]) }
return
@ -73,7 +72,6 @@ extension JSController {
}
let thenBlock: @convention(block) (JSValue) -> Void = { result in
Logger.shared.log(result.toString(), type: "HTMLStrings")
if let jsonString = result.toString(),
let data = jsonString.data(using: .utf8) {
@ -92,7 +90,6 @@ extension JSController {
DispatchQueue.main.async {
completion(resultItems)
}
} else {
Logger.shared.log("Failed to parse JSON", type: "Error")
DispatchQueue.main.async {

View file

@ -8,7 +8,6 @@
import JavaScriptCore
extension JSController {
func fetchStreamUrl(episodeUrl: String, softsub: Bool = false, module: ScrapingModule, completion: @escaping ((streams: [String]?, subtitles: [String]?)) -> Void) {
guard let url = URL(string: episodeUrl) else {
completion((nil, nil))
@ -16,15 +15,15 @@ extension JSController {
}
URLSession.custom.dataTask(with: url) { [weak self] data, _, error in
guard let self = self else { return }
guard let self else { return }
if let error = error {
if let error {
Logger.shared.log("Network error: \(error)", type: "Error")
DispatchQueue.main.async { completion((nil, nil)) }
return
}
guard let data = data, let html = String(data: data, encoding: .utf8) else {
guard let data, let html = String(data: data, encoding: .utf8) else {
Logger.shared.log("Failed to decode HTML", type: "Error")
DispatchQueue.main.async { completion((nil, nil)) }
return
@ -164,15 +163,15 @@ extension JSController {
func fetchStreamUrlJSSecond(episodeUrl: String, softsub: Bool = false, module: ScrapingModule, completion: @escaping ((streams: [String]?, subtitles: [String]?)) -> Void) {
let url = URL(string: episodeUrl)!
let task = URLSession.custom.dataTask(with: url) { [weak self] data, _, error in
guard let self = self else { return }
guard let self else { return }
if let error = error {
if let error {
Logger.shared.log("URLSession error: \(error.localizedDescription)", type: "Error")
DispatchQueue.main.async { completion((nil, nil)) }
return
}
guard let data = data, let htmlString = String(data: data, encoding: .utf8) else {
guard let data, let htmlString = String(data: data, encoding: .utf8) else {
Logger.shared.log("Failed to fetch HTML data", type: "Error")
DispatchQueue.main.async { completion((nil, nil)) }
return

View file

@ -7,8 +7,8 @@
// Thanks to pratikg29 for this code inside his open source project "https://github.com/pratikg29/Custom-Slider-Control?ref=iosexample.com"
//
import Foundation
import Combine
import Foundation
extension Double {
func asTimeString(style: DateComponentsFormatter.UnitsStyle) -> String {
@ -41,11 +41,11 @@ enum TimeStringStyle {
}
class VolumeViewModel: ObservableObject {
@Published var value: Double = 0.0
@Published var value = 0.0
}
class SliderViewModel: ObservableObject {
@Published var sliderValue: Double = 0.0
@Published var sliderValue = 0.0
@Published var introSegments: [ClosedRange<Double>] = []
@Published var outroSegments: [ClosedRange<Double>] = []
}
@ -53,8 +53,10 @@ class SliderViewModel: ObservableObject {
struct AniListMediaResponse: Decodable {
struct DataField: Decodable {
struct Media: Decodable { let idMal: Int? }
let Media: Media?
}
let data: DataField
}
@ -64,9 +66,11 @@ struct AniSkipResponse: Decodable {
let startTime: Double
let endTime: Double
}
let interval: Interval
let skipType: String
}
let found: Bool
let results: [Result]
let statusCode: Int

View file

@ -25,7 +25,7 @@ struct MusicProgressSlider<T: BinaryFloatingPoint>: View {
@State private var localRealProgress: T = 0
@State private var localTempProgress: T = 0
@GestureState private var isActive: Bool = false
@GestureState private var isActive = false
var body: some View {
GeometryReader { bounds in
@ -135,11 +135,10 @@ struct MusicProgressSlider<T: BinaryFloatingPoint>: View {
private func getPrgPercentage(_ value: T) -> T {
let range = inRange.upperBound - inRange.lowerBound
let correctedStartValue = value - inRange.lowerBound
let percentage = correctedStartValue / range
return percentage
return correctedStartValue / range
}
private func getPrgValue() -> T {
return ((localRealProgress + localTempProgress) * (inRange.upperBound - inRange.lowerBound)) + inRange.lowerBound
((localRealProgress + localTempProgress) * (inRange.upperBound - inRange.lowerBound)) + inRange.lowerBound
}
}

View file

@ -15,12 +15,12 @@ struct VolumeSlider<T: BinaryFloatingPoint>: View {
let fillColor: Color
let emptyColor: Color
let height: CGFloat
let onEditingChanged: (Bool) -> Void
let onEditingChanged: ((Bool) -> Void)? = nil
@State private var localRealProgress: T = 0
@State private var localTempProgress: T = 0
@State private var lastVolumeValue: T = 0
@GestureState private var isActive: Bool = false
@GestureState private var isActive = false
var body: some View {
GeometryReader { bounds in
@ -50,6 +50,8 @@ struct VolumeSlider<T: BinaryFloatingPoint>: View {
.onTapGesture {
handleIconTap()
}
.accessibilityAddTraits(.isButton)
.accessibilityLabel("Speaker Icon")
}
.frame(width: isActive ? bounds.size.width * 1.02 : bounds.size.width, alignment: .center)
.animation(animation, value: isActive)
@ -72,7 +74,7 @@ struct VolumeSlider<T: BinaryFloatingPoint>: View {
if !newValue {
value = sliderValueInRange()
}
onEditingChanged(newValue)
onEditingChanged?(newValue)
}
.onAppear {
localRealProgress = progress(for: value)

View file

@ -5,12 +5,12 @@
// Created by Francesco on 23/02/25.
//
import UIKit
import AVKit
import SwiftUI
import MediaPlayer
import AVFoundation
import AVKit
import MarqueeLabel
import MediaPlayer
import SwiftUI
import UIKit
class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDelegate {
let module: ScrapingModule
@ -22,11 +22,11 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
let episodeNumber: Int
let episodeImageUrl: String
let subtitlesURL: String?
let onWatchNext: () -> Void
let onWatchNext: (() -> Void)?
let aniListID: Int
private var aniListUpdatedSuccessfully = false
private var aniListUpdateImpossible: Bool = false
private var aniListUpdateImpossible = false
private var aniListRetryCount = 0
private let aniListMaxRetries = 6
@ -176,7 +176,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
fullUrl: String,
title: String,
episodeNumber: Int,
onWatchNext: @escaping () -> Void,
onWatchNext: (() -> Void)?,
subtitlesURL: String?,
aniListID: Int,
episodeImageUrl: String) {
@ -383,7 +383,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
}
}
@objc private func playerItemDidChange() {
@objc
private func playerItemDidChange() {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
if self.qualityButton.isHidden && self.isHLSStream {
@ -1373,10 +1374,10 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
UserDefaults.standard.set(self.currentTimeVal, forKey: "lastPlayedTime_\(self.fullUrl)")
UserDefaults.standard.set(self.duration, forKey: "totalTime_\(self.fullUrl)")
if self.subtitlesEnabled {
if subtitlesEnabled {
let adjustedTime = self.currentTimeVal - self.subtitleDelay
let cues = self.subtitlesLoader.cues.filter { adjustedTime >= $0.startTime && adjustedTime <= $0.endTime }
if cues.count > 0 {
if cues.isEmpty {
self.subtitleLabels[0].text = cues[0].text.strippedHTML
self.subtitleLabels[0].isHidden = false
} else {
@ -1478,14 +1479,16 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
}
}
@objc private func skipIntro() {
@objc
private func skipIntro() {
if let range = skipIntervals.op {
player.seek(to: range.end)
skipIntroButton.isHidden = true
}
}
@objc private func skipOutro() {
@objc
private func skipOutro() {
if let range = skipIntervals.ed {
player.seek(to: range.end)
skipOutroButton.isHidden = true
@ -1515,7 +1518,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
currentMenuButtonTrailing.isActive = true
}
@objc func toggleControls() {
@objc
func toggleControls() {
if isDimmed {
dimButton.isHidden = false
dimButton.alpha = 1.0
@ -1542,7 +1546,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
}
}
@objc func seekBackwardLongPress(_ gesture: UILongPressGestureRecognizer) {
@objc
func seekBackwardLongPress(_ gesture: UILongPressGestureRecognizer) {
if gesture.state == .began {
let holdValue = UserDefaults.standard.double(forKey: "skipIncrementHold")
let finalSkip = holdValue > 0 ? holdValue : 30
@ -1554,7 +1559,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
}
}
@objc func seekForwardLongPress(_ gesture: UILongPressGestureRecognizer) {
@objc
func seekForwardLongPress(_ gesture: UILongPressGestureRecognizer) {
if gesture.state == .began {
let holdValue = UserDefaults.standard.double(forKey: "skipIncrementHold")
let finalSkip = holdValue > 0 ? holdValue : 30
@ -1566,7 +1572,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
}
}
@objc func seekBackward() {
@objc
func seekBackward() {
let skipValue = UserDefaults.standard.double(forKey: "skipIncrement")
let finalSkip = skipValue > 0 ? skipValue : 10
currentTimeVal = max(currentTimeVal - finalSkip, 0)
@ -1576,7 +1583,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
animateButtonRotation(backwardButton, clockwise: false)
}
@objc func seekForward() {
@objc
func seekForward() {
let skipValue = UserDefaults.standard.double(forKey: "skipIncrement")
let finalSkip = skipValue > 0 ? skipValue : 10
currentTimeVal = min(currentTimeVal + finalSkip, duration)
@ -1585,7 +1593,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
animateButtonRotation(forwardButton)
}
@objc func handleDoubleTap(_ gesture: UITapGestureRecognizer) {
@objc
func handleDoubleTap(_ gesture: UITapGestureRecognizer) {
let tapLocation = gesture.location(in: view)
if tapLocation.x < view.bounds.width / 2 {
seekBackward()
@ -1596,11 +1605,13 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
}
}
@objc func handleSwipeDown(_ gesture: UISwipeGestureRecognizer) {
@objc
func handleSwipeDown(_ gesture: UISwipeGestureRecognizer) {
dismiss(animated: true, completion: nil)
}
@objc func togglePlayPause() {
@objc
func togglePlayPause() {
if isPlaying {
player.pause()
isPlaying = false
@ -1623,23 +1634,27 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
}
}
@objc func dismissTapped() {
@objc
func dismissTapped() {
dismiss(animated: true, completion: nil)
}
@objc func watchNextTapped() {
@objc
func watchNextTapped() {
player.pause()
dismiss(animated: true) { [weak self] in
self?.onWatchNext()
self?.onWatchNext?()
}
}
@objc func skip85Tapped() {
@objc
func skip85Tapped() {
currentTimeVal = min(currentTimeVal + 85, duration)
player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600))
}
@objc private func handleHoldForPause(_ gesture: UILongPressGestureRecognizer) {
@objc
private func handleHoldForPause(_ gesture: UILongPressGestureRecognizer) {
guard isHoldPauseEnabled else { return }
if gesture.state == .began {
@ -1647,7 +1662,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
}
}
@objc private func dimTapped() {
@objc
private func dimTapped() {
isDimmed.toggle()
dimButtonTimer?.invalidate()
@ -1748,7 +1764,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
URLSession.shared.dataTask(with: request) { [weak self] data, _, _ in
guard let self = self,
let data = data,
let data,
let content = String(data: data, encoding: .utf8) else {
Logger.shared.log("Failed to load m3u8 file")
DispatchQueue.main.async {
@ -2066,16 +2082,16 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
self.presentCustomDelayAlert()
}
]
let resetDelayAction = UIAction(title: "Reset Delay") { [weak self] _ in
guard let self = self else { return }
SubtitleSettingsManager.shared.update { settings in settings.subtitleDelay = 0.0 }
self.subtitleDelay = 0.0
self.loadSubtitleSettings()
}
let delayMenu = UIMenu(title: "Subtitle Delay", children: delayActions + [resetDelayAction])
let subtitleOptionsMenu = UIMenu(title: "Subtitle Options", children: [
subtitlesToggleAction, colorMenu, fontSizeMenu, shadowMenu, backgroundMenu, paddingMenu, delayMenu
])
@ -2208,7 +2224,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
}
}
@objc private func handleHoldGesture(_ gesture: UILongPressGestureRecognizer) {
@objc
private func handleHoldGesture(_ gesture: UILongPressGestureRecognizer) {
switch gesture.state {
case .began:
beginHoldSpeed()
@ -2219,7 +2236,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
}
}
@objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
@objc
private func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: view)
switch gesture.state {
@ -2289,8 +2307,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
activeFillColor: .white,
fillColor: .white.opacity(0.6),
emptyColor: .white.opacity(0.3),
height: 10,
onEditingChanged: { _ in }
height: 10
)
.shadow(color: Color.black.opacity(0.6), radius: 4, x: 0, y: 2)
}

View file

@ -9,11 +9,11 @@ import UIKit
struct SubtitleSettings: Codable {
var foregroundColor: UIColor = .white
var fontSize: Double = 20.0
var shadowRadius: Double = 1.0
var backgroundEnabled: Bool = true
var fontSize = 20.0
var shadowRadius = 1.0
var backgroundEnabled = true
var bottomPadding: CGFloat = 20.0
var subtitleDelay: Double = 0.0
var subtitleDelay = 0.0
}
class SubtitleSettingsManager {

View file

@ -30,7 +30,7 @@ class VTTSubtitlesLoader: ObservableObject {
let format = determineSubtitleFormat(from: url)
URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data,
guard let data,
let content = String(data: data, encoding: .utf8),
error == nil else { return }

View file

@ -20,12 +20,13 @@ class NormalPlayer: AVPlayerViewController {
private func setupHoldGesture() {
holdGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleHoldGesture(_:)))
holdGesture?.minimumPressDuration = 0.5
if let holdGesture = holdGesture {
if let holdGesture {
view.addGestureRecognizer(holdGesture)
}
}
@objc private func handleHoldGesture(_ gesture: UILongPressGestureRecognizer) {
@objc
private func handleHoldGesture(_ gesture: UILongPressGestureRecognizer) {
switch gesture.state {
case .began:
beginHoldSpeed()
@ -37,7 +38,7 @@ class NormalPlayer: AVPlayerViewController {
}
private func beginHoldSpeed() {
guard let player = player else { return }
guard let player else { return }
originalRate = player.rate
let holdSpeed = UserDefaults.standard.float(forKey: "holdSpeedPlayer")
player.rate = holdSpeed > 0 ? holdSpeed : 2.0

View file

@ -15,8 +15,8 @@ private struct ModuleLink: Identifiable {
struct CommunityLibraryView: View {
@EnvironmentObject var moduleManager: ModuleManager
@AppStorage("lastCommunityURL") private var inputURL = ""
@AppStorage("lastCommunityURL") private var inputURL: String = ""
@State private var webURL: URL?
@State private var errorMessage: String?
@State private var moduleLinkToAdd: ModuleLink?
@ -30,7 +30,6 @@ struct CommunityLibraryView: View {
}
WebView(url: webURL) { linkURL in
if let comps = URLComponents(url: linkURL, resolvingAgainstBaseURL: false),
let m = comps.queryItems?.first(where: { $0.name == "url" })?.value {
moduleLinkToAdd = ModuleLink(url: m)
@ -92,6 +91,7 @@ struct WebView: UIViewRepresentable {
class Coordinator: NSObject, WKNavigationDelegate {
let onCustom: (URL) -> Void
init(onCustom: @escaping (URL) -> Void) { self.onCustom = onCustom }
func webView(_ webView: WKWebView,

View file

@ -5,8 +5,8 @@
// Created by Francesco on 01/02/25.
//
import SwiftUI
import Kingfisher
import SwiftUI
struct ModuleAdditionSettingsView: View {
@Environment(\.presentationMode) var presentationMode
@ -81,7 +81,6 @@ struct ModuleAdditionSettingsView: View {
}
Divider()
} else if isLoading {
VStack(spacing: 20) {
ProgressView()
@ -91,11 +90,12 @@ struct ModuleAdditionSettingsView: View {
}
.frame(maxHeight: .infinity)
.padding(.top, 100)
} else if let errorMessage = errorMessage {
} else if let errorMessage {
VStack(spacing: 20) {
Image(systemName: "exclamationmark.triangle.fill")
.font(.system(size: 50))
.foregroundColor(.red)
.accessibilityLabel("Error Icon")
Text(errorMessage)
.foregroundColor(.red)
.multilineTextAlignment(.center)
@ -112,6 +112,7 @@ struct ModuleAdditionSettingsView: View {
Button(action: addModule) {
HStack {
Image(systemName: "plus.circle.fill")
.accessibilityLabel("+ Icon")
Text("Add Module")
}
.font(.headline)
@ -128,7 +129,7 @@ struct ModuleAdditionSettingsView: View {
.opacity(isLoading ? 0.6 : 1)
Button(action: {
self.presentationMode.wrappedValue.dismiss()
presentationMode.wrappedValue.dismiss()
}) {
Text("Cancel")
.foregroundColor(.accentColor)
@ -148,8 +149,8 @@ struct ModuleAdditionSettingsView: View {
Task {
guard let url = URL(string: moduleUrl) else {
await MainActor.run {
self.errorMessage = "Invalid URL"
self.isLoading = false
errorMessage = "Invalid URL"
isLoading = false
Logger.shared.log("Failed to open add module ui with url: \(moduleUrl)", type: "Error")
}
return
@ -158,13 +159,13 @@ struct ModuleAdditionSettingsView: View {
let (data, _) = try await URLSession.custom.data(from: url)
let metadata = try JSONDecoder().decode(ModuleMetadata.self, from: data)
await MainActor.run {
self.moduleMetadata = metadata
self.isLoading = false
moduleMetadata = metadata
isLoading = false
}
} catch {
await MainActor.run {
self.errorMessage = "Failed to fetch module: \(error.localizedDescription)"
self.isLoading = false
errorMessage = "Failed to fetch module: \(error.localizedDescription)"
isLoading = false
}
}
}
@ -178,7 +179,7 @@ struct ModuleAdditionSettingsView: View {
await MainActor.run {
isLoading = false
DropManager.shared.showDrop(title: "Module Added", subtitle: "Click it to select it.", duration: 2.0, icon: UIImage(systemName: "gear.badge.checkmark"))
self.presentationMode.wrappedValue.dismiss()
presentationMode.wrappedValue.dismiss()
}
} catch {
await MainActor.run {

View file

@ -31,21 +31,22 @@ class ModuleManager: ObservableObject {
NotificationCenter.default.removeObserver(self)
}
@objc private func handleModulesSyncCompleted() {
@objc
private func handleModulesSyncCompleted() {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
guard let self else { return }
let url = self.getModulesFilePath()
let url = getModulesFilePath()
guard FileManager.default.fileExists(atPath: url.path) else {
Logger.shared.log("No modules file found after sync", type: "Error")
self.modules = []
modules = []
return
}
do {
let data = try Data(contentsOf: url)
let decodedModules = try JSONDecoder().decode([ScrapingModule].self, from: data)
self.modules = decodedModules
modules = decodedModules
Task {
await self.checkJSModuleFiles()
@ -53,7 +54,7 @@ class ModuleManager: ObservableObject {
Logger.shared.log("Reloaded modules after iCloud sync")
} catch {
Logger.shared.log("Error handling modules sync: \(error.localizedDescription)", type: "Error")
self.modules = []
modules = []
}
}
}

View file

@ -50,7 +50,7 @@ struct ScrapingModule: Codable, Identifiable, Hashable {
hasher.combine(id)
}
static func == (lhs: ScrapingModule, rhs: ScrapingModule) -> Bool {
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.id == rhs.id
}
}

View file

@ -8,7 +8,7 @@
import Foundation
struct Profile: Identifiable, Equatable, Codable {
var id: UUID = UUID()
var id = UUID()
var name: String
var emoji: String
}

View file

@ -8,17 +8,16 @@
import SwiftUI
class ProfileStore: ObservableObject {
@AppStorage("profilesData") private var profilesData: Data = Data()
@AppStorage("currentProfileID") private var currentProfileID: String = ""
@AppStorage("profilesData") private var profilesData = Data()
@AppStorage("currentProfileID") private var currentProfileID = ""
@Published public var profiles: [Profile] = []
@Published public var currentProfile: Profile!
@Published var profiles: [Profile] = []
@Published var currentProfile: Profile!
public init() {
init() {
profiles = (try? JSONDecoder().decode([Profile].self, from: profilesData)) ?? []
if profiles.isEmpty {
// load default value
let defaultProfile = Profile(name: String(localized: "Default User"), emoji: "👤")
profiles = [defaultProfile]
@ -26,7 +25,6 @@ class ProfileStore: ObservableObject {
saveProfiles()
setCurrentProfile(defaultProfile)
} else {
// load current profile
if let uuid = UUID(uuidString: currentProfileID),
let match = profiles.first(where: { $0.id == uuid }) {
@ -39,7 +37,7 @@ class ProfileStore: ObservableObject {
}
}
public func getUserDefaultsSuite() -> UserDefaults {
func getUserDefaultsSuite() -> UserDefaults {
guard let suite = UserDefaults(suiteName: currentProfile.id.uuidString) else {
fatalError("This can only fail if suiteName == app bundle id ...")
}
@ -53,12 +51,12 @@ class ProfileStore: ObservableObject {
profilesData = (try? JSONEncoder().encode(profiles)) ?? Data()
}
public func setCurrentProfile(_ profile: Profile) {
func setCurrentProfile(_ profile: Profile) {
currentProfile = profile
currentProfileID = profile.id.uuidString
}
public func addProfile(name: String, emoji: String) {
func addProfile(name: String, emoji: String) {
let newProfile = Profile(name: name, emoji: emoji)
profiles.append(newProfile)
@ -66,7 +64,7 @@ class ProfileStore: ObservableObject {
setCurrentProfile(newProfile)
}
public func editCurrentProfile(name: String, emoji: String) {
func editCurrentProfile(name: String, emoji: String) {
guard let index = profiles.firstIndex(where: { $0.id == currentProfile.id }) else { return }
profiles[index].name = name
profiles[index].emoji = emoji
@ -75,7 +73,7 @@ class ProfileStore: ObservableObject {
setCurrentProfile(profiles[index])
}
public func deleteProfile(removalID: UUID?) {
func deleteProfile(removalID: UUID?) {
guard let removalID,
profiles.count == 1
else { return }

View file

@ -9,6 +9,7 @@ import SwiftUI
enum ShimmerType: String, CaseIterable, Identifiable {
case shimmer, pulse, none
var id: String { self.rawValue }
}
@ -47,7 +48,7 @@ struct ShimmerDefault: ViewModifier {
.mask(content)
.onAppear {
withAnimation(.linear(duration: 1.5).repeatForever(autoreverses: false)) {
self.phase = 1
phase = 1
}
}
}
@ -55,7 +56,7 @@ struct ShimmerDefault: ViewModifier {
struct ShimmerPulse: ViewModifier {
@Environment(\.colorScheme) var colorScheme
@State private var opacity: Double = 0.3
@State private var opacity = 0.3
func body(content: Content) -> some View {
content
@ -69,7 +70,7 @@ struct ShimmerPulse: ViewModifier {
.mask(content)
.onAppear {
withAnimation(.easeInOut(duration: 0.8).repeatForever(autoreverses: true)) {
self.opacity = 0.8
opacity = 0.8
}
}
}

View file

@ -22,8 +22,8 @@ import UIKit
// TODO: update "clear data" feature
// TODO: tests
class iCloudSyncManager {
static let shared = iCloudSyncManager()
class ICloudSyncManager {
static let shared = ICloudSyncManager()
private let defaultsToSync: [String] = [
"externalPlayer",
@ -72,7 +72,8 @@ class iCloudSyncManager {
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange), name: UserDefaults.didChangeNotification, object: nil)
}
@objc private func willEnterBackground() {
@objc
private func willEnterBackground() {
syncToiCloud()
syncModulesToiCloud()
}
@ -125,7 +126,8 @@ class iCloudSyncManager {
iCloud.synchronize()
}
@objc private func iCloudDidChangeExternally(_ notification: Notification) {
@objc
private func iCloudDidChangeExternally(_ notification: Notification) {
guard let userInfo = notification.userInfo,
let reason = userInfo[NSUbiquitousKeyValueStoreChangeReasonKey] as? Int else {
return
@ -137,7 +139,8 @@ class iCloudSyncManager {
}
}
@objc private func userDefaultsDidChange(_ notification: Notification) {
@objc
private func userDefaultsDidChange(_ notification: Notification) {
syncToiCloud()
}

View file

@ -5,33 +5,33 @@
// Created by Francesco on 29/04/25.
//
import SwiftUI
import AVKit
import SwiftUI
struct DownloadView: View {
@StateObject private var viewModel = DownloadManager()
@State private var hlsURL = "https://test-streams.mux.dev/x36xhzz/url_6/193039199_mp4_h264_aac_hq_7.m3u8"
var body: some View {
NavigationView {
VStack {
TextField("Enter HLS URL", text: $hlsURL)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
Button("Download Stream") {
viewModel.downloadAsset(from: URL(string: hlsURL)!)
}
.padding()
List(viewModel.activeDownloads, id: \.0) { (url, progress) in
List(viewModel.activeDownloads, id: \.0) { url, progress in
VStack(alignment: .leading) {
Text(url.absoluteString)
ProgressView(value: progress)
.progressViewStyle(LinearProgressViewStyle())
}
}
NavigationLink("Play Offline Content") {
if let url = viewModel.localPlaybackURL {
VideoPlayer(player: AVPlayer(url: url))

View file

@ -5,8 +5,8 @@
// Created by Dominic on 24.04.25.
//
import SwiftUI
import Kingfisher
import SwiftUI
struct ExploreItem: Identifiable {
let id = UUID()
@ -18,8 +18,8 @@ struct ExploreItem: Identifiable {
struct ExploreView: View {
@AppStorage("hideEmptySections") private var hideEmptySections: Bool?
@AppStorage("selectedModuleId") private var selectedModuleId: String?
@AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2
@AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4
@AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait = 2
@AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape = 4
@StateObject private var jsController = JSController()
@EnvironmentObject private var moduleManager: ModuleManager
@ -73,12 +73,12 @@ struct ExploreView: View {
ScrollView {
let columnsCount = determineColumns()
VStack(spacing: 0) {
if !(hideEmptySections ?? false) && selectedModule == nil {
VStack(spacing: 8) {
Image(systemName: "questionmark.app")
.font(.largeTitle)
.foregroundColor(.secondary)
.accessibilityLabel("Questionmark Icon")
Text("No Module Selected")
.font(.headline)
Text("Please select a module from settings")
@ -92,7 +92,7 @@ struct ExploreView: View {
if isLoading {
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) {
ForEach(0..<columnsCount*4, id: \.self) { _ in
ForEach(0 ..< columnsCount * 4, id: \.self) { _ in
SkeletonCell(type: .explore, cellWidth: cellWidth)
}
}
@ -103,6 +103,7 @@ struct ExploreView: View {
Image(systemName: "star")
.font(.largeTitle)
.foregroundColor(.secondary)
.accessibilityLabel("Star Icon")
Text("No Content Available")
.font(.headline)
Text("Try updating the Module")
@ -182,7 +183,6 @@ struct ExploreView: View {
} label: {
Label("Edit Profiles", systemImage: "slider.horizontal.3")
}
} label: {
Circle()
.fill(Color.secondary.opacity(0.3))
@ -198,7 +198,9 @@ struct ExploreView: View {
ToolbarItem(placement: .navigationBarTrailing) {
Menu {
if getModuleLanguageGroups().isEmpty {
Button("No modules available") { }
Button("No modules available") {
print("[Error] No Modules Button clicked")
}
.disabled(true)
Divider()
@ -225,6 +227,7 @@ struct ExploreView: View {
if module.id.uuidString == selectedModuleId {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
.accessibilityLabel("Checkmark Icon")
}
}
}
@ -234,7 +237,7 @@ struct ExploreView: View {
}
} label: {
HStack(spacing: 4) {
if let selectedModule = selectedModule {
if let selectedModule {
Text(selectedModule.metadata.sourceName)
.font(.headline)
.foregroundColor(.secondary)
@ -245,6 +248,7 @@ struct ExploreView: View {
}
Image(systemName: "chevron.down")
.foregroundColor(.secondary)
.accessibilityLabel("Expand Icon")
}
}
.fixedSize()
@ -311,7 +315,7 @@ struct ExploreView: View {
}
private func cleanLanguageName(_ language: String?) -> String {
guard let language = language else { return "Unknown" }
guard let language else { return "Unknown" }
let cleaned = language.replacingOccurrences(
of: "\\s*\\([^\\)]*\\)",
@ -338,10 +342,10 @@ struct ExploreView: View {
}
private func getModuleLanguageGroups() -> [String] {
return getModulesByLanguage().keys.sorted()
getModulesByLanguage().keys.sorted()
}
private func getModulesForLanguage(_ language: String) -> [ScrapingModule] {
return getModulesByLanguage()[language] ?? []
getModulesByLanguage()[language] ?? []
}
}

View file

@ -36,12 +36,13 @@ class LibraryManager: ObservableObject {
NotificationCenter.default.addObserver(self, selector: #selector(handleiCloudSync), name: .iCloudSyncDidComplete, object: nil)
}
public func updateProfileSuite(_ newSuite: UserDefaults) {
func updateProfileSuite(_ newSuite: UserDefaults) {
userDefaultsSuite = newSuite
loadBookmarks()
}
@objc private func handleiCloudSync() {
@objc
private func handleiCloudSync() {
DispatchQueue.main.async {
self.loadBookmarks()
}

View file

@ -5,8 +5,8 @@
// Created by Francesco on 05/01/25.
//
import SwiftUI
import Kingfisher
import SwiftUI
struct LibraryView: View {
@EnvironmentObject private var libraryManager: LibraryManager
@ -15,13 +15,13 @@ struct LibraryView: View {
@EnvironmentObject private var profileStore: ProfileStore
@AppStorage("hideEmptySections") private var hideEmptySections: Bool?
@AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2
@AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4
@AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait = 2
@AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape = 4
@Environment(\.verticalSizeClass) var verticalSizeClass
@State private var selectedBookmark: LibraryItem?
@State private var isDetailActive: Bool = false
@State private var isDetailActive = false
@State private var continueWatchingItems: [ContinueWatchingItem] = []
@State private var isLandscape: Bool = UIDevice.current.orientation.isLandscape
@ -57,7 +57,6 @@ struct LibraryView: View {
let columnsCount = determineColumns()
VStack(alignment: .leading, spacing: 12) {
if hideEmptySections != true || !continueWatchingManager.items.isEmpty {
Text("Continue Watching")
.font(.title2)
@ -70,6 +69,7 @@ struct LibraryView: View {
Image(systemName: "play.circle")
.font(.largeTitle)
.foregroundColor(.secondary)
.accessibilityLabel("Play Icon")
Text("No items to continue watching.")
.font(.headline)
Text("Recently watched content will appear here.")
@ -98,6 +98,7 @@ struct LibraryView: View {
Image(systemName: "magazine")
.font(.largeTitle)
.foregroundColor(.secondary)
.accessibilityLabel("Magazine Icon")
Text("You have no items saved.")
.font(.headline)
Text("Bookmark items for an easier access later.")
@ -120,7 +121,7 @@ struct LibraryView: View {
.placeholder {
RoundedRectangle(cornerRadius: 10)
.fill(Color.gray.opacity(0.3))
.aspectRatio(2/3, contentMode: .fit)
.aspectRatio(2 / 3, contentMode: .fit)
.shimmering()
}
.resizable()
@ -160,10 +161,12 @@ struct LibraryView: View {
destination: Group {
if let bookmark = selectedBookmark,
let module = moduleManager.modules.first(where: { $0.id.uuidString == bookmark.moduleId }) {
MediaInfoView(title: bookmark.title,
imageUrl: bookmark.imageUrl,
href: bookmark.href,
module: module)
MediaInfoView(
title: bookmark.title,
imageUrl: bookmark.imageUrl,
href: bookmark.href,
module: module
)
} else {
Text("No Data Available")
}
@ -212,7 +215,6 @@ struct LibraryView: View {
} label: {
Label("Edit Profiles", systemImage: "slider.horizontal.3")
}
} label: {
Circle()
.fill(Color.secondary.opacity(0.3))
@ -239,8 +241,8 @@ struct LibraryView: View {
private func markContinueWatchingItemAsWatched(item: ContinueWatchingItem) {
let key = "lastPlayedTime_\(item.fullUrl)"
let totalKey = "totalTime_\(item.fullUrl)"
UserDefaults.standard.set(99999999.0, forKey: key)
UserDefaults.standard.set(99999999.0, forKey: totalKey)
UserDefaults.standard.set(99_999_999.0, forKey: key)
UserDefaults.standard.set(99_999_999.0, forKey: totalKey)
continueWatchingManager.remove(item: item)
}
@ -294,7 +296,7 @@ struct ContinueWatchingCell: View {
var markAsWatched: () -> Void
var removeItem: () -> Void
@State private var currentProgress: Double = 0.0
@State private var currentProgress = 0.0
var body: some View {
Button(action: {
@ -311,7 +313,7 @@ struct ContinueWatchingCell: View {
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootVC = windowScene.windows.first?.rootViewController {
findTopViewController.findViewController(rootVC).present(videoPlayerViewController, animated: true, completion: nil)
FindTopViewController.findViewController(rootVC).present(videoPlayerViewController, animated: true, completion: nil)
}
} else {
let customMediaPlayer = CustomMediaPlayerViewController(
@ -321,7 +323,7 @@ struct ContinueWatchingCell: View {
fullUrl: item.fullUrl,
title: item.mediaTitle,
episodeNumber: item.episodeNumber,
onWatchNext: { },
onWatchNext: nil,
subtitlesURL: item.subtitles,
aniListID: item.aniListID ?? 0,
episodeImageUrl: item.imageUrl
@ -330,7 +332,7 @@ struct ContinueWatchingCell: 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)
FindTopViewController.findViewController(rootVC).present(customMediaPlayer, animated: true, completion: nil)
}
}
}) {
@ -345,7 +347,7 @@ struct ContinueWatchingCell: View {
}
.setProcessor(RoundCornerImageProcessor(cornerRadius: 10))
.resizable()
.aspectRatio(16/9, contentMode: .fill)
.aspectRatio(16 / 9, contentMode: .fill)
.frame(width: 240, height: 135)
.cornerRadius(10)
.clipped()

View file

@ -27,6 +27,7 @@ struct CircularProgressBar: View {
if progress >= 0.9 {
Image(systemName: "checkmark")
.font(.system(size: 12))
.accessibilityLabel("Checkmark Icon")
} else {
Text(String(format: "%.0f%%", min(progress, 1.0) * 100.0))
.font(.system(size: 12))

View file

@ -5,8 +5,8 @@
// Created by Francesco on 18/12/24.
//
import SwiftUI
import Kingfisher
import SwiftUI
struct EpisodeLink: Identifiable {
let id = UUID()
@ -24,10 +24,10 @@ struct EpisodeCell: View {
let onTap: (String) -> Void
let onMarkAllPrevious: () -> Void
@State private var episodeTitle: String = ""
@State private var episodeImageUrl: String = ""
@State private var isLoading: Bool = true
@State private var currentProgress: Double = 0.0
@State private var episodeTitle = ""
@State private var episodeImageUrl = ""
@State private var isLoading = true
@State private var currentProgress = 0.0
@Environment(\.colorScheme) private var colorScheme
@AppStorage("selectedAppearance") private var selectedAppearance: Appearance = .system
@ -39,8 +39,15 @@ struct EpisodeCell: View {
: "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/dev/assets/banner2.png"
}
init(episodeIndex: Int, episode: String, episodeID: Int, progress: Double,
itemID: Int, onTap: @escaping (String) -> Void, onMarkAllPrevious: @escaping () -> Void) {
init(
episodeIndex: Int,
episode: String,
episodeID: Int,
progress: Double,
itemID: Int,
onTap: @escaping (String) -> Void,
onMarkAllPrevious: @escaping () -> Void
) {
self.episodeIndex = episodeIndex
self.episode = episode
self.episodeID = episodeID
@ -55,10 +62,10 @@ struct EpisodeCell: View {
ZStack {
KFImage(URL(string: episodeImageUrl.isEmpty ? defaultBannerImage : episodeImageUrl))
.resizable()
.aspectRatio(16/9, contentMode: .fill)
.aspectRatio(16 / 9, contentMode: .fill)
.frame(width: 100, height: 56)
.cornerRadius(8)
.accessibilityLabel("Episode Icon")
if isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
@ -107,6 +114,7 @@ struct EpisodeCell: View {
.onChange(of: progress) { _ in
updateProgress()
}
.accessibilityAddTraits(.isButton)
.onTapGesture {
let imageUrl = episodeImageUrl.isEmpty ? defaultBannerImage : episodeImageUrl
onTap(imageUrl)
@ -120,7 +128,7 @@ struct EpisodeCell: View {
userDefaults.set(watchedTime, forKey: "lastPlayedTime_\(episode)")
userDefaults.set(totalTime, forKey: "totalTime_\(episode)")
DispatchQueue.main.async {
self.updateProgress()
updateProgress()
}
}
@ -129,7 +137,7 @@ struct EpisodeCell: View {
userDefaults.set(0.0, forKey: "lastPlayedTime_\(episode)")
userDefaults.set(0.0, forKey: "totalTime_\(episode)")
DispatchQueue.main.async {
self.updateProgress()
updateProgress()
}
}
@ -151,14 +159,14 @@ struct EpisodeCell: View {
}
URLSession.custom.dataTask(with: url) { data, _, error in
if let error = error {
if let error {
Logger.shared.log("Failed to fetch anime episode details: \(error)", type: "Error")
DispatchQueue.main.async { self.isLoading = false }
DispatchQueue.main.async { isLoading = false }
return
}
guard let data = data else {
DispatchQueue.main.async { self.isLoading = false }
guard let data else {
DispatchQueue.main.async { isLoading = false }
return
}
@ -170,20 +178,20 @@ struct EpisodeCell: View {
let title = episodeDetails["title"] as? [String: String],
let image = episodeDetails["image"] as? String else {
Logger.shared.log("Invalid anime response format", type: "Error")
DispatchQueue.main.async { self.isLoading = false }
DispatchQueue.main.async { isLoading = false }
return
}
DispatchQueue.main.async {
self.isLoading = false
isLoading = false
if UserDefaults.standard.object(forKey: "fetchEpisodeMetadata") == nil
|| UserDefaults.standard.bool(forKey: "fetchEpisodeMetadata") {
self.episodeTitle = title["en"] ?? ""
self.episodeImageUrl = image
episodeTitle = title["en"] ?? ""
episodeImageUrl = image
}
}
} catch {
DispatchQueue.main.async { self.isLoading = false }
DispatchQueue.main.async { isLoading = false }
}
}.resume()
}

View file

@ -5,9 +5,9 @@
// Created by Francesco on 05/01/25.
//
import SwiftUI
import Kingfisher
import SafariServices
import SwiftUI
struct MediaItem: Identifiable {
let id = UUID()
@ -609,30 +609,30 @@ struct MediaInfoView: View {
if module.metadata.asyncJS == true {
jsController.fetchDetailsJS(url: href) { items, episodes in
if let item = items.first {
self.synopsis = item.description
self.aliases = item.aliases
self.airdate = item.airdate
synopsis = item.description
aliases = item.aliases
airdate = item.airdate
}
self.episodeLinks = episodes
self.isLoading = false
self.isRefetching = false
episodeLinks = episodes
isLoading = false
isRefetching = false
}
} else {
jsController.fetchDetails(url: href) { items, episodes in
if let item = items.first {
self.synopsis = item.description
self.aliases = item.aliases
self.airdate = item.airdate
synopsis = item.description
aliases = item.aliases
airdate = item.airdate
}
self.episodeLinks = episodes
self.isLoading = false
self.isRefetching = false
episodeLinks = episodes
isLoading = false
isRefetching = false
}
}
} catch {
Logger.shared.log("Error loading module: \(error)", type: "Error")
self.isLoading = false
self.isRefetching = false
isLoading = false
isRefetching = false
}
}
}
@ -770,7 +770,7 @@ struct MediaInfoView: View {
func handleStreamFailure(error: Error? = nil) {
self.isFetchingEpisode = false
self.showStreamLoadingView = false
if let error = error {
if let error {
Logger.shared.log("Error loading module: \(error)", type: "Error")
AnalyticsManager.shared.sendEvent(event: "error", additionalData: ["error": error, "message": "Failed to fetch stream"])
}
@ -835,7 +835,7 @@ struct MediaInfoView: View {
}
}
findTopViewController.findViewController(rootVC).present(alert, animated: true)
FindTopViewController.findViewController(rootVC).present(alert, animated: true)
}
DispatchQueue.main.async {
@ -878,7 +878,7 @@ struct MediaInfoView: View {
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootVC = windowScene.windows.first?.rootViewController {
findTopViewController.findViewController(rootVC).present(videoPlayerViewController, animated: true, completion: nil)
FindTopViewController.findViewController(rootVC).present(videoPlayerViewController, animated: true, completion: nil)
}
return
default:
@ -914,7 +914,7 @@ 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)
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"))
@ -982,12 +982,12 @@ struct MediaInfoView: View {
request.httpBody = try? JSONSerialization.data(withJSONObject: parameters)
URLSession.custom.dataTask(with: request) { data, _, error in
if let error = error {
if let error {
completion(.failure(error))
return
}
guard let data = data else {
guard let data else {
completion(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "No data received"])))
return
}
@ -1033,7 +1033,7 @@ struct MediaInfoView: View {
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first,
let rootVC = window.rootViewController {
findTopViewController.findViewController(rootVC).present(alert, animated: true)
FindTopViewController.findViewController(rootVC).present(alert, animated: true)
}
}
}

View file

@ -5,8 +5,8 @@
// Created by Francesco on 05/01/25.
//
import SwiftUI
import Kingfisher
import SwiftUI
struct SearchItem: Identifiable {
let id = UUID()
@ -18,8 +18,8 @@ struct SearchItem: Identifiable {
struct SearchView: View {
@AppStorage("hideEmptySections") private var hideEmptySections: Bool?
@AppStorage("selectedModuleId") private var selectedModuleId: String?
@AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2
@AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4
@AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait = 2
@AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape = 4
@StateObject private var jsController = JSController()
@EnvironmentObject private var moduleManager: ModuleManager
@ -96,6 +96,7 @@ struct SearchView: View {
Image(systemName: "questionmark.app")
.font(.largeTitle)
.foregroundColor(.secondary)
.accessibilityLabel("Questionmark Icon")
Text("No Module Selected")
.font(.headline)
Text("Please select a module from settings")
@ -110,7 +111,7 @@ struct SearchView: View {
if !searchText.isEmpty {
if isSearching {
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) {
ForEach(0..<columnsCount*4, id: \.self) { _ in
ForEach(0 ..< columnsCount * 4, id: \.self) { _ in
SkeletonCell(type: .search, cellWidth: cellWidth)
}
}
@ -121,6 +122,7 @@ struct SearchView: View {
Image(systemName: "magnifyingglass")
.font(.largeTitle)
.foregroundColor(.secondary)
.accessibilityLabel("Magnifying Glass Icon")
Text("No Results Found")
.font(.headline)
Text("Try different keywords")
@ -201,7 +203,6 @@ struct SearchView: View {
} label: {
Label("Edit Profiles", systemImage: "slider.horizontal.3")
}
} label: {
Circle()
.fill(Color.secondary.opacity(0.3))
@ -216,7 +217,9 @@ struct SearchView: View {
ToolbarItem(placement: .navigationBarTrailing) {
Menu {
if getModuleLanguageGroups().isEmpty {
Button("No modules available") { }
Button("No modules available") {
print("[Error] No Modules Button clicked")
}
.disabled(true)
Divider()
@ -243,6 +246,7 @@ struct SearchView: View {
if module.id.uuidString == selectedModuleId {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
.accessibilityLabel("Checkmark Icon")
}
}
}
@ -252,7 +256,7 @@ struct SearchView: View {
}
} label: {
HStack(spacing: 4) {
if let selectedModule = selectedModule {
if let selectedModule {
Text(selectedModule.metadata.sourceName)
.font(.headline)
.foregroundColor(.secondary)
@ -263,6 +267,7 @@ struct SearchView: View {
}
Image(systemName: "chevron.down")
.foregroundColor(.secondary)
.accessibilityLabel("Expand Icon")
}
}
.fixedSize()
@ -338,7 +343,7 @@ struct SearchView: View {
}
private func cleanLanguageName(_ language: String?) -> String {
guard let language = language else { return "Unknown" }
guard let language else { return "Unknown" }
let cleaned = language.replacingOccurrences(
of: "\\s*\\([^\\)]*\\)",
@ -365,11 +370,11 @@ struct SearchView: View {
}
private func getModuleLanguageGroups() -> [String] {
return getModulesByLanguage().keys.sorted()
getModulesByLanguage().keys.sorted()
}
private func getModulesForLanguage(_ language: String) -> [ScrapingModule] {
return getModulesByLanguage()[language] ?? []
getModulesByLanguage()[language] ?? []
}
}
@ -386,27 +391,28 @@ struct SearchBar: View {
.background(Color(.systemGray6))
.cornerRadius(8)
.onChange(of: text) {_ in
debounceTimer?.invalidate()
// Start a new timer to wait before performing the action
debounceTimer?.invalidate()
// Start a new timer to wait before performing the action
debounceTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
// Perform the action after the delay (debouncing)
onSearchButtonClicked()
}
}
// Perform the action after the delay (debouncing)
onSearchButtonClicked()
}
}
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.secondary)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 8)
.accessibilityLabel("Search Icon")
if !text.isEmpty {
Button(action: {
self.text = ""
text = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.secondary)
.padding(.trailing, 8)
.accessibilityLabel("Clear Icon")
}
}
}

View file

@ -9,7 +9,7 @@ import SwiftUI
struct SettingsViewAlternateAppIconPicker: View {
@Binding var isPresented: Bool
@AppStorage("currentAppIcon") private var currentAppIcon: String = "Default"
@AppStorage("currentAppIcon") private var currentAppIcon = "Default"
let icons: [(name: String, icon: String)] = [
("Default", "Default"),
@ -39,11 +39,12 @@ struct SettingsViewAlternateAppIconPicker: View {
currentAppIcon == icon.name ? Color.accentColor.opacity(0.3) : Color.clear
)
.cornerRadius(10)
.accessibilityLabel("Alternative App Icon")
Text(icon.name)
.font(.caption)
.foregroundColor(currentAppIcon == icon.name ? .accentColor : .primary)
}
.accessibilityAddTraits(.isButton)
.onTapGesture {
currentAppIcon = icon.name
setAppIcon(named: icon.icon)
@ -61,7 +62,7 @@ struct SettingsViewAlternateAppIconPicker: View {
if UIApplication.shared.supportsAlternateIcons {
UIApplication.shared.setAlternateIconName(iconName == "Default" ? nil : "AppIcon_\(iconName)", completionHandler: { error in
isPresented = false
if let error = error {
if let error {
print("Failed to set alternate icon: \(error.localizedDescription)")
}
})

View file

@ -70,7 +70,7 @@ struct SettingsViewData: View {
let cacheURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first
do {
if let cacheURL = cacheURL {
if let cacheURL {
let filePaths = try FileManager.default.contentsOfDirectory(at: cacheURL, includingPropertiesForKeys: nil, options: [])
for filePath in filePaths {
try FileManager.default.removeItem(at: filePath)

View file

@ -8,22 +8,22 @@
import SwiftUI
struct SettingsViewGeneral: View {
@AppStorage("episodeChunkSize") private var episodeChunkSize: Int = 100
@AppStorage("refreshModulesOnLaunch") private var refreshModulesOnLaunch: Bool = false
@AppStorage("fetchEpisodeMetadata") private var fetchEpisodeMetadata: Bool = true
@AppStorage("analyticsEnabled") private var analyticsEnabled: Bool = false
@AppStorage("multiThreads") private var multiThreadsEnabled: Bool = false
@AppStorage("metadataProviders") private var metadataProviders: String = "AniList"
@AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2
@AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4
@AppStorage("hideEmptySections") private var hideEmptySections: Bool = false
@AppStorage("currentAppIcon") private var currentAppIcon: String = "Default"
@AppStorage("episodeSortOrder") private var episodeSortOrder: String = "Ascending"
@AppStorage("episodeChunkSize") private var episodeChunkSize = 100
@AppStorage("refreshModulesOnLaunch") private var refreshModulesOnLaunch = false
@AppStorage("fetchEpisodeMetadata") private var fetchEpisodeMetadata = true
@AppStorage("analyticsEnabled") private var analyticsEnabled = false
@AppStorage("multiThreads") private var multiThreadsEnabled = false
@AppStorage("metadataProviders") private var metadataProviders = "AniList"
@AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait = 2
@AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape = 4
@AppStorage("hideEmptySections") private var hideEmptySections = false
@AppStorage("currentAppIcon") private var currentAppIcon = "Default"
@AppStorage("episodeSortOrder") private var episodeSortOrder = "Ascending"
private let metadataProvidersList = ["AniList"]
private let sortOrderOptions = ["Ascending", "Descending"]
@EnvironmentObject var settings: Settings
@State var showAppIconPicker: Bool = false
@State private var showAppIconPicker = false
var body: some View {
Form {
@ -75,7 +75,6 @@ struct SettingsViewGeneral: View {
}
Section(header: Text("Media View"), footer: Text("The episode range controls how many episodes appear on each page. Episodes are grouped into sets (like 1-25, 26-50, and so on), allowing you to navigate through them more easily.\n\nFor episode metadata it is refering to the episode thumbnail and title, since sometimes it can contain spoilers.")) {
HStack {
Text("Episodes Range")
Spacer()
@ -87,6 +86,7 @@ struct SettingsViewGeneral: View {
if episodeChunkSize == chunkSize {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
.accessibilityLabel("Checkmark Icon")
}
Text("\(chunkSize)")
}
@ -108,6 +108,7 @@ struct SettingsViewGeneral: View {
if provider == metadataProviders {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
.accessibilityLabel("Checkmark Icon")
}
Text(provider)
}

View file

@ -8,7 +8,7 @@
import SwiftUI
struct SettingsViewLogger: View {
@State private var logs: String = ""
@State private var logs = ""
@StateObject private var filterViewModel = LogFilterViewModel.shared
var body: some View {
@ -46,10 +46,12 @@ struct SettingsViewLogger: View {
Image(systemName: "ellipsis.circle")
.resizable()
.frame(width: 20, height: 20)
.accessibilityLabel("More Icon")
}
NavigationLink(destination: SettingsViewLoggerFilter(viewModel: filterViewModel)) {
Image(systemName: "slider.horizontal.3")
.accessibilityLabel("Configure Icon")
}
}
}

View file

@ -59,7 +59,7 @@ class LogFilterViewModel: ObservableObject {
}
func isFilterEnabled(for type: String) -> Bool {
return filters.first(where: { $0.type == type })?.isEnabled ?? true
filters.first(where: { $0.type == type })?.isEnabled ?? true
}
private func saveFiltersToUserDefaults() {

View file

@ -5,8 +5,8 @@
// Created by Francesco on 05/01/25.
//
import SwiftUI
import Kingfisher
import SwiftUI
struct SettingsViewModule: View {
@EnvironmentObject var moduleManager: ModuleManager
@ -14,12 +14,12 @@ struct SettingsViewModule: View {
@AppStorage("selectedModuleId") private var selectedModuleId: String?
@AppStorage("hideEmptySections") private var hideEmptySections: Bool?
@AppStorage("didReceiveDefaultPageLink") private var didReceiveDefaultPageLink: Bool = false
@AppStorage("didReceiveDefaultPageLink") private var didReceiveDefaultPageLink = false
@State private var errorMessage: String?
@State private var isLoading = false
@State private var isRefreshing = false
@State private var moduleUrl: String = ""
@State private var moduleUrl = ""
@State private var refreshTask: Task<Void, Never>?
@State private var showLibrary = false
@ -31,6 +31,7 @@ struct SettingsViewModule: View {
Image(systemName: "plus.app")
.font(.largeTitle)
.foregroundColor(.secondary)
.accessibilityLabel("+ Icon")
Text("No Modules")
.font(.headline)
@ -84,9 +85,11 @@ struct SettingsViewModule: View {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
.frame(width: 25, height: 25)
.accessibilityLabel("Checkmark Icon")
}
}
.contentShape(Rectangle())
.accessibilityAddTraits(.isButton)
.onTapGesture {
selectedModuleId = module.id.uuidString
}
@ -194,11 +197,11 @@ struct SettingsViewModule: View {
)
clipboardAlert.addAction(UIAlertAction(title: "Use Clipboard", style: .default, handler: { _ in
self.displayModuleView(url: pasteboardString)
displayModuleView(url: pasteboardString)
}))
clipboardAlert.addAction(UIAlertAction(title: "Enter Manually", style: .cancel, handler: { _ in
self.showManualUrlAlert()
showManualUrlAlert()
}))
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
@ -206,7 +209,6 @@ struct SettingsViewModule: View {
windowScene.windows.first?.tintColor = UIColor(settings.accentColor)
rootViewController.present(clipboardAlert, animated: true, completion: nil)
}
} else {
showManualUrlAlert()
}
@ -226,7 +228,7 @@ struct SettingsViewModule: View {
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alert.addAction(UIAlertAction(title: "Add", style: .default, handler: { _ in
if let url = alert.textFields?.first?.text, !url.isEmpty {
self.displayModuleView(url: url)
displayModuleView(url: url)
}
}))
@ -240,7 +242,7 @@ struct SettingsViewModule: View {
func displayModuleView(url: String) {
DispatchQueue.main.async {
let addModuleView = ModuleAdditionSettingsView(moduleUrl: url)
.environmentObject(self.moduleManager)
.environmentObject(moduleManager)
let hostingController = UIHostingController(rootView: addModuleView)
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,

View file

@ -8,19 +8,19 @@
import SwiftUI
struct SettingsViewPlayer: View {
@AppStorage("externalPlayer") private var externalPlayer: String = "Sora"
@AppStorage("externalPlayer") private var externalPlayer = "Sora"
@AppStorage("alwaysLandscape") private var isAlwaysLandscape = false
@AppStorage("rememberPlaySpeed") private var isRememberPlaySpeed = false
@AppStorage("holdSpeedPlayer") private var holdSpeedPlayer: Double = 2.0
@AppStorage("skipIncrement") private var skipIncrement: Double = 10.0
@AppStorage("skipIncrementHold") private var skipIncrementHold: Double = 30.0
@AppStorage("holdSpeedPlayer") private var holdSpeedPlayer = 2.0
@AppStorage("skipIncrement") private var skipIncrement = 10.0
@AppStorage("skipIncrementHold") private var skipIncrementHold = 30.0
@AppStorage("holdForPauseEnabled") private var holdForPauseEnabled = false
@AppStorage("skip85Visible") private var skip85Visible: Bool = true
@AppStorage("doubleTapSeekEnabled") private var doubleTapSeekEnabled: Bool = false
@AppStorage("skipIntroOutroVisible") private var skipIntroOutroVisible: Bool = true
@AppStorage("skip85Visible") private var skip85Visible = true
@AppStorage("doubleTapSeekEnabled") private var doubleTapSeekEnabled = false
@AppStorage("skipIntroOutroVisible") private var skipIntroOutroVisible = true
private let mediaPlayers = ["Default", "VLC", "OutPlayer", "Infuse", "nPlayer", "SenPlayer", "Sora"]
var body: some View {
Form {
Section(header: Text("Media Player"), footer: Text("Some features are limited to the Sora and Default player, such as ForceLandscape, holdSpeed and custom time skip increments.")) {
@ -36,9 +36,11 @@ struct SettingsViewPlayer: View {
if player == externalPlayer {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
.accessibilityLabel("Checkmark Icon")
} else if player != "Default" && player != "Sora" {
Image(systemName: "arrow.up.forward.app")
.foregroundColor(.secondary)
.accessibilityLabel("External App Icon")
} else {
Color.clear.frame(width: 20)
}
@ -137,7 +139,7 @@ struct SubtitleSettingsSection: View {
Section(header: Text("Subtitle Settings")) {
ColorPicker("Subtitle Color", selection: Binding(
get: {
return Color(foregroundColor)
Color(foregroundColor)
},
set: { newColor in
let uiColor = UIColor(newColor)
@ -162,6 +164,7 @@ struct SubtitleSettingsSection: View {
if shadowRadius == Double(option) {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
.accessibilityLabel("Checkmark Icon")
}
Text("\(option)")
}

View file

@ -9,7 +9,7 @@ import SwiftUI
struct ProfileCell: View {
let profile: Profile
var isSelected: Bool = false
var isSelected = false
var body: some View {
HStack(spacing: 10) {
@ -31,6 +31,7 @@ struct ProfileCell: View {
if isSelected {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
.accessibilityLabel("Checkmark Icon")
}
}
.padding(.vertical, 4)
@ -50,20 +51,21 @@ struct SettingsViewProfile: View {
Button {
profileStore.setCurrentProfile(profile)
} label: {
ProfileCell(profile: profile,
ProfileCell(
profile: profile,
isSelected: profile.id == profileStore.currentProfile.id
)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
if profileStore.profiles.count > 1 {
Button(role: .destructive) {
if profileStore.profiles.count > 1 {
Button(role: .destructive) {
profileIDToRemove = profile.id
showDeleteAlert = true
} label: {
} label: {
Label("Delete", systemImage: "trash")
}
}
}
}
}
}
}
}
@ -73,7 +75,6 @@ struct SettingsViewProfile: View {
TextField("Avatar", text: Binding(
get: { profileStore.currentProfile.emoji },
set: { newValue in
// handle multi unicode emojis like "👨👩👧👦" or "🧙"
let emoji = String(newValue
.trimmingCharacters(in: .whitespacesAndNewlines)
@ -142,6 +143,7 @@ struct SettingsViewProfile: View {
} label: {
Image(systemName: "plus")
.foregroundColor(.accentColor)
.accessibilityLabel("+ Icon")
}
}
}

View file

@ -5,23 +5,23 @@
// Created by Francesco on 23/03/25.
//
import SwiftUI
import Security
import SwiftUI
struct SettingsViewTrackers: View {
@AppStorage("sendPushUpdates") private var isSendPushUpdates = true
@AppStorage("sendTraktUpdates") private var isSendTraktUpdates = true
@State private var anilistStatus: LocalizedStringKey = "You are not logged in"
@State private var isAnilistLoggedIn: Bool = false
@State private var anilistUsername: String = ""
@State private var isAnilistLoading: Bool = false
@State private var isAnilistLoggedIn = false
@State private var anilistUsername = ""
@State private var isAnilistLoading = false
@State private var profileColor: Color = .accentColor
@State private var traktStatus: LocalizedStringKey = "You are not logged in"
@State private var isTraktLoggedIn: Bool = false
@State private var traktUsername: String = ""
@State private var isTraktLoading: Bool = false
@State private var isTraktLoggedIn = false
@State private var traktUsername = ""
@State private var isTraktLoading = false
var body: some View {
Form {
@ -32,6 +32,7 @@ struct SettingsViewTrackers: View {
.frame(width: 80, height: 80)
.clipShape(Rectangle())
.cornerRadius(10)
.accessibilityLabel("AniList Icon")
Text("AniList.co")
.font(.title2)
}
@ -76,6 +77,7 @@ struct SettingsViewTrackers: View {
.frame(width: 80, height: 80)
.clipShape(Rectangle())
.cornerRadius(10)
.accessibilityLabel("Trakt Icon")
Text("Trakt.tv")
.font(.title2)
}
@ -107,7 +109,9 @@ struct SettingsViewTrackers: View {
}
.modifier(SeparatorAlignmentModifier())
Section(footer: Text("Sora and cranci1 are not affiliated with AniList nor Trakt in any way.\n\nAlso note that progresses update may not be 100% accurate.")) {}
Section(footer: Text("Sora and cranci1 are not affiliated with AniList nor Trakt in any way.\n\nAlso note that progresses update may not be 100% accurate.")) {
EmptyView()
}
}
.navigationTitle("Trackers")
.onAppear {
@ -131,33 +135,33 @@ struct SettingsViewTrackers: View {
func setupNotificationObservers() {
NotificationCenter.default.addObserver(forName: AniListToken.authSuccessNotification, object: nil, queue: .main) { _ in
self.anilistStatus = "Authentication successful!"
self.updateAniListStatus()
anilistStatus = "Authentication successful!"
updateAniListStatus()
}
NotificationCenter.default.addObserver(forName: AniListToken.authFailureNotification, object: nil, queue: .main) { notification in
if let error = notification.userInfo?["error"] as? String {
self.anilistStatus = "Login failed: \(error)"
anilistStatus = "Login failed: \(error)"
} else {
self.anilistStatus = "Login failed with unknown error"
anilistStatus = "Login failed with unknown error"
}
self.isAnilistLoggedIn = false
self.isAnilistLoading = false
isAnilistLoggedIn = false
isAnilistLoading = false
}
NotificationCenter.default.addObserver(forName: TraktToken.authSuccessNotification, object: nil, queue: .main) { _ in
self.traktStatus = "Authentication successful!"
self.updateTraktStatus()
traktStatus = "Authentication successful!"
updateTraktStatus()
}
NotificationCenter.default.addObserver(forName: TraktToken.authFailureNotification, object: nil, queue: .main) { notification in
if let error = notification.userInfo?["error"] as? String {
self.traktStatus = "Login failed: \(error)"
traktStatus = "Login failed: \(error)"
} else {
self.traktStatus = "Login failed with unknown error"
traktStatus = "Login failed with unknown error"
}
self.isTraktLoggedIn = false
self.isTraktLoading = false
isTraktLoggedIn = false
isTraktLoading = false
}
}
@ -196,14 +200,14 @@ struct SettingsViewTrackers: View {
URLSession.shared.dataTask(with: request) { data, _, error in
DispatchQueue.main.async {
self.isTraktLoading = false
if let error = error {
self.traktStatus = "Error: \(error.localizedDescription)"
isTraktLoading = false
if let error {
traktStatus = "Error: \(error.localizedDescription)"
return
}
guard let data = data else {
self.traktStatus = "No data received"
guard let data else {
traktStatus = "No data received"
return
}
@ -211,11 +215,11 @@ struct SettingsViewTrackers: View {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let user = json["user"] as? [String: Any],
let username = user["username"] as? String {
self.traktUsername = username
self.traktStatus = "Logged in as \(username)"
traktUsername = username
traktStatus = "Logged in as \(username)"
}
} catch {
self.traktStatus = "Failed to parse response"
traktStatus = "Failed to parse response"
}
}
}.resume()
@ -313,12 +317,12 @@ struct SettingsViewTrackers: View {
URLSession.shared.dataTask(with: request) { data, _, error in
DispatchQueue.main.async {
isAnilistLoading = false
if let error = error {
if let error {
anilistStatus = "Error: \(error.localizedDescription)"
Logger.shared.log("Error: \(error.localizedDescription)", type: "Error")
return
}
guard let data = data else {
guard let data else {
anilistStatus = "No data received"
Logger.shared.log("No data received", type: "Error")
return
@ -330,7 +334,6 @@ struct SettingsViewTrackers: View {
let name = viewer["name"] as? String,
let options = viewer["options"] as? [String: Any],
let colorName = options["profileColor"] as? String {
anilistUsername = name
profileColor = colorFromName(colorName)
anilistStatus = "Logged in as \(name)"

View file

@ -58,6 +58,7 @@ struct SettingsView: View {
.foregroundColor(Color(hex: "7289DA"))
Spacer()
Image(systemName: "safari")
.accessibilityLabel("Safari Icon")
.foregroundColor(Color(hex: "7289DA"))
}
}
@ -71,6 +72,7 @@ struct SettingsView: View {
.foregroundColor(.red)
Spacer()
Image(systemName: "safari")
.accessibilityLabel("Safari Icon")
.foregroundColor(.red)
}
}
@ -84,6 +86,7 @@ struct SettingsView: View {
.foregroundColor(.secondary)
Spacer()
Image(systemName: "safari")
.accessibilityLabel("Safari Icon")
.foregroundColor(.secondary)
}
}
@ -97,6 +100,7 @@ struct SettingsView: View {
.foregroundColor(.secondary)
Spacer()
Image(systemName: "safari")
.accessibilityLabel("Safari Icon")
.foregroundColor(.secondary)
}
}
@ -110,6 +114,7 @@ struct SettingsView: View {
.foregroundColor(.secondary)
Spacer()
Image(systemName: "safari")
.accessibilityLabel("Safari Icon")
.foregroundColor(.secondary)
}
}
@ -174,6 +179,7 @@ class Settings: ObservableObject {
private func applyColorToUIKit(_ color: Color) {
let tempStepper = UIStepper()
tempStepper.tintColor = UIColor(color)
UIStepper.appearance().setDecrementImage(tempStepper.decrementImage(for: .normal), for: .normal)
UIStepper.appearance().setIncrementImage(tempStepper.incrementImage(for: .normal), for: .normal)
}

View file

@ -137,13 +137,13 @@
buildConfigurationList = 1207646F2DB6F6E1003621E9 /* Build configuration list for PBXNativeTarget "SulfurTV" */;
buildPhases = (
120764612DB6F6E0003621E9 /* Sources */,
12DAC1832DBE3C1C00B31A65 /* ShellScript */,
120764622DB6F6E0003621E9 /* Frameworks */,
120764632DB6F6E0003621E9 /* Resources */,
);
buildRules = (
);
dependencies = (
123FEDD42DC41339001C4704 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
120764662DB6F6E0003621E9 /* SulfurTV */,
@ -160,13 +160,13 @@
buildConfigurationList = 133D7C782D2BE2520075467E /* Build configuration list for PBXNativeTarget "Sulfur" */;
buildPhases = (
133D7C662D2BE2500075467E /* Sources */,
12DAC1822DBE3C0300B31A65 /* ShellScript */,
133D7C672D2BE2500075467E /* Frameworks */,
133D7C682D2BE2500075467E /* Resources */,
);
buildRules = (
);
dependencies = (
123FEDD62DC41341001C4704 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
126C42F62DB9AA97006BC27D /* Sora */,
@ -214,6 +214,7 @@
132E351E2D959E1D0007800E /* XCRemoteSwiftPackageReference "FFmpeg-iOS-Lame" */,
132E35212D959E410007800E /* XCRemoteSwiftPackageReference "Kingfisher" */,
13B77E172DA44F8300126FDF /* XCRemoteSwiftPackageReference "MarqueeLabel" */,
123FEDD22DC412F0001C4704 /* XCRemoteSwiftPackageReference "SwiftLintPlugins" */,
);
productRefGroup = 133D7C6B2D2BE2500075467E /* Products */;
projectDirPath = "";
@ -246,43 +247,6 @@
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
12DAC1822DBE3C0300B31A65 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if command -v swiftlint >/dev/null 2>&1\nthen\n swiftlint lint --fix && swiftlint\nelse\n echo \"warning: `swiftlint` command not found - See https://github.com/realm/SwiftLint#installation for installation instructions.\"\nfi\n";
};
12DAC1832DBE3C1C00B31A65 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if command -v swiftlint >/dev/null 2>&1\nthen\n swiftlint lint --fix && swiftlint\nelse\n echo \"warning: `swiftlint` command not found - See https://github.com/realm/SwiftLint#installation for installation instructions.\"\nfi\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
120764612DB6F6E0003621E9 /* Sources */ = {
isa = PBXSourcesBuildPhase;
@ -300,6 +264,17 @@
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
123FEDD42DC41339001C4704 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = 123FEDD32DC41339001C4704 /* SwiftLintBuildToolPlugin */;
};
123FEDD62DC41341001C4704 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = 123FEDD52DC41341001C4704 /* SwiftLintBuildToolPlugin */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
1207646D2DB6F6E1003621E9 /* Debug */ = {
isa = XCBuildConfiguration;
@ -634,6 +609,14 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
123FEDD22DC412F0001C4704 /* XCRemoteSwiftPackageReference "SwiftLintPlugins" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/SimplyDanny/SwiftLintPlugins";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.59.1;
};
};
132E351B2D959DDB0007800E /* XCRemoteSwiftPackageReference "Drops" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/omaralbeik/Drops.git";
@ -669,6 +652,16 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
123FEDD32DC41339001C4704 /* SwiftLintBuildToolPlugin */ = {
isa = XCSwiftPackageProductDependency;
package = 123FEDD22DC412F0001C4704 /* XCRemoteSwiftPackageReference "SwiftLintPlugins" */;
productName = "plugin:SwiftLintBuildToolPlugin";
};
123FEDD52DC41341001C4704 /* SwiftLintBuildToolPlugin */ = {
isa = XCSwiftPackageProductDependency;
package = 123FEDD22DC412F0001C4704 /* XCRemoteSwiftPackageReference "SwiftLintPlugins" */;
productName = "plugin:SwiftLintBuildToolPlugin";
};
132E351C2D959DDB0007800E /* Drops */ = {
isa = XCSwiftPackageProductDependency;
package = 132E351B2D959DDB0007800E /* XCRemoteSwiftPackageReference "Drops" */;

View file

@ -1,5 +1,5 @@
{
"originHash" : "e772caa8d6a8793d24bf04e3d77695cd5ac695f3605d2b657e40115caedf8863",
"originHash" : "c4909124df3eb22bfcc539fb1f3936eb79c309605d2f34c5b02efa1fe3f48447",
"pins" : [
{
"identity" : "drops",
@ -45,6 +45,15 @@
"branch" : "master",
"revision" : "18e4787f4dc1c26d2d581c4bc9aeae34686eeeae"
}
},
{
"identity" : "swiftlintplugins",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SimplyDanny/SwiftLintPlugins",
"state" : {
"revision" : "8545ddf4de043e6f2051c5cf204f39ef778ebf6b",
"version" : "0.59.1"
}
}
],
"version" : 3