mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-21 08:32:00 +00:00
Quick fix 2
This commit is contained in:
parent
516bbf999f
commit
3dfee1c7fb
1 changed files with 60 additions and 56 deletions
|
|
@ -53,6 +53,12 @@ struct EpisodeCell: View {
|
||||||
@Environment(\.colorScheme) private var colorScheme
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
@AppStorage("selectedAppearance") private var selectedAppearance: Appearance = .system
|
@AppStorage("selectedAppearance") private var selectedAppearance: Appearance = .system
|
||||||
|
|
||||||
|
//Filler state & cache
|
||||||
|
@State private var isFiller: Bool = false
|
||||||
|
private static var fillerCache: [String: (fetchedAt: Date, episodes: Set<Int>)] = [:]
|
||||||
|
private static let fillerCacheQueue = DispatchQueue(label: "sora.filler.cache.queue", attributes: .concurrent)
|
||||||
|
private static let fillerCacheTTL: TimeInterval = 60 * 60 * 24
|
||||||
|
|
||||||
init(
|
init(
|
||||||
episodeIndex: Int,
|
episodeIndex: Int,
|
||||||
episode: String,
|
episode: String,
|
||||||
|
|
@ -89,7 +95,6 @@ struct EpisodeCell: View {
|
||||||
self.tmdbID = tmdbID
|
self.tmdbID = tmdbID
|
||||||
self.seasonNumber = seasonNumber
|
self.seasonNumber = seasonNumber
|
||||||
|
|
||||||
|
|
||||||
let isLightMode = (UserDefaults.standard.string(forKey: "selectedAppearance") == "light") ||
|
let isLightMode = (UserDefaults.standard.string(forKey: "selectedAppearance") == "light") ||
|
||||||
((UserDefaults.standard.string(forKey: "selectedAppearance") == "system") &&
|
((UserDefaults.standard.string(forKey: "selectedAppearance") == "system") &&
|
||||||
UITraitCollection.current.userInterfaceStyle == .light)
|
UITraitCollection.current.userInterfaceStyle == .light)
|
||||||
|
|
@ -101,14 +106,6 @@ struct EpisodeCell: View {
|
||||||
(isLightMode ? defaultLightBanner : defaultDarkBanner) : defaultBannerImage
|
(isLightMode ? defaultLightBanner : defaultDarkBanner) : defaultBannerImage
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Filler state & cache
|
|
||||||
@State private var isFiller: Bool = false
|
|
||||||
|
|
||||||
/// Simple thread-safe in-memory cache: slug -> (fetchedAt, episodes)
|
|
||||||
private static var fillerCache: [String: (fetchedAt: Date, episodes: Set<Int>)] = [:]
|
|
||||||
private static let fillerCacheQueue = DispatchQueue(label: "sora.filler.cache.queue", attributes: .concurrent)
|
|
||||||
private static let fillerCacheTTL: TimeInterval = 60 * 60 * 24 // 24 hours
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
actionButtonsBackground
|
actionButtonsBackground
|
||||||
|
|
@ -232,10 +229,9 @@ private extension EpisodeCell {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
Text("Episode \(episodeID + 1)")
|
Text("Episode \(episodeID + 1)")
|
||||||
.font(.system(size: 15))
|
.font(.system(size: 15))
|
||||||
.foregroundColor(isFiller ? .red : .primary)
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
if isFiller {
|
if isFiller {
|
||||||
// Modern capsule badge that matches subtle UI
|
|
||||||
Text("Filler")
|
Text("Filler")
|
||||||
.font(.system(size: 12, weight: .semibold))
|
.font(.system(size: 12, weight: .semibold))
|
||||||
.padding(.horizontal, 8)
|
.padding(.horizontal, 8)
|
||||||
|
|
@ -542,7 +538,7 @@ private extension EpisodeCell {
|
||||||
} else {
|
} else {
|
||||||
fetchAnimeEpisodeDetails()
|
fetchAnimeEpisodeDetails()
|
||||||
}
|
}
|
||||||
// fetch filler info (non-blocking) — uses cache internally
|
// Fetch filler info in parallel with episode details
|
||||||
fetchFillerInfo()
|
fetchFillerInfo()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -694,7 +690,7 @@ private extension EpisodeCell {
|
||||||
Logger.shared.log("Started download for Episode \(self.episodeID + 1): \(self.episode)", type: "Download")
|
Logger.shared.log("Started download for Episode \(self.episodeID + 1): \(self.episode)", type: "Download")
|
||||||
AnalyticsManager.shared.sendEvent(
|
AnalyticsManager.shared.sendEvent(
|
||||||
event: "download",
|
event: "download",
|
||||||
additionalData: ["episode": self.episodeID + 1, "url": streamUrl]
|
additionalData: {"episode": self.episodeID + 1, "url": streamUrl}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
DropManager.shared.error(message)
|
DropManager.shared.error(message)
|
||||||
|
|
@ -964,59 +960,67 @@ private extension EpisodeCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchFillerInfo() {
|
func fetchFillerInfo() {
|
||||||
let raw = parentTitle.trimmingCharacters(in: .whitespacesAndNewlines)
|
let raw = parentTitle.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
guard !raw.isEmpty else { return }
|
guard !raw.isEmpty else { return }
|
||||||
|
|
||||||
var slug = raw.lowercased()
|
var slug = raw.lowercased()
|
||||||
slug = slug.replacingOccurrences(of: " ", with: "-")
|
slug = slug.replacingOccurrences(of: " ", with: "-")
|
||||||
slug = slug.components(separatedBy: CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-")).inverted).joined()
|
slug = slug.components(separatedBy: CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-")).inverted).joined()
|
||||||
let epNum = self.episodeID + 1
|
let epNum = self.episodeID + 1
|
||||||
|
|
||||||
var cachedEpisodes: Set<Int>? = nil
|
// Check cache first
|
||||||
Self.fillerCacheQueue.sync {
|
var cachedEpisodes: Set<Int>? = nil
|
||||||
if let entry = Self.fillerCache[slug] {
|
Self.fillerCacheQueue.sync {
|
||||||
if Date().timeIntervalSince(entry.fetchedAt) < Self.fillerCacheTTL {
|
if let entry = Self.fillerCache[slug], Date().timeIntervalSince(entry.fetchedAt) < Self.fillerCacheTTL {
|
||||||
cachedEpisodes = entry.episodes
|
cachedEpisodes = entry.episodes
|
||||||
} else {
|
|
||||||
Self.fillerCacheQueue.async(flags: .barrier) {
|
|
||||||
Self.fillerCache[slug] = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if let set = cachedEpisodes {
|
if let set = cachedEpisodes {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.isFiller = set.contains(epNum)
|
self.isFiller = set.contains(epNum)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
// Not in cache or expired, fetch from API
|
||||||
|
guard let url = URL(string: "https://sora-filler-episodes-api.jmcrafter26.workers.dev/\(slug)") else { return }
|
||||||
guard let url = URL(string: "https://sora-filler-episodes-api.jmcrafter26.workers.dev/\(slug)") else { return }
|
|
||||||
|
URLSession.shared.dataTask(with: url) { data, response, error in
|
||||||
URLSession.shared.dataTask(with: url) { data, _, error in
|
var episodesSet = Set<Int>()
|
||||||
guard let data = data, error == nil else { return }
|
|
||||||
do {
|
defer {
|
||||||
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
// Cache the result (even if empty) and update UI
|
||||||
let fillerArray = json["fillerEpisodes"] as? [String],
|
|
||||||
let fillerString = fillerArray.first {
|
|
||||||
|
|
||||||
let numbers = fillerString.split(separator: ",").compactMap { Int($0.trimmingCharacters(in: .whitespaces)) }
|
|
||||||
let episodesSet = Set(numbers)
|
|
||||||
|
|
||||||
Self.fillerCacheQueue.async(flags: .barrier) {
|
Self.fillerCacheQueue.async(flags: .barrier) {
|
||||||
Self.fillerCache[slug] = (fetchedAt: Date(), episodes: episodesSet)
|
Self.fillerCache[slug] = (fetchedAt: Date(), episodes: episodesSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
let isF = episodesSet.contains(epNum)
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.isFiller = isF
|
self.isFiller = episodesSet.contains(epNum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
print("Filler parse error: \(error)")
|
// Handle API errors or empty responses
|
||||||
}
|
guard let data = data, error == nil else {
|
||||||
}.resume()
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||||
|
let fillerArray = json["fillerEpisodes"] as? [String],
|
||||||
|
!fillerArray.isEmpty {
|
||||||
|
|
||||||
|
let fillerString = fillerArray[0]
|
||||||
|
let numbers = fillerString.split(separator: ",").compactMap {
|
||||||
|
Int($0.trimmingCharacters(in: .whitespaces))
|
||||||
|
}
|
||||||
|
episodesSet = Set(numbers)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Filler parse error: \(error)")
|
||||||
|
}
|
||||||
|
}.resume()
|
||||||
|
}
|
||||||
|
|
||||||
func handleFetchFailure(error: Error) {
|
func handleFetchFailure(error: Error) {
|
||||||
Logger.shared.log("Episode details fetch error: \(error.localizedDescription)", type: "Error")
|
Logger.shared.log("Episode details fetch error: \(error.localizedDescription)", type: "Error")
|
||||||
|
|
@ -1109,4 +1113,4 @@ private struct AsyncImageView: View {
|
||||||
.frame(width: width, height: height)
|
.frame(width: width, height: height)
|
||||||
.cornerRadius(8)
|
.cornerRadius(8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue