diff --git a/Sora/ContentView.swift b/Sora/ContentView.swift index afbd4ea..8fa65bb 100644 --- a/Sora/ContentView.swift +++ b/Sora/ContentView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import Kingfisher struct ContentView: View { var body: some View { @@ -15,6 +14,10 @@ struct ContentView: View { .tabItem { Label("Library", systemImage: "books.vertical") } + DownloadView() + .tabItem { + Label("Downloads", systemImage: "arrow.down.app.fill") + } SearchView() .tabItem { Label("Search", systemImage: "magnifyingglass") diff --git a/Sora/SoraApp.swift b/Sora/SoraApp.swift index 96d74a1..e7afdc0 100644 --- a/Sora/SoraApp.swift +++ b/Sora/SoraApp.swift @@ -12,7 +12,8 @@ struct SoraApp: App { @StateObject private var settings = Settings() @StateObject private var moduleManager = ModuleManager() @StateObject private var librarykManager = LibraryManager() - + @StateObject private var downloadManager = DownloadManager() + init() { if let userAccentColor = UserDefaults.standard.color(forKey: "accentColor") { UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = userAccentColor @@ -26,13 +27,14 @@ struct SoraApp: App { } } } - + var body: some Scene { WindowGroup { ContentView() .environmentObject(moduleManager) .environmentObject(settings) .environmentObject(librarykManager) + .environmentObject(downloadManager) .accentColor(settings.accentColor) .onAppear { settings.updateAppearance() @@ -51,7 +53,7 @@ struct SoraApp: App { } } } - + private func handleURL(_ url: URL) { guard url.scheme == "sora", let host = url.host else { return } switch host { @@ -61,9 +63,8 @@ struct SoraApp: App { UserDefaults.standard.set(libraryURL, forKey: "lastCommunityURL") UserDefaults.standard.set(true, forKey: "didReceiveDefaultPageLink") - - let communityView = CommunityLibraryView() - .environmentObject(moduleManager) + + let communityView = CommunityLibraryView().environmentObject(moduleManager) let hostingController = UIHostingController(rootView: communityView) DispatchQueue.main.async { if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene, @@ -89,11 +90,10 @@ struct SoraApp: App { else { return } - - let addModuleView = ModuleAdditionSettingsView(moduleUrl: moduleURL) - .environmentObject(moduleManager) + + let addModuleView = ModuleAdditionSettingsView(moduleUrl: moduleURL).environmentObject(moduleManager) let hostingController = UIHostingController(rootView: addModuleView) - + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let window = windowScene.windows.first { window.rootViewController?.present(hostingController, animated: true) @@ -103,19 +103,19 @@ struct SoraApp: App { type: "Error" ) } - + default: break } } - + static func handleRedirect(url: URL) { guard let params = url.queryParameters, let code = params["code"] else { - Logger.shared.log("Failed to extract authorization code") - return - } - + Logger.shared.log("Failed to extract authorization code") + return + } + switch url.host { case "anilist": AniListToken.exchangeAuthorizationCodeForToken(code: code) { success in @@ -125,7 +125,7 @@ struct SoraApp: App { Logger.shared.log("AniList token exchange failed", type: "Error") } } - + case "trakt": TraktToken.exchangeAuthorizationCodeForToken(code: code) { success in if success { @@ -134,7 +134,7 @@ struct SoraApp: App { Logger.shared.log("Trakt token exchange failed", type: "Error") } } - + default: Logger.shared.log("Unknown authentication service", type: "Error") } diff --git a/Sora/Utils/DownloadManager/DownloadManager.swift b/Sora/Utils/DownloadManager/DownloadManager.swift index 9c3d90d..c533b59 100644 --- a/Sora/Utils/DownloadManager/DownloadManager.swift +++ b/Sora/Utils/DownloadManager/DownloadManager.swift @@ -7,23 +7,54 @@ import SwiftUI import AVKit +import Foundation import AVFoundation +struct DownloadedAsset: Identifiable, Codable { + let id: UUID + var name: String + let downloadDate: Date + let originalURL: URL + let localURL: URL + var fileSize: Int64? + let module: ScrapingModule + + init(id: UUID = UUID(), name: String, downloadDate: Date, originalURL: URL, localURL: URL, module: ScrapingModule) { + self.id = id + self.name = name + self.downloadDate = downloadDate + self.originalURL = originalURL + self.localURL = localURL + self.module = module + self.fileSize = getFileSize() + } + + func getFileSize() -> Int64? { + do { + let values = try localURL.resourceValues(forKeys: [.fileSizeKey]) + return Int64(values.fileSize ?? 0) + } catch { + return nil + } + } +} + class DownloadManager: NSObject, ObservableObject { - @Published var activeDownloads: [(URL, Double)] = [] - @Published var localPlaybackURL: URL? + @Published var activeDownloads: [ActiveDownload] = [] + @Published var savedAssets: [DownloadedAsset] = [] private var assetDownloadURLSession: AVAssetDownloadURLSession! - private var activeDownloadTasks: [URLSessionTask: URL] = [:] + private var activeDownloadTasks: [URLSessionTask: (URL, ScrapingModule)] = [:] override init() { super.init() initializeDownloadSession() - loadLocalContent() + loadSavedAssets() + reconcileFileSystemAssets() } private func initializeDownloadSession() { - let configuration = URLSessionConfiguration.background(withIdentifier: "hls-downloader") + let configuration = URLSessionConfiguration.background(withIdentifier: "downloader-\(UUID().uuidString)") assetDownloadURLSession = AVAssetDownloadURLSession( configuration: configuration, assetDownloadDelegate: self, @@ -31,65 +62,170 @@ class DownloadManager: NSObject, ObservableObject { ) } - func downloadAsset(from url: URL) { - let asset = AVURLAsset(url: url) + func downloadAsset(from url: URL, module: ScrapingModule, headers: [String: String]? = nil) { + var urlRequest = URLRequest(url: url) + + + if let headers = headers { + for (key, value) in headers { + urlRequest.addValue(value, forHTTPHeaderField: key) + } + } else if headers == nil { + urlRequest.addValue(module.metadata.baseUrl, forHTTPHeaderField: "Origin") + urlRequest.addValue(module.metadata.baseUrl, forHTTPHeaderField: "Referer") + } + + urlRequest.addValue("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36", forHTTPHeaderField: "User-Agent") + + let asset = AVURLAsset(url: urlRequest.url!, options: ["AVURLAssetHTTPHeaderFieldsKey": urlRequest.allHTTPHeaderFields ?? [:]]) + let task = assetDownloadURLSession.makeAssetDownloadTask( asset: asset, - assetTitle: "Offline Video", + assetTitle: url.lastPathComponent, assetArtworkData: nil, - options: nil + options: [AVAssetDownloadTaskMinimumRequiredMediaBitrateKey: 2_000_000] ) - + + let download = ActiveDownload( + id: UUID(), + originalURL: url, + progress: 0, + task: task! + ) + + activeDownloads.append(download) + activeDownloadTasks[task!] = (url, module) task?.resume() - activeDownloadTasks[task!] = url } - private func loadLocalContent() { + func deleteAsset(_ asset: DownloadedAsset) { + do { + try FileManager.default.removeItem(at: asset.localURL) + savedAssets.removeAll { $0.id == asset.id } + saveAssets() + } catch { + Logger.shared.log("Error deleting asset: \(error)") + } + } + + func renameAsset(_ asset: DownloadedAsset, newName: String) { + guard let index = savedAssets.firstIndex(where: { $0.id == asset.id }) else { return } + savedAssets[index].name = newName + saveAssets() + } + + private func saveAssets() { + do { + let data = try JSONEncoder().encode(savedAssets) + UserDefaults.standard.set(data, forKey: "savedAssets") + } catch { + Logger.shared.log("Error saving assets: \(error)") + } + } + + private func loadSavedAssets() { + guard let data = UserDefaults.standard.data(forKey: "savedAssets") else { return } + do { + savedAssets = try JSONDecoder().decode([DownloadedAsset].self, from: data) + } catch { + Logger.shared.log("Error loading saved assets: \(error)") + } + } + + private func reconcileFileSystemAssets() { guard let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return } do { - let contents = try FileManager.default.contentsOfDirectory( + let fileURLs = try FileManager.default.contentsOfDirectory( at: documents, - includingPropertiesForKeys: nil, + includingPropertiesForKeys: [.creationDateKey, .fileSizeKey], options: .skipsHiddenFiles ) - - if let localURL = contents.first(where: { $0.pathExtension == "movpkg" }) { - localPlaybackURL = localURL - } } catch { - print("Error loading local content: \(error)") + Logger.shared.log("Error reconciling files: \(error)") } } } extension DownloadManager: AVAssetDownloadDelegate { func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) { - activeDownloadTasks.removeValue(forKey: assetDownloadTask) - localPlaybackURL = location + guard let (originalURL, module) = activeDownloadTasks[assetDownloadTask] else { return } + + let newAsset = DownloadedAsset( + name: originalURL.lastPathComponent, + downloadDate: Date(), + originalURL: originalURL, + localURL: location, + module: module + ) + + savedAssets.append(newAsset) + saveAssets() + cleanupDownloadTask(assetDownloadTask) } func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { guard let error = error else { return } - print("Download error: \(error.localizedDescription)") - activeDownloadTasks.removeValue(forKey: task) + Logger.shared.log("Download error: \(error.localizedDescription)") + cleanupDownloadTask(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 (originalURL, _) = activeDownloadTasks[assetDownloadTask], + let downloadIndex = activeDownloads.firstIndex(where: { $0.originalURL == originalURL }) else { return } - 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 { - activeDownloads.append((url, progress)) + activeDownloads[downloadIndex].progress = progress + } + + private func cleanupDownloadTask(_ task: URLSessionTask) { + activeDownloadTasks.removeValue(forKey: task) + activeDownloads.removeAll { $0.task == task } + } +} + +struct DownloadProgressView: View { + let download: ActiveDownload + + var body: some View { + VStack(alignment: .leading) { + Text(download.originalURL.lastPathComponent) + .font(.subheadline) + ProgressView(value: download.progress) + .progressViewStyle(LinearProgressViewStyle()) + Text("\(Int(download.progress * 100))%") + .font(.caption) } } } + +struct AssetRowView: View { + let asset: DownloadedAsset + + var body: some View { + VStack(alignment: .leading) { + Text(asset.name) + .font(.headline) + Text("\(asset.fileSize ?? 0) bytes • \(asset.downloadDate.formatted())") + .font(.caption) + .foregroundColor(.secondary) + } + } +} + +struct ActiveDownload: Identifiable { + let id: UUID + let originalURL: URL + var progress: Double + let task: URLSessionTask +} + +extension URL { + static func isValidHLSURL(string: String) -> Bool { + guard let url = URL(string: string), url.pathExtension == "m3u8" else { return false } + return true + } +} diff --git a/Sora/Utils/Extensions/URLSession.swift b/Sora/Utils/Extensions/URLSession.swift index efa4fcb..78b8834 100644 --- a/Sora/Utils/Extensions/URLSession.swift +++ b/Sora/Utils/Extensions/URLSession.swift @@ -6,22 +6,22 @@ // 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) { - completionHandler(request) // Allow Redirect + completionHandler(request) } else { - completionHandler(nil) // Block Redirect + completionHandler(nil) } } @@ -63,7 +63,7 @@ extension URLSession { configuration.httpAdditionalHeaders = ["User-Agent": randomUserAgent] return URLSession(configuration: configuration) }() - // return url session that redirects based on input + static func fetchData(allowRedirects:Bool) -> URLSession { let delegate = FetchDelegate(allowRedirects:allowRedirects) @@ -72,4 +72,3 @@ extension URLSession { return URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil) } } - diff --git a/Sora/Utils/JSLoader/JSController-Streams.swift b/Sora/Utils/JSLoader/JSController-Streams.swift index 2f29a05..d104474 100644 --- a/Sora/Utils/JSLoader/JSController-Streams.swift +++ b/Sora/Utils/JSLoader/JSController-Streams.swift @@ -36,7 +36,6 @@ extension JSController { if let data = resultString.data(using: .utf8) { do { if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { - print("JSON DATA IS \(json) 2") var streamUrls: [String]? = nil var subtitleUrls: [String]? = nil var streamUrlsAndHeaders : [[String:Any]]? = nil @@ -117,7 +116,6 @@ extension JSController { let data = jsonString.data(using: .utf8) { do { if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { - print("JSON object is \(json) 1") var streamUrls: [String]? = nil var subtitleUrls: [String]? = nil var streamUrlsAndHeaders : [[String:Any]]? = nil @@ -227,7 +225,6 @@ extension JSController { let data = jsonString.data(using: .utf8) { do { if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { - print("JSON object is \(json) 3 ") var streamUrls: [String]? = nil var subtitleUrls: [String]? = nil var streamUrlsAndHeaders : [[String:Any]]? = nil diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 4358ff6..5db5a65 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -301,9 +301,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele } #if os(iOS) && !targetEnvironment(macCatalyst) - if #available(iOS 16.0, *) { - playerViewController.allowsVideoFrameAnalysis = false - } #endif if let url = subtitlesURL, !url.isEmpty { diff --git a/Sora/Views/DownloadView.swift b/Sora/Views/DownloadView.swift index d98a28a..90df7db 100644 --- a/Sora/Views/DownloadView.swift +++ b/Sora/Views/DownloadView.swift @@ -5,43 +5,206 @@ // Created by Francesco on 29/04/25. // -import SwiftUI import AVKit +import SwiftUI + +struct EmptyStateView: View { + let title: String + let systemImage: String + let description: String + + var body: some View { + VStack(spacing: 16) { + Image(systemName: systemImage) + .font(.system(size: 50)) + .foregroundColor(.secondary) + + Text(title) + .font(.title2) + .fontWeight(.medium) + + Text(description) + .font(.subheadline) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + } + .padding() + .frame(maxWidth: .infinity, maxHeight: .infinity) + } +} 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" + @EnvironmentObject private var downloadManager: DownloadManager + @State private var selectedAsset: DownloadedAsset? + @State private var showingDeleteAlert = false + @State private var showingRenameAlert = false + @State private var renameText = "" + @State private var selectedSegment = 0 + @State private var player: AVPlayer? + + @AppStorage("defaultPlayer") private var defaultPlayer: String = "Default" + + enum Tab: String, CaseIterable { + case active = "Active" + case completed = "Completed" + } 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 - VStack(alignment: .leading) { - Text(url.absoluteString) - ProgressView(value: progress) - .progressViewStyle(LinearProgressViewStyle()) + Picker("Downloads", selection: $selectedSegment) { + ForEach(0.. String { + let formatter = ByteCountFormatter() + formatter.allowedUnits = [.useKB, .useMB, .useGB] + formatter.countStyle = .file + return formatter.string(fromByteCount: size) + } + + private func formatDate(_ date: Date) -> String { + let formatter = RelativeDateTimeFormatter() + formatter.unitsStyle = .short + return formatter.localizedString(for: date, relativeTo: Date()) + } } diff --git a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift index bf41b8b..5a6c557 100644 --- a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift +++ b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift @@ -20,6 +20,7 @@ struct EpisodeCell: View { let episodeID: Int let progress: Double let itemID: Int + let module: ScrapingModule let onTap: (String) -> Void let onMarkAllPrevious: () -> Void @@ -28,6 +29,11 @@ struct EpisodeCell: View { @State private var episodeImageUrl: String = "" @State private var isLoading: Bool = true @State private var currentProgress: Double = 0.0 + @State private var isFetchingEpisode: Bool = false + + @StateObject private var jsController = JSController() + @EnvironmentObject private var moduleManager: ModuleManager + @EnvironmentObject private var downloadManager: DownloadManager @Environment(\.colorScheme) private var colorScheme @AppStorage("selectedAppearance") private var selectedAppearance: Appearance = .system @@ -35,17 +41,19 @@ struct EpisodeCell: View { var defaultBannerImage: String { let isLightMode = selectedAppearance == .light || (selectedAppearance == .system && colorScheme == .light) return isLightMode - ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/dev/assets/banner1.png" - : "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/dev/assets/banner2.png" + ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/dev/assets/banner1.png" + : "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) { + itemID: Int, module: ScrapingModule, onTap: @escaping (String) -> Void, + onMarkAllPrevious: @escaping () -> Void) { self.episodeIndex = episodeIndex self.episode = episode self.episodeID = episodeID self.progress = progress self.itemID = itemID + self.module = module self.onTap = onTap self.onMarkAllPrevious = onMarkAllPrevious } @@ -99,6 +107,10 @@ struct EpisodeCell: View { Label("Mark All Previous Watched", systemImage: "checkmark.circle.fill") } } + + Button(action: downloadEpisode) { + Label("Download Episode", systemImage: "arrow.down.circle") + } } .onAppear { updateProgress() @@ -187,4 +199,70 @@ struct EpisodeCell: View { } }.resume() } + + private func downloadEpisode() { + isFetchingEpisode = true + + Task { + do { + let jsContent = try moduleManager.getModuleContent(module) + jsController.loadScript(jsContent) + + if module.metadata.asyncJS == true { + jsController.fetchStreamUrlJS(episodeUrl: episode, softsub: module.metadata.softsub == true, module: module) { result in + if let sources = result.sources, !sources.isEmpty { + let streamUrl = sources[0]["streamUrl"] as? String ?? "" + let headers = sources[0]["headers"] as? [String: String] ?? [:] + self.startDownload(url: streamUrl, headers: headers) + } + else if let streams = result.streams, !streams.isEmpty { + self.startDownload(url: streams[0]) + } + DispatchQueue.main.async { + self.isFetchingEpisode = false + } + } + } else { + jsController.fetchStreamUrl(episodeUrl: episode, softsub: module.metadata.softsub == true, module: module) { result in + if let sources = result.sources, !sources.isEmpty { + let streamUrl = sources[0]["streamUrl"] as? String ?? "" + let headers = sources[0]["headers"] as? [String: String] ?? [:] + self.startDownload(url: streamUrl, headers: headers) + } + else if let streams = result.streams, !streams.isEmpty { + self.startDownload(url: streams[0]) + } + DispatchQueue.main.async { + self.isFetchingEpisode = false + } + } + } + } catch { + Logger.shared.log("Error starting download: \(error)", type: "Error") + DispatchQueue.main.async { + self.isFetchingEpisode = false + } + } + } + } + + private func startDownload(url: String, headers: [String: String]? = nil) { + guard let streamUrl = URL(string: url) else { + Logger.shared.log("Invalid stream URL for download", type: "Error") + return + } + + downloadManager.downloadAsset( + from: streamUrl, + module: module, + headers: headers + ) + + DropManager.shared.showDrop( + title: "Download Started", + subtitle: "Episode \(episodeID + 1)", + duration: 1.0, + icon: UIImage(systemName: "arrow.down.circle.fill") + ) + } } diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index ae1485d..cf8bc54 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -300,6 +300,7 @@ struct MediaInfoView: View { episodeID: ep.number - 1, progress: progress, itemID: itemID ?? 0, + module: module, onTap: { imageUrl in if !isFetchingEpisode { selectedEpisodeNumber = ep.number @@ -350,6 +351,7 @@ struct MediaInfoView: View { episodeID: ep.number - 1, progress: progress, itemID: itemID ?? 0, + module: module, onTap: { imageUrl in if !isFetchingEpisode { selectedEpisodeNumber = ep.number diff --git a/Sora/Views/SearchView.swift b/Sora/Views/SearchView.swift index 856e2a4..0f1a84a 100644 --- a/Sora/Views/SearchView.swift +++ b/Sora/Views/SearchView.swift @@ -38,14 +38,6 @@ struct SearchView: View { return moduleManager.modules.first { $0.id.uuidString == id } } - private var loadingMessages: [String] = [ - "Searching the depths...", - "Looking for results...", - "Fetching data...", - "Please wait...", - "Almost there..." - ] - private var columnsCount: Int { if UIDevice.current.userInterfaceIdiom == .pad { let isLandscape = UIScreen.main.bounds.width > UIScreen.main.bounds.height diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift index 7bce329..f62b368 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift @@ -74,27 +74,6 @@ struct SettingsViewPlayer: View { } } - Section(header: Text("Progress bar Marker Color")) { - ColorPicker("Segments Color", selection: Binding( - get: { - if let data = UserDefaults.standard.data(forKey: "segmentsColorData"), - let uiColor = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? UIColor { - return Color(uiColor) - } - return .yellow - }, - set: { newColor in - let uiColor = UIColor(newColor) - if let data = try? NSKeyedArchiver.archivedData( - withRootObject: uiColor, - requiringSecureCoding: false - ) { - UserDefaults.standard.set(data, forKey: "segmentsColorData") - } - } - )) - } - Section(header: Text("Skip Settings"), footer : Text("Double tapping the screen on it's sides will skip with the short tap setting.")) { HStack { Text("Tap Skip:")