im done
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run

This commit is contained in:
cranci1 2025-01-26 16:41:56 +01:00
parent 54a953a2ea
commit 75fb10a673
5 changed files with 168 additions and 147 deletions

View file

@ -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 */,

View 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)
}
}

View file

@ -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)
}
}

View file

@ -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")

View file

@ -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)
}
}