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

View file

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

View file

@ -28,7 +28,7 @@ class JSController: ObservableObject {
context.evaluateScript(script) 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) ?? "") let searchUrl = module.metadata.searchBaseUrl.replacingOccurrences(of: "%s", with: keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")
guard let url = URL(string: searchUrl) else { guard let url = URL(string: searchUrl) else {
@ -51,17 +51,63 @@ class JSController: ObservableObject {
return 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 results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] {
let mediaItems = results.map { item in let mediaItems = results.map { item in
MediaItem( MediaItem(
title: item["title"] ?? "", 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 { DispatchQueue.main.async {
completion(mediaItems) completion(mediaItems)
print(mediaItems)
} }
} else { } else {
print("Failed to parse results") print("Failed to parse results")

View file

@ -42,6 +42,7 @@ struct ScrapingModule: Codable, Identifiable, Hashable {
class ModuleManager: ObservableObject { class ModuleManager: ObservableObject {
@Published var modules: [ScrapingModule] = [] @Published var modules: [ScrapingModule] = []
private let fileManager = FileManager.default private let fileManager = FileManager.default
private let modulesFileName = "modules.json" 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 id = UUID()
let title: String let title: String
let imageUrl: String let imageUrl: String
let href: String
} }
struct SearchView: View { struct SearchView: View {
@AppStorage("selectedModuleId") private var selectedModuleId: String?
@StateObject private var jsController = JSController() @StateObject private var jsController = JSController()
@EnvironmentObject var moduleManager: ModuleManager @EnvironmentObject var moduleManager: ModuleManager
@State private var searchText = ""
@State private var mediaItems: [MediaItem] = [] @State private var mediaItems: [MediaItem] = []
@State private var selectedMediaItem: MediaItem?
@State private var isSearching = false @State private var isSearching = false
@AppStorage("selectedModuleId") private var selectedModuleId: String? @State private var searchText = ""
private var selectedModule: ScrapingModule? { private var selectedModule: ScrapingModule? {
guard let id = selectedModuleId else { return nil } guard let id = selectedModuleId else { return nil }
@ -69,6 +72,9 @@ struct SearchView: View {
.padding([.leading, .bottom], 8) .padding([.leading, .bottom], 8)
.lineLimit(1) .lineLimit(1)
} }
.onTapGesture {
selectedMediaItem = item
}
} }
} }
.padding() .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()) .navigationViewStyle(StackNavigationViewStyle())
.onChange(of: selectedModuleId) { _ in .onChange(of: selectedModuleId) { _ in
@ -132,7 +141,7 @@ struct SearchView: View {
do { do {
let jsContent = try moduleManager.getModuleContent(module) let jsContent = try moduleManager.getModuleContent(module)
jsController.loadScript(jsContent) jsController.loadScript(jsContent)
jsController.searchContent(keyword: searchText, module: module) { items in jsController.fetchSearchResults(keyword: searchText, module: module) { items in
mediaItems = items mediaItems = items
isSearching = false isSearching = false
} }

View file

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