mirror of
https://github.com/cranci1/Sora.git
synced 2026-03-11 17:45:37 +00:00
This commit is contained in:
parent
54a953a2ea
commit
75fb10a673
5 changed files with 168 additions and 147 deletions
|
|
@ -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 = "<group>"; };
|
||||
138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeCell.swift; sourceTree = "<group>"; };
|
||||
138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = "<group>"; };
|
||||
139935652D468C450065CEFF /* ModuleManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleManager.swift; sourceTree = "<group>"; };
|
||||
1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewLogger.swift; sourceTree = "<group>"; };
|
||||
1399FAD52D3AB3DB00E97C31 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
13D842542D45267500EBBFA6 /* DropManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropManager.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -179,6 +181,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
133D7C892D2BE2640075467E /* Modules.swift */,
|
||||
139935652D468C450065CEFF /* ModuleManager.swift */,
|
||||
);
|
||||
path = Modules;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -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 */,
|
||||
|
|
|
|||
90
Sora/Utils/Modules/ModuleManager.swift
Normal file
90
Sora/Utils/Modules/ModuleManager.swift
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue