mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-19 23:52:09 +00:00
TMDB thanks to @qooode
Co-Authored-By: qooode <71751652+qooode@users.noreply.github.com>
This commit is contained in:
parent
62802fd30e
commit
99a5c6303c
2 changed files with 116 additions and 78 deletions
|
|
@ -69,12 +69,18 @@ struct EpisodeCell: View {
|
|||
}
|
||||
}
|
||||
|
||||
let tmdbID: Int?
|
||||
let seasonNumber: Int?
|
||||
|
||||
init(episodeIndex: Int, episode: String, episodeID: Int, progress: Double,
|
||||
itemID: Int, totalEpisodes: Int? = nil, defaultBannerImage: String = "",
|
||||
module: ScrapingModule, parentTitle: String, showPosterURL: String? = nil,
|
||||
isMultiSelectMode: Bool = false, isSelected: Bool = false,
|
||||
onSelectionChanged: ((Bool) -> Void)? = nil,
|
||||
onTap: @escaping (String) -> Void, onMarkAllPrevious: @escaping () -> Void) {
|
||||
onTap: @escaping (String) -> Void, onMarkAllPrevious: @escaping () -> Void,
|
||||
tmdbID: Int? = nil,
|
||||
seasonNumber: Int? = nil
|
||||
) {
|
||||
self.episodeIndex = episodeIndex
|
||||
self.episode = episode
|
||||
self.episodeID = episodeID
|
||||
|
|
@ -99,6 +105,8 @@ struct EpisodeCell: View {
|
|||
self.onSelectionChanged = onSelectionChanged
|
||||
self.onTap = onTap
|
||||
self.onMarkAllPrevious = onMarkAllPrevious
|
||||
self.tmdbID = tmdbID
|
||||
self.seasonNumber = seasonNumber
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
|
@ -211,13 +219,14 @@ struct EpisodeCell: View {
|
|||
.onAppear {
|
||||
updateProgress()
|
||||
updateDownloadStatus()
|
||||
|
||||
if let type = module.metadata.type?.lowercased(), type == "anime" {
|
||||
if UserDefaults.standard.string(forKey: "metadataProviders") ?? "Anilist" == "AniList" {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
fetchAnimeEpisodeDetails()
|
||||
}
|
||||
} else {
|
||||
isLoading = false
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
fetchTMDBEpisodeImage()
|
||||
}
|
||||
}
|
||||
|
||||
if let totalEpisodes = totalEpisodes, episodeID + 1 < totalEpisodes {
|
||||
|
|
@ -232,12 +241,9 @@ struct EpisodeCell: View {
|
|||
updateProgress()
|
||||
}
|
||||
.onChange(of: itemID) { newID in
|
||||
// 1) Clear any cached title/image so that the UI shows the loading spinner:
|
||||
loadedFromCache = false
|
||||
isLoading = true
|
||||
retryAttempts = maxRetryAttempts // reset retries if you want
|
||||
|
||||
// 2) Call the same logic you already use to pull per-episode info:
|
||||
retryAttempts = maxRetryAttempts
|
||||
fetchEpisodeDetails()
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("downloadProgressChanged"))) { _ in
|
||||
|
|
@ -767,6 +773,34 @@ struct EpisodeCell: View {
|
|||
}
|
||||
}
|
||||
|
||||
private func fetchTMDBEpisodeImage() {
|
||||
guard let tmdbID = tmdbID, let season = seasonNumber else { return }
|
||||
let episodeNum = episodeID + 1
|
||||
let urlString = "https://api.themoviedb.org/3/tv/\(tmdbID)/season/\(season)/episode/\(episodeNum)/images?api_key=738b4edd0a156cc126dc4a4b8aea4aca"
|
||||
guard let url = URL(string: urlString) else { return }
|
||||
|
||||
URLSession.custom.dataTask(with: url) { data, _, error in
|
||||
guard let data = data, error == nil else { return }
|
||||
do {
|
||||
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let stills = json["stills"] as? [[String: Any]],
|
||||
let firstStill = stills.first,
|
||||
let filePath = firstStill["file_path"] as? String {
|
||||
let imageUrl = "https://image.tmdb.org/t/p/w780\(filePath)"
|
||||
DispatchQueue.main.async {
|
||||
self.episodeImageUrl = imageUrl
|
||||
self.isLoading = false
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.shared.log("Failed to parse TMDB episode image response: \(error.localizedDescription)", type: "Error")
|
||||
DispatchQueue.main.async {
|
||||
self.isLoading = false
|
||||
}
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
private func calculateMaxSwipeDistance() -> CGFloat {
|
||||
var buttonCount = 1
|
||||
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ struct MediaInfoView: View {
|
|||
}
|
||||
.onAppear {
|
||||
UIScrollView.appearance().bounces = false
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarTitle("")
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
|
|
@ -335,7 +335,7 @@ struct MediaInfoView: View {
|
|||
}
|
||||
|
||||
playAndBookmarkSection
|
||||
|
||||
|
||||
if episodeLinks.count == 1 {
|
||||
VStack(spacing: 12) {
|
||||
HStack(spacing: 12) {
|
||||
|
|
@ -464,9 +464,9 @@ struct MediaInfoView: View {
|
|||
.foregroundColor(.gray)
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
|
||||
|
||||
Divider()
|
||||
|
||||
|
||||
if let _ = customAniListID {
|
||||
Button(action: {
|
||||
customAniListID = nil
|
||||
|
|
@ -665,6 +665,51 @@ struct MediaInfoView: View {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var flatEpisodeList: some View {
|
||||
LazyVStack(spacing: 15) {
|
||||
ForEach(episodeLinks.indices.filter { selectedRange.contains($0) }, id: \.self) { i in
|
||||
let ep = episodeLinks[i]
|
||||
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(ep.href)")
|
||||
let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(ep.href)")
|
||||
let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0
|
||||
|
||||
let defaultBannerImageValue = getBannerImageBasedOnAppearance()
|
||||
|
||||
EpisodeCell(
|
||||
episodeIndex: i,
|
||||
episode: ep.href,
|
||||
episodeID: ep.number - 1,
|
||||
progress: progress,
|
||||
itemID: itemID ?? 0,
|
||||
totalEpisodes: episodeLinks.count,
|
||||
defaultBannerImage: defaultBannerImageValue,
|
||||
module: module,
|
||||
parentTitle: title,
|
||||
showPosterURL: imageUrl,
|
||||
isMultiSelectMode: isMultiSelectMode,
|
||||
isSelected: selectedEpisodes.contains(ep.number),
|
||||
onSelectionChanged: { isSelected in
|
||||
if isSelected {
|
||||
selectedEpisodes.insert(ep.number)
|
||||
} else {
|
||||
selectedEpisodes.remove(ep.number)
|
||||
}
|
||||
},
|
||||
onTap: { imageUrl in
|
||||
episodeTapAction(ep: ep, imageUrl: imageUrl)
|
||||
},
|
||||
onMarkAllPrevious: {
|
||||
markAllPreviousEpisodesInFlatList(ep: ep, index: i)
|
||||
},
|
||||
tmdbID: tmdbID,
|
||||
seasonNumber: 1
|
||||
)
|
||||
.disabled(isFetchingEpisode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var seasonsEpisodeList: some View {
|
||||
let seasons = groupedEpisodes()
|
||||
|
|
@ -702,9 +747,11 @@ struct MediaInfoView: View {
|
|||
},
|
||||
onMarkAllPrevious: {
|
||||
markAllPreviousEpisodesAsWatched(ep: ep, inSeason: true)
|
||||
}
|
||||
},
|
||||
tmdbID: tmdbID,
|
||||
seasonNumber: selectedSeason + 1
|
||||
)
|
||||
.disabled(isFetchingEpisode)
|
||||
.disabled(isFetchingEpisode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -731,70 +778,6 @@ struct MediaInfoView: View {
|
|||
}
|
||||
}
|
||||
|
||||
private func markAllPreviousEpisodesAsWatched(ep: EpisodeLink, inSeason: Bool) {
|
||||
let userDefaults = UserDefaults.standard
|
||||
var updates = [String: Double]()
|
||||
|
||||
if inSeason {
|
||||
let seasons = groupedEpisodes()
|
||||
for ep2 in seasons[selectedSeason] where ep2.number < ep.number {
|
||||
let href = ep2.href
|
||||
updates["lastPlayedTime_\(href)"] = 99999999.0
|
||||
updates["totalTime_\(href)"] = 99999999.0
|
||||
}
|
||||
|
||||
for (key, value) in updates {
|
||||
userDefaults.set(value, forKey: key)
|
||||
}
|
||||
|
||||
userDefaults.synchronize()
|
||||
Logger.shared.log("Marked episodes watched within season \(selectedSeason + 1) of \"\(title)\".", type: "General")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var flatEpisodeList: some View {
|
||||
LazyVStack(spacing: 15) {
|
||||
ForEach(episodeLinks.indices.filter { selectedRange.contains($0) }, id: \.self) { i in
|
||||
let ep = episodeLinks[i]
|
||||
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(ep.href)")
|
||||
let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(ep.href)")
|
||||
let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0
|
||||
|
||||
let defaultBannerImageValue = getBannerImageBasedOnAppearance()
|
||||
|
||||
EpisodeCell(
|
||||
episodeIndex: i,
|
||||
episode: ep.href,
|
||||
episodeID: ep.number - 1,
|
||||
progress: progress,
|
||||
itemID: itemID ?? 0,
|
||||
totalEpisodes: episodeLinks.count,
|
||||
defaultBannerImage: defaultBannerImageValue,
|
||||
module: module,
|
||||
parentTitle: title,
|
||||
showPosterURL: imageUrl,
|
||||
isMultiSelectMode: isMultiSelectMode,
|
||||
isSelected: selectedEpisodes.contains(ep.number),
|
||||
onSelectionChanged: { isSelected in
|
||||
if isSelected {
|
||||
selectedEpisodes.insert(ep.number)
|
||||
} else {
|
||||
selectedEpisodes.remove(ep.number)
|
||||
}
|
||||
},
|
||||
onTap: { imageUrl in
|
||||
episodeTapAction(ep: ep, imageUrl: imageUrl)
|
||||
},
|
||||
onMarkAllPrevious: {
|
||||
markAllPreviousEpisodesInFlatList(ep: ep, index: i)
|
||||
}
|
||||
)
|
||||
.disabled(isFetchingEpisode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchMetadataIDIfNeeded() {
|
||||
let provider = UserDefaults.standard.string(forKey: "metadataProviders") ?? "Anilist"
|
||||
let cleaned = cleanTitle(title)
|
||||
|
|
@ -822,6 +805,27 @@ struct MediaInfoView: View {
|
|||
}
|
||||
}
|
||||
|
||||
private func markAllPreviousEpisodesAsWatched(ep: EpisodeLink, inSeason: Bool) {
|
||||
let userDefaults = UserDefaults.standard
|
||||
var updates = [String: Double]()
|
||||
|
||||
if inSeason {
|
||||
let seasons = groupedEpisodes()
|
||||
for ep2 in seasons[selectedSeason] where ep2.number < ep.number {
|
||||
let href = ep2.href
|
||||
updates["lastPlayedTime_\(href)"] = 99999999.0
|
||||
updates["totalTime_\(href)"] = 99999999.0
|
||||
}
|
||||
|
||||
for (key, value) in updates {
|
||||
userDefaults.set(value, forKey: key)
|
||||
}
|
||||
|
||||
userDefaults.synchronize()
|
||||
Logger.shared.log("Marked episodes watched within season \(selectedSeason + 1) of \"\(title)\".", type: "General")
|
||||
}
|
||||
}
|
||||
|
||||
private func markAllPreviousEpisodesInFlatList(ep: EpisodeLink, index: Int) {
|
||||
let userDefaults = UserDefaults.standard
|
||||
var updates = [String: Double]()
|
||||
|
|
|
|||
Loading…
Reference in a new issue