import Foundation @Observable @MainActor final class DetailViewModel { var detail: ContentDetail? var selectedSourceIndex = 0 var selectedEpisodeIndex = 0 var isLoading = false var error: String? var currentSource: StreamSource? { guard let detail, selectedSourceIndex < detail.sources.count else { return nil } return detail.sources[selectedSourceIndex] } var currentEpisode: Episode? { guard let source = currentSource, selectedEpisodeIndex < source.episodes.count else { return nil } return source.episodes[selectedEpisodeIndex] } var hasMultipleEpisodes: Bool { guard let source = currentSource else { return false } return source.episodes.count > 1 } func loadDetail(path: String) async { isLoading = true error = nil do { let html = try await APIClient.shared.fetchDetailPage(path: path) let parsedDetail = try HTMLParser.parseContentDetail(html: html) 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 { self.error = error.localizedDescription } isLoading = false } func selectSource(_ index: Int) { selectedSourceIndex = index selectedEpisodeIndex = 0 } func selectEpisode(_ index: Int) { selectedEpisodeIndex = index } func nextEpisode() -> Bool { guard let source = currentSource else { return false } if selectedEpisodeIndex + 1 < source.episodes.count { selectedEpisodeIndex += 1 return true } return false } }