use CollectionView on LibraryView
This commit is contained in:
parent
c3b2fadf91
commit
1c2b1879b4
|
@ -7,57 +7,95 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import SwiftUICollection
|
||||||
|
import JellyfinAPI
|
||||||
|
|
||||||
struct LibraryView: View {
|
struct LibraryView: View {
|
||||||
@StateObject var viewModel: LibraryViewModel
|
@StateObject var viewModel: LibraryViewModel
|
||||||
var title: String
|
var title: String
|
||||||
|
|
||||||
// MARK: tracks for grid
|
// MARK: tracks for grid
|
||||||
var defaultFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], tags: [], sortBy: [.name])
|
var defaultFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], tags: [], sortBy: [.name])
|
||||||
|
|
||||||
@State var isShowingSearchView = false
|
@State var isShowingSearchView = false
|
||||||
@State var isShowingFilterView = false
|
@State var isShowingFilterView = false
|
||||||
|
|
||||||
@State private var tracks: [GridItem] = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 250)
|
var body: some View {
|
||||||
|
if viewModel.isLoading == true {
|
||||||
var body: some View {
|
ProgressView()
|
||||||
Group {
|
} else if !viewModel.items.isEmpty {
|
||||||
if viewModel.isLoading == true {
|
CollectionView(rows: viewModel.rows) { _, _ in
|
||||||
ProgressView()
|
let itemSize = NSCollectionLayoutSize(
|
||||||
} else if !viewModel.items.isEmpty {
|
widthDimension: .fractionalWidth(1),
|
||||||
ScrollView(.vertical) {
|
heightDimension: .fractionalHeight(1)
|
||||||
LazyVGrid(columns: tracks) {
|
|
||||||
ForEach(viewModel.items, id: \.id) { item in
|
|
||||||
if item.type != "Folder" {
|
|
||||||
NavigationLink(destination: LazyView { ItemView(item: item) }) {
|
|
||||||
PortraitItemElement(item: item)
|
|
||||||
}.buttonStyle(PlainNavigationLinkButtonStyle())
|
|
||||||
.onAppear {
|
|
||||||
if item == viewModel.items.last && viewModel.hasNextPage {
|
|
||||||
print("Last item visible, load more items.")
|
|
||||||
viewModel.requestNextPageAsync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.padding()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Text("No results.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
.sheet(isPresented: $isShowingFilterView) {
|
|
||||||
LibraryFilterView(filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType, parentId: viewModel.parentID ?? "")
|
|
||||||
}
|
|
||||||
.background(
|
|
||||||
NavigationLink(destination: LibrarySearchView(viewModel: .init(parentID: viewModel.parentID)),
|
|
||||||
isActive: $isShowingSearchView) {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
*/
|
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||||
|
|
||||||
|
let groupSize = NSCollectionLayoutSize(
|
||||||
|
widthDimension: .absolute(200),
|
||||||
|
heightDimension: .absolute(300)
|
||||||
|
)
|
||||||
|
let group = NSCollectionLayoutGroup.horizontal(
|
||||||
|
layoutSize: groupSize,
|
||||||
|
subitems: [item]
|
||||||
|
)
|
||||||
|
|
||||||
|
let header =
|
||||||
|
NSCollectionLayoutBoundarySupplementaryItem(
|
||||||
|
layoutSize: NSCollectionLayoutSize(
|
||||||
|
widthDimension: .fractionalWidth(1),
|
||||||
|
heightDimension: .absolute(44)
|
||||||
|
),
|
||||||
|
elementKind: UICollectionView.elementKindSectionHeader,
|
||||||
|
alignment: .topLeading
|
||||||
|
)
|
||||||
|
|
||||||
|
let section = NSCollectionLayoutSection(group: group)
|
||||||
|
|
||||||
|
section.contentInsets = NSDirectionalEdgeInsets(top: 30, leading: 0, bottom: 80, trailing: 80)
|
||||||
|
section.interGroupSpacing = 48
|
||||||
|
section.orthogonalScrollingBehavior = .continuous
|
||||||
|
section.boundarySupplementaryItems = [header]
|
||||||
|
return section
|
||||||
|
} cell: { _, item in
|
||||||
|
GeometryReader { _ in
|
||||||
|
if item.type != "Folder" {
|
||||||
|
NavigationLink(destination: LazyView { ItemView(item: item) }) {
|
||||||
|
PortraitItemElement(item: item)
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||||
|
.onAppear {
|
||||||
|
if item == viewModel.items.last && viewModel.hasNextPage {
|
||||||
|
print("Last item visible, load more items.")
|
||||||
|
viewModel.requestNextPageAsync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} supplementaryView: { _, indexPath in
|
||||||
|
HStack {
|
||||||
|
Text("Supp View")
|
||||||
|
.font(.title3)
|
||||||
|
Spacer()
|
||||||
|
}.accessibilityIdentifier("\(indexPath.section).\(indexPath.row)")
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
.ignoresSafeArea(.all)
|
||||||
|
} else {
|
||||||
|
Text("No results.")
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
.sheet(isPresented: $isShowingFilterView) {
|
||||||
|
LibraryFilterView(filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType, parentId: viewModel.parentID ?? "")
|
||||||
|
}
|
||||||
|
.background(
|
||||||
|
NavigationLink(destination: LibrarySearchView(viewModel: .init(parentID: viewModel.parentID)),
|
||||||
|
isActive: $isShowingSearchView) {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stream BM^S by nicki!
|
// stream BM^S by nicki!
|
||||||
|
|
|
@ -252,6 +252,8 @@
|
||||||
62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */; };
|
62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */; };
|
||||||
AE8C3159265D6F90008AA076 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; };
|
AE8C3159265D6F90008AA076 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; };
|
||||||
C45B29BB26FAC5B600CEF5E0 /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */; };
|
C45B29BB26FAC5B600CEF5E0 /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */; };
|
||||||
|
C49FB6592717A06300AAEABB /* SwiftUICollection in Frameworks */ = {isa = PBXBuildFile; productRef = C49FB6582717A06300AAEABB /* SwiftUICollection */; };
|
||||||
|
C4BFD4E527167B63007739E3 /* SwiftUICollection in Frameworks */ = {isa = PBXBuildFile; productRef = C4BFD4E427167B63007739E3 /* SwiftUICollection */; };
|
||||||
C4E5081B2703F82A0045C9AB /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E508172703E8190045C9AB /* LibraryListView.swift */; };
|
C4E5081B2703F82A0045C9AB /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E508172703E8190045C9AB /* LibraryListView.swift */; };
|
||||||
C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */; };
|
C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */; };
|
||||||
E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */; };
|
E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */; };
|
||||||
|
@ -528,6 +530,7 @@
|
||||||
53272535268BF9710035FBF1 /* SwiftUIFocusGuide in Frameworks */,
|
53272535268BF9710035FBF1 /* SwiftUIFocusGuide in Frameworks */,
|
||||||
5358708D2669D7A800D05A09 /* KeychainSwift in Frameworks */,
|
5358708D2669D7A800D05A09 /* KeychainSwift in Frameworks */,
|
||||||
536D3D84267BEA550004248C /* ParallaxView in Frameworks */,
|
536D3D84267BEA550004248C /* ParallaxView in Frameworks */,
|
||||||
|
C49FB6592717A06300AAEABB /* SwiftUICollection in Frameworks */,
|
||||||
53ABFDDC267972BF00886593 /* TVServices.framework in Frameworks */,
|
53ABFDDC267972BF00886593 /* TVServices.framework in Frameworks */,
|
||||||
5358709B2669D7A800D05A09 /* NukeUI in Frameworks */,
|
5358709B2669D7A800D05A09 /* NukeUI in Frameworks */,
|
||||||
53ABFDED26799D7700886593 /* ActivityIndicator in Frameworks */,
|
53ABFDED26799D7700886593 /* ActivityIndicator in Frameworks */,
|
||||||
|
@ -542,6 +545,7 @@
|
||||||
62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */,
|
62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */,
|
||||||
62CB3F462685BAF7003D0A6F /* Defaults in Frameworks */,
|
62CB3F462685BAF7003D0A6F /* Defaults in Frameworks */,
|
||||||
5338F757263B7E2E0014BF09 /* KeychainSwift in Frameworks */,
|
5338F757263B7E2E0014BF09 /* KeychainSwift in Frameworks */,
|
||||||
|
C4BFD4E527167B63007739E3 /* SwiftUICollection in Frameworks */,
|
||||||
53EC6E25267EB10F006DD26A /* SwiftyJSON in Frameworks */,
|
53EC6E25267EB10F006DD26A /* SwiftyJSON in Frameworks */,
|
||||||
53EC6E21267E80B1006DD26A /* Pods_JellyfinPlayer_iOS.framework in Frameworks */,
|
53EC6E21267E80B1006DD26A /* Pods_JellyfinPlayer_iOS.framework in Frameworks */,
|
||||||
53352571265EA0A0006CCA86 /* Introspect in Frameworks */,
|
53352571265EA0A0006CCA86 /* Introspect in Frameworks */,
|
||||||
|
@ -1151,6 +1155,7 @@
|
||||||
53649AAE269CFAF600A2D8B7 /* Puppy */,
|
53649AAE269CFAF600A2D8B7 /* Puppy */,
|
||||||
6261A0DF26A0AB710072EF1C /* CombineExt */,
|
6261A0DF26A0AB710072EF1C /* CombineExt */,
|
||||||
6220D0C826D63F3700B8E046 /* Stinsen */,
|
6220D0C826D63F3700B8E046 /* Stinsen */,
|
||||||
|
C49FB6582717A06300AAEABB /* SwiftUICollection */,
|
||||||
);
|
);
|
||||||
productName = "JellyfinPlayer tvOS";
|
productName = "JellyfinPlayer tvOS";
|
||||||
productReference = 535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */;
|
productReference = 535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */;
|
||||||
|
@ -1186,6 +1191,7 @@
|
||||||
53649AAC269CFAEA00A2D8B7 /* Puppy */,
|
53649AAC269CFAEA00A2D8B7 /* Puppy */,
|
||||||
6260FFF826A09754003FA968 /* CombineExt */,
|
6260FFF826A09754003FA968 /* CombineExt */,
|
||||||
62C29E9B26D0FE4200C1D2E7 /* Stinsen */,
|
62C29E9B26D0FE4200C1D2E7 /* Stinsen */,
|
||||||
|
C4BFD4E427167B63007739E3 /* SwiftUICollection */,
|
||||||
);
|
);
|
||||||
productName = JellyfinPlayer;
|
productName = JellyfinPlayer;
|
||||||
productReference = 5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */;
|
productReference = 5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */;
|
||||||
|
@ -1275,6 +1281,7 @@
|
||||||
53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */,
|
53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */,
|
||||||
6260FFF726A09754003FA968 /* XCRemoteSwiftPackageReference "CombineExt" */,
|
6260FFF726A09754003FA968 /* XCRemoteSwiftPackageReference "CombineExt" */,
|
||||||
62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */,
|
62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */,
|
||||||
|
C4BFD4E327167B63007739E3 /* XCRemoteSwiftPackageReference "SwiftUICollection" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
|
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
@ -2268,6 +2275,14 @@
|
||||||
kind = branch;
|
kind = branch;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
C4BFD4E327167B63007739E3 /* XCRemoteSwiftPackageReference "SwiftUICollection" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/ABJC/SwiftUICollection";
|
||||||
|
requirement = {
|
||||||
|
branch = master;
|
||||||
|
kind = branch;
|
||||||
|
};
|
||||||
|
};
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
@ -2406,6 +2421,16 @@
|
||||||
package = 62CB3F442685BAF7003D0A6F /* XCRemoteSwiftPackageReference "Defaults" */;
|
package = 62CB3F442685BAF7003D0A6F /* XCRemoteSwiftPackageReference "Defaults" */;
|
||||||
productName = Defaults;
|
productName = Defaults;
|
||||||
};
|
};
|
||||||
|
C49FB6582717A06300AAEABB /* SwiftUICollection */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = C4BFD4E327167B63007739E3 /* XCRemoteSwiftPackageReference "SwiftUICollection" */;
|
||||||
|
productName = SwiftUICollection;
|
||||||
|
};
|
||||||
|
C4BFD4E427167B63007739E3 /* SwiftUICollection */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = C4BFD4E327167B63007739E3 /* XCRemoteSwiftPackageReference "SwiftUICollection" */;
|
||||||
|
productName = SwiftUICollection;
|
||||||
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
|
|
||||||
/* Begin XCVersionGroup section */
|
/* Begin XCVersionGroup section */
|
||||||
|
|
|
@ -136,6 +136,15 @@
|
||||||
"version": "0.1.3"
|
"version": "0.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"package": "SwiftUICollection",
|
||||||
|
"repositoryURL": "https://github.com/ABJC/SwiftUICollection",
|
||||||
|
"state": {
|
||||||
|
"branch": "master",
|
||||||
|
"revision": "e27149382ce8ec21995069c8aab7ca83d61a3120",
|
||||||
|
"version": null
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"package": "SwiftUIFocusGuide",
|
"package": "SwiftUIFocusGuide",
|
||||||
"repositoryURL": "https://github.com/rmnblm/SwiftUIFocusGuide",
|
"repositoryURL": "https://github.com/rmnblm/SwiftUIFocusGuide",
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
import SwiftUICollection
|
||||||
|
|
||||||
|
typealias LibraryRow = CollectionRow<Int, BaseItemDto>
|
||||||
|
|
||||||
final class LibraryViewModel: ViewModel {
|
final class LibraryViewModel: ViewModel {
|
||||||
var parentID: String?
|
var parentID: String?
|
||||||
|
@ -18,6 +21,7 @@ final class LibraryViewModel: ViewModel {
|
||||||
var studio: NameGuidPair?
|
var studio: NameGuidPair?
|
||||||
|
|
||||||
@Published var items = [BaseItemDto]()
|
@Published var items = [BaseItemDto]()
|
||||||
|
@Published var rows = [LibraryRow]()
|
||||||
|
|
||||||
@Published var totalPages = 0
|
@Published var totalPages = 0
|
||||||
@Published var currentPage = 0
|
@Published var currentPage = 0
|
||||||
|
@ -26,6 +30,8 @@ final class LibraryViewModel: ViewModel {
|
||||||
|
|
||||||
// temp
|
// temp
|
||||||
@Published var filters: LibraryFilters
|
@Published var filters: LibraryFilters
|
||||||
|
|
||||||
|
private let columns: Int
|
||||||
|
|
||||||
var enabledFilterType: [FilterType] {
|
var enabledFilterType: [FilterType] {
|
||||||
if genre == nil {
|
if genre == nil {
|
||||||
|
@ -35,16 +41,20 @@ final class LibraryViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(parentID: String? = nil,
|
init(
|
||||||
person: BaseItemPerson? = nil,
|
parentID: String? = nil,
|
||||||
genre: NameGuidPair? = nil,
|
person: BaseItemPerson? = nil,
|
||||||
studio: NameGuidPair? = nil,
|
genre: NameGuidPair? = nil,
|
||||||
filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: [.name])) {
|
studio: NameGuidPair? = nil,
|
||||||
|
filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: [.name]),
|
||||||
|
columns: Int = 7
|
||||||
|
) {
|
||||||
self.parentID = parentID
|
self.parentID = parentID
|
||||||
self.person = person
|
self.person = person
|
||||||
self.genre = genre
|
self.genre = genre
|
||||||
self.studio = studio
|
self.studio = studio
|
||||||
self.filters = filters
|
self.filters = filters
|
||||||
|
self.columns = columns
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
$filters
|
$filters
|
||||||
|
@ -79,6 +89,7 @@ final class LibraryViewModel: ViewModel {
|
||||||
self.hasPreviousPage = self.currentPage > 0
|
self.hasPreviousPage = self.currentPage > 0
|
||||||
self.hasNextPage = self.currentPage < self.totalPages - 1
|
self.hasNextPage = self.currentPage < self.totalPages - 1
|
||||||
self.items = response.items ?? []
|
self.items = response.items ?? []
|
||||||
|
self.calculateRows()
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
@ -108,6 +119,7 @@ final class LibraryViewModel: ViewModel {
|
||||||
self.hasPreviousPage = self.currentPage > 0
|
self.hasPreviousPage = self.currentPage > 0
|
||||||
self.hasNextPage = self.currentPage < self.totalPages - 1
|
self.hasNextPage = self.currentPage < self.totalPages - 1
|
||||||
self.items.append(contentsOf: response.items ?? [])
|
self.items.append(contentsOf: response.items ?? [])
|
||||||
|
self.calculateRows()
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
@ -126,4 +138,22 @@ final class LibraryViewModel: ViewModel {
|
||||||
currentPage -= 1
|
currentPage -= 1
|
||||||
requestItems(with: filters)
|
requestItems(with: filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func calculateRows() {
|
||||||
|
let rowCount = items.count / columns
|
||||||
|
rows = [LibraryRow]()
|
||||||
|
for i in (0...rowCount) {
|
||||||
|
let firstItemIndex = i * columns
|
||||||
|
var lastItemIndex = firstItemIndex + columns
|
||||||
|
if lastItemIndex >= items.count {
|
||||||
|
lastItemIndex = items.count - 1
|
||||||
|
}
|
||||||
|
rows.append(
|
||||||
|
LibraryRow(
|
||||||
|
section: i,
|
||||||
|
items: Array(items[firstItemIndex...lastItemIndex])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue