Compare commits
3 Commits
cd8c3afd7d
...
2e5348cc97
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e5348cc97 | |||
| bcfee96fa9 | |||
| 1da412e637 |
@@ -21,7 +21,11 @@
|
||||
"Bash(killall xcodebuild:*)",
|
||||
"Bash(cp:*)",
|
||||
"Bash(pkill:*)",
|
||||
"Bash(git:*)"
|
||||
"Bash(git:*)",
|
||||
"Bash(xcrun simctl:*)",
|
||||
"Bash(xcrun devicectl:*)",
|
||||
"Bash(networksetup:*)",
|
||||
"Bash(system_profiler SPNetworkDataType:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,10 +93,6 @@ enum HTMLParser {
|
||||
let h1 = try doc.select("h1").first()
|
||||
let fullTitle = try h1?.text().trimmingCharacters(in: .whitespacesAndNewlines) ?? "未知标题"
|
||||
|
||||
// 海报
|
||||
let imgSrc = try doc.select("img.w-full.h-full.object-cover").first()?.attr("src") ?? ""
|
||||
let posterURL = URL(string: imgSrc)
|
||||
|
||||
// === 优先从 JSON-LD 提取结构化数据 ===
|
||||
var year = 0
|
||||
var rating: Double?
|
||||
@@ -105,12 +101,17 @@ enum HTMLParser {
|
||||
var genres: [String] = []
|
||||
var description = ""
|
||||
var region = ""
|
||||
var posterURL: URL?
|
||||
|
||||
if let jsonLDScript = try doc.select("script[type=application/ld+json]").first() {
|
||||
let jsonText = try jsonLDScript.data()
|
||||
if let data = jsonText.data(using: .utf8),
|
||||
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
||||
|
||||
if let imageStr = json["image"] as? String {
|
||||
posterURL = URL(string: imageStr)
|
||||
}
|
||||
|
||||
if let published = json["datePublished"] as? String {
|
||||
year = Int(published) ?? 0
|
||||
}
|
||||
@@ -140,6 +141,12 @@ enum HTMLParser {
|
||||
|
||||
// === 从 HTML 补充缺失数据 ===
|
||||
|
||||
// 海报 fallback: 主海报在 .flex-shrink-0 容器中
|
||||
if posterURL == nil {
|
||||
let imgSrc = try doc.select(".flex-shrink-0 img.object-cover").first()?.attr("src") ?? ""
|
||||
posterURL = URL(string: imgSrc)
|
||||
}
|
||||
|
||||
// 评分 fallback
|
||||
if rating == nil {
|
||||
let ratingText = try doc.select(".rating-display").text()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
@Observable
|
||||
@Observable @MainActor
|
||||
final class DetailViewModel {
|
||||
var detail: ContentDetail?
|
||||
var selectedSourceIndex = 0
|
||||
@@ -27,23 +27,22 @@ final class DetailViewModel {
|
||||
func loadDetail(path: String) async {
|
||||
isLoading = true
|
||||
error = nil
|
||||
defer { isLoading = false }
|
||||
|
||||
do {
|
||||
let html = try await APIClient.shared.fetchDetailPage(path: path)
|
||||
let parsedDetail = try HTMLParser.parseContentDetail(html: html)
|
||||
await MainActor.run {
|
||||
self.detail = parsedDetail
|
||||
self.selectedSourceIndex = 0
|
||||
self.selectedEpisodeIndex = 0
|
||||
}
|
||||
} catch is CancellationError {
|
||||
// 被取消时不清除 loading 状态,让视图继续显示加载中
|
||||
return
|
||||
} catch let error as URLError where error.code == .cancelled {
|
||||
return
|
||||
} catch {
|
||||
await MainActor.run { self.error = error.localizedDescription }
|
||||
self.error = error.localizedDescription
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
func selectSource(_ index: Int) {
|
||||
|
||||
@@ -14,9 +14,14 @@ struct CookieInputView: View {
|
||||
}
|
||||
|
||||
Section("Cookie") {
|
||||
#if os(tvOS)
|
||||
TextField("粘贴 Cookie...", text: $cookieText)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
#else
|
||||
TextEditor(text: $cookieText)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
.frame(minHeight: 120)
|
||||
#endif
|
||||
}
|
||||
|
||||
Section {
|
||||
|
||||
@@ -16,22 +16,22 @@ struct DetailView: View {
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if viewModel.isLoading {
|
||||
ProgressView()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else if let error = viewModel.error {
|
||||
errorView(error)
|
||||
} else if let detail = viewModel.detail {
|
||||
if let detail = viewModel.detail {
|
||||
ScrollView {
|
||||
detailContent(detail)
|
||||
}
|
||||
} else if let error = viewModel.error {
|
||||
errorView(error)
|
||||
} else {
|
||||
ProgressView()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
.navigationTitle(item.title)
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#endif
|
||||
.task {
|
||||
.task(id: item.id) {
|
||||
if viewModel.detail == nil {
|
||||
await viewModel.loadDetail(path: item.detailURL)
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ struct AppNavigation: View {
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
private var sidebarLayout: some View {
|
||||
NavigationSplitView {
|
||||
List(AppTab.visibleTabs, selection: $selectedTab) { tab in
|
||||
@@ -106,6 +107,7 @@ struct AppNavigation: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ViewBuilder
|
||||
private func tabContent(for tab: AppTab) -> some View {
|
||||
|
||||
Submodule LocalPackages/SwiftSoup updated: 8b6cf29eea...e98a6d63ce
23
Package.resolved
Normal file
23
Package.resolved
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "lrucache",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/nicklockwood/LRUCache.git",
|
||||
"state" : {
|
||||
"revision" : "cb5b2bd0da83ad29c0bec762d39f41c8ad0eaf3e",
|
||||
"version" : "1.2.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-atomics",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-atomics.git",
|
||||
"state" : {
|
||||
"revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7",
|
||||
"version" : "1.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
27
project.yml
27
project.yml
@@ -4,6 +4,7 @@ options:
|
||||
deploymentTarget:
|
||||
iOS: "17.0"
|
||||
macOS: "14.0"
|
||||
tvOS: "17.0"
|
||||
xcodeVersion: "16.0"
|
||||
groupSortPosition: top
|
||||
createIntermediateGroups: true
|
||||
@@ -32,6 +33,26 @@ targets:
|
||||
INFOPLIST_KEY_CFBundleDisplayName: DDYS
|
||||
CODE_SIGN_ENTITLEMENTS: DDYSClient/DDYSClient.entitlements
|
||||
|
||||
DDYSClient-tvOS:
|
||||
type: application
|
||||
platform: tvOS
|
||||
sources:
|
||||
- path: DDYSClient
|
||||
resources:
|
||||
- path: Resources
|
||||
optional: true
|
||||
dependencies:
|
||||
- package: SwiftSoup
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.fusion.ddys.client
|
||||
MARKETING_VERSION: "1.0.0"
|
||||
CURRENT_PROJECT_VERSION: 1
|
||||
SWIFT_VERSION: "5.9"
|
||||
GENERATE_INFOPLIST_FILE: YES
|
||||
INFOPLIST_KEY_CFBundleDisplayName: 低端影视
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation: YES
|
||||
|
||||
schemes:
|
||||
DDYSClient:
|
||||
build:
|
||||
@@ -39,3 +60,9 @@ schemes:
|
||||
DDYSClient: all
|
||||
run:
|
||||
config: Debug
|
||||
DDYSClient-tvOS:
|
||||
build:
|
||||
targets:
|
||||
DDYSClient-tvOS: all
|
||||
run:
|
||||
config: Debug
|
||||
|
||||
Reference in New Issue
Block a user