diff --git a/Jel.xcodeproj/project.pbxproj b/Jel.xcodeproj/project.pbxproj index 473c177..1d3c55d 100644 --- a/Jel.xcodeproj/project.pbxproj +++ b/Jel.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ 3D91FDC92B28C62800919017 /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D91FDC82B28C62800919017 /* SignInView.swift */; }; 3D91FDCB2B28CA2500919017 /* SignInToServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D91FDCA2B28CA2500919017 /* SignInToServerView.swift */; }; 3D91FDCD2B2907E800919017 /* JellyfinDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D91FDCC2B2907E800919017 /* JellyfinDateFormatter.swift */; }; + 3DADBBC42B8A30DD006C242C /* ItemSeriesSelectableEpisodesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DADBBC32B8A30DD006C242C /* ItemSeriesSelectableEpisodesView.swift */; }; 3DAFA8E82B38AFED00D71AD1 /* ItemInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAFA8E72B38AFED00D71AD1 /* ItemInfoView.swift */; }; 3DAFA8EA2B39039900D71AD1 /* BaseItemDtoExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAFA8E92B39039900D71AD1 /* BaseItemDtoExtensions.swift */; }; 3DAFA8EC2B394F9F00D71AD1 /* ViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAFA8EB2B394F9F00D71AD1 /* ViewExtensions.swift */; }; @@ -102,6 +103,7 @@ 3D91FDC82B28C62800919017 /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = ""; }; 3D91FDCA2B28CA2500919017 /* SignInToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInToServerView.swift; sourceTree = ""; }; 3D91FDCC2B2907E800919017 /* JellyfinDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinDateFormatter.swift; sourceTree = ""; }; + 3DADBBC32B8A30DD006C242C /* ItemSeriesSelectableEpisodesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemSeriesSelectableEpisodesView.swift; sourceTree = ""; }; 3DAFA8E72B38AFED00D71AD1 /* ItemInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemInfoView.swift; sourceTree = ""; }; 3DAFA8E92B39039900D71AD1 /* BaseItemDtoExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseItemDtoExtensions.swift; sourceTree = ""; }; 3DAFA8EB2B394F9F00D71AD1 /* ViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewExtensions.swift; sourceTree = ""; }; @@ -194,6 +196,7 @@ 3D2552482B7A8B3100192879 /* ItemSeriesSeasonsView.swift */, 3DDF35D42B7D384000423923 /* ItemSeriesEpisodesView.swift */, 3DDF35D62B7D4D0100423923 /* ItemSeriesEpisodeIconView.swift */, + 3DADBBC32B8A30DD006C242C /* ItemSeriesSelectableEpisodesView.swift */, ); path = Series; sourceTree = ""; @@ -397,6 +400,7 @@ files = ( 3D6A0DC12B867B4C001FDA40 /* ScreenSize.swift in Sources */, 3D9063CD2B279A310063DD2A /* ContentView.swift in Sources */, + 3DADBBC42B8A30DD006C242C /* ItemSeriesSelectableEpisodesView.swift in Sources */, 3D13F96F2B38A32500E91913 /* StickyHeaderView.swift in Sources */, 3DF1ED3E2B282836000AD8EA /* JellyfinClientController.swift in Sources */, 3D1015D92B27F57400F5C29A /* AddServerView.swift in Sources */, diff --git a/Jel/Views/Item/ItemView.swift b/Jel/Views/Item/ItemView.swift index f8eba43..5afcc99 100644 --- a/Jel/Views/Item/ItemView.swift +++ b/Jel/Views/Item/ItemView.swift @@ -27,7 +27,7 @@ struct ItemView: View { } } } - .scrollIndicators(.hidden) +// .scrollIndicators(.hidden) } } diff --git a/Jel/Views/Item/Series/ItemSeriesEpisodeIconView.swift b/Jel/Views/Item/Series/ItemSeriesEpisodeIconView.swift index c198e66..46a5f5b 100644 --- a/Jel/Views/Item/Series/ItemSeriesEpisodeIconView.swift +++ b/Jel/Views/Item/Series/ItemSeriesEpisodeIconView.swift @@ -37,7 +37,6 @@ struct ItemSeriesEpisodeIconView: View { ExpandableText((item.overviewNL ?? "").replacingOccurrences(of: "
", with: "\n")) .foregroundColor(.secondary) } - .padding(.vertical) } } diff --git a/Jel/Views/Item/Series/ItemSeriesEpisodesView.swift b/Jel/Views/Item/Series/ItemSeriesEpisodesView.swift index 338f8a7..f0d95cf 100644 --- a/Jel/Views/Item/Series/ItemSeriesEpisodesView.swift +++ b/Jel/Views/Item/Series/ItemSeriesEpisodesView.swift @@ -10,36 +10,46 @@ import JellyfinKit struct ItemSeriesEpisodesView: View { @EnvironmentObject var jellyfinClient: JellyfinClientController - @EnvironmentObject var size: ScreenSize - @ObservedObject var authState: AuthStateController = AuthStateController.shared var item: BaseItemDto @State var episodeItems: [BaseItemDto] = [] + @State var loading: Bool = true var body: some View { - VStack(alignment: .leading) { + LazyVStack(alignment: .leading) { ForEach(episodeItems) {episode in ItemSeriesEpisodeIconView(item: episode) } } + .if(loading) {view in + view + .redacted(reason: .placeholder) + } + .onChange(of: item) { + self.loadEpisodes() + } .onAppear { - Task { - let parameters = Paths.GetItemsParameters( - userID: authState.userId, - parentID: item.id ?? "" - ) - let req = Paths.getItems(parameters: parameters) - - do { - let res = try await jellyfinClient.send(req) - episodeItems = res.value.items ?? [] - } catch {} - } + self.loadEpisodes() } } -} + + func loadEpisodes() { + Task { + let parameters = Paths.GetItemsParameters( + userID: authState.userId, + parentID: item.id ?? "" + ) + let req = Paths.getItems(parameters: parameters) + + do { + let res = try await jellyfinClient.send(req) + episodeItems = res.value.items ?? [] + loading = false + } catch {} + } + }} //#Preview { // ItemSeriesEpisodesView() diff --git a/Jel/Views/Item/Series/ItemSeriesSeasonsView.swift b/Jel/Views/Item/Series/ItemSeriesSeasonsView.swift index d5c269b..c1d2fd9 100644 --- a/Jel/Views/Item/Series/ItemSeriesSeasonsView.swift +++ b/Jel/Views/Item/Series/ItemSeriesSeasonsView.swift @@ -15,6 +15,7 @@ struct ItemSeriesSeasonsView: View { @ObservedObject var authState: AuthStateController = AuthStateController.shared @State var seriesItems: [BaseItemDto] = [] + @State var loading: Bool = true var body: some View { VStack(alignment: .leading) { @@ -34,9 +35,12 @@ struct ItemSeriesSeasonsView: View { } } }.padding(.horizontal) - } + }.scrollIndicators(.hidden) } - .onAppear{ + .if(loading) {view in + view.redacted(reason: .placeholder) + } + .onAppear { Task { let parameters = Paths.GetItemsParameters( userID: authState.userId ?? "", @@ -48,6 +52,8 @@ struct ItemSeriesSeasonsView: View { let res = try await jellyfinClient.send(req) seriesItems = res.value.items ?? [] } catch {} + + loading = false } } } diff --git a/Jel/Views/Item/Series/ItemSeriesSelectableEpisodesView.swift b/Jel/Views/Item/Series/ItemSeriesSelectableEpisodesView.swift new file mode 100644 index 0000000..30cb97d --- /dev/null +++ b/Jel/Views/Item/Series/ItemSeriesSelectableEpisodesView.swift @@ -0,0 +1,84 @@ +// +// ItemSeriesSelectableEpisodesView.swift +// Jel +// +// Created by zerocool on 2/24/24. +// + +import SwiftUI +import JellyfinKit + +struct Season: Identifiable, Hashable { + var name: String + var id: String + var season: BaseItemDto +} + +extension Season { + static let emptySelection = Season(name: "", id: "", season: BaseItemDto()) +} + +struct ItemSeriesSelectableEpisodesView: View { + @EnvironmentObject var jellyfinClient: JellyfinClientController + @StateObject var authState: AuthStateController = AuthStateController.shared + + var item: BaseItemDto // series + + @State var seasons: [Season] = [] + @State var currentSeason: Season = .emptySelection + @State var currentSeasonCopy: Season = .emptySelection + + @State var loading: Bool = true + var body: some View { + VStack(alignment: .leading) { + HStack { + Text("Episodes") + .font(.title2) + .padding(.horizontal) + + Picker("Season", selection: $currentSeason) { + ForEach(seasons, id: \.self) {season in + Text(season.name).tag(season) + } + } + } + + ItemSeriesEpisodesView(item: currentSeasonCopy.season) + .padding(.horizontal) + } + .if(loading) {view in + view.redacted(reason: .placeholder) + } + .onChange(of: currentSeason) { + currentSeasonCopy = currentSeason + } + .onAppear { + Task { + let parameters = Paths.GetItemsParameters( + userID: authState.userId ?? "", + parentID: item.id ?? "" + ) + let req = Paths.getItems(parameters: parameters) + + do { + let res = try await jellyfinClient.send(req) + seasons = [] + for season in res.value.items ?? [] { + let newSeason = Season( + name: season.name ?? "---", + id: season.id ?? "", + season: season + ) + seasons.append(newSeason) + currentSeason = seasons.first ?? .emptySelection + loading = false + } + } catch {} + } + } + } +} + +//#Preview { +// ItemSeriesSelectableEpisodesView() +//} diff --git a/Jel/Views/Item/Types/ItemSeriesView.swift b/Jel/Views/Item/Types/ItemSeriesView.swift index 6fc14a9..3054da6 100644 --- a/Jel/Views/Item/Types/ItemSeriesView.swift +++ b/Jel/Views/Item/Types/ItemSeriesView.swift @@ -34,7 +34,7 @@ struct ItemSeriesView: View { ItemGenresView(item: item) .foregroundStyle(Color.primary) - // TODO: Settable series with ItemSeriesEpisodesView + ItemSeriesSelectableEpisodesView(item: item) ItemSeriesSeasonsView(item: item) .foregroundStyle(Color.primary)