From 75fb10a673e04c4aa92ae456edce371216e10503 Mon Sep 17 00:00:00 2001 From: cranci1 <100066266+cranci1@users.noreply.github.com> Date: Sun, 26 Jan 2025 16:41:56 +0100 Subject: [PATCH] im done --- Sora.xcodeproj/project.pbxproj | 4 + Sora/Utils/Modules/ModuleManager.swift | 90 ++++++++++++ Sora/Utils/Modules/Modules.swift | 81 ----------- Sora/Views/SearchView.swift | 3 + .../SettingsSubViews/SettingsViewModule.swift | 137 +++++++++--------- 5 files changed, 168 insertions(+), 147 deletions(-) create mode 100644 Sora/Utils/Modules/ModuleManager.swift diff --git a/Sora.xcodeproj/project.pbxproj b/Sora.xcodeproj/project.pbxproj index 2a68f2f..f73138d 100644 --- a/Sora.xcodeproj/project.pbxproj +++ b/Sora.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 133F55BB2D33B55100E08EEA /* LibraryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133F55BA2D33B55100E08EEA /* LibraryManager.swift */; }; 138AA1B82D2D66FD0021F9DF /* EpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */; }; 138AA1B92D2D66FD0021F9DF /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */; }; + 139935662D468C450065CEFF /* ModuleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 139935652D468C450065CEFF /* ModuleManager.swift */; }; 1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */; }; 1399FAD62D3AB3DB00E97C31 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1399FAD52D3AB3DB00E97C31 /* Logger.swift */; }; 13D842522D4523B800EBBFA6 /* Drops in Frameworks */ = {isa = PBXBuildFile; productRef = 13D842512D4523B800EBBFA6 /* Drops */; }; @@ -56,6 +57,7 @@ 133F55BA2D33B55100E08EEA /* LibraryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryManager.swift; sourceTree = ""; }; 138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeCell.swift; sourceTree = ""; }; 138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = ""; }; + 139935652D468C450065CEFF /* ModuleManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleManager.swift; sourceTree = ""; }; 1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewLogger.swift; sourceTree = ""; }; 1399FAD52D3AB3DB00E97C31 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 13D842542D45267500EBBFA6 /* DropManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropManager.swift; sourceTree = ""; }; @@ -179,6 +181,7 @@ isa = PBXGroup; children = ( 133D7C892D2BE2640075467E /* Modules.swift */, + 139935652D468C450065CEFF /* ModuleManager.swift */, ); path = Modules; sourceTree = ""; @@ -356,6 +359,7 @@ files = ( 13DC0C462D302C7500D0F966 /* VideoPlayer.swift in Sources */, 1399FAD62D3AB3DB00E97C31 /* Logger.swift in Sources */, + 139935662D468C450065CEFF /* ModuleManager.swift in Sources */, 133D7C902D2BE2640075467E /* SettingsView.swift in Sources */, 13EA2BD72D32D97400C1EBD7 /* MusicProgressSlider.swift in Sources */, 1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */, diff --git a/Sora/Utils/Modules/ModuleManager.swift b/Sora/Utils/Modules/ModuleManager.swift new file mode 100644 index 0000000..5c75fc6 --- /dev/null +++ b/Sora/Utils/Modules/ModuleManager.swift @@ -0,0 +1,90 @@ +// +// ModuleManager.swift +// Sora +// +// Created by Francesco on 26/01/25. +// + +import Foundation + +class ModuleManager: ObservableObject { + @Published var modules: [ScrapingModule] = [] + + private let fileManager = FileManager.default + private let modulesFileName = "modules.json" + + init() { + loadModules() + } + + private func getDocumentsDirectory() -> URL { + fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] + } + + private func getModulesFilePath() -> URL { + getDocumentsDirectory().appendingPathComponent(modulesFileName) + } + + func loadModules() { + let url = getModulesFilePath() + guard let data = try? Data(contentsOf: url) else { return } + modules = (try? JSONDecoder().decode([ScrapingModule].self, from: data)) ?? [] + } + + private func saveModules() { + let url = getModulesFilePath() + guard let data = try? JSONEncoder().encode(modules) else { return } + try? data.write(to: url) + } + + func addModule(metadataUrl: String) async throws -> ScrapingModule { + guard let url = URL(string: metadataUrl) else { + throw NSError(domain: "Invalid metadata URL", code: -1) + } + + let (metadataData, _) = try await URLSession.custom.data(from: url) + let metadata = try JSONDecoder().decode(ModuleMetadata.self, from: metadataData) + + guard let scriptUrl = URL(string: metadata.scriptUrl) else { + throw NSError(domain: "Invalid script URL", code: -1) + } + + let (scriptData, _) = try await URLSession.custom.data(from: scriptUrl) + guard let jsContent = String(data: scriptData, encoding: .utf8) else { + throw NSError(domain: "Invalid script encoding", code: -1) + } + + let fileName = "\(UUID().uuidString).js" + let localUrl = getDocumentsDirectory().appendingPathComponent(fileName) + try jsContent.write(to: localUrl, atomically: true, encoding: .utf8) + + let module = ScrapingModule( + metadata: metadata, + localPath: fileName, + metadataUrl: metadataUrl + ) + + DispatchQueue.main.async { + self.modules.append(module) + self.saveModules() + Logger.shared.log("Added module: \(module.metadata.sourceName)") + } + + return module + } + + func deleteModule(_ module: ScrapingModule) { + let localUrl = getDocumentsDirectory().appendingPathComponent(module.localPath) + try? fileManager.removeItem(at: localUrl) + + modules.removeAll { $0.id == module.id } + saveModules() + Logger.shared.log("Deleted module: \(module.metadata.sourceName)") + } + + func getModuleContent(_ module: ScrapingModule) throws -> String { + let localUrl = getDocumentsDirectory().appendingPathComponent(module.localPath) + return try String(contentsOf: localUrl, encoding: .utf8) + } +} + diff --git a/Sora/Utils/Modules/Modules.swift b/Sora/Utils/Modules/Modules.swift index cbf96ea..301a8e1 100644 --- a/Sora/Utils/Modules/Modules.swift +++ b/Sora/Utils/Modules/Modules.swift @@ -42,84 +42,3 @@ struct ScrapingModule: Codable, Identifiable, Hashable { lhs.id == rhs.id } } - -class ModuleManager: ObservableObject { - @Published var modules: [ScrapingModule] = [] - - private let fileManager = FileManager.default - private let modulesFileName = "modules.json" - - init() { - loadModules() - } - - private func getDocumentsDirectory() -> URL { - fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] - } - - private func getModulesFilePath() -> URL { - getDocumentsDirectory().appendingPathComponent(modulesFileName) - } - - func loadModules() { - let url = getModulesFilePath() - guard let data = try? Data(contentsOf: url) else { return } - modules = (try? JSONDecoder().decode([ScrapingModule].self, from: data)) ?? [] - } - - private func saveModules() { - let url = getModulesFilePath() - guard let data = try? JSONEncoder().encode(modules) else { return } - try? data.write(to: url) - } - - func addModule(metadataUrl: String) async throws -> ScrapingModule { - guard let url = URL(string: metadataUrl) else { - throw NSError(domain: "Invalid metadata URL", code: -1) - } - - let (metadataData, _) = try await URLSession.custom.data(from: url) - let metadata = try JSONDecoder().decode(ModuleMetadata.self, from: metadataData) - - guard let scriptUrl = URL(string: metadata.scriptUrl) else { - throw NSError(domain: "Invalid script URL", code: -1) - } - - let (scriptData, _) = try await URLSession.custom.data(from: scriptUrl) - guard let jsContent = String(data: scriptData, encoding: .utf8) else { - throw NSError(domain: "Invalid script encoding", code: -1) - } - - let fileName = "\(UUID().uuidString).js" - let localUrl = getDocumentsDirectory().appendingPathComponent(fileName) - try jsContent.write(to: localUrl, atomically: true, encoding: .utf8) - - let module = ScrapingModule( - metadata: metadata, - localPath: fileName, - metadataUrl: metadataUrl - ) - - DispatchQueue.main.async { - self.modules.append(module) - self.saveModules() - Logger.shared.log("Added module: \(module.metadata.sourceName)") - } - - return module - } - - func deleteModule(_ module: ScrapingModule) { - let localUrl = getDocumentsDirectory().appendingPathComponent(module.localPath) - try? fileManager.removeItem(at: localUrl) - - modules.removeAll { $0.id == module.id } - saveModules() - Logger.shared.log("Deleted module: \(module.metadata.sourceName)") - } - - func getModuleContent(_ module: ScrapingModule) throws -> String { - let localUrl = getDocumentsDirectory().appendingPathComponent(module.localPath) - return try String(contentsOf: localUrl, encoding: .utf8) - } -} diff --git a/Sora/Views/SearchView.swift b/Sora/Views/SearchView.swift index 8ccedb4..535fe23 100644 --- a/Sora/Views/SearchView.swift +++ b/Sora/Views/SearchView.swift @@ -41,6 +41,9 @@ struct SearchView: View { if selectedModule == nil { VStack(spacing: 8) { + Image(systemName: "questionmark.app") + .font(.largeTitle) + .foregroundColor(.secondary) Text("No Module Selected") .font(.headline) Text("Please select a module from settings") diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewModule.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewModule.swift index 15fc62f..e21d053 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewModule.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewModule.swift @@ -5,7 +5,6 @@ // Created by Francesco on 05/01/25. // -import Drops import SwiftUI import Kingfisher @@ -15,73 +14,89 @@ struct SettingsViewModule: View { @State private var errorMessage: String? @State private var isLoading = false - @State private var addedModuleUrl: String? var body: some View { VStack { Form { - ForEach(moduleManager.modules) { module in - HStack { - KFImage(URL(string: module.metadata.iconUrl)) - .resizable() - .frame(width: 50, height: 50) - .clipShape(Circle()) - .padding(.trailing, 10) - - VStack(alignment: .leading) { - HStack(alignment: .bottom, spacing: 4) { - Text(module.metadata.sourceName) - .font(.headline) - .foregroundColor(.primary) - Text("v\(module.metadata.version)") + if moduleManager.modules.isEmpty { + VStack(spacing: 8) { + Image(systemName: "plus.app") + .font(.largeTitle) + .foregroundColor(.secondary) + Text("No Modules") + .font(.headline) + Text("Click the plus button to add a module!") + .font(.caption) + .foregroundColor(.secondary) + .frame(maxWidth: .infinity) + } + .padding() + .frame(maxWidth: .infinity) + } + else { + ForEach(moduleManager.modules) { module in + HStack { + KFImage(URL(string: module.metadata.iconUrl)) + .resizable() + .frame(width: 50, height: 50) + .clipShape(Circle()) + .padding(.trailing, 10) + + VStack(alignment: .leading) { + HStack(alignment: .bottom, spacing: 4) { + Text(module.metadata.sourceName) + .font(.headline) + .foregroundColor(.primary) + Text("v\(module.metadata.version)") + .font(.subheadline) + .foregroundColor(.secondary) + } + Text("Author: \(module.metadata.author)") + .font(.subheadline) + .foregroundColor(.secondary) + Text("Language: \(module.metadata.language)") .font(.subheadline) .foregroundColor(.secondary) } - Text("Author: \(module.metadata.author)") - .font(.subheadline) - .foregroundColor(.secondary) - Text("Language: \(module.metadata.language)") - .font(.subheadline) - .foregroundColor(.secondary) - } - - Spacer() - - if module.id.uuidString == selectedModuleId { - Image(systemName: "checkmark.circle.fill") - .foregroundColor(.accentColor) - .frame(width: 25, height: 25) - } - } - .contentShape(Rectangle()) - .onTapGesture { - selectedModuleId = module.id.uuidString - } - .contextMenu { - Button(action: { - UIPasteboard.general.string = module.metadataUrl - DropManager.shared.showDrop(title: "Copied to Clipboard", subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill")) - }) { - Label("Copy URL", systemImage: "doc.on.doc") - } - Button(role: .destructive) { - if selectedModuleId != module.id.uuidString { - moduleManager.deleteModule(module) - DropManager.shared.showDrop(title: "Module Removed", subtitle: "", duration: 1.0, icon: UIImage(systemName: "trash")) + + Spacer() + + if module.id.uuidString == selectedModuleId { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.accentColor) + .frame(width: 25, height: 25) } - } label: { - Label("Delete", systemImage: "trash") } - .disabled(selectedModuleId == module.id.uuidString) - } - .swipeActions { - if selectedModuleId != module.id.uuidString { + .contentShape(Rectangle()) + .onTapGesture { + selectedModuleId = module.id.uuidString + } + .contextMenu { + Button(action: { + UIPasteboard.general.string = module.metadataUrl + DropManager.shared.showDrop(title: "Copied to Clipboard", subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill")) + }) { + Label("Copy URL", systemImage: "doc.on.doc") + } Button(role: .destructive) { - moduleManager.deleteModule(module) - DropManager.shared.showDrop(title: "Module Removed", subtitle: "", duration: 1.0, icon: UIImage(systemName: "trash")) + if selectedModuleId != module.id.uuidString { + moduleManager.deleteModule(module) + DropManager.shared.showDrop(title: "Module Removed", subtitle: "", duration: 1.0, icon: UIImage(systemName: "trash")) + } } label: { Label("Delete", systemImage: "trash") } + .disabled(selectedModuleId == module.id.uuidString) + } + .swipeActions { + if selectedModuleId != module.id.uuidString { + Button(role: .destructive) { + moduleManager.deleteModule(module) + DropManager.shared.showDrop(title: "Module Removed", subtitle: "", duration: 1.0, icon: UIImage(systemName: "trash")) + } label: { + Label("Delete", systemImage: "trash") + } + } } } } @@ -127,14 +142,13 @@ struct SettingsViewModule: View { private func addModule(from url: String) { isLoading = true errorMessage = nil - addedModuleUrl = url Task { do { _ = try await moduleManager.addModule(metadataUrl: url) DispatchQueue.main.async { isLoading = false - showDrop() + DropManager.shared.showDrop(title: "Module Added", subtitle: "clicking it to select it", duration: 2.0, icon: UIImage(systemName: "app.badge.checkmark")) } } catch { DispatchQueue.main.async { @@ -145,13 +159,4 @@ struct SettingsViewModule: View { } } } - - private func showDrop() { - let aTitle = "Module Added!" - let subtitle = "clicking it to select it" - let duration = 2.0 - let icon = UIImage(systemName: "app.badge.checkmark") - - DropManager.shared.showDrop(title: aTitle, subtitle: subtitle, duration: duration, icon: icon) - } }