diff --git a/Sora.xcodeproj/project.pbxproj b/Sora.xcodeproj/project.pbxproj index f73138d..5310197 100644 --- a/Sora.xcodeproj/project.pbxproj +++ b/Sora.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 131845F92D47C62D00CA7A54 /* SettingsViewUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131845F82D47C62D00CA7A54 /* SettingsViewUI.swift */; }; 133D7C6E2D2BE2500075467E /* SoraApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C6D2D2BE2500075467E /* SoraApp.swift */; }; 133D7C702D2BE2500075467E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C6F2D2BE2500075467E /* ContentView.swift */; }; 133D7C722D2BE2520075467E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 133D7C712D2BE2520075467E /* Assets.xcassets */; }; @@ -40,6 +41,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 131845F82D47C62D00CA7A54 /* SettingsViewUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewUI.swift; sourceTree = ""; }; 133D7C6A2D2BE2500075467E /* Sora.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sora.app; sourceTree = BUILT_PRODUCTS_DIR; }; 133D7C6D2D2BE2500075467E /* SoraApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoraApp.swift; sourceTree = ""; }; 133D7C6F2D2BE2500075467E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -152,6 +154,7 @@ 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */, 1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */, 133D7C842D2BE2630075467E /* SettingsViewModule.swift */, + 131845F82D47C62D00CA7A54 /* SettingsViewUI.swift */, ); path = SettingsSubViews; sourceTree = ""; @@ -368,6 +371,7 @@ 133D7C702D2BE2500075467E /* ContentView.swift in Sources */, 13EA2BD62D32D97400C1EBD7 /* Double+Extension.swift in Sources */, 133D7C8F2D2BE2640075467E /* MediaInfoView.swift in Sources */, + 131845F92D47C62D00CA7A54 /* SettingsViewUI.swift in Sources */, 133D7C8D2D2BE2640075467E /* HomeView.swift in Sources */, 13EA2BDC2D32D9FF00C1EBD7 /* MiruDataStruct.swift in Sources */, 13D842552D45267500EBBFA6 /* DropManager.swift in Sources */, diff --git a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift index 9e6e6fe..0fa38f0 100644 --- a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift +++ b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift @@ -89,13 +89,6 @@ struct EpisodeCell: View { } func fetchEpisodeDetails() { - let cacheKey = "episodeDetails_\(itemID)_\(episodeID)" - - if let cachedData = UserDefaults.standard.data(forKey: cacheKey) { - parseEpisodeDetails(data: cachedData) - return - } - guard let url = URL(string: "https://api.ani.zip/mappings?anilist_id=\(itemID)") else { isLoading = false return @@ -117,39 +110,30 @@ struct EpisodeCell: View { return } - DispatchQueue.main.async { - self.parseEpisodeDetails(data: data) - UserDefaults.standard.set(data, forKey: cacheKey) - self.isLoading = false + do { + let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) + guard let json = jsonObject as? [String: Any], + let episodes = json["episodes"] as? [String: Any], + let episodeDetails = episodes["\(episodeID + 1)"] as? [String: Any], + let title = episodeDetails["title"] as? [String: String], + let image = episodeDetails["image"] as? String else { + Logger.shared.log("Invalid response format", type: "Error") + DispatchQueue.main.async { + self.isLoading = false + } + return + } + + DispatchQueue.main.async { + self.episodeTitle = title["en"] ?? "" + self.episodeImageUrl = image + self.isLoading = false + } + } catch { + DispatchQueue.main.async { + self.isLoading = false + } } }.resume() } - - func parseEpisodeDetails(data: Data) { - do { - let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) - guard let json = jsonObject as? [String: Any], - let episodes = json["episodes"] as? [String: Any], - let episodeDetails = episodes["\(episodeID + 1)"] as? [String: Any], - let title = episodeDetails["title"] as? [String: String], - let image = episodeDetails["image"] as? String else { - Logger.shared.log("Invalid response format", type: "Error") - DispatchQueue.main.async { - self.isLoading = false - } - return - } - - DispatchQueue.main.async { - self.episodeTitle = title["en"] ?? "" - self.episodeImageUrl = image - self.isLoading = false - } - } catch { - Logger.shared.log("Failed to parse JSON: \(error)", type: "Error") - DispatchQueue.main.async { - self.isLoading = false - } - } - } } diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index 230d9c6..ae9f2c2 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -34,11 +34,14 @@ struct MediaInfoView: View { @State var isRefetching: Bool = true @AppStorage("externalPlayer") private var externalPlayer: String = "Default" + @AppStorage("episodeChunkSize") private var episodeChunkSize: Int = 100 @StateObject private var jsController = JSController() @EnvironmentObject var moduleManager: ModuleManager @EnvironmentObject private var libraryManager: LibraryManager + @State private var selectedRange: Range = 0..<100 + var body: some View { Group { if isLoading { @@ -164,11 +167,31 @@ struct MediaInfoView: View { if !episodeLinks.isEmpty { VStack(alignment: .leading, spacing: 10) { - Text("Episodes") - .font(.system(size: 18)) - .fontWeight(.bold) + HStack { + Text("Episodes") + .font(.system(size: 18)) + .fontWeight(.bold) + + Spacer() + + if episodeLinks.count > episodeChunkSize { + Menu { + ForEach(generateRanges(), id: \.self) { range in + Button(action: { + selectedRange = range + }) { + Text("\(range.lowerBound + 1)-\(range.upperBound)") + } + } + } label: { + Text("\(selectedRange.lowerBound + 1)-\(selectedRange.upperBound)") + .font(.system(size: 14)) + .foregroundColor(.accentColor) + } + } + } - ForEach(episodeLinks.indices, id: \.self) { i in + 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)") @@ -233,9 +256,23 @@ struct MediaInfoView: View { } hasFetched = true } + selectedRange = 0.. [Range] { + let chunkSize = episodeChunkSize + let totalEpisodes = episodeLinks.count + var ranges: [Range] = [] + + for i in stride(from: 0, to: totalEpisodes, by: chunkSize) { + let end = min(i + chunkSize, totalEpisodes) + ranges.append(i..