diff --git a/Fixtures/Thumbnail+Fixtures.swift b/Fixtures/Thumbnail+Fixtures.swift index d3916d7c..1de1329b 100644 --- a/Fixtures/Thumbnail+Fixtures.swift +++ b/Fixtures/Thumbnail+Fixtures.swift @@ -10,7 +10,7 @@ extension Thumbnail { } private static var fixturesHost: String { - "https://invidious.home.arekf.net" + "https://invidious.snopyta.org" } private static func fixtureUrl(videoId: String, quality: Thumbnail.Quality) -> URL { diff --git a/Fixtures/Video+Fixtures.swift b/Fixtures/Video+Fixtures.swift index 09c422ea..57868c47 100644 --- a/Fixtures/Video+Fixtures.swift +++ b/Fixtures/Video+Fixtures.swift @@ -7,7 +7,7 @@ extension Video { return Video( videoID: UUID().uuidString, - title: "Relaxing Piano Music that will make you feel amazingly good", + title: "Relaxing Piano Music to feel good", author: "Fancy Videotuber", length: 582, published: "7 years ago", diff --git a/Model/Applications/PipedAPI.swift b/Model/Applications/PipedAPI.swift index cba29ce3..fc4879f3 100644 --- a/Model/Applications/PipedAPI.swift +++ b/Model/Applications/PipedAPI.swift @@ -232,6 +232,8 @@ final class PipedAPI: Service, ObservableObject, VideosAPI { if let channel = extractChannel(from: content) { return ContentItem(channel: channel) } + default: + return nil } return nil diff --git a/Model/Applications/VideosAPI.swift b/Model/Applications/VideosAPI.swift index adfab845..6369baf7 100644 --- a/Model/Applications/VideosAPI.swift +++ b/Model/Applications/VideosAPI.swift @@ -74,6 +74,8 @@ extension VideosAPI { case .playlist: urlComponents.path = "/playlist" queryItems.append(.init(name: "list", value: item.playlist.id)) + default: + return nil } if !time.isNil, time!.seconds.isFinite { diff --git a/Model/ContentItem.swift b/Model/ContentItem.swift index 2e370a02..a113fdca 100644 --- a/Model/ContentItem.swift +++ b/Model/ContentItem.swift @@ -2,7 +2,7 @@ import Foundation struct ContentItem: Identifiable { enum ContentType: String { - case video, playlist, channel + case video, playlist, channel, placeholder private var sortOrder: Int { switch self { @@ -35,6 +35,6 @@ struct ContentItem: Identifiable { } var contentType: ContentType { - video.isNil ? (channel.isNil ? .playlist : .channel) : .video + video.isNil ? (channel.isNil ? (playlist.isNil ? .placeholder : .playlist) : .channel) : .video } } diff --git a/Shared/Search/SearchView.swift b/Shared/Search/SearchView.swift index 10cc63cb..5d6f50d3 100644 --- a/Shared/Search/SearchView.swift +++ b/Shared/Search/SearchView.swift @@ -234,7 +234,7 @@ struct SearchView: View { } .edgesIgnoringSafeArea(.horizontal) #else - VerticalCells(items: items) + VerticalCells(items: items, allowEmpty: state.query.isEmpty) .environment(\.loadMoreContentHandler) { state.loadNextPage() } #endif diff --git a/Shared/Videos/HorizontalCells.swift b/Shared/Videos/HorizontalCells.swift index f2c6efd9..ec3fa9bf 100644 --- a/Shared/Videos/HorizontalCells.swift +++ b/Shared/Videos/HorizontalCells.swift @@ -11,7 +11,7 @@ struct HorizontalCells: View { var body: some View { ScrollView(.horizontal, showsIndicators: false) { LazyHStack(spacing: 20) { - ForEach(items) { item in + ForEach(contentItems) { item in ContentItemView(item: item) .environment(\.horizontalCells, true) .onAppear { loadMoreContentItemsIfNeeded(current: item) } @@ -36,6 +36,14 @@ struct HorizontalCells: View { .edgesIgnoringSafeArea(.horizontal) } + var contentItems: [ContentItem] { + items.isEmpty ? placeholders : items + } + + var placeholders: [ContentItem] { + (0 ..< 9).map { _ in .init() } + } + func loadMoreContentItemsIfNeeded(current item: ContentItem) { let thresholdIndex = items.index(items.endIndex, offsetBy: -5) if items.firstIndex(where: { $0.id == item.id }) == thresholdIndex { diff --git a/Shared/Videos/VerticalCells.swift b/Shared/Videos/VerticalCells.swift index bc7f1cb7..791d4665 100644 --- a/Shared/Videos/VerticalCells.swift +++ b/Shared/Videos/VerticalCells.swift @@ -9,11 +9,12 @@ struct VerticalCells: View { @Environment(\.loadMoreContentHandler) private var loadMoreContentHandler var items = [ContentItem]() + var allowEmpty = false var body: some View { ScrollView(.vertical, showsIndicators: scrollViewShowsIndicators) { LazyVGrid(columns: columns, alignment: .center) { - ForEach(items.sorted { $0 < $1 }) { item in + ForEach(contentItems) { item in ContentItemView(item: item) .onAppear { loadMoreContentItemsIfNeeded(current: item) } } @@ -27,6 +28,14 @@ struct VerticalCells: View { #endif } + var contentItems: [ContentItem] { + items.isEmpty ? (allowEmpty ? items : placeholders) : items.sorted { $0 < $1 } + } + + var placeholders: [ContentItem] { + (0 ..< 9).map { _ in .init() } + } + func loadMoreContentItemsIfNeeded(current item: ContentItem) { let thresholdIndex = items.index(items.endIndex, offsetBy: -5) if items.firstIndex(where: { $0.id == item.id }) == thresholdIndex { diff --git a/Shared/Views/ContentItemView.swift b/Shared/Views/ContentItemView.swift index 94a82d6f..7ba73335 100644 --- a/Shared/Views/ContentItemView.swift +++ b/Shared/Views/ContentItemView.swift @@ -11,8 +11,10 @@ struct ContentItemView: View { ChannelPlaylistCell(playlist: item.playlist) case .channel: ChannelCell(channel: item.channel) - default: + case .video: VideoCell(video: item.video) + default: + PlaceholderCell() } } } diff --git a/Shared/Views/PlaceholderCell.swift b/Shared/Views/PlaceholderCell.swift new file mode 100644 index 00000000..88b6d5bb --- /dev/null +++ b/Shared/Views/PlaceholderCell.swift @@ -0,0 +1,16 @@ +import Defaults +import SwiftUI + +struct PlaceholderCell: View { + var body: some View { + VideoCell(video: .fixture) + .redacted(reason: .placeholder) + } +} + +struct PlaceholderCell_Previews: PreviewProvider { + static var previews: some View { + PlaceholderCell() + .injectFixtureEnvironmentObjects() + } +} diff --git a/Yattee.xcodeproj/project.pbxproj b/Yattee.xcodeproj/project.pbxproj index 0135ef44..43070269 100644 --- a/Yattee.xcodeproj/project.pbxproj +++ b/Yattee.xcodeproj/project.pbxproj @@ -590,6 +590,9 @@ 37FD43E42704847C0073EE42 /* View+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43E22704847C0073EE42 /* View+Fixtures.swift */; }; 37FD43E52704847C0073EE42 /* View+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43E22704847C0073EE42 /* View+Fixtures.swift */; }; 37FD43F02704A9C00073EE42 /* RecentsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C194C626F6A9C8005D3B96 /* RecentsModel.swift */; }; + 37FEF11327EFD8580033912F /* PlaceholderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FEF11227EFD8580033912F /* PlaceholderCell.swift */; }; + 37FEF11427EFD8580033912F /* PlaceholderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FEF11227EFD8580033912F /* PlaceholderCell.swift */; }; + 37FEF11527EFD8580033912F /* PlaceholderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FEF11227EFD8580033912F /* PlaceholderCell.swift */; }; 37FFC440272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; }; 37FFC441272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; }; 37FFC442272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; }; @@ -822,6 +825,7 @@ 37FB285D272225E800A57617 /* ContentItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentItemView.swift; sourceTree = ""; }; 37FD43DB270470B70073EE42 /* InstancesSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesSettings.swift; sourceTree = ""; }; 37FD43E22704847C0073EE42 /* View+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Fixtures.swift"; sourceTree = ""; }; + 37FEF11227EFD8580033912F /* PlaceholderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderCell.swift; sourceTree = ""; }; 37FFC43F272734C3009FFD26 /* Throttle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Throttle.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1005,6 +1009,7 @@ 37B17D9F268A1F25006AEE9B /* VideoContextMenuView.swift */, 37E70922271CD43000D34DDE /* WelcomeScreen.swift */, 3769C02D2779F18600DDB3EA /* PlaceholderProgressView.swift */, + 37FEF11227EFD8580033912F /* PlaceholderCell.swift */, ); path = Views; sourceTree = ""; @@ -2021,6 +2026,7 @@ 37C3A24927235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */, 373CFACB26966264003CB2C6 /* SearchQuery.swift in Sources */, 37141673267A8E10006CA35D /* Country.swift in Sources */, + 37FEF11327EFD8580033912F /* PlaceholderCell.swift in Sources */, 37B2631A2735EAAB00FE0D40 /* FavoriteResourceObserver.swift in Sources */, 3748186E26A769D60084E870 /* DetailBadge.swift in Sources */, 376BE50B27349108009AD608 /* BrowsingSettings.swift in Sources */, @@ -2167,6 +2173,7 @@ 3748186726A7627F0084E870 /* Video+Fixtures.swift in Sources */, 3784B23E2728B85300B09468 /* ShareButton.swift in Sources */, 37BE0BDA26A214630092E2DB /* PlayerViewController.swift in Sources */, + 37FEF11427EFD8580033912F /* PlaceholderCell.swift in Sources */, 37E64DD226D597EB00C71877 /* SubscriptionsModel.swift in Sources */, 374108D1272B11B2006C5CC8 /* PictureInPictureDelegate.swift in Sources */, 37C7A1D6267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */, @@ -2392,6 +2399,7 @@ 374C053727242D9F009BDDBE /* SponsorBlockSettings.swift in Sources */, 37C069802725C8D400F7F6CB /* CMTime+DefaultTimescale.swift in Sources */, 3711404126B206A6005B3555 /* SearchModel.swift in Sources */, + 37FEF11527EFD8580033912F /* PlaceholderCell.swift in Sources */, 37FD43F02704A9C00073EE42 /* RecentsModel.swift in Sources */, 379775952689365600DD52A8 /* Array+Next.swift in Sources */, 3705B180267B4DFB00704544 /* TrendingCountry.swift in Sources */,