diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index a48a49af..85610cd8 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -286,6 +286,7 @@ C4BE07892728448B003F4AD1 /* LiveTVChannelsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07872728448B003F4AD1 /* LiveTVChannelsCoordinator.swift */; }; C4BE078C272844AF003F4AD1 /* LiveTVChannelsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE078A272844AF003F4AD1 /* LiveTVChannelsView.swift */; }; C4BE078E27298818003F4AD1 /* LiveTVHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE078D27298817003F4AD1 /* LiveTVHomeView.swift */; }; + C4D0CE4B2848570700345D11 /* ASCollectionView in Frameworks */ = {isa = PBXBuildFile; productRef = C4D0CE4A2848570700345D11 /* ASCollectionView */; }; C4E5081B2703F82A0045C9AB /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E508172703E8190045C9AB /* LibraryListView.swift */; }; C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */; }; C4E5598928124C10003DECA5 /* LiveTVChannelItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5598828124C10003DECA5 /* LiveTVChannelItemElement.swift */; }; @@ -945,6 +946,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C4D0CE4B2848570700345D11 /* ASCollectionView in Frameworks */, 62666E3E27E503FA00EC0ECD /* MediaAccessibility.framework in Frameworks */, 62666DFF27E5016400EC0ECD /* CFNetwork.framework in Frameworks */, E13DD3D327168E65009D4DAF /* Defaults in Frameworks */, @@ -1960,6 +1962,7 @@ E1002B672793CFBA00E47059 /* Algorithms */, 62666E3827E502CE00EC0ECD /* SwizzleSwift */, E1101176281B1E8A006A3584 /* Puppy */, + C4D0CE4A2848570700345D11 /* ASCollectionView */, ); productName = JellyfinPlayer; productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */; @@ -2054,6 +2057,7 @@ E1002B662793CFBA00E47059 /* XCRemoteSwiftPackageReference "swift-algorithms" */, 62666E3727E502CE00EC0ECD /* XCRemoteSwiftPackageReference "SwizzleSwift" */, E1101175281B1E8A006A3584 /* XCRemoteSwiftPackageReference "Puppy" */, + C4D0CE492848570700345D11 /* XCRemoteSwiftPackageReference "ASCollectionView" */, ); productRefGroup = 5377CBF2263B596A003A4E83 /* Products */; projectDirPath = ""; @@ -3117,6 +3121,14 @@ kind = branch; }; }; + C4D0CE492848570700345D11 /* XCRemoteSwiftPackageReference "ASCollectionView" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apptekstudios/ASCollectionView"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.0.0; + }; + }; E1002B662793CFBA00E47059 /* XCRemoteSwiftPackageReference "swift-algorithms" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/apple/swift-algorithms.git"; @@ -3253,6 +3265,11 @@ package = 62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */; productName = Stinsen; }; + C4D0CE4A2848570700345D11 /* ASCollectionView */ = { + isa = XCSwiftPackageProductDependency; + package = C4D0CE492848570700345D11 /* XCRemoteSwiftPackageReference "ASCollectionView" */; + productName = ASCollectionView; + }; E1002B672793CFBA00E47059 /* Algorithms */ = { isa = XCSwiftPackageProductDependency; package = E1002B662793CFBA00E47059 /* XCRemoteSwiftPackageReference "swift-algorithms" */; diff --git a/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b8168ffe..71388939 100644 --- a/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -18,6 +18,15 @@ "version" : "0.6.4" } }, + { + "identity" : "ascollectionview", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apptekstudios/ASCollectionView", + "state" : { + "revision" : "4288744ba484c1062c109c0f28d72b629d321d55", + "version" : "2.1.1" + } + }, { "identity" : "combineext", "kind" : "remoteSourceControl", @@ -45,6 +54,15 @@ "version" : "6.2.1" } }, + { + "identity" : "differencekit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ra1028/DifferenceKit", + "state" : { + "revision" : "62745d7780deef4a023a792a1f8f763ec7bf9705", + "version" : "1.2.0" + } + }, { "identity" : "gifu", "kind" : "remoteSourceControl", diff --git a/Swiftfin/Views/LiveTVChannelsView.swift b/Swiftfin/Views/LiveTVChannelsView.swift index 6b56f97b..241c374d 100644 --- a/Swiftfin/Views/LiveTVChannelsView.swift +++ b/Swiftfin/Views/LiveTVChannelsView.swift @@ -6,6 +6,7 @@ // Copyright (c) 2022 Jellyfin & Jellyfin Contributors // +import ASCollectionView import Foundation import JellyfinAPI import SwiftUI @@ -20,19 +21,34 @@ struct LiveTVChannelsView: View { var viewModel = LiveTVChannelsViewModel() @State private var isPortrait = false - + private var columns: Int { + if UIDevice.current.userInterfaceIdiom == .pad { + return 2 + } else { + if isPortrait { + return 1 + } else { + return 2 + } + } + } + var body: some View { if viewModel.isLoading == true { ProgressView() - } else if !viewModel.rows.isEmpty { - CollectionView(rows: viewModel.rows) { _, _ in - createGridLayout() - } cell: { indexPath, cell in - makeCellView(indexPath: indexPath, cell: cell) - } supplementaryView: { _, indexPath in - EmptyView() - .accessibilityIdentifier("\(indexPath.section).\(indexPath.row)") - } + } else if !viewModel.channelPrograms.isEmpty { + ASCollectionView(data: viewModel.channelPrograms, dataID: \.self) + { channelProgram, _ in + makeCellView(channelProgram) + } + .layout + { + .grid( + layoutMode: .fixedNumberOfColumns(columns), + itemSpacing: 16, + lineSpacing: 4, + itemSize: .absolute(144)) + } .frame(maxWidth: .infinity, maxHeight: .infinity) .ignoresSafeArea() .onAppear { @@ -58,22 +74,21 @@ struct LiveTVChannelsView: View { } @ViewBuilder - func makeCellView(indexPath: IndexPath, cell: LiveTVChannelRowCell) -> some View { - let item = cell.item - let channel = item.channel - let currentProgramDisplayText = item.currentProgram? + func makeCellView(_ channelProgram: LiveTVChannelProgram) -> some View { + let channel = channelProgram.channel + let currentProgramDisplayText = channelProgram.currentProgram? .programDisplayText(timeFormatter: viewModel.timeFormatter) ?? LiveTVChannelViewProgram(timeDisplay: "", title: "") - let nextItems = item.programs.filter { program in + let nextItems = channelProgram.programs.filter { program in guard let start = program.startDate else { return false } - guard let currentStart = item.currentProgram?.startDate else { + guard let currentStart = channelProgram.currentProgram?.startDate else { return false } return start > currentStart } LiveTVChannelItemWideElement(channel: channel, - currentProgram: item.currentProgram, + currentProgram: channelProgram.currentProgram, currentProgramText: currentProgramDisplayText, nextProgramsText: nextProgramsDisplayText(nextItems: nextItems, timeFormatter: viewModel.timeFormatter), @@ -88,62 +103,6 @@ struct LiveTVChannelsView: View { }) } - private func createGridLayout() -> NSCollectionLayoutSection { - if UIDevice.current.userInterfaceIdiom == .pad { - let itemSize = NSCollectionLayoutSize(widthDimension: .absolute((UIScreen.main.bounds.width / 2) - 16), - heightDimension: .fractionalHeight(1)) - let item = NSCollectionLayoutItem(layoutSize: itemSize) - item.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .flexible(0), top: nil, - trailing: .flexible(2), bottom: .flexible(2)) - let item2 = NSCollectionLayoutItem(layoutSize: itemSize) - item2.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: nil, top: nil, - trailing: .flexible(0), bottom: .flexible(2)) - let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), - heightDimension: .absolute(144)) - let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, - subitems: [item, item2]) - let section = NSCollectionLayoutSection(group: group) - return section - } else { - if isPortrait { - let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(UIScreen.main.bounds.width - 32), - heightDimension: .fractionalHeight(1)) - let item = NSCollectionLayoutItem(layoutSize: itemSize) - item.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .flexible(0), top: nil, - trailing: .flexible(2), bottom: .flexible(2)) - let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), - heightDimension: .absolute(144)) - let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, - subitems: [item]) - let section = NSCollectionLayoutSection(group: group) - return section - } else { - - let scenes = UIApplication.shared.connectedScenes - let windowScene = scenes.first as? UIWindowScene - var width = (UIScreen.main.bounds.width / 2) - 32 - if let safeArea = windowScene?.keyWindow?.safeAreaInsets { - width = (UIScreen.main.bounds.width / 2) - safeArea.left - safeArea.right - } - - let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(width), - heightDimension: .fractionalHeight(1)) - let item = NSCollectionLayoutItem(layoutSize: itemSize) - item.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .flexible(0), top: nil, - trailing: .flexible(2), bottom: .flexible(2)) - let item2 = NSCollectionLayoutItem(layoutSize: itemSize) - item2.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: nil, top: nil, - trailing: .flexible(0), bottom: .flexible(2)) - let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), - heightDimension: .absolute(144)) - let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, - subitems: [item, item2]) - let section = NSCollectionLayoutSection(group: group) - return section - } - } - } - private func checkOrientation() { let scenes = UIApplication.shared.connectedScenes let windowScene = scenes.first as? UIWindowScene