diff --git a/Model/NavigationModel.swift b/Model/NavigationModel.swift index 7ba899ec..dfa6fde8 100644 --- a/Model/NavigationModel.swift +++ b/Model/NavigationModel.swift @@ -84,6 +84,9 @@ final class NavigationModel: ObservableObject { @Published var presentingAccounts = false @Published var presentingWelcomeScreen = false + @Published var presentingChannelSheet = false + @Published var channelPresentedInSheet: Channel! + @Published var presentingShareSheet = false @Published var shareURL: URL? @@ -103,7 +106,6 @@ final class NavigationModel: ObservableObject { hideKeyboard() let presentingPlayer = player.presentingPlayer - player.hide() presentingChannel = false #if os(macOS) @@ -113,20 +115,30 @@ final class NavigationModel: ObservableObject { let recent = RecentItem(from: channel) recents.add(RecentItem(from: channel)) - if navigationStyle == .sidebar { - sidebarSectionChanged.toggle() - tabSelection = .recentlyOpened(recent.tag) - } else { - var delay = 0.0 + let navigateToChannel = { #if os(iOS) - if presentingPlayer { delay = 1.0 } + self.player.hide() #endif - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + + if navigationStyle == .sidebar { + self.sidebarSectionChanged.toggle() + self.tabSelection = .recentlyOpened(recent.tag) + } else { withAnimation(Constants.overlayAnimation) { self.presentingChannel = true } } } + + #if os(iOS) + if presentingPlayer { + presentChannelInSheet(channel) + } else { + navigateToChannel() + } + #else + navigateToChannel() + #endif } func openChannelPlaylist(_ playlist: ChannelPlaylist, navigationStyle: NavigationStyle) { @@ -273,6 +285,11 @@ final class NavigationModel: ObservableObject { shareURL = url presentingShareSheet = true } + + func presentChannelInSheet(_ channel: Channel) { + channelPresentedInSheet = channel + presentingChannelSheet = true + } } typealias TabSelection = NavigationModel.TabSelection diff --git a/Model/OpenVideosModel.swift b/Model/OpenVideosModel.swift index b546b07d..a1ac400d 100644 --- a/Model/OpenVideosModel.swift +++ b/Model/OpenVideosModel.swift @@ -108,6 +108,7 @@ struct OpenVideosModel { ) WatchNextViewModel.shared.hide() + NavigationModel.shared.presentingChannelSheet = false if playbackMode == .playNow || playbackMode == .shuffleAll { #if os(iOS) diff --git a/Model/Player/PlayerModel.swift b/Model/Player/PlayerModel.swift index cbbd1c01..0a0c2a7b 100644 --- a/Model/Player/PlayerModel.swift +++ b/Model/Player/PlayerModel.swift @@ -335,6 +335,7 @@ final class PlayerModel: ObservableObject { videoBeingOpened = video WatchNextViewModel.shared.hide() + navigation.presentingChannelSheet = false var changeBackendHandler: (() -> Void)? diff --git a/Model/Player/PlayerQueue.swift b/Model/Player/PlayerQueue.swift index 7aca5818..7ff5d8d1 100644 --- a/Model/Player/PlayerQueue.swift +++ b/Model/Player/PlayerQueue.swift @@ -15,6 +15,8 @@ extension PlayerModel { func play(_ videos: [Video], shuffling: Bool = false) { WatchNextViewModel.shared.hide() + navigation.presentingChannelSheet = false + playbackMode = shuffling ? .shuffle : .queue videos.forEach { enqueueVideo($0, loadDetails: false) } @@ -33,6 +35,8 @@ extension PlayerModel { } func playNow(_ video: Video, at time: CMTime? = nil) { + navigation.presentingChannelSheet = false + if playingInPictureInPicture, closePiPOnNavigation { closePiP() } @@ -56,6 +60,7 @@ extension PlayerModel { comments.reset() stream = nil WatchNextViewModel.shared.hide() + navigation.presentingChannelSheet = false withAnimation { aspectRatio = VideoPlayerView.defaultAspectRatio @@ -176,6 +181,7 @@ extension PlayerModel { remove(newItem) WatchNextViewModel.shared.hide() + navigation.presentingChannelSheet = false currentItem = newItem currentItem.playbackTime = time @@ -219,9 +225,12 @@ extension PlayerModel { let item = PlayerQueueItem(video, playbackTime: atTime) if play { + navigation.presentingChannelSheet = false + withAnimation { aspectRatio = VideoPlayerView.defaultAspectRatio WatchNextViewModel.shared.hide() + navigation.presentingChannelSheet = false currentItem = item } videoBeingOpened = video diff --git a/Shared/Channels/ChannelVideosView.swift b/Shared/Channels/ChannelVideosView.swift index 6c782dbc..7a673f8f 100644 --- a/Shared/Channels/ChannelVideosView.swift +++ b/Shared/Channels/ChannelVideosView.swift @@ -6,6 +6,7 @@ import SwiftUI struct ChannelVideosView: View { var channel: Channel? var showCloseButton = false + var inNavigationView = true @State private var presentingShareSheet = false @State private var shareURL: URL? @@ -119,24 +120,28 @@ struct ChannelVideosView: View { Button { withAnimation(Constants.overlayAnimation) { navigation.presentingChannel = false + navigation.presentingChannelSheet = false } } label: { Label("Close", systemImage: "xmark") } + #if !os(macOS) .buttonStyle(.plain) + #endif } } - #if !os(iOS) + #if os(macOS) ToolbarItem(placement: .navigation) { thumbnail } - ToolbarItem { + ToolbarItemGroup { + if !inNavigationView { + Text(navigationTitle) + .fontWeight(.bold) + } + ListingStyleButtons(listingStyle: $channelPlaylistListingStyle) - } - ToolbarItem { HideShortsButtons(hide: $hideShorts) - } - ToolbarItem { contentTypePicker } @@ -160,10 +165,12 @@ struct ChannelVideosView: View { ToolbarItem { favoriteButton + .labelStyle(.iconOnly) } ToolbarItem { toggleWatchedButton + .labelStyle(.iconOnly) } #endif } @@ -234,14 +241,14 @@ struct ChannelVideosView: View { Group { if let subscribers = store.item?.channel?.subscriptionsString { HStack(spacing: 0) { - Text(subscribers) Image(systemName: "person.2.fill") + Text(subscribers) } } else if store.item.isNil { HStack(spacing: 0) { + Image(systemName: "person.2.fill") Text("1234") .redacted(reason: .placeholder) - Image(systemName: "person.2.fill") } } } @@ -252,10 +259,10 @@ struct ChannelVideosView: View { var viewsLabel: some View { HStack(spacing: 0) { if let views = store.item?.channel?.totalViewsString { - Text(views) - Image(systemName: "eye.fill") .imageScale(.small) + + Text(views) } } .foregroundColor(.secondary) @@ -328,6 +335,7 @@ struct ChannelVideosView: View { } } } + .labelsHidden() } private func typeAvailable(_ type: Channel.ContentType) -> Bool { @@ -463,7 +471,7 @@ struct ChannelVideosView: View { struct ChannelVideosView_Previews: PreviewProvider { static var previews: some View { #if os(macOS) - ChannelVideosView(channel: Video.fixture.channel) + ChannelVideosView(channel: Video.fixture.channel, showCloseButton: true, inNavigationView: false) .environment(\.navigationStyle, .sidebar) #else NavigationView { diff --git a/Shared/Defaults.swift b/Shared/Defaults.swift index 5c363ab5..fe3011fd 100644 --- a/Shared/Defaults.swift +++ b/Shared/Defaults.swift @@ -241,6 +241,7 @@ extension Defaults.Keys { static let openWatchNextOnFinishedWatchingDelay = Key("openWatchNextOnFinishedWatchingDelay", default: "5") static let hideShorts = Key("hideShorts", default: false) + static let showInspector = Key("showInspector", default: .onlyLocal) } enum ResolutionSetting: String, CaseIterable, Defaults.Serializable { diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index 60535987..91b0bd15 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -120,6 +120,15 @@ struct ContentView: View { OpenVideosView() } ) + #if !os(macOS) + .background( + EmptyView().sheet(isPresented: $navigation.presentingChannelSheet) { + NavigationView { + ChannelVideosView(channel: navigation.channelPresentedInSheet, showCloseButton: true) + } + } + ) + #endif .alert(isPresented: $navigation.presentingAlert) { navigation.alert } } diff --git a/Shared/Player/Controls/VideoDetailsOverlay.swift b/Shared/Player/Controls/VideoDetailsOverlay.swift index 972869bb..da3b97bd 100644 --- a/Shared/Player/Controls/VideoDetailsOverlay.swift +++ b/Shared/Player/Controls/VideoDetailsOverlay.swift @@ -5,7 +5,7 @@ struct VideoDetailsOverlay: View { @ObservedObject private var controls = PlayerControlsModel.shared var body: some View { - VideoDetails(video: controls.player.videoForDisplay, fullScreen: fullScreenBinding) + VideoDetails(video: controls.player.videoForDisplay, fullScreen: fullScreenBinding, sidebarQueue: .constant(false)) .clipShape(RoundedRectangle(cornerRadius: 4)) } diff --git a/Shared/Player/RelatedView.swift b/Shared/Player/RelatedView.swift index 175bab28..1e411e38 100644 --- a/Shared/Player/RelatedView.swift +++ b/Shared/Player/RelatedView.swift @@ -5,9 +5,9 @@ struct RelatedView: View { @ObservedObject private var player = PlayerModel.shared var body: some View { - List { - if let related = player.currentVideo?.related { - Section(header: Text("Related")) { + LazyVStack { + if let related = player.videoForDisplay?.related { + Section(header: header) { ForEach(related) { video in PlayerQueueRow(item: PlayerQueueItem(video)) .listRowBackground(Color.clear) @@ -34,6 +34,15 @@ struct RelatedView: View { .listStyle(.plain) #endif } + + var header: some View { + Text("Related") + #if !os(macOS) + .font(.caption) + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, alignment: .leading) + #endif + } } struct RelatedView_Previews: PreviewProvider { diff --git a/Shared/Player/Video Details/CommentsView.swift b/Shared/Player/Video Details/CommentsView.swift index 6af4d8ce..19986443 100644 --- a/Shared/Player/Video Details/CommentsView.swift +++ b/Shared/Player/Video Details/CommentsView.swift @@ -25,7 +25,6 @@ struct CommentsView: View { .borderBottom(height: comment != last ? 0.5 : 0, color: Color("ControlsBorderColor")) } } - .padding(.top, 55) if embedInScrollView { ScrollView(.vertical, showsIndicators: false) { diff --git a/Shared/Player/Video Details/InspectorView.swift b/Shared/Player/Video Details/InspectorView.swift index f22a02a6..227be4ee 100644 --- a/Shared/Player/Video Details/InspectorView.swift +++ b/Shared/Player/Video Details/InspectorView.swift @@ -6,7 +6,7 @@ struct InspectorView: View { @ObservedObject private var player = PlayerModel.shared var body: some View { - ScrollView { + Section(header: header) { VStack(alignment: .leading, spacing: 12) { if let video { VStack(spacing: 4) { @@ -53,10 +53,14 @@ struct InspectorView: View { NoCommentsView(text: "Not playing", systemImage: "stop.circle.fill") } } - .padding(.top, 60) - .padding(.bottom, 50) } - .padding(.horizontal) + } + + var header: some View { + Text("Inspector") + .font(.caption) + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, alignment: .leading) } @ViewBuilder func videoDetailGroupHeading(_ heading: String, image systemName: String? = nil) -> some View { diff --git a/Shared/Player/Video Details/PlayerQueueView.swift b/Shared/Player/Video Details/PlayerQueueView.swift index b6115190..057f7d5d 100644 --- a/Shared/Player/Video Details/PlayerQueueView.swift +++ b/Shared/Player/Video Details/PlayerQueueView.swift @@ -13,7 +13,7 @@ struct PlayerQueueView: View { @Default(.saveHistory) private var saveHistory var body: some View { - List { + Group { Group { if player.playbackMode == .related { autoplaying @@ -34,15 +34,6 @@ struct PlayerQueueView: View { .listRowSeparator(false) } .environment(\.inNavigationView, false) - #if os(macOS) - .listStyle(.inset) - #elseif os(iOS) - .listStyle(.grouped) - .backport - .scrollContentBackground(false) - #else - .listStyle(.plain) - #endif } @ViewBuilder var autoplaying: some View { @@ -65,6 +56,8 @@ struct PlayerQueueView: View { var autoplayingHeader: some View { HStack { Text("Autoplaying Next") + .foregroundColor(.secondary) + .font(.caption) Spacer() Button { player.setRelatedAutoplayItem() @@ -78,7 +71,7 @@ struct PlayerQueueView: View { } var playingNext: some View { - Section(header: Text("Queue")) { + Section(header: queueHeader) { if player.queue.isEmpty { Text("Queue is empty") .foregroundColor(.secondary) @@ -96,6 +89,15 @@ struct PlayerQueueView: View { } } + var queueHeader: some View { + Text("Queue".localized()) + #if !os(macOS) + .foregroundColor(.secondary) + .font(.caption) + .frame(maxWidth: .infinity, alignment: .leading) + #endif + } + private var visibleWatches: [Watch] { watches.filter { $0.videoID != player.currentVideo?.videoID } } diff --git a/Shared/Player/Video Details/VideoDetails.swift b/Shared/Player/Video Details/VideoDetails.swift index f30bb54d..f1e5818f 100644 --- a/Shared/Player/Video Details/VideoDetails.swift +++ b/Shared/Player/Video Details/VideoDetails.swift @@ -4,21 +4,146 @@ import SDWebImageSwiftUI import SwiftUI struct VideoDetails: View { - enum DetailsPage: String, CaseIterable, Defaults.Serializable { - case info, comments, chapters, inspector + struct TitleView: View { + @ObservedObject private var model = PlayerModel.shared + @State private var titleSize = CGSize.zero - var systemImageName: String { - switch self { - case .info: - return "info.circle" - case .inspector: - return "wand.and.stars" - case .comments: - return "text.bubble" - case .chapters: - return "bookmark" + var video: Video? { model.videoForDisplay } + + var body: some View { + HStack(spacing: 0) { + Text(model.videoForDisplay?.displayTitle ?? "Not playing") + .font(.title3.bold()) + .lineLimit(4) + } + .padding(.vertical, 4) + } + } + + struct ChannelView: View { + @ObservedObject private var model = PlayerModel.shared + + var video: Video? { model.videoForDisplay } + + var body: some View { + HStack { + Button { + guard let channel = video?.channel else { return } + NavigationModel.shared.openChannel(channel, navigationStyle: .sidebar) + } label: { + ChannelAvatarView( + channel: video?.channel, + video: video + ) + .frame(maxWidth: 40, maxHeight: 40) + .padding(.trailing, 5) + } + .buttonStyle(.plain) + + VStack(alignment: .leading, spacing: 2) { + HStack { + Text(model.videoForDisplay?.channel.name ?? "Yattee") + .font(.subheadline) + .fontWeight(.semibold) + .lineLimit(1) + + if let video, !video.isLocal { + Group { + Text("•") + + HStack(spacing: 2) { + Image(systemName: "person.2.fill") + + if let channel = model.videoForDisplay?.channel { + if let subscriptions = channel.subscriptionsString { + Text(subscriptions) + } else { + Text("1234").redacted(reason: .placeholder) + } + } + } + } + .font(.caption2) + } + } + .foregroundColor(.secondary) + + if video != nil { + VideoMetadataView() + } + } } } + } + + struct VideoMetadataView: View { + @ObservedObject private var model = PlayerModel.shared + @Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike + + var video: Video? { model.videoForDisplay } + + var body: some View { + HStack(spacing: 4) { + publishedDateSection + + Text("•") + + HStack(spacing: 4) { + if model.videoBeingOpened != nil || video?.viewsCount != nil { + Image(systemName: "eye") + } + + if let views = video?.viewsCount { + Text(views) + } else if model.videoBeingOpened != nil { + Text("1,234M").redacted(reason: .placeholder) + } + + if model.videoBeingOpened != nil || video?.likesCount != nil { + Image(systemName: "hand.thumbsup") + } + + if let likes = video?.likesCount { + Text(likes) + } else if model.videoBeingOpened == nil { + Text("1,234M").redacted(reason: .placeholder) + } + + if enableReturnYouTubeDislike { + if model.videoBeingOpened != nil || video?.dislikesCount != nil { + Image(systemName: "hand.thumbsdown") + } + + if let dislikes = video?.dislikesCount { + Text(dislikes) + } else if model.videoBeingOpened == nil { + Text("1,234M").redacted(reason: .placeholder) + } + } + } + } + .font(.caption2) + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, alignment: .leading) + } + + var publishedDateSection: some View { + Group { + if let video { + HStack(spacing: 4) { + if let published = video.publishedDate { + Text(published) + } else { + Text("1 century ago").redacted(reason: .placeholder) + } + } + } + } + } + } + + enum DetailsPage: String, CaseIterable, Defaults.Serializable { + case info, comments, queue var title: String { rawValue.capitalized.localized() @@ -28,7 +153,7 @@ struct VideoDetails: View { var video: Video? @Binding var fullScreen: Bool - var bottomPadding = false + @Binding var sidebarQueue: Bool @State private var detailsSize = CGSize.zero @State private var detailsVisibility = Constants.detailsVisibility @@ -49,22 +174,40 @@ struct VideoDetails: View { @Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike @Default(.playerSidebar) private var playerSidebar + @Default(.showInspector) private var showInspector var body: some View { VStack(alignment: .leading, spacing: 0) { - ControlsBar( - fullScreen: $fullScreen, - expansionState: .constant(.full), - presentingControls: false, - backgroundEnabled: false, - borderTop: false, - detailsTogglePlayer: false, - detailsToggleFullScreen: true - ) - .animation(nil, value: player.currentItem) + VStack(alignment: .leading, spacing: 0) { + TitleView() + if video != nil, !video!.isLocal { + ChannelView() + .layoutPriority(1) + .padding(.bottom, 6) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) + .padding(.horizontal, 16) + #if !os(tvOS) + .tapRecognizer( + tapSensitivity: 0.2, + doubleTapAction: { + withAnimation(.default) { + fullScreen.toggle() + } + } + ) + #endif VideoActions(video: player.videoForDisplay) + .padding(.vertical, 5) + .frame(maxHeight: 50) + .frame(maxWidth: .infinity) + .borderTop(height: 0.5, color: Color("ControlsBorderColor")) + .borderBottom(height: 0.5, color: Color("ControlsBorderColor")) .animation(nil, value: player.currentItem) + .frame(minWidth: 0, maxWidth: .infinity) pageView #if os(iOS) @@ -100,210 +243,112 @@ struct VideoDetails: View { } @ViewBuilder var pageMenu: some View { - #if os(macOS) - pagePicker - .labelsHidden() - .offset(x: 15, y: 15) - .frame(maxWidth: 200) - #elseif os(iOS) - Menu { - pagePicker - } label: { - HStack { - Label(page.title, systemImage: page.systemImageName) - Image(systemName: "chevron.up.chevron.down") - .imageScale(.small) - } - .padding(10) - .fixedSize(horizontal: true, vertical: false) - .modifier(ControlBackgroundModifier()) - .clipShape(RoundedRectangle(cornerRadius: 6)) - .frame(width: 200, alignment: .leading) - .transaction { t in t.animation = nil } - } - .animation(nil, value: detailsVisibility) - .modifier(SettingsPickerModifier()) - .offset(x: 15, y: 5) - #endif - } - - var pagePicker: some View { Picker("Page", selection: $page) { ForEach(DetailsPage.allCases.filter { pageAvailable($0) }, id: \.rawValue) { page in - Label(page.title, systemImage: page.systemImageName).tag(page) + Text(page.title).tag(page) } } + .pickerStyle(.segmented) + .labelsHidden() } func pageAvailable(_ page: DetailsPage) -> Bool { guard let video else { return false } switch page { - case .inspector: - return true + case .queue: + return !player.queue.isEmpty default: return !video.isLocal } } var pageView: some View { - ZStack(alignment: .topLeading) { - switch page { - case .info: - ScrollView(.vertical, showsIndicators: false) { - if let video { - VStack(alignment: .leading, spacing: 10) { - HStack { - videoProperties - .frame(maxWidth: .infinity, alignment: .trailing) - } - .padding(.bottom, 12) + ScrollViewReader { proxy in + ScrollView(.vertical, showsIndicators: false) { + LazyVStack { + pageMenu + .id("top") + .padding(5) - if !player.videoBeingOpened.isNil && (video.description.isNil || video.description!.isEmpty) { - VStack { - ProgressView() - .progressViewStyle(.circular) + switch page { + case .info: + Group { + if let video { + VStack(alignment: .leading, spacing: 10) { + if !player.videoBeingOpened.isNil && (video.description.isNil || video.description!.isEmpty) { + VStack { + ProgressView() + .progressViewStyle(.circular) + } + .frame(maxWidth: .infinity) + } else if let description = video.description, !description.isEmpty { + VideoDescription(video: video, detailsSize: detailsSize) + } else if !video.isLocal { + Text("No description") + .font(.caption) + .foregroundColor(.secondary) + } + + if video.isLocal || showInspector == .always { + InspectorView(video: player.videoForDisplay) + } + + if !sidebarQueue, + !(player.videoForDisplay?.related.isEmpty ?? true) + { + RelatedView() + .padding(.top, 20) + } } - .frame(maxWidth: .infinity) - } else if video.description != nil, !video.description!.isEmpty { - VideoDescription(video: video, detailsSize: detailsSize) - #if os(iOS) - .padding(.bottom, player.playingFullScreen ? 10 : SafeArea.insets.bottom) - #endif - } else if !video.isLocal { - Text("No description") - .font(.caption) - .foregroundColor(.secondary) + .padding(.bottom, 60) } } - .padding(.top, 18) - .padding(.bottom, 60) - } - } - .onAppear { - if video != nil, !pageAvailable(page) { - page = .inspector - } - } - #if os(iOS) - .onAppear { - if fullScreen { - if let video, video.isLocal { - page = .inspector + .onChange(of: player.currentVideo?.cacheKey) { _ in + proxy.scrollTo("top") + page = .info } - detailsVisibility = true - return + .onAppear { + if video != nil, !pageAvailable(page) { + page = .info + } + } + .transition(.opacity) + .animation(nil, value: player.currentItem) + .padding(.horizontal) + #if os(iOS) + .frame(maxWidth: YatteeApp.isForPreviews ? .infinity : maxWidth) + #endif + + case .queue: + PlayerQueueView(sidebarQueue: false) + .padding(.horizontal) + + case .comments: + CommentsView(embedInScrollView: false) + .onAppear { + comments.loadIfNeeded() + } } - Delay.by(0.4) { withAnimation(.easeIn(duration: 0.25)) { self.detailsVisibility = true } } } - #endif - .transition(.opacity) - .animation(nil, value: player.currentItem) - .padding(.horizontal) - #if os(iOS) - .frame(maxWidth: YatteeApp.isForPreviews ? .infinity : maxWidth) - #endif - - case .inspector: - InspectorView(video: video) - - case .chapters: - ChaptersView() - - case .comments: - CommentsView(embedInScrollView: true) - .onAppear { - comments.loadIfNeeded() - } } - - pageMenu - .font(.headline) - .foregroundColor(.accentColor) - .zIndex(1) - - #if !os(tvOS) - if #available(iOS 16, macOS 13, *) { - Rectangle() - .fill( - LinearGradient( - gradient: .init(colors: [fadePlaceholderStartColor, .clear]), - startPoint: .top, - endPoint: .bottom - ) - ) - .zIndex(0) - .frame(maxHeight: 22) - } - #endif } - } - - var fadePlaceholderStartColor: Color { - #if os(macOS) - .secondaryBackground - #elseif os(iOS) - .background - #else - .clear + #if os(iOS) + .onAppear { + if fullScreen { + if let video, video.isLocal { + page = .info + } + detailsVisibility = true + return + } + Delay.by(0.8) { withAnimation(.easeIn(duration: 0.25)) { self.detailsVisibility = true } } + } #endif - } - @ViewBuilder var videoProperties: some View { - HStack(spacing: 4) { - Spacer() - publishedDateSection - - Text("•") - - HStack(spacing: 4) { - if player.videoBeingOpened != nil || video?.viewsCount != nil { - Image(systemName: "eye") - } - - if let views = video?.viewsCount { - Text(views) - } else if player.videoBeingOpened != nil { - Text("1,234M").redacted(reason: .placeholder) - } - - if player.videoBeingOpened != nil || video?.likesCount != nil { - Image(systemName: "hand.thumbsup") - } - - if let likes = video?.likesCount { - Text(likes) - } else if player.videoBeingOpened == nil { - Text("1,234M").redacted(reason: .placeholder) - } - - if enableReturnYouTubeDislike { - if player.videoBeingOpened != nil || video?.dislikesCount != nil { - Image(systemName: "hand.thumbsdown") - } - - if let dislikes = video?.dislikesCount { - Text(dislikes) - } else if player.videoBeingOpened == nil { - Text("1,234M").redacted(reason: .placeholder) - } - } - } - } - .font(.caption) - .foregroundColor(.secondary) - } - - var publishedDateSection: some View { - Group { - if let video { - HStack(spacing: 4) { - if let published = video.publishedDate { - Text(published) - } else { - Text("1 century ago").redacted(reason: .placeholder) - } - } + .onChange(of: player.queue) { _ in + if video != nil, !pageAvailable(page) { + page = .info } } } @@ -311,6 +356,6 @@ struct VideoDetails: View { struct VideoDetails_Previews: PreviewProvider { static var previews: some View { - VideoDetails(video: .fixture, fullScreen: .constant(false)) + VideoDetails(video: .fixture, fullScreen: .constant(false), sidebarQueue: .constant(false)) } } diff --git a/Shared/Player/VideoPlayerView.swift b/Shared/Player/VideoPlayerView.swift index 3abf134e..ca4b76e2 100644 --- a/Shared/Player/VideoPlayerView.swift +++ b/Shared/Player/VideoPlayerView.swift @@ -92,6 +92,14 @@ struct VideoPlayerView: View { .onChange(of: playerSidebar) { _ in updateSidebarQueue() } + #if os(macOS) + .background( + EmptyView().sheet(isPresented: $navigation.presentingChannelSheet) { + ChannelVideosView(channel: navigation.channelPresentedInSheet, showCloseButton: true, inNavigationView: false) + .frame(minWidth: 1000, minHeight: 700) + } + ) + #endif } var videoPlayer: some View { @@ -323,7 +331,7 @@ struct VideoPlayerView: View { VideoDetails( video: player.videoForDisplay, fullScreen: $fullScreenDetails, - bottomPadding: detailsNeedBottomPadding + sidebarQueue: $sidebarQueue ) #if os(iOS) .ignoresSafeArea(.all, edges: .bottom) @@ -386,16 +394,29 @@ struct VideoPlayerView: View { if !fullScreenPlayer { #if os(iOS) if sidebarQueue { - PlayerQueueView(sidebarQueue: true) - .frame(maxWidth: 350) - .background(colorScheme == .dark ? Color.black : Color.white) - .transition(.move(edge: .bottom)) + List { + PlayerQueueView(sidebarQueue: true) + } + #if os(macOS) + .listStyle(.inset) + #elseif os(iOS) + .listStyle(.grouped) + .backport + .scrollContentBackground(false) + #else + .listStyle(.plain) + #endif + .frame(maxWidth: 350) + .background(colorScheme == .dark ? Color.black : Color.white) + .transition(.move(edge: .bottom)) } #elseif os(macOS) if Defaults[.playerSidebar] != .never { - PlayerQueueView(sidebarQueue: true) - .frame(width: 350) - .background(colorScheme == .dark ? Color.black : Color.white) + List { + PlayerQueueView(sidebarQueue: true) + } + .frame(maxWidth: 350) + .background(colorScheme == .dark ? Color.black : Color.white) } #endif } @@ -415,14 +436,6 @@ struct VideoPlayerView: View { #endif } - var detailsNeedBottomPadding: Bool { - #if os(iOS) - return true - #else - return false - #endif - } - var fullScreenPlayer: Bool { #if os(iOS) player.playingFullScreen || verticalSizeClass == .compact diff --git a/Shared/Settings/PlayerSettings.swift b/Shared/Settings/PlayerSettings.swift index bb4ccc48..f0e508c7 100644 --- a/Shared/Settings/PlayerSettings.swift +++ b/Shared/Settings/PlayerSettings.swift @@ -27,6 +27,7 @@ struct PlayerSettings: View { @Default(.openWatchNextOnClose) private var openWatchNextOnClose @Default(.openWatchNextOnFinishedWatching) private var openWatchNextOnFinishedWatching @Default(.openWatchNextOnFinishedWatchingDelay) private var openWatchNextOnFinishedWatchingDelay + @Default(.showInspector) private var showInspector @ObservedObject private var accounts = AccountsModel.shared @@ -68,6 +69,12 @@ struct PlayerSettings: View { #endif } + #if !os(tvOS) + Section(header: SettingsHeader(text: "Inspector".localized())) { + inspectorVisibilityPicker + } + #endif + Section(header: SettingsHeader(text: "Watch Next")) { openWatchNextOnFinishedWatchingToggle openWatchNextOnFinishedWatchingDelayTextField @@ -235,6 +242,14 @@ struct PlayerSettings: View { Toggle("Close PiP and open player when application enters foreground", isOn: $closePiPAndOpenPlayerOnEnteringForeground) } #endif + + private var inspectorVisibilityPicker: some View { + Picker("Visibility", selection: $showInspector) { + Text("Always").tag(ShowInspectorSetting.always) + Text("Only for local files and URLs").tag(ShowInspectorSetting.onlyLocal) + } + .labelsHidden() + } } struct PlayerSettings_Previews: PreviewProvider {