Media Info view

This commit is contained in:
cranci1 2025-01-05 17:21:43 +01:00
parent d519fc6110
commit ee75542b2d
7 changed files with 131 additions and 12 deletions

View file

@ -12,6 +12,7 @@
1329D5CB2D298199008AEDA2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1329D5CA2D298199008AEDA2 /* Assets.xcassets */; };
1329D5CE2D298199008AEDA2 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1329D5CD2D298199008AEDA2 /* Preview Assets.xcassets */; };
133D7C1B2D2ADC430075467E /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C1A2D2ADC430075467E /* URLSession.swift */; };
133D7C1E2D2ADF110075467E /* MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C1D2D2ADF110075467E /* MediaInfoView.swift */; };
13AEE6192D2A75110096D953 /* Modules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE6182D2A75110096D953 /* Modules.swift */; };
13AEE61B2D2A78050096D953 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE61A2D2A78050096D953 /* SettingsView.swift */; };
13AEE61D2D2A78160096D953 /* JSController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE61C2D2A78160096D953 /* JSController.swift */; };
@ -30,6 +31,7 @@
1329D5CD2D298199008AEDA2 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
1329D5DA2D29821B008AEDA2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
133D7C1A2D2ADC430075467E /* URLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = "<group>"; };
133D7C1D2D2ADF110075467E /* MediaInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInfoView.swift; sourceTree = "<group>"; };
13AEE6182D2A75110096D953 /* Modules.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modules.swift; sourceTree = "<group>"; };
13AEE61A2D2A78050096D953 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
13AEE61C2D2A78160096D953 /* JSController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSController.swift; sourceTree = "<group>"; };
@ -107,9 +109,18 @@
path = Extensions;
sourceTree = "<group>";
};
133D7C1C2D2ADF060075467E /* MediaInfoView */ = {
isa = PBXGroup;
children = (
133D7C1D2D2ADF110075467E /* MediaInfoView.swift */,
);
path = MediaInfoView;
sourceTree = "<group>";
};
13AEE61E2D2AAD1E0096D953 /* Views */ = {
isa = PBXGroup;
children = (
133D7C1C2D2ADF060075467E /* MediaInfoView */,
13AEE62A2D2ABCB40096D953 /* SettingsView */,
13AEE6242D2AB1730096D953 /* HomeView.swift */,
13AEE6262D2AB1990096D953 /* LibraryView.swift */,
@ -237,6 +248,7 @@
13AEE6252D2AB1730096D953 /* HomeView.swift in Sources */,
13AEE61B2D2A78050096D953 /* SettingsView.swift in Sources */,
1329D5C72D298198008AEDA2 /* Sora_JSApp.swift in Sources */,
133D7C1E2D2ADF110075467E /* MediaInfoView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View file

@ -15,17 +15,14 @@ struct ContentView: View {
.tabItem {
Label("Home", systemImage: "house")
}
LibraryView()
.tabItem {
Label("Library", systemImage: "books.vertical")
}
SearchView()
.tabItem {
Label("Search", systemImage: "magnifyingglass")
}
SettingsView()
.tabItem {
Label("Settings", systemImage: "gear")

View file

@ -28,7 +28,7 @@ class JSController: ObservableObject {
context.evaluateScript(script)
}
func searchContent(keyword: String, module: ScrapingModule, completion: @escaping ([MediaItem]) -> Void) {
func fetchSearchResults(keyword: String, module: ScrapingModule, completion: @escaping ([MediaItem]) -> Void) {
let searchUrl = module.metadata.searchBaseUrl.replacingOccurrences(of: "%s", with: keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")
guard let url = URL(string: searchUrl) else {
@ -51,17 +51,63 @@ class JSController: ObservableObject {
return
}
if let parseFunction = self.context.objectForKeyedSubscript("parseHTML"),
if let parseFunction = self.context.objectForKeyedSubscript("searchResults"),
let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] {
let mediaItems = results.map { item in
MediaItem(
title: item["title"] ?? "",
imageUrl: item["image"] ?? ""
imageUrl: item["image"] ?? "",
href: item["href"] ?? ""
)
}
DispatchQueue.main.async {
completion(mediaItems)
}
} else {
print("Failed to parse results")
DispatchQueue.main.async { completion([]) }
}
}.resume()
}
func fetchInfoContent(href: String, module: ScrapingModule, completion: @escaping ([MediaItem]) -> Void) {
var baseUrl = module.metadata.baseUrl
if !baseUrl.hasSuffix("/") && !href.hasPrefix("/") {
baseUrl += "/"
}
baseUrl += href
guard let url = URL(string: baseUrl) else {
completion([])
return
}
URLSession.custom.dataTask(with: url) { [weak self] data, response, error in
guard let self = self else { return }
if let error = error {
print("Network error: \(error)")
DispatchQueue.main.async { completion([]) }
return
}
guard let data = data, let html = String(data: data, encoding: .utf8) else {
print("Failed to decode HTML")
DispatchQueue.main.async { completion([]) }
return
}
if let parseFunction = self.context.objectForKeyedSubscript("fetchInfo"),
let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] {
let mediaItems = results.map { item in
MediaItem(
title: item["title"] ?? "",
imageUrl: item["image"] ?? "",
href: item["href"] ?? ""
)
}
DispatchQueue.main.async {
completion(mediaItems)
print(mediaItems)
}
} else {
print("Failed to parse results")

View file

@ -42,6 +42,7 @@ struct ScrapingModule: Codable, Identifiable, Hashable {
class ModuleManager: ObservableObject {
@Published var modules: [ScrapingModule] = []
private let fileManager = FileManager.default
private let modulesFileName = "modules.json"

View file

@ -0,0 +1,53 @@
//
// MediaInfoView.swift
// Sora-JS
//
// Created by Francesco on 05/01/25.
//
import SwiftUI
import Kingfisher
struct MediaInfoView: View {
let title: String
let imageUrl: String
let href: String
let module: ScrapingModule
var body: some View {
VStack {
KFImage(URL(string: imageUrl))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: .infinity)
.padding()
Text(title)
.font(.largeTitle)
.padding()
Button(action: {
var finalHref = href
if !href.starts(with: "http") {
var baseUrl = module.metadata.baseUrl
if !baseUrl.hasSuffix("/") && !href.hasPrefix("/") {
baseUrl += "/"
}
finalHref = baseUrl + href
}
if let url = URL(string: finalHref) {
UIApplication.shared.open(url)
}
}) {
Text("Open Link")
.font(.headline)
.foregroundColor(.blue)
}
.padding()
Spacer()
}
.navigationTitle("Media Info")
.navigationBarTitleDisplayMode(.inline)
}
}

View file

@ -12,15 +12,18 @@ struct MediaItem: Identifiable {
let id = UUID()
let title: String
let imageUrl: String
let href: String
}
struct SearchView: View {
@AppStorage("selectedModuleId") private var selectedModuleId: String?
@StateObject private var jsController = JSController()
@EnvironmentObject var moduleManager: ModuleManager
@State private var searchText = ""
@State private var mediaItems: [MediaItem] = []
@State private var selectedMediaItem: MediaItem?
@State private var isSearching = false
@AppStorage("selectedModuleId") private var selectedModuleId: String?
@State private var searchText = ""
private var selectedModule: ScrapingModule? {
guard let id = selectedModuleId else { return nil }
@ -69,6 +72,9 @@ struct SearchView: View {
.padding([.leading, .bottom], 8)
.lineLimit(1)
}
.onTapGesture {
selectedMediaItem = item
}
}
}
.padding()
@ -111,6 +117,9 @@ struct SearchView: View {
}
}
}
.sheet(item: $selectedMediaItem) { item in
MediaInfoView(title: item.title, imageUrl: item.imageUrl, href: item.href, module: selectedModule!)
}
}
.navigationViewStyle(StackNavigationViewStyle())
.onChange(of: selectedModuleId) { _ in
@ -132,7 +141,7 @@ struct SearchView: View {
do {
let jsContent = try moduleManager.getModuleContent(module)
jsController.loadScript(jsContent)
jsController.searchContent(keyword: searchText, module: module) { items in
jsController.fetchSearchResults(keyword: searchText, module: module) { items in
mediaItems = items
isSearching = false
}

View file

@ -9,10 +9,11 @@ import SwiftUI
import Kingfisher
struct SettingsViewModule: View {
@EnvironmentObject var moduleManager: ModuleManager
@AppStorage("selectedModuleId") private var selectedModuleId: String?
@State private var isLoading = false
@EnvironmentObject var moduleManager: ModuleManager
@State private var errorMessage: String?
@State private var isLoading = false
var body: some View {
VStack {