mirror of
https://github.com/cranci1/Sora.git
synced 2026-03-11 17:45:37 +00:00
This commit is contained in:
parent
fdbb3e1edc
commit
0d9ff05bb6
6 changed files with 352 additions and 86 deletions
|
|
@ -21,6 +21,8 @@
|
|||
133D7C932D2BE2640075467E /* Modules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C892D2BE2640075467E /* Modules.swift */; };
|
||||
133D7C942D2BE2640075467E /* JSController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C8B2D2BE2640075467E /* JSController.swift */; };
|
||||
133D7C972D2BE2AF0075467E /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 133D7C962D2BE2AF0075467E /* Kingfisher */; };
|
||||
138AA1B82D2D66FD0021F9DF /* EpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */; };
|
||||
138AA1B92D2D66FD0021F9DF /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
|
@ -38,6 +40,8 @@
|
|||
133D7C872D2BE2640075467E /* URLSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = "<group>"; };
|
||||
133D7C892D2BE2640075467E /* Modules.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Modules.swift; sourceTree = "<group>"; };
|
||||
133D7C8B2D2BE2640075467E /* JSController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSController.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>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
|
@ -105,6 +109,7 @@
|
|||
133D7C7F2D2BE2630075467E /* MediaInfoView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
138AA1B52D2D66EC0021F9DF /* EpisodeCell */,
|
||||
133D7C802D2BE2630075467E /* MediaInfoView.swift */,
|
||||
);
|
||||
path = MediaInfoView;
|
||||
|
|
@ -152,6 +157,15 @@
|
|||
path = Loaders;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
138AA1B52D2D66EC0021F9DF /* EpisodeCell */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */,
|
||||
138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */,
|
||||
);
|
||||
path = EpisodeCell;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
|
@ -233,12 +247,14 @@
|
|||
133D7C702D2BE2500075467E /* ContentView.swift in Sources */,
|
||||
133D7C8F2D2BE2640075467E /* MediaInfoView.swift in Sources */,
|
||||
133D7C8D2D2BE2640075467E /* HomeView.swift in Sources */,
|
||||
138AA1B82D2D66FD0021F9DF /* EpisodeCell.swift in Sources */,
|
||||
133D7C8C2D2BE2640075467E /* SearchView.swift in Sources */,
|
||||
133D7C942D2BE2640075467E /* JSController.swift in Sources */,
|
||||
133D7C922D2BE2640075467E /* URLSession.swift in Sources */,
|
||||
133D7C912D2BE2640075467E /* SettingsViewModule.swift in Sources */,
|
||||
133D7C8E2D2BE2640075467E /* LibraryView.swift in Sources */,
|
||||
133D7C6E2D2BE2500075467E /* SoraApp.swift in Sources */,
|
||||
138AA1B92D2D66FD0021F9DF /* CircularProgressBar.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,10 +8,40 @@
|
|||
import Foundation
|
||||
|
||||
extension URLSession {
|
||||
static let userAgents = [
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.2365.92",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.2277.128",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_3_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14.3; rv:123.0) Gecko/20100101 Firefox/123.0",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14.2; rv:122.0) Gecko/20100101 Firefox/122.0",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0",
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:122.0) Gecko/20100101 Firefox/122.0",
|
||||
"Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.105 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.105 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
|
||||
"Mozilla/5.0 (iPad; CPU OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
|
||||
"Mozilla/5.0 (Android 14; Mobile; rv:123.0) Gecko/123.0 Firefox/123.0",
|
||||
"Mozilla/5.0 (Android 13; Mobile; rv:122.0) Gecko/122.0 Firefox/122.0"
|
||||
]
|
||||
|
||||
static let randomUserAgent: String = {
|
||||
userAgents.randomElement() ?? userAgents[0]
|
||||
}()
|
||||
|
||||
static let custom: URLSession = {
|
||||
let configuration = URLSessionConfiguration.default
|
||||
configuration.httpAdditionalHeaders = [
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
|
||||
"User-Agent": randomUserAgent
|
||||
]
|
||||
return URLSession(configuration: configuration)
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class JSController: ObservableObject {
|
|||
return
|
||||
}
|
||||
|
||||
URLSession.custom.dataTask(with: url) { [weak self] data, response, error in
|
||||
URLSession.custom.dataTask(with: url) { [weak self] data, _, error in
|
||||
guard let self = self else { return }
|
||||
|
||||
if let error = error {
|
||||
|
|
@ -69,4 +69,44 @@ class JSController: ObservableObject {
|
|||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
func fetchDetails(url: String, completion: @escaping ([MediaItem]) -> Void) {
|
||||
guard let url = URL(string: url) else {
|
||||
completion([])
|
||||
return
|
||||
}
|
||||
|
||||
URLSession.custom.dataTask(with: url) { [weak self] data, _, 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("extractDetails"),
|
||||
let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] {
|
||||
let resultItems = results.map { item in
|
||||
MediaItem(
|
||||
description: item["description"] ?? "",
|
||||
aliases: item["aliases"] ?? "",
|
||||
airdate: item["airdate"] ?? ""
|
||||
)
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
completion(resultItems)
|
||||
}
|
||||
} else {
|
||||
print("Failed to parse results")
|
||||
DispatchQueue.main.async { completion([]) }
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// CircularProgressBar.swift
|
||||
// Sora
|
||||
//
|
||||
// Created by Francesco on 18/12/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CircularProgressBar: View {
|
||||
var progress: Double
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Circle()
|
||||
.stroke(lineWidth: 5.0)
|
||||
.opacity(0.3)
|
||||
.foregroundColor(Color.accentColor)
|
||||
|
||||
Circle()
|
||||
.trim(from: 0.0, to: CGFloat(min(progress, 1.0)))
|
||||
.stroke(style: StrokeStyle(lineWidth: 5.0, lineCap: .round, lineJoin: .round))
|
||||
.foregroundColor(Color.accentColor)
|
||||
.rotationEffect(Angle(degrees: 270.0))
|
||||
.animation(.linear, value: progress)
|
||||
|
||||
if progress >= 0.90 {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.system(size: 12))
|
||||
} else {
|
||||
Text(String(format: "%.0f%%", min(progress, 1.0) * 100.0))
|
||||
.font(.system(size: 12))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
119
Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift
Normal file
119
Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
//
|
||||
// EpisodeCell.swift
|
||||
// Sora
|
||||
//
|
||||
// Created by Francesco on 18/12/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct EpisodeCell: View {
|
||||
let episode: String
|
||||
let episodeID: Int
|
||||
let imageUrl: String
|
||||
let progress: Double
|
||||
let itemID: Int
|
||||
|
||||
@State private var episodeTitle: String = ""
|
||||
@State private var episodeImageUrl: String = ""
|
||||
@State private var isLoading: Bool = true
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
ZStack {
|
||||
KFImage(URL(string: episodeImageUrl.isEmpty ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/banner2.png" : episodeImageUrl))
|
||||
.resizable()
|
||||
.aspectRatio(16/9, contentMode: .fill)
|
||||
.frame(width: 100, height: 56)
|
||||
.cornerRadius(8)
|
||||
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle())
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("Episode \(episodeID + 1)")
|
||||
.font(.system(size: 15))
|
||||
if !episodeTitle.isEmpty {
|
||||
Text(episodeTitle)
|
||||
.font(.system(size: 13))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
CircularProgressBar(progress: progress)
|
||||
.frame(width: 40, height: 40)
|
||||
}
|
||||
.onAppear {
|
||||
fetchEpisodeDetails()
|
||||
}
|
||||
}
|
||||
|
||||
func fetchEpisodeDetails() {
|
||||
let cacheKey = "episodeDetails_\(itemID)_\(episodeID)"
|
||||
|
||||
if let cachedData = UserDefaults.standard.data(forKey: cacheKey) {
|
||||
parseEpisodeDetails(data: cachedData)
|
||||
return
|
||||
}
|
||||
|
||||
guard let url = URL(string: "https://api.ani.zip/mappings?anilist_id=\(itemID)") else {
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
URLSession.custom.dataTask(with: url) { data, _, error in
|
||||
if let error = error {
|
||||
print("Failed to fetch episode details: \(error)")
|
||||
DispatchQueue.main.async {
|
||||
self.isLoading = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = data else {
|
||||
print("No data received")
|
||||
DispatchQueue.main.async {
|
||||
self.isLoading = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
UserDefaults.standard.set(data, forKey: cacheKey)
|
||||
self.parseEpisodeDetails(data: data)
|
||||
}.resume()
|
||||
}
|
||||
|
||||
func parseEpisodeDetails(data: Data) {
|
||||
do {
|
||||
let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
|
||||
guard let json = jsonObject as? [String: Any],
|
||||
let episodes = json["episodes"] as? [String: Any],
|
||||
let episodeDetails = episodes["\(episodeID + 1)"] as? [String: Any],
|
||||
let title = episodeDetails["title"] as? [String: String],
|
||||
let image = episodeDetails["image"] as? String else {
|
||||
print("Invalid response format")
|
||||
DispatchQueue.main.async {
|
||||
self.isLoading = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.episodeTitle = title["en"] ?? ""
|
||||
self.episodeImageUrl = image
|
||||
self.isLoading = false
|
||||
}
|
||||
} catch {
|
||||
print("Failed to parse JSON: \(error)")
|
||||
DispatchQueue.main.async {
|
||||
self.isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,13 @@
|
|||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct MediaItem: Identifiable {
|
||||
let id = UUID()
|
||||
let description: String
|
||||
let aliases: String
|
||||
let airdate: String
|
||||
}
|
||||
|
||||
struct MediaInfoView: View {
|
||||
let title: String
|
||||
let imageUrl: String
|
||||
|
|
@ -25,104 +32,122 @@ struct MediaInfoView: View {
|
|||
|
||||
@AppStorage("externalPlayer") private var externalPlayer: String = "Default"
|
||||
|
||||
@ObservedObject var jsController = JSController()
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
HStack(alignment: .top, spacing: 10) {
|
||||
KFImage(URL(string: imageUrl))
|
||||
.resizable()
|
||||
.aspectRatio(2/3, contentMode: .fill)
|
||||
.cornerRadius(10)
|
||||
.frame(width: 150, height: 225)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(title)
|
||||
.font(.system(size: 17))
|
||||
.fontWeight(.bold)
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
.padding()
|
||||
} else {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
HStack(alignment: .top, spacing: 10) {
|
||||
KFImage(URL(string: imageUrl))
|
||||
.resizable()
|
||||
.aspectRatio(2/3, contentMode: .fill)
|
||||
.cornerRadius(10)
|
||||
.frame(width: 150, height: 225)
|
||||
|
||||
if !aliases.isEmpty && aliases != title {
|
||||
Text(aliases)
|
||||
.font(.system(size: 13))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(alignment: .center, spacing: 12) {
|
||||
Text(module.metadata.sourceName)
|
||||
.font(.system(size: 13))
|
||||
.padding(4)
|
||||
.background(Capsule().fill(Color.accentColor.opacity(0.4)))
|
||||
|
||||
Button(action: {
|
||||
}) {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
}) {
|
||||
Image(systemName: "safari")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !synopsis.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
HStack(alignment: .center) {
|
||||
Text("Synopsis")
|
||||
.font(.system(size: 18))
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(title)
|
||||
.font(.system(size: 17))
|
||||
.fontWeight(.bold)
|
||||
|
||||
if !aliases.isEmpty && aliases != title {
|
||||
Text(aliases)
|
||||
.font(.system(size: 13))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
showFullSynopsis.toggle()
|
||||
}) {
|
||||
Text(showFullSynopsis ? "Less" : "More")
|
||||
.font(.system(size: 14))
|
||||
HStack(alignment: .center, spacing: 12) {
|
||||
Text(module.metadata.sourceName)
|
||||
.font(.system(size: 13))
|
||||
.padding(4)
|
||||
.background(Capsule().fill(Color.accentColor.opacity(0.4)))
|
||||
|
||||
Button(action: {
|
||||
}) {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
}) {
|
||||
Image(systemName: "safari")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(synopsis)
|
||||
.lineLimit(showFullSynopsis ? nil : 4)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
|
||||
HStack {
|
||||
Button(action: {
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "play.fill")
|
||||
.foregroundColor(.primary)
|
||||
Text("Start Watching")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.accentColor)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
}) {
|
||||
Image(systemName: "bookmark")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 27)
|
||||
if !synopsis.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
HStack(alignment: .center) {
|
||||
Text("Synopsis")
|
||||
.font(.system(size: 18))
|
||||
.fontWeight(.bold)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
showFullSynopsis.toggle()
|
||||
}) {
|
||||
Text(showFullSynopsis ? "Less" : "More")
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
|
||||
Text(synopsis)
|
||||
.lineLimit(showFullSynopsis ? nil : 4)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
|
||||
HStack {
|
||||
Button(action: {
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "play.fill")
|
||||
.foregroundColor(.primary)
|
||||
Text("Start Watching")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.accentColor)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
}) {
|
||||
Image(systemName: "bookmark")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 27)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarTitle(title)
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
.padding()
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarTitle(title)
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
jsController.fetchDetails(url: href) { items in
|
||||
if let item = items.first {
|
||||
print("Fetched item: \(item)")
|
||||
self.synopsis = item.description
|
||||
self.aliases = item.aliases
|
||||
self.airdate = item.airdate
|
||||
}
|
||||
self.isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue