From 1cd2dbe5f7234b15605e6652351e0172dd3803b5 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Tue, 13 Dec 2022 11:40:08 +0100 Subject: [PATCH] Add list views for channels, playlists and placeholders --- .../ChannelAvatarView.swift | 0 Shared/{Views => Channels}/ChannelCell.swift | 8 -- Shared/Channels/ChannelListItem.swift | 97 ++++++++++++++++ .../ChannelPlaylistCell.swift | 8 -- Shared/Channels/ChannelPlaylistListItem.swift | 104 ++++++++++++++++++ .../ChannelPlaylistView.swift | 8 +- .../ChannelVideosView.swift | 2 +- .../PlaylistVideosView.swift | 0 Shared/Views/ContentItemView.swift | 87 +++++++++++---- Shared/Views/PlaceholderListItem.swift | 15 +++ Yattee.xcodeproj/project.pbxproj | 46 +++++++- 11 files changed, 332 insertions(+), 43 deletions(-) rename Shared/{Views => Channels}/ChannelAvatarView.swift (100%) rename Shared/{Views => Channels}/ChannelCell.swift (87%) create mode 100644 Shared/Channels/ChannelListItem.swift rename Shared/{Views => Channels}/ChannelPlaylistCell.swift (86%) create mode 100644 Shared/Channels/ChannelPlaylistListItem.swift rename Shared/{Views => Channels}/ChannelPlaylistView.swift (94%) rename Shared/{Views => Channels}/ChannelVideosView.swift (99%) rename Shared/{Views => Playlists}/PlaylistVideosView.swift (100%) create mode 100644 Shared/Views/PlaceholderListItem.swift diff --git a/Shared/Views/ChannelAvatarView.swift b/Shared/Channels/ChannelAvatarView.swift similarity index 100% rename from Shared/Views/ChannelAvatarView.swift rename to Shared/Channels/ChannelAvatarView.swift diff --git a/Shared/Views/ChannelCell.swift b/Shared/Channels/ChannelCell.swift similarity index 87% rename from Shared/Views/ChannelCell.swift rename to Shared/Channels/ChannelCell.swift index 3a48267f..4d611973 100644 --- a/Shared/Views/ChannelCell.swift +++ b/Shared/Channels/ChannelCell.swift @@ -45,14 +45,6 @@ struct ChannelCell: View { var labelContent: some View { VStack { - HStack(alignment: .top, spacing: 3) { - Image(systemName: "person.crop.rectangle") - Text("Channel".localized().uppercased()) - .fontWeight(.light) - .opacity(0.6) - } - .foregroundColor(.secondary) - WebImage(url: channel.thumbnailURL, options: [.lowPriority]) .resizable() .placeholder { diff --git a/Shared/Channels/ChannelListItem.swift b/Shared/Channels/ChannelListItem.swift new file mode 100644 index 00000000..7366dfe5 --- /dev/null +++ b/Shared/Channels/ChannelListItem.swift @@ -0,0 +1,97 @@ +import SwiftUI + +struct ChannelListItem: View { + var channel: Channel + + @Environment(\.inChannelView) private var inChannelView + @Environment(\.inNavigationView) private var inNavigationView + @Environment(\.navigationStyle) private var navigationStyle + + var body: some View { + channelControl + .contentShape(Rectangle()) + } + + @ViewBuilder private var channelControl: some View { + if !channel.name.isEmpty { + #if os(tvOS) + channelButton + #else + if navigationStyle == .tab, inNavigationView { + channelNavigationLink + } else { + channelButton + } + #endif + } + } + + @ViewBuilder private var channelNavigationLink: some View { + NavigationLink(destination: ChannelVideosView(channel: channel)) { + label + } + } + + @ViewBuilder private var channelButton: some View { + Button { + guard !inChannelView else { return } + + NavigationModel.shared.openChannel( + channel, + navigationStyle: navigationStyle + ) + } label: { + label + } + #if os(tvOS) + .buttonStyle(.card) + #else + .buttonStyle(.plain) + #endif + .help("\(channel.name) Channel") + } + + @ViewBuilder private var displayAuthor: some View { + if !channel.name.isEmpty { + Text(channel.name) + .fontWeight(.semibold) + } + } + + private var label: some View { + HStack(alignment: .top, spacing: 12) { + VStack { + ChannelAvatarView(channel: channel) + #if os(tvOS) + .frame(width: 90, height: 90) + #else + .frame(width: 60, height: 60) + #endif + } + .frame(width: thumbnailWidth) + + displayAuthor + .multilineTextAlignment(.leading) + } + .frame(maxWidth: .infinity, alignment: .leading) + #if os(tvOS) + .frame(minHeight: 120) + #else + .frame(minHeight: 60) + #endif + } + + private var thumbnailWidth: Double { + #if os(tvOS) + 250 + #else + 100 + #endif + } +} + +struct ChannelListItem_Previews: PreviewProvider { + static var previews: some View { + ChannelListItem(channel: Video.fixture.channel) + } +} diff --git a/Shared/Views/ChannelPlaylistCell.swift b/Shared/Channels/ChannelPlaylistCell.swift similarity index 86% rename from Shared/Views/ChannelPlaylistCell.swift rename to Shared/Channels/ChannelPlaylistCell.swift index c88f3c17..7b8e5dc2 100644 --- a/Shared/Views/ChannelPlaylistCell.swift +++ b/Shared/Channels/ChannelPlaylistCell.swift @@ -29,14 +29,6 @@ struct ChannelPlaylistCell: View { var content: some View { VStack { - HStack(alignment: .top, spacing: 3) { - Image(systemName: "list.and.film") - Text("Playlist".localized().uppercased()) - .fontWeight(.light) - .opacity(0.6) - } - .foregroundColor(.secondary) - WebImage(url: playlist.thumbnailURL, options: [.lowPriority]) .resizable() .placeholder { diff --git a/Shared/Channels/ChannelPlaylistListItem.swift b/Shared/Channels/ChannelPlaylistListItem.swift new file mode 100644 index 00000000..e1568ecb --- /dev/null +++ b/Shared/Channels/ChannelPlaylistListItem.swift @@ -0,0 +1,104 @@ +import SwiftUI + +struct ChannelPlaylistListItem: View { + var playlist: ChannelPlaylist + + @Environment(\.inNavigationView) private var inNavigationView + @Environment(\.navigationStyle) private var navigationStyle + + var body: some View { + playlistControl + .contentShape(Rectangle()) + } + + var thumbnailView: some View { + ThumbnailView(url: playlist.thumbnailURL) + #if os(tvOS) + .frame(width: 250, height: 140) + #else + .frame(width: 100, height: 60) + #endif + .clipShape(RoundedRectangle(cornerRadius: 2)) + } + + @ViewBuilder private var playlistControl: some View { + #if os(tvOS) + playlistButton + #else + if navigationStyle == .tab, inNavigationView { + playlistNavigationLink + } else { + playlistButton + } + #endif + } + + @ViewBuilder private var playlistNavigationLink: some View { + NavigationLink(destination: ChannelPlaylistView(playlist: playlist)) { + label + } + } + + @ViewBuilder private var playlistButton: some View { + Button { + NavigationModel.shared.openChannelPlaylist( + playlist, + navigationStyle: navigationStyle + ) + } label: { + label + } + #if os(tvOS) + .buttonStyle(.card) + #else + .buttonStyle(.plain) + #endif + .help("\(playlist.title) playlist") + } + + @ViewBuilder private var displayTitle: some View { + Text(playlist.title) + .fontWeight(.semibold) + } + + private var label: some View { + HStack(alignment: .top, spacing: 12) { + VStack { + thumbnailView + } + .frame(width: thumbnailWidth) + #if os(tvOS) + .frame(minHeight: 100) + #else + .frame(minHeight: 60) + #endif + + VStack(alignment: .leading) { + displayTitle + Text("\(playlist.videosCount ?? playlist.videos.count) videos") + .font(.caption) + .foregroundColor(.secondary) + } + .frame(maxWidth: .infinity, alignment: .leading) + .multilineTextAlignment(.leading) + } + #if os(tvOS) + .padding(.vertical) + #endif + .frame(maxWidth: .infinity, alignment: .leading) + } + + private var thumbnailWidth: Double { + #if os(tvOS) + 250 + #else + 100 + #endif + } +} + +struct ChannelPlaylistListItem_Previews: PreviewProvider { + static var previews: some View { + ChannelPlaylistListItem(playlist: ChannelPlaylist.fixture) + } +} diff --git a/Shared/Views/ChannelPlaylistView.swift b/Shared/Channels/ChannelPlaylistView.swift similarity index 94% rename from Shared/Views/ChannelPlaylistView.swift rename to Shared/Channels/ChannelPlaylistView.swift index b800a412..7a914158 100644 --- a/Shared/Views/ChannelPlaylistView.swift +++ b/Shared/Channels/ChannelPlaylistView.swift @@ -43,9 +43,14 @@ struct ChannelPlaylistView: View { #if os(tvOS) HStack { if let playlist = presentedPlaylist { + ThumbnailView(url: store.item?.thumbnailURL ?? playlist.thumbnailURL) + .frame(width: 140, height: 80) + .clipShape(RoundedRectangle(cornerRadius: 2)) + Text(playlist.title) - .font(.title2) + .font(.headline) .frame(alignment: .leading) + .lineLimit(1) Spacer() @@ -129,6 +134,7 @@ struct ChannelPlaylistView: View { ThumbnailView(url: store.item?.thumbnailURL ?? playlist?.thumbnailURL) .frame(width: 60, height: 30) .clipShape(RoundedRectangle(cornerRadius: 2)) + Text(label) .font(.headline) .foregroundColor(.primary) diff --git a/Shared/Views/ChannelVideosView.swift b/Shared/Channels/ChannelVideosView.swift similarity index 99% rename from Shared/Views/ChannelVideosView.swift rename to Shared/Channels/ChannelVideosView.swift index 94c75319..6020f5dd 100644 --- a/Shared/Views/ChannelVideosView.swift +++ b/Shared/Channels/ChannelVideosView.swift @@ -50,7 +50,7 @@ struct ChannelVideosView: View { thumbnail Text(navigationTitle) - .font(.title2) + .font(.headline) .frame(alignment: .leading) Spacer() diff --git a/Shared/Views/PlaylistVideosView.swift b/Shared/Playlists/PlaylistVideosView.swift similarity index 100% rename from Shared/Views/PlaylistVideosView.swift rename to Shared/Playlists/PlaylistVideosView.swift diff --git a/Shared/Views/ContentItemView.swift b/Shared/Views/ContentItemView.swift index 79f70e45..f93faa6c 100644 --- a/Shared/Views/ContentItemView.swift +++ b/Shared/Views/ContentItemView.swift @@ -9,28 +9,77 @@ struct ContentItemView: View { Group { switch item.contentType { case .video: - if listingStyle == .cells { - VideoCell(video: item.video) - } else { - PlayerQueueRow(item: .init(item.video)) - .contextMenu { - VideoContextMenuView(video: item.video) - } - #if os(tvOS) - .padding(.horizontal, 30) - #endif - - #if !os(tvOS) - Divider() - #endif - } - case .playlist: - ChannelPlaylistCell(playlist: item.playlist) + videoItem(item.video) case .channel: - ChannelCell(channel: item.channel) + channelItem(item.channel) + case .playlist: + playlistItem(item.playlist) default: - PlaceholderCell() + placeholderItem() } } } + + @ViewBuilder func videoItem(_ video: Video) -> some View { + if listingStyle == .cells { + VideoCell(video: video) + } else { + PlayerQueueRow(item: .init(video)) + .contextMenu { + VideoContextMenuView(video: video) + } + #if os(tvOS) + .padding(.horizontal, 30) + #endif + + #if !os(tvOS) + Divider() + #endif + } + } + + @ViewBuilder func playlistItem(_ playlist: ChannelPlaylist) -> some View { + if listingStyle == .cells { + ChannelPlaylistCell(playlist: playlist) + } else { + ChannelPlaylistListItem(playlist: playlist) + #if os(tvOS) + .padding(.horizontal, 30) + #endif + + #if !os(tvOS) + Divider() + #endif + } + } + + @ViewBuilder func channelItem(_ channel: Channel) -> some View { + if listingStyle == .cells { + ChannelCell(channel: channel) + } else { + ChannelListItem(channel: channel) + #if os(tvOS) + .padding(.horizontal, 30) + #endif + + #if !os(tvOS) + Divider() + #endif + } + } + + @ViewBuilder func placeholderItem() -> some View { + if listingStyle == .cells { + PlaceholderCell() + } else { + PlaceholderListItem() + #if os(tvOS) + .padding(.horizontal, 30) + #endif + + #if !os(tvOS) + Divider() + #endif + } + } } diff --git a/Shared/Views/PlaceholderListItem.swift b/Shared/Views/PlaceholderListItem.swift new file mode 100644 index 00000000..961380a6 --- /dev/null +++ b/Shared/Views/PlaceholderListItem.swift @@ -0,0 +1,15 @@ +import Defaults +import SwiftUI + +struct PlaceholderListItem: View { + var body: some View { + VideoBanner(id: UUID().uuidString, video: .fixture) + .redacted(reason: .placeholder) + } +} + +struct PlaceholderListItem_Previews: PreviewProvider { + static var previews: some View { + PlaceholderListItem() + } +} diff --git a/Yattee.xcodeproj/project.pbxproj b/Yattee.xcodeproj/project.pbxproj index c9c934dd..a26baf49 100644 --- a/Yattee.xcodeproj/project.pbxproj +++ b/Yattee.xcodeproj/project.pbxproj @@ -142,6 +142,9 @@ 370F4FFF27CC1756001B35DC /* libxcb.1.1.0.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 370F4FB027CC16CA001B35DC /* libxcb.1.1.0.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 370F500027CC1756001B35DC /* libXdmcp.6.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 370F4FC727CC16CB001B35DC /* libXdmcp.6.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 370F500C27CC1821001B35DC /* MPVViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C2211C27ADA33300305B41 /* MPVViewController.swift */; }; + 3710A55529488C7D006F8025 /* PlaceholderListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3710A55429488C7D006F8025 /* PlaceholderListItem.swift */; }; + 3710A55629488C7D006F8025 /* PlaceholderListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3710A55429488C7D006F8025 /* PlaceholderListItem.swift */; }; + 3710A55729488C7D006F8025 /* PlaceholderListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3710A55429488C7D006F8025 /* PlaceholderListItem.swift */; }; 3711403F26B206A6005B3555 /* SearchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3711403E26B206A6005B3555 /* SearchModel.swift */; }; 3711404026B206A6005B3555 /* SearchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3711403E26B206A6005B3555 /* SearchModel.swift */; }; 3711404126B206A6005B3555 /* SearchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3711403E26B206A6005B3555 /* SearchModel.swift */; }; @@ -740,6 +743,13 @@ 37BD07C82698B71C003EBB87 /* AppTabNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C32671614700C925CA /* AppTabNavigation.swift */; }; 37BD07C92698FBDB003EBB87 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BD07B42698AA4D003EBB87 /* ContentView.swift */; }; 37BD672426F13D65004BE0C1 /* AppSidebarPlaylists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA794A26DC30EC002A0235 /* AppSidebarPlaylists.swift */; }; + 37BDFF1929487B99000C6404 /* PlaylistVideosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA793A26DB8EE4002A0235 /* PlaylistVideosView.swift */; }; + 37BDFF1B29487C5A000C6404 /* ChannelListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BDFF1A29487C5A000C6404 /* ChannelListItem.swift */; }; + 37BDFF1C29487C5A000C6404 /* ChannelListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BDFF1A29487C5A000C6404 /* ChannelListItem.swift */; }; + 37BDFF1D29487C5A000C6404 /* ChannelListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BDFF1A29487C5A000C6404 /* ChannelListItem.swift */; }; + 37BDFF1F29488117000C6404 /* ChannelPlaylistListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BDFF1E29488117000C6404 /* ChannelPlaylistListItem.swift */; }; + 37BDFF2029488117000C6404 /* ChannelPlaylistListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BDFF1E29488117000C6404 /* ChannelPlaylistListItem.swift */; }; + 37BDFF2129488117000C6404 /* ChannelPlaylistListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BDFF1E29488117000C6404 /* ChannelPlaylistListItem.swift */; }; 37BE0BCF26A0E2D50092E2DB /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */; }; 37BE0BD026A0E2D50092E2DB /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */; }; 37BE0BD126A0E2D50092E2DB /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */; }; @@ -1119,6 +1129,7 @@ 370F4FC727CC16CB001B35DC /* libXdmcp.6.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libXdmcp.6.dylib; sourceTree = ""; }; 370F4FC827CC16CB001B35DC /* libbrotlicommon.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libbrotlicommon.1.dylib; sourceTree = ""; }; 370F500A27CC176F001B35DC /* BridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BridgingHeader.h; sourceTree = ""; }; + 3710A55429488C7D006F8025 /* PlaceholderListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderListItem.swift; sourceTree = ""; }; 3711403E26B206A6005B3555 /* SearchModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchModel.swift; sourceTree = ""; }; 3712643B2865FF4500D77974 /* Shared Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Shared Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 37130A5A277657090033018A /* Yattee.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Yattee.xcdatamodel; sourceTree = ""; }; @@ -1343,6 +1354,8 @@ 37BD07B42698AA4D003EBB87 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 37BD07BA2698AB60003EBB87 /* AppSidebarNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSidebarNavigation.swift; sourceTree = ""; }; 37BD07C42698ADEE003EBB87 /* Yattee.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Yattee.entitlements; sourceTree = ""; }; + 37BDFF1A29487C5A000C6404 /* ChannelListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListItem.swift; sourceTree = ""; }; + 37BDFF1E29488117000C6404 /* ChannelPlaylistListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelPlaylistListItem.swift; sourceTree = ""; }; 37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = ""; }; 37BE0BD226A1D4780092E2DB /* AppleAVPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleAVPlayerView.swift; sourceTree = ""; }; 37BE0BD526A1D4A90092E2DB /* AppleAVPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleAVPlayerViewController.swift; sourceTree = ""; }; @@ -1771,6 +1784,7 @@ 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */, 373CFAEA26975CBF003CB2C6 /* PlaylistFormView.swift */, 376578902685490700D4EA09 /* PlaylistsView.swift */, + 37BA793A26DB8EE4002A0235 /* PlaylistVideosView.swift */, ); path = Playlists; sourceTree = ""; @@ -1791,11 +1805,6 @@ isa = PBXGroup; children = ( 37E6D79F2944CD3800550C3D /* CacheStatusHeader.swift */, - 3776924D294630110055EC18 /* ChannelAvatarView.swift */, - 3743B86727216D3600261544 /* ChannelCell.swift */, - 37C3A24827235FAA0087A57A /* ChannelPlaylistCell.swift */, - 37C3A250272366440087A57A /* ChannelPlaylistView.swift */, - 37BA793E26DB8F97002A0235 /* ChannelVideosView.swift */, 37FB285D272225E800A57617 /* ContentItemView.swift */, 372CFD14285F2E2A00B0B54B /* ControlsBar.swift */, 3748186D26A769D60084E870 /* DetailBadge.swift */, @@ -1808,7 +1817,6 @@ 3763C988290C7A50004D3B5F /* OpenVideosView.swift */, 37FEF11227EFD8580033912F /* PlaceholderCell.swift */, 3769C02D2779F18600DDB3EA /* PlaceholderProgressView.swift */, - 37BA793A26DB8EE4002A0235 /* PlaylistVideosView.swift */, 37AAF27D26737323007FC770 /* PopularView.swift */, 371CC76F29468BDC00979C1A /* SettingsButtons.swift */, 37F7D82B289EB05F00E2B3D0 /* SettingsPickerModifier.swift */, @@ -1816,6 +1824,7 @@ 376B2E0626F920D600B1D64D /* SignInRequiredView.swift */, 37B17D9F268A1F25006AEE9B /* VideoContextMenuView.swift */, 37E70922271CD43000D34DDE /* WelcomeScreen.swift */, + 3710A55429488C7D006F8025 /* PlaceholderListItem.swift */, ); path = Views; sourceTree = ""; @@ -2123,6 +2132,20 @@ path = Extensions; sourceTree = ""; }; + 37BDFF1829487B74000C6404 /* Channels */ = { + isa = PBXGroup; + children = ( + 3776924D294630110055EC18 /* ChannelAvatarView.swift */, + 3743B86727216D3600261544 /* ChannelCell.swift */, + 37BDFF1A29487C5A000C6404 /* ChannelListItem.swift */, + 37C3A24827235FAA0087A57A /* ChannelPlaylistCell.swift */, + 37BDFF1E29488117000C6404 /* ChannelPlaylistListItem.swift */, + 37C3A250272366440087A57A /* ChannelPlaylistView.swift */, + 37BA793E26DB8F97002A0235 /* ChannelVideosView.swift */, + ); + path = Channels; + sourceTree = ""; + }; 37BE0BD826A214500092E2DB /* macOS */ = { isa = PBXGroup; children = ( @@ -2192,6 +2215,7 @@ 37D4B0C12671614700C925CA /* Shared */ = { isa = PBXGroup; children = ( + 37BDFF1829487B74000C6404 /* Channels */, 37494EA329200AD4000DF176 /* Documents */, 3788AC2126F683AB00F6BAA9 /* Home */, 3761AC0526F0F96100AA496F /* Modifiers */, @@ -3031,6 +3055,7 @@ 37C0698227260B2100F7F6CB /* ThumbnailsModel.swift in Sources */, 37BC50A82778A84700510953 /* HistorySettings.swift in Sources */, 37DD9DB12785D58D00539416 /* RefreshControl.swift in Sources */, + 37BDFF1F29488117000C6404 /* ChannelPlaylistListItem.swift in Sources */, 371CC76C29466F5A00979C1A /* AccountsViewModel.swift in Sources */, 37B4E805277D0AB4004BF56A /* Orientation.swift in Sources */, 37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */, @@ -3177,6 +3202,7 @@ 37599F30272B42810087F250 /* FavoriteItem.swift in Sources */, 374924E729215FB60017D862 /* TapRecognizerViewModifier.swift in Sources */, 373197D92732015300EF734F /* RelatedView.swift in Sources */, + 3710A55529488C7D006F8025 /* PlaceholderListItem.swift in Sources */, 37F4AD1B28612B23004D0F66 /* OpeningStream.swift in Sources */, 373CFAEB26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */, 372915E62687E3B900F5A35B /* Defaults.swift in Sources */, @@ -3209,6 +3235,7 @@ 37C0697A2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */, 379B0253287A1CDF001015B5 /* OrientationTracker.swift in Sources */, 37E80F3C287B107F00561799 /* VideoDetailsOverlay.swift in Sources */, + 37BDFF1B29487C5A000C6404 /* ChannelListItem.swift in Sources */, 370B79C9286279810045DB77 /* NSObject+Swizzle.swift in Sources */, 3776924E294630110055EC18 /* ChannelAvatarView.swift in Sources */, 37D4B0E42671614900C925CA /* YatteeApp.swift in Sources */, @@ -3416,6 +3443,7 @@ 37B2631B2735EAAB00FE0D40 /* FavoriteResourceObserver.swift in Sources */, 3700155C271B0D4D0049C794 /* PipedAPI.swift in Sources */, 376BE50C27349108009AD608 /* BrowsingSettings.swift in Sources */, + 3710A55629488C7D006F8025 /* PlaceholderListItem.swift in Sources */, 37EBD8CB27AF26C200F1C24B /* MPVBackend.swift in Sources */, 37D4B19826717E1500C925CA /* Video.swift in Sources */, 371B7E5D27596B8400D21217 /* Comment.swift in Sources */, @@ -3434,9 +3462,11 @@ 37D4B0E52671614900C925CA /* YatteeApp.swift in Sources */, 37130A60277657300033018A /* PersistenceController.swift in Sources */, 37E8B0F127B326F30024006F /* Comparable+Clamped.swift in Sources */, + 37BDFF1C29487C5A000C6404 /* ChannelListItem.swift in Sources */, 37EBD8C727AF26B300F1C24B /* AVPlayerBackend.swift in Sources */, 37BD07C12698AD3B003EBB87 /* TrendingCountry.swift in Sources */, 37BA794026DB8F97002A0235 /* ChannelVideosView.swift in Sources */, + 37BDFF2029488117000C6404 /* ChannelPlaylistListItem.swift in Sources */, 3711404026B206A6005B3555 /* SearchModel.swift in Sources */, 37484C2A26FC83FF00287258 /* AccountForm.swift in Sources */, 37BE0BD026A0E2D50092E2DB /* VideoPlayerView.swift in Sources */, @@ -3558,6 +3588,7 @@ 3718B9A32921A96A0003DB2E /* VideoDetailsToolbar.swift in Sources */, 37F4AE7426828F0900BD60EA /* VerticalCells.swift in Sources */, 376578872685429C00D4EA09 /* CaseIterable+Next.swift in Sources */, + 37BDFF1D29487C5A000C6404 /* ChannelListItem.swift in Sources */, 37D4B1802671650A00C925CA /* YatteeApp.swift in Sources */, 3748187026A769D60084E870 /* DetailBadge.swift in Sources */, 3741A32C27E7EFFD00D266D1 /* PlayerControls.swift in Sources */, @@ -3572,6 +3603,7 @@ 37F0F4EC286F397E00C06C2E /* SettingsModel.swift in Sources */, 3776925429463C310055EC18 /* PlaylistsCacheModel.swift in Sources */, 37BD07C92698FBDB003EBB87 /* ContentView.swift in Sources */, + 37BDFF2129488117000C6404 /* ChannelPlaylistListItem.swift in Sources */, 37BC50AA2778A84700510953 /* HistorySettings.swift in Sources */, 37F13B64285E43C000B137E4 /* ControlsOverlay.swift in Sources */, 376B2E0926F920D600B1D64D /* SignInRequiredView.swift in Sources */, @@ -3613,6 +3645,7 @@ 37F5E8B8291BE9D0006C15F5 /* URLBookmarkModel.swift in Sources */, 37A5DBCA285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */, 37E80F46287B7AEC00561799 /* PlayerQueueView.swift in Sources */, + 37BDFF1929487B99000C6404 /* PlaylistVideosView.swift in Sources */, 37C0698427260B2100F7F6CB /* ThumbnailsModel.swift in Sources */, 374924DC2921050B0017D862 /* LocationsSettings.swift in Sources */, 37D6025B28C17375009E8D98 /* PlaybackStatsView.swift in Sources */, @@ -3669,6 +3702,7 @@ 37BA795126DC3E0E002A0235 /* Int+Format.swift in Sources */, 3748186C26A764FB0084E870 /* Thumbnail+Fixtures.swift in Sources */, 3744A96228B99ADD005DE0A7 /* PlayerControlsLayout.swift in Sources */, + 3710A55729488C7D006F8025 /* PlaceholderListItem.swift in Sources */, 377A20AB2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */, 3748186826A7627F0084E870 /* Video+Fixtures.swift in Sources */, 377F9F7D294403F20043F856 /* VideosCacheModel.swift in Sources */,