iOS/iPadOS - Library List View (#542)

This commit is contained in:
Ethan Pippin 2022-08-29 08:58:38 -06:00 committed by GitHub
parent d078d71393
commit 3b755adf87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 200 additions and 29 deletions

View File

@ -0,0 +1,25 @@
//
// 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 (c) 2022 Jellyfin & Jellyfin Contributors
//
import Defaults
import Foundation
enum LibraryViewType: String, CaseIterable, Defaults.Serializable {
case grid
case list
// TODO: localize after organization
var localizedName: String {
switch self {
case .grid:
return "Grid"
case .list:
return "List"
}
}
}

View File

@ -37,11 +37,15 @@ extension Defaults.Keys {
static let latestInLibraryPosterType = Key<PosterType>("latestInLibraryPosterType", default: .portrait, suite: .generalSuite) static let latestInLibraryPosterType = Key<PosterType>("latestInLibraryPosterType", default: .portrait, suite: .generalSuite)
static let recommendedPosterType = Key<PosterType>("recommendedPosterType", default: .portrait, suite: .generalSuite) static let recommendedPosterType = Key<PosterType>("recommendedPosterType", default: .portrait, suite: .generalSuite)
static let searchPosterType = Key<PosterType>("searchPosterType", default: .portrait, suite: .generalSuite) static let searchPosterType = Key<PosterType>("searchPosterType", default: .portrait, suite: .generalSuite)
static let libraryPosterType = Key<PosterType>("libraryPosterType", default: .portrait, suite: .generalSuite)
enum Episodes { enum Episodes {
static let useSeriesLandscapeBackdrop = Key<Bool>("useSeriesBackdrop", default: true, suite: .generalSuite) static let useSeriesLandscapeBackdrop = Key<Bool>("useSeriesBackdrop", default: true, suite: .generalSuite)
} }
enum Library {
static let viewType = Key<LibraryViewType>("Customization.Library.viewType", default: .grid, suite: .generalSuite)
static let gridPosterType = Key<PosterType>("Customization.Library.gridPosterType", default: .portrait, suite: .generalSuite)
}
} }
// Video player / overlay settings // Video player / overlay settings

View File

