mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-21 00:22:12 +00:00
episodes range + critical bug
the critical bug was an issue of the episodes response being cached in the userdefaults causing more than 120mb to be saved for 1k episodes
This commit is contained in:
parent
a75ca179ce
commit
e2252e13d0
5 changed files with 124 additions and 57 deletions
|
|
@ -7,6 +7,7 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* 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 */; };
|
133D7C6E2D2BE2500075467E /* SoraApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C6D2D2BE2500075467E /* SoraApp.swift */; };
|
||||||
133D7C702D2BE2500075467E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C6F2D2BE2500075467E /* ContentView.swift */; };
|
133D7C702D2BE2500075467E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C6F2D2BE2500075467E /* ContentView.swift */; };
|
||||||
133D7C722D2BE2520075467E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 133D7C712D2BE2520075467E /* Assets.xcassets */; };
|
133D7C722D2BE2520075467E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 133D7C712D2BE2520075467E /* Assets.xcassets */; };
|
||||||
|
|
@ -40,6 +41,7 @@
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
131845F82D47C62D00CA7A54 /* SettingsViewUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewUI.swift; sourceTree = "<group>"; };
|
||||||
133D7C6A2D2BE2500075467E /* Sora.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sora.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
133D7C6D2D2BE2500075467E /* SoraApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoraApp.swift; sourceTree = "<group>"; };
|
||||||
133D7C6F2D2BE2500075467E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
133D7C6F2D2BE2500075467E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -152,6 +154,7 @@
|
||||||
1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */,
|
1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */,
|
||||||
1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */,
|
1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */,
|
||||||
133D7C842D2BE2630075467E /* SettingsViewModule.swift */,
|
133D7C842D2BE2630075467E /* SettingsViewModule.swift */,
|
||||||
|
131845F82D47C62D00CA7A54 /* SettingsViewUI.swift */,
|
||||||
);
|
);
|
||||||
path = SettingsSubViews;
|
path = SettingsSubViews;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -368,6 +371,7 @@
|
||||||
133D7C702D2BE2500075467E /* ContentView.swift in Sources */,
|
133D7C702D2BE2500075467E /* ContentView.swift in Sources */,
|
||||||
13EA2BD62D32D97400C1EBD7 /* Double+Extension.swift in Sources */,
|
13EA2BD62D32D97400C1EBD7 /* Double+Extension.swift in Sources */,
|
||||||
133D7C8F2D2BE2640075467E /* MediaInfoView.swift in Sources */,
|
133D7C8F2D2BE2640075467E /* MediaInfoView.swift in Sources */,
|
||||||
|
131845F92D47C62D00CA7A54 /* SettingsViewUI.swift in Sources */,
|
||||||
133D7C8D2D2BE2640075467E /* HomeView.swift in Sources */,
|
133D7C8D2D2BE2640075467E /* HomeView.swift in Sources */,
|
||||||
13EA2BDC2D32D9FF00C1EBD7 /* MiruDataStruct.swift in Sources */,
|
13EA2BDC2D32D9FF00C1EBD7 /* MiruDataStruct.swift in Sources */,
|
||||||
13D842552D45267500EBBFA6 /* DropManager.swift in Sources */,
|
13D842552D45267500EBBFA6 /* DropManager.swift in Sources */,
|
||||||
|
|
|
||||||
|
|
@ -89,13 +89,6 @@ struct EpisodeCell: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchEpisodeDetails() {
|
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 {
|
guard let url = URL(string: "https://api.ani.zip/mappings?anilist_id=\(itemID)") else {
|
||||||
isLoading = false
|
isLoading = false
|
||||||
return
|
return
|
||||||
|
|
@ -117,39 +110,30 @@ struct EpisodeCell: View {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
do {
|
||||||
self.parseEpisodeDetails(data: data)
|
let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
|
||||||
UserDefaults.standard.set(data, forKey: cacheKey)
|
guard let json = jsonObject as? [String: Any],
|
||||||
self.isLoading = false
|
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()
|
}.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,14 @@ struct MediaInfoView: View {
|
||||||
@State var isRefetching: Bool = true
|
@State var isRefetching: Bool = true
|
||||||
|
|
||||||
@AppStorage("externalPlayer") private var externalPlayer: String = "Default"
|
@AppStorage("externalPlayer") private var externalPlayer: String = "Default"
|
||||||
|
@AppStorage("episodeChunkSize") private var episodeChunkSize: Int = 100
|
||||||
|
|
||||||
@StateObject private var jsController = JSController()
|
@StateObject private var jsController = JSController()
|
||||||
@EnvironmentObject var moduleManager: ModuleManager
|
@EnvironmentObject var moduleManager: ModuleManager
|
||||||
@EnvironmentObject private var libraryManager: LibraryManager
|
@EnvironmentObject private var libraryManager: LibraryManager
|
||||||
|
|
||||||
|
@State private var selectedRange: Range<Int> = 0..<100
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if isLoading {
|
if isLoading {
|
||||||
|
|
@ -164,11 +167,31 @@ struct MediaInfoView: View {
|
||||||
|
|
||||||
if !episodeLinks.isEmpty {
|
if !episodeLinks.isEmpty {
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
Text("Episodes")
|
HStack {
|
||||||
.font(.system(size: 18))
|
Text("Episodes")
|
||||||
.fontWeight(.bold)
|
.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 ep = episodeLinks[i]
|
||||||
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(ep.href)")
|
let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(ep.href)")
|
||||||
let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(ep.href)")
|
let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(ep.href)")
|
||||||
|
|
@ -233,9 +256,23 @@ struct MediaInfoView: View {
|
||||||
}
|
}
|
||||||
hasFetched = true
|
hasFetched = true
|
||||||
}
|
}
|
||||||
|
selectedRange = 0..<episodeChunkSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func generateRanges() -> [Range<Int>] {
|
||||||
|
let chunkSize = episodeChunkSize
|
||||||
|
let totalEpisodes = episodeLinks.count
|
||||||
|
var ranges: [Range<Int>] = []
|
||||||
|
|
||||||
|
for i in stride(from: 0, to: totalEpisodes, by: chunkSize) {
|
||||||
|
let end = min(i + chunkSize, totalEpisodes)
|
||||||
|
ranges.append(i..<end)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ranges
|
||||||
|
}
|
||||||
|
|
||||||
func fetchDetails() {
|
func fetchDetails() {
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
Task {
|
Task {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
//
|
||||||
|
// SettingsViewUI.swift
|
||||||
|
// Sora
|
||||||
|
//
|
||||||
|
// Created by Francesco on 27/01/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SettingsViewUI: View {
|
||||||
|
@AppStorage("episodeChunkSize") private var episodeChunkSize: Int = 100
|
||||||
|
@EnvironmentObject var settings: Settings
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Form {
|
||||||
|
Section(header: Text("Interface")) {
|
||||||
|
ColorPicker("Accent Color", selection: $settings.accentColor)
|
||||||
|
HStack() {
|
||||||
|
Text("Appearance")
|
||||||
|
Picker("Appearance", selection: $settings.selectedAppearance) {
|
||||||
|
Text("System").tag(Appearance.system)
|
||||||
|
Text("Light").tag(Appearance.light)
|
||||||
|
Text("Dark").tag(Appearance.dark)
|
||||||
|
}
|
||||||
|
.pickerStyle(SegmentedPickerStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Section(header: Text("Episode Chunk Size")) {
|
||||||
|
Menu {
|
||||||
|
Button(action: { episodeChunkSize = 25 }) {
|
||||||
|
Text("25")
|
||||||
|
}
|
||||||
|
Button(action: { episodeChunkSize = 50 }) {
|
||||||
|
Text("50")
|
||||||
|
}
|
||||||
|
Button(action: { episodeChunkSize = 75 }) {
|
||||||
|
Text("75")
|
||||||
|
}
|
||||||
|
Button(action: { episodeChunkSize = 100 }) {
|
||||||
|
Text("100")
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text("Chunk Size: \(episodeChunkSize)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("UI Settings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,28 +8,20 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
@EnvironmentObject var settings: Settings
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
Form {
|
Form {
|
||||||
Section(header: Text("Interface")) {
|
Section(header: Text("Main Settings")) {
|
||||||
ColorPicker("Accent Color", selection: $settings.accentColor)
|
NavigationLink(destination: SettingsViewUI()) {
|
||||||
HStack() {
|
Text("UI Settings")
|
||||||
Text("Appearance")
|
|
||||||
Picker("Appearance", selection: $settings.selectedAppearance) {
|
|
||||||
Text("System").tag(Appearance.system)
|
|
||||||
Text("Light").tag(Appearance.light)
|
|
||||||
Text("Dark").tag(Appearance.dark)
|
|
||||||
}
|
|
||||||
.pickerStyle(SegmentedPickerStyle())
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Section(header: Text("External Features")) {
|
|
||||||
NavigationLink(destination: SettingsViewModule()) {
|
NavigationLink(destination: SettingsViewModule()) {
|
||||||
Text("Modules")
|
Text("Modules")
|
||||||
}
|
}
|
||||||
|
NavigationLink(destination: SettingsViewModule()) {
|
||||||
|
Text("Media Player")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(header: Text("Debug")) {
|
Section(header: Text("Debug")) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue