mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-19 23:52:09 +00:00
Update EpisodeCell.swift
This commit is contained in:
parent
5742556bfe
commit
d7479dbb73
1 changed files with 69 additions and 104 deletions
|
|
@ -22,7 +22,7 @@ struct EpisodeCell: View {
|
|||
let showPosterURL: String?
|
||||
let tmdbID: Int?
|
||||
let seasonNumber: Int?
|
||||
let malID: Int? // MAL ID for the series
|
||||
let malID: Int?
|
||||
|
||||
let isMultiSelectMode: Bool
|
||||
let isSelected: Bool
|
||||
|
|
@ -54,17 +54,14 @@ struct EpisodeCell: View {
|
|||
@Environment(\.colorScheme) private var colorScheme
|
||||
@AppStorage("selectedAppearance") private var selectedAppearance: Appearance = .system
|
||||
|
||||
// Filler state
|
||||
@State private var isFiller: Bool = false
|
||||
|
||||
// Jikan API cache
|
||||
private static var jikanCache: [Int: (fetchedAt: Date, episodes: [JikanEpisode])] = [:]
|
||||
private static let jikanCacheQueue = DispatchQueue(label: "sora.jikan.cache.queue", attributes: .concurrent)
|
||||
private static let jikanCacheTTL: TimeInterval = 60 * 60 * 24 * 7 // 1 week
|
||||
private static let jikanCacheTTL: TimeInterval = 60 * 60 * 24 * 7
|
||||
private static var inProgressMALIDs: Set<Int> = []
|
||||
private static let inProgressQueue = DispatchQueue(label: "sora.jikan.inprogress.queue")
|
||||
|
||||
// Jikan data models
|
||||
private struct JikanResponse: Decodable {
|
||||
let data: [JikanEpisode]
|
||||
}
|
||||
|
|
@ -92,7 +89,7 @@ struct EpisodeCell: View {
|
|||
onMarkAllPrevious: @escaping () -> Void,
|
||||
tmdbID: Int? = nil,
|
||||
seasonNumber: Int? = nil,
|
||||
malID: Int? = nil // MAL ID parameter
|
||||
malID: Int? = nil
|
||||
) {
|
||||
self.episodeIndex = episodeIndex
|
||||
self.episode = episode
|
||||
|
|
@ -110,7 +107,7 @@ struct EpisodeCell: View {
|
|||
self.onMarkAllPrevious = onMarkAllPrevious
|
||||
self.tmdbID = tmdbID
|
||||
self.seasonNumber = seasonNumber
|
||||
self.malID = malID // Initialize MAL ID
|
||||
self.malID = malID
|
||||
|
||||
let isLightMode = (UserDefaults.standard.string(forKey: "selectedAppearance") == "light") ||
|
||||
((UserDefaults.standard.string(forKey: "selectedAppearance") == "system") &&
|
||||
|
|
@ -154,11 +151,8 @@ struct EpisodeCell: View {
|
|||
updateProgress()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension EpisodeCell {
|
||||
|
||||
var actionButtonsBackground: some View {
|
||||
private var actionButtonsBackground: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
actionButtons
|
||||
|
|
@ -166,7 +160,7 @@ private extension EpisodeCell {
|
|||
.zIndex(0)
|
||||
}
|
||||
|
||||
var episodeCellContent: some View {
|
||||
private var episodeCellContent: some View {
|
||||
HStack {
|
||||
episodeThumbnail
|
||||
episodeInfo
|
||||
|
|
@ -203,7 +197,7 @@ private extension EpisodeCell {
|
|||
.onTapGesture { handleTap() }
|
||||
}
|
||||
|
||||
var cellBackground: some View {
|
||||
private var cellBackground: some View {
|
||||
RoundedRectangle(cornerRadius: 15)
|
||||
.fill(Color(UIColor.systemBackground))
|
||||
.overlay(
|
||||
|
|
@ -226,7 +220,7 @@ private extension EpisodeCell {
|
|||
)
|
||||
}
|
||||
|
||||
var episodeThumbnail: some View {
|
||||
private var episodeThumbnail: some View {
|
||||
ZStack {
|
||||
AsyncImageView(
|
||||
url: episodeImageUrl.isEmpty ? defaultBannerImage : episodeImageUrl,
|
||||
|
|
@ -241,7 +235,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
var episodeInfo: some View {
|
||||
private var episodeInfo: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 8) {
|
||||
Text("Episode \(episodeID + 1)")
|
||||
|
|
@ -273,13 +267,13 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
var downloadedIndicator: some View {
|
||||
private var downloadedIndicator: some View {
|
||||
Image(systemName: "externaldrive.fill.badge.checkmark")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.system(size: 18))
|
||||
}
|
||||
|
||||
var contextMenuContent: some View {
|
||||
private var contextMenuContent: some View {
|
||||
Group {
|
||||
if progress <= 0.9 {
|
||||
Button(action: markAsWatched) {
|
||||
|
|
@ -305,7 +299,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
var actionButtons: some View {
|
||||
private var actionButtons: some View {
|
||||
HStack(spacing: 8) {
|
||||
ActionButton(
|
||||
icon: "arrow.down.circle",
|
||||
|
|
@ -351,11 +345,8 @@ private extension EpisodeCell {
|
|||
}
|
||||
.padding(.horizontal, 8)
|
||||
}
|
||||
}
|
||||
|
||||
private extension EpisodeCell {
|
||||
|
||||
enum DragState {
|
||||
private enum DragState {
|
||||
case inactive
|
||||
case pressing
|
||||
case dragging(translation: CGSize)
|
||||
|
|
@ -388,7 +379,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
func handleDragChanged(_ value: DragGesture.Value) {
|
||||
private func handleDragChanged(_ value: DragGesture.Value) {
|
||||
let translation = value.translation
|
||||
let velocity = value.velocity
|
||||
|
||||
|
|
@ -417,7 +408,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
func handleDragEnded(_ value: DragGesture.Value) {
|
||||
private func handleDragEnded(_ value: DragGesture.Value) {
|
||||
let translation = value.translation
|
||||
let velocity = value.velocity
|
||||
|
||||
|
|
@ -449,7 +440,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
func handleTap() {
|
||||
private func handleTap() {
|
||||
if isShowingActions {
|
||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
|
||||
swipeOffset = 0
|
||||
|
|
@ -463,7 +454,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
func calculateMaxSwipeDistance() -> CGFloat {
|
||||
private func calculateMaxSwipeDistance() -> CGFloat {
|
||||
var buttonCount = 1
|
||||
|
||||
if progress <= 0.9 { buttonCount += 1 }
|
||||
|
|
@ -477,11 +468,8 @@ private extension EpisodeCell {
|
|||
|
||||
return swipeDistance
|
||||
}
|
||||
}
|
||||
|
||||
private extension EpisodeCell {
|
||||
|
||||
func closeActionsAndPerform(action: @escaping () -> Void) {
|
||||
private func closeActionsAndPerform(action: @escaping () -> Void) {
|
||||
withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) {
|
||||
isShowingActions = false
|
||||
swipeOffset = 0
|
||||
|
|
@ -492,7 +480,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
func markAsWatched() {
|
||||
private func markAsWatched() {
|
||||
let defaults = UserDefaults.standard
|
||||
let totalTime = 1000.0
|
||||
defaults.set(totalTime, forKey: "lastPlayedTime_\(episode)")
|
||||
|
|
@ -509,30 +497,30 @@ private extension EpisodeCell {
|
|||
) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
Logger.shared.log("AniList sync: marked ep \(epNum) as \(newStatus)", type: "General")
|
||||
print("AniList sync: marked ep \(epNum) as \(newStatus)")
|
||||
case .failure(let err):
|
||||
Logger.shared.log("AniList sync failed: \(err.localizedDescription)", type: "Error")
|
||||
print("AniList sync failed: \(err.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func resetProgress() {
|
||||
private func resetProgress() {
|
||||
let userDefaults = UserDefaults.standard
|
||||
userDefaults.set(0.0, forKey: "lastPlayedTime_\(episode)")
|
||||
userDefaults.set(0.0, forKey: "totalTime_\(episode)")
|
||||
updateProgress()
|
||||
}
|
||||
|
||||
func updateProgress() {
|
||||
private func updateProgress() {
|
||||
let userDefaults = UserDefaults.standard
|
||||
let lastPlayedTime = userDefaults.double(forKey: "lastPlayedTime_\(episode)")
|
||||
let totalTime = userDefaults.double(forKey: "totalTime_\(episode)")
|
||||
currentProgress = totalTime > 0 ? min(lastPlayedTime / totalTime, 1.0) : 0
|
||||
}
|
||||
|
||||
func updateDownloadStatus() {
|
||||
private func updateDownloadStatus() {
|
||||
let newStatus = jsController.isEpisodeDownloadedOrInProgress(
|
||||
showTitle: parentTitle,
|
||||
episodeNumber: episodeID + 1
|
||||
|
|
@ -542,38 +530,32 @@ private extension EpisodeCell {
|
|||
downloadStatus = newStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension EpisodeCell {
|
||||
func setupOnAppear() {
|
||||
|
||||
private func setupOnAppear() {
|
||||
updateProgress()
|
||||
updateDownloadStatus()
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
if UserDefaults.standard.string(forKey: "metadataProviders") ?? "TMDB" == "TMDB" {
|
||||
fetchTMDBEpisodeImage()
|
||||
self.fetchTMDBEpisodeImage()
|
||||
} else {
|
||||
fetchAnimeEpisodeDetails()
|
||||
self.fetchAnimeEpisodeDetails()
|
||||
}
|
||||
// Fetch filler info using Jikan API
|
||||
fetchJikanFillerInfo()
|
||||
self.fetchJikanFillerInfo()
|
||||
}
|
||||
}
|
||||
|
||||
func handleItemIDChange() {
|
||||
private func handleItemIDChange() {
|
||||
isLoading = true
|
||||
retryAttempts = 0
|
||||
fetchEpisodeDetails()
|
||||
}
|
||||
|
||||
func fetchEpisodeDetails() {
|
||||
private func fetchEpisodeDetails() {
|
||||
fetchAnimeEpisodeDetails()
|
||||
}
|
||||
}
|
||||
|
||||
private extension EpisodeCell {
|
||||
|
||||
func downloadEpisode() {
|
||||
private func downloadEpisode() {
|
||||
updateDownloadStatus()
|
||||
|
||||
guard case .notDownloaded = downloadStatus, !isDownloading else {
|
||||
|
|
@ -598,7 +580,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
func handleAlreadyDownloadedOrInProgress() {
|
||||
private func handleAlreadyDownloadedOrInProgress() {
|
||||
switch downloadStatus {
|
||||
case .downloaded:
|
||||
DropManager.shared.info("Episode \(episodeID + 1) is already downloaded")
|
||||
|
|
@ -609,7 +591,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
func tryNextDownloadMethod(methodIndex: Int, downloadID: UUID, softsub: Bool) {
|
||||
private func tryNextDownloadMethod(methodIndex: Int, downloadID: UUID, softsub: Bool) {
|
||||
guard isDownloading else { return }
|
||||
|
||||
switch methodIndex {
|
||||
|
|
@ -642,7 +624,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
func handleDownloadResult(
|
||||
private func handleDownloadResult(
|
||||
_ result: (streams: [String]?, subtitles: [String]?, sources: [[String: Any]]?),
|
||||
downloadID: UUID,
|
||||
methodIndex: Int,
|
||||
|
|
@ -681,7 +663,7 @@ private extension EpisodeCell {
|
|||
tryNextDownloadMethod(methodIndex: methodIndex + 1, downloadID: downloadID, softsub: softsub)
|
||||
}
|
||||
|
||||
func startActualDownload(url: URL, streamUrl: String, downloadID: UUID, subtitleURL: URL? = nil) {
|
||||
private func startActualDownload(url: URL, streamUrl: String, downloadID: UUID, subtitleURL: URL? = nil) {
|
||||
let headers = createDownloadHeaders(for: url)
|
||||
let episodeThumbnailURL = URL(string: episodeImageUrl.isEmpty ? defaultBannerImage : episodeImageUrl)
|
||||
let showPosterImageURL = URL(string: showPosterURL ?? defaultBannerImage)
|
||||
|
|
@ -704,7 +686,7 @@ private extension EpisodeCell {
|
|||
showPosterURL: showPosterImageURL
|
||||
) { success, message in
|
||||
if success {
|
||||
Logger.shared.log("Started download for Episode \(self.episodeID + 1): \(self.episode)", type: "Download")
|
||||
print("Started download for Episode \(self.episodeID + 1): \(self.episode)")
|
||||
AnalyticsManager.shared.sendEvent(
|
||||
event: "download",
|
||||
additionalData: ["episode": self.episodeID + 1, "url": streamUrl]
|
||||
|
|
@ -716,7 +698,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
func createDownloadHeaders(for url: URL) -> [String: String] {
|
||||
private func createDownloadHeaders(for url: URL) -> [String: String] {
|
||||
if !module.metadata.baseUrl.isEmpty && !module.metadata.baseUrl.contains("undefined") {
|
||||
return [
|
||||
"Origin": module.metadata.baseUrl,
|
||||
|
|
@ -746,11 +728,8 @@ private extension EpisodeCell {
|
|||
return [:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension EpisodeCell {
|
||||
|
||||
func showDownloadStreamSelectionAlert(streams: [Any], downloadID: UUID, subtitleURL: String? = nil) {
|
||||
private func showDownloadStreamSelectionAlert(streams: [Any], downloadID: UUID, subtitleURL: String? = nil) {
|
||||
DispatchQueue.main.async {
|
||||
let alert = UIAlertController(
|
||||
title: "Select Download Server",
|
||||
|
|
@ -768,7 +747,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
func addStreamActions(to alert: UIAlertController, streams: [Any], downloadID: UUID, subtitleURL: String?) {
|
||||
private func addStreamActions(to alert: UIAlertController, streams: [Any], downloadID: UUID, subtitleURL: String?) {
|
||||
var index = 0
|
||||
var streamIndex = 1
|
||||
|
||||
|
|
@ -791,7 +770,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
func parseStreamInfo(streams: [Any], index: Int, streamIndex: Int) -> (title: String, streamUrl: String, newIndex: Int) {
|
||||
private func parseStreamInfo(streams: [Any], index: Int, streamIndex: Int) -> (title: String, streamUrl: String, newIndex: Int) {
|
||||
if let streams = streams as? [String] {
|
||||
if index + 1 < streams.count && !streams[index].lowercased().contains("http") {
|
||||
return (streams[index], streams[index + 1], index + 2)
|
||||
|
|
@ -807,7 +786,7 @@ private extension EpisodeCell {
|
|||
return ("Server \(streamIndex)", "", index + 1)
|
||||
}
|
||||
|
||||
func presentAlert(_ alert: UIAlertController) {
|
||||
private func presentAlert(_ alert: UIAlertController) {
|
||||
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let window = windowScene.windows.first,
|
||||
let rootVC = window.rootViewController else { return }
|
||||
|
|
@ -828,7 +807,7 @@ private extension EpisodeCell {
|
|||
findTopViewController(rootVC).present(alert, animated: true)
|
||||
}
|
||||
|
||||
func findTopViewController(_ controller: UIViewController) -> UIViewController {
|
||||
private func findTopViewController(_ controller: UIViewController) -> UIViewController {
|
||||
if let navigationController = controller as? UINavigationController {
|
||||
return findTopViewController(navigationController.visibleViewController!)
|
||||
}
|
||||
|
|
@ -842,11 +821,8 @@ private extension EpisodeCell {
|
|||
}
|
||||
return controller
|
||||
}
|
||||
}
|
||||
|
||||
private extension EpisodeCell {
|
||||
|
||||
func fetchAnimeEpisodeDetails() {
|
||||
private func fetchAnimeEpisodeDetails() {
|
||||
if let fetchMeta = UserDefaults.standard.object(forKey: "fetchEpisodeMetadata"),
|
||||
!(fetchMeta as? Bool ?? true) {
|
||||
DispatchQueue.main.async {
|
||||
|
|
@ -857,17 +833,17 @@ private extension EpisodeCell {
|
|||
}
|
||||
guard let url = URL(string: "https://api.ani.zip/mappings?anilist_id=\(itemID)") else {
|
||||
isLoading = false
|
||||
Logger.shared.log("Invalid URL for itemID: \(itemID)", type: "Error")
|
||||
print("Invalid URL for itemID: \(itemID)")
|
||||
return
|
||||
}
|
||||
|
||||
if retryAttempts > 0 {
|
||||
Logger.shared.log("Retrying episode details fetch (attempt \(retryAttempts)/\(maxRetryAttempts))", type: "Debug")
|
||||
print("Retrying episode details fetch (attempt \(retryAttempts)/\(maxRetryAttempts))")
|
||||
}
|
||||
|
||||
URLSession.custom.dataTask(with: url) { data, response, error in
|
||||
if let error = error {
|
||||
Logger.shared.log("Failed to fetch anime episode details: \(error)", type: "Error")
|
||||
print("Failed to fetch anime episode details: \(error)")
|
||||
self.handleFetchFailure(error: error)
|
||||
return
|
||||
}
|
||||
|
|
@ -881,7 +857,7 @@ private extension EpisodeCell {
|
|||
}.resume()
|
||||
}
|
||||
|
||||
func processAnimeEpisodeData(_ data: Data) {
|
||||
private func processAnimeEpisodeData(_ data: Data) {
|
||||
do {
|
||||
let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
|
||||
guard let json = jsonObject as? [String: Any],
|
||||
|
|
@ -892,7 +868,7 @@ private extension EpisodeCell {
|
|||
|
||||
let episodeKey = "\(episodeID + 1)"
|
||||
guard let episodeDetails = episodes[episodeKey] as? [String: Any] else {
|
||||
Logger.shared.log("Episode \(episodeKey) not found in response", type: "Error")
|
||||
print("Episode \(episodeKey) not found in response")
|
||||
DispatchQueue.main.async {
|
||||
self.isLoading = false
|
||||
self.retryAttempts = 0
|
||||
|
|
@ -903,7 +879,7 @@ private extension EpisodeCell {
|
|||
updateEpisodeMetadata(from: episodeDetails)
|
||||
|
||||
} catch {
|
||||
Logger.shared.log("JSON parsing error: \(error.localizedDescription)", type: "Error")
|
||||
print("JSON parsing error: \(error.localizedDescription)")
|
||||
DispatchQueue.main.async {
|
||||
self.isLoading = false
|
||||
self.retryAttempts = 0
|
||||
|
|
@ -911,7 +887,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
func updateEpisodeMetadata(from episodeDetails: [String: Any]) {
|
||||
private func updateEpisodeMetadata(from episodeDetails: [String: Any]) {
|
||||
let title = episodeDetails["title"] as? [String: String] ?? [:]
|
||||
let image = episodeDetails["image"] as? String ?? ""
|
||||
|
||||
|
|
@ -930,7 +906,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
|
||||
func fetchTMDBEpisodeImage() {
|
||||
private func fetchTMDBEpisodeImage() {
|
||||
if let fetchMeta = UserDefaults.standard.object(forKey: "fetchEpisodeMetadata"),
|
||||
!(fetchMeta as? Bool ?? true) {
|
||||
DispatchQueue.main.async {
|
||||
|
|
@ -968,7 +944,7 @@ private extension EpisodeCell {
|
|||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.shared.log("Failed to parse TMDB episode details: \(error.localizedDescription)", type: "Error")
|
||||
print("Failed to parse TMDB episode details: \(error.localizedDescription)")
|
||||
DispatchQueue.main.async {
|
||||
self.isLoading = false
|
||||
}
|
||||
|
|
@ -976,16 +952,13 @@ private extension EpisodeCell {
|
|||
}.resume()
|
||||
}
|
||||
|
||||
// MARK: - Jikan Filler Implementation
|
||||
|
||||
private func fetchJikanFillerInfo() {
|
||||
guard let malID = malID else {
|
||||
Logger.shared.log("MAL ID not available for filler info", type: "Debug")
|
||||
print("MAL ID not available for filler info")
|
||||
return
|
||||
}
|
||||
let episodeNumber = episodeID + 1
|
||||
|
||||
// Check cache first
|
||||
var cachedEpisodes: [JikanEpisode]? = nil
|
||||
Self.jikanCacheQueue.sync {
|
||||
if let entry = Self.jikanCache[malID], Date().timeIntervalSince(entry.fetchedAt) < Self.jikanCacheTTL {
|
||||
|
|
@ -994,12 +967,11 @@ private extension EpisodeCell {
|
|||
}
|
||||
|
||||
if let episodes = cachedEpisodes {
|
||||
Logger.shared.log("Using cached filler info for MAL ID: \(malID)", type: "Debug")
|
||||
print("Using cached filler info for MAL ID: \(malID)")
|
||||
updateFillerStatus(episodes: episodes)
|
||||
return
|
||||
}
|
||||
|
||||
// Prevent duplicate requests
|
||||
var shouldFetch = false
|
||||
Self.inProgressQueue.sync {
|
||||
if !Self.inProgressMALIDs.contains(malID) {
|
||||
|
|
@ -1009,30 +981,26 @@ private extension EpisodeCell {
|
|||
}
|
||||
|
||||
if !shouldFetch {
|
||||
Logger.shared.log("Fetch already in progress for MAL ID: \(malID)", type: "Debug")
|
||||
print("Fetch already in progress for MAL ID: \(malID)")
|
||||
return
|
||||
}
|
||||
|
||||
Logger.shared.log("Fetching filler info for MAL ID: \(malID)", type: "Debug")
|
||||
print("Fetching filler info for MAL ID: \(malID)")
|
||||
|
||||
// Fetch all pages
|
||||
fetchAllJikanPages(malID: malID) { episodes in
|
||||
// Update cache
|
||||
if let episodes = episodes {
|
||||
Logger.shared.log("Successfully fetched filler info for MAL ID: \(malID)", type: "Debug")
|
||||
print("Successfully fetched filler info for MAL ID: \(malID)")
|
||||
Self.jikanCacheQueue.async(flags: .barrier) {
|
||||
Self.jikanCache[malID] = (Date(), episodes)
|
||||
}
|
||||
|
||||
// Update UI
|
||||
DispatchQueue.main.async {
|
||||
self.updateFillerStatus(episodes: episodes)
|
||||
}
|
||||
} else {
|
||||
Logger.shared.log("Failed to fetch filler info for MAL ID: \(malID)", type: "Error")
|
||||
print("Failed to fetch filler info for MAL ID: \(malID)")
|
||||
}
|
||||
|
||||
// Remove from in-progress set
|
||||
Self.inProgressQueue.async {
|
||||
Self.inProgressMALIDs.remove(malID)
|
||||
}
|
||||
|
|
@ -1044,7 +1012,7 @@ private extension EpisodeCell {
|
|||
|
||||
URLSession.shared.dataTask(with: url) { data, response, error in
|
||||
guard let data = data, error == nil else {
|
||||
Logger.shared.log("Jikan API request failed: \(error?.localizedDescription ?? "Unknown error")", type: "Error")
|
||||
print("Jikan API request failed: \(error?.localizedDescription ?? "Unknown error")")
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
|
|
@ -1053,7 +1021,7 @@ private extension EpisodeCell {
|
|||
let response = try JSONDecoder().decode(JikanResponse.self, from: data)
|
||||
completion(response.data)
|
||||
} catch {
|
||||
Logger.shared.log("Failed to parse Jikan response: \(error)", type: "Error")
|
||||
print("Failed to parse Jikan response: \(error)")
|
||||
completion(nil)
|
||||
}
|
||||
}.resume()
|
||||
|
|
@ -1063,7 +1031,7 @@ private extension EpisodeCell {
|
|||
let episodeNumber = episodeID + 1
|
||||
|
||||
guard episodeNumber <= episodes.count else {
|
||||
Logger.shared.log("Episode \(episodeNumber) not found in Jikan response", type: "Debug")
|
||||
print("Episode \(episodeNumber) not found in Jikan response")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1071,27 +1039,27 @@ private extension EpisodeCell {
|
|||
isFiller = jikanEpisode.filler
|
||||
|
||||
if jikanEpisode.filler {
|
||||
Logger.shared.log("Marking episode \(episodeNumber) as filler", type: "Debug")
|
||||
print("Marking episode \(episodeNumber) as filler")
|
||||
} else {
|
||||
Logger.shared.log("Episode \(episodeNumber) is not filler", type: "Debug")
|
||||
print("Episode \(episodeNumber) is not filler")
|
||||
}
|
||||
}
|
||||
|
||||
func handleFetchFailure(error: Error) {
|
||||
Logger.shared.log("Episode details fetch error: \(error.localizedDescription)", type: "Error")
|
||||
private func handleFetchFailure(error: Error) {
|
||||
print("Episode details fetch error: \(error.localizedDescription)")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if self.retryAttempts < self.maxRetryAttempts {
|
||||
self.retryAttempts += 1
|
||||
let backoffDelay = self.initialBackoffDelay * pow(2.0, Double(self.retryAttempts - 1))
|
||||
|
||||
Logger.shared.log("Will retry episode details fetch in \(backoffDelay) seconds", type: "Debug")
|
||||
print("Will retry episode details fetch in \(backoffDelay) seconds")
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + backoffDelay) {
|
||||
self.fetchAnimeEpisodeDetails()
|
||||
}
|
||||
} else {
|
||||
Logger.shared.log("Failed to fetch episode details after \(self.maxRetryAttempts) attempts", type: "Error")
|
||||
print("Failed to fetch episode details after \(self.maxRetryAttempts) attempts")
|
||||
self.isLoading = false
|
||||
self.retryAttempts = 0
|
||||
}
|
||||
|
|
@ -1150,9 +1118,6 @@ private struct AsyncImageView: View {
|
|||
.cornerRadius(8)
|
||||
} else if state.error != nil {
|
||||
placeholderView
|
||||
.onAppear {
|
||||
Logger.shared.log("Failed to load episode image: \(state.error?.localizedDescription ?? "Unknown error")", type: "Error")
|
||||
}
|
||||
} else {
|
||||
placeholderView
|
||||
}
|
||||
|
|
@ -1168,4 +1133,4 @@ private struct AsyncImageView: View {
|
|||
.frame(width: width, height: height)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue