Transition Media View (#541)

This commit is contained in:
Ethan Pippin 2022-08-28 22:06:56 -06:00 committed by GitHub
parent 98a5507b52
commit d078d71393
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 388 additions and 309 deletions

View File

@ -1,46 +0,0 @@
//
// 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 Foundation
import Stinsen
import SwiftUI
final class LibraryListCoordinator: NavigationCoordinatable {
let stack = NavigationStack(initial: \LibraryListCoordinator.start)
@Root
var start = makeStart
@Route(.push)
var library = makeLibrary
#if os(iOS)
@Route(.push)
var liveTV = makeLiveTV
#endif
let viewModel: LibraryListViewModel
init(viewModel: LibraryListViewModel) {
self.viewModel = viewModel
}
func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator {
LibraryCoordinator(viewModel: params.viewModel, title: params.title)
}
#if os(iOS)
func makeLiveTV() -> LiveTVCoordinator {
LiveTVCoordinator()
}
#endif
@ViewBuilder
func makeStart() -> some View {
LibraryListView(viewModel: self.viewModel)
}
}

View File

@ -14,15 +14,15 @@ final class MainTabCoordinator: TabCoordinatable {
var child = TabChild(startingItems: [
\MainTabCoordinator.home,
\MainTabCoordinator.search,
\MainTabCoordinator.allMedia,
\MainTabCoordinator.media,
])
@Route(tabItem: makeHomeTab, onTapped: onHomeTapped)
var home = makeHome
@Route(tabItem: makeSearchTab, onTapped: onSearchTapped)
var search = makeSearch
@Route(tabItem: makeAllMediaTab, onTapped: onMediaTapped)
var allMedia = makeAllMedia
@Route(tabItem: makeMediaTab, onTapped: onMediaTapped)
var media = makeMedia
func makeHome() -> NavigationViewCoordinator<HomeCoordinator> {
NavigationViewCoordinator(HomeCoordinator())
@ -56,20 +56,20 @@ final class MainTabCoordinator: TabCoordinatable {
L10n.search.text
}
func makeAllMedia() -> NavigationViewCoordinator<LibraryListCoordinator> {
NavigationViewCoordinator(LibraryListCoordinator(viewModel: LibraryListViewModel()))
func makeMedia() -> NavigationViewCoordinator<MediaCoordinator> {
NavigationViewCoordinator(MediaCoordinator())
}
func onMediaTapped(isRepeat: Bool, coordinator: NavigationViewCoordinator<LibraryListCoordinator>) {
func onMediaTapped(isRepeat: Bool, coordinator: NavigationViewCoordinator<MediaCoordinator>) {
if isRepeat {
coordinator.child.popToRoot()
}
}
@ViewBuilder
func makeAllMediaTab(isActive: Bool) -> some View {
Image(systemName: "folder")
L10n.allMedia.text
func makeMediaTab(isActive: Bool) -> some View {
Image(systemName: "rectangle.stack.fill")
L10n.media.text
}
@ViewBuilder

View File

@ -16,7 +16,7 @@ final class MainTabCoordinator: TabCoordinatable {
\MainTabCoordinator.tv,
\MainTabCoordinator.movies,
\MainTabCoordinator.search,
\MainTabCoordinator.other,
\MainTabCoordinator.media,
\MainTabCoordinator.settings,
])
@ -28,8 +28,8 @@ final class MainTabCoordinator: TabCoordinatable {
var movies = makeMovies
@Route(tabItem: makeSearchTab)
var search = makeSearch
@Route(tabItem: makeOtherTab)
var other = makeOther
@Route(tabItem: makeMediaTab)
var media = makeMedia
@Route(tabItem: makeSettingsTab)
var settings = makeSettings
@ -81,15 +81,15 @@ final class MainTabCoordinator: TabCoordinatable {
}
}
func makeOther() -> NavigationViewCoordinator<LibraryListCoordinator> {
NavigationViewCoordinator(LibraryListCoordinator(viewModel: LibraryListViewModel()))
func makeMedia() -> NavigationViewCoordinator<MediaCoordinator> {
NavigationViewCoordinator(MediaCoordinator())
}
@ViewBuilder
func makeOtherTab(isActive: Bool) -> some View {
func makeMediaTab(isActive: Bool) -> some View {
HStack {
Image(systemName: "folder")
L10n.other.text
Image(systemName: "rectangle.stack")
L10n.media.text
}
}

View File

@ -0,0 +1,48 @@
//
// 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 Foundation
import Stinsen
import SwiftUI
final class MediaCoordinator: NavigationCoordinatable {
let stack = NavigationStack(initial: \MediaCoordinator.start)
@Root
var start = makeStart
#if os(tvOS)
@Route(.modal)
var library = makeLibrary
#else
@Route(.push)
var library = makeLibrary
@Route(.push)
var liveTV = makeLiveTV
#endif
#if os(tvOS)
func makeLibrary(params: LibraryCoordinatorParams) -> NavigationViewCoordinator<LibraryCoordinator> {
NavigationViewCoordinator(LibraryCoordinator(viewModel: params.viewModel, title: params.title))
}
#else
func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator {
LibraryCoordinator(viewModel: params.viewModel, title: params.title)
}
func makeLiveTV() -> LiveTVCoordinator {
LiveTVCoordinator()
}
#endif
@ViewBuilder
func makeStart() -> some View {
MediaView(viewModel: .init())
}
}

View File

@ -0,0 +1,27 @@
//
// 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 Foundation
extension Array {
func appending(_ element: Element) -> [Element] {
self + [element]
}
func appending(_ element: Element, if condition: Bool) -> [Element] {
if condition {
return self + [element]
} else {
return self
}
}
func appending(_ contents: [Element]) -> [Element] {
self + contents
}
}

View File

@ -0,0 +1,32 @@
//
// 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 LibraryItem: Equatable, Poster {
var library: BaseItemDto
var viewModel: MediaViewModel
var title: String = ""
var subtitle: String?
var showTitle: Bool = false
func portraitPosterImageSource(maxWidth: CGFloat) -> ImageSource {
.init()
}
func landscapePosterImageSources(maxWidth: CGFloat, single: Bool) -> [ImageSource] {
viewModel.libraryImages[library.id ?? ""] ?? []
}
static func == (lhs: LibraryItem, rhs: LibraryItem) -> Bool {
lhs.library == rhs.library &&
lhs.viewModel.libraryImages[lhs.library.id ?? ""] == rhs.viewModel.libraryImages[rhs.library.id ?? ""]
}
}

View File

@ -18,6 +18,7 @@ struct LibraryFilters: Codable, Hashable {
var sortBy: [SortBy] = [.name]
static let `default` = LibraryFilters()
static let favorites: LibraryFilters = .init(filters: [.isFavorite], sortOrder: [.ascending], sortBy: [.name])
}
public enum SortBy: String, Codable, CaseIterable {

View File

@ -1,47 +0,0 @@
//
// 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
import JellyfinAPI
final class LibraryListViewModel: ViewModel {
@Published
var libraries: [BaseItemDto] = []
var filteredLibraries: [BaseItemDto] {
var supportedLibraries = ["movies", "tvshows", "unknown"]
if Defaults[.Experimental.liveTVAlphaEnabled] {
supportedLibraries.append("livetv")
}
return libraries.filter { supportedLibraries.contains($0.collectionType ?? "unknown") }
}
// temp
let withFavorites = LibraryFilters(filters: [.isFavorite], sortOrder: [], withGenres: [], sortBy: [])
override init() {
super.init()
requestLibraries()
}
func requestLibraries() {
UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
.trackActivity(loading)
.sink(receiveCompletion: { completion in
self.handleAPIRequestError(completion: completion)
}, receiveValue: { response in
self.libraries = response.items ?? []
})
.store(in: &cancellables)
}
}

View File

@ -0,0 +1,66 @@
//
// 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
import JellyfinAPI
final class MediaViewModel: ViewModel {
@Published
var libraries: [BaseItemDto] = []
@Published
var libraryImages: [String: [ImageSource]] = [:]
private var supportedLibraries: [String] {
["movies", "tvshows", "unknown"]
.appending("livetv", if: Defaults[.Experimental.liveTVAlphaEnabled])
}
override init() {
super.init()
requestLibraries()
getRandomItemImageSource(with: [.isFavorite], id: nil, key: "favorites")
}
func requestLibraries() {
UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
.trackActivity(loading)
.sink(receiveCompletion: { completion in
self.handleAPIRequestError(completion: completion)
}, receiveValue: { response in
guard let items = response.items else { return }
self.libraries = items.filter { self.supportedLibraries.contains($0.collectionType ?? "unknown") }
self.libraries.forEach {
self.getRandomItemImageSource(with: nil, id: $0.id, key: $0.id ?? "")
}
})
.store(in: &cancellables)
}
private func getRandomItemImageSource(with filters: [ItemFilter]?, id: String?, key: String) {
ItemsAPI.getItemsByUserId(
userId: SessionManager.main.currentLogin.user.id,
limit: 3,
recursive: true,
parentId: id,
includeItemTypes: [.movie, .series],
filters: filters,
sortBy: ["Random"]
)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
}, receiveValue: { [weak self] response in
guard let items = response.items else { return }
let imageSources = items.map { $0.imageSource(.backdrop, maxWidth: 500) }
self?.libraryImages[key] = imageSources
})
.store(in: &cancellables)
}
}

View File

@ -53,7 +53,6 @@ final class SearchViewModel: ViewModel {
private func cancelPreviousSearch() {
searchCancellables.forEach { $0.cancel() }
print(searchCancellables.count)
}
func search(with query: String) {

View File

@ -61,6 +61,7 @@ struct PosterButton<Item: Poster, Content: View, ImageOverlay: View, ContextMenu
Button {
onSelect(item)
} label: {
Group {
switch type {
case .portrait:
ImageView(item.portraitPosterImageSource(maxWidth: itemWidth))
@ -70,14 +71,15 @@ struct PosterButton<Item: Poster, Content: View, ImageOverlay: View, ContextMenu
.posterStyle(type: type, width: itemWidth)
}
}
.buttonStyle(CardButtonStyle())
.contextMenu(menuItems: {
contextMenu(item)
})
.overlay {
imageOverlay(item)
.posterStyle(type: type, width: itemWidth)
}
}
.buttonStyle(CardButtonStyle())
.contextMenu(menuItems: {
contextMenu(item)
})
.posterShadow()
content(item)

View File

@ -1,66 +0,0 @@
//
// 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 Foundation
import JellyfinAPI
import Stinsen
import SwiftUI
struct LibraryListView: View {
@EnvironmentObject
var mainCoordinator: MainCoordinator.Router
@EnvironmentObject
private var libraryListRouter: LibraryListCoordinator.Router
@StateObject
var viewModel = LibraryListViewModel()
var body: some View {
ScrollView {
LazyVStack {
if !viewModel.isLoading {
ForEach(viewModel.filteredLibraries, id: \.id) { library in
Button {
if library.collectionType == "livetv" {
self.mainCoordinator.root(\.liveTV)
} else {
self.libraryListRouter.route(
to: \.library,
(viewModel: LibraryViewModel(parentID: library.id), 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 {
ProgressView()
}
}.padding(.leading, 16)
.padding(.trailing, 16)
.padding(.top, 8)
}
}
}

View File

@ -0,0 +1,63 @@
//
// 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 CollectionView
import JellyfinAPI
import Stinsen
import SwiftUI
struct MediaView: View {
@EnvironmentObject
private var router: MediaCoordinator.Router
@ObservedObject
var viewModel: MediaViewModel
private var libraryItems: [LibraryItem] {
[LibraryItem(library: .init(name: L10n.favorites, id: "favorites"), viewModel: viewModel)] +
viewModel.libraries.map { LibraryItem(library: $0, viewModel: viewModel) }
}
var body: some View {
CollectionView(items: libraryItems) { _, item, _ in
PosterButton(item: item, type: .landscape)
.scaleItem(0.8)
.onSelect { _ in
if item.library.id == "favorites" {
router.route(to: \.library, (viewModel: .init(filters: .favorites), title: ""))
} else {
router.route(to: \.library, (viewModel: .init(parentID: item.library.id), title: ""))
}
}
.imageOverlay { _ in
ZStack {
Color.black
.opacity(0.5)
Text(item.library.displayName)
.foregroundColor(.white)
.font(.title2)
.fontWeight(.semibold)
.lineLimit(2)
.multilineTextAlignment(.center)
.frame(alignment: .center)
}
}
}
.layout { _, layoutEnvironment in
.grid(
layoutEnvironment: layoutEnvironment,
layoutMode: .adaptive(withMinItemSize: 400),
lineSpacing: 50,
itemSize: .estimated(400),
sectionInsets: .zero
)
}
.ignoresSafeArea()
}
}

View File

@ -75,7 +75,7 @@
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */; };
53A83C33268A309300DF3D92 /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A83C32268A309300DF3D92 /* LibraryView.swift */; };
53ABFDDC267972BF00886593 /* TVServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53ABFDDB267972BF00886593 /* TVServices.framework */; };
53ABFDE4267974EF00886593 /* LibraryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5742678C33500530A6E /* LibraryListViewModel.swift */; };
53ABFDE4267974EF00886593 /* MediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5742678C33500530A6E /* MediaViewModel.swift */; };
53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB57B2678CE1000530A6E /* ViewModel.swift */; };
53ABFDE6267974EF00886593 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5321753A2671BCFC005491E6 /* SettingsViewModel.swift */; };
53ABFDE7267974EF00886593 /* ConnectToServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */; };
@ -90,7 +90,7 @@
5D160403278A41FD00D22B99 /* VLCPlayer+subtitles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D160402278A41FD00D22B99 /* VLCPlayer+subtitles.swift */; };
5D32EA12278C95E30020E292 /* VLCPlayer+subtitles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D160402278A41FD00D22B99 /* VLCPlayer+subtitles.swift */; };
5D64683D277B1649009E09AE /* PreferenceUIHostingSwizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D64683C277B1649009E09AE /* PreferenceUIHostingSwizzling.swift */; };
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6213388F265F83A900A81A2A /* LibraryListView.swift */; };
62133890265F83A900A81A2A /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6213388F265F83A900A81A2A /* MediaView.swift */; };
621338932660107500A81A2A /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338922660107500A81A2A /* StringExtensions.swift */; };
6220D0AD26D5EABB00B8E046 /* ViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0AC26D5EABB00B8E046 /* ViewExtensions.swift */; };
6220D0AE26D5EABB00B8E046 /* ViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0AC26D5EABB00B8E046 /* ViewExtensions.swift */; };
@ -105,7 +105,7 @@
62400C4B287ED19600F6AD3D /* UDPBroadcast.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 637FCAF3287B5B2600C0A353 /* UDPBroadcast.xcframework */; };
62400C4C287ED19600F6AD3D /* UDPBroadcast.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 637FCAF3287B5B2600C0A353 /* UDPBroadcast.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5722678C32A00530A6E /* HomeViewModel.swift */; };
625CB5752678C33500530A6E /* LibraryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5742678C33500530A6E /* LibraryListViewModel.swift */; };
625CB5752678C33500530A6E /* MediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5742678C33500530A6E /* MediaViewModel.swift */; };
625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */; };
625CB57A2678C4A400530A6E /* ActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 625CB5792678C4A400530A6E /* ActivityIndicator */; };
6264E88C273850380081A12A /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6264E88B273850380081A12A /* Strings.swift */; };
@ -152,7 +152,7 @@
62C29EA126D102A500C1D2E7 /* iOSMainTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA026D102A500C1D2E7 /* iOSMainTabCoordinator.swift */; };
62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA226D1030F00C1D2E7 /* ConnectToServerCoodinator.swift */; };
62C29EA626D1036A00C1D2E7 /* HomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */; };
62C29EA826D103D500C1D2E7 /* LibraryListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */; };
62C29EA826D103D500C1D2E7 /* MediaCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA726D103D500C1D2E7 /* MediaCoordinator.swift */; };
62C83B08288C6A630004ED0C /* FontPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C83B07288C6A630004ED0C /* FontPicker.swift */; };
62E1DCC3273CE19800C9AE76 /* URLExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E1DCC2273CE19800C9AE76 /* URLExtensions.swift */; };
62E1DCC4273CE19800C9AE76 /* URLExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E1DCC2273CE19800C9AE76 /* URLExtensions.swift */; };
@ -220,7 +220,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 */; };
C4E5081B2703F82A0045C9AB /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E508172703E8190045C9AB /* LibraryListView.swift */; };
C4E5081B2703F82A0045C9AB /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E508172703E8190045C9AB /* MediaView.swift */; };
C4E5598928124C10003DECA5 /* LiveTVChannelItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5598828124C10003DECA5 /* LiveTVChannelItemElement.swift */; };
E1002B5F2793C3BE00E47059 /* VLCPlayerChapterOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1002B5E2793C3BE00E47059 /* VLCPlayerChapterOverlayView.swift */; };
E1002B642793CEE800E47059 /* ChapterInfoExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1002B632793CEE700E47059 /* ChapterInfoExtensions.swift */; };
@ -391,7 +391,7 @@
E193D53427193F7F00900D82 /* HomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */; };
E193D53527193F8100900D82 /* ItemCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0BF26D61C5000B8E046 /* ItemCoordinator.swift */; };
E193D53627193F8500900D82 /* LibraryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0B326D5ED8000B8E046 /* LibraryCoordinator.swift */; };
E193D53727193F8700900D82 /* LibraryListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */; };
E193D53727193F8700900D82 /* MediaCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA726D103D500C1D2E7 /* MediaCoordinator.swift */; };
E193D53927193F8E00900D82 /* SearchCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */; };
E193D53A27193F9000900D82 /* ServerListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3E827177ED6009D4DAF /* ServerListCoordinator.swift */; };
E193D53B27193F9200900D82 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0B026D5EC9900B8E046 /* SettingsCoordinator.swift */; };
@ -478,6 +478,10 @@
E1E1643A28BAC2EF00323B0A /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1643928BAC2EF00323B0A /* SearchView.swift */; };
E1E1643E28BB074000323B0A /* MultiSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1643D28BB074000323B0A /* MultiSelectorView.swift */; };
E1E1643F28BB075C00323B0A /* MultiSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1643D28BB074000323B0A /* MultiSelectorView.swift */; };
E1E1644128BB301900323B0A /* ArrayExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1644028BB301900323B0A /* ArrayExtensions.swift */; };
E1E1644228BB301900323B0A /* ArrayExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1644028BB301900323B0A /* ArrayExtensions.swift */; };
E1E1644428BC60C600323B0A /* LibraryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1644328BC60C600323B0A /* LibraryItem.swift */; };
E1E1644528BC60C600323B0A /* LibraryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1644328BC60C600323B0A /* LibraryItem.swift */; };
E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */; };
E1E5D5442783BB5100692DFE /* ItemDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5432783BB5100692DFE /* ItemDetailsView.swift */; };
E1E5D5492783CDD700692DFE /* OverlaySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5472783CCF900692DFE /* OverlaySettingsView.swift */; };
@ -618,7 +622,7 @@
5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleSize.swift; sourceTree = "<group>"; };
5D160402278A41FD00D22B99 /* VLCPlayer+subtitles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VLCPlayer+subtitles.swift"; sourceTree = "<group>"; };
5D64683C277B1649009E09AE /* PreferenceUIHostingSwizzling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingSwizzling.swift; sourceTree = "<group>"; };
6213388F265F83A900A81A2A /* LibraryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListView.swift; sourceTree = "<group>"; };
6213388F265F83A900A81A2A /* MediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaView.swift; sourceTree = "<group>"; };
621338922660107500A81A2A /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = "<group>"; };
6220D0AC26D5EABB00B8E046 /* ViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewExtensions.swift; sourceTree = "<group>"; };
6220D0B026D5EC9900B8E046 /* SettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = "<group>"; };
@ -629,7 +633,7 @@
6220D0C526D62D8700B8E046 /* iOSVideoPlayerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSVideoPlayerCoordinator.swift; sourceTree = "<group>"; };
6220D0CB26D640C400B8E046 /* AppURLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppURLHandler.swift; sourceTree = "<group>"; };
625CB5722678C32A00530A6E /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = "<group>"; };
625CB5742678C33500530A6E /* LibraryListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListViewModel.swift; sourceTree = "<group>"; };
625CB5742678C33500530A6E /* MediaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaViewModel.swift; sourceTree = "<group>"; };
625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerViewModel.swift; sourceTree = "<group>"; };
625CB57B2678CE1000530A6E /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = "<group>"; };
625CB57D2678E81E00530A6E /* TVVLCKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = TVVLCKit.xcframework; path = Carthage/Build/TVVLCKit.xcframework; sourceTree = "<group>"; };
@ -668,7 +672,7 @@
62C29EA026D102A500C1D2E7 /* iOSMainTabCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSMainTabCoordinator.swift; sourceTree = "<group>"; };
62C29EA226D1030F00C1D2E7 /* ConnectToServerCoodinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerCoodinator.swift; sourceTree = "<group>"; };
62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCoordinator.swift; sourceTree = "<group>"; };
62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListCoordinator.swift; sourceTree = "<group>"; };
62C29EA726D103D500C1D2E7 /* MediaCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaCoordinator.swift; sourceTree = "<group>"; };
62C83B07288C6A630004ED0C /* FontPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontPicker.swift; sourceTree = "<group>"; };
62E1DCC2273CE19800C9AE76 /* URLExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLExtensions.swift; sourceTree = "<group>"; };
62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaViewModel.swift; sourceTree = "<group>"; };
@ -715,7 +719,7 @@
C4BE07872728448B003F4AD1 /* LiveTVChannelsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelsCoordinator.swift; sourceTree = "<group>"; };
C4BE078A272844AF003F4AD1 /* LiveTVChannelsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelsView.swift; sourceTree = "<group>"; };
C4BE078D27298817003F4AD1 /* LiveTVHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVHomeView.swift; sourceTree = "<group>"; };
C4E508172703E8190045C9AB /* LibraryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListView.swift; sourceTree = "<group>"; };
C4E508172703E8190045C9AB /* MediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaView.swift; sourceTree = "<group>"; };
C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelItemElement.swift; sourceTree = "<group>"; };
C4E5598828124C10003DECA5 /* LiveTVChannelItemElement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveTVChannelItemElement.swift; sourceTree = "<group>"; };
E1002B5E2793C3BE00E47059 /* VLCPlayerChapterOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VLCPlayerChapterOverlayView.swift; sourceTree = "<group>"; };
@ -891,6 +895,8 @@
E1E00A34278628A40022235B /* DoubleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoubleExtensions.swift; sourceTree = "<group>"; };
E1E1643928BAC2EF00323B0A /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
E1E1643D28BB074000323B0A /* MultiSelectorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultiSelectorView.swift; sourceTree = "<group>"; };
E1E1644028BB301900323B0A /* ArrayExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtensions.swift; sourceTree = "<group>"; };
E1E1644328BC60C600323B0A /* LibraryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryItem.swift; sourceTree = "<group>"; };
E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshHelper.swift; sourceTree = "<group>"; };
E1E5D5432783BB5100692DFE /* ItemDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDetailsView.swift; sourceTree = "<group>"; };
E1E5D5472783CCF900692DFE /* OverlaySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlaySettingsView.swift; sourceTree = "<group>"; };
@ -1028,10 +1034,10 @@
E107BB9127880A4000354E07 /* ItemViewModel */,
62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */,
62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */,
625CB5742678C33500530A6E /* LibraryListViewModel.swift */,
62E632DF267D30CA0063E547 /* LibraryViewModel.swift */,
C4BE07842728446F003F4AD1 /* LiveTVChannelsViewModel.swift */,
C4BE07752725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift */,
625CB5742678C33500530A6E /* MediaViewModel.swift */,
C40CD924271F8D1E000FB198 /* MovieLibrariesViewModel.swift */,
6334175C287DE0D0000603CE /* QuickConnectSettingsViewModel.swift */,
62E632DB267D2E130063E547 /* SearchViewModel.swift */,
@ -1144,6 +1150,7 @@
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */,
E19169CD272514760085832A /* HTTPScheme.swift */,
E1C925F328875037002A7A66 /* ItemViewType.swift */,
E1E1644328BC60C600323B0A /* LibraryItem.swift */,
E1AA331E2782639D00F6439C /* OverlayType.swift */,
E1C925F62887504B002A7A66 /* PanDirectionGestureRecognizer.swift */,
E193D4DA27193CCA00900D82 /* PillStackable.swift */,
@ -1421,6 +1428,7 @@
621338912660106C00A81A2A /* Extensions */ = {
isa = PBXGroup;
children = (
E1E1644028BB301900323B0A /* ArrayExtensions.swift */,
E1A2C157279A7D76005EC829 /* BundleExtensions.swift */,
E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */,
6267B3D526710B8900A7371D /* CollectionExtensions.swift */,
@ -1460,12 +1468,12 @@
6220D0BF26D61C5000B8E046 /* ItemCoordinator.swift */,
E1EBCB43278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift */,
6220D0B326D5ED8000B8E046 /* LibraryCoordinator.swift */,
62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */,
C4BE07872728448B003F4AD1 /* LiveTVChannelsCoordinator.swift */,
C45942C427F67DA400C54FE7 /* LiveTVCoordinator.swift */,
C4BE07702725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift */,
C4BE07782726EE82003F4AD1 /* LiveTVTabCoordinator.swift */,
E193D5412719404B00900D82 /* MainCoordinator */,
62C29EA726D103D500C1D2E7 /* MediaCoordinator.swift */,
C40CD921271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift */,
E18CE0B828A2322D0092E7F1 /* QuickConnectCoordinator.swift */,
6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */,
@ -1615,12 +1623,12 @@
E193D54E271942C000900D82 /* ItemView */,
E1C925F828875647002A7A66 /* LatestInLibraryView.swift */,
E193D54C2719426600900D82 /* LibraryFilterView.swift */,
C4E508172703E8190045C9AB /* LibraryListView.swift */,
53A83C32268A309300DF3D92 /* LibraryView.swift */,
C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */,
C4BE078A272844AF003F4AD1 /* LiveTVChannelsView.swift */,
C4BE078D27298817003F4AD1 /* LiveTVHomeView.swift */,
C4BE07732725EB66003F4AD1 /* LiveTVProgramsView.swift */,
C4E508172703E8190045C9AB /* MediaView.swift */,
C40CD927271F8DAB000FB198 /* MovieLibrariesView.swift */,
E1E1643928BAC2EF00323B0A /* SearchView.swift */,
E193D54F2719430400900D82 /* ServerDetailView.swift */,
@ -1673,13 +1681,13 @@
E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */,
E14F7D0A26DB3714007C3AE6 /* ItemView */,
53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */,
6213388F265F83A900A81A2A /* LibraryListView.swift */,
53DF641D263D9C0600A7CD1A /* LibraryView.swift */,
C4E5598828124C10003DECA5 /* LiveTVChannelItemElement.swift */,
C400DB6C27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift */,
C400DB6927FE894F007B65FE /* LiveTVChannelsView.swift */,
C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */,
C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */,
6213388F265F83A900A81A2A /* MediaView.swift */,
E1171A1828A2212600FA1AF5 /* QuickConnectView.swift */,
53EE24E5265060780068F029 /* SearchView.swift */,
E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */,
@ -2433,7 +2441,7 @@
62E632E7267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
E1C926122887565C002A7A66 /* SeriesItemContentView.swift in Sources */,
C4BE0767271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */,
E193D53727193F8700900D82 /* LibraryListCoordinator.swift in Sources */,
E193D53727193F8700900D82 /* MediaCoordinator.swift in Sources */,
E18E023C288749540022598C /* UIScrollViewExtensions.swift in Sources */,
C4534983279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift in Sources */,
E1C9260F2887565C002A7A66 /* AttributeHStack.swift in Sources */,
@ -2483,6 +2491,7 @@
53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */,
E184C161288C5C08000B25BA /* RequestBuilderExtensions.swift in Sources */,
E19169CF272514760085832A /* HTTPScheme.swift in Sources */,
E1E1644528BC60C600323B0A /* LibraryItem.swift in Sources */,
E13849452780370B0024FB48 /* PlaybackSpeed.swift in Sources */,
E1C812CD277AE40A00918266 /* VideoPlayerViewModel.swift in Sources */,
C45B29BB26FAC5B600CEF5E0 /* ColorExtension.swift in Sources */,
@ -2498,6 +2507,7 @@
E1E00A36278628A40022235B /* DoubleExtensions.swift in Sources */,
E1E1643A28BAC2EF00323B0A /* SearchView.swift in Sources */,
E1FA2F7427818A8800B4C270 /* SmallMenuOverlay.swift in Sources */,
E1E1644228BB301900323B0A /* ArrayExtensions.swift in Sources */,
E193D53C27193F9500900D82 /* UserListCoordinator.swift in Sources */,
E1CEFBF727914E6400F60429 /* CustomizeViewsSettings.swift in Sources */,
E18CE0B528A22EDD0092E7F1 /* RepeatingTimer.swift in Sources */,
@ -2515,7 +2525,7 @@
6264E88D273850380081A12A /* Strings.swift in Sources */,
E1C926102887565C002A7A66 /* PlayButton.swift in Sources */,
E193D5512719432400900D82 /* ServerDetailViewModel.swift in Sources */,
C4E5081B2703F82A0045C9AB /* LibraryListView.swift in Sources */,
C4E5081B2703F82A0045C9AB /* MediaView.swift in Sources */,
E193D53B27193F9200900D82 /* SettingsCoordinator.swift in Sources */,
E18E021C2887492B0022598C /* BlurView.swift in Sources */,
E1E5D5442783BB5100692DFE /* ItemDetailsView.swift in Sources */,
@ -2527,7 +2537,7 @@
E1D4BF7F2719D1DD00A11E64 /* BasicAppSettingsViewModel.swift in Sources */,
E18E021D2887492B0022598C /* AttributeOutlineView.swift in Sources */,
E193D53227193F7B00900D82 /* ConnectToServerCoodinator.swift in Sources */,
53ABFDE4267974EF00886593 /* LibraryListViewModel.swift in Sources */,
53ABFDE4267974EF00886593 /* MediaViewModel.swift in Sources */,
5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */,
E1937A62288F32DB00CB80AA /* Poster.swift in Sources */,
C4BE0764271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift in Sources */,
@ -2563,7 +2573,7 @@
E1C925F428875037002A7A66 /* ItemViewType.swift in Sources */,
62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */,
625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */,
62C29EA826D103D500C1D2E7 /* LibraryListCoordinator.swift in Sources */,
62C29EA826D103D500C1D2E7 /* MediaCoordinator.swift in Sources */,
62E632DC267D2E130063E547 /* SearchViewModel.swift in Sources */,
C400DB6D27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift in Sources */,
62C29E9F26D1016600C1D2E7 /* iOSMainCoordinator.swift in Sources */,
@ -2579,6 +2589,7 @@
62C29EA126D102A500C1D2E7 /* iOSMainTabCoordinator.swift in Sources */,
E18E01E8288747230022598C /* SeriesItemContentView.swift in Sources */,
E16AA60828A364A6009A983C /* PosterButton.swift in Sources */,
E1E1644128BB301900323B0A /* ArrayExtensions.swift in Sources */,
E18CE0AF28A222240092E7F1 /* PublicUserSignInView.swift in Sources */,
E18E01E5288747230022598C /* CinematicScrollView.swift in Sources */,
E1C812C0277A8E5D00918266 /* VLCPlayerView.swift in Sources */,
@ -2608,7 +2619,7 @@
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */,
C4AE2C3027498D2300AE13CF /* LiveTVHomeView.swift in Sources */,
E1EBCB42278BD174009FE6E9 /* TruncatedTextView.swift in Sources */,
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */,
62133890265F83A900A81A2A /* MediaView.swift in Sources */,
62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */,
E18E01E1288747230022598C /* EpisodeItemContentView.swift in Sources */,
62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */,
@ -2694,6 +2705,7 @@
62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */,
E11CEB8B28998552003E74C7 /* iOSViewExtensions.swift in Sources */,
E193D4DB27193CCA00900D82 /* PillStackable.swift in Sources */,
E1E1644428BC60C600323B0A /* LibraryItem.swift in Sources */,
E18E0206288749200022598C /* AttributeFillView.swift in Sources */,
E1AA331F2782639D00F6439C /* OverlayType.swift in Sources */,
E1A2C154279A7D5A005EC829 /* UIApplicationExtensions.swift in Sources */,
@ -2746,7 +2758,7 @@
E13DD4022717EE79009D4DAF /* UserListCoordinator.swift in Sources */,
E1FCD09626C47118007C8DCF /* ErrorMessage.swift in Sources */,
53EE24E6265060780068F029 /* SearchView.swift in Sources */,
625CB5752678C33500530A6E /* LibraryListViewModel.swift in Sources */,
625CB5752678C33500530A6E /* MediaViewModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -51,6 +51,7 @@ struct PosterButton<Item: Poster, Content: View, ImageOverlay: View, ContextMenu
Button {
onSelect(item)
} label: {
Group {
switch type {
case .portrait:
ImageView(item.portraitPosterImageSource(maxWidth: itemWidth))
@ -58,14 +59,15 @@ struct PosterButton<Item: Poster, Content: View, ImageOverlay: View, ContextMenu
ImageView(item.landscapePosterImageSources(maxWidth: itemWidth, single: singleImage))
}
}
.contextMenu(menuItems: {
contextMenu(item)
})
.posterStyle(type: type, width: itemWidth)
.overlay {
imageOverlay(item)
.posterStyle(type: type, width: itemWidth)
}
}
.contextMenu(menuItems: {
contextMenu(item)
})
.posterStyle(type: type, width: itemWidth)
.posterShadow()
content(item)

View File

@ -1,93 +0,0 @@
//
// 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 Foundation
import JellyfinAPI
import Stinsen
import SwiftUI
struct LibraryListView: View {
@EnvironmentObject
private var libraryListRouter: LibraryListCoordinator.Router
@StateObject
var viewModel = LibraryListViewModel()
var body: some View {
ScrollView {
LazyVStack {
Button {
libraryListRouter.route(
to: \.library,
(viewModel: LibraryViewModel(filters: viewModel.withFavorites), title: L10n.favorites)
)
} label: {
ZStack {
HStack {
Spacer()
L10n.yourFavorites.text
.foregroundColor(.black)
.font(.subheadline)
.fontWeight(.semibold)
Spacer()
}
}
.padding(16)
.background(Color.white)
.frame(minWidth: 100, maxWidth: .infinity)
}
.cornerRadius(10)
.shadow(radius: 5)
.padding(.bottom, 5)
if !viewModel.isLoading {
ForEach(viewModel.filteredLibraries, id: \.id) { library in
Button {
if library.collectionType == "livetv" {
libraryListRouter.route(to: \.liveTV)
} else {
libraryListRouter.route(
to: \.library,
(
viewModel: LibraryViewModel(parentID: library.id),
title: library.name ?? ""
)
)
}
} label: {
ZStack {
ImageView(library.imageSource(.primary, maxWidth: 500))
.opacity(0.4)
.accessibilityIgnoresInvertColors()
HStack {
Spacer()
VStack {
Text(library.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)
}
} else {
ProgressView()
}
}.padding(.leading, 16)
.padding(.trailing, 16)
.padding(.top, 8)
}
.navigationTitle(L10n.allMedia)
}
}

View File

@ -0,0 +1,79 @@
//
// 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 CollectionView
import Defaults
import JellyfinAPI
import Stinsen
import SwiftUI
struct MediaView: View {
@EnvironmentObject
private var router: MediaCoordinator.Router
@ObservedObject
var viewModel: MediaViewModel
@Default(.Experimental.liveTVAlphaEnabled)
var liveTVEnabled
private var libraryItems: [LibraryItem] {
[LibraryItem(library: .init(name: L10n.favorites, id: "favorites"), viewModel: viewModel)]
.appending(.init(library: .init(name: "LiveTV", id: "liveTV"), viewModel: viewModel), if: liveTVEnabled)
.appending(viewModel.libraries.map { LibraryItem(library: $0, viewModel: viewModel) })
}
private var gridLayout: NSCollectionLayoutSection.GridLayoutMode {
if UIDevice.isPhone {
return .fixedNumberOfColumns(2)
} else {
return .adaptive(withMinItemSize: PosterType.landscape.width)
}
}
var body: some View {
CollectionView(items: libraryItems) { _, item, _ in
PosterButton(item: item, type: .landscape)
.scaleItem(UIDevice.isPhone ? 0.9 : 1)
.onSelect { _ in
if item.library.id == "favorites" {
router.route(to: \.library, (viewModel: .init(filters: .favorites), title: ""))
} else if item.library.id == "liveTV" {
router.route(to: \.liveTV)
} else {
router.route(to: \.library, (viewModel: .init(parentID: item.library.id), title: ""))
}
}
.imageOverlay { _ in
ZStack {
Color.black
.opacity(0.5)
Text(item.library.displayName)
.foregroundColor(.white)
.font(.title2)
.fontWeight(.semibold)
.lineLimit(2)
.multilineTextAlignment(.center)
.frame(alignment: .center)
}
}
}
.layout { _, layoutEnvironment in
.grid(
layoutEnvironment: layoutEnvironment,
layoutMode: gridLayout,
sectionInsets: .init(top: 0, leading: 10, bottom: 0, trailing: 10)
)
}
.configure { configuration in
configuration.showsVerticalScrollIndicator = false
}
.ignoresSafeArea()
.navigationTitle(L10n.allMedia)
}
}