Merge pull request #261 from LePips/collections-support
This commit is contained in:
commit
27ccd5533b
|
@ -0,0 +1,69 @@
|
||||||
|
//
|
||||||
|
/*
|
||||||
|
* SwiftFin is subject to the terms of the Mozilla Public
|
||||||
|
* License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*
|
||||||
|
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Defaults
|
||||||
|
import Introspect
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CinematicCollectionItemView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject var itemRouter: ItemCoordinator.Router
|
||||||
|
@ObservedObject var viewModel: CollectionItemViewModel
|
||||||
|
@State var wrappedScrollView: UIScrollView?
|
||||||
|
@Default(.showPosterLabels) var showPosterLabels
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
|
||||||
|
ImageView(src: viewModel.item.getBackdropImage(maxWidth: 1920),
|
||||||
|
bh: viewModel.item.getBackdropImageBlurHash())
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
|
||||||
|
CinematicItemViewTopRow(viewModel: viewModel,
|
||||||
|
wrappedScrollView: wrappedScrollView,
|
||||||
|
title: viewModel.item.name ?? "",
|
||||||
|
showDetails: false)
|
||||||
|
.focusSection()
|
||||||
|
.frame(height: UIScreen.main.bounds.height - 10)
|
||||||
|
|
||||||
|
ZStack(alignment: .topLeading) {
|
||||||
|
|
||||||
|
Color.black.ignoresSafeArea()
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 20) {
|
||||||
|
|
||||||
|
CinematicItemAboutView(viewModel: viewModel)
|
||||||
|
|
||||||
|
PortraitItemsRowView(rowTitle: "Items",
|
||||||
|
items: viewModel.collectionItems) { item in
|
||||||
|
itemRouter.route(to: \.item, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !viewModel.similarItems.isEmpty {
|
||||||
|
PortraitItemsRowView(rowTitle: "Recommended",
|
||||||
|
items: viewModel.similarItems,
|
||||||
|
showItemTitles: showPosterLabels) { item in
|
||||||
|
itemRouter.route(to: \.item, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.vertical, 50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.introspectScrollView { scrollView in
|
||||||
|
wrappedScrollView = scrollView
|
||||||
|
}
|
||||||
|
.ignoresSafeArea()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,19 @@ struct CinematicItemViewTopRow: View {
|
||||||
@State var wrappedScrollView: UIScrollView?
|
@State var wrappedScrollView: UIScrollView?
|
||||||
@State var title: String
|
@State var title: String
|
||||||
@State var subtitle: String?
|
@State var subtitle: String?
|
||||||
|
let showDetails: Bool
|
||||||
|
|
||||||
|
init(viewModel: ItemViewModel,
|
||||||
|
wrappedScrollView: UIScrollView? = nil,
|
||||||
|
title: String,
|
||||||
|
subtitle: String? = nil,
|
||||||
|
showDetails: Bool = true) {
|
||||||
|
self.viewModel = viewModel
|
||||||
|
self.wrappedScrollView = wrappedScrollView
|
||||||
|
self.title = title
|
||||||
|
self.subtitle = subtitle
|
||||||
|
self.showDetails = showDetails
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .bottom) {
|
ZStack(alignment: .bottom) {
|
||||||
|
@ -69,35 +82,39 @@ struct CinematicItemViewTopRow: View {
|
||||||
|
|
||||||
HStack(alignment: .PlayInformationAlignmentGuide, spacing: 20) {
|
HStack(alignment: .PlayInformationAlignmentGuide, spacing: 20) {
|
||||||
|
|
||||||
if viewModel.item.itemType == .series {
|
if showDetails {
|
||||||
if let airTime = viewModel.item.airTime {
|
if viewModel.item.itemType == .series {
|
||||||
Text(airTime)
|
if let airTime = viewModel.item.airTime {
|
||||||
.font(.subheadline)
|
Text(airTime)
|
||||||
.fontWeight(.medium)
|
.font(.subheadline)
|
||||||
}
|
.fontWeight(.medium)
|
||||||
} else {
|
}
|
||||||
if let runtime = viewModel.item.getItemRuntime() {
|
} else {
|
||||||
Text(runtime)
|
if let runtime = viewModel.item.getItemRuntime() {
|
||||||
.font(.subheadline)
|
Text(runtime)
|
||||||
.fontWeight(.medium)
|
.font(.subheadline)
|
||||||
|
.fontWeight(.medium)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let productionYear = viewModel.item.productionYear {
|
||||||
|
Text(String(productionYear))
|
||||||
|
.font(.subheadline)
|
||||||
|
.fontWeight(.medium)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let productionYear = viewModel.item.productionYear {
|
if let officialRating = viewModel.item.officialRating {
|
||||||
Text(String(productionYear))
|
Text(officialRating)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.fontWeight(.medium)
|
.fontWeight(.semibold)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
|
||||||
|
.overlay(RoundedRectangle(cornerRadius: 2)
|
||||||
|
.stroke(Color.secondary, lineWidth: 1))
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
Text("")
|
||||||
if let officialRating = viewModel.item.officialRating {
|
|
||||||
Text(officialRating)
|
|
||||||
.font(.subheadline)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.lineLimit(1)
|
|
||||||
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
|
|
||||||
.overlay(RoundedRectangle(cornerRadius: 2)
|
|
||||||
.stroke(Color.secondary, lineWidth: 1))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
|
|
@ -60,6 +60,8 @@ struct ItemView: View {
|
||||||
} else {
|
} else {
|
||||||
SeriesItemView(viewModel: SeriesItemViewModel(item: item))
|
SeriesItemView(viewModel: SeriesItemViewModel(item: item))
|
||||||
}
|
}
|
||||||
|
case .boxset:
|
||||||
|
CinematicCollectionItemView(viewModel: CollectionItemViewModel(item: item))
|
||||||
default:
|
default:
|
||||||
Text(L10n.notImplementedYetWithType(item.type ?? ""))
|
Text(L10n.notImplementedYetWithType(item.type ?? ""))
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,38 +22,38 @@ struct LibraryListView: View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVStack {
|
LazyVStack {
|
||||||
if !viewModel.isLoading {
|
if !viewModel.isLoading {
|
||||||
ForEach(viewModel.libraries, id: \.id) { library in
|
|
||||||
if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" || library.collectionType ?? "" == "music" {
|
if let collectionLibraryItem = viewModel.libraries.first(where: { $0.collectionType == "boxsets" }) {
|
||||||
EmptyView()
|
Button() {
|
||||||
} else {
|
self.libraryListRouter.route(to: \.library,
|
||||||
if library.collectionType == "livetv" {
|
(viewModel: LibraryViewModel(parentID: collectionLibraryItem.id), title: collectionLibraryItem.name ?? ""))
|
||||||
if liveTVAlphaEnabled {
|
}
|
||||||
Button() {
|
label: {
|
||||||
self.mainCoordinator.root(\.liveTV)
|
ZStack {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
Text(collectionLibraryItem.name ?? "")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.font(.title2)
|
||||||
|
.fontWeight(.semibold)
|
||||||
}
|
}
|
||||||
label: {
|
Spacer()
|
||||||
ZStack {
|
}.padding(32)
|
||||||
HStack {
|
}
|
||||||
Spacer()
|
.frame(minWidth: 100, maxWidth: .infinity)
|
||||||
VStack {
|
.frame(height: 100)
|
||||||
Text(library.name ?? "")
|
}
|
||||||
.foregroundColor(.white)
|
.cornerRadius(10)
|
||||||
.font(.title2)
|
.shadow(radius: 5)
|
||||||
.fontWeight(.semibold)
|
.padding(.bottom, 5)
|
||||||
}
|
}
|
||||||
Spacer()
|
|
||||||
}.padding(32)
|
ForEach(viewModel.libraries.filter({ $0.collectionType != "boxsets" }), id: \.id) { library in
|
||||||
}
|
if library.collectionType == "livetv" {
|
||||||
.frame(minWidth: 100, maxWidth: .infinity)
|
if liveTVAlphaEnabled {
|
||||||
.frame(height: 100)
|
|
||||||
}
|
|
||||||
.cornerRadius(10)
|
|
||||||
.shadow(radius: 5)
|
|
||||||
.padding(.bottom, 5)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Button() {
|
Button() {
|
||||||
self.libraryListRouter.route(to: \.library, (viewModel: LibraryViewModel(), title: library.name ?? ""))
|
self.mainCoordinator.root(\.liveTV)
|
||||||
}
|
}
|
||||||
label: {
|
label: {
|
||||||
ZStack {
|
ZStack {
|
||||||
|
@ -75,6 +75,29 @@ struct LibraryListView: View {
|
||||||
.shadow(radius: 5)
|
.shadow(radius: 5)
|
||||||
.padding(.bottom, 5)
|
.padding(.bottom, 5)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Button() {
|
||||||
|
self.libraryListRouter.route(to: \.library, (viewModel: LibraryViewModel(), title: library.name ?? ""))
|
||||||
|
}
|
||||||
|
label: {
|
||||||
|
ZStack {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
Text(library.name ?? "")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.font(.title2)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}.padding(32)
|
||||||
|
}
|
||||||
|
.frame(minWidth: 100, maxWidth: .infinity)
|
||||||
|
.frame(height: 100)
|
||||||
|
}
|
||||||
|
.cornerRadius(10)
|
||||||
|
.shadow(radius: 5)
|
||||||
|
.padding(.bottom, 5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -246,6 +246,9 @@
|
||||||
C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */; };
|
C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */; };
|
||||||
C4E52305272CE68800654268 /* LiveTVChannelItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */; };
|
C4E52305272CE68800654268 /* LiveTVChannelItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */; };
|
||||||
E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */; };
|
E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */; };
|
||||||
|
E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */; };
|
||||||
|
E107BB9427880A8F00354E07 /* CollectionItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */; };
|
||||||
|
E107BB972788104100354E07 /* CinematicCollectionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107BB952788104100354E07 /* CinematicCollectionItemView.swift */; };
|
||||||
E10D87DA2784E4F100BD264C /* ItemViewDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10D87D92784E4F100BD264C /* ItemViewDetailsView.swift */; };
|
E10D87DA2784E4F100BD264C /* ItemViewDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10D87D92784E4F100BD264C /* ItemViewDetailsView.swift */; };
|
||||||
E10D87DC2784EC5200BD264C /* EpisodesRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10D87DB2784EC5200BD264C /* EpisodesRowView.swift */; };
|
E10D87DC2784EC5200BD264C /* EpisodesRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10D87DB2784EC5200BD264C /* EpisodesRowView.swift */; };
|
||||||
E10D87DE278510E400BD264C /* PosterSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10D87DD278510E300BD264C /* PosterSize.swift */; };
|
E10D87DE278510E400BD264C /* PosterSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10D87DD278510E300BD264C /* PosterSize.swift */; };
|
||||||
|
@ -628,6 +631,8 @@
|
||||||
C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchView.swift; sourceTree = "<group>"; };
|
C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchView.swift; sourceTree = "<group>"; };
|
||||||
C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelItemElement.swift; sourceTree = "<group>"; };
|
C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelItemElement.swift; sourceTree = "<group>"; };
|
||||||
E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayButtonRowView.swift; sourceTree = "<group>"; };
|
E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayButtonRowView.swift; sourceTree = "<group>"; };
|
||||||
|
E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
E107BB952788104100354E07 /* CinematicCollectionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicCollectionItemView.swift; sourceTree = "<group>"; };
|
||||||
E10D87D92784E4F100BD264C /* ItemViewDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewDetailsView.swift; sourceTree = "<group>"; };
|
E10D87D92784E4F100BD264C /* ItemViewDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewDetailsView.swift; sourceTree = "<group>"; };
|
||||||
E10D87DB2784EC5200BD264C /* EpisodesRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodesRowView.swift; sourceTree = "<group>"; };
|
E10D87DB2784EC5200BD264C /* EpisodesRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodesRowView.swift; sourceTree = "<group>"; };
|
||||||
E10D87DD278510E300BD264C /* PosterSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosterSize.swift; sourceTree = "<group>"; };
|
E10D87DD278510E300BD264C /* PosterSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosterSize.swift; sourceTree = "<group>"; };
|
||||||
|
@ -817,22 +822,18 @@
|
||||||
children = (
|
children = (
|
||||||
E1D4BF7D2719D1DC00A11E64 /* BasicAppSettingsViewModel.swift */,
|
E1D4BF7D2719D1DC00A11E64 /* BasicAppSettingsViewModel.swift */,
|
||||||
625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */,
|
625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */,
|
||||||
62E632E5267D3F5B0063E547 /* EpisodeItemViewModel.swift */,
|
|
||||||
E10D87E127852FD000BD264C /* EpisodesRowViewModel.swift */,
|
E10D87E127852FD000BD264C /* EpisodesRowViewModel.swift */,
|
||||||
625CB5722678C32A00530A6E /* HomeViewModel.swift */,
|
625CB5722678C32A00530A6E /* HomeViewModel.swift */,
|
||||||
62E632F2267D54030063E547 /* ItemViewModel.swift */,
|
E107BB9127880A4000354E07 /* ItemViewModel */,
|
||||||
62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */,
|
62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */,
|
||||||
62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */,
|
62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */,
|
||||||
625CB5742678C33500530A6E /* LibraryListViewModel.swift */,
|
625CB5742678C33500530A6E /* LibraryListViewModel.swift */,
|
||||||
62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */,
|
62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */,
|
||||||
62E632DF267D30CA0063E547 /* LibraryViewModel.swift */,
|
62E632DF267D30CA0063E547 /* LibraryViewModel.swift */,
|
||||||
|
C4BE07842728446F003F4AD1 /* LiveTVChannelsViewModel.swift */,
|
||||||
C4BE07752725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift */,
|
C4BE07752725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift */,
|
||||||
536D3D75267BA9BB0004248C /* MainTabViewModel.swift */,
|
536D3D75267BA9BB0004248C /* MainTabViewModel.swift */,
|
||||||
C4BE07842728446F003F4AD1 /* LiveTVChannelsViewModel.swift */,
|
|
||||||
62E632E2267D3BA60063E547 /* MovieItemViewModel.swift */,
|
|
||||||
C40CD924271F8D1E000FB198 /* MovieLibrariesViewModel.swift */,
|
C40CD924271F8D1E000FB198 /* MovieLibrariesViewModel.swift */,
|
||||||
62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */,
|
|
||||||
62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */,
|
|
||||||
E173DA5326D050F500CC4EB7 /* ServerDetailViewModel.swift */,
|
E173DA5326D050F500CC4EB7 /* ServerDetailViewModel.swift */,
|
||||||
E13DD3E027176BD3009D4DAF /* ServerListViewModel.swift */,
|
E13DD3E027176BD3009D4DAF /* ServerListViewModel.swift */,
|
||||||
5321753A2671BCFC005491E6 /* SettingsViewModel.swift */,
|
5321753A2671BCFC005491E6 /* SettingsViewModel.swift */,
|
||||||
|
@ -1296,6 +1297,19 @@
|
||||||
path = Pods;
|
path = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E107BB9127880A4000354E07 /* ItemViewModel */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */,
|
||||||
|
62E632E5267D3F5B0063E547 /* EpisodeItemViewModel.swift */,
|
||||||
|
62E632F2267D54030063E547 /* ItemViewModel.swift */,
|
||||||
|
62E632E2267D3BA60063E547 /* MovieItemViewModel.swift */,
|
||||||
|
62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */,
|
||||||
|
62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */,
|
||||||
|
);
|
||||||
|
path = ItemViewModel;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E12186DF2718F2030010884C /* App */ = {
|
E12186DF2718F2030010884C /* App */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1538,6 +1552,7 @@
|
||||||
E1E5D53C2783A85F00692DFE /* CinematicItemView */ = {
|
E1E5D53C2783A85F00692DFE /* CinematicItemView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E107BB952788104100354E07 /* CinematicCollectionItemView.swift */,
|
||||||
E1E5D5362783A52C00692DFE /* CinematicEpisodeItemView.swift */,
|
E1E5D5362783A52C00692DFE /* CinematicEpisodeItemView.swift */,
|
||||||
E1E5D5452783C28100692DFE /* CinematicItemAboutView.swift */,
|
E1E5D5452783C28100692DFE /* CinematicItemAboutView.swift */,
|
||||||
E1E5D53A2783A80900692DFE /* CinematicItemViewTopRow.swift */,
|
E1E5D53A2783A80900692DFE /* CinematicItemViewTopRow.swift */,
|
||||||
|
@ -1999,9 +2014,11 @@
|
||||||
E193D4DC27193CCA00900D82 /* PillStackable.swift in Sources */,
|
E193D4DC27193CCA00900D82 /* PillStackable.swift in Sources */,
|
||||||
E193D53327193F7D00900D82 /* FilterCoordinator.swift in Sources */,
|
E193D53327193F7D00900D82 /* FilterCoordinator.swift in Sources */,
|
||||||
6267B3DC2671139500A7371D /* ImageExtensions.swift in Sources */,
|
6267B3DC2671139500A7371D /* ImageExtensions.swift in Sources */,
|
||||||
|
E107BB9427880A8F00354E07 /* CollectionItemViewModel.swift in Sources */,
|
||||||
C40CD929271F8DAB000FB198 /* MovieLibrariesView.swift in Sources */,
|
C40CD929271F8DAB000FB198 /* MovieLibrariesView.swift in Sources */,
|
||||||
C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */,
|
C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */,
|
||||||
53ABFDE9267974EF00886593 /* HomeViewModel.swift in Sources */,
|
53ABFDE9267974EF00886593 /* HomeViewModel.swift in Sources */,
|
||||||
|
E107BB972788104100354E07 /* CinematicCollectionItemView.swift in Sources */,
|
||||||
53116A17268B919A003024C9 /* SeriesItemView.swift in Sources */,
|
53116A17268B919A003024C9 /* SeriesItemView.swift in Sources */,
|
||||||
E13DD3F027178F87009D4DAF /* SwiftfinNotificationCenter.swift in Sources */,
|
E13DD3F027178F87009D4DAF /* SwiftfinNotificationCenter.swift in Sources */,
|
||||||
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */,
|
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */,
|
||||||
|
@ -2211,6 +2228,7 @@
|
||||||
E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */,
|
E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */,
|
||||||
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */,
|
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */,
|
||||||
E14F7D0926DB36F7007C3AE6 /* ItemLandscapeMainView.swift in Sources */,
|
E14F7D0926DB36F7007C3AE6 /* ItemLandscapeMainView.swift in Sources */,
|
||||||
|
E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */,
|
||||||
532175402671EE4F005491E6 /* LibraryFilterView.swift in Sources */,
|
532175402671EE4F005491E6 /* LibraryFilterView.swift in Sources */,
|
||||||
C4BE0763271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift in Sources */,
|
C4BE0763271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift in Sources */,
|
||||||
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */,
|
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */,
|
||||||
|
|
|
@ -46,6 +46,8 @@ private struct ItemView: View {
|
||||||
self.viewModel = EpisodeItemViewModel(item: item)
|
self.viewModel = EpisodeItemViewModel(item: item)
|
||||||
case .series:
|
case .series:
|
||||||
self.viewModel = SeriesItemViewModel(item: item)
|
self.viewModel = SeriesItemViewModel(item: item)
|
||||||
|
case .boxset:
|
||||||
|
self.viewModel = CollectionItemViewModel(item: item)
|
||||||
default:
|
default:
|
||||||
self.viewModel = ItemViewModel(item: item)
|
self.viewModel = ItemViewModel(item: item)
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,19 @@ struct ItemViewBody: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Collection Items
|
||||||
|
|
||||||
|
if let collectionViewModel = viewModel as? CollectionItemViewModel {
|
||||||
|
PortraitImageHStackView(items: collectionViewModel.collectionItems) {
|
||||||
|
Text("Items")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.padding(.bottom)
|
||||||
|
.padding(.horizontal)
|
||||||
|
} selectedAction: { collectionItem in
|
||||||
|
itemRouter.route(to: \.item, collectionItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Cast & Crew
|
// MARK: Cast & Crew
|
||||||
|
|
||||||
|
@ -115,8 +128,14 @@ struct ItemViewBody: View {
|
||||||
|
|
||||||
// MARK: Details
|
// MARK: Details
|
||||||
|
|
||||||
ItemViewDetailsView(viewModel: viewModel)
|
switch viewModel.item.itemType {
|
||||||
.padding()
|
case .movie, .episode:
|
||||||
|
ItemViewDetailsView(viewModel: viewModel)
|
||||||
|
.padding()
|
||||||
|
default:
|
||||||
|
EmptyView()
|
||||||
|
.frame(height: 50)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,36 @@ struct LibraryListView: View {
|
||||||
.padding(.bottom, 5)
|
.padding(.bottom, 5)
|
||||||
|
|
||||||
if !viewModel.isLoading {
|
if !viewModel.isLoading {
|
||||||
|
|
||||||
|
if let collectionsLibraryItem = viewModel.libraries.first(where: { $0.collectionType == "boxsets" }) {
|
||||||
|
Button {
|
||||||
|
libraryListRouter.route(to: \.library,
|
||||||
|
(viewModel: LibraryViewModel(parentID: collectionsLibraryItem.id),
|
||||||
|
title: collectionsLibraryItem.name ?? ""))
|
||||||
|
} label: {
|
||||||
|
ZStack {
|
||||||
|
ImageView(src: collectionsLibraryItem.getPrimaryImage(maxWidth: 500),
|
||||||
|
bh: collectionsLibraryItem.getPrimaryImageBlurHash())
|
||||||
|
.opacity(0.4)
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
Text(collectionsLibraryItem.name ?? "")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.font(.title2)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}.padding(32)
|
||||||
|
}.background(Color.black)
|
||||||
|
.frame(minWidth: 100, maxWidth: .infinity)
|
||||||
|
.frame(height: 100)
|
||||||
|
}
|
||||||
|
.cornerRadius(10)
|
||||||
|
.shadow(radius: 5)
|
||||||
|
.padding(.bottom, 5)
|
||||||
|
}
|
||||||
|
|
||||||
ForEach(viewModel.libraries, id: \.id) { library in
|
ForEach(viewModel.libraries, id: \.id) { library in
|
||||||
if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" {
|
if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" {
|
||||||
Button {
|
Button {
|
||||||
|
|
|
@ -205,6 +205,7 @@ public extension BaseItemDto {
|
||||||
case season = "Season"
|
case season = "Season"
|
||||||
case episode = "Episode"
|
case episode = "Episode"
|
||||||
case series = "Series"
|
case series = "Series"
|
||||||
|
case boxset = "BoxSet"
|
||||||
|
|
||||||
case unknown
|
case unknown
|
||||||
|
|
||||||
|
@ -227,7 +228,7 @@ public extension BaseItemDto {
|
||||||
|
|
||||||
func portraitHeaderViewURL(maxWidth: Int) -> URL {
|
func portraitHeaderViewURL(maxWidth: Int) -> URL {
|
||||||
switch itemType {
|
switch itemType {
|
||||||
case .movie, .season, .series:
|
case .movie, .season, .series, .boxset:
|
||||||
return getPrimaryImage(maxWidth: maxWidth)
|
return getPrimaryImage(maxWidth: maxWidth)
|
||||||
case .episode:
|
case .episode:
|
||||||
return getSeriesPrimaryImage(maxWidth: maxWidth)
|
return getSeriesPrimaryImage(maxWidth: maxWidth)
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
/*
|
||||||
|
* SwiftFin is subject to the terms of the Mozilla Public
|
||||||
|
* License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*
|
||||||
|
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
import JellyfinAPI
|
||||||
|
|
||||||
|
final class CollectionItemViewModel: ItemViewModel {
|
||||||
|
|
||||||
|
@Published var collectionItems: [BaseItemDto] = []
|
||||||
|
|
||||||
|
override init(item: BaseItemDto) {
|
||||||
|
super.init(item: item)
|
||||||
|
|
||||||
|
getCollectionItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getCollectionItems() {
|
||||||
|
ItemsAPI.getItems(userId: SessionManager.main.currentLogin.user.id,
|
||||||
|
parentId: item.id,
|
||||||
|
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people])
|
||||||
|
.trackActivity(loading)
|
||||||
|
.sink { [weak self] completion in
|
||||||
|
self?.handleAPIRequestError(completion: completion)
|
||||||
|
} receiveValue: { [weak self] response in
|
||||||
|
self?.collectionItems = response.items ?? []
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ import JellyfinAPI
|
||||||
|
|
||||||
final class LibraryListViewModel: ViewModel {
|
final class LibraryListViewModel: ViewModel {
|
||||||
|
|
||||||
@Published var libraries = [BaseItemDto]()
|
@Published var libraries: [BaseItemDto] = []
|
||||||
|
|
||||||
// temp
|
// temp
|
||||||
var withFavorites = LibraryFilters(filters: [.isFavorite], sortOrder: [], withGenres: [], sortBy: [])
|
var withFavorites = LibraryFilters(filters: [.isFavorite], sortOrder: [], withGenres: [], sortBy: [])
|
||||||
|
@ -29,7 +29,7 @@ final class LibraryListViewModel: ViewModel {
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { response in
|
||||||
self.libraries.append(contentsOf: response.items ?? [])
|
self.libraries = response.items ?? []
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,12 +79,23 @@ final class LibraryViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
let sortBy = filters.sortBy.map(\.rawValue)
|
let sortBy = filters.sortBy.map(\.rawValue)
|
||||||
|
|
||||||
ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id, startIndex: currentPage * 100, limit: 100,
|
ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id,
|
||||||
|
startIndex: currentPage * 100,
|
||||||
|
limit: 100,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
searchTerm: nil, sortOrder: filters.sortOrder, parentId: parentID,
|
searchTerm: nil,
|
||||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters], includeItemTypes: filters.filters.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series"],
|
sortOrder: filters.sortOrder,
|
||||||
filters: filters.filters, sortBy: sortBy, tags: filters.tags,
|
parentId: parentID,
|
||||||
enableUserData: true, personIds: personIDs, studioIds: studioIDs, genreIds: genreIDs, enableImages: true)
|
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters],
|
||||||
|
includeItemTypes: filters.filters.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series", "BoxSet"],
|
||||||
|
filters: filters.filters,
|
||||||
|
sortBy: sortBy,
|
||||||
|
tags: filters.tags,
|
||||||
|
enableUserData: true,
|
||||||
|
personIds: personIDs,
|
||||||
|
studioIds: studioIDs,
|
||||||
|
genreIds: genreIDs,
|
||||||
|
enableImages: true)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
|
@ -112,12 +123,22 @@ final class LibraryViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
let sortBy = filters.sortBy.map(\.rawValue)
|
let sortBy = filters.sortBy.map(\.rawValue)
|
||||||
|
|
||||||
ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id, startIndex: currentPage * 100, limit: 100,
|
ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id, startIndex: currentPage * 100,
|
||||||
|
limit: 100,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
searchTerm: nil, sortOrder: filters.sortOrder, parentId: parentID,
|
searchTerm: nil,
|
||||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters], includeItemTypes: filters.filters.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series"],
|
sortOrder: filters.sortOrder,
|
||||||
filters: filters.filters, sortBy: sortBy, tags: filters.tags,
|
parentId: parentID,
|
||||||
enableUserData: true, personIds: personIDs, studioIds: studioIDs, genreIds: genreIDs, enableImages: true)
|
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters],
|
||||||
|
includeItemTypes: filters.filters.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series"],
|
||||||
|
filters: filters.filters,
|
||||||
|
sortBy: sortBy,
|
||||||
|
tags: filters.tags,
|
||||||
|
enableUserData: true,
|
||||||
|
personIds: personIDs,
|
||||||
|
studioIds: studioIDs,
|
||||||
|
genreIds: genreIDs,
|
||||||
|
enableImages: true)
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
}, receiveValue: { [weak self] response in
|
}, receiveValue: { [weak self] response in
|
||||||
|
|
Loading…
Reference in New Issue