Move to `IdentifiedArray` in `PagingLibraryViewModel` (#1346)

* init

* update packages, cleanup
This commit is contained in:
Ethan Pippin 2024-12-08 10:44:52 -07:00 committed by GitHub
parent 0797fb5fee
commit e856303181
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 215 additions and 151 deletions

View File

@ -1086,10 +1086,10 @@ internal enum L10n {
internal static let run = L10n.tr("Localizable", "run", fallback: "Run") internal static let run = L10n.tr("Localizable", "run", fallback: "Run")
/// Running... /// Running...
internal static let running = L10n.tr("Localizable", "running", fallback: "Running...") internal static let running = L10n.tr("Localizable", "running", fallback: "Running...")
/// Runtime
internal static let runtime = L10n.tr("Localizable", "runtime", fallback: "Runtime")
/// Run Time /// Run Time
internal static let runTime = L10n.tr("Localizable", "runTime", fallback: "Run Time") internal static let runTime = L10n.tr("Localizable", "runTime", fallback: "Run Time")
/// Runtime
internal static let runtime = L10n.tr("Localizable", "runtime", fallback: "Runtime")
/// Save /// Save
internal static let save = L10n.tr("Localizable", "save", fallback: "Save") internal static let save = L10n.tr("Localizable", "save", fallback: "Save")
/// Scan All Libraries /// Scan All Libraries

View File

@ -10,6 +10,7 @@ import Combine
import Defaults import Defaults
import Foundation import Foundation
import Get import Get
import IdentifiedCollections
import JellyfinAPI import JellyfinAPI
import OrderedCollections import OrderedCollections
import UIKit import UIKit
@ -32,7 +33,7 @@ private let DefaultPageSize = 50
on remembering other filters. on remembering other filters.
*/ */
class PagingLibraryViewModel<Element: Poster>: ViewModel, Eventful, Stateful { class PagingLibraryViewModel<Element: Poster & Identifiable>: ViewModel, Eventful, Stateful {
// MARK: Event // MARK: Event
@ -67,7 +68,7 @@ class PagingLibraryViewModel<Element: Poster>: ViewModel, Eventful, Stateful {
@Published @Published
final var backgroundStates: OrderedSet<BackgroundState> = [] final var backgroundStates: OrderedSet<BackgroundState> = []
@Published @Published
final var elements: OrderedSet<Element> final var elements: IdentifiedArrayOf<Element>
@Published @Published
final var state: State = .initial final var state: State = .initial
@Published @Published
@ -103,7 +104,7 @@ class PagingLibraryViewModel<Element: Poster>: ViewModel, Eventful, Stateful {
parent: (any LibraryParent)? = nil parent: (any LibraryParent)? = nil
) { ) {
self.filterViewModel = nil self.filterViewModel = nil
self.elements = OrderedSet(data) self.elements = IdentifiedArray(uniqueElements: data)
self.isStatic = true self.isStatic = true
self.hasNextPage = false self.hasNextPage = false
self.pageSize = DefaultPageSize self.pageSize = DefaultPageSize
@ -130,7 +131,7 @@ class PagingLibraryViewModel<Element: Poster>: ViewModel, Eventful, Stateful {
filters: ItemFilterCollection? = nil, filters: ItemFilterCollection? = nil,
pageSize: Int = DefaultPageSize pageSize: Int = DefaultPageSize
) { ) {
self.elements = OrderedSet() self.elements = IdentifiedArray()
self.isStatic = false self.isStatic = false
self.pageSize = pageSize self.pageSize = pageSize
self.parent = parent self.parent = parent

View File

@ -11,7 +11,8 @@ import JellyfinAPI
extension MediaViewModel { extension MediaViewModel {
enum MediaType: Displayable, Hashable { enum MediaType: Displayable, Hashable, Identifiable {
case collectionFolder(BaseItemDto) case collectionFolder(BaseItemDto)
case downloads case downloads
case favorites case favorites
@ -29,5 +30,18 @@ extension MediaViewModel {
return L10n.liveTV return L10n.liveTV
} }
} }
var id: String? {
switch self {
case let .collectionFolder(item):
return item.id
case .downloads:
return "downloads"
case .favorites:
return "favorites"
case let .liveTV(item):
return item.id
}
}
} }
} }

View File

@ -12,19 +12,19 @@ import SwiftUI
// TODO: trailing content refactor? // TODO: trailing content refactor?
struct PosterHStack<Item: Poster>: View { struct PosterHStack<Element: Poster & Identifiable, Data: Collection>: View where Data.Element == Element, Data.Index == Int {
private var data: Data
private var title: String? private var title: String?
private var type: PosterDisplayType private var type: PosterDisplayType
private var items: Binding<OrderedSet<Item>> private var content: (Element) -> any View
private var content: (Item) -> any View private var imageOverlay: (Element) -> any View
private var imageOverlay: (Item) -> any View private var contextMenu: (Element) -> any View
private var contextMenu: (Item) -> any View
private var trailingContent: () -> any View private var trailingContent: () -> any View
private var onSelect: (Item) -> Void private var onSelect: (Element) -> Void
// See PosterButton for implementation reason // See PosterButton for implementation reason
private var focusedItem: Binding<Item?>? private var focusedItem: Binding<Element?>?
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 20) { VStack(alignment: .leading, spacing: 20) {
@ -42,7 +42,7 @@ struct PosterHStack<Item: Poster>: View {
} }
CollectionHStack( CollectionHStack(
items, uniqueElements: data,
columns: type == .landscape ? 4 : 7 columns: type == .landscape ? 4 : 7
) { item in ) { item in
PosterButton(item: item, type: type) PosterButton(item: item, type: type)
@ -86,42 +86,29 @@ extension PosterHStack {
init( init(
title: String? = nil, title: String? = nil,
type: PosterDisplayType, type: PosterDisplayType,
items: Binding<OrderedSet<Item>> items: Data
) { ) {
self.init( self.init(
data: items,
title: title, title: title,
type: type, type: type,
items: items,
content: { PosterButton.TitleSubtitleContentView(item: $0) }, content: { PosterButton.TitleSubtitleContentView(item: $0) },
imageOverlay: { PosterButton.DefaultOverlay(item: $0) }, imageOverlay: { PosterButton.DefaultOverlay(item: $0) },
contextMenu: { _ in EmptyView() }, contextMenu: { _ in EmptyView() },
trailingContent: { EmptyView() }, trailingContent: { EmptyView() },
onSelect: { _ in }, onSelect: { _ in }
focusedItem: nil
) )
} }
init<S: Sequence<Item>>( func content(@ViewBuilder _ content: @escaping (Element) -> any View) -> Self {
title: String? = nil,
type: PosterDisplayType,
items: S
) {
self.init(
title: title,
type: type,
items: .constant(OrderedSet(items))
)
}
func content(@ViewBuilder _ content: @escaping (Item) -> any View) -> Self {
copy(modifying: \.content, with: content) copy(modifying: \.content, with: content)
} }
func imageOverlay(@ViewBuilder _ content: @escaping (Item) -> any View) -> Self { func imageOverlay(@ViewBuilder _ content: @escaping (Element) -> any View) -> Self {
copy(modifying: \.imageOverlay, with: content) copy(modifying: \.imageOverlay, with: content)
} }
func contextMenu(@ViewBuilder _ content: @escaping (Item) -> any View) -> Self { func contextMenu(@ViewBuilder _ content: @escaping (Element) -> any View) -> Self {
copy(modifying: \.contextMenu, with: content) copy(modifying: \.contextMenu, with: content)
} }
@ -129,11 +116,11 @@ extension PosterHStack {
copy(modifying: \.trailingContent, with: content) copy(modifying: \.trailingContent, with: content)
} }
func onSelect(_ action: @escaping (Item) -> Void) -> Self { func onSelect(_ action: @escaping (Element) -> Void) -> Self {
copy(modifying: \.onSelect, with: action) copy(modifying: \.onSelect, with: action)
} }
func focusedItem(_ binding: Binding<Item?>) -> Self { func focusedItem(_ binding: Binding<Element?>) -> Self {
copy(modifying: \.focusedItem, with: binding) copy(modifying: \.focusedItem, with: binding)
} }
} }

View File

@ -22,7 +22,7 @@ struct ChannelLibraryView: View {
@ViewBuilder @ViewBuilder
private var contentView: some View { private var contentView: some View {
CollectionVGrid( CollectionVGrid(
$viewModel.elements, uniqueElements: viewModel.elements,
layout: .columns(3, insets: .init(0), itemSpacing: 25, lineSpacing: 25) layout: .columns(3, insets: .init(0), itemSpacing: 25, lineSpacing: 25)
) { channel in ) { channel in
WideChannelGridItem(channel: channel) WideChannelGridItem(channel: channel)

View File

@ -24,7 +24,7 @@ extension HomeView {
PosterHStack( PosterHStack(
title: L10n.latestWithString(viewModel.parent?.displayTitle ?? .emptyDash), title: L10n.latestWithString(viewModel.parent?.displayTitle ?? .emptyDash),
type: .portrait, type: .portrait,
items: $viewModel.elements items: viewModel.elements
) )
.onSelect { item in .onSelect { item in
router.route(to: \.item, item) router.route(to: \.item, item)

View File

@ -27,7 +27,7 @@ extension HomeView {
PosterHStack( PosterHStack(
title: L10n.nextUp, title: L10n.nextUp,
type: nextUpPosterType, type: nextUpPosterType,
items: $viewModel.elements items: viewModel.elements
) )
.onSelect { item in .onSelect { item in
router.route(to: \.item, item) router.route(to: \.item, item)

View File

@ -27,7 +27,7 @@ extension HomeView {
PosterHStack( PosterHStack(
title: L10n.recentlyAdded, title: L10n.recentlyAdded,
type: recentlyAddedPosterType, type: recentlyAddedPosterType,
items: $viewModel.elements items: viewModel.elements
) )
.onSelect { item in .onSelect { item in
router.route(to: \.item, item) router.route(to: \.item, item)

View File

@ -30,13 +30,13 @@ extension SeriesEpisodeSelector {
private var lastFocusedEpisodeID: String? private var lastFocusedEpisodeID: String?
@StateObject @StateObject
private var proxy = CollectionHStackProxy<BaseItemDto>() private var proxy = CollectionHStackProxy()
let playButtonItem: BaseItemDto? let playButtonItem: BaseItemDto?
private func contentView(viewModel: SeasonItemViewModel) -> some View { private func contentView(viewModel: SeasonItemViewModel) -> some View {
CollectionHStack( CollectionHStack(
$viewModel.elements, uniqueElements: viewModel.elements,
columns: 3.5 columns: 3.5
) { episode in ) { episode in
SeriesEpisodeSelector.EpisodeCard(episode: episode) SeriesEpisodeSelector.EpisodeCard(episode: episode)
@ -103,7 +103,7 @@ extension SeriesEpisodeSelector {
var body: some View { var body: some View {
CollectionHStack( CollectionHStack(
0 ..< 1, count: 1,
columns: 3.5 columns: 3.5
) { _ in ) { _ in
SeriesEpisodeSelector.ErrorCard(error: error) SeriesEpisodeSelector.ErrorCard(error: error)
@ -121,7 +121,7 @@ extension SeriesEpisodeSelector {
var body: some View { var body: some View {
CollectionHStack( CollectionHStack(
0 ..< Int.random(in: 2 ..< 5), count: Int.random(in: 2 ..< 5),
columns: 3.5 columns: 3.5
) { _ in ) { _ in
SeriesEpisodeSelector.LoadingCard() SeriesEpisodeSelector.LoadingCard()

View File

@ -31,7 +31,7 @@ extension ItemView {
PosterHStack( PosterHStack(
title: L10n.recommended, title: L10n.recommended,
type: similarPosterType, type: similarPosterType,
items: $viewModel.elements items: viewModel.elements
) )
.onSelect { item in .onSelect { item in
router.route(to: \.item, item) router.route(to: \.item, item)

View File

@ -23,7 +23,7 @@ struct MediaView: View {
@ViewBuilder @ViewBuilder
private var contentView: some View { private var contentView: some View {
CollectionVGrid( CollectionVGrid(
$viewModel.mediaItems, uniqueElements: viewModel.mediaItems,
layout: .columns(4, insets: .init(50), itemSpacing: 50, lineSpacing: 50) layout: .columns(4, insets: .init(50), itemSpacing: 50, lineSpacing: 50)
) { mediaType in ) { mediaType in
MediaItem(viewModel: viewModel, type: mediaType) MediaItem(viewModel: viewModel, type: mediaType)

View File

@ -16,7 +16,7 @@ import SwiftUI
// TODO: list row view (LibraryRow) // TODO: list row view (LibraryRow)
// TODO: fix paging for next item focusing the tab // TODO: fix paging for next item focusing the tab
struct PagingLibraryView<Element: Poster>: View { struct PagingLibraryView<Element: Poster & Identifiable>: View {
@Default(.Customization.Library.cinematicBackground) @Default(.Customization.Library.cinematicBackground)
private var cinematicBackground private var cinematicBackground
@ -159,7 +159,7 @@ struct PagingLibraryView<Element: Poster>: View {
@ViewBuilder @ViewBuilder
private var contentView: some View { private var contentView: some View {
CollectionVGrid( CollectionVGrid(
$viewModel.elements, uniqueElements: viewModel.elements,
layout: layout layout: layout
) { item in ) { item in
switch (posterType, viewType) { switch (posterType, viewType) {

View File

@ -453,13 +453,12 @@
E1153D9C2BBA3E9D00424D36 /* LoadingCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1153D9B2BBA3E9D00424D36 /* LoadingCard.swift */; }; E1153D9C2BBA3E9D00424D36 /* LoadingCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1153D9B2BBA3E9D00424D36 /* LoadingCard.swift */; };
E1153DA42BBA614F00424D36 /* CollectionVGrid in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DA32BBA614F00424D36 /* CollectionVGrid */; }; E1153DA42BBA614F00424D36 /* CollectionVGrid in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DA32BBA614F00424D36 /* CollectionVGrid */; };
E1153DAC2BBA6AD200424D36 /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DAB2BBA6AD200424D36 /* CollectionHStack */; }; E1153DAC2BBA6AD200424D36 /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DAB2BBA6AD200424D36 /* CollectionHStack */; };
E1153DAF2BBA734200424D36 /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DAE2BBA734200424D36 /* CollectionHStack */; };
E1153DB12BBA734C00424D36 /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DB02BBA734C00424D36 /* CollectionHStack */; };
E1153DB42BBA80FB00424D36 /* EmptyCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1153DB22BBA80B400424D36 /* EmptyCard.swift */; }; E1153DB42BBA80FB00424D36 /* EmptyCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1153DB22BBA80B400424D36 /* EmptyCard.swift */; };
E1153DCC2BBB633B00424D36 /* FastSVGView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1153DCB2BBB633B00424D36 /* FastSVGView.swift */; }; E1153DCC2BBB633B00424D36 /* FastSVGView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1153DCB2BBB633B00424D36 /* FastSVGView.swift */; };
E1153DCD2BBB633B00424D36 /* FastSVGView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1153DCB2BBB633B00424D36 /* FastSVGView.swift */; }; E1153DCD2BBB633B00424D36 /* FastSVGView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1153DCB2BBB633B00424D36 /* FastSVGView.swift */; };
E1153DD02BBB634F00424D36 /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DCF2BBB634F00424D36 /* SVGKit */; }; E1153DD02BBB634F00424D36 /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DCF2BBB634F00424D36 /* SVGKit */; };
E1153DD22BBB649C00424D36 /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DD12BBB649C00424D36 /* SVGKit */; }; E1153DD22BBB649C00424D36 /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DD12BBB649C00424D36 /* SVGKit */; };
E1155ACB2D0584A90021557A /* IdentifiedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = E1155ACA2D0584A90021557A /* IdentifiedCollections */; };
E11562952C818CB2001D5DE4 /* BindingBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11562942C818CB2001D5DE4 /* BindingBox.swift */; }; E11562952C818CB2001D5DE4 /* BindingBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11562942C818CB2001D5DE4 /* BindingBox.swift */; };
E11562962C818CB2001D5DE4 /* BindingBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11562942C818CB2001D5DE4 /* BindingBox.swift */; }; E11562962C818CB2001D5DE4 /* BindingBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11562942C818CB2001D5DE4 /* BindingBox.swift */; };
E118959D289312020042947B /* BaseItemPerson+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E118959C289312020042947B /* BaseItemPerson+Poster.swift */; }; E118959D289312020042947B /* BaseItemPerson+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E118959C289312020042947B /* BaseItemPerson+Poster.swift */; };
@ -525,8 +524,6 @@
E12E30F5296392EC0022FAC9 /* EnumPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12E30F4296392EC0022FAC9 /* EnumPickerView.swift */; }; E12E30F5296392EC0022FAC9 /* EnumPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12E30F4296392EC0022FAC9 /* EnumPickerView.swift */; };
E12F038C28F8B0B100976CC3 /* EdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12F038B28F8B0B100976CC3 /* EdgeInsets.swift */; }; E12F038C28F8B0B100976CC3 /* EdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12F038B28F8B0B100976CC3 /* EdgeInsets.swift */; };
E132D3C82BD200C10058A2DF /* CollectionVGrid in Frameworks */ = {isa = PBXBuildFile; productRef = E132D3C72BD200C10058A2DF /* CollectionVGrid */; }; E132D3C82BD200C10058A2DF /* CollectionVGrid in Frameworks */ = {isa = PBXBuildFile; productRef = E132D3C72BD200C10058A2DF /* CollectionVGrid */; };
E132D3CD2BD2179C0058A2DF /* CollectionVGrid in Frameworks */ = {isa = PBXBuildFile; productRef = E132D3CC2BD2179C0058A2DF /* CollectionVGrid */; };
E132D3CF2BD217AA0058A2DF /* CollectionVGrid in Frameworks */ = {isa = PBXBuildFile; productRef = E132D3CE2BD217AA0058A2DF /* CollectionVGrid */; };
E13316FE2ADE42B6009BF865 /* OnSizeChangedModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13316FD2ADE42B6009BF865 /* OnSizeChangedModifier.swift */; }; E13316FE2ADE42B6009BF865 /* OnSizeChangedModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13316FD2ADE42B6009BF865 /* OnSizeChangedModifier.swift */; };
E13316FF2ADE42B6009BF865 /* OnSizeChangedModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13316FD2ADE42B6009BF865 /* OnSizeChangedModifier.swift */; }; E13316FF2ADE42B6009BF865 /* OnSizeChangedModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13316FD2ADE42B6009BF865 /* OnSizeChangedModifier.swift */; };
E133328829538D8D00EE76AB /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = E133328729538D8D00EE76AB /* Files.swift */; }; E133328829538D8D00EE76AB /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = E133328729538D8D00EE76AB /* Files.swift */; };
@ -749,6 +746,10 @@
E1763A722BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A702BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift */; }; E1763A722BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A702BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift */; };
E1763A742BF3FA4C004DF6AB /* AppLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A732BF3FA4C004DF6AB /* AppLoadingView.swift */; }; E1763A742BF3FA4C004DF6AB /* AppLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A732BF3FA4C004DF6AB /* AppLoadingView.swift */; };
E1763A762BF3FF01004DF6AB /* AppLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A752BF3FF01004DF6AB /* AppLoadingView.swift */; }; E1763A762BF3FF01004DF6AB /* AppLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A752BF3FF01004DF6AB /* AppLoadingView.swift */; };
E176EBDE2D050067009F4CF1 /* IdentifiedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = E176EBDD2D050067009F4CF1 /* IdentifiedCollections */; };
E176EBE02D0502A6009F4CF1 /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E176EBDF2D0502A6009F4CF1 /* CollectionHStack */; };
E176EBE32D0502C6009F4CF1 /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E176EBE22D0502C6009F4CF1 /* CollectionHStack */; };
E176EBE92D050925009F4CF1 /* CollectionVGrid in Frameworks */ = {isa = PBXBuildFile; productRef = E176EBE82D050925009F4CF1 /* CollectionVGrid */; };
E178859B2780F1F40094FBCF /* tvOSSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E178859A2780F1F40094FBCF /* tvOSSlider.swift */; }; E178859B2780F1F40094FBCF /* tvOSSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E178859A2780F1F40094FBCF /* tvOSSlider.swift */; };
E178859E2780F53B0094FBCF /* SliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E178859D2780F53B0094FBCF /* SliderView.swift */; }; E178859E2780F53B0094FBCF /* SliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E178859D2780F53B0094FBCF /* SliderView.swift */; };
E17885A4278105170094FBCF /* SFSymbolButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17885A3278105170094FBCF /* SFSymbolButton.swift */; }; E17885A4278105170094FBCF /* SFSymbolButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17885A3278105170094FBCF /* SFSymbolButton.swift */; };
@ -878,6 +879,10 @@
E19E6E0728A0B958005C10C8 /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = E19E6E0628A0B958005C10C8 /* NukeUI */; }; E19E6E0728A0B958005C10C8 /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = E19E6E0628A0B958005C10C8 /* NukeUI */; };
E19E6E0A28A0BEFF005C10C8 /* BlurHashKit in Frameworks */ = {isa = PBXBuildFile; productRef = E19E6E0928A0BEFF005C10C8 /* BlurHashKit */; }; E19E6E0A28A0BEFF005C10C8 /* BlurHashKit in Frameworks */ = {isa = PBXBuildFile; productRef = E19E6E0928A0BEFF005C10C8 /* BlurHashKit */; };
E19F6C5D28F5189300C5197E /* MediaStreamInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19F6C5C28F5189300C5197E /* MediaStreamInfoView.swift */; }; E19F6C5D28F5189300C5197E /* MediaStreamInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19F6C5C28F5189300C5197E /* MediaStreamInfoView.swift */; };
E1A09F722D05933D00835265 /* CollectionVGrid in Frameworks */ = {isa = PBXBuildFile; productRef = E1A09F712D05933D00835265 /* CollectionVGrid */; };
E1A09F752D05935100835265 /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E1A09F742D05935100835265 /* CollectionHStack */; };
E1A09F772D05935A00835265 /* CollectionVGrid in Frameworks */ = {isa = PBXBuildFile; productRef = E1A09F762D05935A00835265 /* CollectionVGrid */; };
E1A09F792D05935A00835265 /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E1A09F782D05935A00835265 /* CollectionHStack */; };
E1A1528228FD126C00600579 /* VerticalAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A1528128FD126C00600579 /* VerticalAlignment.swift */; }; E1A1528228FD126C00600579 /* VerticalAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A1528128FD126C00600579 /* VerticalAlignment.swift */; };
E1A1528528FD191A00600579 /* TextPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A1528428FD191A00600579 /* TextPair.swift */; }; E1A1528528FD191A00600579 /* TextPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A1528428FD191A00600579 /* TextPair.swift */; };
E1A1528828FD229500600579 /* ChevronButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A1528728FD229500600579 /* ChevronButton.swift */; }; E1A1528828FD229500600579 /* ChevronButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A1528728FD229500600579 /* ChevronButton.swift */; };
@ -1934,6 +1939,7 @@
E13AF3B828A0C598009093AB /* NukeExtensions in Frameworks */, E13AF3B828A0C598009093AB /* NukeExtensions in Frameworks */,
E1575E58293E7685001665B1 /* Files in Frameworks */, E1575E58293E7685001665B1 /* Files in Frameworks */,
E1B5F7A729577BCE004B26CF /* Pulse in Frameworks */, E1B5F7A729577BCE004B26CF /* Pulse in Frameworks */,
E1A09F792D05935A00835265 /* CollectionHStack in Frameworks */,
E13AF3BA28A0C598009093AB /* NukeUI in Frameworks */, E13AF3BA28A0C598009093AB /* NukeUI in Frameworks */,
E1B5F7AB29577BCE004B26CF /* PulseUI in Frameworks */, E1B5F7AB29577BCE004B26CF /* PulseUI in Frameworks */,
E1B5F7A929577BCE004B26CF /* PulseLogHandler in Frameworks */, E1B5F7A929577BCE004B26CF /* PulseLogHandler in Frameworks */,
@ -1943,18 +1949,18 @@
62666E1927E501D000EC0ECD /* CoreFoundation.framework in Frameworks */, 62666E1927E501D000EC0ECD /* CoreFoundation.framework in Frameworks */,
E19D41B22BF2BFA50082B8B2 /* KeychainSwift in Frameworks */, E19D41B22BF2BFA50082B8B2 /* KeychainSwift in Frameworks */,
E18443CB2A037773002DDDC8 /* UDPBroadcast in Frameworks */, E18443CB2A037773002DDDC8 /* UDPBroadcast in Frameworks */,
E1155ACB2D0584A90021557A /* IdentifiedCollections in Frameworks */,
62666E2E27E5021400EC0ECD /* Security.framework in Frameworks */, 62666E2E27E5021400EC0ECD /* Security.framework in Frameworks */,
E1B5F7AD29577BDD004B26CF /* OrderedCollections in Frameworks */, E1B5F7AD29577BDD004B26CF /* OrderedCollections in Frameworks */,
53ABFDDC267972BF00886593 /* TVServices.framework in Frameworks */, 53ABFDDC267972BF00886593 /* TVServices.framework in Frameworks */,
62666E1F27E501DF00EC0ECD /* CoreText.framework in Frameworks */, 62666E1F27E501DF00EC0ECD /* CoreText.framework in Frameworks */,
E13DD3CD27164CA7009D4DAF /* CoreStore in Frameworks */, E13DD3CD27164CA7009D4DAF /* CoreStore in Frameworks */,
E1A7B1652B9A9F7800152546 /* PreferencesView in Frameworks */, E1A7B1652B9A9F7800152546 /* PreferencesView in Frameworks */,
E1A09F772D05935A00835265 /* CollectionVGrid in Frameworks */,
E1153DD22BBB649C00424D36 /* SVGKit in Frameworks */, E1153DD22BBB649C00424D36 /* SVGKit in Frameworks */,
62666E1527E501C800EC0ECD /* AVFoundation.framework in Frameworks */, 62666E1527E501C800EC0ECD /* AVFoundation.framework in Frameworks */,
E132D3CF2BD217AA0058A2DF /* CollectionVGrid in Frameworks */,
E13AF3BC28A0C59E009093AB /* BlurHashKit in Frameworks */, E13AF3BC28A0C59E009093AB /* BlurHashKit in Frameworks */,
E150C0C32BFD6DA200944FFA /* JellyfinAPI in Frameworks */, E150C0C32BFD6DA200944FFA /* JellyfinAPI in Frameworks */,
E1153DB12BBA734C00424D36 /* CollectionHStack in Frameworks */,
62666E1327E501C300EC0ECD /* AudioToolbox.framework in Frameworks */, 62666E1327E501C300EC0ECD /* AudioToolbox.framework in Frameworks */,
E13AF3B628A0C598009093AB /* Nuke in Frameworks */, E13AF3B628A0C598009093AB /* Nuke in Frameworks */,
E12186DE2718F1C50010884C /* Defaults in Frameworks */, E12186DE2718F1C50010884C /* Defaults in Frameworks */,
@ -1981,6 +1987,7 @@
62666E0627E5017A00EC0ECD /* CoreVideo.framework in Frameworks */, 62666E0627E5017A00EC0ECD /* CoreVideo.framework in Frameworks */,
E19DDEC72948EF9900954E10 /* OrderedCollections in Frameworks */, E19DDEC72948EF9900954E10 /* OrderedCollections in Frameworks */,
E10706102942F57D00646DAF /* Pulse in Frameworks */, E10706102942F57D00646DAF /* Pulse in Frameworks */,
E176EBE92D050925009F4CF1 /* CollectionVGrid in Frameworks */,
E192608328D2D0DB002314B4 /* Factory in Frameworks */, E192608328D2D0DB002314B4 /* Factory in Frameworks */,
E150C0C12BFD62FD00944FFA /* JellyfinAPI in Frameworks */, E150C0C12BFD62FD00944FFA /* JellyfinAPI in Frameworks */,
E113A2A72B5A178D009CAAAA /* CollectionHStack in Frameworks */, E113A2A72B5A178D009CAAAA /* CollectionHStack in Frameworks */,
@ -1990,7 +1997,9 @@
62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */, 62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */,
62666E0227E5016D00EC0ECD /* CoreGraphics.framework in Frameworks */, 62666E0227E5016D00EC0ECD /* CoreGraphics.framework in Frameworks */,
E1575E3C293C6B15001665B1 /* Files in Frameworks */, E1575E3C293C6B15001665B1 /* Files in Frameworks */,
E176EBE02D0502A6009F4CF1 /* CollectionHStack in Frameworks */,
E14EA1652BF70A8E00DE757A /* Mantis in Frameworks */, E14EA1652BF70A8E00DE757A /* Mantis in Frameworks */,
E176EBDE2D050067009F4CF1 /* IdentifiedCollections in Frameworks */,
62666E1027E501B400EC0ECD /* VideoToolbox.framework in Frameworks */, 62666E1027E501B400EC0ECD /* VideoToolbox.framework in Frameworks */,
62666E0C27E501A500EC0ECD /* OpenGLES.framework in Frameworks */, 62666E0C27E501A500EC0ECD /* OpenGLES.framework in Frameworks */,
E19E6E0A28A0BEFF005C10C8 /* BlurHashKit in Frameworks */, E19E6E0A28A0BEFF005C10C8 /* BlurHashKit in Frameworks */,
@ -2003,22 +2012,23 @@
E18A8E7A28D5FEDF00333B9A /* VLCUI in Frameworks */, E18A8E7A28D5FEDF00333B9A /* VLCUI in Frameworks */,
E114DB332B1944FA00B75FB3 /* CollectionVGrid in Frameworks */, E114DB332B1944FA00B75FB3 /* CollectionVGrid in Frameworks */,
E15210562946DF1B00375CC2 /* PulseLogHandler in Frameworks */, E15210562946DF1B00375CC2 /* PulseLogHandler in Frameworks */,
E1153DAF2BBA734200424D36 /* CollectionHStack in Frameworks */,
62666E0427E5017500EC0ECD /* CoreText.framework in Frameworks */, 62666E0427E5017500EC0ECD /* CoreText.framework in Frameworks */,
E132D3CD2BD2179C0058A2DF /* CollectionVGrid in Frameworks */,
E13DD3C62716499E009D4DAF /* CoreStore in Frameworks */, E13DD3C62716499E009D4DAF /* CoreStore in Frameworks */,
E176EBE32D0502C6009F4CF1 /* CollectionHStack in Frameworks */,
62666E0E27E501AF00EC0ECD /* Security.framework in Frameworks */, 62666E0E27E501AF00EC0ECD /* Security.framework in Frameworks */,
E1DC9814296DC06200982F06 /* PulseLogHandler in Frameworks */, E1DC9814296DC06200982F06 /* PulseLogHandler in Frameworks */,
E15EFA842BA167350080E926 /* CollectionHStack in Frameworks */, E15EFA842BA167350080E926 /* CollectionHStack in Frameworks */,
E15EFA862BA1685F0080E926 /* SwiftUIIntrospect in Frameworks */, E15EFA862BA1685F0080E926 /* SwiftUIIntrospect in Frameworks */,
62666DFE27E5015700EC0ECD /* AVFoundation.framework in Frameworks */, 62666DFE27E5015700EC0ECD /* AVFoundation.framework in Frameworks */,
62666DFD27E5014F00EC0ECD /* AudioToolbox.framework in Frameworks */, 62666DFD27E5014F00EC0ECD /* AudioToolbox.framework in Frameworks */,
E1A09F722D05933D00835265 /* CollectionVGrid in Frameworks */,
E19E6E0528A0B958005C10C8 /* Nuke in Frameworks */, E19E6E0528A0B958005C10C8 /* Nuke in Frameworks */,
E1153DAC2BBA6AD200424D36 /* CollectionHStack in Frameworks */, E1153DAC2BBA6AD200424D36 /* CollectionHStack in Frameworks */,
62666E0D27E501AA00EC0ECD /* QuartzCore.framework in Frameworks */, 62666E0D27E501AA00EC0ECD /* QuartzCore.framework in Frameworks */,
E15D4F052B1B0C3C00442DB8 /* PreferencesView in Frameworks */, E15D4F052B1B0C3C00442DB8 /* PreferencesView in Frameworks */,
E19E6E0728A0B958005C10C8 /* NukeUI in Frameworks */, E19E6E0728A0B958005C10C8 /* NukeUI in Frameworks */,
62666E3F27E5040300EC0ECD /* SystemConfiguration.framework in Frameworks */, 62666E3F27E5040300EC0ECD /* SystemConfiguration.framework in Frameworks */,
E1A09F752D05935100835265 /* CollectionHStack in Frameworks */,
62666E3927E502CE00EC0ECD /* SwizzleSwift in Frameworks */, 62666E3927E502CE00EC0ECD /* SwizzleSwift in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -4611,11 +4621,12 @@
E18443CA2A037773002DDDC8 /* UDPBroadcast */, E18443CA2A037773002DDDC8 /* UDPBroadcast */,
E1A7B1642B9A9F7800152546 /* PreferencesView */, E1A7B1642B9A9F7800152546 /* PreferencesView */,
E1392FEC2BA218A80034110D /* SwiftUIIntrospect */, E1392FEC2BA218A80034110D /* SwiftUIIntrospect */,
E1153DB02BBA734C00424D36 /* CollectionHStack */,
E1153DD12BBB649C00424D36 /* SVGKit */, E1153DD12BBB649C00424D36 /* SVGKit */,
E132D3CE2BD217AA0058A2DF /* CollectionVGrid */,
E19D41B12BF2BFA50082B8B2 /* KeychainSwift */, E19D41B12BF2BFA50082B8B2 /* KeychainSwift */,
E150C0C22BFD6DA200944FFA /* JellyfinAPI */, E150C0C22BFD6DA200944FFA /* JellyfinAPI */,
E1155ACA2D0584A90021557A /* IdentifiedCollections */,
E1A09F762D05935A00835265 /* CollectionVGrid */,
E1A09F782D05935A00835265 /* CollectionHStack */,
); );
productName = "JellyfinPlayer tvOS"; productName = "JellyfinPlayer tvOS";
productReference = 535870602669D21600D05A09 /* Swiftfin tvOS.app */; productReference = 535870602669D21600D05A09 /* Swiftfin tvOS.app */;
@ -4665,13 +4676,17 @@
E18D6AA52BAA96F000A0D167 /* CollectionHStack */, E18D6AA52BAA96F000A0D167 /* CollectionHStack */,
E1153DA32BBA614F00424D36 /* CollectionVGrid */, E1153DA32BBA614F00424D36 /* CollectionVGrid */,
E1153DAB2BBA6AD200424D36 /* CollectionHStack */, E1153DAB2BBA6AD200424D36 /* CollectionHStack */,
E1153DAE2BBA734200424D36 /* CollectionHStack */,
E1153DCF2BBB634F00424D36 /* SVGKit */, E1153DCF2BBB634F00424D36 /* SVGKit */,
E132D3C72BD200C10058A2DF /* CollectionVGrid */, E132D3C72BD200C10058A2DF /* CollectionVGrid */,
E132D3CC2BD2179C0058A2DF /* CollectionVGrid */,
E145EB4A2BE16849003BF6F3 /* KeychainSwift */, E145EB4A2BE16849003BF6F3 /* KeychainSwift */,
E14EA1642BF70A8E00DE757A /* Mantis */, E14EA1642BF70A8E00DE757A /* Mantis */,
E150C0C02BFD62FD00944FFA /* JellyfinAPI */, E150C0C02BFD62FD00944FFA /* JellyfinAPI */,
E176EBDD2D050067009F4CF1 /* IdentifiedCollections */,
E176EBDF2D0502A6009F4CF1 /* CollectionHStack */,
E176EBE22D0502C6009F4CF1 /* CollectionHStack */,
E176EBE82D050925009F4CF1 /* CollectionVGrid */,
E1A09F712D05933D00835265 /* CollectionVGrid */,
E1A09F742D05935100835265 /* CollectionHStack */,
); );
productName = JellyfinPlayer; productName = JellyfinPlayer;
productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */; productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */;
@ -4739,12 +4754,13 @@
E1DC9812296DC06200982F06 /* XCRemoteSwiftPackageReference "PulseLogHandler" */, E1DC9812296DC06200982F06 /* XCRemoteSwiftPackageReference "PulseLogHandler" */,
E1FAD1C42A0375BA007F5521 /* XCRemoteSwiftPackageReference "UDPBroadcastConnection" */, E1FAD1C42A0375BA007F5521 /* XCRemoteSwiftPackageReference "UDPBroadcastConnection" */,
E15D4F032B1B0C3C00442DB8 /* XCLocalSwiftPackageReference "PreferencesView" */, E15D4F032B1B0C3C00442DB8 /* XCLocalSwiftPackageReference "PreferencesView" */,
E1153DAD2BBA734200424D36 /* XCRemoteSwiftPackageReference "CollectionHStack" */,
E1153DCE2BBB634F00424D36 /* XCRemoteSwiftPackageReference "SVGKit" */, E1153DCE2BBB634F00424D36 /* XCRemoteSwiftPackageReference "SVGKit" */,
E132D3CB2BD2179C0058A2DF /* XCRemoteSwiftPackageReference "CollectionVGrid" */,
E145EB492BE16849003BF6F3 /* XCRemoteSwiftPackageReference "keychain-swift" */, E145EB492BE16849003BF6F3 /* XCRemoteSwiftPackageReference "keychain-swift" */,
E14EA1632BF70A8E00DE757A /* XCRemoteSwiftPackageReference "Mantis" */, E14EA1632BF70A8E00DE757A /* XCRemoteSwiftPackageReference "Mantis" */,
E150C0BF2BFD62FD00944FFA /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */, E150C0BF2BFD62FD00944FFA /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */,
E176EBDC2D050067009F4CF1 /* XCRemoteSwiftPackageReference "swift-identified-collections" */,
E1A09F702D05933D00835265 /* XCRemoteSwiftPackageReference "CollectionVGrid" */,
E1A09F732D05935100835265 /* XCRemoteSwiftPackageReference "CollectionHStack" */,
); );
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */; productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -6359,14 +6375,6 @@
minimumVersion = 2.0.0; minimumVersion = 2.0.0;
}; };
}; };
E1153DAD2BBA734200424D36 /* XCRemoteSwiftPackageReference "CollectionHStack" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/LePips/CollectionHStack";
requirement = {
branch = main;
kind = branch;
};
};
E1153DCE2BBB634F00424D36 /* XCRemoteSwiftPackageReference "SVGKit" */ = { E1153DCE2BBB634F00424D36 /* XCRemoteSwiftPackageReference "SVGKit" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/SVGKit/SVGKit"; repositoryURL = "https://github.com/SVGKit/SVGKit";
@ -6375,14 +6383,6 @@
minimumVersion = 3.0.0; minimumVersion = 3.0.0;
}; };
}; };
E132D3CB2BD2179C0058A2DF /* XCRemoteSwiftPackageReference "CollectionVGrid" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/LePips/CollectionVGrid";
requirement = {
branch = main;
kind = branch;
};
};
E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */ = { E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/JohnEstropia/CoreStore.git"; repositoryURL = "https://github.com/JohnEstropia/CoreStore.git";
@ -6439,6 +6439,14 @@
minimumVersion = 4.0.0; minimumVersion = 4.0.0;
}; };
}; };
E176EBDC2D050067009F4CF1 /* XCRemoteSwiftPackageReference "swift-identified-collections" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/pointfreeco/swift-identified-collections";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.1.0;
};
};
E18A8E7828D5FEDF00333B9A /* XCRemoteSwiftPackageReference "VLCUI" */ = { E18A8E7828D5FEDF00333B9A /* XCRemoteSwiftPackageReference "VLCUI" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/LePips/VLCUI"; repositoryURL = "https://github.com/LePips/VLCUI";
@ -6479,6 +6487,22 @@
minimumVersion = 1.0.0; minimumVersion = 1.0.0;
}; };
}; };
E1A09F702D05933D00835265 /* XCRemoteSwiftPackageReference "CollectionVGrid" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/LePips/CollectionVGrid";
requirement = {
branch = main;
kind = branch;
};
};
E1A09F732D05935100835265 /* XCRemoteSwiftPackageReference "CollectionHStack" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/LePips/CollectionHStack";
requirement = {
branch = main;
kind = branch;
};
};
E1DC9812296DC06200982F06 /* XCRemoteSwiftPackageReference "PulseLogHandler" */ = { E1DC9812296DC06200982F06 /* XCRemoteSwiftPackageReference "PulseLogHandler" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kean/PulseLogHandler"; repositoryURL = "https://github.com/kean/PulseLogHandler";
@ -6558,16 +6582,6 @@
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = CollectionHStack; productName = CollectionHStack;
}; };
E1153DAE2BBA734200424D36 /* CollectionHStack */ = {
isa = XCSwiftPackageProductDependency;
package = E1153DAD2BBA734200424D36 /* XCRemoteSwiftPackageReference "CollectionHStack" */;
productName = CollectionHStack;
};
E1153DB02BBA734C00424D36 /* CollectionHStack */ = {
isa = XCSwiftPackageProductDependency;
package = E1153DAD2BBA734200424D36 /* XCRemoteSwiftPackageReference "CollectionHStack" */;
productName = CollectionHStack;
};
E1153DCF2BBB634F00424D36 /* SVGKit */ = { E1153DCF2BBB634F00424D36 /* SVGKit */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = E1153DCE2BBB634F00424D36 /* XCRemoteSwiftPackageReference "SVGKit" */; package = E1153DCE2BBB634F00424D36 /* XCRemoteSwiftPackageReference "SVGKit" */;
@ -6578,6 +6592,11 @@
package = E1153DCE2BBB634F00424D36 /* XCRemoteSwiftPackageReference "SVGKit" */; package = E1153DCE2BBB634F00424D36 /* XCRemoteSwiftPackageReference "SVGKit" */;
productName = SVGKit; productName = SVGKit;
}; };
E1155ACA2D0584A90021557A /* IdentifiedCollections */ = {
isa = XCSwiftPackageProductDependency;
package = E176EBDC2D050067009F4CF1 /* XCRemoteSwiftPackageReference "swift-identified-collections" */;
productName = IdentifiedCollections;
};
E12186DD2718F1C50010884C /* Defaults */ = { E12186DD2718F1C50010884C /* Defaults */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */; package = E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */;
@ -6587,16 +6606,6 @@
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = CollectionVGrid; productName = CollectionVGrid;
}; };
E132D3CC2BD2179C0058A2DF /* CollectionVGrid */ = {
isa = XCSwiftPackageProductDependency;
package = E132D3CB2BD2179C0058A2DF /* XCRemoteSwiftPackageReference "CollectionVGrid" */;
productName = CollectionVGrid;
};
E132D3CE2BD217AA0058A2DF /* CollectionVGrid */ = {
isa = XCSwiftPackageProductDependency;
package = E132D3CB2BD2179C0058A2DF /* XCRemoteSwiftPackageReference "CollectionVGrid" */;
productName = CollectionVGrid;
};
E1388A45293F0ABA009721B1 /* SwizzleSwift */ = { E1388A45293F0ABA009721B1 /* SwizzleSwift */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 62666E3727E502CE00EC0ECD /* XCRemoteSwiftPackageReference "SwizzleSwift" */; package = 62666E3727E502CE00EC0ECD /* XCRemoteSwiftPackageReference "SwizzleSwift" */;
@ -6709,6 +6718,24 @@
package = 5335256F265EA0A0006CCA86 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; package = 5335256F265EA0A0006CCA86 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
productName = SwiftUIIntrospect; productName = SwiftUIIntrospect;
}; };
E176EBDD2D050067009F4CF1 /* IdentifiedCollections */ = {
isa = XCSwiftPackageProductDependency;
package = E176EBDC2D050067009F4CF1 /* XCRemoteSwiftPackageReference "swift-identified-collections" */;
productName = IdentifiedCollections;
};
E176EBDF2D0502A6009F4CF1 /* CollectionHStack */ = {
isa = XCSwiftPackageProductDependency;
package = E176EBDC2D050067009F4CF1 /* XCRemoteSwiftPackageReference "swift-identified-collections" */;
productName = CollectionHStack;
};
E176EBE22D0502C6009F4CF1 /* CollectionHStack */ = {
isa = XCSwiftPackageProductDependency;
productName = CollectionHStack;
};
E176EBE82D050925009F4CF1 /* CollectionVGrid */ = {
isa = XCSwiftPackageProductDependency;
productName = CollectionVGrid;
};
E18443CA2A037773002DDDC8 /* UDPBroadcast */ = { E18443CA2A037773002DDDC8 /* UDPBroadcast */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = E1FAD1C42A0375BA007F5521 /* XCRemoteSwiftPackageReference "UDPBroadcastConnection" */; package = E1FAD1C42A0375BA007F5521 /* XCRemoteSwiftPackageReference "UDPBroadcastConnection" */;
@ -6758,6 +6785,26 @@
package = E19E6E0828A0BEFF005C10C8 /* XCRemoteSwiftPackageReference "BlurHashKit" */; package = E19E6E0828A0BEFF005C10C8 /* XCRemoteSwiftPackageReference "BlurHashKit" */;
productName = BlurHashKit; productName = BlurHashKit;
}; };
E1A09F712D05933D00835265 /* CollectionVGrid */ = {
isa = XCSwiftPackageProductDependency;
package = E1A09F702D05933D00835265 /* XCRemoteSwiftPackageReference "CollectionVGrid" */;
productName = CollectionVGrid;
};
E1A09F742D05935100835265 /* CollectionHStack */ = {
isa = XCSwiftPackageProductDependency;
package = E1A09F732D05935100835265 /* XCRemoteSwiftPackageReference "CollectionHStack" */;
productName = CollectionHStack;
};
E1A09F762D05935A00835265 /* CollectionVGrid */ = {
isa = XCSwiftPackageProductDependency;
package = E1A09F702D05933D00835265 /* XCRemoteSwiftPackageReference "CollectionVGrid" */;
productName = CollectionVGrid;
};
E1A09F782D05935A00835265 /* CollectionHStack */ = {
isa = XCSwiftPackageProductDependency;
package = E1A09F732D05935100835265 /* XCRemoteSwiftPackageReference "CollectionHStack" */;
productName = CollectionHStack;
};
E1A7B1642B9A9F7800152546 /* PreferencesView */ = { E1A7B1642B9A9F7800152546 /* PreferencesView */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = E15D4F032B1B0C3C00442DB8 /* XCLocalSwiftPackageReference "PreferencesView" */; package = E15D4F032B1B0C3C00442DB8 /* XCLocalSwiftPackageReference "PreferencesView" */;

View File

@ -1,5 +1,5 @@
{ {
"originHash" : "651194fc1966b57201a0de2cba27dc40798bbdf515febdc83f00d634d916fea4", "originHash" : "b7189175c8066640649da818750e83deee8ef2f766db25c34025f23d451b301d",
"pins" : [ "pins" : [
{ {
"identity" : "blurhashkit", "identity" : "blurhashkit",
@ -25,7 +25,7 @@
"location" : "https://github.com/LePips/CollectionHStack", "location" : "https://github.com/LePips/CollectionHStack",
"state" : { "state" : {
"branch" : "main", "branch" : "main",
"revision" : "894b595185bbfce007d60b219ee3e4013884131c" "revision" : "00beb78cc570ee1014a92eb1cd7a60c099bce5ec"
} }
}, },
{ {
@ -34,7 +34,7 @@
"location" : "https://github.com/LePips/CollectionVGrid", "location" : "https://github.com/LePips/CollectionVGrid",
"state" : { "state" : {
"branch" : "main", "branch" : "main",
"revision" : "91ba930a502761924204ae74a59ded05f3b7ef89" "revision" : "4b1591321339481756af6157c9205051d7fe3040"
} }
}, },
{ {
@ -181,6 +181,15 @@
"version" : "1.0.5" "version" : "1.0.5"
} }
}, },
{
"identity" : "swift-identified-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-identified-collections",
"state" : {
"revision" : "2f5ab6e091dd032b63dacbda052405756010dc3b",
"version" : "1.1.0"
}
},
{ {
"identity" : "swift-log", "identity" : "swift-log",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",

View File

@ -10,23 +10,23 @@ import CollectionHStack
import OrderedCollections import OrderedCollections
import SwiftUI import SwiftUI
struct PosterHStack<Item: Poster>: View { struct PosterHStack<Element: Poster & Identifiable, Data: Collection>: View where Data.Element == Element, Data.Index == Int {
private var data: Data
private var header: () -> any View private var header: () -> any View
private var title: String? private var title: String?
private var type: PosterDisplayType private var type: PosterDisplayType
private var items: Binding<OrderedSet<Item>> private var content: (Element) -> any View
private var content: (Item) -> any View private var imageOverlay: (Element) -> any View
private var imageOverlay: (Item) -> any View private var contextMenu: (Element) -> any View
private var contextMenu: (Item) -> any View
private var trailingContent: () -> any View private var trailingContent: () -> any View
private var onSelect: (Item) -> Void private var onSelect: (Element) -> Void
@ViewBuilder @ViewBuilder
private var padHStack: some View { private var padHStack: some View {
CollectionHStack( CollectionHStack(
items, uniqueElements: data,
minWidth: type == .portrait ? 140 : 220 columns: type == .portrait ? 140 : 220
) { item in ) { item in
PosterButton( PosterButton(
item: item, item: item,
@ -47,7 +47,7 @@ struct PosterHStack<Item: Poster>: View {
@ViewBuilder @ViewBuilder
private var phoneHStack: some View { private var phoneHStack: some View {
CollectionHStack( CollectionHStack(
items, uniqueElements: data,
columns: type == .portrait ? 3 : 2 columns: type == .portrait ? 3 : 2
) { item in ) { item in
PosterButton( PosterButton(
@ -94,13 +94,13 @@ extension PosterHStack {
init( init(
title: String? = nil, title: String? = nil,
type: PosterDisplayType, type: PosterDisplayType,
items: Binding<OrderedSet<Item>> items: Data
) { ) {
self.init( self.init(
data: items,
header: { DefaultHeader(title: title) }, header: { DefaultHeader(title: title) },
title: title, title: title,
type: type, type: type,
items: items,
content: { PosterButton.TitleSubtitleContentView(item: $0) }, content: { PosterButton.TitleSubtitleContentView(item: $0) },
imageOverlay: { PosterButton.DefaultOverlay(item: $0) }, imageOverlay: { PosterButton.DefaultOverlay(item: $0) },
contextMenu: { _ in EmptyView() }, contextMenu: { _ in EmptyView() },
@ -109,31 +109,19 @@ extension PosterHStack {
) )
} }
init<S: Sequence<Item>>(
title: String? = nil,
type: PosterDisplayType,
items: S
) {
self.init(
title: title,
type: type,
items: .constant(OrderedSet(items))
)
}
func header(@ViewBuilder _ header: @escaping () -> any View) -> Self { func header(@ViewBuilder _ header: @escaping () -> any View) -> Self {
copy(modifying: \.header, with: header) copy(modifying: \.header, with: header)
} }
func content(@ViewBuilder _ content: @escaping (Item) -> any View) -> Self { func content(@ViewBuilder _ content: @escaping (Element) -> any View) -> Self {
copy(modifying: \.content, with: content) copy(modifying: \.content, with: content)
} }
func imageOverlay(@ViewBuilder _ content: @escaping (Item) -> any View) -> Self { func imageOverlay(@ViewBuilder _ content: @escaping (Element) -> any View) -> Self {
copy(modifying: \.imageOverlay, with: content) copy(modifying: \.imageOverlay, with: content)
} }
func contextMenu(@ViewBuilder _ content: @escaping (Item) -> any View) -> Self { func contextMenu(@ViewBuilder _ content: @escaping (Element) -> any View) -> Self {
copy(modifying: \.contextMenu, with: content) copy(modifying: \.contextMenu, with: content)
} }
@ -141,7 +129,7 @@ extension PosterHStack {
copy(modifying: \.trailingContent, with: content) copy(modifying: \.trailingContent, with: content)
} }
func onSelect(_ action: @escaping (Item) -> Void) -> Self { func onSelect(_ action: @escaping (Element) -> Void) -> Self {
copy(modifying: \.onSelect, with: action) copy(modifying: \.onSelect, with: action)
} }
} }

View File

@ -32,7 +32,8 @@ struct ActiveSessionsView: View {
L10n.noResults.text L10n.noResults.text
} else { } else {
CollectionVGrid( CollectionVGrid(
viewModel.sessions.keys, uniqueElements: viewModel.sessions.keys,
id: \.self,
layout: .columns(1, insets: .zero, itemSpacing: 0, lineSpacing: 0) layout: .columns(1, insets: .zero, itemSpacing: 0, lineSpacing: 0)
) { id in ) { id in
ActiveSessionRow(box: viewModel.sessions[id]!) { ActiveSessionRow(box: viewModel.sessions[id]!) {

View File

@ -98,8 +98,8 @@ struct ChannelLibraryView: View {
@ViewBuilder @ViewBuilder
private var contentView: some View { private var contentView: some View {
CollectionVGrid( CollectionVGrid(
$viewModel.elements, uniqueElements: viewModel.elements,
layout: $layout layout: layout
) { channel in ) { channel in
switch channelDisplayType { switch channelDisplayType {
case .grid: case .grid:

View File

@ -34,7 +34,7 @@ extension HomeView {
var body: some View { var body: some View {
CollectionHStack( CollectionHStack(
$viewModel.resumeItems, uniqueElements: viewModel.resumeItems,
columns: columnCount columns: columnCount
) { item in ) { item in
PosterButton(item: item, type: .landscape) PosterButton(item: item, type: .landscape)

View File

@ -30,7 +30,7 @@ extension HomeView {
PosterHStack( PosterHStack(
title: L10n.latestWithString(viewModel.parent?.displayTitle ?? .emptyDash), title: L10n.latestWithString(viewModel.parent?.displayTitle ?? .emptyDash),
type: latestInLibraryPosterType, type: latestInLibraryPosterType,
items: $viewModel.elements items: viewModel.elements
) )
.trailing { .trailing {
SeeAllButton() SeeAllButton()

View File

@ -31,7 +31,7 @@ extension HomeView {
PosterHStack( PosterHStack(
title: L10n.nextUp, title: L10n.nextUp,
type: nextUpPosterType, type: nextUpPosterType,
items: $viewModel.elements items: viewModel.elements
) )
.content { item in .content { item in
if item.type == .episode { if item.type == .episode {

View File

@ -28,7 +28,7 @@ extension HomeView {
PosterHStack( PosterHStack(
title: L10n.recentlyAdded, title: L10n.recentlyAdded,
type: recentlyAddedPosterType, type: recentlyAddedPosterType,
items: $viewModel.elements items: viewModel.elements
) )
.trailing { .trailing {
SeeAllButton() SeeAllButton()

View File

@ -22,12 +22,25 @@ extension ItemView {
struct AboutView: View { struct AboutView: View {
private enum AboutViewItem: Hashable { private enum AboutViewItem: Hashable, Identifiable {
case image case image
case overview case overview
case mediaSource(MediaSourceInfo) case mediaSource(MediaSourceInfo)
case ratings case ratings
var id: String? {
switch self {
case .image:
return "image"
case .overview:
return "overview"
case let .mediaSource(source):
return source.id
case .ratings:
return "ratings"
}
}
} }
@Default(.accentColor) @Default(.accentColor)
@ -121,7 +134,10 @@ extension ItemView {
.accessibility(addTraits: [.isHeader]) .accessibility(addTraits: [.isHeader])
.edgePadding(.horizontal) .edgePadding(.horizontal)
CollectionHStack($items, variadicWidths: true) { item in CollectionHStack(
uniqueElements: items,
variadicWidths: true
) { item in
switch item { switch item {
case .image: case .image:
imageView imageView

View File

@ -24,13 +24,13 @@ extension SeriesEpisodeSelector {
private var didScrollToPlayButtonItem = false private var didScrollToPlayButtonItem = false
@StateObject @StateObject
private var proxy = CollectionHStackProxy<BaseItemDto>() private var proxy = CollectionHStackProxy()
let playButtonItem: BaseItemDto? let playButtonItem: BaseItemDto?
private func contentView(viewModel: SeasonItemViewModel) -> some View { private func contentView(viewModel: SeasonItemViewModel) -> some View {
CollectionHStack( CollectionHStack(
$viewModel.elements, uniqueElements: viewModel.elements,
columns: UIDevice.isPhone ? 1.5 : 3.5 columns: UIDevice.isPhone ? 1.5 : 3.5
) { episode in ) { episode in
SeriesEpisodeSelector.EpisodeCard(episode: episode) SeriesEpisodeSelector.EpisodeCard(episode: episode)
@ -71,7 +71,7 @@ extension SeriesEpisodeSelector {
var body: some View { var body: some View {
CollectionHStack( CollectionHStack(
0 ..< 1, count: 1,
columns: UIDevice.isPhone ? 1.5 : 3.5 columns: UIDevice.isPhone ? 1.5 : 3.5
) { _ in ) { _ in
SeriesEpisodeSelector.EmptyCard() SeriesEpisodeSelector.EmptyCard()
@ -92,7 +92,7 @@ extension SeriesEpisodeSelector {
var body: some View { var body: some View {
CollectionHStack( CollectionHStack(
0 ..< 1, count: 1,
columns: UIDevice.isPhone ? 1.5 : 3.5 columns: UIDevice.isPhone ? 1.5 : 3.5
) { _ in ) { _ in
SeriesEpisodeSelector.ErrorCard(error: error) SeriesEpisodeSelector.ErrorCard(error: error)
@ -110,7 +110,7 @@ extension SeriesEpisodeSelector {
var body: some View { var body: some View {
CollectionHStack( CollectionHStack(
0 ..< Int.random(in: 2 ..< 5), count: Int.random(in: 2 ..< 5),
columns: UIDevice.isPhone ? 1.5 : 3.5 columns: UIDevice.isPhone ? 1.5 : 3.5
) { _ in ) { _ in
SeriesEpisodeSelector.LoadingCard() SeriesEpisodeSelector.LoadingCard()

View File

@ -32,7 +32,7 @@ extension ItemView {
PosterHStack( PosterHStack(
title: L10n.recommended, title: L10n.recommended,
type: similarPosterType, type: similarPosterType,
items: $viewModel.elements items: viewModel.elements
) )
.trailing { .trailing {
SeeAllButton() SeeAllButton()

View File

@ -36,7 +36,7 @@ struct MediaView: View {
@ViewBuilder @ViewBuilder
private var contentView: some View { private var contentView: some View {
CollectionVGrid( CollectionVGrid(
$viewModel.mediaItems, uniqueElements: viewModel.mediaItems,
layout: UIDevice.isPhone ? phoneLayout : padLayout layout: UIDevice.isPhone ? phoneLayout : padLayout
) { mediaType in ) { mediaType in
MediaItem(viewModel: viewModel, type: mediaType) MediaItem(viewModel: viewModel, type: mediaType)

View File

@ -36,7 +36,7 @@ import SwiftUI
should be applied. should be applied.
*/ */
struct PagingLibraryView<Element: Poster>: View { struct PagingLibraryView<Element: Poster & Identifiable>: View {
@Default(.Customization.Library.enabledDrawerFilters) @Default(.Customization.Library.enabledDrawerFilters)
private var enabledDrawerFilters private var enabledDrawerFilters
@ -71,7 +71,7 @@ struct PagingLibraryView<Element: Poster>: View {
private var posterType: PosterDisplayType private var posterType: PosterDisplayType
@StateObject @StateObject
private var collectionVGridProxy: CollectionVGridProxy<Element> = .init() private var collectionVGridProxy: CollectionVGridProxy = .init()
@StateObject @StateObject
private var viewModel: PagingLibraryViewModel<Element> private var viewModel: PagingLibraryViewModel<Element>
@ -239,8 +239,8 @@ struct PagingLibraryView<Element: Poster>: View {
@ViewBuilder @ViewBuilder
private var gridView: some View { private var gridView: some View {
CollectionVGrid( CollectionVGrid(
$viewModel.elements, uniqueElements: viewModel.elements,
layout: $layout layout: layout
) { item in ) { item in
let displayType = Defaults[.Customization.Library.rememberLayout] ? _displayType.wrappedValue : _defaultDisplayType let displayType = Defaults[.Customization.Library.rememberLayout] ? _displayType.wrappedValue : _defaultDisplayType

View File

@ -9,6 +9,7 @@
import CollectionHStack import CollectionHStack
import Defaults import Defaults
import JellyfinAPI import JellyfinAPI
import OrderedCollections
import SwiftUI import SwiftUI
import VLCUI import VLCUI
@ -42,7 +43,7 @@ extension VideoPlayer.Overlay {
private var size: CGSize = .zero private var size: CGSize = .zero
@StateObject @StateObject
private var collectionHStackProxy: CollectionHStackProxy<ChapterInfo.FullInfo> = .init() private var collectionHStackProxy: CollectionHStackProxy = .init()
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
@ -61,7 +62,7 @@ extension VideoPlayer.Overlay {
Button { Button {
if let currentChapter = viewModel.chapter(from: currentProgressHandler.seconds) { if let currentChapter = viewModel.chapter(from: currentProgressHandler.seconds) {
collectionHStackProxy.scrollTo(element: currentChapter, animated: true) collectionHStackProxy.scrollTo(element: currentChapter)
} }
} label: { } label: {
Text(L10n.current) Text(L10n.current)
@ -73,7 +74,7 @@ extension VideoPlayer.Overlay {
.edgePadding(.horizontal) .edgePadding(.horizontal)
CollectionHStack( CollectionHStack(
viewModel.chapters, uniqueElements: viewModel.chapters,
minWidth: 200 minWidth: 200
) { chapter in ) { chapter in
ChapterButton(chapter: chapter) ChapterButton(chapter: chapter)