diff --git a/Model/InvidiousAPI.swift b/Model/InvidiousAPI.swift index fa6e0b65..9806bc9e 100644 --- a/Model/InvidiousAPI.swift +++ b/Model/InvidiousAPI.swift @@ -87,6 +87,10 @@ final class InvidiousAPI: Service { Channel(json: content.json) } + configureTransformer("/channels/*/latest", requestMethods: [.get]) { (content: Entity) -> [Video] in + content.json.arrayValue.map(Video.init) + } + configureTransformer("/videos/*", requestMethods: [.get]) { (content: Entity) -> Video in Video(content.json) } @@ -120,6 +124,10 @@ final class InvidiousAPI: Service { resource("/channels/\(id)") } + func channelVideos(_ id: String) -> Resource { + resource("/channels/\(id)/latest") + } + func video(_ id: String) -> Resource { resource("/videos/\(id)") } diff --git a/Model/NavigationState.swift b/Model/NavigationState.swift index 68e383c8..378b89b1 100644 --- a/Model/NavigationState.swift +++ b/Model/NavigationState.swift @@ -3,10 +3,10 @@ import SwiftUI final class NavigationState: ObservableObject { enum TabSelection: Hashable { - case subscriptions, popular, trending, playlists, channel(String), playlist(String), search + case watchNow, subscriptions, popular, trending, playlists, channel(String), playlist(String), search } - @Published var tabSelection: TabSelection = .subscriptions + @Published var tabSelection: TabSelection = .watchNow @Published var showingVideoDetails = false @Published var showingVideo = false diff --git a/Model/PlaybackState.swift b/Model/PlaybackState.swift index 5fc85d0f..30cc6b8b 100644 --- a/Model/PlaybackState.swift +++ b/Model/PlaybackState.swift @@ -6,7 +6,7 @@ final class PlaybackState: ObservableObject { @Published var stream: Stream? @Published var time: CMTime? - var aspectRatio: CGFloat? { + var aspectRatio: Double? { let tracks = stream?.videoAsset.tracks(withMediaType: .video) guard tracks != nil else { diff --git a/Pearvidious.xcodeproj/project.pbxproj b/Pearvidious.xcodeproj/project.pbxproj index 675400bf..aed1e09e 100644 --- a/Pearvidious.xcodeproj/project.pbxproj +++ b/Pearvidious.xcodeproj/project.pbxproj @@ -57,6 +57,12 @@ 3748186E26A769D60084E870 /* DetailBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3748186D26A769D60084E870 /* DetailBadge.swift */; }; 3748186F26A769D60084E870 /* DetailBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3748186D26A769D60084E870 /* DetailBadge.swift */; }; 3748187026A769D60084E870 /* DetailBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3748186D26A769D60084E870 /* DetailBadge.swift */; }; + 3761ABFD26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */; }; + 3761ABFE26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */; }; + 3761ABFF26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */; }; + 3761AC0F26F0F9A600AA496F /* UnsubscribeAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761AC0E26F0F9A600AA496F /* UnsubscribeAlertModifier.swift */; }; + 3761AC1026F0F9A600AA496F /* UnsubscribeAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761AC0E26F0F9A600AA496F /* UnsubscribeAlertModifier.swift */; }; + 3761AC1126F0F9A600AA496F /* UnsubscribeAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761AC0E26F0F9A600AA496F /* UnsubscribeAlertModifier.swift */; }; 3763495126DFF59D00B9A393 /* AppSidebarRecentlyOpened.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3763495026DFF59D00B9A393 /* AppSidebarRecentlyOpened.swift */; }; 3763495226DFF59D00B9A393 /* AppSidebarRecentlyOpened.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3763495026DFF59D00B9A393 /* AppSidebarRecentlyOpened.swift */; }; 376578852685429C00D4EA09 /* CaseIterable+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376578842685429C00D4EA09 /* CaseIterable+Next.swift */; }; @@ -84,6 +90,15 @@ 377FC7E5267A084E00A6BBAF /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27F26737550007FC770 /* SearchView.swift */; }; 377FC7ED267A0A0800A6BBAF /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 377FC7EC267A0A0800A6BBAF /* SwiftyJSON */; }; 377FC7F3267A0A0800A6BBAF /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 377FC7F2267A0A0800A6BBAF /* Logging */; }; + 3788AC2326F683DE00F6BAA9 /* WatchNowPlaylistSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2226F683DE00F6BAA9 /* WatchNowPlaylistSection.swift */; }; + 3788AC2426F683DE00F6BAA9 /* WatchNowPlaylistSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2226F683DE00F6BAA9 /* WatchNowPlaylistSection.swift */; }; + 3788AC2526F683DE00F6BAA9 /* WatchNowPlaylistSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2226F683DE00F6BAA9 /* WatchNowPlaylistSection.swift */; }; + 3788AC2726F6840700F6BAA9 /* WatchNowSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* WatchNowSection.swift */; }; + 3788AC2826F6840700F6BAA9 /* WatchNowSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* WatchNowSection.swift */; }; + 3788AC2926F6840700F6BAA9 /* WatchNowSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* WatchNowSection.swift */; }; + 3788AC2B26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2A26F6842D00F6BAA9 /* WatchNowSectionBody.swift */; }; + 3788AC2C26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2A26F6842D00F6BAA9 /* WatchNowSectionBody.swift */; }; + 3788AC2D26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2A26F6842D00F6BAA9 /* WatchNowSectionBody.swift */; }; 3797757D268922D100DD52A8 /* Siesta in Frameworks */ = {isa = PBXBuildFile; productRef = 3797757C268922D100DD52A8 /* Siesta */; }; 37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37977582268922F600DD52A8 /* InvidiousAPI.swift */; }; 37977584268922F600DD52A8 /* InvidiousAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37977582268922F600DD52A8 /* InvidiousAPI.swift */; }; @@ -94,12 +109,12 @@ 379775932689365600DD52A8 /* Array+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379775922689365600DD52A8 /* Array+Next.swift */; }; 379775942689365600DD52A8 /* Array+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379775922689365600DD52A8 /* Array+Next.swift */; }; 379775952689365600DD52A8 /* Array+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379775922689365600DD52A8 /* Array+Next.swift */; }; - 379DDFEE26DEDB0E00EA08E7 /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DDFED26DEDB0E00EA08E7 /* EnvironmentValues.swift */; }; - 379DDFEF26DEDB0E00EA08E7 /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DDFED26DEDB0E00EA08E7 /* EnvironmentValues.swift */; }; - 379DDFF026DEDB0E00EA08E7 /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DDFED26DEDB0E00EA08E7 /* EnvironmentValues.swift */; }; - 379DDFF326DEE2BA00EA08E7 /* UnsubscribeAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DDFF226DEE2BA00EA08E7 /* UnsubscribeAlertModifier.swift */; }; - 379DDFF426DEE2BA00EA08E7 /* UnsubscribeAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DDFF226DEE2BA00EA08E7 /* UnsubscribeAlertModifier.swift */; }; - 379DDFF526DEE2BA00EA08E7 /* UnsubscribeAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DDFF226DEE2BA00EA08E7 /* UnsubscribeAlertModifier.swift */; }; + 37A9965A26D6F8CA006E3224 /* VideosCellsHorizontal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965926D6F8CA006E3224 /* VideosCellsHorizontal.swift */; }; + 37A9965B26D6F8CA006E3224 /* VideosCellsHorizontal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965926D6F8CA006E3224 /* VideosCellsHorizontal.swift */; }; + 37A9965C26D6F8CA006E3224 /* VideosCellsHorizontal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965926D6F8CA006E3224 /* VideosCellsHorizontal.swift */; }; + 37A9965E26D6F9B9006E3224 /* WatchNowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965D26D6F9B9006E3224 /* WatchNowView.swift */; }; + 37A9965F26D6F9B9006E3224 /* WatchNowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965D26D6F9B9006E3224 /* WatchNowView.swift */; }; + 37A9966026D6F9B9006E3224 /* WatchNowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965D26D6F9B9006E3224 /* WatchNowView.swift */; }; 37AAF27E26737323007FC770 /* PopularView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27D26737323007FC770 /* PopularView.swift */; }; 37AAF28026737550007FC770 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27F26737550007FC770 /* SearchView.swift */; }; 37AAF29026740715007FC770 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF28F26740715007FC770 /* Channel.swift */; }; @@ -139,7 +154,6 @@ 37BA794526DBA973002A0235 /* Playlists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA794226DBA973002A0235 /* Playlists.swift */; }; 37BA794726DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA794626DC2E56002A0235 /* AppSidebarSubscriptions.swift */; }; 37BA794826DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA794626DC2E56002A0235 /* AppSidebarSubscriptions.swift */; }; - 37BA794B26DC30EC002A0235 /* AppSidebarPlaylists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA794A26DC30EC002A0235 /* AppSidebarPlaylists.swift */; }; 37BA794C26DC30EC002A0235 /* AppSidebarPlaylists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA794A26DC30EC002A0235 /* AppSidebarPlaylists.swift */; }; 37BA794F26DC3E0E002A0235 /* Int+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA794E26DC3E0E002A0235 /* Int+Format.swift */; }; 37BA795026DC3E0E002A0235 /* Int+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA794E26DC3E0E002A0235 /* Int+Format.swift */; }; @@ -162,6 +176,7 @@ 37BD07C72698B27B003EBB87 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 37BD07C62698B27B003EBB87 /* Introspect */; }; 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 */; }; 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 */; }; @@ -213,9 +228,9 @@ 37F49BA426CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F49BA226CAA59B00304AC0 /* Playlist+Fixtures.swift */; }; 37F49BA526CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F49BA226CAA59B00304AC0 /* Playlist+Fixtures.swift */; }; 37F49BA826CB0FCE00304AC0 /* PlaylistFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEA26975CBF003CB2C6 /* PlaylistFormView.swift */; }; - 37F4AE7226828F0900BD60EA /* VideosCellsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F4AE7126828F0900BD60EA /* VideosCellsView.swift */; }; - 37F4AE7326828F0900BD60EA /* VideosCellsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F4AE7126828F0900BD60EA /* VideosCellsView.swift */; }; - 37F4AE7426828F0900BD60EA /* VideosCellsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F4AE7126828F0900BD60EA /* VideosCellsView.swift */; }; + 37F4AE7226828F0900BD60EA /* VideosCellsVertical.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F4AE7126828F0900BD60EA /* VideosCellsVertical.swift */; }; + 37F4AE7326828F0900BD60EA /* VideosCellsVertical.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F4AE7126828F0900BD60EA /* VideosCellsVertical.swift */; }; + 37F4AE7426828F0900BD60EA /* VideosCellsVertical.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F4AE7126828F0900BD60EA /* VideosCellsVertical.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -268,18 +283,23 @@ 3748186526A7627F0084E870 /* Video+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Video+Fixtures.swift"; sourceTree = ""; }; 3748186926A764FB0084E870 /* Thumbnail+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Thumbnail+Fixtures.swift"; sourceTree = ""; }; 3748186D26A769D60084E870 /* DetailBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailBadge.swift; sourceTree = ""; }; + 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.swift; sourceTree = ""; }; + 3761AC0E26F0F9A600AA496F /* UnsubscribeAlertModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnsubscribeAlertModifier.swift; sourceTree = ""; }; 3763495026DFF59D00B9A393 /* AppSidebarRecentlyOpened.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSidebarRecentlyOpened.swift; sourceTree = ""; }; 376578842685429C00D4EA09 /* CaseIterable+Next.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CaseIterable+Next.swift"; sourceTree = ""; }; 376578882685471400D4EA09 /* Playlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Playlist.swift; sourceTree = ""; }; 376578902685490700D4EA09 /* PlaylistsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistsView.swift; sourceTree = ""; }; 37754C9C26B7500000DBD602 /* VideosView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideosView.swift; sourceTree = ""; }; 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedContentAccessors.swift; sourceTree = ""; }; + 3788AC2226F683DE00F6BAA9 /* WatchNowPlaylistSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchNowPlaylistSection.swift; sourceTree = ""; }; + 3788AC2626F6840700F6BAA9 /* WatchNowSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchNowSection.swift; sourceTree = ""; }; + 3788AC2A26F6842D00F6BAA9 /* WatchNowSectionBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchNowSectionBody.swift; sourceTree = ""; }; 37977582268922F600DD52A8 /* InvidiousAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvidiousAPI.swift; sourceTree = ""; }; 3797758A2689345500DD52A8 /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = ""; }; 379775922689365600DD52A8 /* Array+Next.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Next.swift"; sourceTree = ""; }; 37992DC726CC50BC003D4C27 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 379DDFED26DEDB0E00EA08E7 /* EnvironmentValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.swift; sourceTree = ""; }; - 379DDFF226DEE2BA00EA08E7 /* UnsubscribeAlertModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsubscribeAlertModifier.swift; sourceTree = ""; }; + 37A9965926D6F8CA006E3224 /* VideosCellsHorizontal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideosCellsHorizontal.swift; sourceTree = ""; }; + 37A9965D26D6F9B9006E3224 /* WatchNowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchNowView.swift; sourceTree = ""; }; 37AAF27D26737323007FC770 /* PopularView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopularView.swift; sourceTree = ""; }; 37AAF27F26737550007FC770 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; 37AAF28F26740715007FC770 /* Channel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channel.swift; sourceTree = ""; }; @@ -335,7 +355,7 @@ 37EAD86A267B9C5600D9E01B /* SponsorBlockAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SponsorBlockAPI.swift; sourceTree = ""; }; 37EAD86E267B9ED100D9E01B /* Segment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Segment.swift; sourceTree = ""; }; 37F49BA226CAA59B00304AC0 /* Playlist+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Playlist+Fixtures.swift"; sourceTree = ""; }; - 37F4AE7126828F0900BD60EA /* VideosCellsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideosCellsView.swift; sourceTree = ""; }; + 37F4AE7126828F0900BD60EA /* VideosCellsVertical.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideosCellsVertical.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -458,7 +478,8 @@ isa = PBXGroup; children = ( 3748186D26A769D60084E870 /* DetailBadge.swift */, - 37F4AE7126828F0900BD60EA /* VideosCellsView.swift */, + 37A9965926D6F8CA006E3224 /* VideosCellsHorizontal.swift */, + 37F4AE7126828F0900BD60EA /* VideosCellsVertical.swift */, 37AAF29926740A01007FC770 /* VideosListView.swift */, 37754C9C26B7500000DBD602 /* VideosView.swift */, 37D4B18B26717B3800C925CA /* VideoView.swift */, @@ -499,6 +520,14 @@ path = Fixtures; sourceTree = ""; }; + 3761AC0526F0F96100AA496F /* Modifiers */ = { + isa = PBXGroup; + children = ( + 3761AC0E26F0F9A600AA496F /* UnsubscribeAlertModifier.swift */, + ); + path = Modifiers; + sourceTree = ""; + }; 377FC7D1267A080300A6BBAF /* Frameworks */ = { isa = PBXGroup; children = ( @@ -506,6 +535,17 @@ name = Frameworks; sourceTree = ""; }; + 3788AC2126F683AB00F6BAA9 /* Watch Now */ = { + isa = PBXGroup; + children = ( + 3788AC2226F683DE00F6BAA9 /* WatchNowPlaylistSection.swift */, + 3788AC2626F6840700F6BAA9 /* WatchNowSection.swift */, + 3788AC2A26F6842D00F6BAA9 /* WatchNowSectionBody.swift */, + 37A9965D26D6F9B9006E3224 /* WatchNowView.swift */, + ); + path = "Watch Now"; + sourceTree = ""; + }; 37992DC826CC50CD003D4C27 /* iOS */ = { isa = PBXGroup; children = ( @@ -514,14 +554,6 @@ path = iOS; sourceTree = ""; }; - 379DDFF126DEE2A800EA08E7 /* Modifiers */ = { - isa = PBXGroup; - children = ( - 379DDFF226DEE2BA00EA08E7 /* UnsubscribeAlertModifier.swift */, - ); - path = Modifiers; - sourceTree = ""; - }; 37BA796426DC40CB002A0235 /* Shared Tests */ = { isa = PBXGroup; children = ( @@ -580,15 +612,16 @@ 37D4B0C12671614700C925CA /* Shared */ = { isa = PBXGroup; children = ( + 3761AC0526F0F96100AA496F /* Modifiers */, 371AAE2326CEB9E800901972 /* Navigation */, 371AAE2426CEBA4100901972 /* Player */, 371AAE2626CEBF1600901972 /* Playlists */, 371AAE2526CEBF0B00901972 /* Trending */, 371AAE2726CEBF4700901972 /* Videos */, 371AAE2826CEC7D900901972 /* Views */, - 379DDFF126DEE2A800EA08E7 /* Modifiers */, + 3788AC2126F683AB00F6BAA9 /* Watch Now */, 372915E52687E3B900F5A35B /* Defaults.swift */, - 379DDFED26DEDB0E00EA08E7 /* EnvironmentValues.swift */, + 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */, 37D4B0C22671614700C925CA /* PearvidiousApp.swift */, 37D4B0C42671614800C925CA /* Assets.xcassets */, 37BD07C42698ADEE003EBB87 /* Pearvidious.entitlements */, @@ -969,6 +1002,7 @@ 37BA793B26DB8EE4002A0235 /* PlaylistVideosView.swift in Sources */, 37BD07B52698AA4D003EBB87 /* ContentView.swift in Sources */, 37152EEA26EFEB95004FB96D /* LazyView.swift in Sources */, + 3761ABFD26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */, 37C7A1DA267CACF50010EAD6 /* TrendingCountry.swift in Sources */, 37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */, 37BE0BD626A1D4A90092E2DB /* PlayerViewController.swift in Sources */, @@ -977,8 +1011,9 @@ 3711403F26B206A6005B3555 /* SearchState.swift in Sources */, 37B81AF926D2C9A700675966 /* VideoPlayerSizeModifier.swift in Sources */, 37BE0BD326A1D4780092E2DB /* Player.swift in Sources */, + 37A9965E26D6F9B9006E3224 /* WatchNowView.swift in Sources */, 37CEE4C12677B697005A1EFE /* Stream.swift in Sources */, - 37F4AE7226828F0900BD60EA /* VideosCellsView.swift in Sources */, + 37F4AE7226828F0900BD60EA /* VideosCellsVertical.swift in Sources */, 376578852685429C00D4EA09 /* CaseIterable+Next.swift in Sources */, 3748186626A7627F0084E870 /* Video+Fixtures.swift in Sources */, 37BA794726DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */, @@ -990,26 +1025,30 @@ 37B81B0526D2CEDA00675966 /* PlaybackState.swift in Sources */, 373CFADB269663F1003CB2C6 /* Thumbnail.swift in Sources */, 37C7A1DC267CE9D90010EAD6 /* Profile.swift in Sources */, + 3788AC2B26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */, 373CFAC026966149003CB2C6 /* CoverSectionView.swift in Sources */, - 379DDFF326DEE2BA00EA08E7 /* UnsubscribeAlertModifier.swift in Sources */, 3714166F267A8ACC006CA35D /* TrendingView.swift in Sources */, 373CFAEF2697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */, 377FC7E3267A084A00A6BBAF /* VideoView.swift in Sources */, 37BA794326DBA973002A0235 /* Playlists.swift in Sources */, 37AAF29026740715007FC770 /* Channel.swift in Sources */, 3748186A26A764FB0084E870 /* Thumbnail+Fixtures.swift in Sources */, + 3761AC0F26F0F9A600AA496F /* UnsubscribeAlertModifier.swift in Sources */, + 3788AC2326F683DE00F6BAA9 /* WatchNowPlaylistSection.swift in Sources */, 37B81AFF26D2CA3700675966 /* VideoDetails.swift in Sources */, 377FC7E5267A084E00A6BBAF /* SearchView.swift in Sources */, 376578912685490700D4EA09 /* PlaylistsView.swift in Sources */, 377A20A92693C9A2002842B8 /* TypedContentAccessors.swift in Sources */, + 37BD672426F13D65004BE0C1 /* AppSidebarPlaylists.swift in Sources */, 37B17DA2268A1F8A006AEE9B /* VideoContextMenuView.swift in Sources */, 379775932689365600DD52A8 /* Array+Next.swift in Sources */, 37B81AFC26D2C9C900675966 /* VideoDetailsPaddingModifier.swift in Sources */, 37C7A1D5267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */, 37BA794F26DC3E0E002A0235 /* Int+Format.swift in Sources */, 37F49BA326CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */, - 37BA794B26DC30EC002A0235 /* AppSidebarPlaylists.swift in Sources */, + 37A9965A26D6F8CA006E3224 /* VideosCellsHorizontal.swift in Sources */, 37B767DB2677C3CA0098BAA8 /* PlayerState.swift in Sources */, + 3788AC2726F6840700F6BAA9 /* WatchNowSection.swift in Sources */, 373CFACB26966264003CB2C6 /* SearchQuery.swift in Sources */, 373CFAC226966159003CB2C6 /* CoverSectionRowView.swift in Sources */, 37141673267A8E10006CA35D /* Country.swift in Sources */, @@ -1020,7 +1059,6 @@ 372915E62687E3B900F5A35B /* Defaults.swift in Sources */, 37D4B19726717E1500C925CA /* Video.swift in Sources */, 371F2F1A269B43D300E4A7AB /* NavigationState.swift in Sources */, - 379DDFEE26DEDB0E00EA08E7 /* EnvironmentValues.swift in Sources */, 37BE0BCF26A0E2D50092E2DB /* VideoPlayerView.swift in Sources */, 37BD07BB2698AB60003EBB87 /* AppSidebarNavigation.swift in Sources */, 37D4B0E42671614900C925CA /* PearvidiousApp.swift in Sources */, @@ -1033,19 +1071,23 @@ buildActionMask = 2147483647; files = ( 37BE0BDC26A2367F0092E2DB /* Player.swift in Sources */, + 3788AC2C26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */, 37CEE4BE2677B670005A1EFE /* SingleAssetStream.swift in Sources */, 373CFABF26966149003CB2C6 /* CoverSectionView.swift in Sources */, 37BA794826DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */, 37EAD86C267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */, 37CEE4C22677B697005A1EFE /* Stream.swift in Sources */, 371F2F1B269B43D300E4A7AB /* NavigationState.swift in Sources */, + 3761ABFE26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */, 37BA795026DC3E0E002A0235 /* Int+Format.swift in Sources */, 377FC7DD267A081A00A6BBAF /* PopularView.swift in Sources */, 3705B183267B4E4900704544 /* TrendingCategory.swift in Sources */, 37B81B0026D2CA3700675966 /* VideoDetails.swift in Sources */, 37BD07C32698AD4F003EBB87 /* ContentView.swift in Sources */, 37F49BA426CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */, + 3788AC2426F683DE00F6BAA9 /* WatchNowPlaylistSection.swift in Sources */, 37EAD870267B9ED100D9E01B /* Segment.swift in Sources */, + 3788AC2826F6840700F6BAA9 /* WatchNowSection.swift in Sources */, 37BA793C26DB8EE4002A0235 /* PlaylistVideosView.swift in Sources */, 37141670267A8ACC006CA35D /* TrendingView.swift in Sources */, 37152EEB26EFEB95004FB96D /* LazyView.swift in Sources */, @@ -1064,9 +1106,11 @@ 377A20AA2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */, 37B81B0326D2CAE700675966 /* PlaybackBar.swift in Sources */, 376578862685429C00D4EA09 /* CaseIterable+Next.swift in Sources */, - 37F4AE7326828F0900BD60EA /* VideosCellsView.swift in Sources */, + 37A9965F26D6F9B9006E3224 /* WatchNowView.swift in Sources */, + 37F4AE7326828F0900BD60EA /* VideosCellsVertical.swift in Sources */, 37B81AFD26D2C9C900675966 /* VideoDetailsPaddingModifier.swift in Sources */, - 379DDFEF26DEDB0E00EA08E7 /* EnvironmentValues.swift in Sources */, + 37A9965B26D6F8CA006E3224 /* VideosCellsHorizontal.swift in Sources */, + 3761AC1026F0F9A600AA496F /* UnsubscribeAlertModifier.swift in Sources */, 379775942689365600DD52A8 /* Array+Next.swift in Sources */, 3748186726A7627F0084E870 /* Video+Fixtures.swift in Sources */, 37BE0BDA26A214630092E2DB /* PlayerViewController.swift in Sources */, @@ -1082,7 +1126,6 @@ 37D4B19826717E1500C925CA /* Video.swift in Sources */, 37D4B0E52671614900C925CA /* PearvidiousApp.swift in Sources */, 37BD07C12698AD3B003EBB87 /* TrendingCountry.swift in Sources */, - 379DDFF426DEE2BA00EA08E7 /* UnsubscribeAlertModifier.swift in Sources */, 37BA794026DB8F97002A0235 /* ChannelVideosView.swift in Sources */, 3711404026B206A6005B3555 /* SearchState.swift in Sources */, 37BE0BD026A0E2D50092E2DB /* VideoPlayerView.swift in Sources */, @@ -1119,22 +1162,24 @@ files = ( 37754C9F26B7500000DBD602 /* VideosView.swift in Sources */, 37AAF28026737550007FC770 /* SearchView.swift in Sources */, + 3788AC2D26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */, 37EAD871267B9ED100D9E01B /* Segment.swift in Sources */, 37F49BA526CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */, 37CEE4BF2677B670005A1EFE /* SingleAssetStream.swift in Sources */, 37BE0BD426A1D47D0092E2DB /* Player.swift in Sources */, 37977585268922F600DD52A8 /* InvidiousAPI.swift in Sources */, - 37F4AE7426828F0900BD60EA /* VideosCellsView.swift in Sources */, + 37F4AE7426828F0900BD60EA /* VideosCellsVertical.swift in Sources */, 376578872685429C00D4EA09 /* CaseIterable+Next.swift in Sources */, 37D4B1802671650A00C925CA /* PearvidiousApp.swift in Sources */, 3748187026A769D60084E870 /* DetailBadge.swift in Sources */, 373CFAC926966188003CB2C6 /* SearchOptionsView.swift in Sources */, + 37A9965C26D6F8CA006E3224 /* VideosCellsHorizontal.swift in Sources */, 37BD07C92698FBDB003EBB87 /* ContentView.swift in Sources */, 37B17DA6268A285E006AEE9B /* VideoDetailsView.swift in Sources */, 37141671267A8ACC006CA35D /* TrendingView.swift in Sources */, + 3788AC2926F6840700F6BAA9 /* WatchNowSection.swift in Sources */, 37BA794126DB8F97002A0235 /* ChannelVideosView.swift in Sources */, 37AAF29226740715007FC770 /* Channel.swift in Sources */, - 379DDFF526DEE2BA00EA08E7 /* UnsubscribeAlertModifier.swift in Sources */, 37EAD86D267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */, 37B81B0726D2D6CF00675966 /* PlaybackState.swift in Sources */, 3765788B2685471400D4EA09 /* Playlist.swift in Sources */, @@ -1144,9 +1189,11 @@ 373CFABE26966148003CB2C6 /* CoverSectionView.swift in Sources */, 37B767DD2677C3CA0098BAA8 /* PlayerState.swift in Sources */, 373CFAF12697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */, + 3761AC1126F0F9A600AA496F /* UnsubscribeAlertModifier.swift in Sources */, 37D4B18E26717B3800C925CA /* VideoView.swift in Sources */, 37BE0BD126A0E2D50092E2DB /* VideoPlayerView.swift in Sources */, 37AAF27E26737323007FC770 /* PopularView.swift in Sources */, + 37A9966026D6F9B9006E3224 /* WatchNowView.swift in Sources */, 37AAF29A26740A01007FC770 /* VideosListView.swift in Sources */, 37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */, 376578932685490700D4EA09 /* PlaylistsView.swift in Sources */, @@ -1163,7 +1210,6 @@ 37BA793D26DB8EE4002A0235 /* PlaylistVideosView.swift in Sources */, 3711404126B206A6005B3555 /* SearchState.swift in Sources */, 379775952689365600DD52A8 /* Array+Next.swift in Sources */, - 379DDFF026DEDB0E00EA08E7 /* EnvironmentValues.swift in Sources */, 3705B180267B4DFB00704544 /* TrendingCountry.swift in Sources */, 373CFACD26966264003CB2C6 /* SearchQuery.swift in Sources */, 37141675267A8E10006CA35D /* Country.swift in Sources */, @@ -1172,6 +1218,8 @@ 373CFAC42696616C003CB2C6 /* CoverSectionRowView.swift in Sources */, 37D4B19926717E1500C925CA /* Video.swift in Sources */, 3705B184267B4E4900704544 /* TrendingCategory.swift in Sources */, + 3788AC2526F683DE00F6BAA9 /* WatchNowPlaylistSection.swift in Sources */, + 3761ABFF26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */, 37AAF2A226741C97007FC770 /* SubscriptionsView.swift in Sources */, 372915E82687E3B900F5A35B /* Defaults.swift in Sources */, 37BAB54C269B39FD00E75ED1 /* TVNavigationView.swift in Sources */, diff --git a/Shared/EnvironmentValues.swift b/Shared/EnvironmentValues.swift index 0eaff639..67cd6dd1 100644 --- a/Shared/EnvironmentValues.swift +++ b/Shared/EnvironmentValues.swift @@ -5,9 +5,18 @@ private struct InNavigationViewKey: EnvironmentKey { static let defaultValue = false } +private struct HorizontalCellsKey: EnvironmentKey { + static let defaultValue = false +} + extension EnvironmentValues { var inNavigationView: Bool { get { self[InNavigationViewKey.self] } set { self[InNavigationViewKey.self] = newValue } } + + var horizontalCells: Bool { + get { self[HorizontalCellsKey.self] } + set { self[HorizontalCellsKey.self] = newValue } + } } diff --git a/Shared/Navigation/AppSidebarNavigation.swift b/Shared/Navigation/AppSidebarNavigation.swift index 93632503..e1db00fd 100644 --- a/Shared/Navigation/AppSidebarNavigation.swift +++ b/Shared/Navigation/AppSidebarNavigation.swift @@ -121,6 +121,14 @@ struct AppSidebarNavigation: View { var mainNavigationLinks: some View { Section("Videos") { + NavigationLink(tag: TabSelection.watchNow, selection: selection) { + WatchNowView() + } + label: { + Label("Watch Now", systemImage: "play.circle") + .accessibility(label: Text("Watch Now")) + } + NavigationLink(destination: LazyView(SubscriptionsView()), tag: TabSelection.subscriptions, selection: selection) { Label("Subscriptions", systemImage: "star.circle.fill") .accessibility(label: Text("Subscriptions")) diff --git a/Shared/Navigation/AppTabNavigation.swift b/Shared/Navigation/AppTabNavigation.swift index 67299e43..24cce9df 100644 --- a/Shared/Navigation/AppTabNavigation.swift +++ b/Shared/Navigation/AppTabNavigation.swift @@ -9,6 +9,15 @@ struct AppTabNavigation: View { var body: some View { TabView(selection: $navigationState.tabSelection) { + NavigationView { + WatchNowView() + } + .tabItem { + Label("Watch Now", systemImage: "play.circle") + .accessibility(label: Text("Subscriptions")) + } + .tag(TabSelection.watchNow) + NavigationView { SubscriptionsView() } @@ -18,14 +27,16 @@ struct AppTabNavigation: View { } .tag(TabSelection.subscriptions) - NavigationView { - PopularView() - } - .tabItem { - Label("Popular", systemImage: "chart.bar") - .accessibility(label: Text("Popular")) - } - .tag(TabSelection.popular) +// TODO: reenable with settings +// ============================ +// NavigationView { +// PopularView() +// } +// .tabItem { +// Label("Popular", systemImage: "chart.bar") +// .accessibility(label: Text("Popular")) +// } +// .tag(TabSelection.popular) NavigationView { TrendingView() diff --git a/Shared/Player/PlaybackBar.swift b/Shared/Player/PlaybackBar.swift index 27339651..854c2297 100644 --- a/Shared/Player/PlaybackBar.swift +++ b/Shared/Player/PlaybackBar.swift @@ -64,7 +64,7 @@ struct PlaybackBar: View { var closeButton: some View { Button(action: { dismiss() }) { - Image(systemName: "chevron.down.circle.fill") + Image(systemName: "xmark.circle.fill") } .accessibilityLabel(Text("Close")) .buttonStyle(.borderless) diff --git a/Shared/Player/VideoDetailsPaddingModifier.swift b/Shared/Player/VideoDetailsPaddingModifier.swift index 19f100c7..b826db04 100644 --- a/Shared/Player/VideoDetailsPaddingModifier.swift +++ b/Shared/Player/VideoDetailsPaddingModifier.swift @@ -3,15 +3,15 @@ import SwiftUI struct VideoDetailsPaddingModifier: ViewModifier { let geometry: GeometryProxy - let aspectRatio: CGFloat? - let minimumHeightLeft: CGFloat - let additionalPadding: CGFloat + let aspectRatio: Double? + let minimumHeightLeft: Double + let additionalPadding: Double init( geometry: GeometryProxy, - aspectRatio: CGFloat? = nil, - minimumHeightLeft: CGFloat? = nil, - additionalPadding: CGFloat = 35.00 + aspectRatio: Double? = nil, + minimumHeightLeft: Double? = nil, + additionalPadding: Double = 35.00 ) { self.geometry = geometry self.aspectRatio = aspectRatio ?? VideoPlayerView.defaultAspectRatio @@ -19,7 +19,7 @@ struct VideoDetailsPaddingModifier: ViewModifier { self.additionalPadding = additionalPadding } - var usedAspectRatio: CGFloat { + var usedAspectRatio: Double { guard aspectRatio != nil else { return VideoPlayerView.defaultAspectRatio } @@ -27,11 +27,11 @@ struct VideoDetailsPaddingModifier: ViewModifier { return [aspectRatio!, VideoPlayerView.defaultAspectRatio].min()! } - var playerHeight: CGFloat { + var playerHeight: Double { [geometry.size.width / usedAspectRatio, geometry.size.height - minimumHeightLeft].min()! } - var topPadding: CGFloat { + var topPadding: Double { playerHeight + additionalPadding } diff --git a/Shared/Player/VideoPlayerSizeModifier.swift b/Shared/Player/VideoPlayerSizeModifier.swift index ad83c0df..9b5d5682 100644 --- a/Shared/Player/VideoPlayerSizeModifier.swift +++ b/Shared/Player/VideoPlayerSizeModifier.swift @@ -3,8 +3,8 @@ import SwiftUI struct VideoPlayerSizeModifier: ViewModifier { let geometry: GeometryProxy - let aspectRatio: CGFloat? - let minimumHeightLeft: CGFloat + let aspectRatio: Double? + let minimumHeightLeft: Double #if os(iOS) @Environment(\.verticalSizeClass) private var verticalSizeClass @@ -12,8 +12,8 @@ struct VideoPlayerSizeModifier: ViewModifier { init( geometry: GeometryProxy, - aspectRatio: CGFloat? = nil, - minimumHeightLeft: CGFloat? = nil + aspectRatio: Double? = nil, + minimumHeightLeft: Double? = nil ) { self.geometry = geometry self.aspectRatio = aspectRatio ?? VideoPlayerView.defaultAspectRatio @@ -27,7 +27,7 @@ struct VideoPlayerSizeModifier: ViewModifier { .edgesIgnoringSafeArea(edgesIgnoringSafeArea) } - var usedAspectRatio: CGFloat { + var usedAspectRatio: Double { guard aspectRatio != nil else { return VideoPlayerView.defaultAspectRatio } @@ -50,7 +50,7 @@ struct VideoPlayerSizeModifier: ViewModifier { #endif } - var maxHeight: CGFloat { + var maxHeight: Double { #if os(iOS) verticalSizeClass == .regular ? geometry.size.height - minimumHeightLeft : .infinity #else diff --git a/Shared/Player/VideoPlayerView.swift b/Shared/Player/VideoPlayerView.swift index 1b565bff..40474983 100644 --- a/Shared/Player/VideoPlayerView.swift +++ b/Shared/Player/VideoPlayerView.swift @@ -3,8 +3,8 @@ import Siesta import SwiftUI struct VideoPlayerView: View { - static let defaultAspectRatio: CGFloat = 1.77777778 - static var defaultMinimumHeightLeft: CGFloat { + static let defaultAspectRatio: Double = 1.77777778 + static var defaultMinimumHeightLeft: Double { #if os(macOS) 300 #else diff --git a/Shared/Playlists/PlaylistsView.swift b/Shared/Playlists/PlaylistsView.swift index fdca629d..3a847f07 100644 --- a/Shared/Playlists/PlaylistsView.swift +++ b/Shared/Playlists/PlaylistsView.swift @@ -25,7 +25,7 @@ struct PlaylistsView: View { currentPlaylist?.videos ?? [] } - var videosViewMaxHeight: CGFloat { + var videosViewMaxHeight: Double { #if os(tvOS) videos.isEmpty ? 150 : .infinity #else diff --git a/Shared/Videos/VideoView.swift b/Shared/Videos/VideoView.swift index fcc74f9d..09743ccb 100644 --- a/Shared/Videos/VideoView.swift +++ b/Shared/Videos/VideoView.swift @@ -9,6 +9,7 @@ struct VideoView: View { #endif @Environment(\.inNavigationView) private var inNavigationView + @Environment(\.horizontalCells) private var horizontalCells var video: Video var layout: ListingLayout @@ -34,7 +35,7 @@ struct VideoView: View { VStack { if layout == .cells { #if os(iOS) - if verticalSizeClass == .compact { + if verticalSizeClass == .compact, !horizontalCells { horizontalRow .padding(.vertical, 4) } else { @@ -64,14 +65,31 @@ struct VideoView: View { .frame(maxWidth: 320) VStack(alignment: .leading, spacing: 0) { - videoDetail(video.title) + videoDetail(video.title, lineLimit: 5) .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) videoDetail(video.author) - Spacer() + if additionalDetailsAvailable { + Spacer() - additionalDetails + HStack { + if let date = video.publishedDate { + VStack { + Image(systemName: "calendar") + Text(date) + } + } + + if video.views != 0 { + VStack { + Image(systemName: "eye") + Text(video.viewsCount!) + } + } + } + .foregroundColor(.secondary) + } } .padding() .frame(minHeight: 180) @@ -101,11 +119,10 @@ struct VideoView: View { } #endif } - .padding(.trailing) } var verticalRow: some View { - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 0) { thumbnail VStack(alignment: .leading) { @@ -121,7 +138,18 @@ struct VideoView: View { Group { if additionalDetailsAvailable { - additionalDetails + HStack(spacing: 8) { + if let date = video.publishedDate { + Image(systemName: "calendar") + Text(date) + } + + if video.views != 0 { + Image(systemName: "eye") + Text(video.viewsCount!) + } + } + .foregroundColor(.secondary) } else { Spacer() } @@ -129,6 +157,8 @@ struct VideoView: View { .frame(minHeight: 30, alignment: .top) .padding(.bottom, 10) } + .padding(.top, 4) + .frame(minWidth: 0, maxWidth: .infinity, alignment: .topLeading) #if os(tvOS) .padding(.horizontal, 8) #endif @@ -139,21 +169,6 @@ struct VideoView: View { video.publishedDate != nil || video.views != 0 } - var additionalDetails: some View { - HStack(spacing: 8) { - if let date = video.publishedDate { - Image(systemName: "calendar") - Text(date) - } - - if video.views != 0 { - Image(systemName: "eye") - Text(video.viewsCount!) - } - } - .foregroundColor(.secondary) - } - var thumbnail: some View { ZStack(alignment: .leading) { thumbnailImage(quality: .maxresdefault) @@ -184,8 +199,6 @@ struct VideoView: View { .padding(10) } } - .padding([.leading, .top, .trailing], 4) - .frame(maxWidth: 600) } func thumbnailImage(quality: Thumbnail.Quality) -> some View { @@ -196,19 +209,17 @@ struct VideoView: View { .resizable() } placeholder: { ProgressView() - .aspectRatio(contentMode: .fill) } } else { Image(systemName: "exclamationmark.square") } } - .frame(minWidth: 300, maxWidth: .infinity, minHeight: 180, maxHeight: .infinity) .background(.gray) .mask(RoundedRectangle(cornerRadius: 12)) #if os(tvOS) .frame(minHeight: layout == .cells ? 320 : 200) #endif - .aspectRatio(1.777, contentMode: .fit) + .modifier(AspectRatioModifier()) } func videoDetail(_ text: String, lineLimit: Int = 1) -> some View { @@ -218,6 +229,21 @@ struct VideoView: View { .truncationMode(.middle) } + struct AspectRatioModifier: ViewModifier { + @Environment(\.horizontalCells) private var horizontalCells + + func body(content: Content) -> some View { + Group { + if horizontalCells { + content + } else { + content + .aspectRatio(1.777, contentMode: .fill) + } + } + } + } + struct ButtonStyleModifier: ViewModifier { var layout: ListingLayout @@ -236,41 +262,3 @@ struct VideoView: View { } } } - -struct VideoListRowPreview: PreviewProvider { - static var previews: some View { - #if os(tvOS) - List { - ForEach(Video.allFixtures) { video in - VideoView(video: video, layout: .list) - } - } - .listStyle(.grouped) - - HStack { - ForEach(Video.allFixtures) { video in - VideoView(video: video, layout: .cells) - } - } - .frame(maxHeight: 600) - #else - List { - ForEach(Video.allFixtures) { video in - VideoView(video: video, layout: .list) - } - } - #if os(macOS) - .frame(minHeight: 800) - #endif - - #if os(iOS) - List { - ForEach(Video.allFixtures) { video in - VideoView(video: video, layout: .list) - } - } - .previewInterfaceOrientation(.landscapeRight) - #endif - #endif - } -} diff --git a/Shared/Videos/VideosCellsHorizontal.swift b/Shared/Videos/VideosCellsHorizontal.swift new file mode 100644 index 00000000..8e338ed0 --- /dev/null +++ b/Shared/Videos/VideosCellsHorizontal.swift @@ -0,0 +1,61 @@ +import Defaults +import SwiftUI + +struct VideosCellsHorizontal: View { + #if os(iOS) + @Environment(\.verticalSizeClass) private var verticalSizeClass + #endif + + var videos = [Video]() + + var body: some View { + ScrollViewReader { scrollView in + ScrollView(.horizontal, showsIndicators: false) { + LazyHStack(spacing: 20) { + ForEach(videos) { video in + VideoView(video: video, layout: .cells) + .environment(\.horizontalCells, true) + #if os(tvOS) + .frame(width: 580) + .padding(.trailing, 20) + .padding(.bottom, 40) + #else + .frame(maxWidth: 300) + #endif + } + } + #if os(tvOS) + .padding(.horizontal, 40) + .padding(.vertical, 30) + #else + .padding(.horizontal, 15) + .padding(.vertical, 20) + #endif + } + .onChange(of: videos) { [videos] newVideos in + #if !os(tvOS) + guard !videos.isEmpty, let video = newVideos.first else { + return + } + + scrollView.scrollTo(video.id, anchor: .leading) + #endif + } + } + #if os(tvOS) + .frame(height: 560) + #else + .frame(height: 320) + #endif + + .edgesIgnoringSafeArea(.horizontal) + } +} + +struct VideoCellsHorizontal_Previews: PreviewProvider { + static var previews: some View { + VideosCellsHorizontal(videos: Video.allFixtures) + .environmentObject(NavigationState()) + .environmentObject(Subscriptions()) + } +} diff --git a/Shared/Videos/VideosCellsView.swift b/Shared/Videos/VideosCellsVertical.swift similarity index 96% rename from Shared/Videos/VideosCellsView.swift rename to Shared/Videos/VideosCellsVertical.swift index 989a4127..ab406166 100644 --- a/Shared/Videos/VideosCellsView.swift +++ b/Shared/Videos/VideosCellsVertical.swift @@ -1,7 +1,7 @@ import Defaults import SwiftUI -struct VideosCellsView: View { +struct VideosCellsVertical: View { #if os(iOS) @Environment(\.verticalSizeClass) private var verticalSizeClass #endif @@ -49,7 +49,7 @@ struct VideosCellsView: View { [GridItem(.adaptive(minimum: adaptiveGridItemMinimumSize))] } - var adaptiveGridItemMinimumSize: CGFloat { + var adaptiveGridItemMinimumSize: Double { #if os(iOS) return verticalSizeClass == .regular ? 320 : 800 #elseif os(tvOS) diff --git a/Shared/Videos/VideosView.swift b/Shared/Videos/VideosView.swift index 5718ea7f..615b3513 100644 --- a/Shared/Videos/VideosView.swift +++ b/Shared/Videos/VideosView.swift @@ -14,12 +14,12 @@ struct VideosView: View { VStack { #if os(tvOS) if layout == .cells { - VideosCellsView(videos: videos) + VideosCellsVertical(videos: videos) } else { VideosListView(videos: videos) } #else - VideosCellsView(videos: videos) + VideosCellsVertical(videos: videos) #endif } #if os(macOS) diff --git a/Shared/Watch Now/WatchNowPlaylistSection.swift b/Shared/Watch Now/WatchNowPlaylistSection.swift new file mode 100644 index 00000000..7ec48a8d --- /dev/null +++ b/Shared/Watch Now/WatchNowPlaylistSection.swift @@ -0,0 +1,25 @@ +import Siesta +import SwiftUI + +struct WatchNowPlaylistSection: View { + @ObservedObject private var store = Store() + + let id: String + + var resource: Resource { + InvidiousAPI.shared.playlist(id) + } + + init(id: String) { + self.id = id + + resource.addObserver(store) + } + + var body: some View { + WatchNowSectionBody(label: store.item?.title ?? "Loading", videos: store.item?.videos ?? []) + .onAppear { + resource.loadIfNeeded() + } + } +} diff --git a/Shared/Watch Now/WatchNowSection.swift b/Shared/Watch Now/WatchNowSection.swift new file mode 100644 index 00000000..99246422 --- /dev/null +++ b/Shared/Watch Now/WatchNowSection.swift @@ -0,0 +1,23 @@ +import Siesta +import SwiftUI + +struct WatchNowSection: View { + @ObservedObject private var store = Store<[Video]>() + + let resource: Resource + let label: String + + init(resource: Resource, label: String) { + self.resource = resource + self.label = label + + self.resource.addObserver(store) + } + + var body: some View { + WatchNowSectionBody(label: label, videos: store.collection) + .onAppear { + resource.loadIfNeeded() + } + } +} diff --git a/Shared/Watch Now/WatchNowSectionBody.swift b/Shared/Watch Now/WatchNowSectionBody.swift new file mode 100644 index 00000000..212ce4da --- /dev/null +++ b/Shared/Watch Now/WatchNowSectionBody.swift @@ -0,0 +1,21 @@ +import SwiftUI + +struct WatchNowSectionBody: View { + let label: String + let videos: [Video] + + var body: some View { + VStack(alignment: .leading, spacing: 2) { + Text(label) + .font(.title3.bold()) + .foregroundColor(.secondary) + #if os(tvOS) + .padding(.leading, 40) + #else + .padding(.leading, 15) + #endif + + VideosCellsHorizontal(videos: videos) + } + } +} diff --git a/Shared/Watch Now/WatchNowView.swift b/Shared/Watch Now/WatchNowView.swift new file mode 100644 index 00000000..b9f2f2dd --- /dev/null +++ b/Shared/Watch Now/WatchNowView.swift @@ -0,0 +1,38 @@ +import Siesta +import SwiftUI + +struct WatchNowView: View { + var body: some View { + ScrollView(.vertical, showsIndicators: false) { + VStack(alignment: .leading, spacing: 0) { + WatchNowSection(resource: InvidiousAPI.shared.feed, label: "Subscriptions") + WatchNowSection(resource: InvidiousAPI.shared.popular, label: "Popular") + WatchNowSection(resource: InvidiousAPI.shared.trending(category: .default, country: .pl), label: "Trending") + WatchNowSection(resource: InvidiousAPI.shared.trending(category: .movies, country: .pl), label: "Movies") + WatchNowSection(resource: InvidiousAPI.shared.trending(category: .music, country: .pl), label: "Music") + +// TODO: adding sections to view +// =================== +// WatchNowPlaylistSection(id: "IVPLmRFYLGYZpq61SpujNw3EKbzzGNvoDmH") +// WatchNowSection(resource: InvidiousAPI.shared.channelVideos("UCBJycsmduvYEL83R_U4JriQ"), label: "MKBHD") + } + } + #if os(tvOS) + .edgesIgnoringSafeArea(.horizontal) + #else + .navigationTitle("Watch Now") + #endif + #if os(macOS) + .background() + .frame(minWidth: 360) + #endif + } +} + +struct WatchNowView_Previews: PreviewProvider { + static var previews: some View { + WatchNowView() + .environmentObject(Subscriptions()) + .environmentObject(NavigationState()) + } +} diff --git a/tvOS/TVNavigationView.swift b/tvOS/TVNavigationView.swift index e7cc6646..217e0d09 100644 --- a/tvOS/TVNavigationView.swift +++ b/tvOS/TVNavigationView.swift @@ -12,6 +12,10 @@ struct TVNavigationView: View { var body: some View { TabView(selection: $navigationState.tabSelection) { + WatchNowView() + .tabItem { Text("Watch Now") } + .tag(TabSelection.watchNow) + SubscriptionsView() .tabItem { Text("Subscriptions") } .tag(TabSelection.subscriptions)