@ -13,8 +13,8 @@ import UIKit
final class LibraryViewModel: ViewModel { final class LibraryViewModel: ViewModel {
@Default(.Customization.libraryPosterType) @Default(.Customization.Library.gridPosterType)
var libraryPosterType var libraryGridPosterType
@Published @Published
var items: [BaseItemDto] = [] var items: [BaseItemDto] = []
@ -35,8 +35,8 @@ final class LibraryViewModel: ViewModel {
var studio: NameGuidPair? var studio: NameGuidPair?
private var pageItemSize: Int { private var pageItemSize: Int {
let height = libraryPosterType == .portrait ? libraryPosterType.width * 1.5 : libraryPosterType.width / 1.77 let height = libraryGridPosterType == .portrait ? libraryGridPosterType.width * 1.5 : libraryGridPosterType.width / 1.77
return UIScreen.itemsFillableOnScreen(width: libraryPosterType.width, height: height) return UIScreen.itemsFillableOnScreen(width: libraryGridPosterType.width, height: height)
} }
var enabledFilterType: [FilterType] { var enabledFilterType: [FilterType] {

View File

@ -20,7 +20,7 @@ struct LibraryView: View {
@State @State
private var scrollViewOffset: CGPoint = .zero private var scrollViewOffset: CGPoint = .zero
@Default(.Customization.libraryPosterType) @Default(.Customization.Library.gridPosterType)
var libraryPosterType var libraryPosterType
@ViewBuilder @ViewBuilder

View File

@ -83,7 +83,6 @@
53ABFDEB2679753200886593 /* ConnectToServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ABFDEA2679753200886593 /* ConnectToServerView.swift */; }; 53ABFDEB2679753200886593 /* ConnectToServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ABFDEA2679753200886593 /* ConnectToServerView.swift */; };
53ABFDED26799D7700886593 /* ActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 53ABFDEC26799D7700886593 /* ActivityIndicator */; }; 53ABFDED26799D7700886593 /* ActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 53ABFDEC26799D7700886593 /* ActivityIndicator */; };
53CD2A40268A49C2002ABD4E /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53CD2A3F268A49C2002ABD4E /* ItemView.swift */; }; 53CD2A40268A49C2002ABD4E /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53CD2A3F268A49C2002ABD4E /* ItemView.swift */; };
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DF641D263D9C0600A7CD1A /* LibraryView.swift */; };
53EE24E6265060780068F029 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE24E5265060780068F029 /* SearchView.swift */; }; 53EE24E6265060780068F029 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE24E5265060780068F029 /* SearchView.swift */; };
5D1603FC278A3D5800D22B99 /* SubtitleSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */; }; 5D1603FC278A3D5800D22B99 /* SubtitleSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */; };
5D1603FD278A40DB00D22B99 /* SubtitleSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */; }; 5D1603FD278A40DB00D22B99 /* SubtitleSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */; };
@ -306,6 +305,11 @@
E13DD3FA2717E961009D4DAF /* UserListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3F82717E961009D4DAF /* UserListViewModel.swift */; }; E13DD3FA2717E961009D4DAF /* UserListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3F82717E961009D4DAF /* UserListViewModel.swift */; };
E13DD3FC2717EAE8009D4DAF /* UserListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3FB2717EAE8009D4DAF /* UserListView.swift */; }; E13DD3FC2717EAE8009D4DAF /* UserListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3FB2717EAE8009D4DAF /* UserListView.swift */; };
E13DD4022717EE79009D4DAF /* UserListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD4012717EE79009D4DAF /* UserListCoordinator.swift */; }; E13DD4022717EE79009D4DAF /* UserListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD4012717EE79009D4DAF /* UserListCoordinator.swift */; };
E13F05EC28BC9000003499D2 /* LibraryViewType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F05EB28BC9000003499D2 /* LibraryViewType.swift */; };
E13F05ED28BC9000003499D2 /* LibraryViewType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F05EB28BC9000003499D2 /* LibraryViewType.swift */; };
E13F05F128BC9016003499D2 /* LibraryItemRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F05EF28BC9016003499D2 /* LibraryItemRow.swift */; };
E13F05F228BC9016003499D2 /* LibraryItemRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F05EF28BC9016003499D2 /* LibraryItemRow.swift */; };
E13F05F328BC9016003499D2 /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F05F028BC9016003499D2 /* LibraryView.swift */; };
E1546777289AF46E00087E35 /* CollectionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1546776289AF46E00087E35 /* CollectionItemView.swift */; }; E1546777289AF46E00087E35 /* CollectionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1546776289AF46E00087E35 /* CollectionItemView.swift */; };
E154677A289AF48200087E35 /* CollectionItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1546779289AF48200087E35 /* CollectionItemContentView.swift */; }; E154677A289AF48200087E35 /* CollectionItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1546779289AF48200087E35 /* CollectionItemContentView.swift */; };
E168BD10289A4162001A6922 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E168BD08289A4162001A6922 /* HomeView.swift */; }; E168BD10289A4162001A6922 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E168BD08289A4162001A6922 /* HomeView.swift */; };
@ -616,7 +620,6 @@
53ABFDEA2679753200886593 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = "<group>"; }; 53ABFDEA2679753200886593 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = "<group>"; };
53CD2A3F268A49C2002ABD4E /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = "<group>"; }; 53CD2A3F268A49C2002ABD4E /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = "<group>"; };
53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MobileVLCKit.xcframework; path = Carthage/Build/MobileVLCKit.xcframework; sourceTree = "<group>"; }; 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MobileVLCKit.xcframework; path = Carthage/Build/MobileVLCKit.xcframework; sourceTree = "<group>"; };
53DF641D263D9C0600A7CD1A /* LibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = "<group>"; };
53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryFilterView.swift; sourceTree = "<group>"; }; 53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryFilterView.swift; sourceTree = "<group>"; };
53EE24E5265060780068F029 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; }; 53EE24E5265060780068F029 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleSize.swift; sourceTree = "<group>"; }; 5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleSize.swift; sourceTree = "<group>"; };
@ -769,6 +772,9 @@
E13DD3F82717E961009D4DAF /* UserListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListViewModel.swift; sourceTree = "<group>"; }; E13DD3F82717E961009D4DAF /* UserListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListViewModel.swift; sourceTree = "<group>"; };
E13DD3FB2717EAE8009D4DAF /* UserListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListView.swift; sourceTree = "<group>"; }; E13DD3FB2717EAE8009D4DAF /* UserListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListView.swift; sourceTree = "<group>"; };
E13DD4012717EE79009D4DAF /* UserListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListCoordinator.swift; sourceTree = "<group>"; }; E13DD4012717EE79009D4DAF /* UserListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListCoordinator.swift; sourceTree = "<group>"; };
E13F05EB28BC9000003499D2 /* LibraryViewType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryViewType.swift; sourceTree = "<group>"; };
E13F05EF28BC9016003499D2 /* LibraryItemRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryItemRow.swift; sourceTree = "<group>"; };
E13F05F028BC9016003499D2 /* LibraryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = "<group>"; };
E1546776289AF46E00087E35 /* CollectionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemView.swift; sourceTree = "<group>"; }; E1546776289AF46E00087E35 /* CollectionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemView.swift; sourceTree = "<group>"; };
E1546779289AF48200087E35 /* CollectionItemContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemContentView.swift; sourceTree = "<group>"; }; E1546779289AF48200087E35 /* CollectionItemContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemContentView.swift; sourceTree = "<group>"; };
E168BD08289A4162001A6922 /* HomeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; }; E168BD08289A4162001A6922 /* HomeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
@ -1151,6 +1157,7 @@
E19169CD272514760085832A /* HTTPScheme.swift */, E19169CD272514760085832A /* HTTPScheme.swift */,
E1C925F328875037002A7A66 /* ItemViewType.swift */, E1C925F328875037002A7A66 /* ItemViewType.swift */,
E1E1644328BC60C600323B0A /* LibraryItem.swift */, E1E1644328BC60C600323B0A /* LibraryItem.swift */,
E13F05EB28BC9000003499D2 /* LibraryViewType.swift */,
E1AA331E2782639D00F6439C /* OverlayType.swift */, E1AA331E2782639D00F6439C /* OverlayType.swift */,
E1C925F62887504B002A7A66 /* PanDirectionGestureRecognizer.swift */, E1C925F62887504B002A7A66 /* PanDirectionGestureRecognizer.swift */,
E193D4DA27193CCA00900D82 /* PillStackable.swift */, E193D4DA27193CCA00900D82 /* PillStackable.swift */,
@ -1681,7 +1688,7 @@
E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */, E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */,
E14F7D0A26DB3714007C3AE6 /* ItemView */, E14F7D0A26DB3714007C3AE6 /* ItemView */,
53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */, 53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */,
53DF641D263D9C0600A7CD1A /* LibraryView.swift */, E13F05EE28BC9016003499D2 /* LibraryView */,
C4E5598828124C10003DECA5 /* LiveTVChannelItemElement.swift */, C4E5598828124C10003DECA5 /* LiveTVChannelItemElement.swift */,
C400DB6C27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift */, C400DB6C27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift */,
C400DB6927FE894F007B65FE /* LiveTVChannelsView.swift */, C400DB6927FE894F007B65FE /* LiveTVChannelsView.swift */,
@ -1700,6 +1707,15 @@
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
E13F05EE28BC9016003499D2 /* LibraryView */ = {
isa = PBXGroup;
children = (
E1C55AB228BD051700A9AD88 /* Components */,
E13F05F028BC9016003499D2 /* LibraryView.swift */,
);
path = LibraryView;
sourceTree = "<group>";
};
E14F7D0A26DB3714007C3AE6 /* ItemView */ = { E14F7D0A26DB3714007C3AE6 /* ItemView */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -2008,6 +2024,14 @@
path = ContinueWatchingView; path = ContinueWatchingView;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
E1C55AB228BD051700A9AD88 /* Components */ = {
isa = PBXGroup;
children = (
E13F05EF28BC9016003499D2 /* LibraryItemRow.swift */,
);
path = Components;
sourceTree = "<group>";
};
E1C812CF277AE4C700918266 /* VideoPlayerCoordinator */ = { E1C812CF277AE4C700918266 /* VideoPlayerCoordinator */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -2472,6 +2496,7 @@
62E632DD267D2E130063E547 /* SearchViewModel.swift in Sources */, 62E632DD267D2E130063E547 /* SearchViewModel.swift in Sources */,
536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */, 536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */,
5D1603FD278A40DB00D22B99 /* SubtitleSize.swift in Sources */, 5D1603FD278A40DB00D22B99 /* SubtitleSize.swift in Sources */,
E13F05F228BC9016003499D2 /* LibraryItemRow.swift in Sources */,
E103A6A7278AB6D700820EC7 /* CinematicResumeCardView.swift in Sources */, E103A6A7278AB6D700820EC7 /* CinematicResumeCardView.swift in Sources */,
62E1DCC4273CE19800C9AE76 /* URLExtensions.swift in Sources */, 62E1DCC4273CE19800C9AE76 /* URLExtensions.swift in Sources */,
E10EAA54277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */, E10EAA54277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */,
@ -2527,6 +2552,7 @@
E193D5512719432400900D82 /* ServerDetailViewModel.swift in Sources */, E193D5512719432400900D82 /* ServerDetailViewModel.swift in Sources */,
C4E5081B2703F82A0045C9AB /* MediaView.swift in Sources */, C4E5081B2703F82A0045C9AB /* MediaView.swift in Sources */,
E193D53B27193F9200900D82 /* SettingsCoordinator.swift in Sources */, E193D53B27193F9200900D82 /* SettingsCoordinator.swift in Sources */,
E13F05ED28BC9000003499D2 /* LibraryViewType.swift in Sources */,
E18E021C2887492B0022598C /* BlurView.swift in Sources */, E18E021C2887492B0022598C /* BlurView.swift in Sources */,
E1E5D5442783BB5100692DFE /* ItemDetailsView.swift in Sources */, E1E5D5442783BB5100692DFE /* ItemDetailsView.swift in Sources */,
E10D87E327852FD000BD264C /* EpisodesRowManager.swift in Sources */, E10D87E327852FD000BD264C /* EpisodesRowManager.swift in Sources */,
@ -2646,7 +2672,6 @@
E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */, E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */,
532175402671EE4F005491E6 /* LibraryFilterView.swift in Sources */, 532175402671EE4F005491E6 /* LibraryFilterView.swift in Sources */,
E1171A1928A2212600FA1AF5 /* QuickConnectView.swift in Sources */, E1171A1928A2212600FA1AF5 /* QuickConnectView.swift in Sources */,
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */,
E11CEB8D28999B4A003E74C7 /* FontExtensions.swift in Sources */, E11CEB8D28999B4A003E74C7 /* FontExtensions.swift in Sources */,
E1C812CE277AE43100918266 /* VideoPlayerViewModel.swift in Sources */, E1C812CE277AE43100918266 /* VideoPlayerViewModel.swift in Sources */,
E11895A9289383BC0042947B /* ScrollViewOffsetModifier.swift in Sources */, E11895A9289383BC0042947B /* ScrollViewOffsetModifier.swift in Sources */,
@ -2654,6 +2679,7 @@
E18CE0B228A229E70092E7F1 /* UserDtoExtensions.swift in Sources */, E18CE0B228A229E70092E7F1 /* UserDtoExtensions.swift in Sources */,
E18E01F0288747230022598C /* AttributeHStack.swift in Sources */, E18E01F0288747230022598C /* AttributeHStack.swift in Sources */,
6334175B287DDFB9000603CE /* QuickConnectSettingsView.swift in Sources */, 6334175B287DDFB9000603CE /* QuickConnectSettingsView.swift in Sources */,
E13F05F128BC9016003499D2 /* LibraryItemRow.swift in Sources */,
E18E0205288749200022598C /* AppIcon.swift in Sources */, E18E0205288749200022598C /* AppIcon.swift in Sources */,
E168BD10289A4162001A6922 /* HomeView.swift in Sources */, E168BD10289A4162001A6922 /* HomeView.swift in Sources */,
E18E01AB288746AF0022598C /* PillHStack.swift in Sources */, E18E01AB288746AF0022598C /* PillHStack.swift in Sources */,
@ -2753,7 +2779,9 @@
09389CC726819B4600AE350E /* VideoPlayerModel.swift in Sources */, 09389CC726819B4600AE350E /* VideoPlayerModel.swift in Sources */,
E1D4BF872719D27100A11E64 /* Bitrates.swift in Sources */, E1D4BF872719D27100A11E64 /* Bitrates.swift in Sources */,
6220D0B726D5EE1100B8E046 /* SearchCoordinator.swift in Sources */, 6220D0B726D5EE1100B8E046 /* SearchCoordinator.swift in Sources */,
E13F05F328BC9016003499D2 /* LibraryView.swift in Sources */,
E13DD3EF27178F87009D4DAF /* SwiftfinNotificationCenter.swift in Sources */, E13DD3EF27178F87009D4DAF /* SwiftfinNotificationCenter.swift in Sources */,
E13F05EC28BC9000003499D2 /* LibraryViewType.swift in Sources */,
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */, 5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */,
E13DD4022717EE79009D4DAF /* UserListCoordinator.swift in Sources */, E13DD4022717EE79009D4DAF /* UserListCoordinator.swift in Sources */,
E1FCD09626C47118007C8DCF /* ErrorMessage.swift in Sources */, E1FCD09626C47118007C8DCF /* ErrorMessage.swift in Sources */,

View File

@ -0,0 +1,58 @@
//
// 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 (c) 2022 Jellyfin & Jellyfin Contributors
//
import JellyfinAPI
import SwiftUI
struct LibraryItemRow: View {
@EnvironmentObject
private var router: LibraryCoordinator.Router
let item: BaseItemDto
var body: some View {
Button {
router.route(to: \.item, item)
} label: {
HStack(alignment: .bottom) {
PosterButton(item: item, type: .portrait)
.scaleItem(0.6)
.content { _ in }
VStack(alignment: .leading) {
Text(item.displayName)
.foregroundColor(.primary)
.fontWeight(.semibold)
.lineLimit(2)
.multilineTextAlignment(.leading)
.fixedSize(horizontal: false, vertical: true)
DotHStack {
if let premiereYear = item.premiereDateYear {
Text(premiereYear)
}
if let runtime = item.getItemRuntime() {
Text(runtime)
}
if let officialRating = item.officialRating {
Text(officialRating)
}
}
.font(.caption)
.foregroundColor(Color(UIColor.lightGray))
}
.padding(.vertical)
Spacer()
}
}
}
}

View File

@ -13,12 +13,14 @@ import SwiftUI
struct LibraryView: View { struct LibraryView: View {
@EnvironmentObject @EnvironmentObject
private var libraryRouter: LibraryCoordinator.Router private var router: LibraryCoordinator.Router
@ObservedObject @ObservedObject
var viewModel: LibraryViewModel var viewModel: LibraryViewModel
@Default(.Customization.libraryPosterType) @Default(.Customization.Library.gridPosterType)
private var libraryPosterType private var libraryGridPosterType
@Default(.Customization.Library.viewType)
private var libraryViewType
@ViewBuilder @ViewBuilder
private var loadingView: some View { private var loadingView: some View {
@ -31,21 +33,41 @@ struct LibraryView: View {
} }
private var gridLayout: NSCollectionLayoutSection.GridLayoutMode { private var gridLayout: NSCollectionLayoutSection.GridLayoutMode {
if libraryPosterType == .landscape && UIDevice.isPhone { if libraryGridPosterType == .landscape && UIDevice.isPhone {
return .fixedNumberOfColumns(2) return .fixedNumberOfColumns(2)
} else { } else {
return .adaptive(withMinItemSize: libraryPosterType.width + (UIDevice.isIPad ? 10 : 0)) return .adaptive(withMinItemSize: libraryGridPosterType.width + (UIDevice.isIPad ? 10 : 0))
} }
} }
@ViewBuilder @ViewBuilder
private var libraryItemsView: some View { private var libraryListView: some View {
CollectionView(items: viewModel.items) { _, item, _ in CollectionView(items: viewModel.items) { _, item, _ in
PosterButton(item: item, type: libraryPosterType) LibraryItemRow(item: item)
.padding()
}
.layout { _, layoutEnvironment in
.list(using: .init(appearance: .plain), layoutEnvironment: layoutEnvironment)
}
.willReachEdge(insets: .init(top: 0, leading: 0, bottom: 200, trailing: 0)) { edge in
if !viewModel.isLoading && edge == .bottom {
viewModel.requestNextPageAsync()
}
}
.configure { configuration in
configuration.showsVerticalScrollIndicator = false
}
.ignoresSafeArea()
}
@ViewBuilder
private var libraryGridView: some View {
CollectionView(items: viewModel.items) { _, item, _ in
PosterButton(item: item, type: libraryGridPosterType)
.onSelect { item in .onSelect { item in
libraryRouter.route(to: \.item, item) router.route(to: \.item, item)
} }
.scaleItem(libraryPosterType == .landscape && UIDevice.isPhone ? 0.8 : 1) .scaleItem(libraryGridPosterType == .landscape && UIDevice.isPhone ? 0.8 : 1)
} }
.layout { _, layoutEnvironment in .layout { _, layoutEnvironment in
.grid( .grid(
@ -71,15 +93,35 @@ struct LibraryView: View {
} else if viewModel.items.isEmpty { } else if viewModel.items.isEmpty {
noResultsView noResultsView
} else { } else {
libraryItemsView switch libraryViewType {
case .grid:
libraryGridView
case .list:
libraryListView
}
} }
} }
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) { ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
switch libraryViewType {
case .grid:
libraryViewType = .list
case .list:
libraryViewType = .grid
}
} label: {
switch libraryViewType {
case .grid:
Image(systemName: "list.dash")
case .list:
Image(systemName: "square.grid.2x2")
}
}
Button { Button {
libraryRouter router
.route(to: \.filter, ( .route(to: \.filter, (
filters: $viewModel.filters, filters: $viewModel.filters,
enabledFilterType: viewModel.enabledFilterType, enabledFilterType: viewModel.enabledFilterType,

View File

@ -33,12 +33,15 @@ struct CustomizeViewsSettings: View {
var recommendedPosterType var recommendedPosterType
@Default(.Customization.searchPosterType) @Default(.Customization.searchPosterType)
var searchPosterType var searchPosterType
@Default(.Customization.libraryPosterType)
var libraryPosterType
@Default(.Customization.Episodes.useSeriesLandscapeBackdrop) @Default(.Customization.Episodes.useSeriesLandscapeBackdrop)
var useSeriesLandscapeBackdrop var useSeriesLandscapeBackdrop
@Default(.Customization.Library.gridPosterType)
var libraryGridPosterType
@Default(.Customization.Library.viewType)
var libraryViewType
var body: some View { var body: some View {
List { List {
Section { Section {
@ -96,12 +99,6 @@ struct CustomizeViewsSettings: View {
Text(type.localizedName).tag(type.rawValue) Text(type.localizedName).tag(type.rawValue)
} }
} }
Picker(L10n.library, selection: $libraryPosterType) {
ForEach(PosterType.allCases, id: \.self) { type in
Text(type.localizedName).tag(type.rawValue)
}
}
} header: { } header: {
// TODO: localize after organization // TODO: localize after organization
Text("Posters") Text("Posters")
@ -114,6 +111,23 @@ struct CustomizeViewsSettings: View {
// TODO: localize after organization // TODO: localize after organization
Text("Episode Landscape Poster") Text("Episode Landscape Poster")
} }
Section {
Picker(L10n.library, selection: $libraryGridPosterType) {
ForEach(PosterType.allCases, id: \.self) { type in
Text(type.localizedName).tag(type.rawValue)
}
}
Picker(L10n.items, selection: $libraryViewType) {
ForEach(LibraryViewType.allCases, id: \.self) { type in
Text(type.localizedName).tag(type.rawValue)
}
}
} header: {
// TODO: localize after organization
Text("Library")
}
} }
.navigationTitle(L10n.customize) .navigationTitle(L10n.customize)
} }